Skip to content

Commit 413f7c5

Browse files
committed
Auto merge of #2888 - Vanille-N:tb-diags, r=RalfJung
TB diagnostics: avoid printing irrelevant events History contains some events that are relevant to the location but not useful to understand the error. We can make the selection of events more precise, from only "does it affect the location" to also "is it relevant for this kind of error" This is also the occasion to fix #2867 (comment) [Solved] Draft: find a way for blanks in the history to not be confusing, as with the current version the history can show the creation as `Reserved` then show where it transitioned from `Frozen` to `Disabled`, but it will say nothing of the `Reserved -> Frozen` leading up to it.
2 parents dc286ac + 166e355 commit 413f7c5

File tree

8 files changed

+160
-66
lines changed

8 files changed

+160
-66
lines changed

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/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

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;

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;

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)

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)