Skip to content

Commit 8065dab

Browse files
committed
Validate naked functions definitions
1 parent 91fe548 commit 8065dab

File tree

10 files changed

+755
-119
lines changed

10 files changed

+755
-119
lines changed

compiler/rustc_lint_defs/src/builtin.rs

+45
Original file line numberDiff line numberDiff line change
@@ -2742,6 +2742,50 @@ declare_lint! {
27422742
"detects deprecation attributes with no effect",
27432743
}
27442744

2745+
declare_lint! {
2746+
/// The `unsupported_naked_functions` lint detects naked function
2747+
/// definitions that are unsupported but were previously accepted.
2748+
///
2749+
/// ### Example
2750+
///
2751+
/// ```rust
2752+
/// #![feature(naked_functions)]
2753+
///
2754+
/// #[naked]
2755+
/// pub fn f() -> u32 {
2756+
/// 42
2757+
/// }
2758+
/// ```
2759+
///
2760+
/// {{produces}}
2761+
///
2762+
/// ### Explanation
2763+
///
2764+
/// The naked functions must be defined using a single inline assembly
2765+
/// block.
2766+
///
2767+
/// The execution must never fall through past the end of the assembly
2768+
/// code so the block must use `noreturn` option. The asm block can also
2769+
/// use `att_syntax` option, but other options are not allowed.
2770+
///
2771+
/// The asm block must not contain any operands other than `const` and
2772+
/// `sym`. Additionally, naked function should specify a non-Rust ABI.
2773+
///
2774+
/// While other definitions of naked functions were previously accepted,
2775+
/// they are unsupported and might not work reliably. This is a
2776+
/// [future-incompatible] lint that will transition into hard error in
2777+
/// the future.
2778+
///
2779+
/// [future-incompatible]: ../index.md#future-incompatible-lints
2780+
pub UNSUPPORTED_NAKED_FUNCTIONS,
2781+
Warn,
2782+
"unsupported naked function definitions",
2783+
@future_incompatible = FutureIncompatibleInfo {
2784+
reference: "issue #32408 <https://github.com/rust-lang/rust/issues/32408>",
2785+
edition: None,
2786+
};
2787+
}
2788+
27452789
declare_tool_lint! {
27462790
pub rustc::INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
27472791
Deny,
@@ -2832,6 +2876,7 @@ declare_lint_pass! {
28322876
UNINHABITED_STATIC,
28332877
FUNCTION_ITEM_REFERENCES,
28342878
USELESS_DEPRECATED,
2879+
UNSUPPORTED_NAKED_FUNCTIONS,
28352880
]
28362881
}
28372882

compiler/rustc_passes/src/naked_functions.rs

+224-15
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
//! Checks validity of naked functions.
2+
3+
use rustc_ast::InlineAsmOptions;
14
use rustc_hir as hir;
25
use rustc_hir::def_id::LocalDefId;
36
use rustc_hir::intravisit::{ErasedMap, FnKind, NestedVisitorMap, Visitor};
7+
use rustc_hir::{ExprKind, HirId, InlineAsmOperand, StmtKind};
48
use rustc_middle::ty::query::Providers;
59
use rustc_middle::ty::TyCtxt;
10+
use rustc_session::lint::builtin::UNSUPPORTED_NAKED_FUNCTIONS;
611
use rustc_span::symbol::sym;
712
use rustc_span::Span;
13+
use rustc_target::spec::abi::Abi;
814

915
fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
1016
tcx.hir().visit_item_likes_in_module(
@@ -33,27 +39,52 @@ impl<'tcx> Visitor<'tcx> for CheckNakedFunctions<'tcx> {
3339
fk: FnKind<'v>,
3440
_fd: &'tcx hir::FnDecl<'tcx>,
3541
body_id: hir::BodyId,
36-
_span: Span,
37-
_hir_id: hir::HirId,
42+
span: Span,
43+
hir_id: HirId,
3844
) {
45+
let ident_span;
46+
let fn_header;
47+
3948
match fk {
40-
// Rejected during attribute check. Do not validate further.
41-
FnKind::Closure(..) => return,
42-
FnKind::ItemFn(..) | FnKind::Method(..) => {}
49+
FnKind::Closure(..) => {
50+
// Closures with a naked attribute are rejected during attribute
51+
// check. Don't validate them any further.
52+
return;
53+
}
54+
FnKind::ItemFn(ident, _, ref header, ..) => {
55+
ident_span = ident.span;
56+
fn_header = header;
57+
}
58+
59+
FnKind::Method(ident, ref sig, ..) => {
60+
ident_span = ident.span;
61+
fn_header = &sig.header;
62+
}
4363
}
4464

4565
let naked = fk.attrs().iter().any(|attr| attr.has_name(sym::naked));
4666
if naked {
4767
let body = self.tcx.hir().body(body_id);
48-
check_params(self.tcx, body);
49-
check_body(self.tcx, body);
68+
check_abi(self.tcx, hir_id, fn_header.abi, ident_span);
69+
check_no_patterns(self.tcx, body.params);
70+
check_no_parameters_use(self.tcx, body);
71+
check_asm(self.tcx, hir_id, body, span);
5072
}
5173
}
5274
}
5375

76+
/// Checks that function uses non-Rust ABI.
77+
fn check_abi(tcx: TyCtxt<'_>, hir_id: HirId, abi: Abi, fn_ident_span: Span) {
78+
if abi == Abi::Rust {
79+
tcx.struct_span_lint_hir(UNSUPPORTED_NAKED_FUNCTIONS, hir_id, fn_ident_span, |lint| {
80+
lint.build("Rust ABI is unsupported in naked functions").emit();
81+
});
82+
}
83+
}
84+
5485
/// Checks that parameters don't use patterns. Mirrors the checks for function declarations.
55-
fn check_params(tcx: TyCtxt<'_>, body: &hir::Body<'_>) {
56-
for param in body.params {
86+
fn check_no_patterns(tcx: TyCtxt<'_>, params: &[hir::Param<'_>]) {
87+
for param in params {
5788
match param.pat.kind {
5889
hir::PatKind::Wild
5990
| hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, _, None) => {}
@@ -69,23 +100,23 @@ fn check_params(tcx: TyCtxt<'_>, body: &hir::Body<'_>) {
69100
}
70101
}
71102

72-
/// Checks that function parameters aren't referenced in the function body.
73-
fn check_body<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>) {
103+
/// Checks that function parameters aren't used in the function body.
104+
fn check_no_parameters_use<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>) {
74105
let mut params = hir::HirIdSet::default();
75106
for param in body.params {
76107
param.pat.each_binding(|_binding_mode, hir_id, _span, _ident| {
77108
params.insert(hir_id);
78109
});
79110
}
80-
CheckBody { tcx, params }.visit_body(body);
111+
CheckParameters { tcx, params }.visit_body(body);
81112
}
82113

83-
struct CheckBody<'tcx> {
114+
struct CheckParameters<'tcx> {
84115
tcx: TyCtxt<'tcx>,
85116
params: hir::HirIdSet,
86117
}
87118

88-
impl<'tcx> Visitor<'tcx> for CheckBody<'tcx> {
119+
impl<'tcx> Visitor<'tcx> for CheckParameters<'tcx> {
89120
type Map = ErasedMap<'tcx>;
90121

91122
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
@@ -103,11 +134,189 @@ impl<'tcx> Visitor<'tcx> for CheckBody<'tcx> {
103134
.sess
104135
.struct_span_err(
105136
expr.span,
106-
"use of parameters not allowed inside naked functions",
137+
"referencing function parameters is not allowed in naked functions",
107138
)
139+
.help("follow the calling convention in asm block to use parameters")
108140
.emit();
141+
return;
109142
}
110143
}
111144
hir::intravisit::walk_expr(self, expr);
112145
}
113146
}
147+
148+
/// Checks that function body contains a single inline assembly block.
149+
fn check_asm<'tcx>(tcx: TyCtxt<'tcx>, hir_id: HirId, body: &'tcx hir::Body<'tcx>, fn_span: Span) {
150+
let mut this = CheckInlineAssembly { tcx, items: Vec::new() };
151+
this.visit_body(body);
152+
if let &[(ItemKind::Asm, _)] = &this.items[..] {
153+
// Ok.
154+
} else {
155+
tcx.struct_span_lint_hir(UNSUPPORTED_NAKED_FUNCTIONS, hir_id, fn_span, |lint| {
156+
let mut diag = lint.build("naked functions must contain a single asm block");
157+
let mut has_asm = false;
158+
for &(kind, span) in &this.items {
159+
match kind {
160+
ItemKind::Asm if has_asm => {
161+
diag.span_label(
162+
span,
163+
"multiple asm blocks are unsupported in naked functions",
164+
);
165+
}
166+
ItemKind::Asm => has_asm = true,
167+
ItemKind::NonAsm => {
168+
diag.span_label(span, "non-asm is unsupported in naked functions");
169+
}
170+
}
171+
}
172+
diag.emit();
173+
});
174+
}
175+
}
176+
177+
struct CheckInlineAssembly<'tcx> {
178+
tcx: TyCtxt<'tcx>,
179+
items: Vec<(ItemKind, Span)>,
180+
}
181+
182+
#[derive(Copy, Clone)]
183+
enum ItemKind {
184+
Asm,
185+
NonAsm,
186+
}
187+
188+
impl<'tcx> CheckInlineAssembly<'tcx> {
189+
fn check_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, span: Span) {
190+
match expr.kind {
191+
ExprKind::Box(..)
192+
| ExprKind::ConstBlock(..)
193+
| ExprKind::Array(..)
194+
| ExprKind::Call(..)
195+
| ExprKind::MethodCall(..)
196+
| ExprKind::Tup(..)
197+
| ExprKind::Binary(..)
198+
| ExprKind::Unary(..)
199+
| ExprKind::Lit(..)
200+
| ExprKind::Cast(..)
201+
| ExprKind::Type(..)
202+
| ExprKind::Loop(..)
203+
| ExprKind::Match(..)
204+
| ExprKind::Closure(..)
205+
| ExprKind::Assign(..)
206+
| ExprKind::AssignOp(..)
207+
| ExprKind::Field(..)
208+
| ExprKind::Index(..)
209+
| ExprKind::Path(..)
210+
| ExprKind::AddrOf(..)
211+
| ExprKind::Break(..)
212+
| ExprKind::Continue(..)
213+
| ExprKind::Ret(..)
214+
| ExprKind::Struct(..)
215+
| ExprKind::Repeat(..)
216+
| ExprKind::Yield(..) => {
217+
self.items.push((ItemKind::NonAsm, span));
218+
}
219+
220+
ExprKind::InlineAsm(ref asm) => {
221+
self.items.push((ItemKind::Asm, span));
222+
self.check_inline_asm(expr.hir_id, asm, span);
223+
}
224+
225+
ExprKind::LlvmInlineAsm(..) => {
226+
self.items.push((ItemKind::Asm, span));
227+
self.tcx.struct_span_lint_hir(
228+
UNSUPPORTED_NAKED_FUNCTIONS,
229+
expr.hir_id,
230+
span,
231+
|lint| {
232+
lint.build(
233+
"the LLVM-style inline assembly is unsupported in naked functions",
234+
)
235+
.help("use the new asm! syntax specified in RFC 2873")
236+
.emit();
237+
},
238+
);
239+
}
240+
241+
ExprKind::DropTemps(..) | ExprKind::Block(..) | ExprKind::Err => {
242+
hir::intravisit::walk_expr(self, expr);
243+
}
244+
}
245+
}
246+
247+
fn check_inline_asm(&self, hir_id: HirId, asm: &'tcx hir::InlineAsm<'tcx>, span: Span) {
248+
let unsupported_operands: Vec<Span> = asm
249+
.operands
250+
.iter()
251+
.filter_map(|&(ref op, op_sp)| match op {
252+
InlineAsmOperand::Const { .. } | InlineAsmOperand::Sym { .. } => None,
253+
InlineAsmOperand::In { .. }
254+
| InlineAsmOperand::Out { .. }
255+
| InlineAsmOperand::InOut { .. }
256+
| InlineAsmOperand::SplitInOut { .. } => Some(op_sp),
257+
})
258+
.collect();
259+
if !unsupported_operands.is_empty() {
260+
self.tcx.struct_span_lint_hir(
261+
UNSUPPORTED_NAKED_FUNCTIONS,
262+
hir_id,
263+
unsupported_operands,
264+
|lint| {
265+
lint.build("only `const` and `sym` operands are supported in naked functions")
266+
.emit();
267+
},
268+
);
269+
}
270+
271+
let unsupported_options: Vec<&'static str> = [
272+
(InlineAsmOptions::NOMEM, "`nomem`"),
273+
(InlineAsmOptions::NOSTACK, "`nostack`"),
274+
(InlineAsmOptions::PRESERVES_FLAGS, "`preserves_flags`"),
275+
(InlineAsmOptions::PURE, "`pure`"),
276+
(InlineAsmOptions::READONLY, "`readonly`"),
277+
]
278+
.iter()
279+
.filter_map(|&(option, name)| if asm.options.contains(option) { Some(name) } else { None })
280+
.collect();
281+
282+
if !unsupported_options.is_empty() {
283+
self.tcx.struct_span_lint_hir(UNSUPPORTED_NAKED_FUNCTIONS, hir_id, span, |lint| {
284+
lint.build(&format!(
285+
"asm options unsupported in naked functions: {}",
286+
unsupported_options.join(", ")
287+
))
288+
.emit();
289+
});
290+
}
291+
292+
if !asm.options.contains(InlineAsmOptions::NORETURN) {
293+
self.tcx.struct_span_lint_hir(UNSUPPORTED_NAKED_FUNCTIONS, hir_id, span, |lint| {
294+
lint.build("asm in naked functions must use `noreturn` option").emit();
295+
});
296+
}
297+
}
298+
}
299+
300+
impl<'tcx> Visitor<'tcx> for CheckInlineAssembly<'tcx> {
301+
type Map = ErasedMap<'tcx>;
302+
303+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
304+
NestedVisitorMap::None
305+
}
306+
307+
fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
308+
match stmt.kind {
309+
StmtKind::Item(..) => {}
310+
StmtKind::Local(..) => {
311+
self.items.push((ItemKind::NonAsm, stmt.span));
312+
}
313+
StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => {
314+
self.check_expr(expr, stmt.span);
315+
}
316+
}
317+
}
318+
319+
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
320+
self.check_expr(&expr, expr.span);
321+
}
322+
}

0 commit comments

Comments
 (0)