Skip to content

const-eval: disallow unwinding across functions that !fn_can_unwind() #85546

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from May 28, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 34 additions & 30 deletions compiler/rustc_mir/src/interpret/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,13 +757,20 @@ 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 `None`, that indicates the function does not need cleanup during
/// unwinding, and we will just keep propagating that upwards.
pub fn unwind_to_block(&mut self, target: Option<mir::BasicBlock>) {
/// If `target` is `StackPopUnwind::Skip`, 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
/// unwinding, and doing so is UB.
pub fn unwind_to_block(&mut self, target: StackPopUnwind) -> InterpResult<'tcx> {
self.frame_mut().loc = match target {
Some(block) => Ok(mir::Location { block, statement_index: 0 }),
None => Err(self.frame_mut().body.span),
StackPopUnwind::Cleanup(block) => Ok(mir::Location { block, statement_index: 0 }),
StackPopUnwind::Skip => Err(self.frame_mut().body.span),
StackPopUnwind::NotAllowed => {
throw_ub_format!("unwinding past a frame that does not allow unwinding")
}
};
Ok(())
}

/// Pops the current frame from the stack, deallocating the
Expand Down Expand Up @@ -798,27 +805,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
throw_ub_format!("unwinding past the topmost frame of the stack");
}

// Where do we jump next?

// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
// In that case, we return early. We also avoid validation in that case,
// because this is CTFE and the final value will be thoroughly validated anyway.
let (cleanup, next_block) = match (self.frame().return_to_block, unwinding) {
(StackPopCleanup::Goto { ret, .. }, false) => (true, Some(ret)),
(StackPopCleanup::Goto { unwind, .. }, true) => (
true,
Some(match unwind {
StackPopUnwind::Cleanup(unwind) => Some(unwind),
StackPopUnwind::Skip => None,
StackPopUnwind::NotAllowed => {
throw_ub_format!("unwinding past a frame that does not allow unwinding")
}
}),
),
(StackPopCleanup::None { cleanup, .. }, _) => (cleanup, None),
};

let frame = self.stack_mut().pop().unwrap();
let frame =
self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");

if !unwinding {
// Copy the return value to the caller's stack frame.
Expand All @@ -831,9 +819,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}

let return_to_block = frame.return_to_block;

// Now where do we jump next?

// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
// In that case, we return early. We also avoid validation in that case,
// because this is CTFE and the final value will be thoroughly validated anyway.
let cleanup = match return_to_block {
StackPopCleanup::Goto { .. } => true,
StackPopCleanup::None { cleanup, .. } => cleanup,
};

if !cleanup {
assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked");
assert!(next_block.is_none(), "tried to skip cleanup when we have a next block!");
assert!(!unwinding, "tried to skip cleanup during unwinding");
// Leak the locals, skip validation, skip machine hook.
return Ok(());
Expand All @@ -852,11 +851,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Normal return, figure out where to jump.
if unwinding {
// Follow the unwind edge.
let unwind = next_block.expect("Encountered StackPopCleanup::None when unwinding!");
self.unwind_to_block(unwind);
let unwind = match return_to_block {
StackPopCleanup::Goto { unwind, .. } => unwind,
StackPopCleanup::None { .. } => {
panic!("Encountered StackPopCleanup::None when unwinding!")
}
};
self.unwind_to_block(unwind)?;
} else {
// Follow the normal return edge.
if let Some(ret) = next_block {
if let StackPopCleanup::Goto { ret, .. } = return_to_block {
self.return_to_block(ret)?;
}
}
Expand Down
13 changes: 8 additions & 5 deletions compiler/rustc_mir/src/interpret/terminator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
abi,
&args[..],
ret,
if caller_can_unwind {
cleanup.map_or(StackPopUnwind::Skip, StackPopUnwind::Cleanup)
} else {
StackPopUnwind::NotAllowed
match (cleanup, caller_can_unwind) {
(Some(cleanup), true) => StackPopUnwind::Cleanup(*cleanup),
(None, true) => StackPopUnwind::Skip,
(_, false) => StackPopUnwind::NotAllowed,
},
)?;
// Sanity-check that `eval_fn_call` either pushed a new frame or
Expand Down Expand Up @@ -511,7 +511,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Abi::Rust,
&[arg.into()],
Some((&dest.into(), target)),
unwind.map_or(StackPopUnwind::Skip, StackPopUnwind::Cleanup),
match unwind {
Some(cleanup) => StackPopUnwind::Cleanup(cleanup),
None => StackPopUnwind::Skip,
},
)
}
}