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::Tup(..)
186            | ExprKind::Binary(..)
187            | ExprKind::Unary(..)
188            | ExprKind::Lit(..)
189            | ExprKind::Cast(..)
190            | ExprKind::Type(..)
191            | ExprKind::UnsafeBinderCast(..)
192            | ExprKind::Loop(..)
193            | ExprKind::Match(..)
194            | ExprKind::If(..)
195            | ExprKind::Closure { .. }
196            | ExprKind::Assign(..)
197            | ExprKind::AssignOp(..)
198            | ExprKind::Field(..)
199            | ExprKind::Index(..)
200            | ExprKind::Path(..)
201            | ExprKind::AddrOf(..)
202            | ExprKind::Let(..)
203            | ExprKind::Break(..)
204            | ExprKind::Continue(..)
205            | ExprKind::Ret(..)
206            | ExprKind::OffsetOf(..)
207            | ExprKind::Become(..)
208            | ExprKind::Struct(..)
209            | ExprKind::Repeat(..)
210            | ExprKind::Yield(..) => {
211                self.items.push((ItemKind::NonAsm, span));
212            }
213
214            ExprKind::InlineAsm(asm) => match asm.asm_macro {
215                rustc_ast::AsmMacro::Asm => {
216                    self.items.push((ItemKind::InlineAsm, span));
217                }
218                rustc_ast::AsmMacro::NakedAsm => {
219                    self.items.push((ItemKind::NakedAsm, span));
220                }
221                rustc_ast::AsmMacro::GlobalAsm => {
222                    span_bug!(span, "`global_asm!` is not allowed in this position")
223                }
224            },
225
226            ExprKind::DropTemps(..) | ExprKind::Block(..) => {
227                hir::intravisit::walk_expr(self, expr);
228            }
229
230            ExprKind::Err(_) => {
231                self.items.push((ItemKind::Err, span));
232            }
233        }
234    }
235}
236
237impl<'tcx> Visitor<'tcx> for CheckInlineAssembly {
238    fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
239        match stmt.kind {
240            StmtKind::Item(..) => {}
241            StmtKind::Let(..) => {
242                self.items.push((ItemKind::NonAsm, stmt.span));
243            }
244            StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
245                self.check_expr(expr, stmt.span);
246            }
247        }
248    }
249
250    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
251        self.check_expr(expr, expr.span);
252    }
253}
254
255struct CheckNakedAsmInNakedFn<'tcx> {
256    tcx: TyCtxt<'tcx>,
257}
258
259impl<'tcx> Visitor<'tcx> for CheckNakedAsmInNakedFn<'tcx> {
260    type NestedFilter = OnlyBodies;
261
262    fn nested_visit_map(&mut self) -> Self::Map {
263        self.tcx.hir()
264    }
265
266    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
267        if let ExprKind::InlineAsm(inline_asm) = expr.kind {
268            if let rustc_ast::AsmMacro::NakedAsm = inline_asm.asm_macro {
269                self.tcx.dcx().emit_err(NakedAsmOutsideNakedFn { span: expr.span });
270            }
271        }
272
273        hir::intravisit::walk_expr(self, expr);
274    }
275}