Skip to content

Commit

Permalink
More assertions, tests, and miri coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
compiler-errors committed Jan 11, 2025
1 parent 76eb4f3 commit 305a3e0
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 62 deletions.
54 changes: 26 additions & 28 deletions compiler/rustc_const_eval/src/interpret/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,35 +414,33 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {

// Sanity-check that `supertrait_vtable_slot` in this type's vtable indeed produces
// our destination trait.
if cfg!(debug_assertions) {
let vptr_entry_idx =
self.tcx.supertrait_vtable_slot((src_pointee_ty, dest_pointee_ty));
let vtable_entries = self.vtable_entries(data_a.principal(), ty);
if let Some(entry_idx) = vptr_entry_idx {
let Some(&ty::VtblEntry::TraitVPtr(upcast_trait_ref)) =
vtable_entries.get(entry_idx)
else {
span_bug!(
self.cur_span(),
"invalid vtable entry index in {} -> {} upcast",
src_pointee_ty,
dest_pointee_ty
);
};
let erased_trait_ref =
ty::ExistentialTraitRef::erase_self_ty(*self.tcx, upcast_trait_ref);
assert!(data_b.principal().is_some_and(|b| self.eq_in_param_env(
erased_trait_ref,
self.tcx.instantiate_bound_regions_with_erased(b)
)));
} else {
// In this case codegen would keep using the old vtable. We don't want to do
// that as it has the wrong trait. The reason codegen can do this is that
// one vtable is a prefix of the other, so we double-check that.
let vtable_entries_b = self.vtable_entries(data_b.principal(), ty);
assert!(&vtable_entries[..vtable_entries_b.len()] == vtable_entries_b);
let vptr_entry_idx =
self.tcx.supertrait_vtable_slot((src_pointee_ty, dest_pointee_ty));
let vtable_entries = self.vtable_entries(data_a.principal(), ty);
if let Some(entry_idx) = vptr_entry_idx {
let Some(&ty::VtblEntry::TraitVPtr(upcast_trait_ref)) =
vtable_entries.get(entry_idx)
else {
span_bug!(
self.cur_span(),
"invalid vtable entry index in {} -> {} upcast",
src_pointee_ty,
dest_pointee_ty
);
};
}
let erased_trait_ref =
ty::ExistentialTraitRef::erase_self_ty(*self.tcx, upcast_trait_ref);
assert!(data_b.principal().is_some_and(|b| self.eq_in_param_env(
erased_trait_ref,
self.tcx.instantiate_bound_regions_with_erased(b)
)));
} else {
// In this case codegen would keep using the old vtable. We don't want to do
// that as it has the wrong trait. The reason codegen can do this is that
// one vtable is a prefix of the other, so we double-check that.
let vtable_entries_b = self.vtable_entries(data_b.principal(), ty);
assert!(&vtable_entries[..vtable_entries_b.len()] == vtable_entries_b);
};

// Get the destination trait vtable and return that.
let new_vptr = self.get_vtable_ptr(ty, data_b)?;
Expand Down
57 changes: 23 additions & 34 deletions compiler/rustc_trait_selection/src/traits/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ use std::fmt::Debug;
use std::ops::ControlFlow;

use rustc_hir::def_id::DefId;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::ObligationCause;
use rustc_infer::traits::util::PredicateSet;
use rustc_middle::bug;
use rustc_middle::query::Providers;
Expand All @@ -14,7 +12,7 @@ use rustc_span::DUMMY_SP;
use smallvec::{SmallVec, smallvec};
use tracing::debug;

use crate::traits::{ObligationCtxt, impossible_predicates, is_vtable_safe_method};
use crate::traits::{impossible_predicates, is_vtable_safe_method};

#[derive(Clone, Debug)]
pub enum VtblSegment<'tcx> {
Expand Down Expand Up @@ -230,6 +228,11 @@ fn vtable_entries<'tcx>(
trait_ref: ty::TraitRef<'tcx>,
) -> &'tcx [VtblEntry<'tcx>] {
debug_assert!(!trait_ref.has_non_region_infer() && !trait_ref.has_non_region_param());
debug_assert_eq!(
tcx.normalize_erasing_regions(ty::TypingEnv::fully_monomorphized(), trait_ref),
trait_ref,
"vtable trait ref should be normalized"
);

debug!("vtable_entries({:?})", trait_ref);

Expand Down Expand Up @@ -307,6 +310,11 @@ fn vtable_entries<'tcx>(
// for `Supertrait`'s methods in the vtable of `Subtrait`.
pub(crate) fn first_method_vtable_slot<'tcx>(tcx: TyCtxt<'tcx>, key: ty::TraitRef<'tcx>) -> usize {
debug_assert!(!key.has_non_region_infer() && !key.has_non_region_param());
debug_assert_eq!(
tcx.normalize_erasing_regions(ty::TypingEnv::fully_monomorphized(), key),
key,
"vtable trait ref should be normalized"
);

let ty::Dynamic(source, _, _) = *key.self_ty().kind() else {
bug!();
Expand All @@ -325,11 +333,9 @@ pub(crate) fn first_method_vtable_slot<'tcx>(tcx: TyCtxt<'tcx>, key: ty::TraitRe
vptr_offset += TyCtxt::COMMON_VTABLE_ENTRIES.len();
}
VtblSegment::TraitOwnEntries { trait_ref: vtable_principal, emit_vptr } => {
if trait_refs_are_compatible(
tcx,
ty::ExistentialTraitRef::erase_self_ty(tcx, vtable_principal),
target_principal,
) {
if ty::ExistentialTraitRef::erase_self_ty(tcx, vtable_principal)
== target_principal
{
return ControlFlow::Break(vptr_offset);
}

Expand Down Expand Up @@ -360,6 +366,12 @@ pub(crate) fn supertrait_vtable_slot<'tcx>(
),
) -> Option<usize> {
debug_assert!(!key.has_non_region_infer() && !key.has_non_region_param());
debug_assert_eq!(
tcx.normalize_erasing_regions(ty::TypingEnv::fully_monomorphized(), key),
key,
"upcasting trait refs should be normalized"
);

let (source, target) = key;

// If the target principal is `None`, we can just return `None`.
Expand All @@ -386,11 +398,9 @@ pub(crate) fn supertrait_vtable_slot<'tcx>(
VtblSegment::TraitOwnEntries { trait_ref: vtable_principal, emit_vptr } => {
vptr_offset +=
tcx.own_existential_vtable_entries(vtable_principal.def_id).len();
if trait_refs_are_compatible(
tcx,
ty::ExistentialTraitRef::erase_self_ty(tcx, vtable_principal),
target_principal,
) {
if ty::ExistentialTraitRef::erase_self_ty(tcx, vtable_principal)
== target_principal
{
if emit_vptr {
return ControlFlow::Break(Some(vptr_offset));
} else {
Expand All @@ -410,27 +420,6 @@ pub(crate) fn supertrait_vtable_slot<'tcx>(
prepare_vtable_segments(tcx, source_principal, vtable_segment_callback).unwrap()
}

fn trait_refs_are_compatible<'tcx>(
tcx: TyCtxt<'tcx>,
vtable_principal: ty::ExistentialTraitRef<'tcx>,
target_principal: ty::ExistentialTraitRef<'tcx>,
) -> bool {
if vtable_principal.def_id != target_principal.def_id {
return false;
}

let (infcx, param_env) =
tcx.infer_ctxt().build_with_typing_env(ty::TypingEnv::fully_monomorphized());
let ocx = ObligationCtxt::new(&infcx);
let source_principal = ocx.normalize(&ObligationCause::dummy(), param_env, vtable_principal);
let target_principal = ocx.normalize(&ObligationCause::dummy(), param_env, target_principal);
let Ok(()) = ocx.eq(&ObligationCause::dummy(), param_env, target_principal, source_principal)
else {
return false;
};
ocx.select_all_or_error().is_empty()
}

pub(super) fn provide(providers: &mut Providers) {
*providers = Providers {
own_existential_vtable_entries,
Expand Down
50 changes: 50 additions & 0 deletions src/tools/miri/tests/pass/dyn-upcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ fn main() {
replace_vptr();
vtable_nop_cast();
drop_principal();
modulo_binder();
modulo_assoc();
}

fn vtable_nop_cast() {
Expand Down Expand Up @@ -482,3 +484,51 @@ fn drop_principal() {
println!("before");
drop(y);
}

fn modulo_binder() {
trait Supertrait<T> {
fn _print_numbers(&self, mem: &[usize; 100]) {
println!("{mem:?}");
}
}
impl<T> Supertrait<T> for () {}

trait Trait<T, U>: Supertrait<T> + Supertrait<U> {
fn say_hello(&self, _: &usize) {
println!("Hello!");
}
}
impl<T, U> Trait<T, U> for () {}

(&() as &'static dyn for<'a> Trait<&'static (), &'a ()>
as &'static dyn Trait<&'static (), &'static ()>)
.say_hello(&0);
}

fn modulo_assoc() {
trait Supertrait<T> {
fn _print_numbers(&self, mem: &[usize; 100]) {
println!("{mem:?}");
}
}
impl<T> Supertrait<T> for () {}

trait Identity {
type Selff;
}
impl<Selff> Identity for Selff {
type Selff = Selff;
}

trait Middle<T>: Supertrait<()> + Supertrait<T> {
fn say_hello(&self, _: &usize) {
println!("Hello!");
}
}
impl<T> Middle<T> for () {}

trait Trait: Middle<<() as Identity>::Selff> {}
impl Trait for () {}

(&() as &dyn Trait as &dyn Middle<()>).say_hello(&0);
}
2 changes: 2 additions & 0 deletions src/tools/miri/tests/pass/dyn-upcast.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ before
goodbye
before
goodbye
Hello!
Hello!

0 comments on commit 305a3e0

Please sign in to comment.