1 use rustc_hir::def_id::{CrateNum, LocalDefId, LOCAL_CRATE};
2 use rustc_middle::mir::*;
3 use rustc_middle::ty::layout;
4 use rustc_middle::ty::query::Providers;
5 use rustc_middle::ty::{self, TyCtxt};
6 use rustc_session::lint::builtin::FFI_UNWIND_CALLS;
7 use rustc_target::spec::abi::Abi;
8 use rustc_target::spec::PanicStrategy;
10 fn abi_can_unwind(abi: Abi) -> bool {
18 | Vectorcall { unwind }
22 | SysV64 { unwind } => unwind,
29 | AvrNonBlockingInterrupt
34 | Unadjusted => false,
35 Rust | RustCall | RustCold => true,
39 // Check if the body of this def_id can possibly leak a foreign unwind into Rust code.
40 fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
41 debug!("has_ffi_unwind_calls({local_def_id:?})");
43 // Only perform check on functions because constants cannot call FFI functions.
44 let def_id = local_def_id.to_def_id();
45 let kind = tcx.def_kind(def_id);
46 if !kind.is_fn_like() {
50 let body = &*tcx.mir_built(ty::WithOptConstParam::unknown(local_def_id)).borrow();
52 let body_ty = tcx.type_of(def_id);
53 let body_abi = match body_ty.kind() {
54 ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
55 ty::Closure(..) => Abi::RustCall,
56 ty::Generator(..) => Abi::Rust,
57 _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty),
59 let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi);
61 // Foreign unwinds cannot leak past functions that themselves cannot unwind.
66 let mut tainted = false;
68 for block in body.basic_blocks.iter() {
72 let Some(terminator) = &block.terminator else { continue };
73 let TerminatorKind::Call { func, .. } = &terminator.kind else { continue };
75 let ty = func.ty(body, tcx);
76 let sig = ty.fn_sig(tcx);
78 // Rust calls cannot themselves create foreign unwinds.
79 if let Abi::Rust | Abi::RustCall | Abi::RustCold = sig.abi() {
83 let fn_def_id = match ty.kind() {
85 &ty::FnDef(def_id, _) => {
86 // Rust calls cannot themselves create foreign unwinds.
87 if !tcx.is_foreign_item(def_id) {
92 _ => bug!("invalid callee of type {:?}", ty),
95 if layout::fn_can_unwind(tcx, fn_def_id, sig.abi()) && abi_can_unwind(sig.abi()) {
96 // We have detected a call that can possibly leak foreign unwind.
98 // Because the function body itself can unwind, we are not aborting this function call
99 // upon unwind, so this call can possibly leak foreign unwind into Rust code if the
100 // panic runtime linked is panic-abort.
102 let lint_root = body.source_scopes[terminator.source_info.scope]
105 .assert_crate_local()
107 let span = terminator.source_info.span;
109 let msg = match fn_def_id {
110 Some(_) => "call to foreign function with FFI-unwind ABI",
111 None => "call to function pointer with FFI-unwind ABI",
113 tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, msg, |lint| {
114 lint.span_label(span, msg)
124 fn required_panic_strategy(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<PanicStrategy> {
125 assert_eq!(cnum, LOCAL_CRATE);
127 if tcx.is_panic_runtime(LOCAL_CRATE) {
128 return Some(tcx.sess.panic_strategy());
131 if tcx.sess.panic_strategy() == PanicStrategy::Abort {
132 return Some(PanicStrategy::Abort);
135 for def_id in tcx.hir().body_owners() {
136 if tcx.has_ffi_unwind_calls(def_id) {
137 // Given that this crate is compiled in `-C panic=unwind`, the `AbortUnwindingCalls`
138 // MIR pass will not be run on FFI-unwind call sites, therefore a foreign exception
139 // can enter Rust through these sites.
141 // On the other hand, crates compiled with `-C panic=abort` expects that all Rust
142 // functions cannot unwind (whether it's caused by Rust panic or foreign exception),
143 // and this expectation mismatch can cause unsoundness (#96926).
145 // To address this issue, we enforce that if FFI-unwind calls are used in a crate
146 // compiled with `panic=unwind`, then the final panic strategy must be `panic=unwind`.
147 // This will ensure that no crates will have wrong unwindability assumption.
149 // It should be noted that it is okay to link `panic=unwind` into a `panic=abort`
150 // program if it contains no FFI-unwind calls. In such case foreign exception can only
151 // enter Rust in a `panic=abort` crate, which will lead to an abort. There will also
152 // be no exceptions generated from Rust, so the assumption which `panic=abort` crates
153 // make, that no Rust function can unwind, indeed holds for crates compiled with
154 // `panic=unwind` as well. In such case this function returns `None`, indicating that
155 // the crate does not require a particular final panic strategy, and can be freely
156 // linked to crates with either strategy (we need such ability for libstd and its
158 return Some(PanicStrategy::Unwind);
162 // This crate can be linked with either runtime.
166 pub(crate) fn provide(providers: &mut Providers) {
167 *providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers };