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