Skip to content
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

#[naked]: report incompatible attributes #127853

Merged
merged 7 commits into from
Jul 28, 2024
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
5 changes: 5 additions & 0 deletions compiler/rustc_builtin_macros/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ builtin_macros_multiple_defaults = multiple declared defaults
.note = only one variant can be default
.suggestion = make `{$ident}` default

builtin_macros_naked_functions_testing_attribute =
cannot use `#[naked]` with testing attributes
.label = function marked with testing attribute here
.naked_attribute = `#[naked]` is incompatible with testing attributes

builtin_macros_no_default_variant = no default declared
.help = make a unit variant default by placing `#[default]` above it
.suggestion = make `{$ident}` default
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_builtin_macros/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,3 +912,13 @@ pub(crate) struct ExpectedItem<'a> {
pub span: Span,
pub token: &'a str,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_naked_functions_testing_attribute, code = E0736)]
pub struct NakedFunctionTestingAttribute {
#[primary_span]
#[label(builtin_macros_naked_attribute)]
pub naked_span: Span,
#[label]
pub testing_span: Span,
}
8 changes: 8 additions & 0 deletions compiler/rustc_builtin_macros/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ pub(crate) fn expand_test_or_bench(
};
};

if let Some(attr) = attr::find_by_name(&item.attrs, sym::naked) {
cx.dcx().emit_err(errors::NakedFunctionTestingAttribute {
testing_span: attr_sp,
naked_span: attr.span,
});
return vec![Annotatable::Item(item)];
}

// check_*_signature will report any errors in the type so compilation
// will fail. We shouldn't try to expand in this case because the errors
// would be spurious.
Expand Down
18 changes: 12 additions & 6 deletions compiler/rustc_error_codes/src/error_codes/E0736.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
`#[track_caller]` and `#[naked]` cannot both be applied to the same function.
Functions marked with the `#[naked]` attribute are restricted in what other
attributes they may be marked with.

Notable attributes that are incompatible with `#[naked]` are:

* `#[inline]`
* `#[track_caller]`
* `#[test]`, `#[ignore]`, `#[should_panic]`

Erroneous code example:

```compile_fail,E0736
#[inline]
#[naked]
#[track_caller]
fn foo() {}
```

This is primarily due to ABI incompatibilities between the two attributes.
See [RFC 2091] for details on this and other limitations.

[RFC 2091]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md
These incompatibilities are due to the fact that naked functions deliberately
impose strict restrictions regarding the code that the compiler is
allowed to produce for this function.
2 changes: 1 addition & 1 deletion compiler/rustc_error_codes/src/error_codes/E0739.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
`#[track_caller]` can not be applied on struct.
`#[track_caller]` must be applied to a function

Erroneous code example:

Expand Down
11 changes: 5 additions & 6 deletions compiler/rustc_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ passes_break_non_loop =
.suggestion = use `break` on its own without a value inside this `{$kind}` loop
.break_expr_suggestion = alternatively, you might have meant to use the available loop label

passes_cannot_inline_naked_function =
naked functions cannot be inlined

passes_cannot_stabilize_deprecated =
an API can't be stabilized after it is deprecated
.label = invalid version
Expand Down Expand Up @@ -485,16 +482,18 @@ passes_naked_functions_asm_block =
passes_naked_functions_asm_options =
asm options unsupported in naked functions: {$unsupported_options}

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

passes_naked_functions_must_use_noreturn =
asm in naked functions must use `noreturn` option
.suggestion = consider specifying that the asm block is responsible for returning from the function

passes_naked_functions_operands =
only `const` and `sym` operands are supported in naked functions

passes_naked_tracked_caller =
cannot use `#[track_caller]` with `#[naked]`

passes_no_link =
attribute should be applied to an `extern crate` item
.label = not an `extern crate` item
Expand Down
73 changes: 64 additions & 9 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
[sym::rustc_std_internal_symbol] => {
self.check_rustc_std_internal_symbol(attr, span, target)
}
[sym::naked] => self.check_naked(hir_id, attr, span, target),
[sym::naked] => self.check_naked(hir_id, attr, span, target, attrs),
[sym::rustc_never_returns_null_ptr] => {
self.check_applied_to_fn_or_method(hir_id, attr, span, target)
}
Expand Down Expand Up @@ -410,12 +410,71 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}

/// Checks if `#[naked]` is applied to a function definition.
fn check_naked(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
fn check_naked(
&self,
hir_id: HirId,
attr: &Attribute,
span: Span,
target: Target,
attrs: &[Attribute],
) -> bool {
// many attributes don't make sense in combination with #[naked].
// Notable attributes that are incompatible with `#[naked]` are:
//
// * `#[inline]`
// * `#[track_caller]`
// * `#[test]`, `#[ignore]`, `#[should_panic]`
//
// NOTE: when making changes to this list, check that `error_codes/E0736.md` remains accurate
const ALLOW_LIST: &[rustc_span::Symbol] = &[
// conditional compilation
sym::cfg,
sym::cfg_attr,
// testing (allowed here so better errors can be generated in `rustc_builtin_macros::test`)
sym::test,
sym::ignore,
sym::should_panic,
sym::bench,
// diagnostics
sym::allow,
sym::warn,
sym::deny,
sym::forbid,
sym::deprecated,
sym::must_use,
// abi, linking and FFI
sym::export_name,
sym::link_section,
sym::linkage,
sym::no_mangle,
sym::naked,
sym::instruction_set,
// code generation
sym::cold,
sym::target_feature,
// documentation
sym::doc,
];

match target {
Target::Fn
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => {
for other_attr in attrs {
if !ALLOW_LIST.iter().any(|name| other_attr.has_name(*name)) {
self.dcx().emit_err(errors::NakedFunctionIncompatibleAttribute {
span: other_attr.span,
naked_span: attr.span,
attr: other_attr.name_or_empty(),
});

return false;
}
}

true
}
// FIXME(#80564): We permit struct fields, match arms and macro defs to have an
// `#[allow_internal_unstable]` attribute with just a lint, because we previously
// `#[naked]` attribute with just a lint, because we previously
// erroneously allowed it and some crates used it accidentally, to be compatible
// with crates depending on them, we can't throw an error here.
Target::Field | Target::Arm | Target::MacroDef => {
Expand Down Expand Up @@ -488,7 +547,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}

/// Checks if a `#[track_caller]` is applied to a non-naked function. Returns `true` if valid.
/// Checks if a `#[track_caller]` is applied to a function. Returns `true` if valid.
fn check_track_caller(
&self,
hir_id: HirId,
Expand All @@ -498,10 +557,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
target: Target,
) -> bool {
match target {
_ if attrs.iter().any(|attr| attr.has_name(sym::naked)) => {
self.dcx().emit_err(errors::NakedTrackedCaller { attr_span });
false
}
Target::Fn => {
// `#[track_caller]` is not valid on weak lang items because they are called via
// `extern` declarations and `#[track_caller]` would alter their ABI.
Expand Down
25 changes: 11 additions & 14 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,6 @@ pub struct AttrShouldBeAppliedToFn {
pub on_crate: bool,
}

#[derive(Diagnostic)]
#[diag(passes_naked_tracked_caller, code = E0736)]
pub struct NakedTrackedCaller {
#[primary_span]
pub attr_span: Span,
}

#[derive(Diagnostic)]
#[diag(passes_should_be_applied_to_fn, code = E0739)]
pub struct TrackedCallerWrongLocation {
Expand Down Expand Up @@ -1124,13 +1117,6 @@ pub struct UnlabeledCfInWhileCondition<'a> {
pub cf_type: &'a str,
}

#[derive(Diagnostic)]
#[diag(passes_cannot_inline_naked_function)]
pub struct CannotInlineNakedFunction {
#[primary_span]
pub span: Span,
}

#[derive(LintDiagnostic)]
#[diag(passes_undefined_naked_function_abi)]
pub struct UndefinedNakedFunctionAbi;
Expand Down Expand Up @@ -1196,6 +1182,17 @@ pub struct NakedFunctionsMustUseNoreturn {
pub last_span: Span,
}

#[derive(Diagnostic)]
#[diag(passes_naked_functions_incompatible_attribute, code = E0736)]
pub struct NakedFunctionIncompatibleAttribute {
#[primary_span]
#[label]
pub span: Span,
#[label(passes_naked_attribute)]
pub naked_span: Span,
pub attr: Symbol,
}

#[derive(Diagnostic)]
#[diag(passes_attr_only_in_functions)]
pub struct AttrOnlyInFunctions {
Expand Down
14 changes: 2 additions & 12 deletions compiler/rustc_passes/src/naked_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ use rustc_span::Span;
use rustc_target::spec::abi::Abi;

use crate::errors::{
CannotInlineNakedFunction, NakedFunctionsAsmBlock, NakedFunctionsAsmOptions,
NakedFunctionsMustUseNoreturn, NakedFunctionsOperands, NoPatterns, ParamsNotAllowed,
UndefinedNakedFunctionAbi,
NakedFunctionsAsmBlock, NakedFunctionsAsmOptions, NakedFunctionsMustUseNoreturn,
NakedFunctionsOperands, NoPatterns, ParamsNotAllowed, UndefinedNakedFunctionAbi,
};

pub(crate) fn provide(providers: &mut Providers) {
Expand Down Expand Up @@ -53,15 +52,6 @@ fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) {
check_no_patterns(tcx, body.params);
check_no_parameters_use(tcx, body);
check_asm(tcx, def_id, body);
check_inline(tcx, def_id);
}
}

/// Check that the function isn't inlined.
fn check_inline(tcx: TyCtxt<'_>, def_id: LocalDefId) {
let attrs = tcx.get_attrs(def_id, sym::inline);
for attr in attrs {
tcx.dcx().emit_err(CannotInlineNakedFunction { span: attr.span });
}
}

Expand Down
38 changes: 38 additions & 0 deletions tests/ui/asm/naked-functions-inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//@ needs-asm-support
#![feature(naked_functions)]
#![crate_type = "lib"]

use std::arch::asm;

#[naked]
pub unsafe extern "C" fn inline_none() {
asm!("", options(noreturn));
}

#[naked]
#[inline]
//~^ ERROR [E0736]
pub unsafe extern "C" fn inline_hint() {
asm!("", options(noreturn));
}

#[naked]
#[inline(always)]
//~^ ERROR [E0736]
pub unsafe extern "C" fn inline_always() {
asm!("", options(noreturn));
}

#[naked]
#[inline(never)]
//~^ ERROR [E0736]
pub unsafe extern "C" fn inline_never() {
asm!("", options(noreturn));
}

#[naked]
#[cfg_attr(all(), inline(never))]
//~^ ERROR [E0736]
pub unsafe extern "C" fn conditional_inline_never() {
asm!("", options(noreturn));
}
35 changes: 35 additions & 0 deletions tests/ui/asm/naked-functions-inline.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
error[E0736]: attribute incompatible with `#[naked]`
--> $DIR/naked-functions-inline.rs:13:1
|
LL | #[naked]
| -------- function marked with `#[naked]` here
LL | #[inline]
| ^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]`

error[E0736]: attribute incompatible with `#[naked]`
--> $DIR/naked-functions-inline.rs:20:1
|
LL | #[naked]
| -------- function marked with `#[naked]` here
LL | #[inline(always)]
| ^^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]`

error[E0736]: attribute incompatible with `#[naked]`
--> $DIR/naked-functions-inline.rs:27:1
|
LL | #[naked]
| -------- function marked with `#[naked]` here
LL | #[inline(never)]
| ^^^^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]`

error[E0736]: attribute incompatible with `#[naked]`
--> $DIR/naked-functions-inline.rs:34:19
|
LL | #[naked]
| -------- function marked with `#[naked]` here
LL | #[cfg_attr(all(), inline(never))]
| ^^^^^^^^^^^^^ the `inline` attribute is incompatible with `#[naked]`

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0736`.
30 changes: 30 additions & 0 deletions tests/ui/asm/naked-functions-instruction-set.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//@ compile-flags: --target armv5te-unknown-linux-gnueabi
//@ needs-llvm-components: arm
//@ needs-asm-support
//@ build-pass

#![crate_type = "lib"]
#![feature(no_core, lang_items, rustc_attrs, naked_functions)]
#![no_core]

#[rustc_builtin_macro]
macro_rules! asm {
() => {};
}

#[lang = "sized"]
trait Sized {}

#[no_mangle]
#[naked]
#[instruction_set(arm::t32)]
unsafe extern "C" fn test_thumb() {
asm!("bx lr", options(noreturn));
}

#[no_mangle]
#[naked]
#[instruction_set(arm::t32)]
unsafe extern "C" fn test_arm() {
asm!("bx lr", options(noreturn));
}
Loading
Loading