Skip to content

Commit a91f379

Browse files
committed
Auto merge of #1127 - rust-lang:stacked_borrow_tracing, r=RalfJung
Add a scheme for emitting errors without halting interpretation cc #797
2 parents 85ab348 + dbffbe5 commit a91f379

File tree

4 files changed

+128
-80
lines changed

4 files changed

+128
-80
lines changed

src/diagnostics.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use rustc_mir::interpret::InterpErrorInfo;
2+
use std::cell::RefCell;
3+
4+
use crate::*;
5+
6+
/// Miri specific diagnostics
7+
pub enum NonHaltingDiagnostic {
8+
PoppedTrackedPointerTag(Item),
9+
}
10+
11+
/// Emit a custom diagnostic without going through the miri-engine machinery
12+
pub fn report_diagnostic<'tcx, 'mir>(
13+
ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
14+
mut e: InterpErrorInfo<'tcx>,
15+
) -> Option<i64> {
16+
// Special treatment for some error kinds
17+
let msg = match e.kind {
18+
InterpError::MachineStop(ref info) => {
19+
let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
20+
match info {
21+
TerminationInfo::Exit(code) => return Some(*code),
22+
TerminationInfo::Abort => format!("the evaluated program aborted execution"),
23+
}
24+
}
25+
err_unsup!(NoMirFor(..)) => format!(
26+
"{}. Did you set `MIRI_SYSROOT` to a Miri-enabled sysroot? You can prepare one with `cargo miri setup`.",
27+
e
28+
),
29+
InterpError::InvalidProgram(_) => bug!("This error should be impossible in Miri: {}", e),
30+
_ => e.to_string(),
31+
};
32+
e.print_backtrace();
33+
report_msg(ecx, msg, true)
34+
}
35+
36+
/// Report an error or note (depending on the `error` argument) at the current frame's current statement.
37+
/// Also emits a full stacktrace of the interpreter stack.
38+
pub fn report_msg<'tcx, 'mir>(
39+
ecx: &InterpCx<'mir, 'tcx, Evaluator<'tcx>>,
40+
msg: String,
41+
error: bool,
42+
) -> Option<i64> {
43+
if let Some(frame) = ecx.stack().last() {
44+
let span = frame.current_source_info().unwrap().span;
45+
46+
let mut err = if error {
47+
let msg = format!("Miri evaluation error: {}", msg);
48+
ecx.tcx.sess.struct_span_err(span, msg.as_str())
49+
} else {
50+
ecx.tcx.sess.diagnostic().span_note_diag(span, msg.as_str())
51+
};
52+
let frames = ecx.generate_stacktrace(None);
53+
err.span_label(span, msg);
54+
// We iterate with indices because we need to look at the next frame (the caller).
55+
for idx in 0..frames.len() {
56+
let frame_info = &frames[idx];
57+
let call_site_is_local = frames
58+
.get(idx + 1)
59+
.map_or(false, |caller_info| caller_info.instance.def_id().is_local());
60+
if call_site_is_local {
61+
err.span_note(frame_info.call_site, &frame_info.to_string());
62+
} else {
63+
err.note(&frame_info.to_string());
64+
}
65+
}
66+
err.emit();
67+
} else {
68+
ecx.tcx.sess.err(&msg);
69+
}
70+
71+
for (i, frame) in ecx.stack().iter().enumerate() {
72+
trace!("-------------------");
73+
trace!("Frame {}", i);
74+
trace!(" return: {:?}", frame.return_place.map(|p| *p));
75+
for (i, local) in frame.locals.iter().enumerate() {
76+
trace!(" local {}: {:?}", i, local.value);
77+
}
78+
}
79+
// Let the reported error determine the return code.
80+
return None;
81+
}
82+
83+
thread_local! {
84+
static DIAGNOSTICS: RefCell<Vec<NonHaltingDiagnostic>> = RefCell::new(Vec::new());
85+
}
86+
87+
/// Schedule a diagnostic for emitting. This function works even if you have no `InterpCx` available.
88+
/// The diagnostic will be emitted after the current interpreter step is finished.
89+
pub fn register_diagnostic(e: NonHaltingDiagnostic) {
90+
DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(e));
91+
}
92+
93+
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
94+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
95+
/// Emit all diagnostics that were registed with `register_diagnostics`
96+
fn process_diagnostics(&self) {
97+
let this = self.eval_context_ref();
98+
DIAGNOSTICS.with(|diagnostics| {
99+
for e in diagnostics.borrow_mut().drain(..) {
100+
let msg = match e {
101+
NonHaltingDiagnostic::PoppedTrackedPointerTag(item) =>
102+
format!("popped tracked tag for item {:?}", item),
103+
};
104+
report_msg(this, msg, false);
105+
}
106+
});
107+
}
108+
}

src/eval.rs

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ pub struct MiriConfig {
3333
/// Details of premature program termination.
3434
pub enum TerminationInfo {
3535
Exit(i64),
36-
PoppedTrackedPointerTag(Item),
3736
Abort,
3837
}
3938

@@ -183,7 +182,9 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
183182

184183
// Perform the main execution.
185184
let res: InterpResult<'_, i64> = (|| {
186-
ecx.run()?;
185+
while ecx.step()? {
186+
ecx.process_diagnostics();
187+
}
187188
// Read the return code pointer *before* we run TLS destructors, to assert
188189
// that it was written to by the time that `start` lang item returned.
189190
let return_code = ecx.read_scalar(ret_place.into())?.not_undef()?.to_machine_isize(&ecx)?;
@@ -203,66 +204,8 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) ->
203204
return None;
204205
}
205206
}
206-
return Some(return_code);
207-
}
208-
Err(mut e) => {
209-
// Special treatment for some error kinds
210-
let msg = match e.kind {
211-
InterpError::MachineStop(ref info) => {
212-
let info = info
213-
.downcast_ref::<TerminationInfo>()
214-
.expect("invalid MachineStop payload");
215-
match info {
216-
TerminationInfo::Exit(code) => return Some(*code),
217-
TerminationInfo::PoppedTrackedPointerTag(item) =>
218-
format!("popped tracked tag for item {:?}", item),
219-
TerminationInfo::Abort =>
220-
format!("the evaluated program aborted execution"),
221-
}
222-
}
223-
err_unsup!(NoMirFor(..)) => format!(
224-
"{}. Did you set `MIRI_SYSROOT` to a Miri-enabled sysroot? You can prepare one with `cargo miri setup`.",
225-
e
226-
),
227-
InterpError::InvalidProgram(_) =>
228-
bug!("This error should be impossible in Miri: {}", e),
229-
_ => e.to_string(),
230-
};
231-
e.print_backtrace();
232-
if let Some(frame) = ecx.stack().last() {
233-
let span = frame.current_source_info().unwrap().span;
234-
235-
let msg = format!("Miri evaluation error: {}", msg);
236-
let mut err = ecx.tcx.sess.struct_span_err(span, msg.as_str());
237-
let frames = ecx.generate_stacktrace(None);
238-
err.span_label(span, msg);
239-
// We iterate with indices because we need to look at the next frame (the caller).
240-
for idx in 0..frames.len() {
241-
let frame_info = &frames[idx];
242-
let call_site_is_local = frames
243-
.get(idx + 1)
244-
.map_or(false, |caller_info| caller_info.instance.def_id().is_local());
245-
if call_site_is_local {
246-
err.span_note(frame_info.call_site, &frame_info.to_string());
247-
} else {
248-
err.note(&frame_info.to_string());
249-
}
250-
}
251-
err.emit();
252-
} else {
253-
ecx.tcx.sess.err(&msg);
254-
}
255-
256-
for (i, frame) in ecx.stack().iter().enumerate() {
257-
trace!("-------------------");
258-
trace!("Frame {}", i);
259-
trace!(" return: {:?}", frame.return_place.map(|p| *p));
260-
for (i, local) in frame.locals.iter().enumerate() {
261-
trace!(" local {}: {:?}", i, local.value);
262-
}
263-
}
264-
// Let the reported error determine the return code.
265-
return None;
207+
Some(return_code)
266208
}
209+
Err(e) => report_diagnostic(&ecx, e),
267210
}
268211
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extern crate rustc_data_structures;
1616
extern crate rustc_mir;
1717
extern crate rustc_target;
1818

19+
mod diagnostics;
1920
mod eval;
2021
mod helpers;
2122
mod intptrcast;
@@ -41,6 +42,9 @@ pub use crate::shims::time::EvalContextExt as TimeEvalContextExt;
4142
pub use crate::shims::tls::{EvalContextExt as TlsEvalContextExt, TlsData};
4243
pub use crate::shims::EvalContextExt as ShimsEvalContextExt;
4344

45+
pub use crate::diagnostics::{
46+
register_diagnostic, report_diagnostic, EvalContextExt as DiagnosticsEvalContextExt, NonHaltingDiagnostic,
47+
};
4448
pub use crate::eval::{create_ecx, eval_main, MiriConfig, TerminationInfo};
4549
pub use crate::helpers::EvalContextExt as HelpersEvalContextExt;
4650
pub use crate::machine::{

src/stacked_borrows.rs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ use rustc_hir::Mutability;
1111
use rustc::mir::RetagKind;
1212
use rustc::ty::{self, layout::Size};
1313

14-
use crate::{
15-
AllocId, HelpersEvalContextExt, ImmTy, Immediate, InterpResult, MPlaceTy, MemoryKind,
16-
MiriMemoryKind, PlaceTy, Pointer, RangeMap, TerminationInfo,
17-
};
14+
use crate::*;
1815

1916
pub type PtrId = NonZeroU64;
2017
pub type CallId = NonZeroU64;
@@ -269,7 +266,7 @@ impl<'tcx> Stack {
269266
fn check_protector(item: &Item, tag: Option<Tag>, global: &GlobalState) -> InterpResult<'tcx> {
270267
if let Tag::Tagged(id) = item.tag {
271268
if Some(id) == global.tracked_pointer_tag {
272-
throw_machine_stop!(TerminationInfo::PoppedTrackedPointerTag(item.clone()));
269+
register_diagnostic(NonHaltingDiagnostic::PoppedTrackedPointerTag(item.clone()));
273270
}
274271
}
275272
if let Some(call) = item.protector {
@@ -296,12 +293,9 @@ impl<'tcx> Stack {
296293
// Two main steps: Find granting item, remove incompatible items above.
297294

298295
// Step 1: Find granting item.
299-
let granting_idx = self.find_granting(access, tag).ok_or_else(|| {
300-
err_ub!(UbExperimental(format!(
301-
"no item granting {} to tag {:?} found in borrow stack",
302-
access, tag,
303-
)))
304-
})?;
296+
let granting_idx = self.find_granting(access, tag).ok_or_else(|| err_ub!(UbExperimental(
297+
format!("no item granting {} to tag {:?} found in borrow stack.", access, tag),
298+
)))?;
305299

306300
// Step 2: Remove incompatible items above them. Make sure we do not remove protected
307301
// items. Behavior differs for reads and writes.
@@ -340,12 +334,10 @@ impl<'tcx> Stack {
340334
/// active protectors at all because we will remove all items.
341335
fn dealloc(&mut self, tag: Tag, global: &GlobalState) -> InterpResult<'tcx> {
342336
// Step 1: Find granting item.
343-
self.find_granting(AccessKind::Write, tag).ok_or_else(|| {
344-
err_ub!(UbExperimental(format!(
345-
"no item granting write access for deallocation to tag {:?} found in borrow stack",
346-
tag,
347-
)))
348-
})?;
337+
self.find_granting(AccessKind::Write, tag).ok_or_else(|| err_ub!(UbExperimental(format!(
338+
"no item granting write access for deallocation to tag {:?} found in borrow stack",
339+
tag,
340+
))))?;
349341

350342
// Step 2: Remove all items. Also checks for protectors.
351343
for item in self.borrows.drain(..).rev() {
@@ -367,7 +359,8 @@ impl<'tcx> Stack {
367359
// We use that to determine where to put the new item.
368360
let granting_idx = self.find_granting(access, derived_from)
369361
.ok_or_else(|| err_ub!(UbExperimental(format!(
370-
"trying to reborrow for {:?}, but parent tag {:?} does not have an appropriate item in the borrow stack", new.perm, derived_from,
362+
"trying to reborrow for {:?}, but parent tag {:?} does not have an appropriate item in the borrow stack",
363+
new.perm, derived_from,
371364
))))?;
372365

373366
// Compute where to put the new item.

0 commit comments

Comments
 (0)