about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_interface/src/passes.rs1
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs40
-rw-r--r--compiler/rustc_metadata/src/creader.rs2
-rw-r--r--compiler/rustc_metadata/src/dependency_format.rs22
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder.rs4
-rw-r--r--compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs2
-rw-r--r--compiler/rustc_metadata/src/rmeta/encoder.rs2
-rw-r--r--compiler/rustc_metadata/src/rmeta/mod.rs2
-rw-r--r--compiler/rustc_middle/src/query/mod.rs8
-rw-r--r--compiler/rustc_mir_transform/src/abort_unwinding_calls.rs7
-rw-r--r--compiler/rustc_mir_transform/src/ffi_unwind_calls.rs170
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs5
12 files changed, 240 insertions, 25 deletions
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 502afa493fe..5b263aded9c 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -972,6 +972,7 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
             if !tcx.sess.opts.debugging_opts.thir_unsafeck {
                 rustc_mir_transform::check_unsafety::check_unsafety(tcx, def_id);
             }
+            tcx.ensure().has_ffi_unwind_calls(def_id);
 
             if tcx.hir().body_const_context(def_id).is_some() {
                 tcx.ensure()
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index d52455e2576..40601bb5aad 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -3231,6 +3231,7 @@ declare_lint_pass! {
         UNEXPECTED_CFGS,
         DEPRECATED_WHERE_CLAUSE_LOCATION,
         TEST_UNSTABLE_LINT,
+        FFI_UNWIND_CALLS,
     ]
 }
 
@@ -3896,3 +3897,42 @@ declare_lint! {
     "this unstable lint is only for testing",
     @feature_gate = sym::test_unstable_lint;
 }
+
+declare_lint! {
+    /// The `ffi_unwind_calls` lint detects calls to foreign functions or function pointers with
+    /// `C-unwind` or other FFI-unwind ABIs.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,ignore (need FFI)
+    /// #![feature(ffi_unwind_calls)]
+    /// #![feature(c_unwind)]
+    ///
+    /// # mod impl {
+    /// #     #[no_mangle]
+    /// #     pub fn "C-unwind" fn foo() {}
+    /// # }
+    ///
+    /// extern "C-unwind" {
+    ///     fn foo();
+    /// }
+    ///
+    /// fn bar() {
+    ///     unsafe { foo(); }
+    ///     let ptr: unsafe extern "C-unwind" fn() = foo;
+    ///     unsafe { ptr(); }
+    /// }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// For crates containing such calls, if they are compiled with `-C panic=unwind` then the
+    /// produced library cannot be linked with crates compiled with `-C panic=abort`. For crates
+    /// that desire this ability it is therefore necessary to avoid such calls.
+    pub FFI_UNWIND_CALLS,
+    Allow,
+    "call to foreign functions or function pointers with FFI-unwind ABI",
+    @feature_gate = sym::c_unwind;
+}
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index 555db5846ed..cb50c0fb738 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -748,7 +748,7 @@ impl<'a> CrateLoader<'a> {
         if !data.is_panic_runtime() {
             self.sess.err(&format!("the crate `{}` is not a panic runtime", name));
         }
-        if data.panic_strategy() != desired_strategy {
+        if data.required_panic_strategy() != Some(desired_strategy) {
             self.sess.err(&format!(
                 "the crate `{}` does not have the panic \
                                     strategy `{}`",
diff --git a/compiler/rustc_metadata/src/dependency_format.rs b/compiler/rustc_metadata/src/dependency_format.rs
index 245b2076ebc..770d164894a 100644
--- a/compiler/rustc_metadata/src/dependency_format.rs
+++ b/compiler/rustc_metadata/src/dependency_format.rs
@@ -60,7 +60,6 @@ use rustc_middle::ty::TyCtxt;
 use rustc_session::config::CrateType;
 use rustc_session::cstore::CrateDepKind;
 use rustc_session::cstore::LinkagePreference::{self, RequireDynamic, RequireStatic};
-use rustc_target::spec::PanicStrategy;
 
 pub(crate) fn calculate(tcx: TyCtxt<'_>) -> Dependencies {
     tcx.sess
@@ -367,14 +366,19 @@ fn verify_ok(tcx: TyCtxt<'_>, list: &[Linkage]) {
                     prev_name, cur_name
                 ));
             }
-            panic_runtime = Some((cnum, tcx.panic_strategy(cnum)));
+            panic_runtime = Some((
+                cnum,
+                tcx.required_panic_strategy(cnum).unwrap_or_else(|| {
+                    bug!("cannot determine panic strategy of a panic runtime");
+                }),
+            ));
         }
     }
 
     // If we found a panic runtime, then we know by this point that it's the
     // only one, but we perform validation here that all the panic strategy
     // compilation modes for the whole DAG are valid.
-    if let Some((cnum, found_strategy)) = panic_runtime {
+    if let Some((runtime_cnum, found_strategy)) = panic_runtime {
         let desired_strategy = sess.panic_strategy();
 
         // First up, validate that our selected panic runtime is indeed exactly
@@ -384,7 +388,7 @@ fn verify_ok(tcx: TyCtxt<'_>, list: &[Linkage]) {
                 "the linked panic runtime `{}` is \
                                not compiled with this crate's \
                                panic strategy `{}`",
-                tcx.crate_name(cnum),
+                tcx.crate_name(runtime_cnum),
                 desired_strategy.desc()
             ));
         }
@@ -397,18 +401,14 @@ fn verify_ok(tcx: TyCtxt<'_>, list: &[Linkage]) {
             if let Linkage::NotLinked = *linkage {
                 continue;
             }
-            if desired_strategy == PanicStrategy::Abort {
-                continue;
-            }
             let cnum = CrateNum::new(i + 1);
-            if tcx.is_compiler_builtins(cnum) {
+            if cnum == runtime_cnum || tcx.is_compiler_builtins(cnum) {
                 continue;
             }
 
-            let found_strategy = tcx.panic_strategy(cnum);
-            if desired_strategy != found_strategy {
+            if let Some(found_strategy) = tcx.required_panic_strategy(cnum) && desired_strategy != found_strategy {
                 sess.err(&format!(
-                    "the crate `{}` is compiled with the \
+                    "the crate `{}` requires \
                                panic strategy `{}` which is \
                                incompatible with this crate's \
                                strategy of `{}`",
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index 3280fd5c310..f0ccf02c9fa 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -1758,8 +1758,8 @@ impl CrateMetadata {
         self.dep_kind.with_lock(|dep_kind| *dep_kind = f(*dep_kind))
     }
 
-    pub(crate) fn panic_strategy(&self) -> PanicStrategy {
-        self.root.panic_strategy
+    pub(crate) fn required_panic_strategy(&self) -> Option<PanicStrategy> {
+        self.root.required_panic_strategy
     }
 
     pub(crate) fn needs_panic_runtime(&self) -> bool {
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index 0bea2a10da8..565eec18ea9 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -246,7 +246,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
     has_global_allocator => { cdata.root.has_global_allocator }
     has_panic_handler => { cdata.root.has_panic_handler }
     is_profiler_runtime => { cdata.root.profiler_runtime }
-    panic_strategy => { cdata.root.panic_strategy }
+    required_panic_strategy => { cdata.root.required_panic_strategy }
     panic_in_drop_strategy => { cdata.root.panic_in_drop_strategy }
     extern_crate => {
         let r = *cdata.extern_crate.lock();
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index 75286b89068..bb4b502bded 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -665,7 +665,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
             triple: tcx.sess.opts.target_triple.clone(),
             hash: tcx.crate_hash(LOCAL_CRATE),
             stable_crate_id: tcx.def_path_hash(LOCAL_CRATE.as_def_id()).stable_crate_id(),
-            panic_strategy: tcx.sess.panic_strategy(),
+            required_panic_strategy: tcx.required_panic_strategy(LOCAL_CRATE),
             panic_in_drop_strategy: tcx.sess.opts.debugging_opts.panic_in_drop,
             edition: tcx.sess.edition(),
             has_global_allocator: tcx.has_global_allocator(LOCAL_CRATE),
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index a58c0e68ee3..a50eb2a71cf 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -217,7 +217,7 @@ pub(crate) struct CrateRoot {
     extra_filename: String,
     hash: Svh,
     stable_crate_id: StableCrateId,
-    panic_strategy: PanicStrategy,
+    required_panic_strategy: Option<PanicStrategy>,
     panic_in_drop_strategy: PanicStrategy,
     edition: Edition,
     has_global_allocator: bool,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index a1065eef850..294f56d16b1 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1356,9 +1356,13 @@ rustc_queries! {
         desc { "query a crate is `#![profiler_runtime]`" }
         separate_provide_extern
     }
-    query panic_strategy(_: CrateNum) -> PanicStrategy {
+    query has_ffi_unwind_calls(key: LocalDefId) -> bool {
+        desc { |tcx| "check if `{}` contains FFI-unwind calls", tcx.def_path_str(key.to_def_id()) }
+        cache_on_disk_if { true }
+    }
+    query required_panic_strategy(_: CrateNum) -> Option<PanicStrategy> {
         fatal_cycle
-        desc { "query a crate's configured panic strategy" }
+        desc { "query a crate's required panic strategy" }
         separate_provide_extern
     }
     query panic_in_drop_strategy(_: CrateNum) -> PanicStrategy {
diff --git a/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs b/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs
index 11980382ffd..2c8389e532d 100644
--- a/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs
+++ b/compiler/rustc_mir_transform/src/abort_unwinding_calls.rs
@@ -1,6 +1,5 @@
 use crate::MirPass;
 use rustc_ast::InlineAsmOptions;
-use rustc_hir::def::DefKind;
 use rustc_middle::mir::*;
 use rustc_middle::ty::layout;
 use rustc_middle::ty::{self, TyCtxt};
@@ -31,11 +30,7 @@ impl<'tcx> MirPass<'tcx> for AbortUnwindingCalls {
 
         // We don't simplify the MIR of constants at this time because that
         // namely results in a cyclic query when we call `tcx.type_of` below.
-        let is_function = match kind {
-            DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true,
-            _ => tcx.is_closure(def_id),
-        };
-        if !is_function {
+        if !kind.is_fn_like() {
             return;
         }
 
diff --git a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs
new file mode 100644
index 00000000000..7728fdaffb0
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs
@@ -0,0 +1,170 @@
+use rustc_hir::def_id::{CrateNum, LocalDefId, LOCAL_CRATE};
+use rustc_middle::mir::*;
+use rustc_middle::ty::layout;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_session::lint::builtin::FFI_UNWIND_CALLS;
+use rustc_target::spec::abi::Abi;
+use rustc_target::spec::PanicStrategy;
+
+fn abi_can_unwind(abi: Abi) -> bool {
+    use Abi::*;
+    match abi {
+        C { unwind }
+        | System { unwind }
+        | Cdecl { unwind }
+        | Stdcall { unwind }
+        | Fastcall { unwind }
+        | Vectorcall { unwind }
+        | Thiscall { unwind }
+        | Aapcs { unwind }
+        | Win64 { unwind }
+        | SysV64 { unwind } => unwind,
+        PtxKernel
+        | Msp430Interrupt
+        | X86Interrupt
+        | AmdGpuKernel
+        | EfiApi
+        | AvrInterrupt
+        | AvrNonBlockingInterrupt
+        | CCmseNonSecureCall
+        | Wasm
+        | RustIntrinsic
+        | PlatformIntrinsic
+        | Unadjusted => false,
+        Rust | RustCall | RustCold => true,
+    }
+}
+
+// Check if the body of this def_id can possibly leak a foreign unwind into Rust code.
+fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
+    debug!("has_ffi_unwind_calls({local_def_id:?})");
+
+    // Only perform check on functions because constants cannot call FFI functions.
+    let def_id = local_def_id.to_def_id();
+    let kind = tcx.def_kind(def_id);
+    if !kind.is_fn_like() {
+        return false;
+    }
+
+    let body = &*tcx.mir_built(ty::WithOptConstParam::unknown(local_def_id)).borrow();
+
+    let body_ty = tcx.type_of(def_id);
+    let body_abi = match body_ty.kind() {
+        ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
+        ty::Closure(..) => Abi::RustCall,
+        ty::Generator(..) => Abi::Rust,
+        _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty),
+    };
+    let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi);
+
+    // Foreign unwinds cannot leak past functions that themselves cannot unwind.
+    if !body_can_unwind {
+        return false;
+    }
+
+    let mut tainted = false;
+
+    for block in body.basic_blocks() {
+        if block.is_cleanup {
+            continue;
+        }
+        let Some(terminator) = &block.terminator else { continue };
+        let TerminatorKind::Call { func, .. } = &terminator.kind else { continue };
+
+        let ty = func.ty(body, tcx);
+        let sig = ty.fn_sig(tcx);
+
+        // Rust calls cannot themselves create foreign unwinds.
+        if let Abi::Rust | Abi::RustCall | Abi::RustCold = sig.abi() {
+            continue;
+        };
+
+        let fn_def_id = match ty.kind() {
+            ty::FnPtr(_) => None,
+            &ty::FnDef(def_id, _) => {
+                // Rust calls cannot themselves create foreign unwinds.
+                if !tcx.is_foreign_item(def_id) {
+                    continue;
+                }
+                Some(def_id)
+            }
+            _ => bug!("invalid callee of type {:?}", ty),
+        };
+
+        if layout::fn_can_unwind(tcx, fn_def_id, sig.abi()) && abi_can_unwind(sig.abi()) {
+            // We have detected a call 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()
+                .assert_crate_local()
+                .lint_root;
+            let span = terminator.source_info.span;
+
+            tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, |lint| {
+                let msg = match fn_def_id {
+                    Some(_) => "call to foreign function with FFI-unwind ABI",
+                    None => "call to function pointer with FFI-unwind ABI",
+                };
+                let mut db = lint.build(msg);
+                db.span_label(span, msg);
+                db.emit();
+            });
+
+            tainted = true;
+        }
+    }
+
+    tainted
+}
+
+fn required_panic_strategy(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<PanicStrategy> {
+    assert_eq!(cnum, LOCAL_CRATE);
+
+    if tcx.is_panic_runtime(LOCAL_CRATE) {
+        return Some(tcx.sess.panic_strategy());
+    }
+
+    if tcx.sess.panic_strategy() == PanicStrategy::Abort {
+        return Some(PanicStrategy::Abort);
+    }
+
+    for def_id in tcx.hir().body_owners() {
+        if tcx.has_ffi_unwind_calls(def_id) {
+            // Given that this crate is compiled in `-C panic=unwind`, the `AbortUnwindingCalls`
+            // MIR pass will not be run on FFI-unwind call sites, therefore a foreign exception
+            // can enter Rust through these sites.
+            //
+            // On the other hand, crates compiled with `-C panic=abort` expects that all Rust
+            // functions cannot unwind (whether it's caused by Rust panic or foreign exception),
+            // and this expectation mismatch can cause unsoundness (#96926).
+            //
+            // To address this issue, we enforce that if FFI-unwind calls are used in a crate
+            // compiled with `panic=unwind`, then the final panic strategy must be `panic=unwind`.
+            // This will ensure that no crates will have wrong unwindability assumption.
+            //
+            // It should be noted that it is okay to link `panic=unwind` into a `panic=abort`
+            // program if it contains no FFI-unwind calls. In such case foreign exception can only
+            // enter Rust in a `panic=abort` crate, which will lead to an abort. There will also
+            // be no exceptions generated from Rust, so the assumption which `panic=abort` crates
+            // make, that no Rust function can unwind, indeed holds for crates compiled with
+            // `panic=unwind` as well. In such case this function returns `None`, indicating that
+            // the crate does not require a particular final panic strategy, and can be freely
+            // linked to crates with either strategy (we need such ability for libstd and its
+            // dependencies).
+            return Some(PanicStrategy::Unwind);
+        }
+    }
+
+    // This crate can be linked with either runtime.
+    None
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers };
+}
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index b7caa61ef07..12fcb299ce3 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -59,6 +59,7 @@ pub mod dump_mir;
 mod early_otherwise_branch;
 mod elaborate_box_derefs;
 mod elaborate_drops;
+mod ffi_unwind_calls;
 mod function_item_references;
 mod generator;
 mod inline;
@@ -98,6 +99,7 @@ pub fn provide(providers: &mut Providers) {
     check_unsafety::provide(providers);
     check_packed_ref::provide(providers);
     coverage::query::provide(providers);
+    ffi_unwind_calls::provide(providers);
     shim::provide(providers);
     *providers = Providers {
         mir_keys,
@@ -223,6 +225,9 @@ fn mir_const<'tcx>(
         }
     }
 
+    // has_ffi_unwind_calls query uses the raw mir, so make sure it is run.
+    tcx.ensure().has_ffi_unwind_calls(def.did);
+
     let mut body = tcx.mir_built(def).steal();
 
     rustc_middle::mir::dump_mir(tcx, None, "mir_map", &0, &body, |_, _| Ok(()));