• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_panic` intrinsic instead of the
7 //!   target-native panic runtime. (This lives in the rustc repo.)
8 //! - An implementation of `miri_start_panic` 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.
13 
14 use log::trace;
15 
16 use rustc_ast::Mutability;
17 use rustc_middle::{mir, ty};
18 use rustc_span::Symbol;
19 use rustc_target::spec::abi::Abi;
20 use rustc_target::spec::PanicStrategy;
21 
22 use crate::*;
23 use helpers::check_arg_count;
24 
25 /// Holds all of the relevant data for when unwinding hits a `try` frame.
26 #[derive(Debug)]
27 pub struct CatchUnwindData<'tcx> {
28     /// The `catch_fn` callback to call in case of a panic.
29     catch_fn: Pointer<Option<Provenance>>,
30     /// The `data` argument for that callback.
31     data: Scalar<Provenance>,
32     /// The return place from the original call to `try`.
33     dest: PlaceTy<'tcx, Provenance>,
34     /// The return block from the original call to `try`.
35     ret: mir::BasicBlock,
36 }
37 
38 impl VisitTags for CatchUnwindData<'_> {
visit_tags(&self, visit: &mut dyn FnMut(BorTag))39     fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
40         let CatchUnwindData { catch_fn, data, dest, ret: _ } = self;
41         catch_fn.visit_tags(visit);
42         data.visit_tags(visit);
43         dest.visit_tags(visit);
44     }
45 }
46 
47 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
48 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
49     /// Handles the special `miri_start_panic` intrinsic, which is called
50     /// by libpanic_unwind to delegate the actual unwinding process to Miri.
handle_miri_start_panic( &mut self, abi: Abi, link_name: Symbol, args: &[OpTy<'tcx, Provenance>], unwind: mir::UnwindAction, ) -> InterpResult<'tcx>51     fn handle_miri_start_panic(
52         &mut self,
53         abi: Abi,
54         link_name: Symbol,
55         args: &[OpTy<'tcx, Provenance>],
56         unwind: mir::UnwindAction,
57     ) -> InterpResult<'tcx> {
58         let this = self.eval_context_mut();
59 
60         trace!("miri_start_panic: {:?}", this.frame().instance);
61 
62         // Get the raw pointer stored in arg[0] (the panic payload).
63         let [payload] = this.check_shim(abi, Abi::Rust, link_name, args)?;
64         let payload = this.read_scalar(payload)?;
65         let thread = this.active_thread_mut();
66         thread.panic_payloads.push(payload);
67 
68         // Jump to the unwind block to begin unwinding.
69         this.unwind_to_block(unwind)?;
70         Ok(())
71     }
72 
73     /// Handles the `try` intrinsic, the underlying implementation of `std::panicking::try`.
handle_try( &mut self, args: &[OpTy<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: mir::BasicBlock, ) -> InterpResult<'tcx>74     fn handle_try(
75         &mut self,
76         args: &[OpTy<'tcx, Provenance>],
77         dest: &PlaceTy<'tcx, Provenance>,
78         ret: mir::BasicBlock,
79     ) -> InterpResult<'tcx> {
80         let this = self.eval_context_mut();
81 
82         // Signature:
83         //   fn r#try(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32
84         // Calls `try_fn` with `data` as argument. If that executes normally, returns 0.
85         // If that unwinds, calls `catch_fn` with the first argument being `data` and
86         // then second argument being a target-dependent `payload` (i.e. it is up to us to define
87         // what that is), and returns 1.
88         // The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to
89         // return a `Box<dyn Any + Send + 'static>`.
90         // In Miri, `miri_start_panic` is passed exactly that type, so we make the `payload` simply
91         // a pointer to `Box<dyn Any + Send + 'static>`.
92 
93         // Get all the arguments.
94         let [try_fn, data, catch_fn] = check_arg_count(args)?;
95         let try_fn = this.read_pointer(try_fn)?;
96         let data = this.read_scalar(data)?;
97         let catch_fn = this.read_pointer(catch_fn)?;
98 
99         // Now we make a function call, and pass `data` as first and only argument.
100         let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?;
101         trace!("try_fn: {:?}", f_instance);
102         this.call_function(
103             f_instance,
104             Abi::Rust,
105             &[data.into()],
106             None,
107             // Directly return to caller.
108             StackPopCleanup::Goto { ret: Some(ret), unwind: mir::UnwindAction::Continue },
109         )?;
110 
111         // We ourselves will return `0`, eventually (will be overwritten if we catch a panic).
112         this.write_null(dest)?;
113 
114         // In unwind mode, we tag this frame with the extra data needed to catch unwinding.
115         // This lets `handle_stack_pop` (below) know that we should stop unwinding
116         // when we pop this frame.
117         if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind {
118             this.frame_mut().extra.catch_unwind =
119                 Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret });
120         }
121 
122         Ok(())
123     }
124 
handle_stack_pop_unwind( &mut self, mut extra: FrameExtra<'tcx>, unwinding: bool, ) -> InterpResult<'tcx, StackPopJump>125     fn handle_stack_pop_unwind(
126         &mut self,
127         mut extra: FrameExtra<'tcx>,
128         unwinding: bool,
129     ) -> InterpResult<'tcx, StackPopJump> {
130         let this = self.eval_context_mut();
131         trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding);
132 
133         // We only care about `catch_panic` if we're unwinding - if we're doing a normal
134         // return, then we don't need to do anything special.
135         if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) {
136             // We've just popped a frame that was pushed by `try`,
137             // and we are unwinding, so we should catch that.
138             trace!(
139                 "unwinding: found catch_panic frame during unwinding: {:?}",
140                 this.frame().instance
141             );
142 
143             // We set the return value of `try` to 1, since there was a panic.
144             this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?;
145 
146             // The Thread's `panic_payload` holds what was passed to `miri_start_panic`.
147             // This is exactly the second argument we need to pass to `catch_fn`.
148             let payload = this.active_thread_mut().panic_payloads.pop().unwrap();
149 
150             // Push the `catch_fn` stackframe.
151             let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?;
152             trace!("catch_fn: {:?}", f_instance);
153             this.call_function(
154                 f_instance,
155                 Abi::Rust,
156                 &[catch_unwind.data.into(), payload.into()],
157                 None,
158                 // Directly return to caller of `try`.
159                 StackPopCleanup::Goto {
160                     ret: Some(catch_unwind.ret),
161                     unwind: mir::UnwindAction::Continue,
162                 },
163             )?;
164 
165             // We pushed a new stack frame, the engine should not do any jumping now!
166             Ok(StackPopJump::NoJump)
167         } else {
168             Ok(StackPopJump::Normal)
169         }
170     }
171 
172     /// Start a panic in the interpreter with the given message as payload.
start_panic(&mut self, msg: &str, unwind: mir::UnwindAction) -> InterpResult<'tcx>173     fn start_panic(&mut self, msg: &str, unwind: mir::UnwindAction) -> InterpResult<'tcx> {
174         let this = self.eval_context_mut();
175 
176         // First arg: message.
177         let msg = this.allocate_str(msg, MiriMemoryKind::Machine.into(), Mutability::Not)?;
178 
179         // Call the lang item.
180         let panic = this.tcx.lang_items().panic_fn().unwrap();
181         let panic = ty::Instance::mono(this.tcx.tcx, panic);
182         this.call_function(
183             panic,
184             Abi::Rust,
185             &[msg.to_ref(this)],
186             None,
187             StackPopCleanup::Goto { ret: None, unwind },
188         )
189     }
190 
assert_panic( &mut self, msg: &mir::AssertMessage<'tcx>, unwind: mir::UnwindAction, ) -> InterpResult<'tcx>191     fn assert_panic(
192         &mut self,
193         msg: &mir::AssertMessage<'tcx>,
194         unwind: mir::UnwindAction,
195     ) -> InterpResult<'tcx> {
196         use rustc_middle::mir::AssertKind::*;
197         let this = self.eval_context_mut();
198 
199         match msg {
200             BoundsCheck { index, len } => {
201                 // Forward to `panic_bounds_check` lang item.
202 
203                 // First arg: index.
204                 let index = this.read_scalar(&this.eval_operand(index, None)?)?;
205                 // Second arg: len.
206                 let len = this.read_scalar(&this.eval_operand(len, None)?)?;
207 
208                 // Call the lang item.
209                 let panic_bounds_check = this.tcx.lang_items().panic_bounds_check_fn().unwrap();
210                 let panic_bounds_check = ty::Instance::mono(this.tcx.tcx, panic_bounds_check);
211                 this.call_function(
212                     panic_bounds_check,
213                     Abi::Rust,
214                     &[index.into(), len.into()],
215                     None,
216                     StackPopCleanup::Goto { ret: None, unwind },
217                 )?;
218             }
219             MisalignedPointerDereference { required, found } => {
220                 // Forward to `panic_misaligned_pointer_dereference` lang item.
221 
222                 // First arg: required.
223                 let required = this.read_scalar(&this.eval_operand(required, None)?)?;
224                 // Second arg: found.
225                 let found = this.read_scalar(&this.eval_operand(found, None)?)?;
226 
227                 // Call the lang item.
228                 let panic_misaligned_pointer_dereference =
229                     this.tcx.lang_items().panic_misaligned_pointer_dereference_fn().unwrap();
230                 let panic_misaligned_pointer_dereference =
231                     ty::Instance::mono(this.tcx.tcx, panic_misaligned_pointer_dereference);
232                 this.call_function(
233                     panic_misaligned_pointer_dereference,
234                     Abi::Rust,
235                     &[required.into(), found.into()],
236                     None,
237                     StackPopCleanup::Goto { ret: None, unwind },
238                 )?;
239             }
240 
241             _ => {
242                 // Forward everything else to `panic` lang item.
243                 this.start_panic(msg.description(), unwind)?;
244             }
245         }
246         Ok(())
247     }
248 }
249