Skip to content

Commit 6205a56

Browse files
authored
Add support to all object-safe receiver types (#1326)
* Add support to all object-safe receiver types For object-safe traits, associated methods that can be dynamically dispatched must have the receiver type of Self as one of the following: - &Self - &mut Self - Box<Self> - Rc<Self> - Arc<Self> - Pin<P> where P is one of the types above ref: https://doc.rust-lang.org/reference/items/traits.html#object-safety Before this change, we only supported &Self and &mut Self. In order to add support to the other types, which are structures not pointers, we needed to make sure that the structure type that we encode in the vtable is compatible with the concrete type (ref 6.2.7.3 ISO/IEC 9899:202x). For that, we mirror the original type and replace the raw pointer type to be the data pointer type of `&dyn T`. * Move test to expected Issues with vtable encoding manifest as unreachable assertions instead of verification failures. Thus, use expected test to ensure the assertions are reachable and successful.
1 parent 25fa123 commit 6205a56

File tree

4 files changed

+459
-35
lines changed

4 files changed

+459
-35
lines changed

kani-compiler/src/codegen_cprover_gotoc/codegen/statement.rs

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustc_middle::ty::layout::LayoutOf;
2121
use rustc_middle::ty::{Instance, InstanceDef, Ty};
2222
use rustc_span::Span;
2323
use rustc_target::abi::{FieldsShape, Primitive, TagEncoding, Variants};
24-
use tracing::{debug, info_span, warn};
24+
use tracing::{debug, info_span, trace, warn};
2525

2626
impl<'tcx> GotocCtx<'tcx> {
2727
fn codegen_ret_unit(&mut self) -> Stmt {
@@ -379,6 +379,7 @@ impl<'tcx> GotocCtx<'tcx> {
379379
target: &Option<BasicBlock>,
380380
span: Span,
381381
) -> Stmt {
382+
debug!(?func, ?args, ?destination, ?span, "codegen_funcall");
382383
if self.is_intrinsic(func) {
383384
return self.codegen_funcall_of_intrinsic(func, args, destination, target, span);
384385
}
@@ -411,19 +412,9 @@ impl<'tcx> GotocCtx<'tcx> {
411412
}
412413
// Handle a virtual function call via a vtable lookup
413414
InstanceDef::Virtual(def_id, idx) => {
414-
// We must have at least one argument, and the first one
415-
// should be a fat pointer for the trait
416-
let trait_fat_ptr = fargs[0].to_owned();
417-
418-
//Check the Gotoc-level fat pointer type
419-
assert!(
420-
trait_fat_ptr.typ().is_rust_trait_fat_ptr(&self.symbol_table),
421-
"Expected fat pointer, got:\n{:?}",
422-
trait_fat_ptr,
423-
);
424-
415+
let self_ty = self.operand_ty(&args[0]);
425416
self.codegen_virtual_funcall(
426-
trait_fat_ptr,
417+
self_ty,
427418
def_id,
428419
idx,
429420
destination,
@@ -467,32 +458,87 @@ impl<'tcx> GotocCtx<'tcx> {
467458
};
468459
}
469460

461+
/// Extract a reference to self for virtual method calls.
462+
/// See [codegen_dynamic_function_sig](GotocCtx::codegen_dynamic_function_sig) for more
463+
/// details.
464+
fn extract_ptr(&self, arg_expr: Expr, arg_ty: Ty<'tcx>) -> Expr {
465+
// Generate an expression that indexes the pointer.
466+
let expr = self
467+
.receiver_data_path(arg_ty)
468+
.fold(arg_expr, |curr_expr, (name, _)| curr_expr.member(name, &self.symbol_table));
469+
470+
trace!(?arg_ty, gotoc_ty=?expr.typ(), gotoc_expr=?expr.value(), "extract_ptr");
471+
expr
472+
}
473+
474+
/// Codegen the dynamic call to a trait method via the fat pointer vtable.
475+
///
476+
/// If the original call was of the form
477+
/// f(arg0, arg1);
478+
///
479+
/// The new call should be of the form
480+
/// arg0.vtable->f(arg0.data,arg1);
481+
///
482+
/// For that, we do the following:
483+
/// 1. Extract the fat pointer out of the first argument.
484+
/// 2. Obtain the function pointer out of the fat pointer vtable.
485+
/// 3. Change the first argument to only reference the data pointer (instead of the fat one).
486+
/// - When the receiver type is a `struct` we need to build a structure that mirrors
487+
/// the original one but uses a thin pointer instead.
488+
/// 4. Generate the function call.
470489
fn codegen_virtual_funcall(
471490
&mut self,
472-
trait_fat_ptr: Expr,
491+
self_ty: Ty<'tcx>,
473492
def_id: DefId,
474493
idx: usize,
475494
place: &Place<'tcx>,
476495
fargs: &mut [Expr],
477496
loc: Location,
478497
) -> Vec<Stmt> {
479498
let vtable_field_name = self.vtable_field_name(def_id, idx);
499+
trace!(?self_ty, ?place, ?vtable_field_name, "codegen_virtual_funcall");
500+
501+
let trait_fat_ptr = self.extract_ptr(fargs[0].clone(), self_ty);
502+
assert!(
503+
trait_fat_ptr.typ().is_rust_trait_fat_ptr(&self.symbol_table),
504+
"Expected fat pointer, but got {:?}",
505+
trait_fat_ptr.typ()
506+
);
480507

481-
// Now that we have all the stuff we need, we can actually build the dynamic call
482-
// If the original call was of the form
483-
// f(arg0, arg1);
484-
// The new call should be of the form
485-
// arg0.vtable->f(arg0.data,arg1);
486508
let vtable_ref = trait_fat_ptr.to_owned().member("vtable", &self.symbol_table);
487509
let vtable = vtable_ref.dereference();
488510
let fn_ptr = vtable.member(vtable_field_name, &self.symbol_table);
489-
490-
// Update the argument from arg0 to arg0.data
491-
fargs[0] = trait_fat_ptr.to_owned().member("data", &self.symbol_table);
511+
trace!(fn_typ=?fn_ptr.typ(), "codegen_virtual_funcall");
512+
513+
let data_ptr = trait_fat_ptr.to_owned().member("data", &self.symbol_table);
514+
let mut ret_stmts = vec![];
515+
fargs[0] = if self_ty.is_adt() {
516+
// Generate a temp variable and assign its inner pointer to the fat_ptr.data.
517+
match fn_ptr.typ() {
518+
Type::Pointer { typ: box Type::Code { parameters, .. } } => {
519+
let param_typ = parameters.first().unwrap().typ();
520+
let tmp = self.gen_temp_variable(param_typ.clone(), loc).to_expr();
521+
debug!(?tmp,
522+
orig=?data_ptr.typ(),
523+
"codegen_virtual_funcall");
524+
ret_stmts.push(Stmt::decl(tmp.clone(), None, loc));
525+
ret_stmts.push(Stmt::assign(
526+
self.extract_ptr(tmp.clone(), self_ty),
527+
data_ptr,
528+
loc,
529+
));
530+
tmp
531+
}
532+
_ => unreachable!("Unexpected virtual function type: {:?}", fn_ptr.typ()),
533+
}
534+
} else {
535+
// Update the argument from arg0 to arg0.data if arg0 is a fat pointer.
536+
data_ptr
537+
};
492538

493539
// For soundness, add an assertion that the vtable function call is not null.
494-
// Otherwise, CBMC might treat this as an assert(0) and later user-added assertions
495-
// could be vacuously true.
540+
// Otherwise, CBMC might treat this as an assume(0) and later user-added assertions
541+
// could become unreachable.
496542
let call_is_nonnull = fn_ptr.clone().is_nonnull();
497543
let assert_msg = format!("Non-null virtual function call for {:?}", vtable_field_name);
498544
let assert_nonnull =
@@ -506,7 +552,9 @@ impl<'tcx> GotocCtx<'tcx> {
506552
} else {
507553
call_stmt
508554
};
509-
vec![assert_nonnull, call_stmt]
555+
ret_stmts.push(assert_nonnull);
556+
ret_stmts.push(call_stmt);
557+
ret_stmts
510558
}
511559

512560
/// 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.

kani-compiler/src/codegen_cprover_gotoc/codegen/typ.rs

Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ impl TypeExt for Type {
101101
}
102102
}
103103
}
104+
104105
trait ExprExt {
105106
fn unit(symbol_table: &SymbolTable) -> Self;
106107

@@ -354,9 +355,11 @@ impl<'tcx> GotocCtx<'tcx> {
354355
Type::unit().to_typedef(inner_name)
355356
}
356357

357-
/// This will codegen the raw pointer to the trait data.
358+
/// Codegen the pointer type for a concrete object that implements the trait object.
359+
/// I.e.: A trait object is a fat pointer which contains a pointer to a concrete object
360+
/// and a pointer to its vtable. This method returns a type for the first pointer.
358361
pub fn codegen_trait_data_pointer(&mut self, typ: ty::Ty<'tcx>) -> Type {
359-
assert!(typ.is_trait());
362+
assert!(self.use_vtable_fat_pointer(typ));
360363
self.codegen_ty(typ).to_pointer()
361364
}
362365

@@ -440,6 +443,7 @@ impl<'tcx> GotocCtx<'tcx> {
440443

441444
vtable_base.append(&mut flds);
442445
}
446+
debug!(ty=?t, ?vtable_base, "trait_vtable_field_types");
443447
vtable_base
444448
} else {
445449
unreachable!("Expected to get a dynamic object here");
@@ -918,24 +922,35 @@ impl<'tcx> GotocCtx<'tcx> {
918922

919923
/// Generate code for a trait function declaration.
920924
///
921-
/// Dynamic function calls first parameter is the fat-pointer representing self.
922-
/// For closures, the type of the first argument is dyn T not &dyn T.
923-
pub fn codegen_dynamic_function_sig(&mut self, sig: PolyFnSig<'tcx>) -> Type {
925+
/// Dynamic function calls first parameter is self which must be one of the following:
926+
///
927+
/// As of Jul 2022:
928+
/// P = &Self | &mut Self | Box<Self> | Rc<Self> | Arc<Self>
929+
/// S = P | Pin<P>
930+
///
931+
/// See <https://doc.rust-lang.org/reference/items/traits.html#object-safety> for more details.
932+
fn codegen_dynamic_function_sig(&mut self, sig: PolyFnSig<'tcx>) -> Type {
924933
let sig = self.monomorphize(sig);
925934
let sig = self.tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), sig);
926-
927935
let mut is_first = true;
928936
let params = sig
929937
.inputs()
930938
.iter()
931939
.filter_map(|arg_type| {
932940
if is_first {
933-
// This should &dyn T or dyn T (for closures).
934941
is_first = false;
935-
let first_ty = pointee_type(*arg_type).unwrap_or(*arg_type);
936942
debug!(self_type=?arg_type, fn_signature=?sig, "codegen_dynamic_function_sig");
937-
assert!(first_ty.is_trait(), "Expected self type to be a trait");
938-
Some(self.codegen_trait_data_pointer(first_ty))
943+
if arg_type.is_ref() {
944+
// Convert fat pointer to thin pointer to data portion.
945+
let first_ty = pointee_type(*arg_type).unwrap();
946+
Some(self.codegen_trait_data_pointer(first_ty))
947+
} else if arg_type.is_trait() {
948+
// Convert dyn T to thin pointer.
949+
Some(self.codegen_trait_data_pointer(*arg_type))
950+
} else {
951+
// Codegen type with thin pointer (E.g.: Box<dyn T> -> Box<data_ptr>).
952+
Some(self.codegen_trait_receiver(*arg_type))
953+
}
939954
} else if self.ignore_var_ty(*arg_type) {
940955
debug!("Ignoring type {:?} in function signature", arg_type);
941956
None
@@ -1367,6 +1382,52 @@ impl<'tcx> GotocCtx<'tcx> {
13671382
_ => false,
13681383
}
13691384
}
1385+
1386+
/// Generate code for a valid object-safe trait receiver type.
1387+
///
1388+
/// Note that all these types only contain the data pointer and ZST fields. Thus, we generate
1389+
/// the non-ZST branch manually. In some cases, this method is called from inside
1390+
/// `codegen_ty(arg_ty)` so we don't have information about the final type.
1391+
fn codegen_trait_receiver(&mut self, arg_ty: Ty<'tcx>) -> Type {
1392+
// Collect structs that need to be modified
1393+
// Collect the non-ZST fields until we find a fat pointer.
1394+
let mut data_path = vec![arg_ty];
1395+
data_path.extend(self.receiver_data_path(arg_ty).map(|(_, typ)| typ));
1396+
1397+
trace!(?arg_ty, ?data_path, "codegen_trait_receiver");
1398+
let orig_pointer_ty = data_path.pop().unwrap();
1399+
assert!(self.is_vtable_fat_pointer(orig_pointer_ty));
1400+
1401+
// Traverse type and replace pointer type.
1402+
let ptr_type = self.codegen_trait_data_pointer(pointee_type(orig_pointer_ty).unwrap());
1403+
data_path.iter().rev().fold(ptr_type, |last_type, curr| {
1404+
// Codegen the type replacing the non-zst field.
1405+
let new_name = self.ty_mangled_name(*curr).to_string() + "::WithDataPtr";
1406+
if let ty::Adt(adt_def, adt_substs) = curr.kind() {
1407+
let fields = &adt_def.variants().get(VariantIdx::from_u32(0)).unwrap().fields;
1408+
self.ensure_struct(new_name, NO_PRETTY_NAME, |ctx, s_name| {
1409+
let fields_shape = ctx.layout_of(*curr).layout.fields();
1410+
let components = fields_shape
1411+
.index_by_increasing_offset()
1412+
.map(|idx| {
1413+
let name = fields[idx].name.to_string().intern();
1414+
let field_ty = fields[idx].ty(ctx.tcx, adt_substs);
1415+
let typ = if !ctx.layout_of(field_ty).is_zst() {
1416+
last_type.clone()
1417+
} else {
1418+
ctx.codegen_ty(field_ty)
1419+
};
1420+
DatatypeComponent::Field { name, typ }
1421+
})
1422+
.collect();
1423+
trace!(?data_path, ?curr, ?s_name, ?components, "codegen_trait_receiver");
1424+
components
1425+
})
1426+
} else {
1427+
unreachable!("Expected structs only {:?}", curr);
1428+
}
1429+
})
1430+
}
13701431
}
13711432

13721433
/// Use maps instead of lists to manage mir struct components.
@@ -1412,6 +1473,70 @@ impl<'tcx> GotocCtx<'tcx> {
14121473
}
14131474
if typ.is_trait() { Some(typ) } else { None }
14141475
}
1476+
1477+
/// This function provides an iterator that traverses the data path of a receiver type. I.e.:
1478+
/// the path that leads to the data pointer.
1479+
///
1480+
/// E.g.: For `Rc<dyn T>` where the Rc definition is:
1481+
/// ```
1482+
/// pub struct Rc<T: ?Sized> {
1483+
/// ptr: NonNull<RcBox<T>>,
1484+
/// phantom: PhantomData<RcBox<T>>,
1485+
/// }
1486+
///
1487+
/// pub struct NonNull<T: ?Sized> {
1488+
/// pointer: *const T,
1489+
/// }
1490+
/// ```
1491+
///
1492+
/// The behavior will be:
1493+
/// ```text
1494+
/// let it = self.receiver_data_path(rc_typ);
1495+
/// assert_eq!(it.next(), Some((String::from("ptr"), non_null_typ);
1496+
/// assert_eq!(it.next(), Some((String::from("pointer"), raw_ptr_typ);
1497+
/// assert_eq!(it.next(), None);
1498+
/// ```
1499+
///
1500+
/// Pre-condition: The argument must be a valid receiver for dispatchable trait functions.
1501+
/// See <https://doc.rust-lang.org/reference/items/traits.html#object-safety> for more details.
1502+
pub fn receiver_data_path<'a>(
1503+
self: &'a Self,
1504+
typ: Ty<'tcx>,
1505+
) -> impl Iterator<Item = (String, Ty<'tcx>)> + 'a {
1506+
struct ReceiverIter<'tcx, 'a> {
1507+
pub curr: Ty<'tcx>,
1508+
pub ctx: &'a GotocCtx<'tcx>,
1509+
}
1510+
1511+
impl<'tcx, 'a> Iterator for ReceiverIter<'tcx, 'a> {
1512+
type Item = (String, Ty<'tcx>);
1513+
1514+
fn next(&mut self) -> Option<Self::Item> {
1515+
if let ty::Adt(adt_def, adt_substs) = self.curr.kind() {
1516+
assert_eq!(
1517+
adt_def.variants().len(),
1518+
1,
1519+
"Expected a single-variant ADT. Found {:?}",
1520+
self.curr
1521+
);
1522+
let ctx = self.ctx;
1523+
let fields = &adt_def.variants().get(VariantIdx::from_u32(0)).unwrap().fields;
1524+
let mut non_zsts = fields
1525+
.iter()
1526+
.filter(|field| !ctx.layout_of(field.ty(ctx.tcx, adt_substs)).is_zst())
1527+
.map(|non_zst| (non_zst.name.to_string(), non_zst.ty(ctx.tcx, adt_substs)));
1528+
let (name, next) = non_zsts.next().expect("Expected one non-zst field.");
1529+
self.curr = next;
1530+
assert!(non_zsts.next().is_none(), "Expected only one non-zst field.");
1531+
Some((name, self.curr))
1532+
} else {
1533+
None
1534+
}
1535+
}
1536+
}
1537+
1538+
ReceiverIter { ctx: self, curr: typ }
1539+
}
14151540
}
14161541

14171542
/// The mir type is a mir pointer type.

0 commit comments

Comments
 (0)