Auto merge of #102906 - nbdd0121:mir, r=wesleywiser,tmiasko
Refactor unwind in MIR This makes unwinding from current `Option<BasicBlock>` into ```rust enum UnwindAction { Continue, Cleanup(BasicBlock), Unreachable, Terminate, } ``` cc `@JakobDegen` `@RalfJung` `@Amanieu`
This commit is contained in:
commit
da14081468
294 changed files with 1225 additions and 933 deletions
|
@ -23,7 +23,7 @@ use rustc_target::spec::abi::Abi as CallAbi;
|
|||
|
||||
use crate::interpret::{
|
||||
self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx,
|
||||
InterpResult, OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
|
||||
InterpResult, OpTy, PlaceTy, Pointer, Scalar,
|
||||
};
|
||||
|
||||
use super::error::*;
|
||||
|
@ -271,7 +271,7 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
|
|||
/* with_caller_location = */ false,
|
||||
dest,
|
||||
ret,
|
||||
StackPopUnwind::NotAllowed,
|
||||
mir::UnwindAction::Unreachable,
|
||||
)?;
|
||||
Ok(ControlFlow::Break(()))
|
||||
} else {
|
||||
|
@ -401,7 +401,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
|||
args: &[OpTy<'tcx>],
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
_unwind: StackPopUnwind, // unwinding is not supported in consts
|
||||
_unwind: mir::UnwindAction, // unwinding is not supported in consts
|
||||
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
|
||||
debug!("find_mir_or_eval_fn: {:?}", instance);
|
||||
|
||||
|
@ -450,7 +450,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
|||
args: &[OpTy<'tcx>],
|
||||
dest: &PlaceTy<'tcx, Self::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
_unwind: StackPopUnwind,
|
||||
_unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Shared intrinsics.
|
||||
if ecx.emulate_intrinsic(instance, args, dest, target)? {
|
||||
|
@ -526,7 +526,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
|||
fn assert_panic(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, Self>,
|
||||
msg: &AssertMessage<'tcx>,
|
||||
_unwind: Option<mir::BasicBlock>,
|
||||
_unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx> {
|
||||
use rustc_middle::mir::AssertKind::*;
|
||||
// Convert `AssertKind<Operand>` to `AssertKind<Scalar>`.
|
||||
|
|
|
@ -139,17 +139,6 @@ pub struct FrameInfo<'tcx> {
|
|||
pub lint_root: Option<hir::HirId>,
|
||||
}
|
||||
|
||||
/// Unwind information.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub enum StackPopUnwind {
|
||||
/// The cleanup block.
|
||||
Cleanup(mir::BasicBlock),
|
||||
/// No cleanup needs to be done.
|
||||
Skip,
|
||||
/// Unwinding is not allowed (UB).
|
||||
NotAllowed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)] // Miri debug-prints these
|
||||
pub enum StackPopCleanup {
|
||||
/// Jump to the next block in the caller, or cause UB if None (that's a function
|
||||
|
@ -157,7 +146,7 @@ pub enum StackPopCleanup {
|
|||
/// we can validate it at that layout.
|
||||
/// `ret` stores the block we jump to on a normal return, while `unwind`
|
||||
/// stores the block used for cleanup during unwinding.
|
||||
Goto { ret: Option<mir::BasicBlock>, unwind: StackPopUnwind },
|
||||
Goto { ret: Option<mir::BasicBlock>, unwind: mir::UnwindAction },
|
||||
/// The root frame of the stack: nowhere else to jump to.
|
||||
/// `cleanup` says whether locals are deallocated. Static computation
|
||||
/// wants them leaked to intern what they need (and just throw away
|
||||
|
@ -735,18 +724,22 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
/// *Unwind* to the given `target` basic block.
|
||||
/// Do *not* use for returning! Use `return_to_block` instead.
|
||||
///
|
||||
/// If `target` is `StackPopUnwind::Skip`, that indicates the function does not need cleanup
|
||||
/// If `target` is `UnwindAction::Continue`, that indicates the function does not need cleanup
|
||||
/// during unwinding, and we will just keep propagating that upwards.
|
||||
///
|
||||
/// If `target` is `StackPopUnwind::NotAllowed`, that indicates the function does not allow
|
||||
/// If `target` is `UnwindAction::Unreachable`, that indicates the function does not allow
|
||||
/// unwinding, and doing so is UB.
|
||||
pub fn unwind_to_block(&mut self, target: StackPopUnwind) -> InterpResult<'tcx> {
|
||||
pub fn unwind_to_block(&mut self, target: mir::UnwindAction) -> InterpResult<'tcx> {
|
||||
self.frame_mut().loc = match target {
|
||||
StackPopUnwind::Cleanup(block) => Left(mir::Location { block, statement_index: 0 }),
|
||||
StackPopUnwind::Skip => Right(self.frame_mut().body.span),
|
||||
StackPopUnwind::NotAllowed => {
|
||||
mir::UnwindAction::Cleanup(block) => Left(mir::Location { block, statement_index: 0 }),
|
||||
mir::UnwindAction::Continue => Right(self.frame_mut().body.span),
|
||||
mir::UnwindAction::Unreachable => {
|
||||
throw_ub_format!("unwinding past a stack frame that does not allow unwinding")
|
||||
}
|
||||
mir::UnwindAction::Terminate => {
|
||||
self.frame_mut().loc = Right(self.frame_mut().body.span);
|
||||
M::abort(self, "panic in a function that cannot unwind".to_owned())?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::const_eval::CheckAlignment;
|
|||
|
||||
use super::{
|
||||
AllocBytes, AllocId, AllocRange, Allocation, ConstAllocation, Frame, ImmTy, InterpCx,
|
||||
InterpResult, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar, StackPopUnwind,
|
||||
InterpResult, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar,
|
||||
};
|
||||
|
||||
/// Data returned by Machine::stack_pop,
|
||||
|
@ -185,7 +185,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
|
|||
args: &[OpTy<'tcx, Self::Provenance>],
|
||||
destination: &PlaceTy<'tcx, Self::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
unwind: StackPopUnwind,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>>;
|
||||
|
||||
/// Execute `fn_val`. It is the hook's responsibility to advance the instruction
|
||||
|
@ -197,7 +197,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
|
|||
args: &[OpTy<'tcx, Self::Provenance>],
|
||||
destination: &PlaceTy<'tcx, Self::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
unwind: StackPopUnwind,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx>;
|
||||
|
||||
/// Directly process an intrinsic without pushing a stack frame. It is the hook's
|
||||
|
@ -208,17 +208,17 @@ pub trait Machine<'mir, 'tcx>: Sized {
|
|||
args: &[OpTy<'tcx, Self::Provenance>],
|
||||
destination: &PlaceTy<'tcx, Self::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
unwind: StackPopUnwind,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx>;
|
||||
|
||||
/// Called to evaluate `Assert` MIR terminators that trigger a panic.
|
||||
fn assert_panic(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, Self>,
|
||||
msg: &mir::AssertMessage<'tcx>,
|
||||
unwind: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx>;
|
||||
|
||||
/// Called to evaluate `Abort` MIR terminator.
|
||||
/// Called to abort evaluation.
|
||||
fn abort(_ecx: &mut InterpCx<'mir, 'tcx, Self>, _msg: String) -> InterpResult<'tcx, !> {
|
||||
throw_unsup_format!("aborting execution is not supported")
|
||||
}
|
||||
|
@ -487,7 +487,7 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
|
|||
_args: &[OpTy<$tcx>],
|
||||
_destination: &PlaceTy<$tcx, Self::Provenance>,
|
||||
_target: Option<mir::BasicBlock>,
|
||||
_unwind: StackPopUnwind,
|
||||
_unwind: mir::UnwindAction,
|
||||
) -> InterpResult<$tcx> {
|
||||
match fn_val {}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,7 @@ mod visitor;
|
|||
|
||||
pub use rustc_middle::mir::interpret::*; // have all the `interpret` symbols in one place: here
|
||||
|
||||
pub use self::eval_context::{
|
||||
Frame, FrameInfo, InterpCx, LocalState, LocalValue, StackPopCleanup, StackPopUnwind,
|
||||
};
|
||||
pub use self::eval_context::{Frame, FrameInfo, InterpCx, LocalState, LocalValue, StackPopCleanup};
|
||||
pub use self::intern::{intern_const_alloc_recursive, InternKind};
|
||||
pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump};
|
||||
pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};
|
||||
|
|
|
@ -13,7 +13,7 @@ use rustc_target::spec::abi::Abi;
|
|||
|
||||
use super::{
|
||||
FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand,
|
||||
PlaceTy, Scalar, StackPopCleanup, StackPopUnwind,
|
||||
PlaceTy, Scalar, StackPopCleanup,
|
||||
};
|
||||
|
||||
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
|
@ -60,7 +60,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
ref args,
|
||||
destination,
|
||||
target,
|
||||
ref cleanup,
|
||||
unwind,
|
||||
from_hir_call: _,
|
||||
fn_span: _,
|
||||
} => {
|
||||
|
@ -106,11 +106,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
with_caller_location,
|
||||
&destination,
|
||||
target,
|
||||
match (cleanup, fn_abi.can_unwind) {
|
||||
(Some(cleanup), true) => StackPopUnwind::Cleanup(*cleanup),
|
||||
(None, true) => StackPopUnwind::Skip,
|
||||
(_, false) => StackPopUnwind::NotAllowed,
|
||||
},
|
||||
if fn_abi.can_unwind { unwind } else { mir::UnwindAction::Unreachable },
|
||||
)?;
|
||||
// Sanity-check that `eval_fn_call` either pushed a new frame or
|
||||
// did a jump to another block.
|
||||
|
@ -137,19 +133,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
self.drop_in_place(&place, instance, target, unwind)?;
|
||||
}
|
||||
|
||||
Assert { ref cond, expected, ref msg, target, cleanup } => {
|
||||
Assert { ref cond, expected, ref msg, target, unwind } => {
|
||||
let ignored =
|
||||
M::ignore_optional_overflow_checks(self) && msg.is_optional_overflow_check();
|
||||
let cond_val = self.read_scalar(&self.eval_operand(cond, None)?)?.to_bool()?;
|
||||
if ignored || expected == cond_val {
|
||||
self.go_to_block(target);
|
||||
} else {
|
||||
M::assert_panic(self, msg, cleanup)?;
|
||||
M::assert_panic(self, msg, unwind)?;
|
||||
}
|
||||
}
|
||||
|
||||
Abort => {
|
||||
M::abort(self, "the program aborted execution".to_owned())?;
|
||||
Terminate => {
|
||||
// FIXME: maybe should call `panic_no_unwind` lang item instead.
|
||||
M::abort(self, "panic in a function that cannot unwind".to_owned())?;
|
||||
}
|
||||
|
||||
// When we encounter Resume, we've finished unwinding
|
||||
|
@ -351,7 +348,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
with_caller_location: bool,
|
||||
destination: &PlaceTy<'tcx, M::Provenance>,
|
||||
target: Option<mir::BasicBlock>,
|
||||
mut unwind: StackPopUnwind,
|
||||
mut unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx> {
|
||||
trace!("eval_fn_call: {:#?}", fn_val);
|
||||
|
||||
|
@ -410,9 +407,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
}
|
||||
}
|
||||
|
||||
if !matches!(unwind, StackPopUnwind::NotAllowed) && !callee_fn_abi.can_unwind {
|
||||
// The callee cannot unwind.
|
||||
unwind = StackPopUnwind::NotAllowed;
|
||||
if !callee_fn_abi.can_unwind {
|
||||
// The callee cannot unwind, so force the `Unreachable` unwind handling.
|
||||
unwind = mir::UnwindAction::Unreachable;
|
||||
}
|
||||
|
||||
self.push_stack_frame(
|
||||
|
@ -676,7 +673,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
place: &PlaceTy<'tcx, M::Provenance>,
|
||||
instance: ty::Instance<'tcx>,
|
||||
target: mir::BasicBlock,
|
||||
unwind: Option<mir::BasicBlock>,
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx> {
|
||||
trace!("drop_in_place: {:?},\n {:?}, {:?}", *place, place.layout.ty, instance);
|
||||
// We take the address of the object. This may well be unaligned, which is fine
|
||||
|
@ -717,10 +714,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
false,
|
||||
&ret.into(),
|
||||
Some(target),
|
||||
match unwind {
|
||||
Some(cleanup) => StackPopUnwind::Cleanup(cleanup),
|
||||
None => StackPopUnwind::Skip,
|
||||
},
|
||||
unwind,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1031,9 +1031,9 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
|||
self.check_op(ops::Generator(hir::GeneratorKind::Gen))
|
||||
}
|
||||
|
||||
TerminatorKind::Abort => {
|
||||
TerminatorKind::Terminate => {
|
||||
// Cleanup blocks are skipped for const checking (see `visit_basic_block_data`).
|
||||
span_bug!(self.span, "`Abort` terminator outside of cleanup block")
|
||||
span_bug!(self.span, "`Terminate` terminator outside of cleanup block")
|
||||
}
|
||||
|
||||
TerminatorKind::Assert { .. }
|
||||
|
|
|
@ -104,7 +104,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Abort
|
||||
mir::TerminatorKind::Terminate
|
||||
| mir::TerminatorKind::Call { .. }
|
||||
| mir::TerminatorKind::Assert { .. }
|
||||
| mir::TerminatorKind::FalseEdge { .. }
|
||||
|
|
|
@ -807,7 +807,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
|
|||
kind: TerminatorKind::Call {
|
||||
func,
|
||||
args,
|
||||
cleanup: None,
|
||||
unwind: UnwindAction::Continue,
|
||||
destination: Place::from(new_temp),
|
||||
target: Some(new_target),
|
||||
from_hir_call,
|
||||
|
|
|
@ -10,7 +10,7 @@ use rustc_middle::mir::{
|
|||
traversal, BasicBlock, BinOp, Body, BorrowKind, CastKind, CopyNonOverlapping, Local, Location,
|
||||
MirPass, MirPhase, NonDivergingIntrinsic, Operand, Place, PlaceElem, PlaceRef, ProjectionElem,
|
||||
RetagKind, RuntimePhase, Rvalue, SourceScope, Statement, StatementKind, Terminator,
|
||||
TerminatorKind, UnOp, VarDebugInfo, VarDebugInfoContents, START_BLOCK,
|
||||
TerminatorKind, UnOp, UnwindAction, VarDebugInfo, VarDebugInfoContents, START_BLOCK,
|
||||
};
|
||||
use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeVisitableExt};
|
||||
use rustc_mir_dataflow::impls::MaybeStorageLive;
|
||||
|
@ -232,6 +232,24 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_unwind_edge(&mut self, location: Location, unwind: UnwindAction) {
|
||||
let is_cleanup = self.body.basic_blocks[location.block].is_cleanup;
|
||||
match unwind {
|
||||
UnwindAction::Cleanup(unwind) => {
|
||||
if is_cleanup {
|
||||
self.fail(location, "unwind on cleanup block");
|
||||
}
|
||||
self.check_edge(location, unwind, EdgeKind::Unwind);
|
||||
}
|
||||
UnwindAction::Continue => {
|
||||
if is_cleanup {
|
||||
self.fail(location, "unwind on cleanup block");
|
||||
}
|
||||
}
|
||||
UnwindAction::Unreachable | UnwindAction::Terminate => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if src can be assigned into dest.
|
||||
/// This is not precise, it will accept some incorrect assignments.
|
||||
fn mir_assign_valid_types(&self, src: Ty<'tcx>, dest: Ty<'tcx>) -> bool {
|
||||
|
@ -902,11 +920,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
}
|
||||
TerminatorKind::Drop { target, unwind, .. } => {
|
||||
self.check_edge(location, *target, EdgeKind::Normal);
|
||||
if let Some(unwind) = unwind {
|
||||
self.check_edge(location, *unwind, EdgeKind::Unwind);
|
||||
}
|
||||
self.check_unwind_edge(location, *unwind);
|
||||
}
|
||||
TerminatorKind::Call { func, args, destination, target, cleanup, .. } => {
|
||||
TerminatorKind::Call { func, args, destination, target, unwind, .. } => {
|
||||
let func_ty = func.ty(&self.body.local_decls, self.tcx);
|
||||
match func_ty.kind() {
|
||||
ty::FnPtr(..) | ty::FnDef(..) => {}
|
||||
|
@ -918,9 +934,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
if let Some(target) = target {
|
||||
self.check_edge(location, *target, EdgeKind::Normal);
|
||||
}
|
||||
if let Some(cleanup) = cleanup {
|
||||
self.check_edge(location, *cleanup, EdgeKind::Unwind);
|
||||
}
|
||||
self.check_unwind_edge(location, *unwind);
|
||||
|
||||
// The call destination place and Operand::Move place used as an argument might be
|
||||
// passed by a reference to the callee. Consequently they must be non-overlapping.
|
||||
|
@ -946,7 +960,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
);
|
||||
}
|
||||
}
|
||||
TerminatorKind::Assert { cond, target, cleanup, .. } => {
|
||||
TerminatorKind::Assert { cond, target, unwind, .. } => {
|
||||
let cond_ty = cond.ty(&self.body.local_decls, self.tcx);
|
||||
if cond_ty != self.tcx.types.bool {
|
||||
self.fail(
|
||||
|
@ -958,9 +972,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
);
|
||||
}
|
||||
self.check_edge(location, *target, EdgeKind::Normal);
|
||||
if let Some(cleanup) = cleanup {
|
||||
self.check_edge(location, *cleanup, EdgeKind::Unwind);
|
||||
}
|
||||
self.check_unwind_edge(location, *unwind);
|
||||
}
|
||||
TerminatorKind::Yield { resume, drop, .. } => {
|
||||
if self.body.generator.is_none() {
|
||||
|
@ -992,17 +1004,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
);
|
||||
}
|
||||
self.check_edge(location, *real_target, EdgeKind::Normal);
|
||||
if let Some(unwind) = unwind {
|
||||
self.check_edge(location, *unwind, EdgeKind::Unwind);
|
||||
}
|
||||
self.check_unwind_edge(location, *unwind);
|
||||
}
|
||||
TerminatorKind::InlineAsm { destination, cleanup, .. } => {
|
||||
TerminatorKind::InlineAsm { destination, unwind, .. } => {
|
||||
if let Some(destination) = destination {
|
||||
self.check_edge(location, *destination, EdgeKind::Normal);
|
||||
}
|
||||
if let Some(cleanup) = cleanup {
|
||||
self.check_edge(location, *cleanup, EdgeKind::Unwind);
|
||||
}
|
||||
self.check_unwind_edge(location, *unwind);
|
||||
}
|
||||
TerminatorKind::GeneratorDrop => {
|
||||
if self.body.generator.is_none() {
|
||||
|
@ -1015,10 +1023,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
|||
);
|
||||
}
|
||||
}
|
||||
TerminatorKind::Resume | TerminatorKind::Abort => {
|
||||
TerminatorKind::Resume | TerminatorKind::Terminate => {
|
||||
let bb = location.block;
|
||||
if !self.body.basic_blocks[bb].is_cleanup {
|
||||
self.fail(location, "Cannot `Resume` or `Abort` from non-cleanup basic block")
|
||||
self.fail(
|
||||
location,
|
||||
"Cannot `Resume` or `Terminate` from non-cleanup basic block",
|
||||
)
|
||||
}
|
||||
}
|
||||
TerminatorKind::Return => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue