]>
Commit | Line | Data |
---|---|---|
fc512014 XL |
1 | //! Checks validity of naked functions. |
2 | ||
94222f64 | 3 | use rustc_ast::{Attribute, InlineAsmOptions}; |
5e7ed085 | 4 | use rustc_errors::{struct_span_err, Applicability}; |
fc512014 XL |
5 | use rustc_hir as hir; |
6 | use rustc_hir::def_id::LocalDefId; | |
5099ac24 | 7 | use rustc_hir::intravisit::{FnKind, Visitor}; |
fc512014 XL |
8 | use rustc_hir::{ExprKind, HirId, InlineAsmOperand, StmtKind}; |
9 | use rustc_middle::ty::query::Providers; | |
10 | use rustc_middle::ty::TyCtxt; | |
94222f64 | 11 | use rustc_session::lint::builtin::UNDEFINED_NAKED_FUNCTION_ABI; |
fc512014 XL |
12 | use rustc_span::symbol::sym; |
13 | use rustc_span::Span; | |
14 | use rustc_target::spec::abi::Abi; | |
15 | ||
16 | fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { | |
923072b8 | 17 | tcx.hir().deep_visit_item_likes_in_module(module_def_id, &mut CheckNakedFunctions { tcx }); |
fc512014 XL |
18 | } |
19 | ||
923072b8 | 20 | pub(crate) fn provide(providers: &mut Providers) { |
fc512014 XL |
21 | *providers = Providers { check_mod_naked_functions, ..*providers }; |
22 | } | |
23 | ||
24 | struct CheckNakedFunctions<'tcx> { | |
25 | tcx: TyCtxt<'tcx>, | |
26 | } | |
27 | ||
28 | impl<'tcx> Visitor<'tcx> for CheckNakedFunctions<'tcx> { | |
fc512014 XL |
29 | fn visit_fn( |
30 | &mut self, | |
a2a8927a | 31 | fk: FnKind<'_>, |
fc512014 XL |
32 | _fd: &'tcx hir::FnDecl<'tcx>, |
33 | body_id: hir::BodyId, | |
34 | span: Span, | |
35 | hir_id: HirId, | |
36 | ) { | |
37 | let ident_span; | |
38 | let fn_header; | |
39 | ||
40 | match fk { | |
6a06907d | 41 | FnKind::Closure => { |
fc512014 XL |
42 | // Closures with a naked attribute are rejected during attribute |
43 | // check. Don't validate them any further. | |
44 | return; | |
45 | } | |
46 | FnKind::ItemFn(ident, _, ref header, ..) => { | |
47 | ident_span = ident.span; | |
48 | fn_header = header; | |
49 | } | |
50 | ||
51 | FnKind::Method(ident, ref sig, ..) => { | |
52 | ident_span = ident.span; | |
53 | fn_header = &sig.header; | |
54 | } | |
55 | } | |
56 | ||
6a06907d XL |
57 | let attrs = self.tcx.hir().attrs(hir_id); |
58 | let naked = attrs.iter().any(|attr| attr.has_name(sym::naked)); | |
fc512014 XL |
59 | if naked { |
60 | let body = self.tcx.hir().body(body_id); | |
61 | check_abi(self.tcx, hir_id, fn_header.abi, ident_span); | |
62 | check_no_patterns(self.tcx, body.params); | |
63 | check_no_parameters_use(self.tcx, body); | |
5099ac24 FG |
64 | check_asm(self.tcx, body, span); |
65 | check_inline(self.tcx, attrs); | |
fc512014 XL |
66 | } |
67 | } | |
68 | } | |
69 | ||
94222f64 | 70 | /// Check that the function isn't inlined. |
5099ac24 | 71 | fn check_inline(tcx: TyCtxt<'_>, attrs: &[Attribute]) { |
94222f64 | 72 | for attr in attrs.iter().filter(|attr| attr.has_name(sym::inline)) { |
5099ac24 | 73 | tcx.sess.struct_span_err(attr.span, "naked functions cannot be inlined").emit(); |
94222f64 XL |
74 | } |
75 | } | |
76 | ||
fc512014 XL |
77 | /// Checks that function uses non-Rust ABI. |
78 | fn check_abi(tcx: TyCtxt<'_>, hir_id: HirId, abi: Abi, fn_ident_span: Span) { | |
79 | if abi == Abi::Rust { | |
94222f64 | 80 | tcx.struct_span_lint_hir(UNDEFINED_NAKED_FUNCTION_ABI, hir_id, fn_ident_span, |lint| { |
fc512014 XL |
81 | lint.build("Rust ABI is unsupported in naked functions").emit(); |
82 | }); | |
83 | } | |
84 | } | |
85 | ||
86 | /// Checks that parameters don't use patterns. Mirrors the checks for function declarations. | |
87 | fn check_no_patterns(tcx: TyCtxt<'_>, params: &[hir::Param<'_>]) { | |
88 | for param in params { | |
89 | match param.pat.kind { | |
90 | hir::PatKind::Wild | |
91 | | hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, _, None) => {} | |
92 | _ => { | |
93 | tcx.sess | |
94 | .struct_span_err( | |
95 | param.pat.span, | |
96 | "patterns not allowed in naked function parameters", | |
97 | ) | |
98 | .emit(); | |
99 | } | |
100 | } | |
101 | } | |
102 | } | |
103 | ||
104 | /// Checks that function parameters aren't used in the function body. | |
105 | fn check_no_parameters_use<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>) { | |
106 | let mut params = hir::HirIdSet::default(); | |
107 | for param in body.params { | |
108 | param.pat.each_binding(|_binding_mode, hir_id, _span, _ident| { | |
109 | params.insert(hir_id); | |
110 | }); | |
111 | } | |
112 | CheckParameters { tcx, params }.visit_body(body); | |
113 | } | |
114 | ||
115 | struct CheckParameters<'tcx> { | |
116 | tcx: TyCtxt<'tcx>, | |
117 | params: hir::HirIdSet, | |
118 | } | |
119 | ||
120 | impl<'tcx> Visitor<'tcx> for CheckParameters<'tcx> { | |
fc512014 XL |
121 | fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { |
122 | if let hir::ExprKind::Path(hir::QPath::Resolved( | |
123 | _, | |
124 | hir::Path { res: hir::def::Res::Local(var_hir_id), .. }, | |
125 | )) = expr.kind | |
126 | { | |
127 | if self.params.contains(var_hir_id) { | |
128 | self.tcx | |
129 | .sess | |
130 | .struct_span_err( | |
131 | expr.span, | |
132 | "referencing function parameters is not allowed in naked functions", | |
133 | ) | |
134 | .help("follow the calling convention in asm block to use parameters") | |
135 | .emit(); | |
136 | return; | |
137 | } | |
138 | } | |
139 | hir::intravisit::walk_expr(self, expr); | |
140 | } | |
141 | } | |
142 | ||
143 | /// Checks that function body contains a single inline assembly block. | |
5099ac24 | 144 | fn check_asm<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>, fn_span: Span) { |
fc512014 XL |
145 | let mut this = CheckInlineAssembly { tcx, items: Vec::new() }; |
146 | this.visit_body(body); | |
5e7ed085 | 147 | if let [(ItemKind::Asm | ItemKind::Err, _)] = this.items[..] { |
fc512014 XL |
148 | // Ok. |
149 | } else { | |
5099ac24 FG |
150 | let mut diag = struct_span_err!( |
151 | tcx.sess, | |
152 | fn_span, | |
153 | E0787, | |
154 | "naked functions must contain a single asm block" | |
155 | ); | |
5e7ed085 FG |
156 | |
157 | let mut must_show_error = false; | |
5099ac24 | 158 | let mut has_asm = false; |
5e7ed085 | 159 | let mut has_err = false; |
5099ac24 FG |
160 | for &(kind, span) in &this.items { |
161 | match kind { | |
162 | ItemKind::Asm if has_asm => { | |
5e7ed085 | 163 | must_show_error = true; |
5099ac24 FG |
164 | diag.span_label(span, "multiple asm blocks are unsupported in naked functions"); |
165 | } | |
166 | ItemKind::Asm => has_asm = true, | |
167 | ItemKind::NonAsm => { | |
5e7ed085 | 168 | must_show_error = true; |
5099ac24 | 169 | diag.span_label(span, "non-asm is unsupported in naked functions"); |
fc512014 | 170 | } |
5e7ed085 | 171 | ItemKind::Err => has_err = true, |
fc512014 | 172 | } |
5099ac24 | 173 | } |
5e7ed085 FG |
174 | |
175 | // If the naked function only contains a single asm block and a non-zero number of | |
176 | // errors, then don't show an additional error. This allows for appending/prepending | |
177 | // `compile_error!("...")` statements and reduces error noise. | |
178 | if must_show_error || !has_err { | |
179 | diag.emit(); | |
180 | } else { | |
181 | diag.cancel(); | |
182 | } | |
fc512014 XL |
183 | } |
184 | } | |
185 | ||
186 | struct CheckInlineAssembly<'tcx> { | |
187 | tcx: TyCtxt<'tcx>, | |
188 | items: Vec<(ItemKind, Span)>, | |
189 | } | |
190 | ||
191 | #[derive(Copy, Clone)] | |
192 | enum ItemKind { | |
193 | Asm, | |
194 | NonAsm, | |
5e7ed085 | 195 | Err, |
fc512014 XL |
196 | } |
197 | ||
198 | impl<'tcx> CheckInlineAssembly<'tcx> { | |
199 | fn check_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, span: Span) { | |
200 | match expr.kind { | |
201 | ExprKind::Box(..) | |
202 | | ExprKind::ConstBlock(..) | |
203 | | ExprKind::Array(..) | |
204 | | ExprKind::Call(..) | |
205 | | ExprKind::MethodCall(..) | |
206 | | ExprKind::Tup(..) | |
207 | | ExprKind::Binary(..) | |
208 | | ExprKind::Unary(..) | |
209 | | ExprKind::Lit(..) | |
210 | | ExprKind::Cast(..) | |
211 | | ExprKind::Type(..) | |
212 | | ExprKind::Loop(..) | |
213 | | ExprKind::Match(..) | |
5869c6ff | 214 | | ExprKind::If(..) |
923072b8 | 215 | | ExprKind::Closure { .. } |
fc512014 XL |
216 | | ExprKind::Assign(..) |
217 | | ExprKind::AssignOp(..) | |
218 | | ExprKind::Field(..) | |
219 | | ExprKind::Index(..) | |
220 | | ExprKind::Path(..) | |
221 | | ExprKind::AddrOf(..) | |
94222f64 | 222 | | ExprKind::Let(..) |
fc512014 XL |
223 | | ExprKind::Break(..) |
224 | | ExprKind::Continue(..) | |
225 | | ExprKind::Ret(..) | |
226 | | ExprKind::Struct(..) | |
227 | | ExprKind::Repeat(..) | |
228 | | ExprKind::Yield(..) => { | |
229 | self.items.push((ItemKind::NonAsm, span)); | |
230 | } | |
231 | ||
232 | ExprKind::InlineAsm(ref asm) => { | |
233 | self.items.push((ItemKind::Asm, span)); | |
5099ac24 | 234 | self.check_inline_asm(asm, span); |
fc512014 XL |
235 | } |
236 | ||
5e7ed085 | 237 | ExprKind::DropTemps(..) | ExprKind::Block(..) => { |
fc512014 XL |
238 | hir::intravisit::walk_expr(self, expr); |
239 | } | |
5e7ed085 FG |
240 | |
241 | ExprKind::Err => { | |
242 | self.items.push((ItemKind::Err, span)); | |
243 | } | |
fc512014 XL |
244 | } |
245 | } | |
246 | ||
5099ac24 | 247 | fn check_inline_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>, span: Span) { |
fc512014 XL |
248 | let unsupported_operands: Vec<Span> = asm |
249 | .operands | |
250 | .iter() | |
251 | .filter_map(|&(ref op, op_sp)| match op { | |
04454e1e FG |
252 | InlineAsmOperand::Const { .. } |
253 | | InlineAsmOperand::SymFn { .. } | |
254 | | InlineAsmOperand::SymStatic { .. } => None, | |
fc512014 XL |
255 | InlineAsmOperand::In { .. } |
256 | | InlineAsmOperand::Out { .. } | |
257 | | InlineAsmOperand::InOut { .. } | |
258 | | InlineAsmOperand::SplitInOut { .. } => Some(op_sp), | |
259 | }) | |
260 | .collect(); | |
261 | if !unsupported_operands.is_empty() { | |
5099ac24 FG |
262 | struct_span_err!( |
263 | self.tcx.sess, | |
fc512014 | 264 | unsupported_operands, |
5099ac24 FG |
265 | E0787, |
266 | "only `const` and `sym` operands are supported in naked functions", | |
267 | ) | |
268 | .emit(); | |
fc512014 XL |
269 | } |
270 | ||
271 | let unsupported_options: Vec<&'static str> = [ | |
5099ac24 | 272 | (InlineAsmOptions::MAY_UNWIND, "`may_unwind`"), |
fc512014 XL |
273 | (InlineAsmOptions::NOMEM, "`nomem`"), |
274 | (InlineAsmOptions::NOSTACK, "`nostack`"), | |
275 | (InlineAsmOptions::PRESERVES_FLAGS, "`preserves_flags`"), | |
276 | (InlineAsmOptions::PURE, "`pure`"), | |
277 | (InlineAsmOptions::READONLY, "`readonly`"), | |
278 | ] | |
279 | .iter() | |
280 | .filter_map(|&(option, name)| if asm.options.contains(option) { Some(name) } else { None }) | |
281 | .collect(); | |
282 | ||
283 | if !unsupported_options.is_empty() { | |
5099ac24 FG |
284 | struct_span_err!( |
285 | self.tcx.sess, | |
286 | span, | |
287 | E0787, | |
288 | "asm options unsupported in naked functions: {}", | |
289 | unsupported_options.join(", ") | |
290 | ) | |
291 | .emit(); | |
fc512014 XL |
292 | } |
293 | ||
294 | if !asm.options.contains(InlineAsmOptions::NORETURN) { | |
5e7ed085 FG |
295 | let last_span = asm |
296 | .operands | |
297 | .last() | |
298 | .map_or_else(|| asm.template_strs.last().unwrap().2, |op| op.1) | |
299 | .shrink_to_hi(); | |
300 | ||
5099ac24 FG |
301 | struct_span_err!( |
302 | self.tcx.sess, | |
303 | span, | |
304 | E0787, | |
305 | "asm in naked functions must use `noreturn` option" | |
306 | ) | |
5e7ed085 FG |
307 | .span_suggestion( |
308 | last_span, | |
309 | "consider specifying that the asm block is responsible \ | |
310 | for returning from the function", | |
923072b8 | 311 | ", options(noreturn)", |
5e7ed085 FG |
312 | Applicability::MachineApplicable, |
313 | ) | |
5099ac24 | 314 | .emit(); |
fc512014 XL |
315 | } |
316 | } | |
317 | } | |
318 | ||
319 | impl<'tcx> Visitor<'tcx> for CheckInlineAssembly<'tcx> { | |
fc512014 XL |
320 | fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { |
321 | match stmt.kind { | |
322 | StmtKind::Item(..) => {} | |
323 | StmtKind::Local(..) => { | |
324 | self.items.push((ItemKind::NonAsm, stmt.span)); | |
325 | } | |
326 | StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { | |
327 | self.check_expr(expr, stmt.span); | |
328 | } | |
329 | } | |
330 | } | |
331 | ||
332 | fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { | |
333 | self.check_expr(&expr, expr.span); | |
334 | } | |
335 | } |