Add MIR validation for unwind out from nounwind functions
This commit is contained in:
parent
cec8e09edf
commit
56b933763e
@ -18,6 +18,7 @@ use rustc_mir_dataflow::impls::MaybeStorageLive;
|
|||||||
use rustc_mir_dataflow::storage::always_storage_live_locals;
|
use rustc_mir_dataflow::storage::always_storage_live_locals;
|
||||||
use rustc_mir_dataflow::{Analysis, ResultsCursor};
|
use rustc_mir_dataflow::{Analysis, ResultsCursor};
|
||||||
use rustc_target::abi::{Size, FIRST_VARIANT};
|
use rustc_target::abi::{Size, FIRST_VARIANT};
|
||||||
|
use rustc_target::spec::abi::Abi;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
enum EdgeKind {
|
enum EdgeKind {
|
||||||
@ -58,6 +59,25 @@ impl<'tcx> MirPass<'tcx> for Validator {
|
|||||||
.iterate_to_fixpoint()
|
.iterate_to_fixpoint()
|
||||||
.into_results_cursor(body);
|
.into_results_cursor(body);
|
||||||
|
|
||||||
|
let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) {
|
||||||
|
// In this case `AbortUnwindingCalls` haven't yet been executed.
|
||||||
|
true
|
||||||
|
} else if !tcx.def_kind(def_id).is_fn_like() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let body_ty = tcx.type_of(def_id).skip_binder();
|
||||||
|
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: {:?} phase {:?}", body_ty, mir_phase)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ty::layout::fn_can_unwind(tcx, Some(def_id), body_abi)
|
||||||
|
};
|
||||||
|
|
||||||
let mut cfg_checker = CfgChecker {
|
let mut cfg_checker = CfgChecker {
|
||||||
when: &self.when,
|
when: &self.when,
|
||||||
body,
|
body,
|
||||||
@ -68,6 +88,7 @@ impl<'tcx> MirPass<'tcx> for Validator {
|
|||||||
storage_liveness,
|
storage_liveness,
|
||||||
place_cache: FxHashSet::default(),
|
place_cache: FxHashSet::default(),
|
||||||
value_cache: FxHashSet::default(),
|
value_cache: FxHashSet::default(),
|
||||||
|
can_unwind,
|
||||||
};
|
};
|
||||||
cfg_checker.visit_body(body);
|
cfg_checker.visit_body(body);
|
||||||
cfg_checker.check_cleanup_control_flow();
|
cfg_checker.check_cleanup_control_flow();
|
||||||
@ -99,6 +120,9 @@ struct CfgChecker<'a, 'tcx> {
|
|||||||
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
|
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
|
||||||
place_cache: FxHashSet<PlaceRef<'tcx>>,
|
place_cache: FxHashSet<PlaceRef<'tcx>>,
|
||||||
value_cache: FxHashSet<u128>,
|
value_cache: FxHashSet<u128>,
|
||||||
|
// If `false`, then the MIR must not contain `UnwindAction::Continue` or
|
||||||
|
// `TerminatorKind::Resume`.
|
||||||
|
can_unwind: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
|
impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
|
||||||
@ -237,13 +261,17 @@ impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
|
|||||||
match unwind {
|
match unwind {
|
||||||
UnwindAction::Cleanup(unwind) => {
|
UnwindAction::Cleanup(unwind) => {
|
||||||
if is_cleanup {
|
if is_cleanup {
|
||||||
self.fail(location, "unwind on cleanup block");
|
self.fail(location, "`UnwindAction::Cleanup` in cleanup block");
|
||||||
}
|
}
|
||||||
self.check_edge(location, unwind, EdgeKind::Unwind);
|
self.check_edge(location, unwind, EdgeKind::Unwind);
|
||||||
}
|
}
|
||||||
UnwindAction::Continue => {
|
UnwindAction::Continue => {
|
||||||
if is_cleanup {
|
if is_cleanup {
|
||||||
self.fail(location, "unwind on cleanup block");
|
self.fail(location, "`UnwindAction::Continue` in cleanup block");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.can_unwind {
|
||||||
|
self.fail(location, "`UnwindAction::Continue` in no-unwind function");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UnwindAction::Unreachable | UnwindAction::Terminate => (),
|
UnwindAction::Unreachable | UnwindAction::Terminate => (),
|
||||||
@ -464,13 +492,19 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TerminatorKind::Resume | TerminatorKind::Terminate => {
|
TerminatorKind::Resume => {
|
||||||
let bb = location.block;
|
let bb = location.block;
|
||||||
if !self.body.basic_blocks[bb].is_cleanup {
|
if !self.body.basic_blocks[bb].is_cleanup {
|
||||||
self.fail(
|
self.fail(location, "Cannot `Resume` from non-cleanup basic block")
|
||||||
location,
|
}
|
||||||
"Cannot `Resume` or `Terminate` from non-cleanup basic block",
|
if !self.can_unwind {
|
||||||
)
|
self.fail(location, "Cannot `Resume` in a function that cannot unwind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TerminatorKind::Terminate => {
|
||||||
|
let bb = location.block;
|
||||||
|
if !self.body.basic_blocks[bb].is_cleanup {
|
||||||
|
self.fail(location, "Cannot `Terminate` from non-cleanup basic block")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TerminatorKind::Return => {
|
TerminatorKind::Return => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user