Skip to content

Commit 591ecb8

Browse files
committed
Auto merge of rust-lang#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 rust-lang/miri#3737
2 parents e9e27ab + 4763d12 commit 591ecb8

File tree

24 files changed

+320
-194
lines changed

24 files changed

+320
-194
lines changed

Diff for: compiler/rustc_const_eval/src/interpret/cast.rs

+6
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
400400
}
401401
(ty::Dynamic(data_a, _, ty::Dyn), ty::Dynamic(data_b, _, ty::Dyn)) => {
402402
let val = self.read_immediate(src)?;
403+
// MIR building generates odd NOP casts, prevent them from causing unexpected trouble.
404+
// See <https://github.com/rust-lang/rust/issues/128880>.
405+
// FIXME: ideally we wouldn't have to do this.
406+
if data_a == data_b {
407+
return self.write_immediate(*val, dest);
408+
}
403409
// Take apart the old pointer, and find the dynamic type.
404410
let (old_data, old_vptr) = val.to_scalar_pair();
405411
let old_data = old_data.to_pointer(self)?;

Diff for: compiler/rustc_const_eval/src/interpret/machine.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustc_target::spec::abi::Abi as CallAbi;
1919
use super::{
2020
throw_unsup, throw_unsup_format, AllocBytes, AllocId, AllocKind, AllocRange, Allocation,
2121
ConstAllocation, CtfeProvenance, FnArg, Frame, ImmTy, InterpCx, InterpResult, MPlaceTy,
22-
MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance,
22+
MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance, CTFE_ALLOC_SALT,
2323
};
2424

2525
/// Data returned by [`Machine::after_stack_pop`], and consumed by
@@ -575,6 +575,14 @@ pub trait Machine<'tcx>: Sized {
575575
{
576576
eval(ecx, val, span, layout)
577577
}
578+
579+
/// Returns the salt to be used for a deduplicated global alloation.
580+
/// If the allocation is for a function, the instance is provided as well
581+
/// (this lets Miri ensure unique addresses for some functions).
582+
fn get_global_alloc_salt(
583+
ecx: &InterpCx<'tcx, Self>,
584+
instance: Option<ty::Instance<'tcx>>,
585+
) -> usize;
578586
}
579587

580588
/// 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>) {
677685
let (prov, offset) = ptr.into_parts();
678686
Some((prov.alloc_id(), offset, prov.immutable()))
679687
}
688+
689+
#[inline(always)]
690+
fn get_global_alloc_salt(
691+
_ecx: &InterpCx<$tcx, Self>,
692+
_instance: Option<ty::Instance<$tcx>>,
693+
) -> usize {
694+
CTFE_ALLOC_SALT
695+
}
680696
}

Diff for: compiler/rustc_const_eval/src/interpret/memory.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
195195

196196
pub fn fn_ptr(&mut self, fn_val: FnVal<'tcx, M::ExtraFnVal>) -> Pointer<M::Provenance> {
197197
let id = match fn_val {
198-
FnVal::Instance(instance) => self.tcx.reserve_and_set_fn_alloc(instance),
198+
FnVal::Instance(instance) => {
199+
let salt = M::get_global_alloc_salt(self, Some(instance));
200+
self.tcx.reserve_and_set_fn_alloc(instance, salt)
201+
}
199202
FnVal::Other(extra) => {
200203
// FIXME(RalfJung): Should we have a cache here?
201204
let id = self.tcx.reserve_alloc_id();

Diff for: compiler/rustc_const_eval/src/interpret/place.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,8 @@ where
10081008
// Use cache for immutable strings.
10091009
let ptr = if mutbl.is_not() {
10101010
// Use dedup'd allocation function.
1011-
let id = tcx.allocate_bytes_dedup(str.as_bytes());
1011+
let salt = M::get_global_alloc_salt(self, None);
1012+
let id = tcx.allocate_bytes_dedup(str.as_bytes(), salt);
10121013

10131014
// Turn untagged "global" pointers (obtained via `tcx`) into the machine pointer to the allocation.
10141015
M::adjust_alloc_root_pointer(&self, Pointer::from(id), Some(kind))?

Diff for: compiler/rustc_const_eval/src/interpret/traits.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
2828
ensure_monomorphic_enough(*self.tcx, ty)?;
2929
ensure_monomorphic_enough(*self.tcx, poly_trait_ref)?;
3030

31-
let vtable_symbolic_allocation = self.tcx.reserve_and_set_vtable_alloc(ty, poly_trait_ref);
31+
let salt = M::get_global_alloc_salt(self, None);
32+
let vtable_symbolic_allocation =
33+
self.tcx.reserve_and_set_vtable_alloc(ty, poly_trait_ref, salt);
3234
let vtable_ptr = self.global_root_pointer(Pointer::from(vtable_symbolic_allocation))?;
3335
Ok(vtable_ptr.into())
3436
}

Diff for: compiler/rustc_middle/src/mir/interpret/mod.rs

+51-94
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use std::num::NonZero;
1313
use std::{fmt, io};
1414

1515
use rustc_ast::LitKind;
16-
use rustc_attr::InlineAttr;
1716
use rustc_data_structures::fx::FxHashMap;
1817
use rustc_data_structures::sync::Lock;
1918
use rustc_errors::ErrorGuaranteed;
@@ -46,7 +45,7 @@ pub use self::pointer::{CtfeProvenance, Pointer, PointerArithmetic, Provenance};
4645
pub use self::value::Scalar;
4746
use crate::mir;
4847
use crate::ty::codec::{TyDecoder, TyEncoder};
49-
use crate::ty::{self, GenericArgKind, Instance, Ty, TyCtxt};
48+
use crate::ty::{self, Instance, Ty, TyCtxt};
5049

5150
/// Uniquely identifies one of the following:
5251
/// - A constant
@@ -126,11 +125,10 @@ pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<I = TyCtxt<'tcx>>>(
126125
AllocDiscriminant::Alloc.encode(encoder);
127126
alloc.encode(encoder);
128127
}
129-
GlobalAlloc::Function { instance, unique } => {
128+
GlobalAlloc::Function { instance } => {
130129
trace!("encoding {:?} with {:#?}", alloc_id, instance);
131130
AllocDiscriminant::Fn.encode(encoder);
132131
instance.encode(encoder);
133-
unique.encode(encoder);
134132
}
135133
GlobalAlloc::VTable(ty, poly_trait_ref) => {
136134
trace!("encoding {:?} with {ty:#?}, {poly_trait_ref:#?}", alloc_id);
@@ -219,38 +217,32 @@ impl<'s> AllocDecodingSession<'s> {
219217
}
220218

221219
// Now decode the actual data.
222-
let alloc_id = decoder.with_position(pos, |decoder| {
223-
match alloc_kind {
224-
AllocDiscriminant::Alloc => {
225-
trace!("creating memory alloc ID");
226-
let alloc = <ConstAllocation<'tcx> as Decodable<_>>::decode(decoder);
227-
trace!("decoded alloc {:?}", alloc);
228-
decoder.interner().reserve_and_set_memory_alloc(alloc)
229-
}
230-
AllocDiscriminant::Fn => {
231-
trace!("creating fn alloc ID");
232-
let instance = ty::Instance::decode(decoder);
233-
trace!("decoded fn alloc instance: {:?}", instance);
234-
let unique = bool::decode(decoder);
235-
// Here we cannot call `reserve_and_set_fn_alloc` as that would use a query, which
236-
// is not possible in this context. That's why the allocation stores
237-
// whether it is unique or not.
238-
decoder.interner().reserve_and_set_fn_alloc_internal(instance, unique)
239-
}
240-
AllocDiscriminant::VTable => {
241-
trace!("creating vtable alloc ID");
242-
let ty = <Ty<'_> as Decodable<D>>::decode(decoder);
243-
let poly_trait_ref =
244-
<Option<ty::PolyExistentialTraitRef<'_>> as Decodable<D>>::decode(decoder);
245-
trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}");
246-
decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref)
247-
}
248-
AllocDiscriminant::Static => {
249-
trace!("creating extern static alloc ID");
250-
let did = <DefId as Decodable<D>>::decode(decoder);
251-
trace!("decoded static def-ID: {:?}", did);
252-
decoder.interner().reserve_and_set_static_alloc(did)
253-
}
220+
let alloc_id = decoder.with_position(pos, |decoder| match alloc_kind {
221+
AllocDiscriminant::Alloc => {
222+
trace!("creating memory alloc ID");
223+
let alloc = <ConstAllocation<'tcx> as Decodable<_>>::decode(decoder);
224+
trace!("decoded alloc {:?}", alloc);
225+
decoder.interner().reserve_and_set_memory_alloc(alloc)
226+
}
227+
AllocDiscriminant::Fn => {
228+
trace!("creating fn alloc ID");
229+
let instance = ty::Instance::decode(decoder);
230+
trace!("decoded fn alloc instance: {:?}", instance);
231+
decoder.interner().reserve_and_set_fn_alloc(instance, CTFE_ALLOC_SALT)
232+
}
233+
AllocDiscriminant::VTable => {
234+
trace!("creating vtable alloc ID");
235+
let ty = <Ty<'_> as Decodable<D>>::decode(decoder);
236+
let poly_trait_ref =
237+
<Option<ty::PolyExistentialTraitRef<'_>> as Decodable<D>>::decode(decoder);
238+
trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}");
239+
decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref, CTFE_ALLOC_SALT)
240+
}
241+
AllocDiscriminant::Static => {
242+
trace!("creating extern static alloc ID");
243+
let did = <DefId as Decodable<D>>::decode(decoder);
244+
trace!("decoded static def-ID: {:?}", did);
245+
decoder.interner().reserve_and_set_static_alloc(did)
254246
}
255247
});
256248

@@ -265,12 +257,7 @@ impl<'s> AllocDecodingSession<'s> {
265257
#[derive(Debug, Clone, Eq, PartialEq, Hash, TyDecodable, TyEncodable, HashStable)]
266258
pub enum GlobalAlloc<'tcx> {
267259
/// The alloc ID is used as a function pointer.
268-
Function {
269-
instance: Instance<'tcx>,
270-
/// Stores whether this instance is unique, i.e. all pointers to this function use the same
271-
/// alloc ID.
272-
unique: bool,
273-
},
260+
Function { instance: Instance<'tcx> },
274261
/// This alloc ID points to a symbolic (not-reified) vtable.
275262
VTable(Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>),
276263
/// The alloc ID points to a "lazy" static variable that did not get computed (yet).
@@ -323,14 +310,17 @@ impl<'tcx> GlobalAlloc<'tcx> {
323310
}
324311
}
325312

313+
pub const CTFE_ALLOC_SALT: usize = 0;
314+
326315
pub(crate) struct AllocMap<'tcx> {
327316
/// Maps `AllocId`s to their corresponding allocations.
328317
alloc_map: FxHashMap<AllocId, GlobalAlloc<'tcx>>,
329318

330-
/// Used to ensure that statics and functions only get one associated `AllocId`.
331-
//
332-
// FIXME: Should we just have two separate dedup maps for statics and functions each?
333-
dedup: FxHashMap<GlobalAlloc<'tcx>, AllocId>,
319+
/// Used to deduplicate global allocations: functions, vtables, string literals, ...
320+
///
321+
/// The `usize` is a "salt" used by Miri to make deduplication imperfect, thus better emulating
322+
/// the actual guarantees.
323+
dedup: FxHashMap<(GlobalAlloc<'tcx>, usize), AllocId>,
334324

335325
/// The `AllocId` to assign to the next requested ID.
336326
/// Always incremented; never gets smaller.
@@ -368,83 +358,50 @@ impl<'tcx> TyCtxt<'tcx> {
368358

369359
/// Reserves a new ID *if* this allocation has not been dedup-reserved before.
370360
/// Should not be used for mutable memory.
371-
fn reserve_and_set_dedup(self, alloc: GlobalAlloc<'tcx>) -> AllocId {
361+
fn reserve_and_set_dedup(self, alloc: GlobalAlloc<'tcx>, salt: usize) -> AllocId {
372362
let mut alloc_map = self.alloc_map.lock();
373363
if let GlobalAlloc::Memory(mem) = alloc {
374364
if mem.inner().mutability.is_mut() {
375365
bug!("trying to dedup-reserve mutable memory");
376366
}
377367
}
378-
if let Some(&alloc_id) = alloc_map.dedup.get(&alloc) {
368+
let alloc_salt = (alloc, salt);
369+
if let Some(&alloc_id) = alloc_map.dedup.get(&alloc_salt) {
379370
return alloc_id;
380371
}
381372
let id = alloc_map.reserve();
382-
debug!("creating alloc {alloc:?} with id {id:?}");
383-
alloc_map.alloc_map.insert(id, alloc.clone());
384-
alloc_map.dedup.insert(alloc, id);
373+
debug!("creating alloc {:?} with id {id:?}", alloc_salt.0);
374+
alloc_map.alloc_map.insert(id, alloc_salt.0.clone());
375+
alloc_map.dedup.insert(alloc_salt, id);
385376
id
386377
}
387378

388379
/// Generates an `AllocId` for a memory allocation. If the exact same memory has been
389380
/// allocated before, this will return the same `AllocId`.
390-
pub fn reserve_and_set_memory_dedup(self, mem: ConstAllocation<'tcx>) -> AllocId {
391-
self.reserve_and_set_dedup(GlobalAlloc::Memory(mem))
381+
pub fn reserve_and_set_memory_dedup(self, mem: ConstAllocation<'tcx>, salt: usize) -> AllocId {
382+
self.reserve_and_set_dedup(GlobalAlloc::Memory(mem), salt)
392383
}
393384

394385
/// Generates an `AllocId` for a static or return a cached one in case this function has been
395386
/// called on the same static before.
396387
pub fn reserve_and_set_static_alloc(self, static_id: DefId) -> AllocId {
397-
self.reserve_and_set_dedup(GlobalAlloc::Static(static_id))
398-
}
399-
400-
/// Generates an `AllocId` for a function. The caller must already have decided whether this
401-
/// function obtains a unique AllocId or gets de-duplicated via the cache.
402-
fn reserve_and_set_fn_alloc_internal(self, instance: Instance<'tcx>, unique: bool) -> AllocId {
403-
let alloc = GlobalAlloc::Function { instance, unique };
404-
if unique {
405-
// Deduplicate.
406-
self.reserve_and_set_dedup(alloc)
407-
} else {
408-
// Get a fresh ID.
409-
let mut alloc_map = self.alloc_map.lock();
410-
let id = alloc_map.reserve();
411-
alloc_map.alloc_map.insert(id, alloc);
412-
id
413-
}
388+
let salt = 0; // Statics have a guaranteed unique address, no salt added.
389+
self.reserve_and_set_dedup(GlobalAlloc::Static(static_id), salt)
414390
}
415391

416-
/// Generates an `AllocId` for a function. Depending on the function type,
417-
/// this might get deduplicated or assigned a new ID each time.
418-
pub fn reserve_and_set_fn_alloc(self, instance: Instance<'tcx>) -> AllocId {
419-
// Functions cannot be identified by pointers, as asm-equal functions can get deduplicated
420-
// by the linker (we set the "unnamed_addr" attribute for LLVM) and functions can be
421-
// duplicated across crates. We thus generate a new `AllocId` for every mention of a
422-
// function. This means that `main as fn() == main as fn()` is false, while `let x = main as
423-
// fn(); x == x` is true. However, as a quality-of-life feature it can be useful to identify
424-
// certain functions uniquely, e.g. for backtraces. So we identify whether codegen will
425-
// actually emit duplicate functions. It does that when they have non-lifetime generics, or
426-
// when they can be inlined. All other functions are given a unique address.
427-
// This is not a stable guarantee! The `inline` attribute is a hint and cannot be relied
428-
// upon for anything. But if we don't do this, backtraces look terrible.
429-
let is_generic = instance
430-
.args
431-
.into_iter()
432-
.any(|kind| !matches!(kind.unpack(), GenericArgKind::Lifetime(_)));
433-
let can_be_inlined = match self.codegen_fn_attrs(instance.def_id()).inline {
434-
InlineAttr::Never => false,
435-
_ => true,
436-
};
437-
let unique = !is_generic && !can_be_inlined;
438-
self.reserve_and_set_fn_alloc_internal(instance, unique)
392+
/// Generates an `AllocId` for a function. Will get deduplicated.
393+
pub fn reserve_and_set_fn_alloc(self, instance: Instance<'tcx>, salt: usize) -> AllocId {
394+
self.reserve_and_set_dedup(GlobalAlloc::Function { instance }, salt)
439395
}
440396

441397
/// Generates an `AllocId` for a (symbolic, not-reified) vtable. Will get deduplicated.
442398
pub fn reserve_and_set_vtable_alloc(
443399
self,
444400
ty: Ty<'tcx>,
445401
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
402+
salt: usize,
446403
) -> AllocId {
447-
self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, poly_trait_ref))
404+
self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, poly_trait_ref), salt)
448405
}
449406

450407
/// Interns the `Allocation` and return a new `AllocId`, even if there's already an identical

Diff for: compiler/rustc_middle/src/ty/context.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1438,11 +1438,11 @@ impl<'tcx> TyCtxt<'tcx> {
14381438

14391439
/// Allocates a read-only byte or string literal for `mir::interpret`.
14401440
/// Returns the same `AllocId` if called again with the same bytes.
1441-
pub fn allocate_bytes_dedup(self, bytes: &[u8]) -> interpret::AllocId {
1441+
pub fn allocate_bytes_dedup(self, bytes: &[u8], salt: usize) -> interpret::AllocId {
14421442
// Create an allocation that just contains these bytes.
14431443
let alloc = interpret::Allocation::from_bytes_byte_aligned_immutable(bytes);
14441444
let alloc = self.mk_const_alloc(alloc);
1445-
self.reserve_and_set_memory_dedup(alloc)
1445+
self.reserve_and_set_memory_dedup(alloc, salt)
14461446
}
14471447

14481448
/// Returns a range of the start/end indices specified with the

Diff for: compiler/rustc_middle/src/ty/vtable.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::fmt;
33
use rustc_ast::Mutability;
44
use rustc_macros::HashStable;
55

6-
use crate::mir::interpret::{alloc_range, AllocId, Allocation, Pointer, Scalar};
6+
use crate::mir::interpret::{alloc_range, AllocId, Allocation, Pointer, Scalar, CTFE_ALLOC_SALT};
77
use crate::ty::{self, Instance, PolyTraitRef, Ty, TyCtxt};
88

99
#[derive(Clone, Copy, PartialEq, HashStable)]
@@ -73,6 +73,11 @@ pub(crate) fn vtable_min_entries<'tcx>(
7373

7474
/// Retrieves an allocation that represents the contents of a vtable.
7575
/// Since this is a query, allocations are cached and not duplicated.
76+
///
77+
/// This is an "internal" `AllocId` that should never be used as a value in the interpreted program.
78+
/// The interpreter should use `AllocId` that refer to a `GlobalAlloc::VTable` instead.
79+
/// (This is similar to statics, which also have a similar "internal" `AllocId` storing their
80+
/// initial contents.)
7681
pub(super) fn vtable_allocation_provider<'tcx>(
7782
tcx: TyCtxt<'tcx>,
7883
key: (Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>),
@@ -114,7 +119,7 @@ pub(super) fn vtable_allocation_provider<'tcx>(
114119
VtblEntry::MetadataDropInPlace => {
115120
if ty.needs_drop(tcx, ty::ParamEnv::reveal_all()) {
116121
let instance = ty::Instance::resolve_drop_in_place(tcx, ty);
117-
let fn_alloc_id = tcx.reserve_and_set_fn_alloc(instance);
122+
let fn_alloc_id = tcx.reserve_and_set_fn_alloc(instance, CTFE_ALLOC_SALT);
118123
let fn_ptr = Pointer::from(fn_alloc_id);
119124
Scalar::from_pointer(fn_ptr, &tcx)
120125
} else {
@@ -127,7 +132,7 @@ pub(super) fn vtable_allocation_provider<'tcx>(
127132
VtblEntry::Method(instance) => {
128133
// Prepare the fn ptr we write into the vtable.
129134
let instance = instance.polymorphize(tcx);
130-
let fn_alloc_id = tcx.reserve_and_set_fn_alloc(instance);
135+
let fn_alloc_id = tcx.reserve_and_set_fn_alloc(instance, CTFE_ALLOC_SALT);
131136
let fn_ptr = Pointer::from(fn_alloc_id);
132137
Scalar::from_pointer(fn_ptr, &tcx)
133138
}

Diff for: compiler/rustc_mir_build/src/build/expr/as_constant.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
33
use rustc_ast as ast;
44
use rustc_hir::LangItem;
5-
use rustc_middle::mir::interpret::{Allocation, LitToConstError, LitToConstInput, Scalar};
5+
use rustc_middle::mir::interpret::{
6+
Allocation, LitToConstError, LitToConstInput, Scalar, CTFE_ALLOC_SALT,
7+
};
68
use rustc_middle::mir::*;
79
use rustc_middle::thir::*;
810
use rustc_middle::ty::{
@@ -140,7 +142,7 @@ fn lit_to_mir_constant<'tcx>(
140142
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
141143
}
142144
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_array() => {
143-
let id = tcx.allocate_bytes_dedup(data);
145+
let id = tcx.allocate_bytes_dedup(data, CTFE_ALLOC_SALT);
144146
ConstValue::Scalar(Scalar::from_pointer(id.into(), &tcx))
145147
}
146148
(ast::LitKind::CStr(data, _), ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Adt(def, _) if tcx.is_lang_item(def.did(), LangItem::CStr)) =>

0 commit comments

Comments
 (0)