Skip to content

Change per-module naked fn checks to happen during typeck instead #141774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions compiler/rustc_hir_typeck/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ hir_typeck_lossy_provenance_ptr2int =

hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}`

hir_typeck_naked_asm_outside_naked_fn =
the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`

hir_typeck_naked_functions_asm_block =
naked functions must contain a single `naked_asm!` invocation
.label_multiple_asm = multiple `naked_asm!` invocations are not allowed in naked functions
.label_non_asm = not allowed in naked functions

hir_typeck_naked_functions_must_naked_asm =
the `asm!` macro is not allowed in naked functions
.label = consider using the `naked_asm!` macro instead

hir_typeck_never_type_fallback_flowing_into_unsafe_call = never type fallback affects this call to an `unsafe` function
.help = specify the type explicitly
hir_typeck_never_type_fallback_flowing_into_unsafe_deref = never type fallback affects this raw pointer dereference
Expand All @@ -159,6 +171,9 @@ hir_typeck_no_field_on_variant = no field named `{$field}` on enum variant `{$co
hir_typeck_no_field_on_variant_enum = this enum variant...
hir_typeck_no_field_on_variant_field = ...does not have this field

hir_typeck_no_patterns =
patterns not allowed in naked function parameters

hir_typeck_note_caller_chooses_ty_for_ty_param = the caller chooses a type for `{$ty_param_name}` which can be different from `{$found_ty}`

hir_typeck_note_edition_guide = for more on editions, read https://doc.rust-lang.org/edition-guide
Expand All @@ -167,6 +182,10 @@ hir_typeck_option_result_asref = use `{$def_path}::as_ref` to convert `{$expecte
hir_typeck_option_result_cloned = use `{$def_path}::cloned` to clone the value inside the `{$def_path}`
hir_typeck_option_result_copied = use `{$def_path}::copied` to copy the value inside the `{$def_path}`

hir_typeck_params_not_allowed =
referencing function parameters is not allowed in naked functions
.help = follow the calling convention in asm block to use parameters

hir_typeck_pass_to_variadic_function = can't pass `{$ty}` to variadic function
.suggestion = cast the value to `{$cast_ty}`
.teach_help = certain types, like `{$ty}`, must be cast before passing them to a variadic function to match the implicit cast that a C compiler would perform as part of C's numeric promotion rules
Expand Down
56 changes: 54 additions & 2 deletions compiler/rustc_hir_typeck/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::borrow::Cow;

use rustc_errors::codes::*;
use rustc_errors::{
Applicability, Diag, DiagArgValue, DiagSymbolList, EmissionGuarantee, IntoDiagArg, MultiSpan,
Subdiagnostic,
Applicability, Diag, DiagArgValue, DiagCtxtHandle, DiagSymbolList, Diagnostic,
EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic,
};
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
use rustc_middle::ty::{self, Ty};
Expand Down Expand Up @@ -983,3 +983,55 @@ pub(crate) struct RegisterTypeUnstable<'a> {
pub span: Span,
pub ty: Ty<'a>,
}

#[derive(Diagnostic)]
#[diag(hir_typeck_naked_asm_outside_naked_fn)]
pub(crate) struct NakedAsmOutsideNakedFn {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(hir_typeck_no_patterns)]
pub(crate) struct NoPatterns {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(hir_typeck_params_not_allowed)]
#[help]
pub(crate) struct ParamsNotAllowed {
#[primary_span]
pub span: Span,
}

pub(crate) struct NakedFunctionsAsmBlock {
pub span: Span,
pub multiple_asms: Vec<Span>,
pub non_asms: Vec<Span>,
}

impl<G: EmissionGuarantee> Diagnostic<'_, G> for NakedFunctionsAsmBlock {
#[track_caller]
fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
let mut diag = Diag::new(dcx, level, fluent::hir_typeck_naked_functions_asm_block);
diag.span(self.span);
diag.code(E0787);
for span in self.multiple_asms.iter() {
diag.span_label(*span, fluent::hir_typeck_label_multiple_asm);
}
for span in self.non_asms.iter() {
diag.span_label(*span, fluent::hir_typeck_label_non_asm);
}
diag
}
}

#[derive(Diagnostic)]
#[diag(hir_typeck_naked_functions_must_naked_asm, code = E0787)]
pub(crate) struct NakedFunctionsMustNakedAsm {
#[primary_span]
#[label]
pub span: Span,
}
16 changes: 11 additions & 5 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ use crate::errors::{
AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr,
BaseExpressionDoubleDotEnableDefaultFieldValues, BaseExpressionDoubleDotRemove,
CantDereference, FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct,
HelpUseLatestEdition, NoFieldOnType, NoFieldOnVariant, ReturnLikeStatementKind,
ReturnStmtOutsideOfFnBody, StructExprNonExhaustive, TypeMismatchFruTypo,
YieldExprOutsideOfCoroutine,
HelpUseLatestEdition, NakedAsmOutsideNakedFn, NoFieldOnType, NoFieldOnVariant,
ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive,
TypeMismatchFruTypo, YieldExprOutsideOfCoroutine,
};
use crate::{
BreakableCtxt, CoroutineTypes, Diverges, FnCtxt, GatherLocalsVisitor, Needs,
Expand Down Expand Up @@ -524,7 +524,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ExprKind::InlineAsm(asm) => {
// We defer some asm checks as we may not have resolved the input and output types yet (they may still be infer vars).
self.deferred_asm_checks.borrow_mut().push((asm, expr.hir_id));
self.check_expr_asm(asm)
self.check_expr_asm(asm, expr.span)
}
ExprKind::OffsetOf(container, fields) => {
self.check_expr_offset_of(container, fields, expr)
Expand Down Expand Up @@ -3761,7 +3761,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}

fn check_expr_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>) -> Ty<'tcx> {
fn check_expr_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>, span: Span) -> Ty<'tcx> {
if let rustc_ast::AsmMacro::NakedAsm = asm.asm_macro {
if !self.tcx.has_attr(self.body_id, sym::naked) {
self.tcx.dcx().emit_err(NakedAsmOutsideNakedFn { span });
}
}

let mut diverge = asm.asm_macro.diverges(asm.options);

for (op, _op_sp) in asm.operands {
Expand Down
7 changes: 6 additions & 1 deletion compiler/rustc_hir_typeck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ mod fn_ctxt;
mod gather_locals;
mod intrinsicck;
mod method;
mod naked_functions;
mod op;
mod opaque_types;
mod pat;
Expand All @@ -55,8 +56,8 @@ use rustc_middle::query::Providers;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_session::config;
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, sym};
use tracing::{debug, instrument};
use typeck_root_ctxt::TypeckRootCtxt;

Expand Down Expand Up @@ -170,6 +171,10 @@ fn typeck_with_inspect<'tcx>(
.map(|(idx, ty)| fcx.normalize(arg_span(idx), ty)),
);

if tcx.has_attr(def_id, sym::naked) {
naked_functions::typeck_naked_fn(tcx, def_id, body);
}

check_fn(&mut fcx, fn_sig, None, decl, def_id, body, tcx.features().unsized_fn_params());
} else {
let expected_type = if let Some(infer_ty) = infer_type_if_missing(&fcx, node) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,29 @@
//! Checks validity of naked functions.

use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{LocalDefId, LocalModDefId};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{ExprKind, HirIdSet, StmtKind};
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_middle::query::Providers;
use rustc_middle::span_bug;
use rustc_middle::ty::TyCtxt;
use rustc_span::{Span, sym};

use crate::errors::{
NakedAsmOutsideNakedFn, NakedFunctionsAsmBlock, NakedFunctionsMustNakedAsm, NoPatterns,
ParamsNotAllowed,
NakedFunctionsAsmBlock, NakedFunctionsMustNakedAsm, NoPatterns, ParamsNotAllowed,
};

pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers { check_mod_naked_functions, ..*providers };
}

fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
let items = tcx.hir_module_items(module_def_id);
for def_id in items.definitions() {
if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
continue;
}

let body = match tcx.hir_node_by_def_id(def_id) {
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn { body: body_id, .. }, ..
})
| hir::Node::TraitItem(hir::TraitItem {
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body_id)),
..
})
| hir::Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(_, body_id), ..
}) => tcx.hir_body(*body_id),
_ => continue,
};

if tcx.has_attr(def_id, sym::naked) {
check_no_patterns(tcx, body.params);
check_no_parameters_use(tcx, body);
check_asm(tcx, def_id, body);
} else {
// `naked_asm!` is not allowed outside of functions marked as `#[naked]`
let mut visitor = CheckNakedAsmInNakedFn { tcx };
visitor.visit_body(body);
}
}
/// Naked fns can only have trivial binding patterns in arguments,
/// may not actually use those arguments, and the body must consist of just
/// a single asm statement.
pub(crate) fn typeck_naked_fn<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
body: &'tcx hir::Body<'tcx>,
) {
debug_assert!(tcx.has_attr(def_id, sym::naked));
check_no_patterns(tcx, body.params);
check_no_parameters_use(tcx, body);
check_asm(tcx, def_id, body);
}

/// Checks that parameters don't use patterns. Mirrors the checks for function declarations.
Expand Down Expand Up @@ -231,25 +204,3 @@ impl<'tcx> Visitor<'tcx> for CheckInlineAssembly {
self.check_expr(expr, expr.span);
}
}

struct CheckNakedAsmInNakedFn<'tcx> {
tcx: TyCtxt<'tcx>,
}

impl<'tcx> Visitor<'tcx> for CheckNakedAsmInNakedFn<'tcx> {
type NestedFilter = OnlyBodies;

fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
}

fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
if let ExprKind::InlineAsm(inline_asm) = expr.kind {
if let rustc_ast::AsmMacro::NakedAsm = inline_asm.asm_macro {
self.tcx.dcx().emit_err(NakedAsmOutsideNakedFn { span: expr.span });
}
}

hir::intravisit::walk_expr(self, expr);
}
}
1 change: 0 additions & 1 deletion compiler/rustc_interface/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,6 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
tcx.par_hir_for_each_module(|module| {
tcx.ensure_ok().check_mod_loops(module);
tcx.ensure_ok().check_mod_attrs(module);
tcx.ensure_ok().check_mod_naked_functions(module);
tcx.ensure_ok().check_mod_unstable_api_usage(module);
});
},
Expand Down
4 changes: 0 additions & 4 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1119,10 +1119,6 @@ rustc_queries! {
desc { |tcx| "checking loops in {}", describe_as_module(key, tcx) }
}

query check_mod_naked_functions(key: LocalModDefId) {
desc { |tcx| "checking naked functions in {}", describe_as_module(key, tcx) }
}

query check_mod_privacy(key: LocalModDefId) {
desc { |tcx| "checking privacy in {}", describe_as_module(key.to_local_def_id(), tcx) }
}
Expand Down
19 changes: 0 additions & 19 deletions compiler/rustc_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -509,23 +509,11 @@ passes_must_not_suspend =
passes_must_use_no_effect =
`#[must_use]` has no effect when applied to {$article} {$target}

passes_naked_asm_outside_naked_fn =
the `naked_asm!` macro can only be used in functions marked with `#[unsafe(naked)]`

passes_naked_functions_asm_block =
naked functions must contain a single `naked_asm!` invocation
.label_multiple_asm = multiple `naked_asm!` invocations are not allowed in naked functions
.label_non_asm = not allowed in naked functions

passes_naked_functions_incompatible_attribute =
attribute incompatible with `#[unsafe(naked)]`
.label = the `{$attr}` attribute is incompatible with `#[unsafe(naked)]`
.naked_attribute = function marked with `#[unsafe(naked)]` here

passes_naked_functions_must_naked_asm =
the `asm!` macro is not allowed in naked functions
.label = consider using the `naked_asm!` macro instead

passes_no_link =
attribute should be applied to an `extern crate` item
.label = not an `extern crate` item
Expand Down Expand Up @@ -556,9 +544,6 @@ passes_no_mangle_foreign =
.note = symbol names in extern blocks are not mangled
.suggestion = remove this attribute

passes_no_patterns =
patterns not allowed in naked function parameters

passes_no_sanitize =
`#[no_sanitize({$attr_str})]` should be applied to {$accepted_kind}
.label = not {$accepted_kind}
Expand Down Expand Up @@ -606,10 +591,6 @@ passes_panic_unwind_without_std =
.note = since the core library is usually precompiled with panic="unwind", rebuilding your crate with panic="abort" may not be enough to fix the problem
.help = using nightly cargo, use -Zbuild-std with panic="abort" to avoid unwinding

passes_params_not_allowed =
referencing function parameters is not allowed in naked functions
.help = follow the calling convention in asm block to use parameters

passes_parent_info =
{$num ->
[one] {$descr}
Expand Down
52 changes: 0 additions & 52 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1196,51 +1196,6 @@ pub(crate) struct UnlabeledCfInWhileCondition<'a> {
pub cf_type: &'a str,
}

#[derive(Diagnostic)]
#[diag(passes_no_patterns)]
pub(crate) struct NoPatterns {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(passes_params_not_allowed)]
#[help]
pub(crate) struct ParamsNotAllowed {
#[primary_span]
pub span: Span,
}

pub(crate) struct NakedFunctionsAsmBlock {
pub span: Span,
pub multiple_asms: Vec<Span>,
pub non_asms: Vec<Span>,
}

impl<G: EmissionGuarantee> Diagnostic<'_, G> for NakedFunctionsAsmBlock {
#[track_caller]
fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
let mut diag = Diag::new(dcx, level, fluent::passes_naked_functions_asm_block);
diag.span(self.span);
diag.code(E0787);
for span in self.multiple_asms.iter() {
diag.span_label(*span, fluent::passes_label_multiple_asm);
}
for span in self.non_asms.iter() {
diag.span_label(*span, fluent::passes_label_non_asm);
}
diag
}
}

#[derive(Diagnostic)]
#[diag(passes_naked_functions_must_naked_asm, code = E0787)]
pub(crate) struct NakedFunctionsMustNakedAsm {
#[primary_span]
#[label]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(passes_naked_functions_incompatible_attribute, code = E0736)]
pub(crate) struct NakedFunctionIncompatibleAttribute {
Expand All @@ -1252,13 +1207,6 @@ pub(crate) struct NakedFunctionIncompatibleAttribute {
pub attr: String,
}

#[derive(Diagnostic)]
#[diag(passes_naked_asm_outside_naked_fn)]
pub(crate) struct NakedAsmOutsideNakedFn {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(passes_attr_only_in_functions)]
pub(crate) struct AttrOnlyInFunctions {
Expand Down
Loading
Loading