Skip to content

Commit 3e5d797

Browse files
authored
Merge pull request #4690 from RalfJung/top-level-error
refactor and unify top-level error handling a bit
2 parents 0d692cb + 4ed54a8 commit 3e5d797

File tree

9 files changed

+143
-131
lines changed

9 files changed

+143
-131
lines changed

src/tools/miri/src/bin/miri.rs

Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ extern crate rustc_span;
2323
mod log;
2424

2525
use std::env;
26-
use std::num::NonZero;
26+
use std::num::{NonZero, NonZeroI32};
2727
use std::ops::Range;
2828
use std::rc::Rc;
2929
use std::str::FromStr;
30-
use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
30+
use std::sync::atomic::{AtomicU32, Ordering};
3131

3232
use miri::{
3333
BacktraceStyle, BorrowTrackerMethod, GenmcConfig, GenmcCtx, MiriConfig, MiriEntryFnType,
3434
ProvenanceMode, TreeBorrowsParams, ValidationMode, run_genmc_mode,
3535
};
3636
use rustc_abi::ExternAbi;
37-
use rustc_data_structures::sync;
37+
use rustc_data_structures::sync::{self, DynSync};
3838
use rustc_driver::Compilation;
3939
use rustc_hir::def_id::LOCAL_CRATE;
4040
use rustc_hir::{self as hir, Node};
@@ -120,15 +120,47 @@ fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, MiriEntryFnType) {
120120
}
121121
}
122122

123+
fn run_many_seeds(
124+
many_seeds: ManySeedsConfig,
125+
eval_entry_once: impl Fn(u64) -> Result<(), NonZeroI32> + DynSync,
126+
) -> Result<(), NonZeroI32> {
127+
let exit_code =
128+
sync::IntoDynSyncSend(AtomicU32::new(rustc_driver::EXIT_SUCCESS.cast_unsigned()));
129+
let num_failed = sync::IntoDynSyncSend(AtomicU32::new(0));
130+
sync::par_for_each_in(many_seeds.seeds.clone(), |&seed| {
131+
if let Err(return_code) = eval_entry_once(seed.into()) {
132+
eprintln!("FAILING SEED: {seed}");
133+
if !many_seeds.keep_going {
134+
// `abort_if_errors` would unwind but would not actually stop miri, since
135+
// `par_for_each` waits for the rest of the threads to finish.
136+
exit(return_code.get());
137+
}
138+
// Preserve the "maximum" return code (when interpreted as `u32`), to make
139+
// the result order-independent and to make it 0 only if all executions were 0.
140+
exit_code.fetch_max(return_code.get().cast_unsigned(), Ordering::Relaxed);
141+
num_failed.fetch_add(1, Ordering::Relaxed);
142+
}
143+
});
144+
let num_failed = num_failed.0.into_inner();
145+
let exit_code = exit_code.0.into_inner().cast_signed();
146+
if num_failed > 0 {
147+
eprintln!("{num_failed}/{total} SEEDS FAILED", total = many_seeds.seeds.count());
148+
Err(NonZeroI32::new(exit_code).unwrap())
149+
} else {
150+
assert!(exit_code == 0);
151+
Ok(())
152+
}
153+
}
154+
123155
impl rustc_driver::Callbacks for MiriCompilerCalls {
124156
fn after_analysis<'tcx>(
125157
&mut self,
126158
_: &rustc_interface::interface::Compiler,
127159
tcx: TyCtxt<'tcx>,
128160
) -> Compilation {
129-
if tcx.sess.dcx().has_errors_or_delayed_bugs().is_some() {
130-
tcx.dcx().fatal("miri cannot be run on programs that fail compilation");
131-
}
161+
tcx.dcx().abort_if_errors();
162+
tcx.dcx().flush_delayed();
163+
132164
if !tcx.crate_types().contains(&CrateType::Executable) {
133165
tcx.dcx().fatal("miri only makes sense on bin crates");
134166
}
@@ -161,64 +193,28 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
161193
optimizations is usually marginal at best.");
162194
}
163195

164-
// Run in GenMC mode if enabled.
165-
if config.genmc_config.is_some() {
166-
// Validate GenMC settings.
167-
if let Err(err) = GenmcConfig::validate(&mut config, tcx) {
168-
fatal_error!("Invalid settings: {err}");
169-
}
170-
171-
// This is the entry point used in GenMC mode.
172-
// This closure will be called multiple times to explore the concurrent execution space of the program.
173-
let eval_entry_once = |genmc_ctx: Rc<GenmcCtx>| {
196+
let res = if config.genmc_config.is_some() {
197+
assert!(self.many_seeds.is_none());
198+
run_genmc_mode(tcx, &config, |genmc_ctx: Rc<GenmcCtx>| {
174199
miri::eval_entry(tcx, entry_def_id, entry_type, &config, Some(genmc_ctx))
175-
};
176-
let return_code = run_genmc_mode(&config, eval_entry_once, tcx).unwrap_or_else(|| {
177-
tcx.dcx().abort_if_errors();
178-
rustc_driver::EXIT_FAILURE
179-
});
180-
exit(return_code);
181-
};
182-
183-
if let Some(many_seeds) = self.many_seeds.take() {
200+
})
201+
} else if let Some(many_seeds) = self.many_seeds.take() {
184202
assert!(config.seed.is_none());
185-
let exit_code = sync::IntoDynSyncSend(AtomicI32::new(rustc_driver::EXIT_SUCCESS));
186-
let num_failed = sync::IntoDynSyncSend(AtomicU32::new(0));
187-
sync::par_for_each_in(many_seeds.seeds.clone(), |seed| {
203+
run_many_seeds(many_seeds, |seed| {
188204
let mut config = config.clone();
189-
config.seed = Some((*seed).into());
205+
config.seed = Some(seed);
190206
eprintln!("Trying seed: {seed}");
191-
let return_code = miri::eval_entry(
192-
tcx,
193-
entry_def_id,
194-
entry_type,
195-
&config,
196-
/* genmc_ctx */ None,
197-
)
198-
.unwrap_or(rustc_driver::EXIT_FAILURE);
199-
if return_code != rustc_driver::EXIT_SUCCESS {
200-
eprintln!("FAILING SEED: {seed}");
201-
if !many_seeds.keep_going {
202-
// `abort_if_errors` would actually not stop, since `par_for_each` waits for the
203-
// rest of the to finish, so we just exit immediately.
204-
exit(return_code);
205-
}
206-
exit_code.store(return_code, Ordering::Relaxed);
207-
num_failed.fetch_add(1, Ordering::Relaxed);
208-
}
209-
});
210-
let num_failed = num_failed.0.into_inner();
211-
if num_failed > 0 {
212-
eprintln!("{num_failed}/{total} SEEDS FAILED", total = many_seeds.seeds.count());
213-
}
214-
exit(exit_code.0.into_inner());
207+
miri::eval_entry(tcx, entry_def_id, entry_type, &config, /* genmc_ctx */ None)
208+
})
215209
} else {
216-
let return_code = miri::eval_entry(tcx, entry_def_id, entry_type, &config, None)
217-
.unwrap_or_else(|| {
218-
tcx.dcx().abort_if_errors();
219-
rustc_driver::EXIT_FAILURE
220-
});
221-
exit(return_code);
210+
miri::eval_entry(tcx, entry_def_id, entry_type, &config, None)
211+
};
212+
213+
if let Err(return_code) = res {
214+
tcx.dcx().abort_if_errors();
215+
exit(return_code.get());
216+
} else {
217+
exit(rustc_driver::EXIT_SUCCESS);
222218
}
223219

224220
// Unreachable.
@@ -743,6 +739,13 @@ fn main() {
743739
);
744740
};
745741

742+
// Validate GenMC settings.
743+
if miri_config.genmc_config.is_some()
744+
&& let Err(err) = GenmcConfig::validate(&mut miri_config)
745+
{
746+
fatal_error!("Invalid settings: {err}");
747+
}
748+
746749
debug!("rustc arguments: {:?}", rustc_args);
747750
debug!("crate arguments: {:?}", miri_config.args);
748751
if !miri_config.native_lib.is_empty() && miri_config.native_lib_enable_tracing {

src/tools/miri/src/concurrency/genmc/config.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
use genmc_sys::LogLevel;
2-
use rustc_abi::Endian;
3-
use rustc_middle::ty::TyCtxt;
42

53
use super::GenmcParams;
64
use crate::{IsolatedOp, MiriConfig, RejectOpWith};
@@ -86,16 +84,11 @@ impl GenmcConfig {
8684
///
8785
/// Unsupported configurations return an error.
8886
/// Adjusts Miri settings where required, printing a warnings if the change might be unexpected for the user.
89-
pub fn validate(miri_config: &mut MiriConfig, tcx: TyCtxt<'_>) -> Result<(), &'static str> {
87+
pub fn validate(miri_config: &mut MiriConfig) -> Result<(), &'static str> {
9088
let Some(genmc_config) = miri_config.genmc_config.as_mut() else {
9189
return Ok(());
9290
};
9391

94-
// Check for supported target.
95-
if tcx.data_layout.endian != Endian::Little || tcx.data_layout.pointer_size().bits() != 64 {
96-
return Err("GenMC only supports 64bit little-endian targets");
97-
}
98-
9992
// Check for disallowed configurations.
10093
if !miri_config.data_race_detector {
10194
return Err("Cannot disable data race detection in GenMC mode");

src/tools/miri/src/concurrency/genmc/dummy.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use rustc_abi::{Align, Size};
22
use rustc_const_eval::interpret::{AllocId, InterpCx, InterpResult};
3-
use rustc_middle::ty::TyCtxt;
43

54
pub use self::intercept::EvalContextExt as GenmcEvalContextExt;
65
pub use self::run::run_genmc_mode;
@@ -23,17 +22,18 @@ pub struct GenmcCtx {}
2322
pub struct GenmcConfig {}
2423

2524
mod run {
25+
use std::num::NonZeroI32;
2626
use std::rc::Rc;
2727

2828
use rustc_middle::ty::TyCtxt;
2929

3030
use crate::{GenmcCtx, MiriConfig};
3131

3232
pub fn run_genmc_mode<'tcx>(
33-
_config: &MiriConfig,
34-
_eval_entry: impl Fn(Rc<GenmcCtx>) -> Option<i32>,
3533
_tcx: TyCtxt<'tcx>,
36-
) -> Option<i32> {
34+
_config: &MiriConfig,
35+
_eval_entry: impl Fn(Rc<GenmcCtx>) -> Result<(), NonZeroI32>,
36+
) -> Result<(), NonZeroI32> {
3737
unreachable!();
3838
}
3939
}
@@ -240,10 +240,7 @@ impl GenmcConfig {
240240
}
241241
}
242242

243-
pub fn validate(
244-
_miri_config: &mut crate::MiriConfig,
245-
_tcx: TyCtxt<'_>,
246-
) -> Result<(), &'static str> {
243+
pub fn validate(_miri_config: &mut crate::MiriConfig) -> Result<(), &'static str> {
247244
Ok(())
248245
}
249246
}

src/tools/miri/src/concurrency/genmc/run.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use std::num::NonZeroI32;
12
use std::rc::Rc;
23
use std::sync::Arc;
34
use std::time::Instant;
45

56
use genmc_sys::EstimationResult;
7+
use rustc_abi::Endian;
68
use rustc_log::tracing;
79
use rustc_middle::ty::TyCtxt;
810

@@ -24,10 +26,15 @@ pub(super) enum GenmcMode {
2426
///
2527
/// Returns `None` is an error is detected, or `Some(return_value)` with the return value of the last run of the program.
2628
pub fn run_genmc_mode<'tcx>(
27-
config: &MiriConfig,
28-
eval_entry: impl Fn(Rc<GenmcCtx>) -> Option<i32>,
2929
tcx: TyCtxt<'tcx>,
30-
) -> Option<i32> {
30+
config: &MiriConfig,
31+
eval_entry: impl Fn(Rc<GenmcCtx>) -> Result<(), NonZeroI32>,
32+
) -> Result<(), NonZeroI32> {
33+
// Check for supported target.
34+
if tcx.data_layout.endian != Endian::Little || tcx.data_layout.pointer_size().bits() != 64 {
35+
tcx.dcx().fatal("GenMC only supports 64bit little-endian targets");
36+
}
37+
3138
let genmc_config = config.genmc_config.as_ref().unwrap();
3239
// Run in Estimation mode if requested.
3340
if genmc_config.do_estimation {
@@ -41,10 +48,10 @@ pub fn run_genmc_mode<'tcx>(
4148

4249
fn run_genmc_mode_impl<'tcx>(
4350
config: &MiriConfig,
44-
eval_entry: &impl Fn(Rc<GenmcCtx>) -> Option<i32>,
51+
eval_entry: &impl Fn(Rc<GenmcCtx>) -> Result<(), NonZeroI32>,
4552
tcx: TyCtxt<'tcx>,
4653
mode: GenmcMode,
47-
) -> Option<i32> {
54+
) -> Result<(), NonZeroI32> {
4855
let time_start = Instant::now();
4956
let genmc_config = config.genmc_config.as_ref().unwrap();
5057

@@ -62,9 +69,9 @@ fn run_genmc_mode_impl<'tcx>(
6269
genmc_ctx.prepare_next_execution();
6370

6471
// Execute the program until completion to get the return value, or return if an error happens:
65-
let Some(return_code) = eval_entry(genmc_ctx.clone()) else {
72+
if let Err(err) = eval_entry(genmc_ctx.clone()) {
6673
genmc_ctx.print_genmc_output(genmc_config, tcx);
67-
return None;
74+
return Err(err);
6875
};
6976

7077
// We inform GenMC that the execution is complete.
@@ -80,18 +87,17 @@ fn run_genmc_mode_impl<'tcx>(
8087
genmc_ctx.print_verification_output(genmc_config, elapsed_time_sec);
8188
}
8289
// Return the return code of the last execution.
83-
return Some(return_code);
90+
return Ok(());
8491
}
8592
ExecutionEndResult::Error(error) => {
8693
// This can be reached for errors that affect the entire execution, not just a specific event.
8794
// For instance, linearizability checking and liveness checking report their errors this way.
88-
// Neither are supported by Miri-GenMC at the moment though. However, GenMC also
89-
// treats races on deallocation as global errors, so this code path is still reachable.
95+
// Neither are supported by Miri-GenMC at the moment though.
9096
// Since we don't have any span information for the error at this point,
9197
// we just print GenMC's error string, and the full GenMC output if requested.
9298
eprintln!("(GenMC) Error detected: {error}");
9399
genmc_ctx.print_genmc_output(genmc_config, tcx);
94-
return None;
100+
return Err(NonZeroI32::new(rustc_driver::EXIT_FAILURE).unwrap());
95101
}
96102
}
97103
}

src/tools/miri/src/diagnostics.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,19 +226,20 @@ pub fn prune_stacktrace<'tcx>(
226226
}
227227
}
228228

229-
/// Emit a custom diagnostic without going through the miri-engine machinery.
229+
/// Report the result of a Miri execution.
230230
///
231-
/// Returns `Some` if this was regular program termination with a given exit code and a `bool` indicating whether a leak check should happen; `None` otherwise.
232-
pub fn report_error<'tcx>(
231+
/// Returns `Some` if this was regular program termination with a given exit code and a `bool`
232+
/// indicating whether a leak check should happen; `None` otherwise.
233+
pub fn report_result<'tcx>(
233234
ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
234-
e: InterpErrorInfo<'tcx>,
235+
res: InterpErrorInfo<'tcx>,
235236
) -> Option<(i32, bool)> {
236237
use InterpErrorKind::*;
237238
use UndefinedBehaviorInfo::*;
238239

239240
let mut labels = vec![];
240241

241-
let (title, helps) = if let MachineStop(info) = e.kind() {
242+
let (title, helps) = if let MachineStop(info) = res.kind() {
242243
let info = info.downcast_ref::<TerminationInfo>().expect("invalid MachineStop payload");
243244
use TerminationInfo::*;
244245
let title = match info {
@@ -334,7 +335,7 @@ pub fn report_error<'tcx>(
334335
};
335336
(title, helps)
336337
} else {
337-
let title = match e.kind() {
338+
let title = match res.kind() {
338339
UndefinedBehavior(ValidationError(validation_err))
339340
if matches!(
340341
validation_err.kind,
@@ -344,7 +345,7 @@ pub fn report_error<'tcx>(
344345
ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`)
345346
bug!(
346347
"This validation error should be impossible in Miri: {}",
347-
format_interp_error(ecx.tcx.dcx(), e)
348+
format_interp_error(ecx.tcx.dcx(), res)
348349
);
349350
}
350351
UndefinedBehavior(_) => "Undefined Behavior",
@@ -363,12 +364,12 @@ pub fn report_error<'tcx>(
363364
ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`)
364365
bug!(
365366
"This error should be impossible in Miri: {}",
366-
format_interp_error(ecx.tcx.dcx(), e)
367+
format_interp_error(ecx.tcx.dcx(), res)
367368
);
368369
}
369370
};
370371
#[rustfmt::skip]
371-
let helps = match e.kind() {
372+
let helps = match res.kind() {
372373
Unsupported(_) =>
373374
vec![
374375
note!("this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support"),
@@ -422,7 +423,7 @@ pub fn report_error<'tcx>(
422423
// We want to dump the allocation if this is `InvalidUninitBytes`.
423424
// Since `format_interp_error` consumes `e`, we compute the outut early.
424425
let mut extra = String::new();
425-
match e.kind() {
426+
match res.kind() {
426427
UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => {
427428
writeln!(
428429
extra,
@@ -448,7 +449,7 @@ pub fn report_error<'tcx>(
448449
if let Some(title) = title {
449450
write!(primary_msg, "{title}: ").unwrap();
450451
}
451-
write!(primary_msg, "{}", format_interp_error(ecx.tcx.dcx(), e)).unwrap();
452+
write!(primary_msg, "{}", format_interp_error(ecx.tcx.dcx(), res)).unwrap();
452453

453454
if labels.is_empty() {
454455
labels.push(format!("{} occurred here", title.unwrap_or("error")));

0 commit comments

Comments
 (0)