Skip to content

Commit ce0d64e

Browse files
committed
Auto merge of #85546 - hyd-dev:unwind, r=RalfJung
const-eval: disallow unwinding across functions that `!fn_can_unwind()` Following rust-lang/miri#1776 (comment), so r? `@RalfJung` This PR turns `unwind` in `StackPopCleanup::Goto` into a new enum `StackPopUnwind`, with a `NotAllowed` variant to indicate that unwinding is not allowed. This variant is chosen based on `rustc_middle::ty::layout::fn_can_unwind()` in `eval_fn_call()` when pushing the frame. A check is added in `unwind_to_block()` to report UB if unwinding happens across a `StackPopUnwind::NotAllowed` frame. Tested with Miri `HEAD` with [minor changes](https://github.com/rust-lang/miri/compare/HEAD..9cf3c7f0d86325a586fbcbf2acdc9232b861f1d8) and the rust-lang/miri#1776 branch with [these changes](https://github.com/rust-lang/miri/compare/d866c1c52f48bf562720383455b75c257bb1ad92..626638fbfe2fff34648dda29a34d59db498a6e52).
2 parents 18135ec + f6348f1 commit ce0d64e

File tree

7 files changed

+168
-94
lines changed

7 files changed

+168
-94
lines changed

compiler/rustc_middle/src/ty/layout.rs

+41-34
Original file line numberDiff line numberDiff line change
@@ -2579,7 +2579,7 @@ where
25792579
fn adjust_for_abi(&mut self, cx: &C, abi: SpecAbi);
25802580
}
25812581

2582-
fn fn_can_unwind(
2582+
pub fn fn_can_unwind(
25832583
panic_strategy: PanicStrategy,
25842584
codegen_fn_attr_flags: CodegenFnAttrFlags,
25852585
call_conv: Conv,
@@ -2641,6 +2641,43 @@ fn fn_can_unwind(
26412641
}
26422642
}
26432643

2644+
pub fn conv_from_spec_abi(tcx: TyCtxt<'_>, abi: SpecAbi) -> Conv {
2645+
use rustc_target::spec::abi::Abi::*;
2646+
match tcx.sess.target.adjust_abi(abi) {
2647+
RustIntrinsic | PlatformIntrinsic | Rust | RustCall => Conv::Rust,
2648+
2649+
// It's the ABI's job to select this, not ours.
2650+
System { .. } => bug!("system abi should be selected elsewhere"),
2651+
EfiApi => bug!("eficall abi should be selected elsewhere"),
2652+
2653+
Stdcall { .. } => Conv::X86Stdcall,
2654+
Fastcall => Conv::X86Fastcall,
2655+
Vectorcall => Conv::X86VectorCall,
2656+
Thiscall { .. } => Conv::X86ThisCall,
2657+
C { .. } => Conv::C,
2658+
Unadjusted => Conv::C,
2659+
Win64 => Conv::X86_64Win64,
2660+
SysV64 => Conv::X86_64SysV,
2661+
Aapcs => Conv::ArmAapcs,
2662+
CCmseNonSecureCall => Conv::CCmseNonSecureCall,
2663+
PtxKernel => Conv::PtxKernel,
2664+
Msp430Interrupt => Conv::Msp430Intr,
2665+
X86Interrupt => Conv::X86Intr,
2666+
AmdGpuKernel => Conv::AmdGpuKernel,
2667+
AvrInterrupt => Conv::AvrInterrupt,
2668+
AvrNonBlockingInterrupt => Conv::AvrNonBlockingInterrupt,
2669+
Wasm => Conv::C,
2670+
2671+
// These API constants ought to be more specific...
2672+
Cdecl => Conv::C,
2673+
}
2674+
}
2675+
2676+
pub fn fn_ptr_codegen_fn_attr_flags() -> CodegenFnAttrFlags {
2677+
// Assume that fn pointers may always unwind
2678+
CodegenFnAttrFlags::UNWIND
2679+
}
2680+
26442681
impl<'tcx, C> FnAbiExt<'tcx, C> for call::FnAbi<'tcx, Ty<'tcx>>
26452682
where
26462683
C: LayoutOf<Ty = Ty<'tcx>, TyAndLayout = TyAndLayout<'tcx>>
@@ -2650,10 +2687,7 @@ where
26502687
+ HasParamEnv<'tcx>,
26512688
{
26522689
fn of_fn_ptr(cx: &C, sig: ty::PolyFnSig<'tcx>, extra_args: &[Ty<'tcx>]) -> Self {
2653-
// Assume that fn pointers may always unwind
2654-
let codegen_fn_attr_flags = CodegenFnAttrFlags::UNWIND;
2655-
2656-
call::FnAbi::new_internal(cx, sig, extra_args, None, codegen_fn_attr_flags, false)
2690+
call::FnAbi::new_internal(cx, sig, extra_args, None, fn_ptr_codegen_fn_attr_flags(), false)
26572691
}
26582692

26592693
fn of_instance(cx: &C, instance: ty::Instance<'tcx>, extra_args: &[Ty<'tcx>]) -> Self {
@@ -2689,35 +2723,7 @@ where
26892723

26902724
let sig = cx.tcx().normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), sig);
26912725

2692-
use rustc_target::spec::abi::Abi::*;
2693-
let conv = match cx.tcx().sess.target.adjust_abi(sig.abi) {
2694-
RustIntrinsic | PlatformIntrinsic | Rust | RustCall => Conv::Rust,
2695-
2696-
// It's the ABI's job to select this, not ours.
2697-
System { .. } => bug!("system abi should be selected elsewhere"),
2698-
EfiApi => bug!("eficall abi should be selected elsewhere"),
2699-
2700-
Stdcall { .. } => Conv::X86Stdcall,
2701-
Fastcall => Conv::X86Fastcall,
2702-
Vectorcall => Conv::X86VectorCall,
2703-
Thiscall { .. } => Conv::X86ThisCall,
2704-
C { .. } => Conv::C,
2705-
Unadjusted => Conv::C,
2706-
Win64 => Conv::X86_64Win64,
2707-
SysV64 => Conv::X86_64SysV,
2708-
Aapcs => Conv::ArmAapcs,
2709-
CCmseNonSecureCall => Conv::CCmseNonSecureCall,
2710-
PtxKernel => Conv::PtxKernel,
2711-
Msp430Interrupt => Conv::Msp430Intr,
2712-
X86Interrupt => Conv::X86Intr,
2713-
AmdGpuKernel => Conv::AmdGpuKernel,
2714-
AvrInterrupt => Conv::AvrInterrupt,
2715-
AvrNonBlockingInterrupt => Conv::AvrNonBlockingInterrupt,
2716-
Wasm => Conv::C,
2717-
2718-
// These API constants ought to be more specific...
2719-
Cdecl => Conv::C,
2720-
};
2726+
let conv = conv_from_spec_abi(cx.tcx(), sig.abi);
27212727

27222728
let mut inputs = sig.inputs();
27232729
let extra_args = if sig.abi == RustCall {
@@ -2753,6 +2759,7 @@ where
27532759
target.os == "linux" && target.arch == "sparc64" && target_env_gnu_like;
27542760
let linux_powerpc_gnu_like =
27552761
target.os == "linux" && target.arch == "powerpc" && target_env_gnu_like;
2762+
use SpecAbi::*;
27562763
let rust_abi = matches!(sig.abi, RustIntrinsic | PlatformIntrinsic | Rust | RustCall);
27572764

27582765
// Handle safe Rust thin and fat pointers.

compiler/rustc_mir/src/const_eval/machine.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_target::spec::abi::Abi;
1717

1818
use crate::interpret::{
1919
self, compile_time_machine, AllocId, Allocation, Frame, ImmTy, InterpCx, InterpResult, Memory,
20-
OpTy, PlaceTy, Pointer, Scalar,
20+
OpTy, PlaceTy, Pointer, Scalar, StackPopUnwind,
2121
};
2222

2323
use super::error::*;
@@ -223,7 +223,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
223223
_abi: Abi,
224224
args: &[OpTy<'tcx>],
225225
_ret: Option<(&PlaceTy<'tcx>, mir::BasicBlock)>,
226-
_unwind: Option<mir::BasicBlock>, // unwinding is not supported in consts
226+
_unwind: StackPopUnwind, // unwinding is not supported in consts
227227
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
228228
debug!("find_mir_or_eval_fn: {:?}", instance);
229229

@@ -263,7 +263,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
263263
instance: ty::Instance<'tcx>,
264264
args: &[OpTy<'tcx>],
265265
ret: Option<(&PlaceTy<'tcx>, mir::BasicBlock)>,
266-
_unwind: Option<mir::BasicBlock>,
266+
_unwind: StackPopUnwind,
267267
) -> InterpResult<'tcx> {
268268
// Shared intrinsics.
269269
if ecx.emulate_intrinsic(instance, args, ret)? {

compiler/rustc_mir/src/interpret/eval_context.rs

+40-19
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,25 @@ pub struct FrameInfo<'tcx> {
134134
pub lint_root: Option<hir::HirId>,
135135
}
136136

137-
#[derive(Clone, Eq, PartialEq, Debug, HashStable)] // Miri debug-prints these
137+
/// Unwind information.
138+
#[derive(Clone, Copy, Eq, PartialEq, Debug, HashStable)]
139+
pub enum StackPopUnwind {
140+
/// The cleanup block.
141+
Cleanup(mir::BasicBlock),
142+
/// No cleanup needs to be done.
143+
Skip,
144+
/// Unwinding is not allowed (UB).
145+
NotAllowed,
146+
}
147+
148+
#[derive(Clone, Copy, Eq, PartialEq, Debug, HashStable)] // Miri debug-prints these
138149
pub enum StackPopCleanup {
139150
/// Jump to the next block in the caller, or cause UB if None (that's a function
140151
/// that may never return). Also store layout of return place so
141152
/// we can validate it at that layout.
142153
/// `ret` stores the block we jump to on a normal return, while `unwind`
143154
/// stores the block used for cleanup during unwinding.
144-
Goto { ret: Option<mir::BasicBlock>, unwind: Option<mir::BasicBlock> },
155+
Goto { ret: Option<mir::BasicBlock>, unwind: StackPopUnwind },
145156
/// Just do nothing: Used by Main and for the `box_alloc` hook in miri.
146157
/// `cleanup` says whether locals are deallocated. Static computation
147158
/// wants them leaked to intern what they need (and just throw away
@@ -746,13 +757,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
746757
/// *Unwind* to the given `target` basic block.
747758
/// Do *not* use for returning! Use `return_to_block` instead.
748759
///
749-
/// If `target` is `None`, that indicates the function does not need cleanup during
750-
/// unwinding, and we will just keep propagating that upwards.
751-
pub fn unwind_to_block(&mut self, target: Option<mir::BasicBlock>) {
760+
/// If `target` is `StackPopUnwind::Skip`, that indicates the function does not need cleanup
761+
/// during unwinding, and we will just keep propagating that upwards.
762+
///
763+
/// If `target` is `StackPopUnwind::NotAllowed`, that indicates the function does not allow
764+
/// unwinding, and doing so is UB.
765+
pub fn unwind_to_block(&mut self, target: StackPopUnwind) -> InterpResult<'tcx> {
752766
self.frame_mut().loc = match target {
753-
Some(block) => Ok(mir::Location { block, statement_index: 0 }),
754-
None => Err(self.frame_mut().body.span),
767+
StackPopUnwind::Cleanup(block) => Ok(mir::Location { block, statement_index: 0 }),
768+
StackPopUnwind::Skip => Err(self.frame_mut().body.span),
769+
StackPopUnwind::NotAllowed => {
770+
throw_ub_format!("unwinding past a stack frame that does not allow unwinding")
771+
}
755772
};
773+
Ok(())
756774
}
757775

758776
/// Pops the current frame from the stack, deallocating the
@@ -801,21 +819,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
801819
}
802820
}
803821

822+
let return_to_block = frame.return_to_block;
823+
804824
// Now where do we jump next?
805825

806826
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
807827
// In that case, we return early. We also avoid validation in that case,
808828
// because this is CTFE and the final value will be thoroughly validated anyway.
809-
let (cleanup, next_block) = match frame.return_to_block {
810-
StackPopCleanup::Goto { ret, unwind } => {
811-
(true, Some(if unwinding { unwind } else { ret }))
812-
}
813-
StackPopCleanup::None { cleanup, .. } => (cleanup, None),
829+
let cleanup = match return_to_block {
830+
StackPopCleanup::Goto { .. } => true,
831+
StackPopCleanup::None { cleanup, .. } => cleanup,
814832
};
815833

816834
if !cleanup {
817835
assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked");
818-
assert!(next_block.is_none(), "tried to skip cleanup when we have a next block!");
819836
assert!(!unwinding, "tried to skip cleanup during unwinding");
820837
// Leak the locals, skip validation, skip machine hook.
821838
return Ok(());
@@ -834,16 +851,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
834851
// Normal return, figure out where to jump.
835852
if unwinding {
836853
// Follow the unwind edge.
837-
let unwind = next_block.expect("Encountered StackPopCleanup::None when unwinding!");
838-
self.unwind_to_block(unwind);
854+
let unwind = match return_to_block {
855+
StackPopCleanup::Goto { unwind, .. } => unwind,
856+
StackPopCleanup::None { .. } => {
857+
panic!("Encountered StackPopCleanup::None when unwinding!")
858+
}
859+
};
860+
self.unwind_to_block(unwind)
839861
} else {
840862
// Follow the normal return edge.
841-
if let Some(ret) = next_block {
842-
self.return_to_block(ret)?;
863+
match return_to_block {
864+
StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret),
865+
StackPopCleanup::None { .. } => Ok(()),
843866
}
844867
}
845-
846-
Ok(())
847868
}
848869

849870
/// Mark a storage as live, killing the previous content.

compiler/rustc_mir/src/interpret/machine.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_target::spec::abi::Abi;
1414

1515
use super::{
1616
AllocId, Allocation, CheckInAllocMsg, Frame, ImmTy, InterpCx, InterpResult, LocalValue,
17-
MemPlace, Memory, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Scalar,
17+
MemPlace, Memory, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Scalar, StackPopUnwind,
1818
};
1919

2020
/// Data returned by Machine::stack_pop,
@@ -163,7 +163,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
163163
abi: Abi,
164164
args: &[OpTy<'tcx, Self::PointerTag>],
165165
ret: Option<(&PlaceTy<'tcx, Self::PointerTag>, mir::BasicBlock)>,
166-
unwind: Option<mir::BasicBlock>,
166+
unwind: StackPopUnwind,
167167
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>>;
168168

169169
/// Execute `fn_val`. It is the hook's responsibility to advance the instruction
@@ -174,7 +174,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
174174
abi: Abi,
175175
args: &[OpTy<'tcx, Self::PointerTag>],
176176
ret: Option<(&PlaceTy<'tcx, Self::PointerTag>, mir::BasicBlock)>,
177-
unwind: Option<mir::BasicBlock>,
177+
unwind: StackPopUnwind,
178178
) -> InterpResult<'tcx>;
179179

180180
/// Directly process an intrinsic without pushing a stack frame. It is the hook's
@@ -184,7 +184,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
184184
instance: ty::Instance<'tcx>,
185185
args: &[OpTy<'tcx, Self::PointerTag>],
186186
ret: Option<(&PlaceTy<'tcx, Self::PointerTag>, mir::BasicBlock)>,
187-
unwind: Option<mir::BasicBlock>,
187+
unwind: StackPopUnwind,
188188
) -> InterpResult<'tcx>;
189189

190190
/// Called to evaluate `Assert` MIR terminators that trigger a panic.
@@ -456,7 +456,7 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
456456
_abi: Abi,
457457
_args: &[OpTy<$tcx>],
458458
_ret: Option<(&PlaceTy<$tcx>, mir::BasicBlock)>,
459-
_unwind: Option<mir::BasicBlock>,
459+
_unwind: StackPopUnwind,
460460
) -> InterpResult<$tcx> {
461461
match fn_val {}
462462
}

compiler/rustc_mir/src/interpret/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ mod visitor;
1818

1919
pub use rustc_middle::mir::interpret::*; // have all the `interpret` symbols in one place: here
2020

21-
pub use self::eval_context::{Frame, FrameInfo, InterpCx, LocalState, LocalValue, StackPopCleanup};
21+
pub use self::eval_context::{
22+
Frame, FrameInfo, InterpCx, LocalState, LocalValue, StackPopCleanup, StackPopUnwind,
23+
};
2224
pub use self::intern::{intern_const_alloc_recursive, InternKind};
2325
pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump};
2426
pub use self::memory::{AllocCheck, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind};

0 commit comments

Comments
 (0)