|
3 | 3 | use super::cbmc::goto_program::{BuiltinFn, Expr, Location, Stmt, Type};
|
4 | 4 | use super::metadata::*;
|
5 | 5 | use super::typ::FN_RETURN_VOID_VAR_NAME;
|
| 6 | +use rustc_hir::def_id::DefId; |
6 | 7 | use rustc_middle::mir;
|
7 | 8 | use rustc_middle::mir::{
|
8 | 9 | BasicBlock, Operand, Place, Statement, StatementKind, SwitchTargets, Terminator, TerminatorKind,
|
@@ -276,79 +277,101 @@ impl<'tcx> GotocCtx<'tcx> {
|
276 | 277 |
|
277 | 278 | let (p, target) = destination.unwrap();
|
278 | 279 |
|
279 |
| - if let ty::InstanceDef::DropGlue(_, None) = instance.def { |
280 |
| - // here an empty drop glue is invoked. we just ignore it. |
281 |
| - return Stmt::goto(self.current_fn().find_label(&target), Location::none()); |
282 |
| - } |
283 |
| - |
284 |
| - // Handle the case of a virtual function call via vtable lookup. |
285 |
| - let mut stmts = if let InstanceDef::Virtual(def_id, size) = instance.def { |
286 |
| - debug!( |
287 |
| - "Codegen a call through a virtual function. def_id: {:?} size: {:?}", |
288 |
| - def_id, size |
289 |
| - ); |
290 |
| - |
291 |
| - // The first argument to a virtual function is a fat pointer for the trait |
292 |
| - let trait_fat_ptr = fargs[0].to_owned(); |
293 |
| - let vtable_field_name = self.vtable_field_name(def_id); |
294 |
| - |
295 |
| - // Now that we have all the stuff we need, we can actually build the dynamic call |
296 |
| - // If the original call was of the form |
297 |
| - // f(arg0, arg1); |
298 |
| - // The new call should be of the form |
299 |
| - // arg0.vtable->f(arg0.data,arg1); |
300 |
| - let vtable_ref = trait_fat_ptr.to_owned().member("vtable", &self.symbol_table); |
301 |
| - let vtable = vtable_ref.dereference(); |
302 |
| - let fn_ptr = vtable.member(&vtable_field_name, &self.symbol_table); |
303 |
| - |
304 |
| - // Update the argument from arg0 to arg0.data |
305 |
| - fargs[0] = trait_fat_ptr.to_owned().member("data", &self.symbol_table); |
| 280 | + let mut stmts: Vec<Stmt> = match instance.def { |
| 281 | + // Here an empty drop glue is invoked; we just ignore it. |
| 282 | + InstanceDef::DropGlue(_, None) => { |
| 283 | + return Stmt::goto(self.current_fn().find_label(&target), Location::none()); |
| 284 | + } |
| 285 | + // Handle a virtual function call via a vtable lookup |
| 286 | + InstanceDef::Virtual(def_id, _) => { |
| 287 | + // We must have at least one argument, and the first one |
| 288 | + // should be a fat pointer for the trait |
| 289 | + let trait_fat_ptr = fargs[0].to_owned(); |
306 | 290 |
|
307 |
| - // For soundness, add an assertion that the vtable function call is not null. |
308 |
| - // Otherwise, CBMC might treat this as an assert(0) and later user-added assertions |
309 |
| - // could be vacuously true. |
310 |
| - let call_is_nonnull = fn_ptr.clone().is_nonnull(); |
311 |
| - let assert_msg = |
312 |
| - format!("Non-null virtual function call for {:?}", vtable_field_name); |
313 |
| - let assert_nonnull = Stmt::assert(call_is_nonnull, &assert_msg, loc.clone()); |
| 291 | + //Check the Gotoc-level fat pointer type |
| 292 | + assert!(trait_fat_ptr.typ().is_rust_trait_fat_ptr(&self.symbol_table)); |
314 | 293 |
|
315 |
| - // Virtual function call and corresponding nonnull assertion. |
316 |
| - let func_exp: Expr = fn_ptr.dereference(); |
317 |
| - vec![ |
318 |
| - assert_nonnull, |
319 |
| - self.codegen_expr_to_place(&p, func_exp.call(fargs)) |
320 |
| - .with_location(loc.clone()), |
321 |
| - ] |
322 |
| - } else { |
323 |
| - // Non-virtual function call. |
324 |
| - let func_exp = self.codegen_operand(func); |
325 |
| - vec![ |
326 |
| - self.codegen_expr_to_place(&p, func_exp.call(fargs)) |
327 |
| - .with_location(loc.clone()), |
328 |
| - ] |
| 294 | + self.codegen_virtual_funcall( |
| 295 | + trait_fat_ptr, |
| 296 | + def_id, |
| 297 | + &p, |
| 298 | + &mut fargs, |
| 299 | + loc.clone(), |
| 300 | + ) |
| 301 | + } |
| 302 | + // Normal, non-virtual function calls |
| 303 | + InstanceDef::Item(..) |
| 304 | + | InstanceDef::DropGlue(_, Some(_)) |
| 305 | + | InstanceDef::Intrinsic(..) |
| 306 | + | InstanceDef::FnPtrShim(.., _) |
| 307 | + | InstanceDef::VtableShim(..) |
| 308 | + | InstanceDef::ReifyShim(..) |
| 309 | + | InstanceDef::ClosureOnceShim { call_once: _ } |
| 310 | + | InstanceDef::CloneShim(..) => { |
| 311 | + let func_exp = self.codegen_operand(func); |
| 312 | + vec![ |
| 313 | + self.codegen_expr_to_place(&p, func_exp.call(fargs)) |
| 314 | + .with_location(loc.clone()), |
| 315 | + ] |
| 316 | + } |
329 | 317 | };
|
330 |
| - |
331 |
| - // Add return jump. |
332 | 318 | stmts.push(Stmt::goto(self.current_fn().find_label(&target), loc.clone()));
|
333 |
| - |
334 |
| - // Produce the full function call block. |
335 |
| - Stmt::block(stmts, loc) |
| 319 | + return Stmt::block(stmts, loc); |
336 | 320 | }
|
| 321 | + // Function call through a pointer |
337 | 322 | ty::FnPtr(_) => {
|
338 | 323 | let (p, target) = destination.unwrap();
|
339 | 324 | let func_expr = self.codegen_operand(func).dereference();
|
340 |
| - // Actually generate the function call, and store the return value, if any. |
341 |
| - Stmt::block( |
| 325 | + // Actually generate the function call and return. |
| 326 | + return Stmt::block( |
342 | 327 | vec![
|
343 | 328 | self.codegen_expr_to_place(&p, func_expr.call(fargs))
|
344 | 329 | .with_location(loc.clone()),
|
345 | 330 | Stmt::goto(self.current_fn().find_label(&target), loc.clone()),
|
346 | 331 | ],
|
347 | 332 | loc,
|
348 |
| - ) |
| 333 | + ); |
349 | 334 | }
|
350 | 335 | x => unreachable!("Function call where the function was of unexpected type: {:?}", x),
|
351 |
| - } |
| 336 | + }; |
| 337 | + } |
| 338 | + |
| 339 | + fn codegen_virtual_funcall( |
| 340 | + &mut self, |
| 341 | + trait_fat_ptr: Expr, |
| 342 | + def_id: DefId, |
| 343 | + place: &Place<'tcx>, |
| 344 | + fargs: &mut Vec<Expr>, |
| 345 | + loc: Location, |
| 346 | + ) -> Vec<Stmt> { |
| 347 | + let vtable_field_name = self.vtable_field_name(def_id); |
| 348 | + |
| 349 | + // Now that we have all the stuff we need, we can actually build the dynamic call |
| 350 | + // If the original call was of the form |
| 351 | + // f(arg0, arg1); |
| 352 | + // The new call should be of the form |
| 353 | + // arg0.vtable->f(arg0.data,arg1); |
| 354 | + let vtable_ref = trait_fat_ptr.to_owned().member("vtable", &self.symbol_table); |
| 355 | + let vtable = vtable_ref.dereference(); |
| 356 | + let fn_ptr = vtable.member(&vtable_field_name, &self.symbol_table); |
| 357 | + |
| 358 | + // Update the argument from arg0 to arg0.data |
| 359 | + fargs[0] = trait_fat_ptr.to_owned().member("data", &self.symbol_table); |
| 360 | + |
| 361 | + // For soundness, add an assertion that the vtable function call is not null. |
| 362 | + // Otherwise, CBMC might treat this as an assert(0) and later user-added assertions |
| 363 | + // could be vacuously true. |
| 364 | + let call_is_nonnull = fn_ptr.clone().is_nonnull(); |
| 365 | + let assert_msg = format!("Non-null virtual function call for {:?}", vtable_field_name); |
| 366 | + let assert_nonnull = Stmt::assert(call_is_nonnull, &assert_msg, loc.clone()); |
| 367 | + |
| 368 | + // Virtual function call and corresponding nonnull assertion. |
| 369 | + let func_exp: Expr = fn_ptr.dereference(); |
| 370 | + vec![ |
| 371 | + assert_nonnull, |
| 372 | + self.codegen_expr_to_place(place, func_exp.call(fargs.to_vec())) |
| 373 | + .with_location(loc.clone()), |
| 374 | + ] |
352 | 375 | }
|
353 | 376 |
|
354 | 377 | /// A place is similar to the C idea of a LHS. For example, the returned value of a function call is stored to a place.
|
|
0 commit comments