Skip to content

Commit 7b3e10c

Browse files
author
hyd-dev
committed
const-eval: disallow unwinding across functions that !fn_can_unwind()
1 parent ed20e1e commit 7b3e10c

File tree

4 files changed

+108
-45
lines changed

4 files changed

+108
-45
lines changed

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 41 additions & 34 deletions
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/interpret/eval_context.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,23 @@ pub struct FrameInfo<'tcx> {
134134
pub lint_root: Option<hir::HirId>,
135135
}
136136

137+
/// Unwind information.
138+
#[derive(Clone, Copy, Eq, PartialEq, Debug, HashStable)]
139+
pub enum StackPopUnwind {
140+
/// The cleanup block.
141+
Cleanup(Option<mir::BasicBlock>),
142+
/// Unwinding is not allowed (UB).
143+
NotAllowed,
144+
}
145+
137146
#[derive(Clone, Eq, PartialEq, Debug, HashStable)] // Miri debug-prints these
138147
pub enum StackPopCleanup {
139148
/// Jump to the next block in the caller, or cause UB if None (that's a function
140149
/// that may never return). Also store layout of return place so
141150
/// we can validate it at that layout.
142151
/// `ret` stores the block we jump to on a normal return, while `unwind`
143152
/// stores the block used for cleanup during unwinding.
144-
Goto { ret: Option<mir::BasicBlock>, unwind: Option<mir::BasicBlock> },
153+
Goto { ret: Option<mir::BasicBlock>, unwind: StackPopUnwind },
145154
/// Just do nothing: Used by Main and for the `box_alloc` hook in miri.
146155
/// `cleanup` says whether locals are deallocated. Static computation
147156
/// wants them leaked to intern what they need (and just throw away
@@ -807,9 +816,29 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
807816
// In that case, we return early. We also avoid validation in that case,
808817
// because this is CTFE and the final value will be thoroughly validated anyway.
809818
let (cleanup, next_block) = match frame.return_to_block {
810-
StackPopCleanup::Goto { ret, unwind } => {
811-
(true, Some(if unwinding { unwind } else { ret }))
812-
}
819+
StackPopCleanup::Goto { ret, unwind } => (
820+
true,
821+
Some(if unwinding {
822+
let def_id = frame.body.source.def_id();
823+
match unwind {
824+
StackPopUnwind::Cleanup(unwind)
825+
// `fn_sig()` can't be used on closures, but closures always have
826+
// "rust-call" ABI, which always allows unwinding anyway.
827+
if self.tcx.is_closure(def_id) || self.fn_can_unwind(
828+
self.tcx.codegen_fn_attrs(def_id).flags,
829+
self.tcx.fn_sig(def_id).abi(),
830+
) =>
831+
{
832+
unwind
833+
}
834+
_ => {
835+
throw_ub_format!("unwind past a frame that does not allow unwinding")
836+
}
837+
}
838+
} else {
839+
ret
840+
}),
841+
),
813842
StackPopCleanup::None { cleanup, .. } => (cleanup, None),
814843
};
815844

compiler/rustc_mir/src/interpret/mod.rs

Lines changed: 3 additions & 1 deletion
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};

compiler/rustc_mir/src/interpret/terminator.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use std::borrow::Cow;
22
use std::convert::TryFrom;
33

4-
use rustc_middle::ty::layout::TyAndLayout;
4+
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
5+
use rustc_middle::ty::layout::{self, TyAndLayout};
56
use rustc_middle::ty::Instance;
67
use rustc_middle::{
78
mir,
@@ -12,9 +13,19 @@ use rustc_target::spec::abi::Abi;
1213

1314
use super::{
1415
FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, StackPopCleanup,
16+
StackPopUnwind,
1517
};
1618

1719
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
20+
pub(super) fn fn_can_unwind(&self, attrs: CodegenFnAttrFlags, abi: Abi) -> bool {
21+
layout::fn_can_unwind(
22+
self.tcx.sess.panic_strategy(),
23+
attrs,
24+
layout::conv_from_spec_abi(*self.tcx, abi),
25+
abi,
26+
)
27+
}
28+
1829
pub(super) fn eval_terminator(
1930
&mut self,
2031
terminator: &mir::Terminator<'tcx>,
@@ -58,12 +69,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
5869
let old_stack = self.frame_idx();
5970
let old_loc = self.frame().loc;
6071
let func = self.eval_operand(func, None)?;
61-
let (fn_val, abi) = match *func.layout.ty.kind() {
72+
let (fn_val, abi, can_unwind) = match *func.layout.ty.kind() {
6273
ty::FnPtr(sig) => {
6374
let caller_abi = sig.abi();
6475
let fn_ptr = self.read_scalar(&func)?.check_init()?;
6576
let fn_val = self.memory.get_fn(fn_ptr)?;
66-
(fn_val, caller_abi)
77+
(
78+
fn_val,
79+
caller_abi,
80+
self.fn_can_unwind(layout::fn_ptr_codegen_fn_attr_flags(), caller_abi),
81+
)
6782
}
6883
ty::FnDef(def_id, substs) => {
6984
let sig = func.layout.ty.fn_sig(*self.tcx);
@@ -72,6 +87,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
7287
self.resolve(ty::WithOptConstParam::unknown(def_id), substs)?,
7388
),
7489
sig.abi(),
90+
self.fn_can_unwind(self.tcx.codegen_fn_attrs(def_id).flags, sig.abi()),
7591
)
7692
}
7793
_ => span_bug!(
@@ -89,7 +105,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
89105
}
90106
None => None,
91107
};
92-
self.eval_fn_call(fn_val, abi, &args[..], ret, *cleanup)?;
108+
self.eval_fn_call(fn_val, abi, &args[..], ret, *cleanup, can_unwind)?;
93109
// Sanity-check that `eval_fn_call` either pushed a new frame or
94110
// did a jump to another block.
95111
if self.frame_idx() == old_stack && self.frame().loc == old_loc {
@@ -220,6 +236,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
220236
args: &[OpTy<'tcx, M::PointerTag>],
221237
ret: Option<(&PlaceTy<'tcx, M::PointerTag>, mir::BasicBlock)>,
222238
unwind: Option<mir::BasicBlock>,
239+
can_unwind: bool,
223240
) -> InterpResult<'tcx> {
224241
trace!("eval_fn_call: {:#?}", fn_val);
225242

@@ -287,7 +304,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
287304
instance,
288305
body,
289306
ret.map(|p| p.0),
290-
StackPopCleanup::Goto { ret: ret.map(|p| p.1), unwind },
307+
StackPopCleanup::Goto {
308+
ret: ret.map(|p| p.1),
309+
unwind: if can_unwind {
310+
StackPopUnwind::Cleanup(unwind)
311+
} else {
312+
StackPopUnwind::NotAllowed
313+
},
314+
},
291315
)?;
292316

293317
// If an error is raised here, pop the frame again to get an accurate backtrace.
@@ -432,7 +456,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
432456
OpTy::from(ImmTy::from_immediate(receiver_place.ptr.into(), this_receiver_ptr));
433457
trace!("Patched self operand to {:#?}", args[0]);
434458
// recurse with concrete function
435-
self.eval_fn_call(drop_fn, caller_abi, &args, ret, unwind)
459+
self.eval_fn_call(drop_fn, caller_abi, &args, ret, unwind, can_unwind)
436460
}
437461
}
438462
}
@@ -472,6 +496,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
472496
&[arg.into()],
473497
Some((&dest.into(), target)),
474498
unwind,
499+
true,
475500
)
476501
}
477502
}

0 commit comments

Comments
 (0)