From d46dfa25d46be353a0cda39373e0319eb4f3ec69 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 20 Jul 2022 18:33:51 -0400 Subject: [PATCH] detect bad vptrs on dyn calls --- .../src/interpret/terminator.rs | 63 ++++++++++++++----- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 90e06f55cbf5..e7e60b5fa236 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -519,7 +519,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } // cannot use the shim here, because that will only result in infinite recursion - ty::InstanceDef::Virtual(_, idx) => { + ty::InstanceDef::Virtual(def_id, idx) => { let mut args = args.to_vec(); // We have to implement all "object safe receivers". So we have to go search for a // pointer or `dyn Trait` type, but it could be wrapped in newtypes. So recursively @@ -552,23 +552,54 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } }; - // Find and consult vtable. The type now could be something like RcBox, - // i.e., it is still not necessarily `ty::Dynamic` (so we cannot use - // `place.vtable()`), but it should have a `dyn Trait` tail. - assert!(matches!( - self.tcx - .struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env) - .kind(), - ty::Dynamic(..) - )); - let vtable = self.scalar_to_ptr(receiver_place.meta.unwrap_meta())?; - let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vtable)?.get(idx).copied() else { - throw_ub_format!( - "calling index {idx} of vtable {vtable} but \ - that vtable is too small or does not have a method at that index" - ) + // Obtain the underlying trait we are working on. + let receiver_tail = self + .tcx + .struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env); + let ty::Dynamic(data, ..) = receiver_tail.kind() else { + span_bug!(self.cur_span(), "dyanmic call on non-`dyn` type {}", receiver_tail) }; + // Get the required information from the vtable. + let vptr = self.scalar_to_ptr(receiver_place.meta.unwrap_meta())?; + let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?; + if dyn_trait != data.principal() { + throw_ub_format!( + "`dyn` call on a pointer whose vtable does not match its type" + ); + } + + // Now determine the actual method to call. We can do that in two different ways and + // compare them to ensure everything fits. + let ty::VtblEntry::Method(fn_inst) = self.get_vtable_entries(vptr)?[idx] else { + span_bug!(self.cur_span(), "dyn call index points at something that is not a method") + }; + if cfg!(debug_assertions) { + let tcx = *self.tcx; + + let trait_def_id = tcx.trait_of_item(def_id).unwrap(); + let virtual_trait_ref = + ty::TraitRef::from_method(tcx, trait_def_id, instance.substs); + assert_eq!( + receiver_tail, + virtual_trait_ref.self_ty(), + "mismatch in underlying dyn trait computation within Miri and MIR building", + ); + let existential_trait_ref = + ty::ExistentialTraitRef::erase_self_ty(tcx, virtual_trait_ref); + let concrete_trait_ref = existential_trait_ref.with_self_ty(tcx, dyn_ty); + + let concrete_method = Instance::resolve( + tcx, + self.param_env, + def_id, + instance.substs.rebase_onto(tcx, trait_def_id, concrete_trait_ref.substs), + ) + .unwrap() + .unwrap(); + assert_eq!(fn_inst, concrete_method); + } + // `*mut receiver_place.layout.ty` is almost the layout that we // want for args[0]: We have to project to field 0 because we want // a thin pointer.