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