Rollup merge of #145724 - folkertdev:track-caller-drop-no-mangle, r=fee1-dead

the `#[track_caller]` shim should not inherit `#[no_mangle]`

fixes https://github.com/rust-lang/rust/issues/143162

builds on https://github.com/rust-lang/rust/pull/143293 which introduced a mechanism to strip attributes from shims.

cc `@Jules-Bertholet` `@workingjubilee` `@bjorn3`

---

Summary:

This PR fixes an interaction between `#[track_caller]`, `#[no_mangle]`, and casting to a function pointer.

A function annotated with `#[track_caller]` internally has a hidden extra argument for the panic location. The `#[track_caller]` attribute is only allowed on `extern "Rust"` functions. When a function is annotated with both `#[no_mangle]` and `#[track_caller]`, the exported symbol has the signature that includes the extra panic location argument. This works on stable rust today:

```rust
extern "Rust" {
    #[track_caller]
    fn rust_track_caller_ffi_test_tracked() -> &'static Location<'static>;
}

mod provides {
    use std::panic::Location;
    #[track_caller] // UB if we did not have this!
    #[no_mangle]
    fn rust_track_caller_ffi_test_tracked() -> &'static Location<'static> {
        Location::caller()
    }
}
```

When a `#[track_caller]` function is converted to a function pointer, a shim is added to drop the additional argument. So this is a valid program:

```rust
#[track_caller]
fn foo() {}

fn main() {
    let f = foo as fn();
    f();
}
```

The issue arises when `foo` is additionally annotated with `#[no_mangle]`, the generated shim currently inherits this attribute, also exporting a symbol named `foo`, but one without the hidden panic location argument. The linker rightfully complains about a duplicate symbol.

The solution of this PR is to have the generated shim drop the `#[no_mangle]` attribute.
This commit is contained in:
Matthias Krüger
2025-10-18 08:08:36 +02:00
committed by GitHub
6 changed files with 69 additions and 12 deletions

View File

@@ -467,7 +467,7 @@ pub(crate) fn codegen_terminator_call<'tcx>(
true
} else {
instance.is_some_and(|inst| {
fx.tcx.codegen_fn_attrs(inst.def_id()).flags.contains(CodegenFnAttrFlags::COLD)
fx.tcx.codegen_instance_attrs(inst.def).flags.contains(CodegenFnAttrFlags::COLD)
})
};
if is_cold {

View File

@@ -200,10 +200,11 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
let fn_ty = bx.fn_decl_backend_type(fn_abi);
let fn_attrs = if bx.tcx().def_kind(fx.instance.def_id()).has_codegen_attrs() {
Some(bx.tcx().codegen_fn_attrs(fx.instance.def_id()))
Some(bx.tcx().codegen_instance_attrs(fx.instance.def))
} else {
None
};
let fn_attrs = fn_attrs.as_deref();
if !fn_abi.can_unwind {
unwind = mir::UnwindAction::Unreachable;

View File

@@ -13,6 +13,8 @@ impl<'tcx> TyCtxt<'tcx> {
self,
instance_kind: InstanceKind<'_>,
) -> Cow<'tcx, CodegenFnAttrs> {
// NOTE: we try to not clone the `CodegenFnAttrs` when that is not needed.
// The `to_mut` method used below clones the inner value.
let mut attrs = Cow::Borrowed(self.codegen_fn_attrs(instance_kind.def_id()));
// Drop the `#[naked]` attribute on non-item `InstanceKind`s, like the shims that
@@ -23,6 +25,28 @@ impl<'tcx> TyCtxt<'tcx> {
}
}
// A shim created by `#[track_caller]` should not inherit any attributes
// that modify the symbol name. Failing to remove these attributes from
// the shim leads to errors like `symbol `foo` is already defined`.
//
// A `ClosureOnceShim` with the track_caller attribute does not have a symbol,
// and therefore can be skipped here.
if let InstanceKind::ReifyShim(_, _) = instance_kind
&& attrs.flags.contains(CodegenFnAttrFlags::TRACK_CALLER)
{
if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) {
attrs.to_mut().flags.remove(CodegenFnAttrFlags::NO_MANGLE);
}
if attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) {
attrs.to_mut().flags.remove(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL);
}
if attrs.symbol_name.is_some() {
attrs.to_mut().symbol_name = None;
}
}
attrs
}
}

View File

@@ -245,7 +245,7 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
fn on_inline_failure(&self, callsite: &CallSite<'tcx>, reason: &'static str) {
let tcx = self.tcx();
let InlineAttr::Force { attr_span, reason: justification } =
tcx.codegen_fn_attrs(callsite.callee.def_id()).inline
tcx.codegen_instance_attrs(callsite.callee.def).inline
else {
bug!("called on item without required inlining");
};
@@ -603,7 +603,8 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
let tcx = inliner.tcx();
check_mir_is_available(inliner, caller_body, callsite.callee)?;
let callee_attrs = tcx.codegen_fn_attrs(callsite.callee.def_id());
let callee_attrs = tcx.codegen_instance_attrs(callsite.callee.def);
let callee_attrs = callee_attrs.as_ref();
check_inline::is_inline_valid_on_fn(tcx, callsite.callee.def_id())?;
check_codegen_attributes(inliner, callsite, callee_attrs)?;
inliner.check_codegen_attributes_extra(callee_attrs)?;