rustc_passes/
naked_functions.rs

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