From 99821e168e9eaa89c10c9276e7804cd0793a952c Mon Sep 17 00:00:00 2001 From: GokhanKabar Date: Fri, 13 Mar 2026 00:03:39 +0100 Subject: [PATCH 1/4] Fix ICE when combining #[eii] with #[core::contracts::ensures] Builtin attribute macros like #[eii] generate AST items programmatically without collected tokens. When another attribute macro was present on the same item, the compiler would panic in TokenStream::from_ast() trying to tokenize the generated items during subsequent attribute expansion. Generate fake token streams (via pretty-print and re-parse) for Item and ForeignItem nodes that lack collected tokens, following the existing pattern used for Crate and out-of-line modules. --- compiler/rustc_expand/src/expand.rs | 9 ++++++ compiler/rustc_parse/src/lib.rs | 9 ++++++ ...ice_contract_attr_on_eii_generated_item.rs | 12 ++++++++ ...contract_attr_on_eii_generated_item.stderr | 30 +++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 tests/ui/eii/ice_contract_attr_on_eii_generated_item.rs create mode 100644 tests/ui/eii/ice_contract_attr_on_eii_generated_item.stderr diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 76a9a6f9d03d9..c06f1ea1c64d6 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -814,6 +814,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> { { rustc_parse::fake_token_stream_for_item(&self.cx.sess.psess, item_inner) } + Annotatable::Item(item_inner) if item_inner.tokens.is_none() => { + rustc_parse::fake_token_stream_for_item(&self.cx.sess.psess, item_inner) + } + Annotatable::ForeignItem(item_inner) if item_inner.tokens.is_none() => { + rustc_parse::fake_token_stream_for_foreign_item( + &self.cx.sess.psess, + item_inner, + ) + } _ => item.to_tokens(), }; let attr_item = attr.get_normal_item(); diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 6b8d6baac9458..4bfa899352393 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -257,6 +257,15 @@ pub fn fake_token_stream_for_item(psess: &ParseSess, item: &ast::Item) -> TokenS unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span))) } +pub fn fake_token_stream_for_foreign_item( + psess: &ParseSess, + item: &ast::ForeignItem, +) -> TokenStream { + let source = pprust::foreign_item_to_string(item); + let filename = FileName::macro_expansion_source_code(&source); + unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span))) +} + pub fn fake_token_stream_for_crate(psess: &ParseSess, krate: &ast::Crate) -> TokenStream { let source = pprust::crate_to_string_for_macros(krate); let filename = FileName::macro_expansion_source_code(&source); diff --git a/tests/ui/eii/ice_contract_attr_on_eii_generated_item.rs b/tests/ui/eii/ice_contract_attr_on_eii_generated_item.rs new file mode 100644 index 0000000000000..0319bada3aeb5 --- /dev/null +++ b/tests/ui/eii/ice_contract_attr_on_eii_generated_item.rs @@ -0,0 +1,12 @@ +//@ compile-flags: --crate-type rlib + +#![feature(extern_item_impls)] +#![feature(contracts)] +//~^ WARN the feature `contracts` is incomplete + +#[eii] +#[core::contracts::ensures] +//~^ ERROR contract annotations is only supported in functions with bodies +//~| ERROR contract annotations can only be used on functions +fn implementation() {} +//~^ ERROR cannot find value `implementation` in module `self` diff --git a/tests/ui/eii/ice_contract_attr_on_eii_generated_item.stderr b/tests/ui/eii/ice_contract_attr_on_eii_generated_item.stderr new file mode 100644 index 0000000000000..3686072f140cb --- /dev/null +++ b/tests/ui/eii/ice_contract_attr_on_eii_generated_item.stderr @@ -0,0 +1,30 @@ +error: contract annotations is only supported in functions with bodies + --> $DIR/ice_contract_attr_on_eii_generated_item.rs:8:1 + | +LL | #[core::contracts::ensures] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: contract annotations can only be used on functions + --> $DIR/ice_contract_attr_on_eii_generated_item.rs:8:1 + | +LL | #[core::contracts::ensures] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0425]: cannot find value `implementation` in module `self` + --> $DIR/ice_contract_attr_on_eii_generated_item.rs:11:4 + | +LL | fn implementation() {} + | ^^^^^^^^^^^^^^ not found in `self` + +warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/ice_contract_attr_on_eii_generated_item.rs:4:12 + | +LL | #![feature(contracts)] + | ^^^^^^^^^ + | + = note: see issue #128044 for more information + = note: `#[warn(incomplete_features)]` on by default + +error: aborting due to 3 previous errors; 1 warning emitted + +For more information about this error, try `rustc --explain E0425`. From b544edd56c70ab3fdcc912375d5910455474a8de Mon Sep 17 00:00:00 2001 From: GokhanKabar Date: Tue, 7 Apr 2026 15:25:55 +0200 Subject: [PATCH 2/4] Preserve EII link through AttrProcMacro token roundtrip and add run-pass test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a function has `eii_impls` set (via `eii_shared_macro`), the `#[hello]` attribute is consumed from `node.attrs()`. A subsequent `AttrProcMacro` expander like `contracts::requires` calls `item.to_tokens()` which uses the current `node.attrs()` — so `#[hello]` is missing from the token stream. After the roundtrip and `parse_ast_fragment`, the new AST item has empty `eii_impls` and the EII link is broken. Fix this by using `fake_token_stream_for_item` when the item is a function with non-empty `eii_impls`. The pretty-printer re-emits `eii_impls` as `#[hello]` in `print_fn_full`, which survives the roundtrip and gets re-expanded by `eii_shared_macro` on the resulting item. Add a run-pass test to verify EII + contract annotation works correctly at runtime. --- compiler/rustc_expand/src/expand.rs | 12 ++++++++++++ tests/ui/eii/eii_impl_with_contract.rs | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/ui/eii/eii_impl_with_contract.rs diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index c06f1ea1c64d6..4b425108b49a2 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -817,6 +817,18 @@ impl<'a, 'b> MacroExpander<'a, 'b> { Annotatable::Item(item_inner) if item_inner.tokens.is_none() => { rustc_parse::fake_token_stream_for_item(&self.cx.sess.psess, item_inner) } + // When a function has EII implementations attached (via `eii_impls`), + // use fake tokens so the pretty-printer re-emits the EII attribute + // (e.g. `#[hello]`) in the token stream. Without this, the EII + // attribute is lost during the token roundtrip performed by + // `AttrProcMacro` expanders like `contracts::requires/ensures`, + // breaking the EII link on the resulting re-parsed item. + Annotatable::Item(item_inner) + if matches!(&item_inner.kind, + ItemKind::Fn(f) if !f.eii_impls.is_empty()) => + { + rustc_parse::fake_token_stream_for_item(&self.cx.sess.psess, item_inner) + } Annotatable::ForeignItem(item_inner) if item_inner.tokens.is_none() => { rustc_parse::fake_token_stream_for_foreign_item( &self.cx.sess.psess, diff --git a/tests/ui/eii/eii_impl_with_contract.rs b/tests/ui/eii/eii_impl_with_contract.rs new file mode 100644 index 0000000000000..43d34c294a79c --- /dev/null +++ b/tests/ui/eii/eii_impl_with_contract.rs @@ -0,0 +1,20 @@ +//@ run-pass +//@ ignore-backends: gcc +//@ ignore-windows + +#![feature(extern_item_impls)] +#![feature(contracts)] +#![allow(incomplete_features)] + +#[eii(hello)] +fn hello(x: u64); + +#[hello] +#[core::contracts::requires(x > 0)] +fn hello_impl(x: u64) { + println!("{x:?}") +} + +fn main() { + hello(42); +} From 48eced88b50ea7dc314ecc7087771f87f4312870 Mon Sep 17 00:00:00 2001 From: GokhanKabar Date: Tue, 7 Apr 2026 22:27:09 +0200 Subject: [PATCH 3/4] Fix platform-specific stderr mismatch in ice_contract_attr_on_eii_generated_item test The previous test used `fn implementation() {}` with a body, which caused `generate_default_impl` to generate a `const _: () = { fn implementation() {} }` item containing `self::implementation`. On Linux (aarch64-gnu-llvm-21), the resolver's `suggest_ident_hidden_by_hygiene` emitted an extra help span on the resulting E0425 error that did not appear on macOS, causing a stderr mismatch. Switch the declaration to `fn implementation();` (no body) so that `generate_default_impl` is not called and no `self::implementation` path is emitted. The test still validates that `#[eii]` + `#[core::contracts::ensures]` produces graceful errors instead of an ICE, via the two contract-annotation errors on the generated foreign item. --- .../ice_contract_attr_on_eii_generated_item.rs | 5 ++--- ..._contract_attr_on_eii_generated_item.stderr | 18 +----------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/tests/ui/eii/ice_contract_attr_on_eii_generated_item.rs b/tests/ui/eii/ice_contract_attr_on_eii_generated_item.rs index 0319bada3aeb5..fce142f6dc08b 100644 --- a/tests/ui/eii/ice_contract_attr_on_eii_generated_item.rs +++ b/tests/ui/eii/ice_contract_attr_on_eii_generated_item.rs @@ -2,11 +2,10 @@ #![feature(extern_item_impls)] #![feature(contracts)] -//~^ WARN the feature `contracts` is incomplete +#![allow(incomplete_features)] #[eii] #[core::contracts::ensures] //~^ ERROR contract annotations is only supported in functions with bodies //~| ERROR contract annotations can only be used on functions -fn implementation() {} -//~^ ERROR cannot find value `implementation` in module `self` +fn implementation(); diff --git a/tests/ui/eii/ice_contract_attr_on_eii_generated_item.stderr b/tests/ui/eii/ice_contract_attr_on_eii_generated_item.stderr index 3686072f140cb..3335346e55eec 100644 --- a/tests/ui/eii/ice_contract_attr_on_eii_generated_item.stderr +++ b/tests/ui/eii/ice_contract_attr_on_eii_generated_item.stderr @@ -10,21 +10,5 @@ error: contract annotations can only be used on functions LL | #[core::contracts::ensures] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0425]: cannot find value `implementation` in module `self` - --> $DIR/ice_contract_attr_on_eii_generated_item.rs:11:4 - | -LL | fn implementation() {} - | ^^^^^^^^^^^^^^ not found in `self` - -warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/ice_contract_attr_on_eii_generated_item.rs:4:12 - | -LL | #![feature(contracts)] - | ^^^^^^^^^ - | - = note: see issue #128044 for more information - = note: `#[warn(incomplete_features)]` on by default - -error: aborting due to 3 previous errors; 1 warning emitted +error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0425`. From db42ae233a20bd78263688f7cc3d514fbfeb5acb Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 9 Apr 2026 21:42:34 +0200 Subject: [PATCH 4/4] error on invalid macho section specifier --- .../src/attributes/link_attrs.rs | 40 ++++++++++++- .../src/session_diagnostics.rs | 19 ++++++ .../rustc_hir/src/attrs/data_structures.rs | 2 +- .../codegen-llvm/naked-fn/naked-functions.rs | 7 ++- tests/ui/linkage-attr/link-section-macho.rs | 58 +++++++++++++++++++ .../ui/linkage-attr/link-section-macho.stderr | 37 ++++++++++++ 6 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 tests/ui/linkage-attr/link-section-macho.rs create mode 100644 tests/ui/linkage-attr/link-section-macho.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 6ae4e31af9df9..3a83ed90cefe9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -14,8 +14,9 @@ use crate::attributes::cfg::parse_cfg_entry; use crate::session_diagnostics::{ AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, - LinkFrameworkApple, LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, - NullOnLinkSection, RawDylibNoNul, RawDylibOnlyWindows, WholeArchiveNeedsStatic, + InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange, + LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibNoNul, RawDylibOnlyWindows, + WholeArchiveNeedsStatic, }; pub(crate) struct LinkNameParser; @@ -465,6 +466,29 @@ impl LinkParser { pub(crate) struct LinkSectionParser; +fn check_link_section_macho(name: Symbol) -> Result<(), InvalidMachoSectionReason> { + let mut parts = name.as_str().split(',').map(|s| s.trim()); + + // The segment can be empty. + let _segment = parts.next(); + + // But the section is required. + let section = match parts.next() { + None | Some("") => return Err(InvalidMachoSectionReason::MissingSection), + Some(section) => section, + }; + + if section.len() > 16 { + return Err(InvalidMachoSectionReason::SectionTooLong { section: section.to_string() }); + } + + // LLVM also checks the other components of the section specifier, but that logic is hard to + // keep in sync. We skip it here for now, assuming that if you got that far you'll be able + // to interpret the LLVM errors. + + Ok(()) +} + impl SingleAttributeParser for LinkSectionParser { const PATH: &[Symbol] = &[sym::link_section]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; @@ -497,6 +521,18 @@ impl SingleAttributeParser for LinkSectionParser { return None; } + // We (currently) only validate macho section specifiers. + match cx.sess.target.binary_format { + BinaryFormat::MachO => match check_link_section_macho(name) { + Ok(()) => {} + Err(reason) => { + cx.emit_err(InvalidMachoSection { name_span: nv.value_span, reason }); + return None; + } + }, + BinaryFormat::Coff | BinaryFormat::Elf | BinaryFormat::Wasm | BinaryFormat::Xcoff => {} + } + Some(LinkSection { name, span: cx.attr_span }) } } diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 1b3e5af5af0a9..848f4193bdc4a 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -1128,3 +1128,22 @@ pub(crate) struct UnstableAttrForAlreadyStableFeature { #[label("the stability attribute annotates this item")] pub item_span: Span, } + +#[derive(Diagnostic)] +#[diag("invalid macho section specifier")] +pub(crate) struct InvalidMachoSection { + #[primary_span] + #[label("not a valid macho section specifier")] + pub name_span: Span, + #[subdiagnostic] + pub reason: InvalidMachoSectionReason, +} + +#[derive(Subdiagnostic)] +pub(crate) enum InvalidMachoSectionReason { + #[note("a macho section specifier requires a segment and a section, separated by a comma")] + #[help("an example of a valid macho section specifier is `__TEXT,__cstring`")] + MissingSection, + #[note("section name `{$section}` is longer than 16 bytes")] + SectionTooLong { section: String }, +} diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index a18ddff947099..190156d330ae1 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1090,8 +1090,8 @@ pub enum AttributeKind { /// Represents [`#[link_section]`](https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute) LinkSection { - name: Symbol, span: Span, + name: Symbol, }, /// Represents `#[linkage]`. diff --git a/tests/codegen-llvm/naked-fn/naked-functions.rs b/tests/codegen-llvm/naked-fn/naked-functions.rs index b5c84ede8f063..770b2b21e8fe0 100644 --- a/tests/codegen-llvm/naked-fn/naked-functions.rs +++ b/tests/codegen-llvm/naked-fn/naked-functions.rs @@ -145,13 +145,16 @@ pub extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize { } // linux: .pushsection .text.some_different_name,\22ax\22, @progbits -// macos: .pushsection .text.some_different_name,regular,pure_instructions +// macos: .pushsection __TEXT,different,regular,pure_instructions // win_x86,win_i686: .pushsection .text.some_different_name,\22xr\22 // thumb: .pushsection .text.some_different_name,\22ax\22, %progbits // CHECK-LABEL: test_link_section: #[no_mangle] #[unsafe(naked)] -#[link_section = ".text.some_different_name"] +// FIXME: configure this with `cfg(target_binary_format = "mach-o")`, +// see https://github.com/rust-lang/rust/issues/152586. +#[cfg_attr(not(target_vendor = "apple"), link_section = ".text.some_different_name")] +#[cfg_attr(target_vendor = "apple", link_section = "__TEXT,different")] pub extern "C" fn test_link_section() { cfg_select! { all(target_arch = "arm", target_feature = "thumb-mode") => { diff --git a/tests/ui/linkage-attr/link-section-macho.rs b/tests/ui/linkage-attr/link-section-macho.rs new file mode 100644 index 0000000000000..72a8f996331e7 --- /dev/null +++ b/tests/ui/linkage-attr/link-section-macho.rs @@ -0,0 +1,58 @@ +//@ add-minicore +//@ compile-flags: --target aarch64-apple-darwin +//@ needs-llvm-components: aarch64 +//@ ignore-backends: gcc +#![feature(no_core, rustc_attrs, lang_items)] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; +use minicore::*; + +#[unsafe(link_section = "foo")] +//~^ ERROR invalid macho section specifier +#[unsafe(no_mangle)] +fn missing_section() {} + +#[unsafe(link_section = "foo,")] +//~^ ERROR invalid macho section specifier +#[unsafe(no_mangle)] +fn empty_section() {} + +#[unsafe(link_section = "foo, ")] +//~^ ERROR invalid macho section specifier +#[unsafe(no_mangle)] +fn whitespace_section() {} + +#[unsafe(link_section = "foo,somelongwindedthing")] +//~^ ERROR invalid macho section specifier +#[unsafe(no_mangle)] +fn section_too_long() {} + +#[unsafe(link_section = "foo,bar")] +#[unsafe(no_mangle)] +fn segment_and_section() {} + +#[unsafe(link_section = "foo,bar,")] +#[unsafe(no_mangle)] +fn segment_and_section_and_comma() {} + +#[unsafe(link_section = ",foo")] +#[unsafe(no_mangle)] +fn missing_segment_is_fine() {} + +#[unsafe(link_section = "__TEXT,__stubs,symbol_stubs,none,16")] +#[unsafe(no_mangle)] +fn stub_size_decimal() {} + +#[unsafe(link_section = "__TEXT,__stubs,symbol_stubs,none,0x10")] +#[unsafe(no_mangle)] +fn stub_size_hex() {} + +#[unsafe(link_section = "__TEXT,__stubs,symbol_stubs,none,020")] +#[unsafe(no_mangle)] +fn stub_size_oct() {} + +#[unsafe(link_section = "__TEXT,__stubs,symbol_stubs,none,020,rest,is,ignored")] +#[unsafe(no_mangle)] +fn rest_is_ignored() {} diff --git a/tests/ui/linkage-attr/link-section-macho.stderr b/tests/ui/linkage-attr/link-section-macho.stderr new file mode 100644 index 0000000000000..6deacf6f4f5dd --- /dev/null +++ b/tests/ui/linkage-attr/link-section-macho.stderr @@ -0,0 +1,37 @@ +error: invalid macho section specifier + --> $DIR/link-section-macho.rs:12:25 + | +LL | #[unsafe(link_section = "foo")] + | ^^^^^ not a valid macho section specifier + | + = note: a macho section specifier requires a segment and a section, separated by a comma + = help: an example of a valid macho section specifier is `__TEXT,__cstring` + +error: invalid macho section specifier + --> $DIR/link-section-macho.rs:17:25 + | +LL | #[unsafe(link_section = "foo,")] + | ^^^^^^ not a valid macho section specifier + | + = note: a macho section specifier requires a segment and a section, separated by a comma + = help: an example of a valid macho section specifier is `__TEXT,__cstring` + +error: invalid macho section specifier + --> $DIR/link-section-macho.rs:22:25 + | +LL | #[unsafe(link_section = "foo, ")] + | ^^^^^^^ not a valid macho section specifier + | + = note: a macho section specifier requires a segment and a section, separated by a comma + = help: an example of a valid macho section specifier is `__TEXT,__cstring` + +error: invalid macho section specifier + --> $DIR/link-section-macho.rs:27:25 + | +LL | #[unsafe(link_section = "foo,somelongwindedthing")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ not a valid macho section specifier + | + = note: section name `somelongwindedthing` is longer than 16 bytes + +error: aborting due to 4 previous errors +