Skip to content

Commit 8ad6454

Browse files
committed
split unwinding logic from panic logic
1 parent a6d792d commit 8ad6454

File tree

4 files changed

+164
-152
lines changed

4 files changed

+164
-152
lines changed

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,10 @@ pub use crate::shims::env::{EnvVars, EvalContextExt as _};
154154
pub use crate::shims::foreign_items::{DynSym, EvalContextExt as _};
155155
pub use crate::shims::io_error::{EvalContextExt as _, IoError, LibcError};
156156
pub use crate::shims::os_str::EvalContextExt as _;
157-
pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as _};
157+
pub use crate::shims::panic::EvalContextExt as _;
158158
pub use crate::shims::time::EvalContextExt as _;
159159
pub use crate::shims::tls::TlsData;
160+
pub use crate::shims::unwind::{CatchUnwindData, EvalContextExt as _};
160161

161162
/// Insert rustc arguments at the beginning of the argument list that Miri wants to be
162163
/// set per default, for maximal validation power.

src/shims/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod os_str;
1919
pub mod panic;
2020
pub mod time;
2121
pub mod tls;
22+
pub mod unwind;
2223

2324
pub use self::files::FdTable;
2425
//#[cfg(target_os = "linux")]

src/shims/panic.rs

Lines changed: 1 addition & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,12 @@
1-
//! Panic runtime for Miri.
2-
//!
3-
//! The core pieces of the runtime are:
4-
//! - An implementation of `__rust_maybe_catch_panic` that pushes the invoked stack frame with
5-
//! some extra metadata derived from the panic-catching arguments of `__rust_maybe_catch_panic`.
6-
//! - A hack in `libpanic_unwind` that calls the `miri_start_unwind` intrinsic instead of the
7-
//! target-native panic runtime. (This lives in the rustc repo.)
8-
//! - An implementation of `miri_start_unwind` that stores its argument (the panic payload), and then
9-
//! immediately returns, but on the *unwind* edge (not the normal return edge), thus initiating unwinding.
10-
//! - A hook executed each time a frame is popped, such that if the frame pushed by `__rust_maybe_catch_panic`
11-
//! gets popped *during unwinding*, we take the panic payload and store it according to the extra
12-
//! metadata we remembered when pushing said frame.
1+
//! Helper functions for causing panics.
132
143
use rustc_abi::ExternAbi;
154
use rustc_middle::{mir, ty};
16-
use rustc_target::spec::PanicStrategy;
175

18-
use self::helpers::check_intrinsic_arg_count;
196
use crate::*;
207

21-
/// Holds all of the relevant data for when unwinding hits a `try` frame.
22-
#[derive(Debug)]
23-
pub struct CatchUnwindData<'tcx> {
24-
/// The `catch_fn` callback to call in case of a panic.
25-
catch_fn: Pointer,
26-
/// The `data` argument for that callback.
27-
data: ImmTy<'tcx>,
28-
/// The return place from the original call to `try`.
29-
dest: MPlaceTy<'tcx>,
30-
/// The return block from the original call to `try`.
31-
ret: Option<mir::BasicBlock>,
32-
}
33-
34-
impl VisitProvenance for CatchUnwindData<'_> {
35-
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
36-
let CatchUnwindData { catch_fn, data, dest, ret: _ } = self;
37-
catch_fn.visit_provenance(visit);
38-
data.visit_provenance(visit);
39-
dest.visit_provenance(visit);
40-
}
41-
}
42-
438
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
449
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
45-
/// Handles the special `miri_start_unwind` intrinsic, which is called
46-
/// by libpanic_unwind to delegate the actual unwinding process to Miri.
47-
fn handle_miri_start_unwind(&mut self, payload: &OpTy<'tcx>) -> InterpResult<'tcx> {
48-
let this = self.eval_context_mut();
49-
50-
trace!("miri_start_unwind: {:?}", this.frame().instance());
51-
52-
let payload = this.read_immediate(payload)?;
53-
let thread = this.active_thread_mut();
54-
thread.unwind_payloads.push(payload);
55-
56-
interp_ok(())
57-
}
58-
59-
/// Handles the `catch_unwind` intrinsic.
60-
fn handle_catch_unwind(
61-
&mut self,
62-
args: &[OpTy<'tcx>],
63-
dest: &MPlaceTy<'tcx>,
64-
ret: Option<mir::BasicBlock>,
65-
) -> InterpResult<'tcx> {
66-
let this = self.eval_context_mut();
67-
68-
// Signature:
69-
// fn catch_unwind(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32
70-
// Calls `try_fn` with `data` as argument. If that executes normally, returns 0.
71-
// If that unwinds, calls `catch_fn` with the first argument being `data` and
72-
// then second argument being a target-dependent `payload` (i.e. it is up to us to define
73-
// what that is), and returns 1.
74-
// The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to
75-
// return a `Box<dyn Any + Send + 'static>`.
76-
// In Miri, `miri_start_unwind` is passed exactly that type, so we make the `payload` simply
77-
// a pointer to `Box<dyn Any + Send + 'static>`.
78-
79-
// Get all the arguments.
80-
let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?;
81-
let try_fn = this.read_pointer(try_fn)?;
82-
let data = this.read_immediate(data)?;
83-
let catch_fn = this.read_pointer(catch_fn)?;
84-
85-
// Now we make a function call, and pass `data` as first and only argument.
86-
let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?;
87-
trace!("try_fn: {:?}", f_instance);
88-
#[allow(clippy::cloned_ref_to_slice_refs)] // the code is clearer as-is
89-
this.call_function(
90-
f_instance,
91-
ExternAbi::Rust,
92-
&[data.clone()],
93-
None,
94-
// Directly return to caller.
95-
StackPopCleanup::Goto { ret, unwind: mir::UnwindAction::Continue },
96-
)?;
97-
98-
// We ourselves will return `0`, eventually (will be overwritten if we catch a panic).
99-
this.write_null(dest)?;
100-
101-
// In unwind mode, we tag this frame with the extra data needed to catch unwinding.
102-
// This lets `handle_stack_pop` (below) know that we should stop unwinding
103-
// when we pop this frame.
104-
if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind {
105-
this.frame_mut().extra.catch_unwind =
106-
Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret });
107-
}
108-
109-
interp_ok(())
110-
}
111-
112-
fn handle_stack_pop_unwind(
113-
&mut self,
114-
mut extra: FrameExtra<'tcx>,
115-
unwinding: bool,
116-
) -> InterpResult<'tcx, ReturnAction> {
117-
let this = self.eval_context_mut();
118-
trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding);
119-
120-
// We only care about `catch_panic` if we're unwinding - if we're doing a normal
121-
// return, then we don't need to do anything special.
122-
if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) {
123-
// We've just popped a frame that was pushed by `catch_unwind`,
124-
// and we are unwinding, so we should catch that.
125-
trace!(
126-
"unwinding: found catch_panic frame during unwinding: {:?}",
127-
this.frame().instance()
128-
);
129-
130-
// We set the return value of `catch_unwind` to 1, since there was a panic.
131-
this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?;
132-
133-
// The Thread's `panic_payload` holds what was passed to `miri_start_unwind`.
134-
// This is exactly the second argument we need to pass to `catch_fn`.
135-
let payload = this.active_thread_mut().unwind_payloads.pop().unwrap();
136-
137-
// Push the `catch_fn` stackframe.
138-
let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?;
139-
trace!("catch_fn: {:?}", f_instance);
140-
this.call_function(
141-
f_instance,
142-
ExternAbi::Rust,
143-
&[catch_unwind.data, payload],
144-
None,
145-
// Directly return to caller of `catch_unwind`.
146-
StackPopCleanup::Goto {
147-
ret: catch_unwind.ret,
148-
// `catch_fn` must not unwind.
149-
unwind: mir::UnwindAction::Unreachable,
150-
},
151-
)?;
152-
153-
// We pushed a new stack frame, the engine should not do any jumping now!
154-
interp_ok(ReturnAction::NoJump)
155-
} else {
156-
interp_ok(ReturnAction::Normal)
157-
}
158-
}
159-
16010
/// Start a panic in the interpreter with the given message as payload.
16111
fn start_panic(&mut self, msg: &str, unwind: mir::UnwindAction) -> InterpResult<'tcx> {
16212
let this = self.eval_context_mut();

src/shims/unwind.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//! Unwinding runtime for Miri.
2+
//!
3+
//! The core pieces of the runtime are:
4+
//! - An implementation of `catch_unwind` that pushes the invoked stack frame with
5+
//! some extra metadata derived from the panic-catching arguments of `catch_unwind`.
6+
//! - A hack in `libpanic_unwind` that calls the `miri_start_unwind` intrinsic instead of the
7+
//! target-native panic runtime. (This lives in the rustc repo.)
8+
//! - An implementation of `miri_start_unwind` that stores its argument (the panic payload), and
9+
//! then immediately returns, but on the *unwind* edge (not the normal return edge), thus
10+
//! initiating unwinding.
11+
//! - A hook executed each time a frame is popped, such that if the frame pushed by `catch_unwind`
12+
//! gets popped *during unwinding*, we take the panic payload and store it according to the extra
13+
//! metadata we remembered when pushing said frame.
14+
15+
use rustc_abi::ExternAbi;
16+
use rustc_middle::mir;
17+
use rustc_target::spec::PanicStrategy;
18+
19+
use self::helpers::check_intrinsic_arg_count;
20+
use crate::*;
21+
22+
/// Holds all of the relevant data for when unwinding hits a `try` frame.
23+
#[derive(Debug)]
24+
pub struct CatchUnwindData<'tcx> {
25+
/// The `catch_fn` callback to call in case of a panic.
26+
catch_fn: Pointer,
27+
/// The `data` argument for that callback.
28+
data: ImmTy<'tcx>,
29+
/// The return place from the original call to `try`.
30+
dest: MPlaceTy<'tcx>,
31+
/// The return block from the original call to `try`.
32+
ret: Option<mir::BasicBlock>,
33+
}
34+
35+
impl VisitProvenance for CatchUnwindData<'_> {
36+
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
37+
let CatchUnwindData { catch_fn, data, dest, ret: _ } = self;
38+
catch_fn.visit_provenance(visit);
39+
data.visit_provenance(visit);
40+
dest.visit_provenance(visit);
41+
}
42+
}
43+
44+
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
45+
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
46+
/// Handles the special `miri_start_unwind` intrinsic, which is called
47+
/// by libpanic_unwind to delegate the actual unwinding process to Miri.
48+
fn handle_miri_start_unwind(&mut self, payload: &OpTy<'tcx>) -> InterpResult<'tcx> {
49+
let this = self.eval_context_mut();
50+
51+
trace!("miri_start_unwind: {:?}", this.frame().instance());
52+
53+
let payload = this.read_immediate(payload)?;
54+
let thread = this.active_thread_mut();
55+
thread.unwind_payloads.push(payload);
56+
57+
interp_ok(())
58+
}
59+
60+
/// Handles the `catch_unwind` intrinsic.
61+
fn handle_catch_unwind(
62+
&mut self,
63+
args: &[OpTy<'tcx>],
64+
dest: &MPlaceTy<'tcx>,
65+
ret: Option<mir::BasicBlock>,
66+
) -> InterpResult<'tcx> {
67+
let this = self.eval_context_mut();
68+
69+
// Signature:
70+
// fn catch_unwind(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32
71+
// Calls `try_fn` with `data` as argument. If that executes normally, returns 0.
72+
// If that unwinds, calls `catch_fn` with the first argument being `data` and
73+
// then second argument being a target-dependent `payload` (i.e. it is up to us to define
74+
// what that is), and returns 1.
75+
// The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to
76+
// return a `Box<dyn Any + Send + 'static>`.
77+
// In Miri, `miri_start_unwind` is passed exactly that type, so we make the `payload` simply
78+
// a pointer to `Box<dyn Any + Send + 'static>`.
79+
80+
// Get all the arguments.
81+
let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?;
82+
let try_fn = this.read_pointer(try_fn)?;
83+
let data = this.read_immediate(data)?;
84+
let catch_fn = this.read_pointer(catch_fn)?;
85+
86+
// Now we make a function call, and pass `data` as first and only argument.
87+
let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?;
88+
trace!("try_fn: {:?}", f_instance);
89+
#[allow(clippy::cloned_ref_to_slice_refs)] // the code is clearer as-is
90+
this.call_function(
91+
f_instance,
92+
ExternAbi::Rust,
93+
&[data.clone()],
94+
None,
95+
// Directly return to caller.
96+
StackPopCleanup::Goto { ret, unwind: mir::UnwindAction::Continue },
97+
)?;
98+
99+
// We ourselves will return `0`, eventually (will be overwritten if we catch a panic).
100+
this.write_null(dest)?;
101+
102+
// In unwind mode, we tag this frame with the extra data needed to catch unwinding.
103+
// This lets `handle_stack_pop` (below) know that we should stop unwinding
104+
// when we pop this frame.
105+
if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind {
106+
this.frame_mut().extra.catch_unwind =
107+
Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret });
108+
}
109+
110+
interp_ok(())
111+
}
112+
113+
fn handle_stack_pop_unwind(
114+
&mut self,
115+
mut extra: FrameExtra<'tcx>,
116+
unwinding: bool,
117+
) -> InterpResult<'tcx, ReturnAction> {
118+
let this = self.eval_context_mut();
119+
trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding);
120+
121+
// We only care about `catch_panic` if we're unwinding - if we're doing a normal
122+
// return, then we don't need to do anything special.
123+
if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) {
124+
// We've just popped a frame that was pushed by `catch_unwind`,
125+
// and we are unwinding, so we should catch that.
126+
trace!(
127+
"unwinding: found catch_panic frame during unwinding: {:?}",
128+
this.frame().instance()
129+
);
130+
131+
// We set the return value of `catch_unwind` to 1, since there was a panic.
132+
this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?;
133+
134+
// The Thread's `panic_payload` holds what was passed to `miri_start_unwind`.
135+
// This is exactly the second argument we need to pass to `catch_fn`.
136+
let payload = this.active_thread_mut().unwind_payloads.pop().unwrap();
137+
138+
// Push the `catch_fn` stackframe.
139+
let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?;
140+
trace!("catch_fn: {:?}", f_instance);
141+
this.call_function(
142+
f_instance,
143+
ExternAbi::Rust,
144+
&[catch_unwind.data, payload],
145+
None,
146+
// Directly return to caller of `catch_unwind`.
147+
StackPopCleanup::Goto {
148+
ret: catch_unwind.ret,
149+
// `catch_fn` must not unwind.
150+
unwind: mir::UnwindAction::Unreachable,
151+
},
152+
)?;
153+
154+
// We pushed a new stack frame, the engine should not do any jumping now!
155+
interp_ok(ReturnAction::NoJump)
156+
} else {
157+
interp_ok(ReturnAction::Normal)
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)