]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/ffi_unwind_calls.rs
d09d2a0b26346ad878b52dfb3864bbab75733910
[rust.git] / compiler / rustc_mir_transform / src / ffi_unwind_calls.rs
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;
9
10 fn abi_can_unwind(abi: Abi) -> bool {
11     use Abi::*;
12     match abi {
13         C { unwind }
14         | System { unwind }
15         | Cdecl { unwind }
16         | Stdcall { unwind }
17         | Fastcall { unwind }
18         | Vectorcall { unwind }
19         | Thiscall { unwind }
20         | Aapcs { unwind }
21         | Win64 { unwind }
22         | SysV64 { unwind } => unwind,
23         PtxKernel
24         | Msp430Interrupt
25         | X86Interrupt
26         | AmdGpuKernel
27         | EfiApi
28         | AvrInterrupt
29         | AvrNonBlockingInterrupt
30         | CCmseNonSecureCall
31         | Wasm
32         | RustIntrinsic
33         | PlatformIntrinsic
34         | Unadjusted => false,
35         Rust | RustCall | RustCold => true,
36     }
37 }
38
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:?})");
42
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() {
47         return false;
48     }
49
50     let body = &*tcx.mir_built(ty::WithOptConstParam::unknown(local_def_id)).borrow();
51
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),
58     };
59     let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi);
60
61     // Foreign unwinds cannot leak past functions that themselves cannot unwind.
62     if !body_can_unwind {
63         return false;
64     }
65
66     let mut tainted = false;
67
68     for block in body.basic_blocks() {
69         if block.is_cleanup {
70             continue;
71         }
72         let Some(terminator) = &block.terminator else { continue };
73         let TerminatorKind::Call { func, .. } = &terminator.kind else { continue };
74
75         let ty = func.ty(body, tcx);
76         let sig = ty.fn_sig(tcx);
77
78         // Rust calls cannot themselves create foreign unwinds.
79         if let Abi::Rust | Abi::RustCall | Abi::RustCold = sig.abi() {
80             continue;
81         };
82
83         let fn_def_id = match ty.kind() {
84             ty::FnPtr(_) => None,
85             &ty::FnDef(def_id, _) => {
86                 // Rust calls cannot themselves create foreign unwinds.
87                 if !tcx.is_foreign_item(def_id) {
88                     continue;
89                 }
90                 Some(def_id)
91             }
92             _ => bug!("invalid callee of type {:?}", ty),
93         };
94
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.
97             //
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.
101
102             let lint_root = body.source_scopes[terminator.source_info.scope]
103                 .local_data
104                 .as_ref()
105                 .assert_crate_local()
106                 .lint_root;
107             let span = terminator.source_info.span;
108
109             tcx.struct_span_lint_hir(FFI_UNWIND_CALLS, lint_root, span, |lint| {
110                 let msg = match fn_def_id {
111                     Some(_) => "call to foreign function with FFI-unwind ABI",
112                     None => "call to function pointer with FFI-unwind ABI",
113                 };
114                 let mut db = lint.build(msg);
115                 db.span_label(span, msg);
116                 db.emit();
117             });
118
119             tainted = true;
120         }
121     }
122
123     tainted
124 }
125
126 fn required_panic_strategy(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<PanicStrategy> {
127     assert_eq!(cnum, LOCAL_CRATE);
128
129     if tcx.is_panic_runtime(LOCAL_CRATE) {
130         return Some(tcx.sess.panic_strategy());
131     }
132
133     if tcx.sess.panic_strategy() == PanicStrategy::Abort {
134         return Some(PanicStrategy::Abort);
135     }
136
137     for def_id in tcx.hir().body_owners() {
138         if tcx.has_ffi_unwind_calls(def_id) {
139             return Some(PanicStrategy::Unwind);
140         }
141     }
142
143     // This crate can be linked with either runtime.
144     None
145 }
146
147 pub(crate) fn provide(providers: &mut Providers) {
148     *providers = Providers { has_ffi_unwind_calls, required_panic_strategy, ..*providers };
149 }