Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CFI: Repair vtables without altering types #122573

Closed
wants to merge 13 commits into from
Prev Previous commit
Next Next commit
CFI: Generate super vtables explicitly
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 15, 2024
commit c74b0187c7964087cd80aeb709df2137ef13c4b6
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 @@ -1214,23 +1215,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();
}