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