1 #![feature(rustc_private, bool_to_option, stmt_expr_attributes)]
2 
3 extern crate rustc_data_structures;
4 extern crate rustc_driver;
5 extern crate rustc_errors;
6 extern crate rustc_hir;
7 extern crate rustc_interface;
8 extern crate rustc_metadata;
9 extern crate rustc_middle;
10 extern crate rustc_session;
11 
12 use std::convert::TryFrom;
13 use std::env;
14 use std::num::NonZeroU64;
15 use std::path::PathBuf;
16 use std::str::FromStr;
17 
18 use hex::FromHexError;
19 use log::debug;
20 
21 use rustc_data_structures::sync::Lrc;
22 use rustc_driver::Compilation;
23 use rustc_errors::emitter::{ColorConfig, HumanReadableErrorType};
24 use rustc_hir::{self as hir, def_id::LOCAL_CRATE, Node};
25 use rustc_interface::interface::Config;
26 use rustc_middle::{
27     middle::exported_symbols::{ExportedSymbol, SymbolExportLevel},
28     ty::{query::ExternProviders, TyCtxt},
29 };
30 use rustc_session::{config::ErrorOutputType, search_paths::PathKind, CtfeBacktrace};
31 
32 struct MiriCompilerCalls {
33     miri_config: miri::MiriConfig,
34 }
35 
36 impl rustc_driver::Callbacks for MiriCompilerCalls {
config(&mut self, config: &mut Config)37     fn config(&mut self, config: &mut Config) {
38         config.override_queries = Some(|_, _, external_providers| {
39             external_providers.used_crate_source = |tcx, cnum| {
40                 let mut providers = ExternProviders::default();
41                 rustc_metadata::provide_extern(&mut providers);
42                 let mut crate_source = (providers.used_crate_source)(tcx, cnum);
43                 // HACK: rustc will emit "crate ... required to be available in rlib format, but
44                 // was not found in this form" errors once we use `tcx.dependency_formats()` if
45                 // there's no rlib provided, so setting a dummy path here to workaround those errors.
46                 Lrc::make_mut(&mut crate_source).rlib = Some((PathBuf::new(), PathKind::All));
47                 crate_source
48             };
49         });
50     }
51 
after_analysis<'tcx>( &mut self, compiler: &rustc_interface::interface::Compiler, queries: &'tcx rustc_interface::Queries<'tcx>, ) -> Compilation52     fn after_analysis<'tcx>(
53         &mut self,
54         compiler: &rustc_interface::interface::Compiler,
55         queries: &'tcx rustc_interface::Queries<'tcx>,
56     ) -> Compilation {
57         compiler.session().abort_if_errors();
58 
59         queries.global_ctxt().unwrap().peek_mut().enter(|tcx| {
60             init_late_loggers(tcx);
61             let (entry_def_id, entry_type) = if let Some(entry_def) = tcx.entry_fn(()) {
62                 entry_def
63             } else {
64                 let output_ty = ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(
65                     ColorConfig::Auto,
66                 ));
67                 rustc_session::early_error(
68                     output_ty,
69                     "miri can only run programs that have a main function",
70                 );
71             };
72             let mut config = self.miri_config.clone();
73 
74             // Add filename to `miri` arguments.
75             config.args.insert(0, compiler.input().filestem().to_string());
76 
77             // Adjust working directory for interpretation.
78             if let Some(cwd) = env::var_os("MIRI_CWD") {
79                 env::set_current_dir(cwd).unwrap();
80             }
81 
82             if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) {
83                 std::process::exit(
84                     i32::try_from(return_code).expect("Return value was too large!"),
85                 );
86             }
87         });
88 
89         compiler.session().abort_if_errors();
90 
91         Compilation::Stop
92     }
93 }
94 
95 struct MiriBeRustCompilerCalls {
96     target_crate: bool,
97 }
98 
99 impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
config(&mut self, config: &mut Config)100     fn config(&mut self, config: &mut Config) {
101         if config.opts.prints.is_empty() && self.target_crate {
102             // Queries overriden here affect the data stored in `rmeta` files of dependencies,
103             // which will be used later in non-`MIRI_BE_RUSTC` mode.
104             config.override_queries = Some(|_, local_providers, _| {
105                 // `exported_symbols()` provided by rustc always returns empty result if
106                 // `tcx.sess.opts.output_types.should_codegen()` is false.
107                 local_providers.exported_symbols = |tcx, cnum| {
108                     assert_eq!(cnum, LOCAL_CRATE);
109                     tcx.arena.alloc_from_iter(
110                         // This is based on:
111                         // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L62-L63
112                         // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L174
113                         tcx.reachable_set(()).iter().filter_map(|&local_def_id| {
114                             // Do the same filtering that rustc does:
115                             // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L84-L102
116                             // Otherwise it may cause unexpected behaviours and ICEs
117                             // (https://github.com/rust-lang/rust/issues/86261).
118                             let is_reachable_non_generic = matches!(
119                                 tcx.hir().get(tcx.hir().local_def_id_to_hir_id(local_def_id)),
120                                 Node::Item(&hir::Item {
121                                     kind: hir::ItemKind::Static(..) | hir::ItemKind::Fn(..),
122                                     ..
123                                 }) | Node::ImplItem(&hir::ImplItem {
124                                     kind: hir::ImplItemKind::Fn(..),
125                                     ..
126                                 })
127                                 if !tcx.generics_of(local_def_id).requires_monomorphization(tcx)
128                             );
129                             (is_reachable_non_generic
130                                 && tcx.codegen_fn_attrs(local_def_id).contains_extern_indicator())
131                             .then_some((
132                                 ExportedSymbol::NonGeneric(local_def_id.to_def_id()),
133                                 SymbolExportLevel::C,
134                             ))
135                         }),
136                     )
137                 }
138             });
139         }
140     }
141 }
142 
init_early_loggers()143 fn init_early_loggers() {
144     // Note that our `extern crate log` is *not* the same as rustc's; as a result, we have to
145     // initialize them both, and we always initialize `miri`'s first.
146     let env = env_logger::Env::new().filter("MIRI_LOG").write_style("MIRI_LOG_STYLE");
147     env_logger::init_from_env(env);
148     // We only initialize `rustc` if the env var is set (so the user asked for it).
149     // If it is not set, we avoid initializing now so that we can initialize
150     // later with our custom settings, and *not* log anything for what happens before
151     // `miri` gets started.
152     if env::var_os("RUSTC_LOG").is_some() {
153         rustc_driver::init_rustc_env_logger();
154     }
155 }
156 
init_late_loggers(tcx: TyCtxt<'_>)157 fn init_late_loggers(tcx: TyCtxt<'_>) {
158     // We initialize loggers right before we start evaluation. We overwrite the `RUSTC_LOG`
159     // env var if it is not set, control it based on `MIRI_LOG`.
160     // (FIXME: use `var_os`, but then we need to manually concatenate instead of `format!`.)
161     if let Ok(var) = env::var("MIRI_LOG") {
162         if env::var_os("RUSTC_LOG").is_none() {
163             // We try to be a bit clever here: if `MIRI_LOG` is just a single level
164             // used for everything, we only apply it to the parts of rustc that are
165             // CTFE-related. Otherwise, we use it verbatim for `RUSTC_LOG`.
166             // This way, if you set `MIRI_LOG=trace`, you get only the right parts of
167             // rustc traced, but you can also do `MIRI_LOG=miri=trace,rustc_const_eval::interpret=debug`.
168             if log::Level::from_str(&var).is_ok() {
169                 env::set_var(
170                     "RUSTC_LOG",
171                     &format!(
172                         "rustc_middle::mir::interpret={0},rustc_const_eval::interpret={0}",
173                         var
174                     ),
175                 );
176             } else {
177                 env::set_var("RUSTC_LOG", &var);
178             }
179             rustc_driver::init_rustc_env_logger();
180         }
181     }
182 
183     // If `MIRI_BACKTRACE` is set and `RUSTC_CTFE_BACKTRACE` is not, set `RUSTC_CTFE_BACKTRACE`.
184     // Do this late, so we ideally only apply this to Miri's errors.
185     if let Some(val) = env::var_os("MIRI_BACKTRACE") {
186         let ctfe_backtrace = match &*val.to_string_lossy() {
187             "immediate" => CtfeBacktrace::Immediate,
188             "0" => CtfeBacktrace::Disabled,
189             _ => CtfeBacktrace::Capture,
190         };
191         *tcx.sess.ctfe_backtrace.borrow_mut() = ctfe_backtrace;
192     }
193 }
194 
195 /// Returns the "default sysroot" that Miri will use if no `--sysroot` flag is set.
196 /// Should be a compile-time constant.
compile_time_sysroot() -> Option<String>197 fn compile_time_sysroot() -> Option<String> {
198     if option_env!("RUSTC_STAGE").is_some() {
199         // This is being built as part of rustc, and gets shipped with rustup.
200         // We can rely on the sysroot computation in librustc_session.
201         return None;
202     }
203     // For builds outside rustc, we need to ensure that we got a sysroot
204     // that gets used as a default.  The sysroot computation in librustc_session would
205     // end up somewhere in the build dir (see `get_or_default_sysroot`).
206     // Taken from PR <https://github.com/Manishearth/rust-clippy/pull/911>.
207     let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
208     let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
209     Some(match (home, toolchain) {
210         (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain),
211         _ =>
212             option_env!("RUST_SYSROOT")
213                 .expect(
214                     "To build Miri without rustup, set the `RUST_SYSROOT` env var at build time",
215                 )
216                 .to_owned(),
217     })
218 }
219 
220 /// Execute a compiler with the given CLI arguments and callbacks.
run_compiler( mut args: Vec<String>, callbacks: &mut (dyn rustc_driver::Callbacks + Send), insert_default_args: bool, ) -> !221 fn run_compiler(
222     mut args: Vec<String>,
223     callbacks: &mut (dyn rustc_driver::Callbacks + Send),
224     insert_default_args: bool,
225 ) -> ! {
226     // Make sure we use the right default sysroot. The default sysroot is wrong,
227     // because `get_or_default_sysroot` in `librustc_session` bases that on `current_exe`.
228     //
229     // Make sure we always call `compile_time_sysroot` as that also does some sanity-checks
230     // of the environment we were built in.
231     // FIXME: Ideally we'd turn a bad build env into a compile-time error via CTFE or so.
232     if let Some(sysroot) = compile_time_sysroot() {
233         let sysroot_flag = "--sysroot";
234         if !args.iter().any(|e| e == sysroot_flag) {
235             // We need to overwrite the default that librustc_session would compute.
236             args.push(sysroot_flag.to_owned());
237             args.push(sysroot);
238         }
239     }
240 
241     if insert_default_args {
242         // Some options have different defaults in Miri than in plain rustc; apply those by making
243         // them the first arguments after the binary name (but later arguments can overwrite them).
244         args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string));
245     }
246 
247     // Invoke compiler, and handle return code.
248     let exit_code = rustc_driver::catch_with_exit_code(move || {
249         rustc_driver::RunCompiler::new(&args, callbacks).run()
250     });
251     std::process::exit(exit_code)
252 }
253 
main()254 fn main() {
255     rustc_driver::install_ice_hook();
256 
257     // If the environment asks us to actually be rustc, then do that.
258     if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") {
259         rustc_driver::init_rustc_env_logger();
260 
261         let target_crate = if crate_kind == "target" {
262             true
263         } else if crate_kind == "host" {
264             false
265         } else {
266             panic!("invalid `MIRI_BE_RUSTC` value: {:?}", crate_kind)
267         };
268 
269         // We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
270         run_compiler(
271             env::args().collect(),
272             &mut MiriBeRustCompilerCalls { target_crate },
273             // Don't insert `MIRI_DEFAULT_ARGS`, in particular, `--cfg=miri`, if we are building
274             // a "host" crate. That may cause procedural macros (and probably build scripts) to
275             // depend on Miri-only symbols, such as `miri_resolve_frame`:
276             // https://github.com/rust-lang/miri/issues/1760
277             #[rustfmt::skip]
278             /* insert_default_args: */ target_crate,
279         )
280     }
281 
282     // Init loggers the Miri way.
283     init_early_loggers();
284 
285     // Parse our arguments and split them across `rustc` and `miri`.
286     let mut miri_config = miri::MiriConfig::default();
287     let mut rustc_args = vec![];
288     let mut after_dashdash = false;
289 
290     // If user has explicitly enabled/disabled isolation
291     let mut isolation_enabled: Option<bool> = None;
292     for arg in env::args() {
293         if rustc_args.is_empty() {
294             // Very first arg: binary name.
295             rustc_args.push(arg);
296         } else if after_dashdash {
297             // Everything that comes after `--` is forwarded to the interpreted crate.
298             miri_config.args.push(arg);
299         } else {
300             match arg.as_str() {
301                 "-Zmiri-disable-validation" => {
302                     miri_config.validate = false;
303                 }
304                 "-Zmiri-disable-stacked-borrows" => {
305                     miri_config.stacked_borrows = false;
306                 }
307                 "-Zmiri-disable-data-race-detector" => {
308                     miri_config.data_race_detector = false;
309                 }
310                 "-Zmiri-disable-alignment-check" => {
311                     miri_config.check_alignment = miri::AlignmentCheck::None;
312                 }
313                 "-Zmiri-symbolic-alignment-check" => {
314                     miri_config.check_alignment = miri::AlignmentCheck::Symbolic;
315                 }
316                 "-Zmiri-check-number-validity" => {
317                     miri_config.check_number_validity = true;
318                 }
319                 "-Zmiri-disable-abi-check" => {
320                     miri_config.check_abi = false;
321                 }
322                 "-Zmiri-disable-isolation" => {
323                     if matches!(isolation_enabled, Some(true)) {
324                         panic!(
325                             "-Zmiri-disable-isolation cannot be used along with -Zmiri-isolation-error"
326                         );
327                     } else {
328                         isolation_enabled = Some(false);
329                     }
330                     miri_config.isolated_op = miri::IsolatedOp::Allow;
331                 }
332                 arg if arg.starts_with("-Zmiri-isolation-error=") => {
333                     if matches!(isolation_enabled, Some(false)) {
334                         panic!(
335                             "-Zmiri-isolation-error cannot be used along with -Zmiri-disable-isolation"
336                         );
337                     } else {
338                         isolation_enabled = Some(true);
339                     }
340 
341                     miri_config.isolated_op = match arg
342                         .strip_prefix("-Zmiri-isolation-error=")
343                         .unwrap()
344                     {
345                         "abort" => miri::IsolatedOp::Reject(miri::RejectOpWith::Abort),
346                         "hide" => miri::IsolatedOp::Reject(miri::RejectOpWith::NoWarning),
347                         "warn" => miri::IsolatedOp::Reject(miri::RejectOpWith::Warning),
348                         "warn-nobacktrace" =>
349                             miri::IsolatedOp::Reject(miri::RejectOpWith::WarningWithoutBacktrace),
350                         _ =>
351                             panic!(
352                                 "-Zmiri-isolation-error must be `abort`, `hide`, `warn`, or `warn-nobacktrace`"
353                             ),
354                     };
355                 }
356                 "-Zmiri-ignore-leaks" => {
357                     miri_config.ignore_leaks = true;
358                 }
359                 "-Zmiri-panic-on-unsupported" => {
360                     miri_config.panic_on_unsupported = true;
361                 }
362                 "-Zmiri-track-raw-pointers" => {
363                     miri_config.track_raw = true;
364                 }
365                 "--" => {
366                     after_dashdash = true;
367                 }
368                 arg if arg.starts_with("-Zmiri-seed=") => {
369                     if miri_config.seed.is_some() {
370                         panic!("Cannot specify -Zmiri-seed multiple times!");
371                     }
372                     let seed_raw = hex::decode(arg.strip_prefix("-Zmiri-seed=").unwrap())
373                         .unwrap_or_else(|err| match err {
374                             FromHexError::InvalidHexCharacter { .. } => panic!(
375                                 "-Zmiri-seed should only contain valid hex digits [0-9a-fA-F]"
376                             ),
377                             FromHexError::OddLength =>
378                                 panic!("-Zmiri-seed should have an even number of digits"),
379                             err => panic!("unknown error decoding -Zmiri-seed as hex: {:?}", err),
380                         });
381                     if seed_raw.len() > 8 {
382                         panic!("-Zmiri-seed must be at most 8 bytes, was {}", seed_raw.len());
383                     }
384 
385                     let mut bytes = [0; 8];
386                     bytes[..seed_raw.len()].copy_from_slice(&seed_raw);
387                     miri_config.seed = Some(u64::from_be_bytes(bytes));
388                 }
389                 arg if arg.starts_with("-Zmiri-env-exclude=") => {
390                     miri_config
391                         .excluded_env_vars
392                         .push(arg.strip_prefix("-Zmiri-env-exclude=").unwrap().to_owned());
393                 }
394                 arg if arg.starts_with("-Zmiri-track-pointer-tag=") => {
395                     let id: u64 =
396                         match arg.strip_prefix("-Zmiri-track-pointer-tag=").unwrap().parse() {
397                             Ok(id) => id,
398                             Err(err) =>
399                                 panic!(
400                                     "-Zmiri-track-pointer-tag requires a valid `u64` argument: {}",
401                                     err
402                                 ),
403                         };
404                     if let Some(id) = miri::PtrId::new(id) {
405                         miri_config.tracked_pointer_tag = Some(id);
406                     } else {
407                         panic!("-Zmiri-track-pointer-tag requires a nonzero argument");
408                     }
409                 }
410                 arg if arg.starts_with("-Zmiri-track-call-id=") => {
411                     let id: u64 = match arg.strip_prefix("-Zmiri-track-call-id=").unwrap().parse() {
412                         Ok(id) => id,
413                         Err(err) =>
414                             panic!("-Zmiri-track-call-id requires a valid `u64` argument: {}", err),
415                     };
416                     if let Some(id) = miri::CallId::new(id) {
417                         miri_config.tracked_call_id = Some(id);
418                     } else {
419                         panic!("-Zmiri-track-call-id requires a nonzero argument");
420                     }
421                 }
422                 arg if arg.starts_with("-Zmiri-track-alloc-id=") => {
423                     let id = match arg
424                         .strip_prefix("-Zmiri-track-alloc-id=")
425                         .unwrap()
426                         .parse()
427                         .ok()
428                         .and_then(NonZeroU64::new)
429                     {
430                         Some(id) => id,
431                         None =>
432                             panic!("-Zmiri-track-alloc-id requires a valid non-zero `u64` argument"),
433                     };
434                     miri_config.tracked_alloc_id = Some(miri::AllocId(id));
435                 }
436                 arg if arg.starts_with("-Zmiri-compare-exchange-weak-failure-rate=") => {
437                     let rate = match arg
438                         .strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=")
439                         .unwrap()
440                         .parse::<f64>()
441                     {
442                         Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
443                         Ok(_) =>
444                             panic!(
445                                 "-Zmiri-compare-exchange-weak-failure-rate must be between `0.0` and `1.0`"
446                             ),
447                         Err(err) =>
448                             panic!(
449                                 "-Zmiri-compare-exchange-weak-failure-rate requires a `f64` between `0.0` and `1.0`: {}",
450                                 err
451                             ),
452                     };
453                     miri_config.cmpxchg_weak_failure_rate = rate;
454                 }
455                 arg if arg.starts_with("-Zmiri-measureme=") => {
456                     let measureme_out = arg.strip_prefix("-Zmiri-measureme=").unwrap();
457                     miri_config.measureme_out = Some(measureme_out.to_string());
458                 }
459                 _ => {
460                     // Forward to rustc.
461                     rustc_args.push(arg);
462                 }
463             }
464         }
465     }
466 
467     debug!("rustc arguments: {:?}", rustc_args);
468     debug!("crate arguments: {:?}", miri_config.args);
469     run_compiler(
470         rustc_args,
471         &mut MiriCompilerCalls { miri_config },
472         /* insert_default_args: */ true,
473     )
474 }
475