Auto merge of #128742 - RalfJung:miri-vtable-uniqueness, r=saethlin
miri: make vtable addresses not globally unique Miri currently gives vtables a unique global address. That's not actually matching reality though. So this PR enables Miri to generate different addresses for the same type-trait pair. To avoid generating an unbounded number of `AllocId` (and consuming unbounded amounts of memory), we use the "salt" technique that we also already use for giving constants non-unique addresses: the cache is keyed on a "salt" value n top of the actually relevant key, and Miri picks a random salt (currently in the range `0..16`) each time it needs to choose an `AllocId` for one of these globals -- that means we'll get up to 16 different addresses for each vtable. The salt scheme is integrated into the global allocation deduplication logic in `tcx`, and also used for functions and string literals. (So this also fixes the problem that casting the same function to a fn ptr over and over will consume unbounded memory.) r? `@saethlin` Fixes https://github.com/rust-lang/miri/issues/3737
This commit is contained in:
commit
591ecb88df
24 changed files with 335 additions and 209 deletions
|
@ -400,6 +400,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
}
|
||||
(ty::Dynamic(data_a, _, ty::Dyn), ty::Dynamic(data_b, _, ty::Dyn)) => {
|
||||
let val = self.read_immediate(src)?;
|
||||
// MIR building generates odd NOP casts, prevent them from causing unexpected trouble.
|
||||
// See <https://github.com/rust-lang/rust/issues/128880>.
|
||||
// FIXME: ideally we wouldn't have to do this.
|
||||
if data_a == data_b {
|
||||
return self.write_immediate(*val, dest);
|
||||
}
|
||||
// Take apart the old pointer, and find the dynamic type.
|
||||
let (old_data, old_vptr) = val.to_scalar_pair();
|
||||
let old_data = old_data.to_pointer(self)?;
|
||||
|
|
|
@ -19,7 +19,7 @@ use rustc_target::spec::abi::Abi as CallAbi;
|
|||
use super::{
|
||||
throw_unsup, throw_unsup_format, AllocBytes, AllocId, AllocKind, AllocRange, Allocation,
|
||||
ConstAllocation, CtfeProvenance, FnArg, Frame, ImmTy, InterpCx, InterpResult, MPlaceTy,
|
||||
MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance,
|
||||
MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance, CTFE_ALLOC_SALT,
|
||||
};
|
||||
|
||||
/// Data returned by [`Machine::after_stack_pop`], and consumed by
|
||||
|
@ -575,6 +575,14 @@ pub trait Machine<'tcx>: Sized {
|
|||
{
|
||||
eval(ecx, val, span, layout)
|
||||
}
|
||||
|
||||
/// Returns the salt to be used for a deduplicated global alloation.
|
||||
/// If the allocation is for a function, the instance is provided as well
|
||||
/// (this lets Miri ensure unique addresses for some functions).
|
||||
fn get_global_alloc_salt(
|
||||
ecx: &InterpCx<'tcx, Self>,
|
||||
instance: Option<ty::Instance<'tcx>>,
|
||||
) -> usize;
|
||||
}
|
||||
|
||||
/// A lot of the flexibility above is just needed for `Miri`, but all "compile-time" machines
|
||||
|
@ -677,4 +685,12 @@ pub macro compile_time_machine(<$tcx: lifetime>) {
|
|||
let (prov, offset) = ptr.into_parts();
|
||||
Some((prov.alloc_id(), offset, prov.immutable()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_global_alloc_salt(
|
||||
_ecx: &InterpCx<$tcx, Self>,
|
||||
_instance: Option<ty::Instance<$tcx>>,
|
||||
) -> usize {
|
||||
CTFE_ALLOC_SALT
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,7 +195,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
|
||||
pub fn fn_ptr(&mut self, fn_val: FnVal<'tcx, M::ExtraFnVal>) -> Pointer<M::Provenance> {
|
||||
let id = match fn_val {
|
||||
FnVal::Instance(instance) => self.tcx.reserve_and_set_fn_alloc(instance),
|
||||
FnVal::Instance(instance) => {
|
||||
let salt = M::get_global_alloc_salt(self, Some(instance));
|
||||
self.tcx.reserve_and_set_fn_alloc(instance, salt)
|
||||
}
|
||||
FnVal::Other(extra) => {
|
||||
// FIXME(RalfJung): Should we have a cache here?
|
||||
let id = self.tcx.reserve_alloc_id();
|
||||
|
|
|
@ -1008,7 +1008,8 @@ where
|
|||
// Use cache for immutable strings.
|
||||
let ptr = if mutbl.is_not() {
|
||||
// Use dedup'd allocation function.
|
||||
let id = tcx.allocate_bytes_dedup(str.as_bytes());
|
||||
let salt = M::get_global_alloc_salt(self, None);
|
||||
let id = tcx.allocate_bytes_dedup(str.as_bytes(), salt);
|
||||
|
||||
// Turn untagged "global" pointers (obtained via `tcx`) into the machine pointer to the allocation.
|
||||
M::adjust_alloc_root_pointer(&self, Pointer::from(id), Some(kind))?
|
||||
|
|
|
@ -28,7 +28,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
|||
ensure_monomorphic_enough(*self.tcx, ty)?;
|
||||
ensure_monomorphic_enough(*self.tcx, poly_trait_ref)?;
|
||||
|
||||
let vtable_symbolic_allocation = self.tcx.reserve_and_set_vtable_alloc(ty, poly_trait_ref);
|
||||
let salt = M::get_global_alloc_salt(self, None);
|
||||
let vtable_symbolic_allocation =
|
||||
self.tcx.reserve_and_set_vtable_alloc(ty, poly_trait_ref, salt);
|
||||
let vtable_ptr = self.global_root_pointer(Pointer::from(vtable_symbolic_allocation))?;
|
||||
Ok(vtable_ptr.into())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue