]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/shims/panic.rs
Rollup merge of #105254 - cjgillot:issue-105251, r=oli-obk
[rust.git] / src / tools / miri / src / shims / panic.rs
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<'_> {
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.
51     fn handle_miri_start_panic(
52         &mut self,
53         abi: Abi,
54         link_name: Symbol,
55         args: &[OpTy<'tcx, Provenance>],
56         unwind: StackPopUnwind,
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         assert!(thread.panic_payload.is_none(), "the panic runtime should avoid double-panics");
67         thread.panic_payload = Some(payload);
68
69         // Jump to the unwind block to begin unwinding.
70         this.unwind_to_block(unwind)?;
71         Ok(())
72     }
73
74     /// Handles the `try` intrinsic, the underlying implementation of `std::panicking::try`.
75     fn handle_try(
76         &mut self,
77         args: &[OpTy<'tcx, Provenance>],
78         dest: &PlaceTy<'tcx, Provenance>,
79         ret: mir::BasicBlock,
80     ) -> InterpResult<'tcx> {
81         let this = self.eval_context_mut();
82
83         // Signature:
84         //   fn r#try(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32
85         // Calls `try_fn` with `data` as argument. If that executes normally, returns 0.
86         // If that unwinds, calls `catch_fn` with the first argument being `data` and
87         // then second argument being a target-dependent `payload` (i.e. it is up to us to define
88         // what that is), and returns 1.
89         // The `payload` is passed (by libstd) to `__rust_panic_cleanup`, which is then expected to
90         // return a `Box<dyn Any + Send + 'static>`.
91         // In Miri, `miri_start_panic` is passed exactly that type, so we make the `payload` simply
92         // a pointer to `Box<dyn Any + Send + 'static>`.
93
94         // Get all the arguments.
95         let [try_fn, data, catch_fn] = check_arg_count(args)?;
96         let try_fn = this.read_pointer(try_fn)?;
97         let data = this.read_scalar(data)?;
98         let catch_fn = this.read_pointer(catch_fn)?;
99
100         // Now we make a function call, and pass `data` as first and only argument.
101         let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?;
102         trace!("try_fn: {:?}", f_instance);
103         this.call_function(
104             f_instance,
105             Abi::Rust,
106             &[data.into()],
107             None,
108             // Directly return to caller.
109             StackPopCleanup::Goto { ret: Some(ret), unwind: StackPopUnwind::Skip },
110         )?;
111
112         // We ourselves will return `0`, eventually (will be overwritten if we catch a panic).
113         this.write_null(dest)?;
114
115         // In unwind mode, we tag this frame with the extra data needed to catch unwinding.
116         // This lets `handle_stack_pop` (below) know that we should stop unwinding
117         // when we pop this frame.
118         if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind {
119             this.frame_mut().extra.catch_unwind =
120                 Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret });
121         }
122
123         Ok(())
124     }
125
126     fn handle_stack_pop_unwind(
127         &mut self,
128         mut extra: FrameExtra<'tcx>,
129         unwinding: bool,
130     ) -> InterpResult<'tcx, StackPopJump> {
131         let this = self.eval_context_mut();
132         trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding);
133
134         // We only care about `catch_panic` if we're unwinding - if we're doing a normal
135         // return, then we don't need to do anything special.
136         if let (true, Some(catch_unwind)) = (unwinding, extra.catch_unwind.take()) {
137             // We've just popped a frame that was pushed by `try`,
138             // and we are unwinding, so we should catch that.
139             trace!(
140                 "unwinding: found catch_panic frame during unwinding: {:?}",
141                 this.frame().instance
142             );
143
144             // We set the return value of `try` to 1, since there was a panic.
145             this.write_scalar(Scalar::from_i32(1), &catch_unwind.dest)?;
146
147             // The Thread's `panic_payload` holds what was passed to `miri_start_panic`.
148             // This is exactly the second argument we need to pass to `catch_fn`.
149             let payload = this.active_thread_mut().panic_payload.take().unwrap();
150
151             // Push the `catch_fn` stackframe.
152             let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?;
153             trace!("catch_fn: {:?}", f_instance);
154             this.call_function(
155                 f_instance,
156                 Abi::Rust,
157                 &[catch_unwind.data.into(), payload.into()],
158                 None,
159                 // Directly return to caller of `try`.
160                 StackPopCleanup::Goto { ret: Some(catch_unwind.ret), unwind: StackPopUnwind::Skip },
161             )?;
162
163             // We pushed a new stack frame, the engine should not do any jumping now!
164             Ok(StackPopJump::NoJump)
165         } else {
166             Ok(StackPopJump::Normal)
167         }
168     }
169
170     /// Start a panic in the interpreter with the given message as payload.
171     fn start_panic(&mut self, msg: &str, unwind: StackPopUnwind) -> InterpResult<'tcx> {
172         let this = self.eval_context_mut();
173
174         // First arg: message.
175         let msg = this.allocate_str(msg, MiriMemoryKind::Machine.into(), Mutability::Not);
176
177         // Call the lang item.
178         let panic = this.tcx.lang_items().panic_fn().unwrap();
179         let panic = ty::Instance::mono(this.tcx.tcx, panic);
180         this.call_function(
181             panic,
182             Abi::Rust,
183             &[msg.to_ref(this)],
184             None,
185             StackPopCleanup::Goto { ret: None, unwind },
186         )
187     }
188
189     fn assert_panic(
190         &mut self,
191         msg: &mir::AssertMessage<'tcx>,
192         unwind: Option<mir::BasicBlock>,
193     ) -> InterpResult<'tcx> {
194         use rustc_middle::mir::AssertKind::*;
195         let this = self.eval_context_mut();
196
197         match msg {
198             BoundsCheck { index, len } => {
199                 // Forward to `panic_bounds_check` lang item.
200
201                 // First arg: index.
202                 let index = this.read_scalar(&this.eval_operand(index, None)?)?;
203                 // Second arg: len.
204                 let len = this.read_scalar(&this.eval_operand(len, None)?)?;
205
206                 // Call the lang item.
207                 let panic_bounds_check = this.tcx.lang_items().panic_bounds_check_fn().unwrap();
208                 let panic_bounds_check = ty::Instance::mono(this.tcx.tcx, panic_bounds_check);
209                 this.call_function(
210                     panic_bounds_check,
211                     Abi::Rust,
212                     &[index.into(), len.into()],
213                     None,
214                     StackPopCleanup::Goto {
215                         ret: None,
216                         unwind: match unwind {
217                             Some(cleanup) => StackPopUnwind::Cleanup(cleanup),
218                             None => StackPopUnwind::Skip,
219                         },
220                     },
221                 )?;
222             }
223             _ => {
224                 // Forward everything else to `panic` lang item.
225                 this.start_panic(
226                     msg.description(),
227                     match unwind {
228                         Some(cleanup) => StackPopUnwind::Cleanup(cleanup),
229                         None => StackPopUnwind::Skip,
230                     },
231                 )?;
232             }
233         }
234         Ok(())
235     }
236 }