1use rustc_abi::ExternAbi;
2use rustc_ast::InlineAsmOptions;
3use rustc_hir::def_id::{LOCAL_CRATE, LocalDefId};
4use rustc_middle::mir::*;
5use rustc_middle::query::{LocalCrate, Providers};
6use rustc_middle::ty::{self, TyCtxt, layout};
7use rustc_middle::{bug, span_bug};
8use rustc_session::lint::builtin::FFI_UNWIND_CALLS;
9use rustc_target::spec::PanicStrategy;
10use tracing::debug;
1112use crate::errors;
1314// Check if the body of this def_id can possibly leak a foreign unwind into Rust code.
15fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
16{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_transform/src/ffi_unwind_calls.rs:16",
"rustc_mir_transform::ffi_unwind_calls",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_transform/src/ffi_unwind_calls.rs"),
::tracing_core::__macro_support::Option::Some(16u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::ffi_unwind_calls"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
let mut iter = __CALLSITE.metadata().fields().iter();
__CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
::tracing::__macro_support::Option::Some(&format_args!("has_ffi_unwind_calls({0:?})",
local_def_id) as &dyn Value))])
});
} else { ; }
};debug!("has_ffi_unwind_calls({local_def_id:?})");
1718// Only perform check on functions because constants cannot call FFI functions.
19let def_id = local_def_id.to_def_id();
20let kind = tcx.def_kind(def_id);
21if !kind.is_fn_like() {
22return false;
23 }
2425let body = &*tcx.mir_built(local_def_id).borrow();
2627let body_ty = tcx.type_of(def_id).skip_binder();
28let body_abi = match body_ty.kind() {
29 ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
30 ty::Closure(..) => ExternAbi::RustCall,
31 ty::CoroutineClosure(..) => ExternAbi::RustCall,
32 ty::Coroutine(..) => ExternAbi::Rust,
33 ty::Error(_) => return false,
34_ => ::rustc_middle::util::bug::span_bug_fmt(body.span,
format_args!("unexpected body ty: {0:?}", body_ty))span_bug!(body.span, "unexpected body ty: {:?}", body_ty),
35 };
36let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi);
3738// Foreign unwinds cannot leak past functions that themselves cannot unwind.
39if !body_can_unwind {
40return false;
41 }
4243let mut tainted = false;
4445for block in body.basic_blocks.iter() {
46if block.is_cleanup {
47continue;
48 }
49let Some(terminator) = &block.terminator else { continue };
5051if let TerminatorKind::InlineAsm { options, .. } = &terminator.kind {
52if options.contains(InlineAsmOptions::MAY_UNWIND) {
53// We have detected an inline asm block that can possibly leak foreign unwind.
54 //
55 // Because the function body itself can unwind, we are not aborting this function call
56 // upon unwind, so this call can possibly leak foreign unwind into Rust code if the
57 // panic runtime linked is panic-abort.
5859let lint_root = body.source_scopes[terminator.source_info.scope]
60 .local_data
61 .as_ref()
62 .unwrap_crate_local()
63 .lint_root;
64let span = terminator.source_info.span;
6566 tcx.emit_node_span_lint(
67 FFI_UNWIND_CALLS,
68 lint_root,
69 span,
70 errors::AsmUnwindCall { span },
71 );
7273 tainted = true;
74 }
75continue;
76 }
7778let TerminatorKind::Call { func, .. } = &terminator.kind else { continue };
7980let ty = func.ty(body, tcx);
81let sig = ty.fn_sig(tcx);
8283// Rust calls cannot themselves create foreign unwinds.
84 // We assume this is true for intrinsics as well.
85if sig.abi().is_rustic_abi() {
86continue;
87 };
8889let fn_def_id = match ty.kind() {
90 ty::FnPtr(..) => None,
91&ty::FnDef(def_id, _) => {
92// Rust calls cannot themselves create foreign unwinds (even if they use a non-Rust
93 // ABI). So the leak of the foreign unwind into Rust can only be elsewhere, not
94 // here.
95if !tcx.is_foreign_item(def_id) {
96continue;
97 }
98Some(def_id)
99 }
100_ => ::rustc_middle::util::bug::bug_fmt(format_args!("invalid callee of type {0:?}",
ty))bug!("invalid callee of type {:?}", ty),
101 };
102103if layout::fn_can_unwind(tcx, fn_def_id, sig.abi()) {
104// We have detected a call that can possibly leak foreign unwind.
105 //
106 // Because the function body itself can unwind, we are not aborting this function call
107 // upon unwind, so this call can possibly leak foreign unwind into Rust code if the
108 // panic runtime linked is panic-abort.
109110let lint_root = body.source_scopes[terminator.source_info.scope]
111 .local_data
112 .as_ref()
113 .unwrap_crate_local()
114 .lint_root;
115let span = terminator.source_info.span;
116117let foreign = fn_def_id.is_some();
118 tcx.emit_node_span_lint(
119 FFI_UNWIND_CALLS,
120 lint_root,
121 span,
122 errors::FfiUnwindCall { span, foreign },
123 );
124125 tainted = true;
126 }
127 }
128129tainted130}
131132fn required_panic_strategy(tcx: TyCtxt<'_>, _: LocalCrate) -> Option<PanicStrategy> {
133let local_strategy = tcx.sess.panic_strategy();
134135if tcx.is_panic_runtime(LOCAL_CRATE) {
136return Some(local_strategy);
137 }
138139match local_strategy {
140 PanicStrategy::Abort | PanicStrategy::ImmediateAbort => return Some(local_strategy),
141_ => {}
142 }
143144for def_id in tcx.hir_body_owners() {
145if tcx.has_ffi_unwind_calls(def_id) {
146// Given that this crate is compiled in `-C panic=unwind`, the `AbortUnwindingCalls`
147 // MIR pass will not be run on FFI-unwind call sites, therefore a foreign exception
148 // can enter Rust through these sites.
149 //
150 // On the other hand, crates compiled with `-C panic=abort` expects that all Rust
151 // functions cannot unwind (whether it's caused by Rust panic or foreign exception),
152 // and this expectation mismatch can cause unsoundness (#96926).
153 //
154 // To address this issue, we enforce that if FFI-unwind calls are used in a crate
155 // compiled with `panic=unwind`, then the final panic strategy must be `panic=unwind`.
156 // This will ensure that no crates will have wrong unwindability assumption.
157 //
158 // It should be noted that it is okay to link `panic=unwind` into a `panic=abort`
159 // program if it contains no FFI-unwind calls. In such case foreign exception can only
160 // enter Rust in a `panic=abort` crate, which will lead to an abort. There will also
161 // be no exceptions generated from Rust, so the assumption which `panic=abort` crates
162 // make, that no Rust function can unwind, indeed holds for crates compiled with
163 // `panic=unwind` as well. In such case this function returns `None`, indicating that
164 // the crate does not require a particular final panic strategy, and can be freely
165 // linked to crates with either strategy (we need such ability for libstd and its
166 // dependencies).
167return Some(PanicStrategy::Unwind);
168 }
169 }
170171// This crate can be linked with either runtime.
172None173}
174175pub(crate) fn provide(providers: &mut Providers) {
176*providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers };
177}