diff --git a/compiler/rustc_attr_data_structures/src/attributes.rs b/compiler/rustc_attr_data_structures/src/attributes.rs index 5eb1931152d31..85a4f1c6765ca 100644 --- a/compiler/rustc_attr_data_structures/src/attributes.rs +++ b/compiler/rustc_attr_data_structures/src/attributes.rs @@ -256,6 +256,9 @@ pub enum AttributeKind { /// Represents `#[link_name]`. LinkName { name: Symbol, span: Span }, + /// Represents [`#[link_section]`](https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute) + LinkSection { name: Symbol, span: Span }, + /// Represents `#[loop_match]`. LoopMatch(Span), diff --git a/compiler/rustc_attr_data_structures/src/encode_cross_crate.rs b/compiler/rustc_attr_data_structures/src/encode_cross_crate.rs index 0d6ee77c718d3..3c55f62cc80b9 100644 --- a/compiler/rustc_attr_data_structures/src/encode_cross_crate.rs +++ b/compiler/rustc_attr_data_structures/src/encode_cross_crate.rs @@ -24,6 +24,7 @@ impl AttributeKind { DocComment { .. } => Yes, ExportName { .. } => Yes, Inline(..) => No, + LinkSection { .. } => No, MacroTransparency(..) => Yes, Repr(..) => No, Stability { .. } => Yes, diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index 39652335f5553..9ad46a83f5084 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -99,6 +99,8 @@ attr_parsing_non_ident_feature = attr_parsing_null_on_export = `export_name` may not contain null characters +attr_parsing_null_on_link_section = `link_section` may not contain null characters + attr_parsing_repr_ident = meta item in `repr` must be an identifier diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 740222178ed58..e298053ab7691 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -1,11 +1,12 @@ use rustc_attr_data_structures::AttributeKind; -use rustc_attr_data_structures::AttributeKind::LinkName; +use rustc_attr_data_structures::AttributeKind::{LinkName, LinkSection}; use rustc_feature::{AttributeTemplate, template}; use rustc_span::{Symbol, sym}; use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser}; use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; +use crate::session_diagnostics::NullOnLinkSection; pub(crate) struct LinkNameParser; @@ -28,3 +29,31 @@ impl SingleAttributeParser for LinkNameParser { Some(LinkName { name, span: cx.attr_span }) } } + +pub(crate) struct LinkSectionParser; + +impl SingleAttributeParser for LinkSectionParser { + const PATH: &[Symbol] = &[sym::link_section]; + const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepFirst; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; + const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); + + fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { + let Some(nv) = args.name_value() else { + cx.expected_name_value(cx.attr_span, None); + return None; + }; + let Some(name) = nv.value_as_str() else { + cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + return None; + }; + if name.as_str().contains('\0') { + // `#[link_section = ...]` will be converted to a null-terminated string, + // so it may not contain any null characters. + cx.emit_err(NullOnLinkSection { span: cx.attr_span }); + return None; + } + + Some(LinkSection { name, span: cx.attr_span }) + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 1ac41b9c7e8b5..eee6d860550eb 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -22,7 +22,7 @@ use crate::attributes::codegen_attrs::{ use crate::attributes::confusables::ConfusablesParser; use crate::attributes::deprecation::DeprecationParser; use crate::attributes::inline::{InlineParser, RustcForceInlineParser}; -use crate::attributes::link_attrs::LinkNameParser; +use crate::attributes::link_attrs::{LinkNameParser, LinkSectionParser}; use crate::attributes::lint_helpers::{AsPtrParser, PubTransparentParser}; use crate::attributes::loop_match::{ConstContinueParser, LoopMatchParser}; use crate::attributes::must_use::MustUseParser; @@ -123,6 +123,7 @@ attribute_parsers!( Single, Single, Single, + Single, Single, Single, Single, diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 7cfce5799792a..53aafaa714fa0 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -452,6 +452,13 @@ pub(crate) struct NullOnExport { pub span: Span, } +#[derive(Diagnostic)] +#[diag(attr_parsing_null_on_link_section, code = E0648)] +pub(crate) struct NullOnLinkSection { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(attr_parsing_stability_outside_std, code = E0734)] pub(crate) struct StabilityOutsideStd { diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 7680f8e107439..a87c26f43f902 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -124,6 +124,9 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { AttributeKind::Naked(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED, AttributeKind::Align { align, .. } => codegen_fn_attrs.alignment = Some(*align), AttributeKind::LinkName { name, .. } => codegen_fn_attrs.link_name = Some(*name), + AttributeKind::LinkSection { name, .. } => { + codegen_fn_attrs.link_section = Some(*name) + } AttributeKind::NoMangle(attr_span) => { if tcx.opt_item_name(did.to_def_id()).is_some() { codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE; @@ -253,16 +256,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { } } } - sym::link_section => { - if let Some(val) = attr.value_str() { - if val.as_str().bytes().any(|b| b == 0) { - let msg = format!("illegal null byte in link_section value: `{val}`"); - tcx.dcx().span_err(attr.span(), msg); - } else { - codegen_fn_attrs.link_section = Some(val); - } - } - } sym::link_ordinal => { link_ordinal_span = Some(attr.span()); if let ordinal @ Some(_) = check_link_ordinal(tcx, attr) { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index ae486a58062bf..34c76bd3f27f3 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -177,6 +177,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::Align { align, span: repr_span }) => { self.check_align(span, target, *align, *repr_span) } + Attribute::Parsed(AttributeKind::LinkSection { span: attr_span, .. }) => { + self.check_link_section(hir_id, *attr_span, span, target) + } Attribute::Parsed(AttributeKind::Naked(attr_span)) => { self.check_naked(hir_id, *attr_span, span, target) } @@ -286,7 +289,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { [sym::ffi_const, ..] => self.check_ffi_const(attr.span(), target), [sym::link_ordinal, ..] => self.check_link_ordinal(attr, span, target), [sym::link, ..] => self.check_link(hir_id, attr, span, target), - [sym::link_section, ..] => self.check_link_section(hir_id, attr, span, target), [sym::macro_use, ..] | [sym::macro_escape, ..] => { self.check_macro_use(hir_id, attr, target) } @@ -1831,7 +1833,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } /// Checks if `#[link_section]` is applied to a function or static. - fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) { + fn check_link_section(&self, hir_id: HirId, attr_span: Span, span: Span, target: Target) { match target { Target::Static | Target::Fn | Target::Method(..) => {} // FIXME(#80564): We permit struct fields, match arms and macro defs to have an @@ -1839,7 +1841,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { // 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 => { - self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "link_section"); + self.inline_attr_str_error_with_macro_def(hir_id, attr_span, "link_section"); } _ => { // FIXME: #[link_section] was previously allowed on non-functions/statics and some @@ -1847,7 +1849,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.tcx.emit_node_span_lint( UNUSED_ATTRIBUTES, hir_id, - attr.span(), + attr_span, errors::LinkSection { span }, ); } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 3cac55493f00a..600c564d71418 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -753,9 +753,12 @@ impl Item { .other_attrs .iter() .filter_map(|attr| { + if let hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) = attr { + Some(format!("#[link_section = \"{name}\"]")) + } // NoMangle is special cased, as it appears in HTML output, and we want to show it in source form, not HIR printing. // It is also used by cargo-semver-checks. - if let hir::Attribute::Parsed(AttributeKind::NoMangle(..)) = attr { + else if let hir::Attribute::Parsed(AttributeKind::NoMangle(..)) = attr { Some("#[no_mangle]".to_string()) } else if let hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) = attr { diff --git a/tests/rustdoc-json/attrs/link_section_2021.rs b/tests/rustdoc-json/attrs/link_section_2021.rs new file mode 100644 index 0000000000000..a1312f4210b48 --- /dev/null +++ b/tests/rustdoc-json/attrs/link_section_2021.rs @@ -0,0 +1,6 @@ +//@ edition: 2021 +#![no_std] + +//@ is "$.index[?(@.name=='example')].attrs" '["#[link_section = \".text\"]"]' +#[link_section = ".text"] +pub extern "C" fn example() {} diff --git a/tests/rustdoc-json/attrs/link_section_2024.rs b/tests/rustdoc-json/attrs/link_section_2024.rs new file mode 100644 index 0000000000000..edb028451a8e0 --- /dev/null +++ b/tests/rustdoc-json/attrs/link_section_2024.rs @@ -0,0 +1,9 @@ +//@ edition: 2024 +#![no_std] + +// Since the 2024 edition the link_section attribute must use the unsafe qualification. +// However, the unsafe qualification is not shown by rustdoc. + +//@ is "$.index[?(@.name=='example')].attrs" '["#[link_section = \".text\"]"]' +#[unsafe(link_section = ".text")] +pub extern "C" fn example() {} diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr index 1243ed5d8f486..02b9e2f06eed5 100644 --- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr +++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr @@ -387,14 +387,6 @@ LL | #![link()] | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! -warning: attribute should be applied to a function or static - --> $DIR/issue-43106-gating-of-builtin-attrs.rs:69:1 - | -LL | #![link_section = "1800"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ not a function or static - | - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - warning: attribute should be applied to a function definition --> $DIR/issue-43106-gating-of-builtin-attrs.rs:62:1 | @@ -411,6 +403,14 @@ LL | #![link_name = "1900"] | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +warning: attribute should be applied to a function or static + --> $DIR/issue-43106-gating-of-builtin-attrs.rs:69:1 + | +LL | #![link_section = "1800"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ not a function or static + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + warning: `#[must_use]` has no effect when applied to a module --> $DIR/issue-43106-gating-of-builtin-attrs.rs:72:1 | diff --git a/tests/ui/lint/unused/unused-attr-duplicate.rs b/tests/ui/lint/unused/unused-attr-duplicate.rs index 407af40654e8b..bf94a42f6e0cf 100644 --- a/tests/ui/lint/unused/unused-attr-duplicate.rs +++ b/tests/ui/lint/unused/unused-attr-duplicate.rs @@ -102,4 +102,10 @@ pub fn no_mangle_test() {} #[used] //~ ERROR unused attribute static FOO: u32 = 0; +#[link_section = ".text"] +//~^ ERROR unused attribute +//~| WARN this was previously accepted +#[link_section = ".bss"] +pub extern "C" fn example() {} + fn main() {} diff --git a/tests/ui/lint/unused/unused-attr-duplicate.stderr b/tests/ui/lint/unused/unused-attr-duplicate.stderr index e8452465efc27..feae8528cf7ae 100644 --- a/tests/ui/lint/unused/unused-attr-duplicate.stderr +++ b/tests/ui/lint/unused/unused-attr-duplicate.stderr @@ -289,5 +289,18 @@ note: attribute also specified here LL | #[used] | ^^^^^^^ -error: aborting due to 23 previous errors +error: unused attribute + --> $DIR/unused-attr-duplicate.rs:105:1 + | +LL | #[link_section = ".text"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/unused-attr-duplicate.rs:108:1 + | +LL | #[link_section = ".bss"] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: aborting due to 24 previous errors