11use rustc:: ty;
22use rustc:: ty:: layout:: { Align , LayoutOf , Size } ;
3- use rustc:: hir:: def_id:: DefId ;
3+ use rustc:: ty:: InstanceDef ;
4+ use rustc_target:: spec:: PanicStrategy ;
45use rustc:: mir;
56use syntax:: attr;
67
@@ -18,10 +19,15 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
1819 ret : Option < mir:: BasicBlock > ,
1920 ) -> EvalResult < ' tcx , Option < & ' mir mir:: Mir < ' tcx > > > {
2021 let this = self . eval_context_mut ( ) ;
21- trace ! ( "eval_fn_call: {:#?}, {:?}" , instance, dest. map( |place| * place) ) ;
22+ trace ! ( "eval_fn_call: {:#?}, {:?} {:?} " , instance, instance . def_id ( ) , dest. map( |place| * place) ) ;
2223
2324 // First, run the common hooks also supported by CTFE.
24- if this. hook_fn ( instance, args, dest) ? {
25+ // We *don't* forward panic-related items to the common hooks,
26+ // as we want to handle those specially
27+ if Some ( instance. def_id ( ) ) != this. tcx . lang_items ( ) . panic_fn ( ) &&
28+ Some ( instance. def_id ( ) ) != this. tcx . lang_items ( ) . begin_panic_fn ( ) &&
29+ this. hook_fn ( instance, args, dest) ? {
30+
2531 this. goto_block ( ret) ?;
2632 return Ok ( None ) ;
2733 }
@@ -39,11 +45,9 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
3945
4046 // Try to see if we can do something about foreign items.
4147 if this. tcx . is_foreign_item ( instance. def_id ( ) ) {
42- // An external function that we cannot find MIR for, but we can still run enough
48+ // An external function that we (possibly) cannot find MIR for, but we can still run enough
4349 // of them to make miri viable.
44- this. emulate_foreign_item ( instance. def_id ( ) , args, dest, ret) ?;
45- // `goto_block` already handled.
46- return Ok ( None ) ;
50+ return Ok ( this. emulate_foreign_item ( instance, args, dest, ret) ?) ;
4751 }
4852
4953 // Otherwise, load the MIR.
@@ -134,11 +138,12 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
134138 /// This function will handle `goto_block` if needed.
135139 fn emulate_foreign_item (
136140 & mut self ,
137- def_id : DefId ,
138- args : & [ OpTy < ' tcx , Tag > ] ,
141+ instance : ty :: Instance < ' tcx > ,
142+ args : & [ OpTy < ' tcx , Borrow > ] ,
139143 dest : Option < PlaceTy < ' tcx , Tag > > ,
140144 ret : Option < mir:: BasicBlock > ,
141- ) -> EvalResult < ' tcx > {
145+ ) -> EvalResult < ' tcx , Option < & ' mir mir:: Mir < ' tcx > > > {
146+ let def_id = instance. def_id ( ) ;
142147 let this = self . eval_context_mut ( ) ;
143148 let attrs = this. tcx . get_attrs ( def_id) ;
144149 let link_name = match attr:: first_attr_value_str_by_name ( & attrs, "link_name" ) {
@@ -151,8 +156,195 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
151156
152157 // First: functions that diverge.
153158 match link_name {
154- "__rust_start_panic" | "panic_impl" => {
155- return err ! ( MachineError ( "the evaluated program panicked" . to_string( ) ) ) ;
159+ "panic_impl" => {
160+ // Manually forward to 'panic_impl' lang item
161+ let panic_impl_real = this. tcx . lang_items ( ) . panic_impl ( ) . unwrap ( ) ;
162+
163+ return Ok ( Some ( this. load_mir ( InstanceDef :: Item ( panic_impl_real) ) ?) ) ;
164+ } ,
165+ "__rust_start_panic" => {
166+ // This function has the signature:
167+ // 'fn __rust_start_panic(payload: usize) -> u32;'
168+ //
169+ // The caller constructs 'payload' as follows
170+ // 1. We start with a type implementing core::panic::BoxMeUp
171+ // 2. We make this type into a trait object, obtaining a '&mut dyn BoxMeUp'
172+ // 3. We obtain a raw pointer to the above mutable reference: that is, we make:
173+ // '*mut &mut dyn BoxMeUp'
174+ // 4. We convert the raw pointer to a 'usize'
175+ //
176+
177+ // When a panic occurs, we (carefully!) reverse the above steps
178+ // to get back to the actual panic payload
179+ //
180+ // Even though our argument is a 'usize', Miri will have kept track
181+ // of the fact that it was created via a cast from a pointer.
182+ // This allows us to construct an ImmTy with the proper layout,
183+ // and dereference it
184+ //
185+ // Reference:
186+ // https://github.com/rust-lang/rust/blob/9ebf47851a357faa4cd97f4b1dc7835f6376e639/src/libpanic_unwind/lib.rs#L101
187+ // https://github.com/rust-lang/rust/blob/9ebf47851a357faa4cd97f4b1dc7835f6376e639/src/libpanic_unwind/lib.rs#L81
188+ //
189+ // payload_raw now represents our '&mut dyn BoxMeUp' - a fat pointer
190+ //
191+ // Note that we intentially call deref_operand before checking
192+ // This ensures that we always check the validity of the argument,
193+ // even if we don't end up using it
194+
195+ trace ! ( "__rustc_start_panic: {:?}" , this. frame( ) . span) ;
196+
197+
198+ // Read our 'usize' payload argument (which was made by casting
199+ // a '*mut &mut dyn BoxMeUp'
200+ let payload_raw = this. read_scalar ( args[ 0 ] ) ?. not_undef ( ) ?;
201+
202+ // Construct an ImmTy, using the precomputed layout of '*mut &mut dyn BoxMeUp'
203+ let imm_ty = ImmTy :: from_scalar (
204+ payload_raw,
205+ this. machine . cached_data . as_ref ( ) . unwrap ( ) . box_me_up_layout
206+ ) ;
207+
208+ // Convert our ImmTy to an MPlace, and read it
209+ let mplace = this. ref_to_mplace ( imm_ty) ?;
210+
211+ // This is an '&mut dyn BoxMeUp'
212+ let payload_dyn = this. read_immediate ( mplace. into ( ) ) ?;
213+
214+ // We deliberately do this after we do some validation of the
215+ // 'payload'. This should help catch some basic errors in
216+ // the caller of this function, even in abort mode
217+ if this. tcx . tcx . sess . panic_strategy ( ) == PanicStrategy :: Abort {
218+ return err ! ( MachineError ( "the evaluated program abort-panicked" . to_string( ) ) ) ;
219+ }
220+
221+ // This part is tricky - we need to call BoxMeUp::box_me_up
222+ // on the vtable.
223+ //
224+ // core::panic::BoxMeUp is declared as follows:
225+ //
226+ // pub unsafe trait BoxMeUp {
227+ // fn box_me_up(&mut self) -> *mut (dyn Any + Send);
228+ // fn get(&mut self) -> &(dyn Any + Send);
229+ // }
230+ //
231+ // box_me_up is the first method in the vtable.
232+ // First, we extract the vtable pointer from our fat pointer,
233+ // and check its alignment
234+
235+ let vtable_ptr = payload_dyn. to_meta ( ) ?. expect ( "Expected fat pointer!" ) . to_ptr ( ) ?;
236+ let data_ptr = payload_dyn. to_scalar_ptr ( ) ?;
237+ this. memory ( ) . check_align ( vtable_ptr. into ( ) , this. tcx . data_layout . pointer_align . abi ) ?;
238+
239+ // Now, we derefernce the vtable pointer.
240+ let alloc = this. memory ( ) . get ( vtable_ptr. alloc_id ) ?;
241+
242+ // Finally, we extract the pointer to 'box_me_up'.
243+ // The vtable is layed out in memory like this:
244+ //
245+ //```
246+ // <drop_ptr> (usize)
247+ // <size> (usize)
248+ // <align> (usize)
249+ // <method_ptr_1> (usize)
250+ // <method_ptr_2> (usize)
251+ // ...
252+ // <method_ptr_n> (usize)
253+ //```
254+ //
255+ // Since box_me_up is the first method pointer
256+ // in the vtable, we use an offset of 3 pointer sizes
257+ // (skipping over <drop_ptr>, <size>, and <align>)
258+
259+ let box_me_up_ptr = alloc. read_ptr_sized (
260+ this,
261+ vtable_ptr. offset ( this. pointer_size ( ) * 3 , this) ?
262+ ) ?. to_ptr ( ) ?;
263+
264+ // Get the actual function instance
265+ let box_me_up_fn = this. memory ( ) . get_fn ( box_me_up_ptr) ?;
266+ let box_me_up_mir = this. load_mir ( box_me_up_fn. def ) ?;
267+
268+ // Extract the signature
269+ // We know that there are no HRBTs here, so it's fine to use
270+ // skip_binder
271+ let fn_sig_temp = box_me_up_fn. ty ( * this. tcx ) . fn_sig ( * this. tcx ) ;
272+ let fn_sig = fn_sig_temp. skip_binder ( ) ;
273+
274+ // This is the layout of '*mut (dyn Any + Send)', which
275+ // is the return type of 'box_me_up'
276+ let dyn_ptr_layout = this. layout_of ( fn_sig. output ( ) ) ?;
277+
278+ // We allocate space to store the return value of box_me_up:
279+ // '*mut (dyn Any + Send)', which is a fat
280+
281+ let temp_ptr = this. allocate ( dyn_ptr_layout, MiriMemoryKind :: UnwindHelper . into ( ) ) ;
282+
283+ // Keep track of our current frame
284+ // This allows us to step throgh the exection of 'box_me_up',
285+ // exiting when we get back to this frame
286+ let cur_frame = this. cur_frame ( ) ;
287+
288+ this. push_stack_frame (
289+ box_me_up_fn,
290+ box_me_up_mir. span ,
291+ box_me_up_mir,
292+ Some ( temp_ptr. into ( ) ) ,
293+ StackPopCleanup :: None { cleanup : true }
294+ ) ?;
295+
296+ let mut args = this. frame ( ) . mir . args_iter ( ) ;
297+ let arg_0 = this. eval_place ( & mir:: Place :: Base ( mir:: PlaceBase :: Local ( args. next ( ) . unwrap ( ) ) ) ) ?;
298+ this. write_scalar ( data_ptr, arg_0) ?;
299+
300+ // Step through execution of 'box_me_up'
301+ // We know that we're finished when our stack depth
302+ // returns to where it was before.
303+ //
304+ // Note that everything will get completely screwed up
305+ // if 'box_me_up' panics. This is fine, since this
306+ // function should never panic, as it's part of the core
307+ // panic handling infrastructure
308+ //
309+ // Normally, we would just let Miri drive
310+ // the execution of this stack frame.
311+ // However, we need to access its return value
312+ // in order to properly unwind.
313+ //
314+ // When we 'return' from '__rustc_start_panic',
315+ // we need to be executing the panic catch handler.
316+ // Therefore, we take care all all of the unwinding logic
317+ // here, instead of letting the Miri main loop do it
318+ while this. cur_frame ( ) != cur_frame {
319+ this. step ( ) ?;
320+ }
321+
322+ // 'box_me_up' has finished. 'temp_ptr' now holds
323+ // a '*mut (dyn Any + Send)'
324+ // We want to split this into its consituient parts -
325+ // the data and vtable pointers - and store them back
326+ // into the panic handler frame
327+ let real_ret = this. read_immediate ( temp_ptr. into ( ) ) ?;
328+ let real_ret_data = real_ret. to_scalar_ptr ( ) ?;
329+ let real_ret_vtable = real_ret. to_meta ( ) ?. expect ( "Expected fat pointer" ) ;
330+
331+ // We're in panic unwind mode. We pop off stack
332+ // frames until one of two things happens: we reach
333+ // a frame with 'catch_panic' set, or we pop of all frames
334+ //
335+ // If we pop off all frames without encountering 'catch_panic',
336+ // we exut.
337+ //
338+ // If we encounter 'catch_panic', we continue execution at that
339+ // frame, filling in data from the panic
340+ //
341+ unwind_stack ( this, real_ret_data, real_ret_vtable) ?;
342+
343+ this. memory_mut ( ) . deallocate ( temp_ptr. to_ptr ( ) ?, None , MiriMemoryKind :: UnwindHelper . into ( ) ) ?;
344+ this. dump_place ( * dest. expect ( "dest is None!" ) ) ;
345+
346+ return Ok ( None )
347+
156348 }
157349 "exit" | "ExitProcess" => {
158350 // it's really u32 for ExitProcess, but we have to put it into the `Exit` error variant anyway
@@ -340,13 +532,27 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
340532 // data_ptr: *mut usize,
341533 // vtable_ptr: *mut usize,
342534 // ) -> u32
343- // We abort on panic, so not much is going on here, but we still have to call the closure.
344535 let f = this. read_scalar ( args[ 0 ] ) ?. to_ptr ( ) ?;
345536 let data = this. read_scalar ( args[ 1 ] ) ?. not_undef ( ) ?;
537+ let data_ptr = this. deref_operand ( args[ 2 ] ) ?;
538+ let vtable_ptr = this. deref_operand ( args[ 3 ] ) ?;
346539 let f_instance = this. memory ( ) . get_fn ( f) ?;
347540 this. write_null ( dest) ?;
348541 trace ! ( "__rust_maybe_catch_panic: {:?}" , f_instance) ;
349542
543+ // In unwind mode, we tag this frame with some extra data.
544+ // This lets '__rust_start_panic' know that it should jump back
545+ // to this frame is a panic occurs.
546+ if this. tcx . tcx . sess . panic_strategy ( ) == PanicStrategy :: Unwind {
547+ this. frame_mut ( ) . extra . catch_panic = Some ( UnwindData {
548+ data : data. to_ptr ( ) ?,
549+ data_ptr,
550+ vtable_ptr,
551+ dest : dest. clone ( ) ,
552+ ret
553+ } )
554+ }
555+
350556 // Now we make a function call.
351557 // TODO: consider making this reusable? `InterpretCx::step` does something similar
352558 // for the TLS destructors, and of course `eval_main`.
@@ -360,6 +566,7 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
360566 // Directly return to caller.
361567 StackPopCleanup :: Goto ( Some ( ret) ) ,
362568 ) ?;
569+
363570 let mut args = this. frame ( ) . mir . args_iter ( ) ;
364571
365572 let arg_local = args. next ( ) . ok_or_else ( ||
@@ -377,7 +584,7 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
377584 this. write_null ( dest) ?;
378585
379586 // Don't fall through, we do *not* want to `goto_block`!
380- return Ok ( ( ) ) ;
587+ return Ok ( None ) ;
381588 }
382589
383590 "memcmp" => {
@@ -890,7 +1097,7 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'
8901097
8911098 this. goto_block ( Some ( ret) ) ?;
8921099 this. dump_place ( * dest) ;
893- Ok ( ( ) )
1100+ Ok ( None )
8941101 }
8951102
8961103 fn write_null ( & mut self , dest : PlaceTy < ' tcx , Tag > ) -> EvalResult < ' tcx > {
@@ -944,3 +1151,89 @@ fn gen_random<'a, 'mir, 'tcx>(
9441151 this. memory_mut ( ) . get_mut ( ptr. alloc_id ) ?
9451152 . write_bytes ( tcx, ptr, & data)
9461153}
1154+
1155+ /// A helper method to unwind the stack.
1156+ ///
1157+ /// We execute the 'unwind' blocks associated with frame
1158+ /// terminators as we go along (these blocks are responsible
1159+ /// for dropping frame locals in the event of a panic)
1160+ ///
1161+ /// When we find our target frame, we write the panic payload
1162+ /// directly into its locals, and jump to it.
1163+ /// After that, panic handling is done - from the perspective
1164+ /// of the caller of '__rust_maybe_catch_panic', the function
1165+ /// has 'returned' normally, after which point Miri excecution
1166+ /// can proceeed normally.
1167+ fn unwind_stack < ' a , ' mir , ' tcx > (
1168+ this : & mut MiriEvalContext < ' a , ' mir , ' tcx > ,
1169+ payload_data_ptr : Scalar < Borrow > ,
1170+ payload_vtable_ptr : Scalar < Borrow >
1171+ ) -> EvalResult < ' tcx > {
1172+
1173+ let mut found = false ;
1174+
1175+ while !this. stack ( ) . is_empty ( ) {
1176+ // When '__rust_maybe_catch_panic' is called, it marks is frame
1177+ // with 'catch_panic'. When we find this marker, we've found
1178+ // our target frame to jump to.
1179+ if let Some ( unwind_data) = this. frame_mut ( ) . extra . catch_panic . take ( ) {
1180+ trace ! ( "unwinding: found target frame: {:?}" , this. frame( ) . span) ;
1181+
1182+ let data_ptr = unwind_data. data_ptr . clone ( ) ;
1183+ let vtable_ptr = unwind_data. vtable_ptr . clone ( ) ;
1184+ let dest = unwind_data. dest . clone ( ) ;
1185+ let ret = unwind_data. ret . clone ( ) ;
1186+ drop ( unwind_data) ;
1187+
1188+
1189+ // Here, we write directly into the frame of the function
1190+ // that called '__rust_maybe_catch_panic'.
1191+ // (NOT the function that called '__rust_start_panic')
1192+
1193+ this. write_scalar ( payload_data_ptr, data_ptr. into ( ) ) ?;
1194+ this. write_scalar ( payload_vtable_ptr, vtable_ptr. into ( ) ) ?;
1195+
1196+ // We 'return' the value 1 from __rust_maybe_catch_panic,
1197+ // since there was a panic
1198+ this. write_scalar ( Scalar :: from_int ( 1 , dest. layout . size ) , dest) ?;
1199+
1200+ // We're done - continue execution in the frame of the function
1201+ // that called '__rust_maybe_catch_panic,'
1202+ this. goto_block ( Some ( ret) ) ?;
1203+ found = true ;
1204+
1205+ break ;
1206+ } else {
1207+ // This frame is above our target frame on the call stack.
1208+ // We pop it off the stack, running its 'unwind' block if applicable
1209+ trace ! ( "unwinding: popping frame: {:?}" , this. frame( ) . span) ;
1210+ let block = & this. frame ( ) . mir . basic_blocks ( ) [ this. frame ( ) . block ] ;
1211+
1212+ // All frames in the call stack should be executing their terminators.,
1213+ // as that's the only way for a basic block to perform a function call
1214+ if let Some ( stmt) = block. statements . get ( this. frame ( ) . stmt ) {
1215+ panic ! ( "Unexpcted statement '{:?}' for frame {:?}" , stmt, this. frame( ) . span) ;
1216+ }
1217+
1218+ // We're only interested in terminator types which allow for a cleanuup
1219+ // block (e.g. Call), and that also actually provide one
1220+ if let Some ( Some ( unwind) ) = block. terminator ( ) . unwind ( ) {
1221+ this. goto_block ( Some ( * unwind) ) ?;
1222+
1223+ // Run the 'unwind' block until we encounter
1224+ // a 'Resume', which indicates that the block
1225+ // is done.
1226+ assert_eq ! ( this. run( ) ?, StepOutcome :: Resume ) ;
1227+ }
1228+
1229+ // Pop this frame, and continue on to the next one
1230+ this. pop_stack_frame_unwind ( ) ?;
1231+ }
1232+ }
1233+
1234+ if !found {
1235+ // The 'start_fn' lang item should always install a panic handler
1236+ return err ! ( Unreachable ) ;
1237+ }
1238+ return Ok ( ( ) )
1239+ }
0 commit comments