Skip to content

Commit

Permalink
CFI: Generate super vtables explicitly
Browse files Browse the repository at this point in the history
CFI shimming means they're not gauranteed to be pre-generated.
Traditionally, the base vtable has all the elements of the supertrait
vtable, and so visiting the base vtable implies you don't need to visit
the supertrait vtable. However, with CFI the base vtable entries will
have invocation type `dyn Child`, and the parent vtable will have
invocation type `dyn Parent`, so they aren't actually the same instance,
and both must be visited.
  • Loading branch information
maurer committed Mar 4, 2024
1 parent 5ec1ec4 commit 18d731e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 13 deletions.
55 changes: 42 additions & 13 deletions compiler/rustc_monomorphize/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ use rustc_span::source_map::{dummy_spanned, respan, Spanned};
use rustc_span::symbol::{sym, Ident};
use rustc_span::{Span, DUMMY_SP};
use rustc_target::abi::Size;
use std::iter;
use std::path::PathBuf;

use crate::errors::{
Expand Down Expand Up @@ -1201,23 +1202,51 @@ fn create_mono_items_for_vtable_methods<'tcx>(
assert!(!poly_trait_ref.has_escaping_bound_vars());

// Walk all methods of the trait, including those of its supertraits
let entries = tcx.vtable_entries(poly_trait_ref);
let methods = entries
.iter()
.filter_map(|entry| match entry {
for entry in tcx.vtable_entries(poly_trait_ref) {
match entry {
VtblEntry::MetadataDropInPlace
| VtblEntry::MetadataSize
| VtblEntry::MetadataAlign
| VtblEntry::Vacant => None,
VtblEntry::TraitVPtr(_) => {
// all super trait items already covered, so skip them.
None
| VtblEntry::Vacant => (),
VtblEntry::TraitVPtr(super_trait) => {
// If CFI shims are enabled, the super_trait will use a different invocation
// type than the instances selected for this trait. This means we must walk
// the super_trait pointer visit its instances as well.
if tcx.sess.cfi_shims() {
let pep: ty::PolyExistentialPredicate<'tcx> =
super_trait.map_bound(|t| {
ty::ExistentialPredicate::Trait(
ty::ExistentialTraitRef::erase_self_ty(tcx, t),
)
});
let existential_predicates = tcx
.mk_poly_existential_predicates_from_iter(
iter::once(pep).chain(trait_ty.iter().skip(1)),
);
let super_trait_ty = Ty::new_dynamic(
tcx,
existential_predicates,
tcx.lifetimes.re_erased,
ty::Dyn,
);

create_mono_items_for_vtable_methods(
tcx,
super_trait_ty,
impl_ty,
source,
output,
);
}
}
VtblEntry::Method(instance) => {
let instance = instance.cfi_shim(tcx, invoke_trait);
if should_codegen_locally(tcx, &instance) {
output.push(create_fn_mono_item(tcx, instance, source));
}
}
VtblEntry::Method(instance) => Some(instance.cfi_shim(tcx, invoke_trait))
.filter(|instance| should_codegen_locally(tcx, instance)),
})
.map(|item| create_fn_mono_item(tcx, item, source));
output.extend(methods);
}
}
};

// Also add the destructor.
Expand Down
39 changes: 39 additions & 0 deletions tests/ui/sanitizer/cfi-super-vtable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Check that super-traits with vptrs have their shims generated

//@ needs-sanitizer-cfi
//@ compile-flags: --crate-type=bin -Cprefer-dynamic=off -Clto -Zsanitizer=cfi
//@ compile-flags: -C codegen-units=1 -C opt-level=0
//@ build-pass

trait Parent1 {
fn p1(&self);
}

trait Parent2 {
fn p2(&self);
}

// We need two parent traits to force the vtable upcasting code to choose to add a pointer to
// another vtable to the child. This vtable is generated even if trait upcasting is not in use.
trait Child : Parent1 + Parent2 {
fn c(&self);
}

struct Foo;

impl Parent1 for Foo {
fn p1(&self) {}
}

impl Parent2 for Foo {
fn p2(&self) {}
}

impl Child for Foo {
fn c(&self) {}
}

fn main() {
let x = &Foo as &dyn Child;
x.c();
}

0 comments on commit 18d731e

Please sign in to comment.