Skip to content

Commit b3d53ec

Browse files
committed
Auto merge of rust-lang#2613 - RalfJung:scalar-only-field-retagging, r=RalfJung
add scalar-abi-only field retagging option `@saethlin` has requested a Stacked Borrows field retagging mode that matches when we do in terms of emitting `noalias` for LLVM. So here you go! Types with `Scalar` and `ScalarPair` ABI are being recursively retagged, everything else is not. Given that many types can get `ScalarPair` ABI, I don't think this actually helps to make many things sound that are unsound under full field retagging -- but it might still be useful for some experimentation.
2 parents fc3bcac + 44808ed commit b3d53ec

File tree

9 files changed

+128
-10
lines changed

9 files changed

+128
-10
lines changed

Diff for: src/tools/miri/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,11 @@ to Miri failing to detect cases of undefined behavior in a program.
377377
* `-Zmiri-retag-fields` changes Stacked Borrows retagging to recurse into fields.
378378
This means that references in fields of structs/enums/tuples/arrays/... are retagged,
379379
and in particular, they are protected when passed as function arguments.
380+
* `-Zmiri-retag-fields=<all|none|scalar>` controls when Stacked Borrows retagging recurses into
381+
fields. `all` means it always recurses (like `-Zmiri-retag-fields`), `none` means it never
382+
recurses (the default), `scalar` means it only recurses for types where we would also emit
383+
`noalias` annotations in the generated LLVM IR (types passed as indivudal scalars or pairs of
384+
scalars).
380385
* `-Zmiri-tag-gc=<blocks>` configures how often the pointer tag garbage collector runs. The default
381386
is to search for and remove unreachable tags once every `10000` basic blocks. Setting this to
382387
`0` disables the garbage collector, which causes some programs to have explosive memory usage

Diff for: src/tools/miri/src/bin/miri.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use rustc_middle::{
3232
};
3333
use rustc_session::{config::CrateType, search_paths::PathKind, CtfeBacktrace};
3434

35-
use miri::{BacktraceStyle, ProvenanceMode};
35+
use miri::{BacktraceStyle, ProvenanceMode, RetagFields};
3636

3737
struct MiriCompilerCalls {
3838
miri_config: miri::MiriConfig,
@@ -426,7 +426,14 @@ fn main() {
426426
} else if arg == "-Zmiri-mute-stdout-stderr" {
427427
miri_config.mute_stdout_stderr = true;
428428
} else if arg == "-Zmiri-retag-fields" {
429-
miri_config.retag_fields = true;
429+
miri_config.retag_fields = RetagFields::Yes;
430+
} else if let Some(retag_fields) = arg.strip_prefix("-Zmiri-retag-fields=") {
431+
miri_config.retag_fields = match retag_fields {
432+
"all" => RetagFields::Yes,
433+
"none" => RetagFields::No,
434+
"scalar" => RetagFields::OnlyScalar,
435+
_ => show_error!("`-Zmiri-retag-fields` can only be `all`, `none`, or `scalar`"),
436+
};
430437
} else if arg == "-Zmiri-track-raw-pointers" {
431438
eprintln!(
432439
"WARNING: `-Zmiri-track-raw-pointers` has no effect; it is enabled by default"

Diff for: src/tools/miri/src/eval.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ pub struct MiriConfig {
126126
/// Report the current instruction being executed every N basic blocks.
127127
pub report_progress: Option<u32>,
128128
/// Whether Stacked Borrows retagging should recurse into fields of datatypes.
129-
pub retag_fields: bool,
129+
pub retag_fields: RetagFields,
130130
/// The location of a shared object file to load when calling external functions
131131
/// FIXME! consider allowing users to specify paths to multiple SO files, or to a directory
132132
pub external_so_file: Option<PathBuf>,
@@ -163,7 +163,7 @@ impl Default for MiriConfig {
163163
mute_stdout_stderr: false,
164164
preemption_rate: 0.01, // 1%
165165
report_progress: None,
166-
retag_fields: false,
166+
retag_fields: RetagFields::No,
167167
external_so_file: None,
168168
gc_interval: 10_000,
169169
num_cpus: 1,

Diff for: src/tools/miri/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ pub use crate::mono_hash_map::MonoHashMap;
105105
pub use crate::operator::EvalContextExt as _;
106106
pub use crate::range_map::RangeMap;
107107
pub use crate::stacked_borrows::{
108-
CallId, EvalContextExt as _, Item, Permission, SbTag, Stack, Stacks,
108+
CallId, EvalContextExt as _, Item, Permission, RetagFields, SbTag, Stack, Stacks,
109109
};
110110
pub use crate::tag_gc::{EvalContextExt as _, VisitTags};
111111

Diff for: src/tools/miri/src/stacked_borrows/mod.rs

+28-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rustc_middle::ty::{
1717
Ty,
1818
};
1919
use rustc_span::DUMMY_SP;
20+
use rustc_target::abi::Abi;
2021
use rustc_target::abi::Size;
2122
use smallvec::SmallVec;
2223

@@ -114,7 +115,18 @@ pub struct GlobalStateInner {
114115
/// The call ids to trace
115116
tracked_call_ids: FxHashSet<CallId>,
116117
/// Whether to recurse into datatypes when searching for pointers to retag.
117-
retag_fields: bool,
118+
retag_fields: RetagFields,
119+
}
120+
121+
#[derive(Copy, Clone, Debug)]
122+
pub enum RetagFields {
123+
/// Don't retag any fields.
124+
No,
125+
/// Retag all fields.
126+
Yes,
127+
/// Only retag fields of types with Scalar and ScalarPair layout,
128+
/// to match the LLVM `noalias` we generate.
129+
OnlyScalar,
118130
}
119131

120132
impl VisitTags for GlobalStateInner {
@@ -173,7 +185,7 @@ impl GlobalStateInner {
173185
pub fn new(
174186
tracked_pointer_tags: FxHashSet<SbTag>,
175187
tracked_call_ids: FxHashSet<CallId>,
176-
retag_fields: bool,
188+
retag_fields: RetagFields,
177189
) -> Self {
178190
GlobalStateInner {
179191
next_ptr_tag: SbTag(NonZeroU64::new(1).unwrap()),
@@ -999,7 +1011,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
9991011
ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>,
10001012
kind: RetagKind,
10011013
retag_cause: RetagCause,
1002-
retag_fields: bool,
1014+
retag_fields: RetagFields,
10031015
}
10041016
impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
10051017
#[inline(always)] // yes this helps in our benchmarks
@@ -1046,6 +1058,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
10461058
return Ok(());
10471059
}
10481060

1061+
let recurse_for_fields = || {
1062+
match self.retag_fields {
1063+
RetagFields::No => false,
1064+
RetagFields::Yes => true,
1065+
RetagFields::OnlyScalar => {
1066+
// Matching `ArgAbi::new` at the time of writing, only fields of
1067+
// `Scalar` and `ScalarPair` ABI are considered.
1068+
matches!(place.layout.abi, Abi::Scalar(..) | Abi::ScalarPair(..))
1069+
}
1070+
}
1071+
};
1072+
10491073
if let Some((ref_kind, protector)) = qualify(place.layout.ty, self.kind) {
10501074
self.retag_place(place, ref_kind, self.retag_cause, protector)?;
10511075
} else if matches!(place.layout.ty.kind(), ty::RawPtr(..)) {
@@ -1054,7 +1078,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
10541078
// Do *not* recurse into them.
10551079
// (No need to worry about wide references, those always "qualify". And Boxes
10561080
// are handles specially by the visitor anyway.)
1057-
} else if self.retag_fields
1081+
} else if recurse_for_fields()
10581082
|| place.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_box())
10591083
{
10601084
// Recurse deeper. Need to always recurse for `Box` to even hit `visit_box`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//@compile-flags: -Zmiri-retag-fields=scalar
2+
//@error-pattern: which is protected
3+
struct Newtype<'a>(&'a mut i32, i32);
4+
5+
fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
6+
dealloc();
7+
}
8+
9+
// Make sure that we protect references inside structs that are passed as ScalarPair.
10+
fn main() {
11+
let ptr = Box::into_raw(Box::new(0i32));
12+
#[rustfmt::skip] // I like my newlines
13+
unsafe {
14+
dealloc_while_running(
15+
Newtype(&mut *ptr, 0),
16+
|| drop(Box::from_raw(ptr)),
17+
)
18+
};
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
error: Undefined Behavior: not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
2+
--> RUSTLIB/alloc/src/boxed.rs:LL:CC
3+
|
4+
LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc)
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <TAG> because that would remove [Unique for <TAG>] which is protected because it is an argument of call ID
6+
|
7+
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
8+
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
9+
help: <TAG> was created by a SharedReadWrite retag at offsets [0x0..0x4]
10+
--> $DIR/newtype_pair_retagging.rs:LL:CC
11+
|
12+
LL | let ptr = Box::into_raw(Box::new(0i32));
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
help: <TAG> is this argument
15+
--> $DIR/newtype_pair_retagging.rs:LL:CC
16+
|
17+
LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
18+
| ^^
19+
= note: BACKTRACE:
20+
= note: inside `std::boxed::Box::<i32>::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC
21+
= note: inside `std::boxed::Box::<i32>::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC
22+
note: inside closure at $DIR/newtype_pair_retagging.rs:LL:CC
23+
--> $DIR/newtype_pair_retagging.rs:LL:CC
24+
|
25+
LL | || drop(Box::from_raw(ptr)),
26+
| ^^^^^^^^^^^^^^^^^^
27+
note: inside `dealloc_while_running::<[closure@$DIR/newtype_pair_retagging.rs:LL:CC]>` at $DIR/newtype_pair_retagging.rs:LL:CC
28+
--> $DIR/newtype_pair_retagging.rs:LL:CC
29+
|
30+
LL | dealloc();
31+
| ^^^^^^^^^
32+
note: inside `main` at $DIR/newtype_pair_retagging.rs:LL:CC
33+
--> $DIR/newtype_pair_retagging.rs:LL:CC
34+
|
35+
LL | / dealloc_while_running(
36+
LL | | Newtype(&mut *ptr, 0),
37+
LL | | || drop(Box::from_raw(ptr)),
38+
LL | | )
39+
| |_________^
40+
41+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
42+
43+
error: aborting due to previous error
44+

Diff for: src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@compile-flags: -Zmiri-retag-fields
1+
//@compile-flags: -Zmiri-retag-fields=scalar
22
//@error-pattern: which is protected
33
struct Newtype<'a>(&'a mut i32);
44

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//@compile-flags: -Zmiri-retag-fields=scalar
2+
3+
struct Newtype<'a>(&'a mut i32, i32, i32);
4+
5+
fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) {
6+
dealloc();
7+
}
8+
9+
// Make sure that with -Zmiri-retag-fields=scalar, we do *not* retag the fields of `Newtype`.
10+
fn main() {
11+
let ptr = Box::into_raw(Box::new(0i32));
12+
#[rustfmt::skip] // I like my newlines
13+
unsafe {
14+
dealloc_while_running(
15+
Newtype(&mut *ptr, 0, 0),
16+
|| drop(Box::from_raw(ptr)),
17+
)
18+
};
19+
}

0 commit comments

Comments
 (0)