Skip to content
Closed
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
3 changes: 3 additions & 0 deletions compiler/rustc_codegen_ssa/src/codegen_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ fn process_builtin_attrs(
codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR_ZEROED
}
sym::thread_local => codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL,
sym::rustc_propagate_ffi_unwind => {
codegen_fn_attrs.flags |= CodegenFnAttrFlags::PROPAGATE_FFI_UNWIND
}
sym::instruction_set => {
codegen_fn_attrs.instruction_set = parse_instruction_set_attr(tcx, attr)
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,10 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
rustc_allocator_zeroed, Normal, template!(Word), WarnFollowing,
EncodeCrossCrate::No,
),
rustc_attr!(
rustc_propagate_ffi_unwind, Normal, template!(Word), WarnFollowing,
EncodeCrossCrate::No,
),
rustc_attr!(
rustc_allocator_zeroed_variant, Normal, template!(NameValueStr: "function"), ErrorPreceding,
EncodeCrossCrate::Yes,
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ bitflags::bitflags! {
const NO_BUILTINS = 1 << 15;
/// Marks foreign items, to make `contains_extern_indicator` cheaper.
const FOREIGN_ITEM = 1 << 16;
/// `#[rustc_propagate_ffi_unwind]`: indicates that the function may leak unwinds from
/// invoked FFI functions regardless of the panic strategy and should always be considered
/// unwinding.
const PROPAGATE_FFI_UNWIND = 1 << 17;
}
}
rustc_data_structures::external_bitflags_debug! { CodegenFnAttrFlags }
Expand Down
9 changes: 7 additions & 2 deletions compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,10 +1219,15 @@ where
#[tracing::instrument(level = "debug", skip(tcx))]
pub fn fn_can_unwind(tcx: TyCtxt<'_>, fn_def_id: Option<DefId>, abi: ExternAbi) -> bool {
if let Some(did) = fn_def_id {
// Special attribute for functions which can't unwind.
if tcx.codegen_fn_attrs(did).flags.contains(CodegenFnAttrFlags::NEVER_UNWIND) {
// Special attributes for functions which can't unwind or unwind regardless of the panic
// strategy.
let flags = tcx.codegen_fn_attrs(did).flags;
if flags.contains(CodegenFnAttrFlags::NEVER_UNWIND) {
return false;
}
if flags.contains(CodegenFnAttrFlags::PROPAGATE_FFI_UNWIND) {
return true;
}

// With `-C panic=abort`, all non-FFI functions are required to not unwind.
//
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_mir_transform/src/ffi_unwind_calls.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rustc_abi::ExternAbi;
use rustc_hir::def_id::{LOCAL_CRATE, LocalDefId};
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::*;
use rustc_middle::query::{LocalCrate, Providers};
use rustc_middle::ty::{self, TyCtxt, layout};
Expand Down Expand Up @@ -39,6 +40,12 @@ fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
return false;
}

// `#[rustc_propagate_ffi_unwind]` functions are assumed to be unwinding by callers, so their
// bodies don't have to be verified for soundness.
if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::PROPAGATE_FFI_UNWIND) {
return false;
}

let mut tainted = false;

for block in body.basic_blocks.iter() {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1950,6 +1950,7 @@ symbols! {
rustc_private,
rustc_proc_macro_decls,
rustc_promotable,
rustc_propagate_ffi_unwind,
rustc_pub_transparent,
rustc_reallocator,
rustc_regions,
Expand Down
12 changes: 2 additions & 10 deletions library/stdarch/crates/core_arch/src/wasm32/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,8 @@ unsafe extern "C-unwind" {
// #[cfg_attr(test, assert_instr(throw, TAG = 0, ptr = core::ptr::null_mut()))]
#[inline]
#[unstable(feature = "wasm_exception_handling_intrinsics", issue = "122465")]
// FIXME: Since this instruction unwinds, `core` built with `-C panic=unwind`
// cannot be linked with `-C panic=abort` programs. But that's not
// entirely supported anyway, because runtimes without EH support won't
// be able to handle `try` blocks in `-C panic=unwind` crates either.
// We ship `-C panic=abort` `core`, so this doesn't affect users
// directly. Resolving this will likely require patching out both `try`
// and `throw` instructions, at which point we can look into whitelisting
// this function in the compiler to allow linking.
// See https://github.com/rust-lang/rust/issues/118168.
#[allow(ffi_unwind_calls)]
// Allow `core` built with `-C panic=unwind` to be linked into `-C panic=abort` programs.
#[rustc_propagate_ffi_unwind]
pub unsafe fn throw<const TAG: i32>(ptr: *mut u8) -> ! {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function must not be callable if any crate is compiled panic=abort. Otherwise you can unwind through panic=abort functions and thus skip destructors, which is unsound.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the declaration of this intrinsic should be moved back to the panic_unwind crate?

Copy link
Contributor Author

@purplesyringa purplesyringa Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's the case -- since #[rustc_propagate_ffi_unwind] functions are always considered unwinding, the abort_unwinding_calls pass will insert the correct logic whenever it's called from a panic=abort crate; and if it's called indirectly via some other rustic function, then that nested function will be recognized as unwinding and ffi_unwind_calls will force the panic strategy to unwind. I think that #[rustc_propagate_ffi_unwind] functions behave identically to FFI functions (i.e. without available source) in this regard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the propagate-ffi-unwind.rs test demonstrates this behavior; please correct me if I'm wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that #[rustc_propagate_ffi_unwind] functions behave identically to FFI functions (i.e. without available source) in this regard.

When calling extern "C" we assume that no panic can happen, when defining an extern "C" function unwinding out of it will abort unconditionally. When calling extern "C-unwind" from a panic=abort crate we insert an aborting landingpad. And if the extern "C-unwind" function is declared in a panic=unwind crate that forces the final compilation to be panic=unwind as we don't know if any panic=unwind function called it that could allow exceptions to escape into panic=abort crates. For calling extern "Rust" functions with a hidden origin no landingpads get inserted because if any crate is panic=abort, we ensure there is no way to unwind through any rust crate thanks to the extern "C-unwind" landingpad + guarantee that if any crate is panic=abort, all extern "C-unwind" functions are declared in panic=abort crates too and thus the landingpads are forced.

AFAICT with this PR you could call throw from a panic=unwind crate (which thus doesn't have an aborting landingpad) and then call this function from a panic=abort crate which also doesn't have an aborting landingpad due to the assumption that you can't unwind into any Rust code if any crate is panic=abort. This unwinding into a panic=abort crate then results in UB.

Copy link
Contributor Author

@purplesyringa purplesyringa Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you're right -- I forgot to update has_ffi_unwind_calls to consider calls to #[rustc_propagate_ffi_unwind] functions as tainting even if the function has rustic ABI. I think that's sufficient to fix the issue?

EDIT: scratch that, that doesn't work due to function pointers. It kind of seems like this would require a new ABI, then...

EDIT 2: Maybe we can just make throw C-unwind (and also use this attribute)? That sounds like it works.

static_assert!(TAG == 0); // LLVM only supports tag 0 == C++ right now.
wasm_throw(TAG, ptr)
Expand Down
24 changes: 24 additions & 0 deletions tests/codegen-llvm/unwind-abis/propagate-ffi-unwind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//@ compile-flags: -C panic=abort -Cno-prepopulate-passes

// Test that Rustic `#[rustc_propagate_ffi_unwind]` functions are considered unwinding even under
// `-C panic=abort`. We disable optimizations to make sure LLVM doesn't infer attributes.

#![feature(rustc_attrs)]
#![crate_type = "lib"]

// CHECK: @caller() unnamed_addr [[ATTR0:#[0-9]+]]
#[no_mangle]
pub fn caller() {
// CHECK: call void @{{.*core9panicking19panic_cannot_unwind}}
may_unwind();
}

// This function would typically be in a different crate.
// CHECK: @may_unwind() unnamed_addr [[ATTR1:#[0-9]+]]
#[no_mangle]
#[rustc_propagate_ffi_unwind]
#[inline(never)]
pub fn may_unwind() {}

// CHECK: attributes [[ATTR0]] = { {{.*}}nounwind{{.*}} }
// CHECK-NOT: attributes [[ATTR1]] = { {{.*}}nounwind{{.*}} }
16 changes: 15 additions & 1 deletion tests/ui/unwind-abis/ffi-unwind-calls-lint.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//@ build-pass
//@ needs-unwind

#![feature(rustc_attrs)]
#![warn(ffi_unwind_calls)]

mod foo {
Expand All @@ -16,10 +17,23 @@ fn main() {
// Call to Rust function is fine.
foo::foo();
// Call to foreign function should warn.
unsafe { foo(); }
unsafe {
foo();
}
//~^ WARNING call to foreign function with FFI-unwind ABI
let ptr: extern "C-unwind" fn() = foo::foo;
// Call to function pointer should also warn.
ptr();
//~^ WARNING call to function pointer with FFI-unwind ABI
}

#[rustc_propagate_ffi_unwind]
fn f() {
// Call to foreign function or a function pointer from within a `#[rustc_propagate_ffi_unwind]`
// function is fine.
unsafe {
foo();
}
let ptr: extern "C-unwind" fn() = foo::foo;
ptr();
}
8 changes: 4 additions & 4 deletions tests/ui/unwind-abis/ffi-unwind-calls-lint.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
warning: call to foreign function with FFI-unwind ABI
--> $DIR/ffi-unwind-calls-lint.rs:19:14
--> $DIR/ffi-unwind-calls-lint.rs:20:9
|
LL | unsafe { foo(); }
| ^^^^^ call to foreign function with FFI-unwind ABI
LL | foo();
| ^^^^^ call to foreign function with FFI-unwind ABI
|
note: the lint level is defined here
--> $DIR/ffi-unwind-calls-lint.rs:4:9
Expand All @@ -11,7 +11,7 @@ LL | #![warn(ffi_unwind_calls)]
| ^^^^^^^^^^^^^^^^

warning: call to function pointer with FFI-unwind ABI
--> $DIR/ffi-unwind-calls-lint.rs:23:5
--> $DIR/ffi-unwind-calls-lint.rs:25:5
|
LL | ptr();
| ^^^^^ call to function pointer with FFI-unwind ABI
Expand Down
7 changes: 7 additions & 0 deletions tests/ui/wasm/wasm-link-to-panic-abort-issue-148246.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//@ only-wasm32
//@ compile-flags: -C panic=abort
//@ build-pass

// Test that a `-C panic=abort` binary crate can link to a `-C panic=unwind` core.

fn main() {}
Loading