@@ -615,6 +615,7 @@ fn phase_cargo_rustc(args: env::Args) {
615615 info. store ( & out_filename ( "" , ".exe" ) ) ;
616616
617617 // 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,
618619 // so we need to run Miri with `MIRI_BE_RUSTC` for a check-only build.
619620 if std:: env:: var_os ( "MIRI_CALLED_FROM_RUSTDOC" ) . is_some ( ) {
620621 let mut cmd = miri ( ) ;
@@ -626,8 +627,12 @@ fn phase_cargo_rustc(args: env::Args) {
626627 cmd. arg ( "--sysroot" ) . arg ( sysroot) ;
627628 }
628629
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 {
631636 cmd. arg ( "--emit=dep-info,metadata" ) ;
632637 }
633638
@@ -703,6 +708,18 @@ fn phase_cargo_rustc(args: env::Args) {
703708 }
704709}
705710
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+
706723fn phase_cargo_runner ( binary : & Path , binary_args : env:: Args ) {
707724 let verbose = std:: env:: var_os ( "MIRI_VERBOSE" ) . is_some ( ) ;
708725
@@ -740,16 +757,7 @@ fn phase_cargo_runner(binary: &Path, binary_args: env::Args) {
740757 let json_flag = "--json" ;
741758 while let Some ( arg) = args. next ( ) {
742759 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) ;
753761 } else if arg. starts_with ( error_format_flag) {
754762 let suffix = & arg[ error_format_flag. len ( ) ..] ;
755763 assert ! ( suffix. starts_with( '=' ) ) ;
@@ -806,38 +814,41 @@ fn phase_cargo_rustdoc(fst_arg: &str, mut args: env::Args) {
806814 // just default to a straight-forward invocation for now:
807815 let mut cmd = Command :: new ( OsString :: from ( "rustdoc" ) ) ;
808816
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.
809821 let extern_flag = "--extern" ;
810822 assert ! ( fst_arg != extern_flag) ;
811823 cmd. arg ( fst_arg) ;
812824
813825 // Patch --extern arguments to use *.rmeta files, since phase_cargo_rustc only creates stub *.rlib files.
814826 while let Some ( arg) = args. next ( ) {
815827 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) ;
826829 } else {
827830 cmd. arg ( arg) ;
828831 }
829832 }
830833
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:
831846 cmd. arg ( "-Z" ) . arg ( "unstable-options" ) ;
832847
833848 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
836851
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-
841852 if verbose {
842853 eprintln ! ( "[cargo-miri rustdoc] {:?}" , cmd) ;
843854 }
@@ -861,10 +872,10 @@ fn main() {
861872 // The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc
862873 // by the arguments alone, and we can't take from the args iterator in this case.
863874 // 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 {
866877 // ...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` ;
868879 // since we don't specify any runtool-args, and rustdoc supplies multiple arguments to
869880 // the test-builder unconditionally, we can just check the number of remaining arguments:
870881 if args. len ( ) == 1 {
@@ -873,7 +884,7 @@ fn main() {
873884 if binary. exists ( ) {
874885 phase_cargo_runner ( binary, args) ;
875886 } 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) ) ;
877888 }
878889 } else {
879890 phase_cargo_rustc ( args) ;
0 commit comments