1 #![feature(rustc_private, bool_to_option, stmt_expr_attributes)]
2 #![allow(clippy::manual_range_contains)]
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;
14 use std::num::NonZeroU64;
15 use std::path::PathBuf;
16 use std::str::FromStr;
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;
26 middle::exported_symbols::{
27 ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel,
29 ty::{query::ExternProviders, TyCtxt},
31 use rustc_session::{config::ErrorOutputType, search_paths::PathKind, CtfeBacktrace};
33 use miri::BacktraceStyle;
35 struct MiriCompilerCalls {
36 miri_config: miri::MiriConfig,
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));
55 fn after_analysis<'tcx>(
57 compiler: &rustc_interface::interface::Compiler,
58 queries: &'tcx rustc_interface::Queries<'tcx>,
60 compiler.session().abort_if_errors();
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(()) {
67 let output_ty = ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(
70 rustc_session::early_error(
72 "miri can only run programs that have a main function",
75 let mut config = self.miri_config.clone();
77 // Add filename to `miri` arguments.
78 config.args.insert(0, compiler.input().filestem().to_string());
80 // Adjust working directory for interpretation.
81 if let Some(cwd) = env::var_os("MIRI_CWD") {
82 env::set_current_dir(cwd).unwrap();
85 if let Some(return_code) = miri::eval_entry(tcx, entry_def_id, entry_type, config) {
87 i32::try_from(return_code).expect("Return value was too large!"),
92 compiler.session().abort_if_errors();
98 struct MiriBeRustCompilerCalls {
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(
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(..),
126 }) | Node::ImplItem(&hir::ImplItem {
127 kind: hir::ImplItemKind::Fn(..),
130 if !tcx.generics_of(local_def_id).requires_monomorphization(tcx)
132 (is_reachable_non_generic
133 && tcx.codegen_fn_attrs(local_def_id).contains_extern_indicator())
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
140 level: SymbolExportLevel::C,
141 kind: SymbolExportKind::Text,
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();
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() {
182 "rustc_middle::mir::interpret={0},rustc_const_eval::interpret={0}",
187 env::set_var("RUSTC_LOG", &var);
189 rustc_driver::init_rustc_env_logger();
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,
201 *tcx.sess.ctfe_backtrace.borrow_mut() = ctfe_backtrace;
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.
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),
222 option_env!("RUST_SYSROOT")
224 "To build Miri without rustup, set the `RUST_SYSROOT` env var at build time",
230 /// Execute a compiler with the given CLI arguments and callbacks.
232 mut args: Vec<String>,
233 callbacks: &mut (dyn rustc_driver::Callbacks + Send),
234 insert_default_args: bool,
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`.
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());
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));
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()
261 std::process::exit(exit_code)
264 /// Parses a comma separated list of `T` from the given string:
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()
272 rustc_driver::install_ice_hook();
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();
278 let target_crate = if crate_kind == "target" {
280 } else if crate_kind == "host" {
283 panic!("invalid `MIRI_BE_RUSTC` value: {:?}", crate_kind)
286 // We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
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
295 /* insert_default_args: */ target_crate,
299 // Init loggers the Miri way.
300 init_early_loggers();
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;
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);
318 "-Zmiri-disable-validation" => {
319 miri_config.validate = false;
321 "-Zmiri-disable-stacked-borrows" => {
322 miri_config.stacked_borrows = false;
324 "-Zmiri-disable-data-race-detector" => {
325 miri_config.data_race_detector = false;
327 "-Zmiri-disable-alignment-check" => {
328 miri_config.check_alignment = miri::AlignmentCheck::None;
330 "-Zmiri-symbolic-alignment-check" => {
331 miri_config.check_alignment = miri::AlignmentCheck::Symbolic;
333 "-Zmiri-check-number-validity" => {
334 miri_config.check_number_validity = true;
336 "-Zmiri-disable-abi-check" => {
337 miri_config.check_abi = false;
339 "-Zmiri-disable-isolation" => {
340 if matches!(isolation_enabled, Some(true)) {
342 "-Zmiri-disable-isolation cannot be used along with -Zmiri-isolation-error"
345 isolation_enabled = Some(false);
347 miri_config.isolated_op = miri::IsolatedOp::Allow;
349 arg if arg.starts_with("-Zmiri-isolation-error=") => {
350 if matches!(isolation_enabled, Some(false)) {
352 "-Zmiri-isolation-error cannot be used along with -Zmiri-disable-isolation"
355 isolation_enabled = Some(true);
358 miri_config.isolated_op = match arg
359 .strip_prefix("-Zmiri-isolation-error=")
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),
369 "-Zmiri-isolation-error must be `abort`, `hide`, `warn`, or `warn-nobacktrace`"
373 "-Zmiri-ignore-leaks" => {
374 miri_config.ignore_leaks = true;
376 "-Zmiri-panic-on-unsupported" => {
377 miri_config.panic_on_unsupported = true;
379 "-Zmiri-tag-raw-pointers" => {
380 miri_config.tag_raw = true;
382 "-Zmiri-strict-provenance" => {
383 miri_config.strict_provenance = true;
384 miri_config.tag_raw = true;
385 miri_config.check_number_validity = true;
387 "-Zmiri-track-raw-pointers" => {
389 "WARNING: -Zmiri-track-raw-pointers has been renamed to -Zmiri-tag-raw-pointers, the old name is deprecated."
391 miri_config.tag_raw = true;
394 after_dashdash = true;
396 arg if arg.starts_with("-Zmiri-seed=") => {
397 if miri_config.seed.is_some() {
398 panic!("Cannot specify -Zmiri-seed multiple times!");
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)"
404 miri_config.seed = Some(seed);
406 arg if arg.starts_with("-Zmiri-env-exclude=") => {
409 .push(arg.strip_prefix("-Zmiri-env-exclude=").unwrap().to_owned());
411 arg if arg.starts_with("-Zmiri-env-forward=") => {
414 .push(arg.strip_prefix("-Zmiri-env-forward=").unwrap().to_owned());
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(),
423 "-Zmiri-track-pointer-tag requires a comma separated list of valid `u64` arguments: {}",
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);
431 panic!("-Zmiri-track-pointer-tag requires nonzero arguments");
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(),
442 "-Zmiri-track-call-id requires a comma separated list of valid `u64` arguments: {}",
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);
450 panic!("-Zmiri-track-call-id requires a nonzero argument");
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(),
458 Ok(ids) => ids.into_iter().map(miri::AllocId).collect(),
461 "-Zmiri-track-alloc-id requires a comma separated list of valid non-zero `u64` arguments: {}",
465 miri_config.tracked_alloc_ids.extend(ids);
467 arg if arg.starts_with("-Zmiri-compare-exchange-weak-failure-rate=") => {
469 .strip_prefix("-Zmiri-compare-exchange-weak-failure-rate=")
473 Ok(rate) if rate >= 0.0 && rate <= 1.0 => rate,
476 "-Zmiri-compare-exchange-weak-failure-rate must be between `0.0` and `1.0`"
480 "-Zmiri-compare-exchange-weak-failure-rate requires a `f64` between `0.0` and `1.0`: {}",
484 miri_config.cmpxchg_weak_failure_rate = rate;
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());
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"),
500 rustc_args.push(arg);
506 debug!("rustc arguments: {:?}", rustc_args);
507 debug!("crate arguments: {:?}", miri_config.args);
510 &mut MiriCompilerCalls { miri_config },
511 /* insert_default_args: */ true,