Skip to content

Commit 486317e

Browse files
committed
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 #3737
2 parents 99823c8 + 3215ece commit 486317e

File tree

5 files changed

+63
-7
lines changed

5 files changed

+63
-7
lines changed

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ extern crate either;
5656
extern crate tracing;
5757

5858
// The rustc crates we need
59+
extern crate rustc_attr;
5960
extern crate rustc_apfloat;
6061
extern crate rustc_ast;
6162
extern crate rustc_const_eval;

src/machine.rs

+48-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rand::rngs::StdRng;
1111
use rand::Rng;
1212
use rand::SeedableRng;
1313

14+
use rustc_attr::InlineAttr;
1415
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1516
#[allow(unused)]
1617
use rustc_data_structures::static_assert_size;
@@ -23,6 +24,7 @@ use rustc_middle::{
2324
Instance, Ty, TyCtxt,
2425
},
2526
};
27+
use rustc_session::config::InliningThreshold;
2628
use rustc_span::def_id::{CrateNum, DefId};
2729
use rustc_span::{Span, SpanData, Symbol};
2830
use rustc_target::abi::{Align, Size};
@@ -47,10 +49,10 @@ pub const SIGRTMIN: i32 = 34;
4749
/// `SIGRTMAX` - `SIGRTMIN` >= 8 (which is the value of `_POSIX_RTSIG_MAX`)
4850
pub const SIGRTMAX: i32 = 42;
4951

50-
/// Each const has multiple addresses, but only this many. Since const allocations are never
51-
/// deallocated, choosing a new [`AllocId`] and thus base address for each evaluation would
52-
/// produce unbounded memory usage.
53-
const ADDRS_PER_CONST: usize = 16;
52+
/// Each anonymous global (constant, vtable, function pointer, ...) has multiple addresses, but only
53+
/// this many. Since const allocations are never deallocated, choosing a new [`AllocId`] and thus
54+
/// base address for each evaluation would produce unbounded memory usage.
55+
const ADDRS_PER_ANON_GLOBAL: usize = 32;
5456

5557
/// Extra data stored with each stack frame
5658
pub struct FrameExtra<'tcx> {
@@ -1372,7 +1374,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
13721374
catch_unwind: None,
13731375
timing,
13741376
is_user_relevant: ecx.machine.is_user_relevant(&frame),
1375-
salt: ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_CONST,
1377+
salt: ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL,
13761378
};
13771379

13781380
Ok(frame.with_extra(extra))
@@ -1518,4 +1520,45 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
15181520
Entry::Occupied(oe) => Ok(oe.get().clone()),
15191521
}
15201522
}
1523+
1524+
fn get_global_alloc_salt(
1525+
ecx: &InterpCx<'tcx, Self>,
1526+
instance: Option<ty::Instance<'tcx>>,
1527+
) -> usize {
1528+
let unique = if let Some(instance) = instance {
1529+
// Functions cannot be identified by pointers, as asm-equal functions can get
1530+
// deduplicated by the linker (we set the "unnamed_addr" attribute for LLVM) and
1531+
// functions can be duplicated across crates. We thus generate a new `AllocId` for every
1532+
// mention of a function. This means that `main as fn() == main as fn()` is false, while
1533+
// `let x = main as fn(); x == x` is true. However, as a quality-of-life feature it can
1534+
// be useful to identify certain functions uniquely, e.g. for backtraces. So we identify
1535+
// whether codegen will actually emit duplicate functions. It does that when they have
1536+
// non-lifetime generics, or when they can be inlined. All other functions are given a
1537+
// unique address. This is not a stable guarantee! The `inline` attribute is a hint and
1538+
// cannot be relied upon for anything. But if we don't do this, the
1539+
// `__rust_begin_short_backtrace`/`__rust_end_short_backtrace` logic breaks and panic
1540+
// backtraces look terrible.
1541+
let is_generic = instance
1542+
.args
1543+
.into_iter()
1544+
.any(|kind| !matches!(kind.unpack(), ty::GenericArgKind::Lifetime(_)));
1545+
let can_be_inlined = matches!(
1546+
ecx.tcx.sess.opts.unstable_opts.cross_crate_inline_threshold,
1547+
InliningThreshold::Always
1548+
) || !matches!(
1549+
ecx.tcx.codegen_fn_attrs(instance.def_id()).inline,
1550+
InlineAttr::Never
1551+
);
1552+
!is_generic && !can_be_inlined
1553+
} else {
1554+
// Non-functions are never unique.
1555+
false
1556+
};
1557+
// Always use the same salt if the allocation is unique.
1558+
if unique {
1559+
CTFE_ALLOC_SALT
1560+
} else {
1561+
ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL
1562+
}
1563+
}
15211564
}

tests/pass/dyn-traits.rs

+10
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,17 @@ fn unsized_dyn_autoderef() {
141141
}
142142
*/
143143

144+
fn vtable_ptr_eq() {
145+
use std::{fmt, ptr};
146+
147+
// We don't always get the same vtable when casting this to a wide pointer.
148+
let x = &2;
149+
let x_wide = x as &dyn fmt::Display;
150+
assert!((0..256).any(|_| !ptr::eq(x as &dyn fmt::Display, x_wide)));
151+
}
152+
144153
fn main() {
145154
ref_box_dyn();
146155
box_box_trait();
156+
vtable_ptr_eq();
147157
}

tests/pass/function_pointers.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ fn main() {
8282
assert!(return_fn_ptr(i) == i);
8383
assert!(return_fn_ptr(i) as unsafe fn() -> i32 == i as fn() -> i32 as unsafe fn() -> i32);
8484
// Miri gives different addresses to different reifications of a generic function.
85-
assert!(return_fn_ptr(f) != f);
85+
// at least if we try often enough.
86+
assert!((0..256).any(|_| return_fn_ptr(f) != f));
8687
// However, if we only turn `f` into a function pointer and use that pointer,
8788
// it is equal to itself.
8889
let f2 = f as fn() -> i32;

tests/pass/rc.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ fn rc_fat_ptr_eq() {
7575
let p = Rc::new(1) as Rc<dyn Debug>;
7676
let a: *const dyn Debug = &*p;
7777
let r = Rc::into_raw(p);
78-
assert!(a == r);
78+
// Only compare the pointer parts, as the vtable might differ.
79+
assert!(a as *const () == r as *const ());
7980
drop(unsafe { Rc::from_raw(r) });
8081
}
8182

0 commit comments

Comments
 (0)