Skip to content
Open
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_mir_transform/messages.ftl
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
mir_transform_arithmetic_overflow = this arithmetic operation will overflow
mir_transform_asm_unwind_call = call to inline assembly that may unwind
mir_transform_const_defined_here = `const` item defined here
mir_transform_const_modify = attempting to modify a `const` item
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_mir_transform/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ impl AssertLintKind {
}
}

#[derive(LintDiagnostic)]
#[diag(mir_transform_asm_unwind_call)]
pub(crate) struct AsmUnwindCall {
#[label(mir_transform_asm_unwind_call)]
pub span: Span,
}

#[derive(LintDiagnostic)]
#[diag(mir_transform_ffi_unwind_call)]
pub(crate) struct FfiUnwindCall {
Expand Down
29 changes: 29 additions & 0 deletions compiler/rustc_mir_transform/src/ffi_unwind_calls.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use rustc_abi::ExternAbi;
use rustc_ast::InlineAsmOptions;
use rustc_hir::def_id::{LOCAL_CRATE, LocalDefId};
use rustc_middle::mir::*;
use rustc_middle::query::{LocalCrate, Providers};
Expand Down Expand Up @@ -46,6 +47,34 @@ fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
continue;
}
let Some(terminator) = &block.terminator else { continue };

if let TerminatorKind::InlineAsm { options, .. } = &terminator.kind {
if options.contains(InlineAsmOptions::MAY_UNWIND) {
// We have detected an inline asm block that can possibly leak foreign unwind.
//
// Because the function body itself can unwind, we are not aborting this function call
// upon unwind, so this call can possibly leak foreign unwind into Rust code if the
// panic runtime linked is panic-abort.

let lint_root = body.source_scopes[terminator.source_info.scope]
.local_data
.as_ref()
.unwrap_crate_local()
.lint_root;
let span = terminator.source_info.span;

tcx.emit_node_span_lint(
FFI_UNWIND_CALLS,
lint_root,
span,
errors::AsmUnwindCall { span },
);

tainted = true;
}
continue;
}

let TerminatorKind::Call { func, .. } = &terminator.kind else { continue };

let ty = func.ty(body, tcx);
Expand Down
2 changes: 1 addition & 1 deletion library/unwind/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#![cfg_attr(not(target_env = "msvc"), feature(libc))]
#![cfg_attr(
all(target_family = "wasm", any(not(target_os = "emscripten"), emscripten_wasm_eh)),
feature(link_llvm_intrinsics, simd_wasm64)
feature(asm_experimental_arch, asm_unwind, simd_wasm64)
)]
#![allow(internal_features)]
#![deny(unsafe_op_in_unsafe_fn)]
Expand Down
40 changes: 13 additions & 27 deletions library/unwind/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,19 @@ pub unsafe fn _Unwind_RaiseException(exception: *mut _Unwind_Exception) -> _Unwi
// enabled exceptions via `-Z build-std` with `-C panic=unwind`.
cfg_select! {
panic = "unwind" => {
// It's important that this intrinsic is defined here rather than in `core`. Since it
// unwinds, invoking it from Rust code compiled with `-C panic=unwind` immediately
// forces `panic_unwind` as the required panic runtime.
//
// We ship unwinding `core` on Emscripten, so making this intrinsic part of `core` would
// prevent linking precompiled `core` into `-C panic=abort` binaries. Unlike `core`,
// this particular module is never precompiled with `-C panic=unwind` because it's only
// used for bare-metal targets, so an error can only arise if the user both manually
// recompiles `std` with `-C panic=unwind` and manually compiles the binary crate with
// `-C panic=abort`, which we don't care to support.
//
// See https://github.com/rust-lang/rust/issues/148246.
unsafe extern "C-unwind" {
/// LLVM lowers this intrinsic to the `throw` instruction.
#[link_name = "llvm.wasm.throw"]
fn wasm_throw(tag: i32, ptr: *mut u8) -> !;
}

// The wasm `throw` instruction takes a "tag", which differentiates certain types of
// exceptions from others. LLVM currently just identifies these via integers, with 0
// corresponding to C++ exceptions and 1 to C setjmp()/longjmp(). Ideally, we'd be able
// to choose something unique for Rust, but for now, we pretend to be C++ and implement
// the Itanium exception-handling ABI.
// corresponds with llvm::WebAssembly::Tag::CPP_EXCEPTION
// in llvm-project/llvm/include/llvm/CodeGen/WasmEHFuncInfo.h
const CPP_EXCEPTION_TAG: i32 = 0;
wasm_throw(CPP_EXCEPTION_TAG, exception.cast())
// LLVM currently only runs cleanup code for exception using the C++ exception tag and
// not those for any other exception tag like the longjmp exception tag. Ideally, we'd
// be able to choose something unique for Rust, but for now, we pretend to be C++ and
// implement the Itanium exception-handling ABI.
// This is using inline asm rather than the llvm.wasm.throw llvm intrinsic as supporting
// unwinding for llvm intrinsics complicates things in the backend.
core::arch::asm!("
.tagtype __cpp_exception i32
.globl __cpp_exception
.weak __cpp_exception
local.get {exc}
throw __cpp_exception
", exc = in(local) exception, options(may_unwind, noreturn, nostack));
}
_ => {
let _ = exception;
Expand Down
14 changes: 7 additions & 7 deletions tests/codegen-llvm/wasm_exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//@ [WASMEXN] compile-flags: -C panic=unwind -Z emscripten-wasm-eh

#![crate_type = "lib"]
#![feature(core_intrinsics, link_llvm_intrinsics)]
#![feature(asm_experimental_arch, asm_unwind, core_intrinsics)]

extern "C-unwind" {
fn may_panic();
Expand Down Expand Up @@ -82,15 +82,15 @@ pub fn test_rtry() {
pub fn test_intrinsic() {
let _log_on_drop = LogOnDrop;

unsafe extern "C-unwind" {
#[link_name = "llvm.wasm.throw"]
fn wasm_throw(tag: i32, ptr: *mut u8) -> !;
}
unsafe {
wasm_throw(0, core::ptr::null_mut());
std::arch::asm!("
.tagtype __cpp_exception i32
local.get {exc}
throw __cpp_exception
", exc = in(local) core::ptr::null_mut::<()>(), options(may_unwind, noreturn, nostack));
}

// WASMEXN-NOT: call
// WASMEXN: invoke void @llvm.wasm.throw(i32 noundef 0, ptr noundef null)
// WASMEXN: invoke void asm sideeffect unwind
// WASMEXN: %cleanuppad = cleanuppad within none []
}
Loading