Skip to content

Commit 730cd27

Browse files
committed
Print more in SB error diagnostics
This tries to clarify exactly why an access is not valid by printing what memory range the access was over, which in combination with tag-tracking may help a user figure out the source of the problem.
1 parent a9a0d0e commit 730cd27

File tree

7 files changed

+126
-40
lines changed

7 files changed

+126
-40
lines changed

src/diagnostics.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub enum TerminationInfo {
1717
UnsupportedInIsolation(String),
1818
ExperimentalUb {
1919
msg: String,
20+
help: Option<String>,
2021
url: String,
2122
},
2223
Deadlock,
@@ -133,6 +134,8 @@ pub fn report_error<'tcx, 'mir>(
133134
) -> Option<i64> {
134135
use InterpError::*;
135136

137+
let mut msg = vec![];
138+
136139
let (title, helps) = match &e.kind() {
137140
MachineStop(info) => {
138141
let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
@@ -152,11 +155,13 @@ pub fn report_error<'tcx, 'mir>(
152155
(None, format!("pass the flag `-Zmiri-disable-isolation` to disable isolation;")),
153156
(None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")),
154157
],
155-
ExperimentalUb { url, .. } =>
158+
ExperimentalUb { url, help, .. } => {
159+
msg.extend(help.clone());
156160
vec![
157161
(None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental")),
158-
(None, format!("see {} for further information", url)),
159-
],
162+
(None, format!("see {} for further information", url))
163+
]
164+
}
160165
MultipleSymbolDefinitions { first, first_crate, second, second_crate, .. } =>
161166
vec![
162167
(Some(*first), format!("it's first defined here, in crate `{}`", first_crate)),
@@ -211,11 +216,11 @@ pub fn report_error<'tcx, 'mir>(
211216
let stacktrace = ecx.generate_stacktrace();
212217
let (stacktrace, was_pruned) = prune_stacktrace(ecx, stacktrace);
213218
e.print_backtrace();
214-
let msg = e.to_string();
219+
msg.insert(0, e.to_string());
215220
report_msg(
216221
*ecx.tcx,
217222
DiagLevel::Error,
218-
&if let Some(title) = title { format!("{}: {}", title, msg) } else { msg.clone() },
223+
&if let Some(title) = title { format!("{}: {}", title, msg[0]) } else { msg[0].clone() },
219224
msg,
220225
helps,
221226
&stacktrace,
@@ -256,11 +261,14 @@ pub fn report_error<'tcx, 'mir>(
256261

257262
/// Report an error or note (depending on the `error` argument) with the given stacktrace.
258263
/// Also emits a full stacktrace of the interpreter stack.
264+
/// We want to present a multi-line span message for some errors. Diagnostics do not support this
265+
/// directly, so we pass the lines as a `Vec<String>` and display each line after the first with an
266+
/// additional `span_label` or `note` call.
259267
fn report_msg<'tcx>(
260268
tcx: TyCtxt<'tcx>,
261269
diag_level: DiagLevel,
262270
title: &str,
263-
span_msg: String,
271+
span_msg: Vec<String>,
264272
mut helps: Vec<(Option<SpanData>, String)>,
265273
stacktrace: &[FrameInfo<'tcx>],
266274
) {
@@ -273,12 +281,17 @@ fn report_msg<'tcx>(
273281

274282
// Show main message.
275283
if span != DUMMY_SP {
276-
err.span_label(span, span_msg);
284+
for line in span_msg {
285+
err.span_label(span, line);
286+
}
277287
} else {
278288
// Make sure we show the message even when it is a dummy span.
279-
err.note(&span_msg);
289+
for line in span_msg {
290+
err.note(&line);
291+
}
280292
err.note("(no span available)");
281293
}
294+
282295
// Show help messages.
283296
if !helps.is_empty() {
284297
// Add visual separator before backtrace.
@@ -413,7 +426,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
413426
_ => ("tracking was triggered", DiagLevel::Note),
414427
};
415428

416-
report_msg(*this.tcx, diag_level, title, msg, vec![], &stacktrace);
429+
report_msg(*this.tcx, diag_level, title, vec![msg], vec![], &stacktrace);
417430
}
418431
});
419432
}

src/stacked_borrows.rs

Lines changed: 99 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,10 @@ impl GlobalState {
220220
}
221221

222222
/// Error reporting
223-
fn err_sb_ub(msg: String) -> InterpError<'static> {
223+
fn err_sb_ub(msg: String, help: Option<String>) -> InterpError<'static> {
224224
err_machine_stop!(TerminationInfo::ExperimentalUb {
225225
msg,
226+
help,
226227
url: format!(
227228
"https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md"
228229
),
@@ -320,12 +321,18 @@ impl<'tcx> Stack {
320321
if let Some(call) = item.protector {
321322
if global.is_active(call) {
322323
if let Some((tag, _)) = provoking_access {
323-
Err(err_sb_ub(format!(
324-
"not granting access to tag {:?} because incompatible item is protected: {:?}",
325-
tag, item
326-
)))?
324+
Err(err_sb_ub(
325+
format!(
326+
"not granting access to tag {:?} because incompatible item is protected: {:?}",
327+
tag, item
328+
),
329+
None,
330+
))?
327331
} else {
328-
Err(err_sb_ub(format!("deallocating while item is protected: {:?}", item)))?
332+
Err(err_sb_ub(
333+
format!("deallocating while item is protected: {:?}", item),
334+
None,
335+
))?
329336
}
330337
}
331338
}
@@ -334,22 +341,21 @@ impl<'tcx> Stack {
334341

335342
/// Test if a memory `access` using pointer tagged `tag` is granted.
336343
/// If yes, return the index of the item that granted it.
344+
/// `range` refers the entire operation, and `offset` refers to the specific offset into the
345+
/// allocation that we are currently checking.
337346
fn access(
338347
&mut self,
339348
access: AccessKind,
340349
tag: SbTag,
341-
dbg_ptr: Pointer<AllocId>, // just for debug printing amd error messages
350+
(alloc_id, range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
342351
global: &GlobalState,
343352
) -> InterpResult<'tcx> {
344353
// Two main steps: Find granting item, remove incompatible items above.
345354

346355
// Step 1: Find granting item.
347-
let granting_idx = self.find_granting(access, tag).ok_or_else(|| {
348-
err_sb_ub(format!(
349-
"no item granting {} to tag {:?} at {:?} found in borrow stack.",
350-
access, tag, dbg_ptr,
351-
))
352-
})?;
356+
let granting_idx = self
357+
.find_granting(access, tag)
358+
.ok_or_else(|| self.access_error(access, tag, alloc_id, range, offset))?;
353359

354360
// Step 2: Remove incompatible items above them. Make sure we do not remove protected
355361
// items. Behavior differs for reads and writes.
@@ -389,15 +395,15 @@ impl<'tcx> Stack {
389395
fn dealloc(
390396
&mut self,
391397
tag: SbTag,
392-
dbg_ptr: Pointer<AllocId>, // just for debug printing amd error messages
398+
dbg_ptr: Pointer<AllocId>, // just for debug printing and error messages
393399
global: &GlobalState,
394400
) -> InterpResult<'tcx> {
395401
// Step 1: Find granting item.
396402
self.find_granting(AccessKind::Write, tag).ok_or_else(|| {
397403
err_sb_ub(format!(
398404
"no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack",
399405
tag, dbg_ptr,
400-
))
406+
), None)
401407
})?;
402408

403409
// Step 2: Remove all items. Also checks for protectors.
@@ -412,23 +418,23 @@ impl<'tcx> Stack {
412418
/// `weak` controls whether this operation is weak or strong: weak granting does not act as
413419
/// an access, and they add the new item directly on top of the one it is derived
414420
/// from instead of all the way at the top of the stack.
421+
/// `range` refers the entire operation, and `offset` refers to the specific location in
422+
/// `range` that we are currently checking.
415423
fn grant(
416424
&mut self,
417425
derived_from: SbTag,
418426
new: Item,
419-
dbg_ptr: Pointer<AllocId>,
427+
(alloc_id, alloc_range, offset): (AllocId, AllocRange, Size), // just for debug printing and error messages
420428
global: &GlobalState,
421429
) -> InterpResult<'tcx> {
422430
// Figure out which access `perm` corresponds to.
423431
let access =
424432
if new.perm.grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read };
425433
// Now we figure out which item grants our parent (`derived_from`) this kind of access.
426434
// We use that to determine where to put the new item.
427-
let granting_idx = self.find_granting(access, derived_from)
428-
.ok_or_else(|| err_sb_ub(format!(
429-
"trying to reborrow for {:?} at {:?}, but parent tag {:?} does not have an appropriate item in the borrow stack",
430-
new.perm, dbg_ptr, derived_from,
431-
)))?;
435+
let granting_idx = self
436+
.find_granting(access, derived_from)
437+
.ok_or_else(|| self.grant_error(derived_from, new, alloc_id, alloc_range, offset))?;
432438

433439
// Compute where to put the new item.
434440
// Either way, we ensure that we insert the new item in a way such that between
@@ -447,7 +453,7 @@ impl<'tcx> Stack {
447453
// A "safe" reborrow for a pointer that actually expects some aliasing guarantees.
448454
// Here, creating a reference actually counts as an access.
449455
// This ensures F2b for `Unique`, by removing offending `SharedReadOnly`.
450-
self.access(access, derived_from, dbg_ptr, global)?;
456+
self.access(access, derived_from, (alloc_id, alloc_range, offset), global)?;
451457

452458
// We insert "as far up as possible": We know only compatible items are remaining
453459
// on top of `derived_from`, and we want the new item at the top so that we
@@ -467,6 +473,72 @@ impl<'tcx> Stack {
467473

468474
Ok(())
469475
}
476+
477+
/// Report a descriptive error when `new` could not be granted from `derived_from`.
478+
fn grant_error(
479+
&self,
480+
derived_from: SbTag,
481+
new: Item,
482+
alloc_id: AllocId,
483+
alloc_range: AllocRange,
484+
error_offset: Size,
485+
) -> InterpError<'static> {
486+
let action = format!(
487+
"trying to reborrow {:?} for {:?} permission at {}[{:#x}]",
488+
derived_from,
489+
new.perm,
490+
alloc_id,
491+
error_offset.bytes(),
492+
);
493+
err_sb_ub(
494+
format!("{}{}", action, self.error_cause(derived_from)),
495+
Some(Self::operation_summary("a reborrow", alloc_id, alloc_range)),
496+
)
497+
}
498+
499+
/// Report a descriptive error when `access` is not permitted based on `tag`.
500+
fn access_error(
501+
&self,
502+
access: AccessKind,
503+
tag: SbTag,
504+
alloc_id: AllocId,
505+
alloc_range: AllocRange,
506+
error_offset: Size,
507+
) -> InterpError<'static> {
508+
let action = format!(
509+
"attempting a {} using {:?} at {}[{:#x}]",
510+
access,
511+
tag,
512+
alloc_id,
513+
error_offset.bytes(),
514+
);
515+
err_sb_ub(
516+
format!("{}{}", action, self.error_cause(tag)),
517+
Some(Self::operation_summary("an access", alloc_id, alloc_range)),
518+
)
519+
}
520+
521+
fn operation_summary(
522+
operation: &'static str,
523+
alloc_id: AllocId,
524+
alloc_range: AllocRange,
525+
) -> String {
526+
format!(
527+
"this error occurs as part of {} at {:?}[{:#x}..{:#x}]",
528+
operation,
529+
alloc_id,
530+
alloc_range.start.bytes(),
531+
alloc_range.end().bytes()
532+
)
533+
}
534+
535+
fn error_cause(&self, tag: SbTag) -> &'static str {
536+
if self.borrows.iter().any(|item| item.tag == tag && item.perm != Permission::Disabled) {
537+
", but that tag only grants SharedReadOnly permission for this location"
538+
} else {
539+
", but that tag does not exist in the borrow stack for this location"
540+
}
541+
}
470542
}
471543
// # Stacked Borrows Core End
472544

@@ -566,7 +638,7 @@ impl Stacks {
566638
);
567639
let global = &*extra.borrow();
568640
self.for_each(range, move |offset, stack| {
569-
stack.access(AccessKind::Read, tag, Pointer::new(alloc_id, offset), global)
641+
stack.access(AccessKind::Read, tag, (alloc_id, range, offset), global)
570642
})
571643
}
572644

@@ -586,7 +658,7 @@ impl Stacks {
586658
);
587659
let global = extra.get_mut();
588660
self.for_each_mut(range, move |offset, stack| {
589-
stack.access(AccessKind::Write, tag, Pointer::new(alloc_id, offset), global)
661+
stack.access(AccessKind::Write, tag, (alloc_id, range, offset), global)
590662
})
591663
}
592664

@@ -693,7 +765,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
693765
};
694766
let item = Item { perm, tag: new_tag, protector };
695767
stacked_borrows.for_each(range, |offset, stack| {
696-
stack.grant(orig_tag, item, Pointer::new(alloc_id, offset), &*global)
768+
stack.grant(orig_tag, item, (alloc_id, range, offset), &*global)
697769
})
698770
})?;
699771
return Ok(());
@@ -707,8 +779,9 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
707779
alloc_extra.stacked_borrows.as_mut().expect("we should have Stacked Borrows data");
708780
let global = memory_extra.stacked_borrows.as_mut().unwrap().get_mut();
709781
let item = Item { perm, tag: new_tag, protector };
782+
let range = alloc_range(base_offset, size);
710783
stacked_borrows.for_each_mut(alloc_range(base_offset, size), |offset, stack| {
711-
stack.grant(orig_tag, item, Pointer::new(alloc_id, offset), global)
784+
stack.grant(orig_tag, item, (alloc_id, range, offset), global)
712785
})?;
713786
Ok(())
714787
}

tests/compile-fail/box-cell-alias.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::cell::Cell;
66

77
fn helper(val: Box<Cell<u8>>, ptr: *const Cell<u8>) -> u8 {
88
val.set(10);
9-
unsafe { (*ptr).set(20); } //~ ERROR does not have an appropriate item in the borrow stack
9+
unsafe { (*ptr).set(20); } //~ ERROR does not exist in the borrow stack
1010
val.get()
1111
}
1212

tests/compile-fail/stacked_borrows/illegal_write3.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ fn main() {
33
// Make sure raw ptr with raw tag cannot mutate frozen location without breaking the shared ref.
44
let r#ref = &target; // freeze
55
let ptr = r#ref as *const _ as *mut _; // raw ptr, with raw tag
6-
unsafe { *ptr = 42; } //~ ERROR borrow stack
6+
unsafe { *ptr = 42; } //~ ERROR only grants SharedReadOnly permission
77
let _val = *r#ref;
88
}

tests/compile-fail/stacked_borrows/raw_tracking.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ fn main() {
77
let raw2 = &mut l as *mut _; // invalidates raw1
88
// Without raw pointer tracking, Stacked Borrows cannot distinguish raw1 and raw2, and thus
99
// fails to realize that raw1 should not be used any more.
10-
unsafe { *raw1 = 13; } //~ ERROR no item granting write access to tag
10+
unsafe { *raw1 = 13; } //~ ERROR does not exist in the borrow stack
1111
unsafe { *raw2 = 13; }
1212
}

tests/compile-fail/stacked_borrows/shr_frozen_violation1.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ fn main() {
99
}
1010

1111
fn unknown_code(x: &i32) {
12-
unsafe { *(x as *const i32 as *mut i32) = 7; } //~ ERROR borrow stack
12+
unsafe { *(x as *const i32 as *mut i32) = 7; } //~ ERROR only grants SharedReadOnly permission
1313
}

tests/compile-fail/stacked_borrows/zst_slice.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// compile-flags: -Zmiri-tag-raw-pointers
2-
// error-pattern: does not have an appropriate item in the borrow stack
2+
// error-pattern: does not exist in the borrow stack
33

44
fn main() {
55
unsafe {

0 commit comments

Comments
 (0)