rustc_passes/
naked_functions.rs

1//! Checks validity of naked functions.
2
3use rustc_abi::ExternAbi;
4use rustc_hir as hir;
5use rustc_hir::def::DefKind;
6use rustc_hir::def_id::{LocalDefId, LocalModDefId};
7use rustc_hir::intravisit::Visitor;
8use rustc_hir::{ExprKind, HirIdSet, StmtKind};
9use rustc_middle::hir::nested_filter::OnlyBodies;
10use rustc_middle::query::Providers;
11use rustc_middle::span_bug;
12use rustc_middle::ty::TyCtxt;
13use rustc_session::lint::builtin::UNDEFINED_NAKED_FUNCTION_ABI;
14use rustc_span::{Span, sym};
15
16use crate::errors::{
17    NakedAsmOutsideNakedFn, NakedFunctionsAsmBlock, NakedFunctionsMustNakedAsm, NoPatterns,
18    ParamsNotAllowed, UndefinedNakedFunctionAbi,
19};
20
21pub(crate) fn provide(providers: &mut Providers) {
22    *providers = Providers { check_mod_naked_functions, ..*providers };
23}
24
25fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
26    let items = tcx.hir_module_items(module_def_id);
27    for def_id in items.definitions() {
28        if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
29            continue;
30        }
31
32        let (fn_header, body_id) = match tcx.hir_node_by_def_id(def_id) {
33            hir::Node::Item(hir::Item {
34                kind: hir::ItemKind::Fn { sig, body: body_id, .. },
35                ..
36            })
37            | hir::Node::TraitItem(hir::TraitItem {
38                kind: hir::TraitItemKind::Fn(sig, hir::TraitFn::Provided(body_id)),
39                ..
40            })
41            | hir::Node::ImplItem(hir::ImplItem {
42                kind: hir::ImplItemKind::Fn(sig, body_id),
43                ..
44            }) => (sig.header, *body_id),
45            _ => continue,
46        };
47
48        let body = tcx.hir_body(body_id);
49
50        if tcx.has_attr(def_id, sym::naked) {
51            check_abi(tcx, def_id, fn_header.abi);
52            check_no_patterns(tcx, body.params);
53            check_no_parameters_use(tcx, body);
54            check_asm(tcx, def_id, body);
55        } else {
56            // `naked_asm!` is not allowed outside of functions marked as `#[naked]`
57            let mut visitor = CheckNakedAsmInNakedFn { tcx };
58            visitor.visit_body(body);
59        }
60    }
61}
62
63/// Checks that function uses non-Rust ABI.
64fn check_abi(tcx: TyCtxt<'_>, def_id: LocalDefId, abi: ExternAbi) {
65    if abi == ExternAbi::Rust {
66        let hir_id = tcx.local_def_id_to_hir_id(def_id);
67        let span = tcx.def_span(def_id);
68        tcx.emit_node_span_lint(
69            UNDEFINED_NAKED_FUNCTION_ABI,
70            hir_id,
71            span,
72            UndefinedNakedFunctionAbi,
73        );
74    }
75}
76
77/// Checks that parameters don't use patterns. Mirrors the checks for function declarations.
78fn check_no_patterns(tcx: TyCtxt<'_>, params: &[hir::Param<'_>]) {
79    for param in params {
80        match param.pat.kind {
81            hir::PatKind::Wild | hir::PatKind::Binding(hir::BindingMode::NONE, _, _, None) => {}
82            _ => {
83                tcx.dcx().emit_err(NoPatterns { span: param.pat.span });
84            }
85        }
86    }
87}
88
89/// Checks that function parameters aren't used in the function body.
90fn check_no_parameters_use<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>) {
91    let mut params = HirIdSet::default();
92    for param in body.params {
93        param.pat.each_binding(|_binding_mode, hir_id, _span, _ident| {
94            params.insert(hir_id);
95        });
96    }
97    CheckParameters { tcx, params }.visit_body(body);
98}
99
100struct CheckParameters<'tcx> {
101    tcx: TyCtxt<'tcx>,
102    params: HirIdSet,
103}
104
105impl<'tcx> Visitor<'tcx> for CheckParameters<'tcx> {
106    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
107        if let hir::ExprKind::Path(hir::QPath::Resolved(
108            _,
109            hir::Path { res: hir::def::Res::Local(var_hir_id), .. },
110        )) = expr.kind
111        {
112            if self.params.contains(var_hir_id) {
113                self.tcx.dcx().emit_err(ParamsNotAllowed { span: expr.span });
114                return;
115            }
116        }
117        hir::intravisit::walk_expr(self, expr);
118    }
119}
120
121/// Checks that function body contains a single inline assembly block.
122fn check_asm<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &'tcx hir::Body<'tcx>) {
123    let mut this = CheckInlineAssembly { items: Vec::new() };
124    this.visit_body(body);
125    if let [(ItemKind::NakedAsm | ItemKind::Err, _)] = this.items[..] {
126        // Ok.
127    } else {
128        let mut must_show_error = false;
129        let mut has_naked_asm = false;
130        let mut has_err = false;
131        let mut multiple_asms = vec![];
132        let mut non_asms = vec![];
133        for &(kind, span) in &this.items {
134            match kind {
135                ItemKind::NakedAsm if has_naked_asm => {
136                    must_show_error = true;
137                    multiple_asms.push(span);
138                }
139                ItemKind::NakedAsm => has_naked_asm = true,
140                ItemKind::InlineAsm => {
141                    has_err = true;
142
143                    tcx.dcx().emit_err(NakedFunctionsMustNakedAsm { span });
144                }
145                ItemKind::NonAsm => {
146                    must_show_error = true;
147                    non_asms.push(span);
148                }
149                ItemKind::Err => has_err = true,
150            }
151        }
152
153        // If the naked function only contains a single asm block and a non-zero number of
154        // errors, then don't show an additional error. This allows for appending/prepending
155        // `compile_error!("...")` statements and reduces error noise.
156        if must_show_error || !has_err {
157            tcx.dcx().emit_err(NakedFunctionsAsmBlock {
158                span: tcx.def_span(def_id),
159                multiple_asms,
160                non_asms,
161            });
162        }
163    }
164}
165
166struct CheckInlineAssembly {
167    items: Vec<(ItemKind, Span)>,
168}
169
170#[derive(Copy, Clone)]
171enum ItemKind {
172    NakedAsm,
173    InlineAsm,
174    NonAsm,
175    Err,
176}
177
178impl CheckInlineAssembly {
179    fn check_expr<'tcx>(&mut self, expr: &'tcx hir::Expr<'tcx>, span: Span) {
180        match expr.kind {
181            ExprKind::ConstBlock(..)
182            | ExprKind::Array(..)
183            | ExprKind::Call(..)
184            | ExprKind::MethodCall(..)
185            | ExprKind::Use(..)
186            | ExprKind::Tup(..)
187            | ExprKind::Binary(..)
188            | ExprKind::Unary(..)
189            | ExprKind::Lit(..)
190            | ExprKind::Cast(..)
191            | ExprKind::Type(..)
192            | ExprKind::UnsafeBinderCast(..)
193            | ExprKind::Loop(..)
194            | ExprKind::Match(..)
195            | ExprKind::If(..)
196            | ExprKind::Closure { .. }
197            | ExprKind::Assign(..)
198            | ExprKind::AssignOp(..)
199            | ExprKind::Field(..)
200            | ExprKind::Index(..)
201            | ExprKind::Path(..)
202            | ExprKind::AddrOf(..)
203            | ExprKind::Let(..)
204            | ExprKind::Break(..)
205            | ExprKind::Continue(..)
206            | ExprKind::Ret(..)
207            | ExprKind::OffsetOf(..)
208            | ExprKind::Become(..)
209            | ExprKind::Struct(..)
210            | ExprKind::Repeat(..)
211            | ExprKind::Yield(..) => {
212                self.items.push((ItemKind::NonAsm, span));
213            }
214
215            ExprKind::InlineAsm(asm) => match asm.asm_macro {
216                rustc_ast::AsmMacro::Asm => {
217                    self.items.push((ItemKind::InlineAsm, span));
218                }
219                rustc_ast::AsmMacro::NakedAsm => {
220                    self.items.push((ItemKind::NakedAsm, span));
221                }
222                rustc_ast::AsmMacro::GlobalAsm => {
223                    span_bug!(span, "`global_asm!` is not allowed in this position")
224                }
225            },
226
227            ExprKind::DropTemps(..) | ExprKind::Block(..) => {
228                hir::intravisit::walk_expr(self, expr);
229            }
230
231            ExprKind::Err(_) => {
232                self.items.push((ItemKind::Err, span));
233            }
234        }
235    }
236}
237
238impl<'tcx> Visitor<'tcx> for CheckInlineAssembly {
239    fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
240        match stmt.kind {
241            StmtKind::Item(..) => {}
242            StmtKind::Let(..) => {
243                self.items.push((ItemKind::NonAsm, stmt.span));
244            }
245            StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
246                self.check_expr(expr, stmt.span);
247            }
248        }
249    }
250
251    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
252        self.check_expr(expr, expr.span);
253    }
254}
255
256struct CheckNakedAsmInNakedFn<'tcx> {
257    tcx: TyCtxt<'tcx>,
258}
259
260impl<'tcx> Visitor<'tcx> for CheckNakedAsmInNakedFn<'tcx> {
261    type NestedFilter = OnlyBodies;
262
263    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
264        self.tcx
265    }
266
267    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
268        if let ExprKind::InlineAsm(inline_asm) = expr.kind {
269            if let rustc_ast::AsmMacro::NakedAsm = inline_asm.asm_macro {
270                self.tcx.dcx().emit_err(NakedAsmOutsideNakedFn { span: expr.span });
271            }
272        }
273
274        hir::intravisit::walk_expr(self, expr);
275    }
276}