-
Notifications
You must be signed in to change notification settings - Fork 348
/
phases.rs
750 lines (673 loc) · 33.4 KB
/
phases.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
//! Implements the various phases of `cargo miri run/test`.
use std::fs::{self, File};
use std::io::{BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, thread};
use rustc_version::VersionMeta;
use crate::{setup::*, util::*};
const CARGO_MIRI_HELP: &str = r"Runs binary crates and tests in Miri
Usage:
cargo miri [subcommand] [<cargo options>...] [--] [<program/test suite options>...]
Subcommands:
run, r Run binaries
test, t Run tests
nextest Run tests with nextest (requires cargo-nextest installed)
setup Only perform automatic setup, but without asking questions (for getting a proper libstd)
clean Clean the Miri cache & target directory
The cargo options are exactly the same as for `cargo run` and `cargo test`, respectively.
Furthermore, the following extra flags and environment variables are recognized for `run` and `test`:
--many-seeds[=from..to] Run the program/tests many times with different seeds in the given range.
The range defaults to `0..64`.
MIRIFLAGS Extra flags to pass to the Miri driver. Use this to pass `-Zmiri-...` flags.
Examples:
cargo miri run
cargo miri test -- test-suite-filter
cargo miri setup --print-sysroot
This will print the path to the generated sysroot (and nothing else) on stdout.
stderr will still contain progress information about how the build is doing.
";
const DEFAULT_MANY_SEEDS: &str = "0..64";
fn show_help() {
println!("{CARGO_MIRI_HELP}");
}
fn show_version() {
print!("miri {}", env!("CARGO_PKG_VERSION"));
let version = format!("{} {}", env!("GIT_HASH"), env!("COMMIT_DATE"));
if version.len() > 1 {
// If there is actually something here, print it.
print!(" ({version})");
}
println!();
}
fn forward_patched_extern_arg(args: &mut impl Iterator<Item = String>, cmd: &mut Command) {
cmd.arg("--extern"); // always forward flag, but adjust filename:
let path = args.next().expect("`--extern` should be followed by a filename");
if let Some(lib) = path.strip_suffix(".rlib") {
// If this is an rlib, make it an rmeta.
cmd.arg(format!("{lib}.rmeta"));
} else {
// Some other extern file (e.g. a `.so`). Forward unchanged.
cmd.arg(path);
}
}
pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
// Check for version and help flags even when invoked as `cargo-miri`.
if has_arg_flag("--help") || has_arg_flag("-h") {
show_help();
return;
}
if has_arg_flag("--version") || has_arg_flag("-V") {
show_version();
return;
}
// Require a subcommand before any flags.
// We cannot know which of those flags take arguments and which do not,
// so we cannot detect subcommands later.
let Some(subcommand) = args.next() else {
show_error!("`cargo miri` needs to be called with a subcommand (`run`, `test`, `clean`)");
};
let subcommand = match &*subcommand {
"setup" => MiriCommand::Setup,
"test" | "t" | "run" | "r" | "nextest" => MiriCommand::Forward(subcommand),
"clean" => MiriCommand::Clean,
_ =>
show_error!(
"`cargo miri` supports the following subcommands: `run`, `test`, `nextest`, `clean`, and `setup`."
),
};
let verbose = num_arg_flag("-v");
let quiet = has_arg_flag("-q") || has_arg_flag("--quiet");
// Determine the involved architectures.
let rustc_version = VersionMeta::for_command(miri_for_host()).unwrap_or_else(|err| {
panic!(
"failed to determine underlying rustc version of Miri ({:?}):\n{err:?}",
miri_for_host()
)
});
let mut targets = get_arg_flag_values("--target").collect::<Vec<_>>();
// If `targets` is empty, we need to add a `--target $HOST` flag ourselves, and also ensure
// that the host target is indeed setup.
let target_flag = if targets.is_empty() {
let host = &rustc_version.host;
targets.push(host.clone());
Some(host)
} else {
// We don't need to add a `--target` flag, we just forward the user's flags.
None
};
// If cleaning the target directory & sysroot cache,
// delete them then exit. There is no reason to setup a new
// sysroot in this execution.
if let MiriCommand::Clean = subcommand {
let metadata = get_cargo_metadata();
clean_target_dir(&metadata);
clean_sysroot();
return;
}
for target in &targets {
// We always setup.
setup(&subcommand, target.as_str(), &rustc_version, verbose, quiet);
}
let miri_sysroot = get_sysroot_dir();
// Invoke actual cargo for the job, but with different flags.
// We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but
// requires some extra work to make the build check-only (see all the `--emit` hacks below).
// <https://github.com/rust-lang/miri/pull/1540#issuecomment-693553191> describes an alternative
// approach that uses `cargo check`, making that part easier but target and binary handling
// harder.
let cargo_miri_path = env::current_exe()
.expect("current executable path invalid")
.into_os_string()
.into_string()
.expect("current executable path is not valid UTF-8");
let cargo_cmd = match subcommand {
MiriCommand::Forward(s) => s,
MiriCommand::Setup => return, // `cargo miri setup` stops here.
MiriCommand::Clean => unreachable!(),
};
let metadata = get_cargo_metadata();
let mut cmd = cargo();
cmd.arg(&cargo_cmd);
// In nextest we have to also forward the main `verb`.
if cargo_cmd == "nextest" {
cmd.arg(
args.next()
.unwrap_or_else(|| show_error!("`cargo miri nextest` expects a verb (e.g. `run`)")),
);
}
// We set the following flags *before* forwarding more arguments.
// This is needed to fix <https://github.com/rust-lang/miri/issues/2829>: cargo will stop
// interpreting things as flags when it sees the first positional argument.
// Make sure the build target is explicitly set.
// This is needed to make the `target.runner` settings do something,
// and it later helps us detect which crates are proc-macro/build-script
// (host crates) and which crates are needed for the program itself.
if let Some(target_flag) = target_flag {
cmd.arg("--target");
cmd.arg(target_flag);
}
// Set ourselves as runner for al binaries invoked by cargo.
// We use `all()` since `true` is not a thing in cfg-lang, but the empty conjunction is. :)
let cargo_miri_path_for_toml = escape_for_toml(&cargo_miri_path);
cmd.arg("--config")
.arg(format!("target.'cfg(all())'.runner=[{cargo_miri_path_for_toml}, 'runner']"));
// Set `--target-dir` to `miri` inside the original target directory.
let target_dir = get_target_dir(&metadata);
cmd.arg("--target-dir").arg(target_dir);
// Store many-seeds argument.
let mut many_seeds = None;
// *After* we set all the flags that need setting, forward everything else. Make sure to skip
// `--target-dir` (which would otherwise be set twice) and `--many-seeds` (which is our flag, not cargo's).
for arg in
ArgSplitFlagValue::from_string_iter(&mut args, "--target-dir").filter_map(Result::err)
{
if arg == "--many-seeds" {
many_seeds = Some(DEFAULT_MANY_SEEDS.to_owned());
} else if let Some(val) = arg.strip_prefix("--many-seeds=") {
many_seeds = Some(val.to_owned());
} else {
cmd.arg(arg);
}
}
// Forward all further arguments after `--` (not consumed by `ArgSplitFlagValue`) to cargo.
cmd.args(args);
// Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation,
// i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish
// the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.)
if env::var_os("RUSTC_WRAPPER").is_some() {
println!(
"WARNING: Ignoring `RUSTC_WRAPPER` environment variable, Miri does not support wrapping."
);
}
cmd.env("RUSTC_WRAPPER", &cargo_miri_path);
// There's also RUSTC_WORKSPACE_WRAPPER, which gets in the way of our own wrapping.
if env::var_os("RUSTC_WORKSPACE_WRAPPER").is_some() {
println!(
"WARNING: Ignoring `RUSTC_WORKSPACE_WRAPPER` environment variable, Miri does not support wrapping."
);
}
cmd.env_remove("RUSTC_WORKSPACE_WRAPPER");
// We are going to invoke `MIRI` for everything, not `RUSTC`.
if env::var_os("RUSTC").is_some() && env::var_os("MIRI").is_none() {
println!(
"WARNING: Ignoring `RUSTC` environment variable; set `MIRI` if you want to control the binary used as the driver."
);
}
// Ideally we would set RUSTC to some non-existent path, so we can be sure our wrapping is
// always applied. However, buggy build scripts (https://github.com/eyre-rs/eyre/issues/84) and
// also cargo (https://github.com/rust-lang/cargo/issues/10885) will invoke `rustc` even when
// `RUSTC_WRAPPER` is set, bypassing the wrapper. To make sure everything is coherent, we want
// that to be the Miri driver, but acting as rustc, in host mode.
//
// In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc
// or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that
// there would be a collision with other invocations of cargo-miri (as rustdoc or as runner). We
// explicitly do this even if RUSTC_STAGE is set, since for these builds we do *not* want the
// bootstrap `rustc` thing in our way! Instead, we have MIRI_HOST_SYSROOT to use for host
// builds.
cmd.env("RUSTC", fs::canonicalize(find_miri()).unwrap());
// In case we get invoked as RUSTC without the wrapper, let's be a host rustc. This makes no
// sense for cross-interpretation situations, but without the wrapper, this will use the host
// sysroot, so asking it to behave like a target build makes even less sense.
cmd.env("MIRI_BE_RUSTC", "host"); // we better remember to *unset* this in the other phases!
// Set rustdoc to us as well, so we can run doctests.
if let Some(orig_rustdoc) = env::var_os("RUSTDOC") {
cmd.env("MIRI_ORIG_RUSTDOC", orig_rustdoc);
}
cmd.env("RUSTDOC", &cargo_miri_path);
// Forward some crucial information to our own re-invocations.
cmd.env("MIRI_SYSROOT", miri_sysroot);
cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
if let Some(many_seeds) = many_seeds {
cmd.env("MIRI_MANY_SEEDS", many_seeds);
}
if verbose > 0 {
cmd.env("MIRI_VERBOSE", verbose.to_string()); // This makes the other phases verbose.
}
// Run cargo.
debug_cmd("[cargo-miri cargo]", verbose, &cmd);
exec(cmd)
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum RustcPhase {
/// `rustc` called during sysroot build.
Setup,
/// `rustc` called by `cargo` for regular build.
Build,
/// `rustc` called by `rustdoc` for doctest.
Rustdoc,
}
pub fn phase_rustc(mut args: impl Iterator<Item = String>, phase: RustcPhase) {
/// Determines if we are being invoked (as rustc) to build a crate for
/// the "target" architecture, in contrast to the "host" architecture.
/// Host crates are for build scripts and proc macros and still need to
/// be built like normal; target crates need to be built for or interpreted
/// by Miri.
///
/// Currently, we detect this by checking for "--target=", which is
/// never set for host crates. This matches what rustc bootstrap does,
/// which hopefully makes it "reliable enough". This relies on us always
/// invoking cargo itself with `--target`, which `in_cargo_miri` ensures.
fn is_target_crate() -> bool {
get_arg_flag_value("--target").is_some()
}
/// Returns whether or not Cargo invoked the wrapper (this binary) to compile
/// the final, binary crate (either a test for 'cargo test', or a binary for 'cargo run')
/// Cargo does not give us this information directly, so we need to check
/// various command-line flags.
fn is_runnable_crate() -> bool {
// Determine whether this is cargo invoking rustc to get some infos. Ideally we'd check "is
// there a filename passed to rustc", but that's very hard as we would have to know whether
// e.g. `--print foo` is a booolean flag `--print` followed by filename `foo` or equivalent
// to `--print=foo`. So instead we use this more fragile approach of detecting the presence
// of a "query" flag rather than the absence of a filename.
let info_query = get_arg_flag_value("--print").is_some() || has_arg_flag("-vV");
if info_query {
// Nothing to run.
return false;
}
let is_bin = get_arg_flag_value("--crate-type").as_deref().unwrap_or("bin") == "bin";
let is_test = has_arg_flag("--test");
is_bin || is_test
}
fn out_filenames() -> Vec<PathBuf> {
if let Some(out_file) = get_arg_flag_value("-o") {
// `-o` has precedence over `--out-dir`.
vec![PathBuf::from(out_file)]
} else {
let out_dir = get_arg_flag_value("--out-dir").unwrap_or_default();
let path = PathBuf::from(out_dir);
// Ask rustc for the filename (since that is target-dependent).
let mut rustc = miri_for_host(); // sysroot doesn't matter for this so we just use the host
rustc.arg("--print").arg("file-names");
for flag in ["--crate-name", "--crate-type", "--target"] {
for val in get_arg_flag_values(flag) {
rustc.arg(flag).arg(val);
}
}
// This is technically passed as `-C extra-filename=...`, but the prefix seems unique
// enough... (and cargo passes this before the filename so it should be unique)
if let Some(extra) = get_arg_flag_value("extra-filename") {
rustc.arg("-C").arg(format!("extra-filename={extra}"));
}
rustc.arg("-");
let output = rustc.output().expect("cannot run rustc to determine file name");
assert!(
output.status.success(),
"rustc failed when determining file name:\n{output:?}"
);
let output =
String::from_utf8(output.stdout).expect("rustc returned non-UTF-8 filename");
output.lines().filter(|l| !l.is_empty()).map(|l| path.join(l)).collect()
}
}
let verbose = env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
let target_crate = is_target_crate();
let store_json = |info: CrateRunInfo| {
if get_arg_flag_value("--emit").unwrap_or_default().split(',').any(|e| e == "dep-info") {
// Create a stub .d file to stop Cargo from "rebuilding" the crate:
// https://github.com/rust-lang/miri/issues/1724#issuecomment-787115693
// As we store a JSON file instead of building the crate here, an empty file is fine.
let dep_info_name = format!(
"{}/{}{}.d",
get_arg_flag_value("--out-dir").unwrap(),
get_arg_flag_value("--crate-name").unwrap(),
get_arg_flag_value("extra-filename").unwrap_or_default(),
);
if verbose > 0 {
eprintln!("[cargo-miri rustc] writing stub dep-info to `{dep_info_name}`");
}
File::create(dep_info_name).expect("failed to create fake .d file");
}
for filename in out_filenames() {
if verbose > 0 {
eprintln!("[cargo-miri rustc] writing run info to `{}`", filename.display());
}
info.store(&filename);
}
};
let runnable_crate = is_runnable_crate();
if runnable_crate && target_crate {
assert!(
phase != RustcPhase::Setup,
"there should be no interpretation during sysroot build"
);
let inside_rustdoc = phase == RustcPhase::Rustdoc;
// This is the binary or test crate that we want to interpret under Miri.
// But we cannot run it here, as cargo invoked us as a compiler -- our stdin and stdout are not
// like we want them.
// Instead of compiling, we write JSON into the output file with all the relevant command-line flags
// and environment variables; this is used when cargo calls us again in the CARGO_TARGET_RUNNER phase.
let env = CrateRunEnv::collect(args, inside_rustdoc);
store_json(CrateRunInfo::RunWith(env.clone()));
// Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
// just creating the JSON file is not enough: we need to detect syntax errors,
// so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
if inside_rustdoc {
let mut cmd = miri();
// Ensure --emit argument for a check-only build is present.
if let Some(val) =
ArgFlagValueIter::from_str_iter(env.args.iter().map(|s| s as &str), "--emit").next()
{
// For `no_run` tests, rustdoc passes a `--emit` flag; make sure it has the right shape.
assert_eq!(val, "metadata");
} else {
// For all other kinds of tests, we can just add our flag.
cmd.arg("--emit=metadata");
}
// Alter the `-o` parameter so that it does not overwrite the JSON file we stored above.
let mut args = env.args;
let mut out_filename = None;
for i in 0..args.len() {
if args[i] == "-o" {
out_filename = Some(args[i + 1].clone());
args[i + 1].push_str(".miri");
}
}
let out_filename = out_filename.expect("rustdoc must pass `-o`");
cmd.args(&args);
cmd.env("MIRI_BE_RUSTC", "target");
if verbose > 0 {
eprintln!(
"[cargo-miri rustc inside rustdoc] captured input:\n{}",
std::str::from_utf8(&env.stdin).unwrap()
);
eprintln!("[cargo-miri rustc inside rustdoc] going to run:\n{cmd:?}");
}
exec_with_pipe(cmd, &env.stdin, format!("{out_filename}.stdin"));
}
return;
}
if runnable_crate && get_arg_flag_values("--extern").any(|krate| krate == "proc_macro") {
// This is a "runnable" `proc-macro` crate (unit tests). We do not support
// interpreting that under Miri now, so we write a JSON file to (display a
// helpful message and) skip it in the runner phase.
store_json(CrateRunInfo::SkipProcMacroTest);
return;
}
let mut cmd = miri();
let mut emit_link_hack = false;
// Arguments are treated very differently depending on whether this crate is
// for interpretation by Miri, or for use by a build script / proc macro.
if target_crate {
if phase != RustcPhase::Setup {
// Set the sysroot -- except during setup, where we don't have an existing sysroot yet
// and where the bootstrap wrapper adds its own `--sysroot` flag so we can't set ours.
cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
}
// Forward arguments, but patched.
let emit_flag = "--emit";
// This hack helps bootstrap run standard library tests in Miri. The issue is as follows:
// when running `cargo miri test` on libcore, cargo builds a local copy of core and makes it
// a dependency of the integration test crate. This copy duplicates all the lang items, so
// the build fails. (Regular testing avoids this because the sysroot is a literal copy of
// what `cargo build` produces, but since Miri builds its own sysroot this does not work for
// us.) So we need to make it so that the locally built libcore contains all the items from
// `core`, but does not re-define them -- we want to replace the entire crate but a
// re-export of the sysroot crate. We do this by swapping out the source file: if
// `MIRI_REPLACE_LIBRS_IF_NOT_TEST` is set and we are building a `lib.rs` file, and a
// `lib.miri.rs` file exists in the same folder, we build that instead. But crucially we
// only do that for the library, not the unit test crate (which would be runnable) or
// rustdoc (which would have a different `phase`).
let replace_librs = env::var_os("MIRI_REPLACE_LIBRS_IF_NOT_TEST").is_some()
&& !runnable_crate
&& phase == RustcPhase::Build;
while let Some(arg) = args.next() {
// Patch `--emit`: remove "link" from "--emit" to make this a check-only build.
if let Some(val) = arg.strip_prefix(emit_flag) {
// Patch this argument. First, extract its value.
let val =
val.strip_prefix('=').expect("`cargo` should pass `--emit=X` as one argument");
let mut val: Vec<_> = val.split(',').collect();
// Now make sure "link" is not in there, but "metadata" is.
if let Some(i) = val.iter().position(|&s| s == "link") {
emit_link_hack = true;
val.remove(i);
if !val.iter().any(|&s| s == "metadata") {
val.push("metadata");
}
}
cmd.arg(format!("{emit_flag}={}", val.join(",")));
continue;
}
// Patch `--extern` filenames, since Cargo sometimes passes stub `.rlib` files:
// https://github.com/rust-lang/miri/issues/1705
if arg == "--extern" {
forward_patched_extern_arg(&mut args, &mut cmd);
continue;
}
// If the REPLACE_LIBRS hack is enabled and we are building a `lib.rs` file, and a
// `lib.miri.rs` file exists, then build that instead.
if replace_librs {
let path = Path::new(&arg);
if path.file_name().is_some_and(|f| f == "lib.rs") && path.is_file() {
let miri_rs = Path::new(&arg).with_extension("miri.rs");
if miri_rs.is_file() {
if verbose > 0 {
eprintln!("Performing REPLACE_LIBRS hack: {arg:?} -> {miri_rs:?}");
}
cmd.arg(miri_rs);
continue;
}
}
}
// Fallback: just propagate the argument.
cmd.arg(arg);
}
// During setup, patch the panic runtime for `libpanic_abort` (mirroring what bootstrap usually does).
if phase == RustcPhase::Setup
&& get_arg_flag_value("--crate-name").as_deref() == Some("panic_abort")
{
cmd.arg("-C").arg("panic=abort");
}
} else {
// This is a host crate.
// When we're running `cargo-miri` from `x.py` we need to pass the sysroot explicitly
// due to bootstrap complications.
if let Some(sysroot) = env::var_os("MIRI_HOST_SYSROOT") {
cmd.arg("--sysroot").arg(sysroot);
}
// Forward everything.
cmd.args(args);
}
// We want to compile, not interpret. We still use Miri to make sure the compiler version etc
// are the exact same as what is used for interpretation.
// MIRI_DEFAULT_ARGS should not be used to build host crates, hence setting "target" or "host"
// as the value here to help Miri differentiate them.
cmd.env("MIRI_BE_RUSTC", if target_crate { "target" } else { "host" });
// Run it.
if verbose > 0 {
eprintln!("[cargo-miri rustc] target_crate={target_crate} runnable_crate={runnable_crate}");
}
// Create a stub .rlib file if "link" was requested by cargo.
// This is necessary to prevent cargo from doing rebuilds all the time.
if emit_link_hack {
for filename in out_filenames() {
if verbose > 0 {
eprintln!("[cargo-miri rustc] creating fake lib file at `{}`", filename.display());
}
File::create(filename).expect("failed to create fake lib file");
}
}
debug_cmd("[cargo-miri rustc]", verbose, &cmd);
exec(cmd);
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum RunnerPhase {
/// `cargo` is running a binary
Cargo,
/// `rustdoc` is running a binary
Rustdoc,
}
pub fn phase_runner(mut binary_args: impl Iterator<Item = String>, phase: RunnerPhase) {
let verbose = env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
let binary = binary_args.next().unwrap();
let file = File::open(&binary)
.unwrap_or_else(|_| show_error!(
"file {:?} not found or `cargo-miri` invoked incorrectly; please only invoke this binary through `cargo miri`", binary
));
let file = BufReader::new(file);
let binary_args = binary_args.collect::<Vec<_>>();
let info = serde_json::from_reader(file).unwrap_or_else(|_| {
show_error!("file {:?} contains outdated or invalid JSON; try `cargo clean`", binary)
});
let info = match info {
CrateRunInfo::RunWith(info) => info,
CrateRunInfo::SkipProcMacroTest => {
eprintln!(
"Running unit tests of `proc-macro` crates is not currently supported by Miri."
);
return;
}
};
let many_seeds = env::var("MIRI_MANY_SEEDS");
run_many_seeds(many_seeds.ok(), |seed| {
let mut cmd = miri();
// Set missing env vars. We prefer build-time env vars over run-time ones; see
// <https://github.com/rust-lang/miri/issues/1661> for the kind of issue that fixes.
for (name, val) in &info.env {
// `CARGO_MAKEFLAGS` contains information about how to reach the jobserver, but by the time
// the program is being run, that jobserver no longer exists (cargo only runs the jobserver
// for the build portion of `cargo run`/`cargo test`). Hence we shouldn't forward this.
// Also see <https://github.com/rust-lang/rust/pull/113730>.
if name == "CARGO_MAKEFLAGS" {
continue;
}
if let Some(old_val) = env::var_os(name) {
if *old_val == *val {
// This one did not actually change, no need to re-set it.
// (This keeps the `debug_cmd` below more manageable.)
continue;
} else if verbose > 0 {
eprintln!(
"[cargo-miri runner] Overwriting run-time env var {name:?}={old_val:?} with build-time value {val:?}"
);
}
}
cmd.env(name, val);
}
if phase != RunnerPhase::Rustdoc {
// Set the sysroot. Not necessary in rustdoc, where we already set the sysroot in
// `phase_rustdoc`. rustdoc will forward that flag when invoking rustc (i.e., us), so the
// flag is present in `info.args`.
cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
}
// Forward rustc arguments.
// We need to patch "--extern" filenames because we forced a check-only
// build without cargo knowing about that: replace `.rlib` suffix by
// `.rmeta`.
// We also need to remove `--error-format` as cargo specifies that to be JSON,
// but when we run here, cargo does not interpret the JSON any more. `--json`
// then also needs to be dropped.
let mut args = info.args.iter();
while let Some(arg) = args.next() {
if arg == "--extern" {
forward_patched_extern_arg(&mut (&mut args).cloned(), &mut cmd);
} else if let Some(suffix) = arg.strip_prefix("--error-format") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else if let Some(suffix) = arg.strip_prefix("--json") {
assert!(suffix.starts_with('='));
// Drop this argument.
} else {
cmd.arg(arg);
}
}
// Respect `MIRIFLAGS`.
if let Ok(a) = env::var("MIRIFLAGS") {
let args = flagsplit(&a);
cmd.args(args);
}
// Set the current seed.
if let Some(seed) = seed {
eprintln!("Trying seed: {seed}");
cmd.arg(format!("-Zmiri-seed={seed}"));
}
// Then pass binary arguments.
cmd.arg("--");
cmd.args(&binary_args);
// Make sure we use the build-time working directory for interpreting Miri/rustc arguments.
// But then we need to switch to the run-time one, which we instruct Miri to do by setting `MIRI_CWD`.
cmd.current_dir(&info.current_dir);
cmd.env("MIRI_CWD", env::current_dir().unwrap());
// Run it.
debug_cmd("[cargo-miri runner]", verbose, &cmd);
match phase {
RunnerPhase::Rustdoc => {
cmd.stdin(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("failed to spawn process");
let child_stdin = child.stdin.take().unwrap();
// Write stdin in a background thread, as it may block.
let exit_status = thread::scope(|s| {
s.spawn(|| {
let mut child_stdin = child_stdin;
// Ignore failure, it is most likely due to the process having terminated.
let _ = child_stdin.write_all(&info.stdin);
});
child.wait().expect("failed to run command")
});
if !exit_status.success() {
std::process::exit(exit_status.code().unwrap_or(-1));
}
}
RunnerPhase::Cargo => {
let exit_status = cmd.status().expect("failed to run command");
if !exit_status.success() {
std::process::exit(exit_status.code().unwrap_or(-1));
}
}
}
});
}
pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
let verbose = env::var("MIRI_VERBOSE")
.map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
// phase_cargo_miri sets the RUSTDOC env var to ourselves, and puts a backup
// of the old value into MIRI_ORIG_RUSTDOC. So that's what we have to invoke now.
let rustdoc = env::var("MIRI_ORIG_RUSTDOC").unwrap_or("rustdoc".to_string());
let mut cmd = Command::new(rustdoc);
while let Some(arg) = args.next() {
if arg == "--extern" {
// Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
forward_patched_extern_arg(&mut args, &mut cmd);
} else if arg == "--runtool" {
// An existing --runtool flag indicates cargo is running in cross-target mode, which we don't support.
// Note that this is only passed when cargo is run with the unstable -Zdoctest-xcompile flag;
// otherwise, we won't be called as rustdoc at all.
show_error!("cross-interpreting doctests is not currently supported by Miri.");
} else {
cmd.arg(arg);
}
}
// Doctests of `proc-macro` crates (and their dependencies) are always built for the host,
// so we are not able to run them in Miri.
if get_arg_flag_values("--crate-type").any(|crate_type| crate_type == "proc-macro") {
eprintln!("Running doctests of `proc-macro` crates is not currently supported by Miri.");
return;
}
// For each doctest, rustdoc starts two child processes: first the test is compiled,
// then the produced executable is invoked. We want to reroute both of these to cargo-miri,
// such that the first time we'll enter phase_cargo_rustc, and phase_cargo_runner second.
//
// rustdoc invokes the test-builder by forwarding most of its own arguments, which makes
// it difficult to determine when phase_cargo_rustc should run instead of phase_cargo_rustdoc.
// Furthermore, the test code is passed via stdin, rather than a temporary file, so we need
// to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag:
cmd.env("MIRI_CALLED_FROM_RUSTDOC", "1");
// The `--test-builder` and `--runtool` arguments are unstable rustdoc features,
// which are disabled by default. We first need to enable them explicitly:
cmd.arg("-Zunstable-options");
// rustdoc needs to know the right sysroot.
cmd.arg("--sysroot").arg(env::var_os("MIRI_SYSROOT").unwrap());
// make sure the 'miri' flag is set for rustdoc
cmd.arg("--cfg").arg("miri");
// Make rustdoc call us back.
let cargo_miri_path = env::current_exe().expect("current executable path invalid");
cmd.arg("--test-builder").arg(&cargo_miri_path); // invoked by forwarding most arguments
cmd.arg("--runtool").arg(&cargo_miri_path); // invoked with just a single path argument
debug_cmd("[cargo-miri rustdoc]", verbose, &cmd);
exec(cmd)
}