@@ -615,6 +615,7 @@ fn phase_cargo_rustc(args: env::Args) {
615
615
info. store ( & out_filename ( "" , ".exe" ) ) ;
616
616
617
617
// Rustdoc expects us to exit with an error code if the test is marked as `compile_fail`,
618
+ // just creating the JSON file is not enough: we need to detect syntax errors,
618
619
// so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
619
620
if std:: env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) {
620
621
let mut cmd = miri ( ) ;
@@ -626,8 +627,12 @@ fn phase_cargo_rustc(args: env::Args) {
626
627
cmd. arg ( "--sysroot" ) . arg ( sysroot) ;
627
628
}
628
629
629
- // don't go into "code generation" (i.e. validation)
630
- if info. args . iter ( ) . position ( |arg| arg. starts_with ( "--emit=" ) ) . is_none ( ) {
630
+ // ensure --emit argument for a check-only build is present
631
+ if let Some ( i) = info. args . iter ( ) . position ( |arg| arg. starts_with ( "--emit=" ) ) {
632
+ // We need to make sure we're not producing a binary that overwrites the JSON file.
633
+ // rustdoc should only ever pass an --emit=metadata argument for tests marked as `no_run`:
634
+ assert_eq ! ( info. args[ i] , "--emit=metadata" ) ;
635
+ } else {
631
636
cmd. arg ( "--emit=dep-info,metadata" ) ;
632
637
}
633
638
@@ -703,6 +708,18 @@ fn phase_cargo_rustc(args: env::Args) {
703
708
}
704
709
}
705
710
711
+ fn try_forward_patched_extern_arg ( args : & mut impl Iterator < Item = String > , cmd : & mut Command ) {
712
+ cmd. arg ( "--extern" ) ; // always forward flag, but adjust filename:
713
+ let path = args. next ( ) . expect ( "`--extern` should be followed by a filename" ) ;
714
+ if let Some ( lib) = path. strip_suffix ( ".rlib" ) {
715
+ // If this is an rlib, make it an rmeta.
716
+ cmd. arg ( format ! ( "{}.rmeta" , lib) ) ;
717
+ } else {
718
+ // Some other extern file (e.g. a `.so`). Forward unchanged.
719
+ cmd. arg ( path) ;
720
+ }
721
+ }
722
+
706
723
fn phase_cargo_runner ( binary : & Path , binary_args : env:: Args ) {
707
724
let verbose = std:: env:: var_os ( "MIRI_VERBOSE" ) . is_some ( ) ;
708
725
@@ -740,16 +757,7 @@ fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
740
757
let json_flag = "--json" ;
741
758
while let Some ( arg) = args. next ( ) {
742
759
if arg == extern_flag {
743
- cmd. arg ( extern_flag) ; // always forward flag, but adjust filename
744
- // `--extern` is always passed as a separate argument by cargo.
745
- let next_arg = args. next ( ) . expect ( "`--extern` should be followed by a filename" ) ;
746
- if let Some ( next_lib) = next_arg. strip_suffix ( ".rlib" ) {
747
- // If this is an rlib, make it an rmeta.
748
- cmd. arg ( format ! ( "{}.rmeta" , next_lib) ) ;
749
- } else {
750
- // Some other extern file (e.g., a `.so`). Forward unchanged.
751
- cmd. arg ( next_arg) ;
752
- }
760
+ try_forward_patched_extern_arg ( & mut args, & mut cmd) ;
753
761
} else if arg. starts_with ( error_format_flag) {
754
762
let suffix = & arg[ error_format_flag. len ( ) ..] ;
755
763
assert ! ( suffix. starts_with( '=' ) ) ;
@@ -806,38 +814,41 @@ fn phase_cargo_rustdoc(fst_arg: &str, mut args: env::Args) {
806
814
// just default to a straight-forward invocation for now:
807
815
let mut cmd = Command :: new ( OsString :: from ( "rustdoc" ) ) ;
808
816
817
+ // Because of the way the main function is structured, we have to take the first argument spearately
818
+ // from the rest; to simplify the following argument patching loop, we'll just skip that one.
819
+ // This is fine for now, because cargo will never pass an --extern argument in the first position,
820
+ // but we should defensively assert that this will work.
809
821
let extern_flag = "--extern" ;
810
822
assert ! ( fst_arg != extern_flag) ;
811
823
cmd. arg ( fst_arg) ;
812
824
813
825
// Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
814
826
while let Some ( arg) = args. next ( ) {
815
827
if arg == extern_flag {
816
- cmd. arg ( extern_flag) ; // always forward flag, but adjust filename
817
- // `--extern` is always passed as a separate argument by cargo.
818
- let next_arg = args. next ( ) . expect ( "`--extern` should be followed by a filename" ) ;
819
- if let Some ( next_lib) = next_arg. strip_suffix ( ".rlib" ) {
820
- // If this is an rlib, make it an rmeta.
821
- cmd. arg ( format ! ( "{}.rmeta" , next_lib) ) ;
822
- } else {
823
- // Some other extern file (e.g., a `.so`). Forward unchanged.
824
- cmd. arg ( next_arg) ;
825
- }
828
+ try_forward_patched_extern_arg ( & mut args, & mut cmd) ;
826
829
} else {
827
830
cmd. arg ( arg) ;
828
831
}
829
832
}
830
833
834
+ // For each doc-test, rustdoc starts two child processes: first the test is compiled,
835
+ // then the produced executable is invoked. We want to reroute both of these to cargo-miri,
836
+ // such that the first time we'll enter phase_cargo_rustc, and phase_cargo_runner second.
837
+ //
838
+ // rustdoc invokes the test-builder by forwarding most of its own arguments, which makes
839
+ // it difficult to determine when phase_cargo_rustc should run instead of phase_cargo_rustdoc.
840
+ // Furthermore, the test code is passed via stdin, rather than a temporary file, so we need
841
+ // to let phase_cargo_rustc know to expect that. We'll use this environment variable as a flag:
842
+ cmd. env ( "MIRI_CALLED_FROM_RUSTDOC" , "1" ) ;
843
+
844
+ // The `--test-builder` and `--runtool` arguments are unstable rustdoc features,
845
+ // which are disabled by default. We first need to enable them explicitly:
831
846
cmd. arg ( "-Z" ) . arg ( "unstable-options" ) ;
832
847
833
848
let cargo_miri_path = std:: env:: current_exe ( ) . expect ( "current executable path invalid" ) ;
834
- cmd. arg ( "--test-builder" ) . arg ( & cargo_miri_path) ;
835
- cmd. arg ( "--runtool" ) . arg ( & cargo_miri_path) ;
849
+ cmd. arg ( "--test-builder" ) . arg ( & cargo_miri_path) ; // invoked by forwarding most arguments
850
+ cmd. arg ( "--runtool" ) . arg ( & cargo_miri_path) ; // invoked with just a single path argument
836
851
837
- // rustdoc passes generated code to rustc via stdin, rather than a temporary file,
838
- // so we need to let the coming invocations know to expect that
839
- cmd. env ( "MIRI_CALLED_FROM_RUSTDOC" , "1" ) ;
840
-
841
852
if verbose {
842
853
eprintln ! ( "[cargo-miri rustdoc] {:?}" , cmd) ;
843
854
}
@@ -861,10 +872,10 @@ fn main() {
861
872
// The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc
862
873
// by the arguments alone, and we can't take from the args iterator in this case.
863
874
// phase_cargo_rustdoc sets this environment variable to let us disambiguate here
864
- let invoked_as_rustc_from_rustdoc = env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) ;
865
- if invoked_as_rustc_from_rustdoc {
875
+ let invoked_by_rustdoc = env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) ;
876
+ if invoked_by_rustdoc {
866
877
// ...however, we then also see this variable when rustdoc invokes us as the testrunner!
867
- // The runner is invoked as `$runtool ($runtool-arg)* output_file;
878
+ // The runner is invoked as `$runtool ($runtool-arg)* output_file` ;
868
879
// since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
869
880
// the test-builder unconditionally, we can just check the number of remaining arguments:
870
881
if args. len ( ) == 1 {
@@ -873,7 +884,7 @@ fn main() {
873
884
if binary. exists ( ) {
874
885
phase_cargo_runner ( binary, args) ;
875
886
} else {
876
- show_error ( format ! ( "`cargo-miri` called with non-existing path argument `{}`; please invoke this binary through `cargo miri`" , arg) ) ;
887
+ show_error ( format ! ( "`cargo-miri` called with non-existing path argument `{}` in rustdoc mode ; please invoke this binary through `cargo miri`" , arg) ) ;
877
888
}
878
889
} else {
879
890
phase_cargo_rustc ( args) ;
0 commit comments