Skip to content

Commit c0de313

Browse files
committed
Auto merge of rust-lang#2985 - RalfJung:retag-fields, r=saethlin
make full field retagging the default The 'scalar' field retagging mode is clearly a hack -- it mirrors details of the codegen backend and how various structs are represented in LLVM. This means whether code has UB or not depends on surprising aspects, such as whether a struct has 2 or 3 (non-zero-sized) fields. Now that both hashbrown and scopeguard have released fixes to be compatible with field retagging, I think it is time to enable full field retagging by default. `@saethlin` do you have an idea of how much fallout enabling full field retagging by default will cause? Do you have objections to enabling it by default? Fixes rust-lang/miri#2528
2 parents e643e0d + 3cdd292 commit c0de313

18 files changed

+140
-89
lines changed

src/tools/miri/README.md

+5-9
Original file line numberDiff line numberDiff line change
@@ -407,15 +407,11 @@ to Miri failing to detect cases of undefined behavior in a program.
407407
application instead of raising an error within the context of Miri (and halting
408408
execution). Note that code might not expect these operations to ever panic, so
409409
this flag can lead to strange (mis)behavior.
410-
* `-Zmiri-retag-fields` changes Stacked Borrows retagging to recurse into *all* fields.
411-
This means that references in fields of structs/enums/tuples/arrays/... are retagged,
412-
and in particular, they are protected when passed as function arguments.
413-
(The default is to recurse only in cases where rustc would actually emit a `noalias` attribute.)
414-
* `-Zmiri-retag-fields=<all|none|scalar>` controls when Stacked Borrows retagging recurses into
415-
fields. `all` means it always recurses (like `-Zmiri-retag-fields`), `none` means it never
416-
recurses, `scalar` (the default) means it only recurses for types where we would also emit
417-
`noalias` annotations in the generated LLVM IR (types passed as individual scalars or pairs of
418-
scalars). Setting this to `none` is **unsound**.
410+
* `-Zmiri-retag-fields[=<all|none|scalar>]` controls when Stacked Borrows retagging recurses into
411+
fields. `all` means it always recurses (the default, and equivalent to `-Zmiri-retag-fields`
412+
without an explicit value), `none` means it never recurses, `scalar` means it only recurses for
413+
types where we would also emit `noalias` annotations in the generated LLVM IR (types passed as
414+
individual scalars or pairs of scalars). Setting this to `none` is **unsound**.
419415
* `-Zmiri-tag-gc=<blocks>` configures how often the pointer tag garbage collector runs. The default
420416
is to search for and remove unreachable tags once every `10000` basic blocks. Setting this to
421417
`0` disables the garbage collector, which causes some programs to have explosive memory usage

src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs

+45-20
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@ use rustc_span::{Span, SpanData};
66
use rustc_target::abi::Size;
77

88
use crate::borrow_tracker::{
9-
stacked_borrows::{err_sb_ub, Permission},
10-
AccessKind, GlobalStateInner, ProtectorKind,
9+
stacked_borrows::Permission, AccessKind, GlobalStateInner, ProtectorKind,
1110
};
1211
use crate::*;
1312

13+
/// Error reporting
14+
fn err_sb_ub<'tcx>(
15+
msg: String,
16+
help: Vec<String>,
17+
history: Option<TagHistory>,
18+
) -> InterpError<'tcx> {
19+
err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history })
20+
}
21+
1422
#[derive(Clone, Debug)]
1523
pub struct AllocHistory {
1624
id: AllocId,
@@ -61,12 +69,15 @@ struct Invalidation {
6169
#[derive(Clone, Debug)]
6270
enum InvalidationCause {
6371
Access(AccessKind),
64-
Retag(Permission, RetagCause),
72+
Retag(Permission, RetagInfo),
6573
}
6674

6775
impl Invalidation {
6876
fn generate_diagnostic(&self) -> (String, SpanData) {
69-
let message = if let InvalidationCause::Retag(_, RetagCause::FnEntry) = self.cause {
77+
let message = if matches!(
78+
self.cause,
79+
InvalidationCause::Retag(_, RetagInfo { cause: RetagCause::FnEntry, .. })
80+
) {
7081
// For a FnEntry retag, our Span points at the caller.
7182
// See `DiagnosticCx::log_invalidation`.
7283
format!(
@@ -87,8 +98,8 @@ impl fmt::Display for InvalidationCause {
8798
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8899
match self {
89100
InvalidationCause::Access(kind) => write!(f, "{kind}"),
90-
InvalidationCause::Retag(perm, kind) =>
91-
write!(f, "{perm:?} {retag}", retag = kind.summary()),
101+
InvalidationCause::Retag(perm, info) =>
102+
write!(f, "{perm:?} {retag}", retag = info.summary()),
92103
}
93104
}
94105
}
@@ -129,13 +140,13 @@ impl<'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
129140

130141
pub fn retag(
131142
machine: &'ecx MiriMachine<'mir, 'tcx>,
132-
cause: RetagCause,
143+
info: RetagInfo,
133144
new_tag: BorTag,
134145
orig_tag: ProvenanceExtra,
135146
range: AllocRange,
136147
) -> Self {
137148
let operation =
138-
Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
149+
Operation::Retag(RetagOp { info, new_tag, orig_tag, range, permission: None });
139150

140151
DiagnosticCxBuilder { machine, operation }
141152
}
@@ -179,13 +190,19 @@ enum Operation {
179190

180191
#[derive(Debug, Clone)]
181192
struct RetagOp {
182-
cause: RetagCause,
193+
info: RetagInfo,
183194
new_tag: BorTag,
184195
orig_tag: ProvenanceExtra,
185196
range: AllocRange,
186197
permission: Option<Permission>,
187198
}
188199

200+
#[derive(Debug, Clone, Copy, PartialEq)]
201+
pub struct RetagInfo {
202+
pub cause: RetagCause,
203+
pub in_field: bool,
204+
}
205+
189206
#[derive(Debug, Clone, Copy, PartialEq)]
190207
pub enum RetagCause {
191208
Normal,
@@ -258,11 +275,11 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
258275
pub fn log_invalidation(&mut self, tag: BorTag) {
259276
let mut span = self.machine.current_span();
260277
let (range, cause) = match &self.operation {
261-
Operation::Retag(RetagOp { cause, range, permission, .. }) => {
262-
if *cause == RetagCause::FnEntry {
278+
Operation::Retag(RetagOp { info, range, permission, .. }) => {
279+
if info.cause == RetagCause::FnEntry {
263280
span = self.machine.caller_span();
264281
}
265-
(*range, InvalidationCause::Retag(permission.unwrap(), *cause))
282+
(*range, InvalidationCause::Retag(permission.unwrap(), *info))
266283
}
267284
Operation::Access(AccessOp { kind, range, .. }) =>
268285
(*range, InvalidationCause::Access(*kind)),
@@ -372,9 +389,13 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
372389
self.history.id,
373390
self.offset.bytes(),
374391
);
392+
let mut helps = vec![operation_summary(&op.info.summary(), self.history.id, op.range)];
393+
if op.info.in_field {
394+
helps.push(format!("errors for retagging in fields are fairly new; please reach out to us (e.g. at <https://rust-lang.zulipchat.com/#narrow/stream/269128-miri>) if you find this error troubling"));
395+
}
375396
err_sb_ub(
376397
format!("{action}{}", error_cause(stack, op.orig_tag)),
377-
Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
398+
helps,
378399
op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
379400
)
380401
}
@@ -397,7 +418,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
397418
);
398419
err_sb_ub(
399420
format!("{action}{}", error_cause(stack, op.tag)),
400-
Some(operation_summary("an access", self.history.id, op.range)),
421+
vec![operation_summary("an access", self.history.id, op.range)],
401422
op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
402423
)
403424
}
@@ -423,7 +444,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
423444
Operation::Dealloc(_) =>
424445
err_sb_ub(
425446
format!("deallocating while item {item:?} is {protected} by call {call_id:?}",),
426-
None,
447+
vec![],
427448
None,
428449
),
429450
Operation::Retag(RetagOp { orig_tag: tag, .. })
@@ -432,7 +453,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
432453
format!(
433454
"not granting access to tag {tag:?} because that would remove {item:?} which is {protected} because it is an argument of call {call_id:?}",
434455
),
435-
None,
456+
vec![],
436457
tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
437458
),
438459
}
@@ -450,7 +471,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
450471
alloc_id = self.history.id,
451472
cause = error_cause(stack, op.tag),
452473
),
453-
None,
474+
vec![],
454475
op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
455476
)
456477
}
@@ -496,14 +517,18 @@ fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
496517
}
497518
}
498519

499-
impl RetagCause {
520+
impl RetagInfo {
500521
fn summary(&self) -> String {
501-
match self {
522+
let mut s = match self.cause {
502523
RetagCause::Normal => "retag",
503524
RetagCause::FnEntry => "function-entry retag",
504525
RetagCause::InPlaceFnPassing => "in-place function argument/return passing protection",
505526
RetagCause::TwoPhase => "two-phase retag",
506527
}
507-
.to_string()
528+
.to_string();
529+
if self.in_field {
530+
s.push_str(" (of a reference/box inside this compound value)");
531+
}
532+
s
508533
}
509534
}

src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs

+30-26
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ pub mod diagnostics;
55
mod item;
66
mod stack;
77

8-
use log::trace;
98
use std::cmp;
109
use std::fmt::Write;
10+
use std::mem;
11+
12+
use log::trace;
1113

1214
use rustc_data_structures::fx::FxHashSet;
1315
use rustc_middle::mir::{Mutability, RetagKind};
@@ -19,12 +21,12 @@ use rustc_middle::ty::{
1921
use rustc_target::abi::{Abi, Size};
2022

2123
use crate::borrow_tracker::{
22-
stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder, TagHistory},
24+
stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder},
2325
AccessKind, GlobalStateInner, ProtectorKind, RetagFields,
2426
};
2527
use crate::*;
2628

27-
use diagnostics::RetagCause;
29+
use diagnostics::{RetagCause, RetagInfo};
2830
pub use item::{Item, Permission};
2931
pub use stack::Stack;
3032

@@ -168,15 +170,6 @@ impl NewPermission {
168170
}
169171
}
170172

171-
/// Error reporting
172-
pub fn err_sb_ub<'tcx>(
173-
msg: String,
174-
help: Option<String>,
175-
history: Option<TagHistory>,
176-
) -> InterpError<'tcx> {
177-
err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history })
178-
}
179-
180173
// # Stacked Borrows Core Begin
181174

182175
/// We need to make at least the following things true:
@@ -623,7 +616,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
623616
size: Size,
624617
new_perm: NewPermission,
625618
new_tag: BorTag,
626-
retag_cause: RetagCause, // What caused this retag, for diagnostics only
619+
retag_info: RetagInfo, // diagnostics info about this retag
627620
) -> InterpResult<'tcx, Option<AllocId>> {
628621
let this = self.eval_context_mut();
629622

@@ -670,7 +663,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
670663
// FIXME: can this be done cleaner?
671664
let dcx = DiagnosticCxBuilder::retag(
672665
&this.machine,
673-
retag_cause,
666+
retag_info,
674667
new_tag,
675668
orig_tag,
676669
alloc_range(base_offset, size),
@@ -761,7 +754,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
761754
let global = machine.borrow_tracker.as_ref().unwrap().borrow();
762755
let dcx = DiagnosticCxBuilder::retag(
763756
machine,
764-
retag_cause,
757+
retag_info,
765758
new_tag,
766759
orig_tag,
767760
alloc_range(base_offset, size),
@@ -804,7 +797,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
804797
let global = this.machine.borrow_tracker.as_ref().unwrap().borrow();
805798
let dcx = DiagnosticCxBuilder::retag(
806799
&this.machine,
807-
retag_cause,
800+
retag_info,
808801
new_tag,
809802
orig_tag,
810803
alloc_range(base_offset, size),
@@ -834,7 +827,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
834827
&mut self,
835828
val: &ImmTy<'tcx, Provenance>,
836829
new_perm: NewPermission,
837-
cause: RetagCause, // What caused this retag, for diagnostics only
830+
info: RetagInfo, // diagnostics info about this retag
838831
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
839832
let this = self.eval_context_mut();
840833
// We want a place for where the ptr *points to*, so we get one.
@@ -852,7 +845,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
852845
let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr();
853846

854847
// Reborrow.
855-
let alloc_id = this.sb_reborrow(&place, size, new_perm, new_tag, cause)?;
848+
let alloc_id = this.sb_reborrow(&place, size, new_perm, new_tag, info)?;
856849

857850
// Adjust pointer.
858851
let new_place = place.map_provenance(|p| {
@@ -886,12 +879,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
886879
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
887880
let this = self.eval_context_mut();
888881
let new_perm = NewPermission::from_ref_ty(val.layout.ty, kind, this);
889-
let retag_cause = match kind {
882+
let cause = match kind {
890883
RetagKind::TwoPhase { .. } => RetagCause::TwoPhase,
891884
RetagKind::FnEntry => unreachable!(),
892885
RetagKind::Raw | RetagKind::Default => RetagCause::Normal,
893886
};
894-
this.sb_retag_reference(val, new_perm, retag_cause)
887+
this.sb_retag_reference(val, new_perm, RetagInfo { cause, in_field: false })
895888
}
896889

897890
fn sb_retag_place_contents(
@@ -906,7 +899,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
906899
RetagKind::FnEntry => RetagCause::FnEntry,
907900
RetagKind::Default => RetagCause::Normal,
908901
};
909-
let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, retag_fields };
902+
let mut visitor =
903+
RetagVisitor { ecx: this, kind, retag_cause, retag_fields, in_field: false };
910904
return visitor.visit_value(place);
911905

912906
// The actual visitor.
@@ -915,17 +909,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
915909
kind: RetagKind,
916910
retag_cause: RetagCause,
917911
retag_fields: RetagFields,
912+
in_field: bool,
918913
}
919914
impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> {
920915
#[inline(always)] // yes this helps in our benchmarks
921916
fn retag_ptr_inplace(
922917
&mut self,
923918
place: &PlaceTy<'tcx, Provenance>,
924919
new_perm: NewPermission,
925-
retag_cause: RetagCause,
926920
) -> InterpResult<'tcx> {
927921
let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?;
928-
let val = self.ecx.sb_retag_reference(&val, new_perm, retag_cause)?;
922+
let val = self.ecx.sb_retag_reference(
923+
&val,
924+
new_perm,
925+
RetagInfo { cause: self.retag_cause, in_field: self.in_field },
926+
)?;
929927
self.ecx.write_immediate(*val, place)?;
930928
Ok(())
931929
}
@@ -943,7 +941,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
943941
fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
944942
// Boxes get a weak protectors, since they may be deallocated.
945943
let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx);
946-
self.retag_ptr_inplace(place, new_perm, self.retag_cause)
944+
self.retag_ptr_inplace(place, new_perm)
947945
}
948946

949947
fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
@@ -960,7 +958,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
960958
ty::Ref(..) => {
961959
let new_perm =
962960
NewPermission::from_ref_ty(place.layout.ty, self.kind, self.ecx);
963-
self.retag_ptr_inplace(place, new_perm, self.retag_cause)?;
961+
self.retag_ptr_inplace(place, new_perm)?;
964962
}
965963
ty::RawPtr(..) => {
966964
// We do *not* want to recurse into raw pointers -- wide raw pointers have
@@ -984,7 +982,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
984982
}
985983
};
986984
if recurse {
985+
let in_field = mem::replace(&mut self.in_field, true); // remember and restore old value
987986
self.walk_value(place)?;
987+
self.in_field = in_field;
988988
}
989989
}
990990
}
@@ -1011,7 +1011,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
10111011
access: Some(AccessKind::Write),
10121012
protector: Some(ProtectorKind::StrongProtector),
10131013
};
1014-
let _new_ptr = this.sb_retag_reference(&ptr, new_perm, RetagCause::InPlaceFnPassing)?;
1014+
let _new_ptr = this.sb_retag_reference(
1015+
&ptr,
1016+
new_perm,
1017+
RetagInfo { cause: RetagCause::InPlaceFnPassing, in_field: false },
1018+
)?;
10151019
// We just throw away `new_ptr`, so nobody can access this memory while it is protected.
10161020

10171021
Ok(())

0 commit comments

Comments
 (0)