Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit cb75f21

Browse files
Vanille-NRalfJung
andcommitted
more precise filtering of events
impl of is_relevant on transitions makes for much less noise in error messages Co-authored-by: Ralf Jung <[email protected]>
1 parent 7a41eac commit cb75f21

File tree

8 files changed

+160
-66
lines changed

8 files changed

+160
-66
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ impl HistoryData {
9292
{
9393
// NOTE: `transition_range` is explicitly absent from the error message, it has no significance
9494
// to the user. The meaningful one is `access_range`.
95-
self.events.push((Some(span.data()), format!("{this} then transitioned {transition} due to a {rel} {access_kind} at offsets {access_range:?}", rel = if is_foreign { "foreign" } else { "child" })));
95+
self.events.push((Some(span.data()), format!("{this} later transitioned to {endpoint} due to a {rel} {access_kind} at offsets {access_range:?}", endpoint = transition.endpoint(), rel = if is_foreign { "foreign" } else { "child" })));
9696
self.events.push((None, format!("this corresponds to {}", transition.summary())));
9797
}
9898
}
@@ -212,12 +212,13 @@ impl History {
212212

213213
/// Reconstruct the history relevant to `error_offset` by filtering
214214
/// only events whose range contains the offset we are interested in.
215-
fn extract_relevant(&self, error_offset: u64) -> Self {
215+
fn extract_relevant(&self, error_offset: u64, error_kind: TransitionError) -> Self {
216216
History {
217217
events: self
218218
.events
219219
.iter()
220220
.filter(|e| e.transition_range.contains(&error_offset))
221+
.filter(|e| e.transition.is_relevant(error_kind))
221222
.cloned()
222223
.collect::<Vec<_>>(),
223224
created: self.created,
@@ -303,7 +304,7 @@ impl TbError<'_> {
303304
history.extend(self.accessed_info.history.forget(), "accessed", false);
304305
}
305306
history.extend(
306-
self.conflicting_info.history.extract_relevant(self.error_offset),
307+
self.conflicting_info.history.extract_relevant(self.error_offset, self.error_kind),
307308
conflicting_tag_name,
308309
true,
309310
);

src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs

Lines changed: 147 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::cmp::{Ordering, PartialOrd};
22
use std::fmt;
33

4+
use crate::borrow_tracker::tree_borrows::diagnostics::TransitionError;
45
use crate::borrow_tracker::tree_borrows::tree::AccessRelatedness;
56
use crate::borrow_tracker::AccessKind;
67

@@ -115,26 +116,31 @@ mod transition {
115116
/// Public interface to the state machine that controls read-write permissions.
116117
/// This is the "private `enum`" pattern.
117118
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118-
pub struct Permission(PermissionPriv);
119+
pub struct Permission {
120+
inner: PermissionPriv,
121+
}
119122

120123
/// Transition from one permission to the next.
121124
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122-
pub struct PermTransition(PermissionPriv, PermissionPriv);
125+
pub struct PermTransition {
126+
from: PermissionPriv,
127+
to: PermissionPriv,
128+
}
123129

124130
impl Permission {
125131
/// Default initial permission of the root of a new tree.
126132
pub fn new_root() -> Self {
127-
Self(Active)
133+
Self { inner: Active }
128134
}
129135

130136
/// Default initial permission of a reborrowed mutable reference.
131137
pub fn new_unique_2phase(ty_is_freeze: bool) -> Self {
132-
Self(Reserved { ty_is_freeze })
138+
Self { inner: Reserved { ty_is_freeze } }
133139
}
134140

135141
/// Default initial permission of a reborrowed shared reference
136142
pub fn new_frozen() -> Self {
137-
Self(Frozen)
143+
Self { inner: Frozen }
138144
}
139145

140146
/// Apply the transition to the inner PermissionPriv.
@@ -144,9 +150,9 @@ impl Permission {
144150
old_perm: Self,
145151
protected: bool,
146152
) -> Option<PermTransition> {
147-
let old_state = old_perm.0;
153+
let old_state = old_perm.inner;
148154
transition::perform_access(kind, rel_pos, old_state, protected)
149-
.map(|new_state| PermTransition(old_state, new_state))
155+
.map(|new_state| PermTransition { from: old_state, to: new_state })
150156
}
151157
}
152158

@@ -155,26 +161,27 @@ impl PermTransition {
155161
/// should be possible, but the same is not guaranteed by construction of
156162
/// transitions inferred by diagnostics. This checks that a transition
157163
/// reconstructed by diagnostics is indeed one that could happen.
158-
fn is_possible(old: PermissionPriv, new: PermissionPriv) -> bool {
159-
old <= new
164+
fn is_possible(self) -> bool {
165+
self.from <= self.to
160166
}
161167

162-
pub fn from(old: Permission, new: Permission) -> Option<Self> {
163-
Self::is_possible(old.0, new.0).then_some(Self(old.0, new.0))
168+
pub fn from(from: Permission, to: Permission) -> Option<Self> {
169+
let t = Self { from: from.inner, to: to.inner };
170+
t.is_possible().then_some(t)
164171
}
165172

166173
pub fn is_noop(self) -> bool {
167-
self.0 == self.1
174+
self.from == self.to
168175
}
169176

170177
/// Extract result of a transition (checks that the starting point matches).
171178
pub fn applied(self, starting_point: Permission) -> Option<Permission> {
172-
(starting_point.0 == self.0).then_some(Permission(self.1))
179+
(starting_point.inner == self.from).then_some(Permission { inner: self.to })
173180
}
174181

175182
/// Extract starting point of a transition
176183
pub fn started(self) -> Permission {
177-
Permission(self.0)
184+
Permission { inner: self.from }
178185
}
179186

180187
/// Determines whether a transition that occured is compatible with the presence
@@ -190,10 +197,9 @@ impl PermTransition {
190197
/// };
191198
/// ```
192199
pub fn is_allowed_by_protector(&self) -> bool {
193-
let &Self(old, new) = self;
194-
assert!(Self::is_possible(old, new));
195-
match (old, new) {
196-
_ if old == new => true,
200+
assert!(self.is_possible());
201+
match (self.from, self.to) {
202+
_ if self.from == self.to => true,
197203
// It is always a protector violation to not be readable anymore
198204
(_, Disabled) => false,
199205
// In the case of a `Reserved` under a protector, both transitions
@@ -204,16 +210,9 @@ impl PermTransition {
204210
(Reserved { .. }, Active) | (Reserved { .. }, Frozen) => true,
205211
// This pointer should have stayed writeable for the whole function
206212
(Active, Frozen) => false,
207-
_ => unreachable!("Transition from {old:?} to {new:?} should never be possible"),
213+
_ => unreachable!("Transition {} should never be possible", self),
208214
}
209215
}
210-
211-
/// Composition function: get the transition that can be added after `app` to
212-
/// produce `self`.
213-
pub fn apply_start(self, app: Self) -> Option<Self> {
214-
let new_start = app.applied(Permission(self.0))?;
215-
Self::from(new_start, Permission(self.1))
216-
}
217216
}
218217

219218
pub mod diagnostics {
@@ -224,24 +223,24 @@ pub mod diagnostics {
224223
f,
225224
"{}",
226225
match self {
227-
PermissionPriv::Reserved { .. } => "Reserved",
228-
PermissionPriv::Active => "Active",
229-
PermissionPriv::Frozen => "Frozen",
230-
PermissionPriv::Disabled => "Disabled",
226+
Reserved { .. } => "Reserved",
227+
Active => "Active",
228+
Frozen => "Frozen",
229+
Disabled => "Disabled",
231230
}
232231
)
233232
}
234233
}
235234

236235
impl fmt::Display for PermTransition {
237236
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238-
write!(f, "from {} to {}", self.0, self.1)
237+
write!(f, "from {} to {}", self.from, self.to)
239238
}
240239
}
241240

242241
impl fmt::Display for Permission {
243242
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244-
write!(f, "{}", self.0)
243+
write!(f, "{}", self.inner)
245244
}
246245
}
247246

@@ -251,7 +250,7 @@ pub mod diagnostics {
251250
// Make sure there are all of the same length as each other
252251
// and also as `diagnostics::DisplayFmtPermission.uninit` otherwise
253252
// alignment will be incorrect.
254-
match self.0 {
253+
match self.inner {
255254
Reserved { ty_is_freeze: true } => "Res",
256255
Reserved { ty_is_freeze: false } => "Re*",
257256
Active => "Act",
@@ -269,16 +268,128 @@ pub mod diagnostics {
269268
/// to have write permissions, because that's what the diagnostics care about
270269
/// (otherwise `Reserved -> Frozen` would be considered a noop).
271270
pub fn summary(&self) -> &'static str {
272-
assert!(Self::is_possible(self.0, self.1));
273-
match (self.0, self.1) {
274-
(_, Active) => "an activation",
271+
assert!(self.is_possible());
272+
match (self.from, self.to) {
273+
(_, Active) => "the first write to a 2-phase borrowed mutable reference",
275274
(_, Frozen) => "a loss of write permissions",
276275
(Frozen, Disabled) => "a loss of read permissions",
277276
(_, Disabled) => "a loss of read and write permissions",
278277
(old, new) =>
279278
unreachable!("Transition from {old:?} to {new:?} should never be possible"),
280279
}
281280
}
281+
282+
/// Determines whether `self` is a relevant transition for the error `err`.
283+
/// `self` will be a transition that happened to a tag some time before
284+
/// that tag caused the error.
285+
///
286+
/// Irrelevant events:
287+
/// - modifications of write permissions when the error is related to read permissions
288+
/// (on failed reads and protected `Frozen -> Disabled`, ignore `Reserved -> Active`,
289+
/// `Reserved -> Frozen`, and `Active -> Frozen`)
290+
/// - all transitions for attempts to deallocate strongly protected tags
291+
///
292+
/// # Panics
293+
///
294+
/// This function assumes that its arguments apply to the same location
295+
/// and that they were obtained during a normal execution. It will panic otherwise.
296+
/// - `err` cannot be a `ProtectedTransition(_)` of a noop transition, as those
297+
/// never trigger protectors;
298+
/// - all transitions involved in `self` and `err` should be increasing
299+
/// (Reserved < Active < Frozen < Disabled);
300+
/// - between `self` and `err` the permission should also be increasing,
301+
/// so all permissions inside `err` should be greater than `self.1`;
302+
/// - `Active` and `Reserved` cannot cause an error due to insufficient permissions,
303+
/// so `err` cannot be a `ChildAccessForbidden(_)` of either of them;
304+
pub(in super::super) fn is_relevant(&self, err: TransitionError) -> bool {
305+
// NOTE: `super::super` is the visibility of `TransitionError`
306+
assert!(self.is_possible());
307+
if self.is_noop() {
308+
return false;
309+
}
310+
match err {
311+
TransitionError::ChildAccessForbidden(insufficient) => {
312+
// Show where the permission was gained then lost,
313+
// but ignore unrelated permissions.
314+
// This eliminates transitions like `Active -> Frozen`
315+
// when the error is a failed `Read`.
316+
match (self.to, insufficient.inner) {
317+
(Frozen, Frozen) => true,
318+
(Active, Frozen) => true,
319+
(Disabled, Disabled) => true,
320+
// A pointer being `Disabled` is a strictly stronger source of
321+
// errors than it being `Frozen`. If we try to access a `Disabled`,
322+
// then where it became `Frozen` (or `Active`) is the least of our concerns for now.
323+
(Active | Frozen, Disabled) => false,
324+
325+
// `Active` and `Reserved` have all permissions, so a
326+
// `ChildAccessForbidden(Reserved | Active)` can never exist.
327+
(_, Active) | (_, Reserved { .. }) =>
328+
unreachable!("this permission cannot cause an error"),
329+
// No transition has `Reserved` as its `.to` unless it's a noop.
330+
(Reserved { .. }, _) => unreachable!("self is a noop transition"),
331+
// All transitions produced in normal executions (using `apply_access`)
332+
// change permissions in the order `Reserved -> Active -> Frozen -> Disabled`.
333+
// We assume that the error was triggered on the same location that
334+
// the transition `self` applies to, so permissions found must be increasing
335+
// in the order `self.from < self.to <= insufficient.inner`
336+
(Disabled, Frozen) =>
337+
unreachable!("permissions between self and err must be increasing"),
338+
}
339+
}
340+
TransitionError::ProtectedTransition(forbidden) => {
341+
assert!(!forbidden.is_noop());
342+
// Show how we got to the starting point of the forbidden transition,
343+
// but ignore what came before.
344+
// This eliminates transitions like `Reserved -> Active`
345+
// when the error is a `Frozen -> Disabled`.
346+
match (self.to, forbidden.from, forbidden.to) {
347+
// We absolutely want to know where it was activated.
348+
(Active, Active, Frozen | Disabled) => true,
349+
// And knowing where it became Frozen is also important.
350+
(Frozen, Frozen, Disabled) => true,
351+
// If the error is a transition `Frozen -> Disabled`, then we don't really
352+
// care whether before that was `Reserved -> Active -> Frozen` or
353+
// `Reserved -> Frozen` or even `Frozen` directly.
354+
// The error will only show either
355+
// - created as Frozen, then Frozen -> Disabled is forbidden
356+
// - created as Reserved, later became Frozen, then Frozen -> Disabled is forbidden
357+
// In both cases the `Reserved -> Active` part is inexistant or irrelevant.
358+
(Active, Frozen, Disabled) => false,
359+
360+
// `Reserved -> Frozen` does not trigger protectors.
361+
(_, Reserved { .. }, Frozen) =>
362+
unreachable!("this transition cannot cause an error"),
363+
// No transition has `Reserved` as its `.to` unless it's a noop.
364+
(Reserved { .. }, _, _) => unreachable!("self is a noop transition"),
365+
(_, Disabled, Disabled) | (_, Frozen, Frozen) | (_, Active, Active) =>
366+
unreachable!("err contains a noop transition"),
367+
368+
// Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`,
369+
// so permissions found must be increasing in the order
370+
// `self.from < self.to <= forbidden.from < forbidden.to`.
371+
(Disabled, Reserved { .. } | Active | Frozen, _)
372+
| (Frozen, Reserved { .. } | Active, _)
373+
| (Active, Reserved { .. }, _) =>
374+
unreachable!("permissions between self and err must be increasing"),
375+
(_, Disabled, Reserved { .. } | Active | Frozen)
376+
| (_, Frozen, Reserved { .. } | Active)
377+
| (_, Active, Reserved { .. }) =>
378+
unreachable!("permissions within err must be increasing"),
379+
}
380+
}
381+
// We don't care because protectors evolve independently from
382+
// permissions.
383+
TransitionError::ProtectedDealloc => false,
384+
}
385+
}
386+
387+
/// Endpoint of a transition.
388+
/// Meant only for diagnostics, use `applied` in non-diagnostics
389+
/// code, which also checks that the starting point matches the current state.
390+
pub fn endpoint(&self) -> Permission {
391+
Permission { inner: self.to }
392+
}
282393
}
283394
}
284395

src/tools/miri/tests/fail/tree-borrows/alternate-read-write.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ help: the conflicting tag <TAG> was created here, in the initial state Reserved
1717
|
1818
LL | let y = unsafe { &mut *(x as *mut u8) };
1919
| ^^^^^^^^^^^^^^^^^^^^
20-
help: the conflicting tag <TAG> then transitioned from Reserved to Active due to a child write access at offsets [0x0..0x1]
20+
help: the conflicting tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x1]
2121
--> $DIR/alternate-read-write.rs:LL:CC
2222
|
2323
LL | *y += 1; // Success
2424
| ^^^^^^^
25-
= help: this corresponds to an activation
26-
help: the conflicting tag <TAG> then transitioned from Active to Frozen due to a foreign read access at offsets [0x0..0x1]
25+
= help: this corresponds to the first write to a 2-phase borrowed mutable reference
26+
help: the conflicting tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1]
2727
--> $DIR/alternate-read-write.rs:LL:CC
2828
|
2929
LL | let _val = *x;

src/tools/miri/tests/fail/tree-borrows/error-range.stderr

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,7 @@ help: the conflicting tag <TAG> was created here, in the initial state Reserved
1717
|
1818
LL | let rmut = &mut *addr_of_mut!(data[0..6]);
1919
| ^^^^
20-
help: the conflicting tag <TAG> then transitioned from Reserved to Active due to a child write access at offsets [0x5..0x6]
21-
--> $DIR/error-range.rs:LL:CC
22-
|
23-
LL | rmut[5] += 1;
24-
| ^^^^^^^^^^^^
25-
= help: this corresponds to an activation
26-
help: the conflicting tag <TAG> then transitioned from Active to Frozen due to a foreign read access at offsets [0x5..0x6]
27-
--> $DIR/error-range.rs:LL:CC
28-
|
29-
LL | let _v = data[5];
30-
| ^^^^^^^
31-
= help: this corresponds to a loss of write permissions
32-
help: the conflicting tag <TAG> then transitioned from Frozen to Disabled due to a foreign write access at offsets [0x5..0x6]
20+
help: the conflicting tag <TAG> later transitioned to Disabled due to a foreign write access at offsets [0x5..0x6]
3321
--> $DIR/error-range.rs:LL:CC
3422
|
3523
LL | data[5] = 1;

src/tools/miri/tests/fail/tree-borrows/fragile-data-race.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ help: the conflicting tag <TAG> was created here, in the initial state Reserved
1717
|
1818
LL | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
1919
| ^
20-
help: the conflicting tag <TAG> then transitioned from Reserved to Frozen due to a foreign read access at offsets [0x0..0x1]
20+
help: the conflicting tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1]
2121
--> RUSTLIB/core/src/ptr/mod.rs:LL:CC
2222
|
2323
LL | crate::intrinsics::read_via_copy(src)

src/tools/miri/tests/fail/tree-borrows/read-to-local.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ help: the accessed tag <TAG> was created here, in the initial state Reserved
1111
|
1212
LL | let mref = &mut root;
1313
| ^^^^^^^^^
14-
help: the accessed tag <TAG> then transitioned from Reserved to Active due to a child write access at offsets [0x0..0x1]
14+
help: the accessed tag <TAG> later transitioned to Active due to a child write access at offsets [0x0..0x1]
1515
--> $DIR/read-to-local.rs:LL:CC
1616
|
1717
LL | *ptr = 0; // Write
1818
| ^^^^^^^^
19-
= help: this corresponds to an activation
20-
help: the accessed tag <TAG> then transitioned from Active to Frozen due to a foreign read access at offsets [0x0..0x1]
19+
= help: this corresponds to the first write to a 2-phase borrowed mutable reference
20+
help: the accessed tag <TAG> later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1]
2121
--> $DIR/read-to-local.rs:LL:CC
2222
|
2323
LL | assert_eq!(root, 0); // Parent Read

0 commit comments

Comments
 (0)