From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- compiler/rustc_passes/src/naked_functions.rs | 335 +++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 compiler/rustc_passes/src/naked_functions.rs (limited to 'compiler/rustc_passes/src/naked_functions.rs') diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs new file mode 100644 index 000000000..20765abf3 --- /dev/null +++ b/compiler/rustc_passes/src/naked_functions.rs @@ -0,0 +1,335 @@ +//! Checks validity of naked functions. + +use rustc_ast::{Attribute, InlineAsmOptions}; +use rustc_errors::{struct_span_err, Applicability}; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{FnKind, Visitor}; +use rustc_hir::{ExprKind, HirId, InlineAsmOperand, StmtKind}; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::lint::builtin::UNDEFINED_NAKED_FUNCTION_ABI; +use rustc_span::symbol::sym; +use rustc_span::Span; +use rustc_target::spec::abi::Abi; + +fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + tcx.hir().visit_item_likes_in_module(module_def_id, &mut CheckNakedFunctions { tcx }); +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { check_mod_naked_functions, ..*providers }; +} + +struct CheckNakedFunctions<'tcx> { + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for CheckNakedFunctions<'tcx> { + fn visit_fn( + &mut self, + fk: FnKind<'_>, + _fd: &'tcx hir::FnDecl<'tcx>, + body_id: hir::BodyId, + span: Span, + hir_id: HirId, + ) { + let ident_span; + let fn_header; + + match fk { + FnKind::Closure => { + // Closures with a naked attribute are rejected during attribute + // check. Don't validate them any further. + return; + } + FnKind::ItemFn(ident, _, ref header, ..) => { + ident_span = ident.span; + fn_header = header; + } + + FnKind::Method(ident, ref sig, ..) => { + ident_span = ident.span; + fn_header = &sig.header; + } + } + + let attrs = self.tcx.hir().attrs(hir_id); + let naked = attrs.iter().any(|attr| attr.has_name(sym::naked)); + if naked { + let body = self.tcx.hir().body(body_id); + check_abi(self.tcx, hir_id, fn_header.abi, ident_span); + check_no_patterns(self.tcx, body.params); + check_no_parameters_use(self.tcx, body); + check_asm(self.tcx, body, span); + check_inline(self.tcx, attrs); + } + } +} + +/// Check that the function isn't inlined. +fn check_inline(tcx: TyCtxt<'_>, attrs: &[Attribute]) { + for attr in attrs.iter().filter(|attr| attr.has_name(sym::inline)) { + tcx.sess.struct_span_err(attr.span, "naked functions cannot be inlined").emit(); + } +} + +/// Checks that function uses non-Rust ABI. +fn check_abi(tcx: TyCtxt<'_>, hir_id: HirId, abi: Abi, fn_ident_span: Span) { + if abi == Abi::Rust { + tcx.struct_span_lint_hir(UNDEFINED_NAKED_FUNCTION_ABI, hir_id, fn_ident_span, |lint| { + lint.build("Rust ABI is unsupported in naked functions").emit(); + }); + } +} + +/// Checks that parameters don't use patterns. Mirrors the checks for function declarations. +fn check_no_patterns(tcx: TyCtxt<'_>, params: &[hir::Param<'_>]) { + for param in params { + match param.pat.kind { + hir::PatKind::Wild + | hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, _, None) => {} + _ => { + tcx.sess + .struct_span_err( + param.pat.span, + "patterns not allowed in naked function parameters", + ) + .emit(); + } + } + } +} + +/// Checks that function parameters aren't used in the function body. +fn check_no_parameters_use<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>) { + let mut params = hir::HirIdSet::default(); + for param in body.params { + param.pat.each_binding(|_binding_mode, hir_id, _span, _ident| { + params.insert(hir_id); + }); + } + CheckParameters { tcx, params }.visit_body(body); +} + +struct CheckParameters<'tcx> { + tcx: TyCtxt<'tcx>, + params: hir::HirIdSet, +} + +impl<'tcx> Visitor<'tcx> for CheckParameters<'tcx> { + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(var_hir_id), .. }, + )) = expr.kind + { + if self.params.contains(var_hir_id) { + self.tcx + .sess + .struct_span_err( + expr.span, + "referencing function parameters is not allowed in naked functions", + ) + .help("follow the calling convention in asm block to use parameters") + .emit(); + return; + } + } + hir::intravisit::walk_expr(self, expr); + } +} + +/// Checks that function body contains a single inline assembly block. +fn check_asm<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>, fn_span: Span) { + let mut this = CheckInlineAssembly { tcx, items: Vec::new() }; + this.visit_body(body); + if let [(ItemKind::Asm | ItemKind::Err, _)] = this.items[..] { + // Ok. + } else { + let mut diag = struct_span_err!( + tcx.sess, + fn_span, + E0787, + "naked functions must contain a single asm block" + ); + + let mut must_show_error = false; + let mut has_asm = false; + let mut has_err = false; + for &(kind, span) in &this.items { + match kind { + ItemKind::Asm if has_asm => { + must_show_error = true; + diag.span_label(span, "multiple asm blocks are unsupported in naked functions"); + } + ItemKind::Asm => has_asm = true, + ItemKind::NonAsm => { + must_show_error = true; + diag.span_label(span, "non-asm is unsupported in naked functions"); + } + ItemKind::Err => has_err = true, + } + } + + // If the naked function only contains a single asm block and a non-zero number of + // errors, then don't show an additional error. This allows for appending/prepending + // `compile_error!("...")` statements and reduces error noise. + if must_show_error || !has_err { + diag.emit(); + } else { + diag.cancel(); + } + } +} + +struct CheckInlineAssembly<'tcx> { + tcx: TyCtxt<'tcx>, + items: Vec<(ItemKind, Span)>, +} + +#[derive(Copy, Clone)] +enum ItemKind { + Asm, + NonAsm, + Err, +} + +impl<'tcx> CheckInlineAssembly<'tcx> { + fn check_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, span: Span) { + match expr.kind { + ExprKind::Box(..) + | ExprKind::ConstBlock(..) + | ExprKind::Array(..) + | ExprKind::Call(..) + | ExprKind::MethodCall(..) + | ExprKind::Tup(..) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::Lit(..) + | ExprKind::Cast(..) + | ExprKind::Type(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) + | ExprKind::If(..) + | ExprKind::Closure { .. } + | ExprKind::Assign(..) + | ExprKind::AssignOp(..) + | ExprKind::Field(..) + | ExprKind::Index(..) + | ExprKind::Path(..) + | ExprKind::AddrOf(..) + | ExprKind::Let(..) + | ExprKind::Break(..) + | ExprKind::Continue(..) + | ExprKind::Ret(..) + | ExprKind::Struct(..) + | ExprKind::Repeat(..) + | ExprKind::Yield(..) => { + self.items.push((ItemKind::NonAsm, span)); + } + + ExprKind::InlineAsm(ref asm) => { + self.items.push((ItemKind::Asm, span)); + self.check_inline_asm(asm, span); + } + + ExprKind::DropTemps(..) | ExprKind::Block(..) => { + hir::intravisit::walk_expr(self, expr); + } + + ExprKind::Err => { + self.items.push((ItemKind::Err, span)); + } + } + } + + fn check_inline_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>, span: Span) { + let unsupported_operands: Vec = asm + .operands + .iter() + .filter_map(|&(ref op, op_sp)| match op { + InlineAsmOperand::Const { .. } + | InlineAsmOperand::SymFn { .. } + | InlineAsmOperand::SymStatic { .. } => None, + InlineAsmOperand::In { .. } + | InlineAsmOperand::Out { .. } + | InlineAsmOperand::InOut { .. } + | InlineAsmOperand::SplitInOut { .. } => Some(op_sp), + }) + .collect(); + if !unsupported_operands.is_empty() { + struct_span_err!( + self.tcx.sess, + unsupported_operands, + E0787, + "only `const` and `sym` operands are supported in naked functions", + ) + .emit(); + } + + let unsupported_options: Vec<&'static str> = [ + (InlineAsmOptions::MAY_UNWIND, "`may_unwind`"), + (InlineAsmOptions::NOMEM, "`nomem`"), + (InlineAsmOptions::NOSTACK, "`nostack`"), + (InlineAsmOptions::PRESERVES_FLAGS, "`preserves_flags`"), + (InlineAsmOptions::PURE, "`pure`"), + (InlineAsmOptions::READONLY, "`readonly`"), + ] + .iter() + .filter_map(|&(option, name)| if asm.options.contains(option) { Some(name) } else { None }) + .collect(); + + if !unsupported_options.is_empty() { + struct_span_err!( + self.tcx.sess, + span, + E0787, + "asm options unsupported in naked functions: {}", + unsupported_options.join(", ") + ) + .emit(); + } + + if !asm.options.contains(InlineAsmOptions::NORETURN) { + let last_span = asm + .operands + .last() + .map_or_else(|| asm.template_strs.last().unwrap().2, |op| op.1) + .shrink_to_hi(); + + struct_span_err!( + self.tcx.sess, + span, + E0787, + "asm in naked functions must use `noreturn` option" + ) + .span_suggestion( + last_span, + "consider specifying that the asm block is responsible \ + for returning from the function", + ", options(noreturn)", + Applicability::MachineApplicable, + ) + .emit(); + } + } +} + +impl<'tcx> Visitor<'tcx> for CheckInlineAssembly<'tcx> { + fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { + match stmt.kind { + StmtKind::Item(..) => {} + StmtKind::Local(..) => { + self.items.push((ItemKind::NonAsm, stmt.span)); + } + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + self.check_expr(expr, stmt.span); + } + } + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + self.check_expr(&expr, expr.span); + } +} -- cgit v1.2.3