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