1use std::ffi::{OsStr, OsString};
4use std::num::NonZeroI32;
5use std::panic::{self, AssertUnwindSafe};
6use std::path::PathBuf;
7use std::rc::Rc;
8use std::task::Poll;
9use std::{iter, thread};
10
11use rustc_abi::ExternAbi;
12use rustc_data_structures::fx::{FxHashMap, FxHashSet};
13use rustc_hir::def::Namespace;
14use rustc_hir::def_id::DefId;
15use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutCx};
16use rustc_middle::ty::{self, Ty, TyCtxt};
17use rustc_session::config::EntryFnType;
18use rustc_target::spec::Os;
19
20use crate::concurrency::GenmcCtx;
21use crate::concurrency::thread::TlsAllocAction;
22use crate::diagnostics::report_leaks;
23use crate::shims::{global_ctor, tls};
24use crate::*;
25
26#[derive(Copy, Clone, Debug)]
27pub enum MiriEntryFnType {
28 MiriStart,
29 Rustc(EntryFnType),
30}
31
32const MAIN_THREAD_YIELDS_AT_SHUTDOWN: u32 = 256;
36
37#[derive(Clone)]
39pub struct MiriConfig {
40 pub env: Vec<(OsString, OsString)>,
43 pub validation: ValidationMode,
45 pub borrow_tracker: Option<BorrowTrackerMethod>,
47 pub check_alignment: AlignmentCheck,
49 pub isolated_op: IsolatedOp,
51 pub ignore_leaks: bool,
53 pub forwarded_env_vars: Vec<String>,
55 pub set_env_vars: FxHashMap<String, String>,
57 pub args: Vec<String>,
59 pub seed: Option<u64>,
61 pub tracked_pointer_tags: FxHashSet<BorTag>,
63 pub tracked_alloc_ids: FxHashSet<AllocId>,
65 pub track_alloc_accesses: bool,
67 pub data_race_detector: bool,
69 pub weak_memory_emulation: bool,
71 pub genmc_config: Option<GenmcConfig>,
73 pub track_outdated_loads: bool,
75 pub cmpxchg_weak_failure_rate: f64,
78 pub measureme_out: Option<String>,
81 pub backtrace_style: BacktraceStyle,
83 pub provenance_mode: ProvenanceMode,
85 pub mute_stdout_stderr: bool,
88 pub preemption_rate: f64,
90 pub report_progress: Option<u32>,
92 pub native_lib: Vec<PathBuf>,
94 pub native_lib_enable_tracing: bool,
96 pub gc_interval: u32,
98 pub num_cpus: u32,
100 pub page_size: Option<u64>,
102 pub collect_leak_backtraces: bool,
104 pub address_reuse_rate: f64,
106 pub address_reuse_cross_thread_rate: f64,
108 pub fixed_scheduling: bool,
110 pub force_intrinsic_fallback: bool,
112 pub float_nondet: bool,
114 pub float_rounding_error: FloatRoundingErrorMode,
116 pub short_fd_operations: bool,
118 pub user_relevant_crates: Vec<String>,
120}
121
122impl Default for MiriConfig {
123 fn default() -> MiriConfig {
124 MiriConfig {
125 env: vec![],
126 validation: ValidationMode::Shallow,
127 borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows),
128 check_alignment: AlignmentCheck::Int,
129 isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
130 ignore_leaks: false,
131 forwarded_env_vars: vec![],
132 set_env_vars: FxHashMap::default(),
133 args: vec![],
134 seed: None,
135 tracked_pointer_tags: FxHashSet::default(),
136 tracked_alloc_ids: FxHashSet::default(),
137 track_alloc_accesses: false,
138 data_race_detector: true,
139 weak_memory_emulation: true,
140 genmc_config: None,
141 track_outdated_loads: false,
142 cmpxchg_weak_failure_rate: 0.8, measureme_out: None,
144 backtrace_style: BacktraceStyle::Short,
145 provenance_mode: ProvenanceMode::Default,
146 mute_stdout_stderr: false,
147 preemption_rate: 0.01, report_progress: None,
149 native_lib: vec![],
150 native_lib_enable_tracing: false,
151 gc_interval: 10_000,
152 num_cpus: 1,
153 page_size: None,
154 collect_leak_backtraces: true,
155 address_reuse_rate: 0.5,
156 address_reuse_cross_thread_rate: 0.1,
157 fixed_scheduling: false,
158 force_intrinsic_fallback: false,
159 float_nondet: true,
160 float_rounding_error: FloatRoundingErrorMode::Random,
161 short_fd_operations: true,
162 user_relevant_crates: vec![],
163 }
164 }
165}
166
167#[derive(Debug)]
169enum MainThreadState<'tcx> {
170 GlobalCtors {
171 ctor_state: global_ctor::GlobalCtorState<'tcx>,
172 entry_id: DefId,
174 entry_type: MiriEntryFnType,
175 argc: ImmTy<'tcx>,
177 argv: ImmTy<'tcx>,
178 },
179 Running,
180 TlsDtors(tls::TlsDtorsState<'tcx>),
181 Yield {
182 remaining: u32,
183 },
184 Done,
185}
186
187impl<'tcx> MainThreadState<'tcx> {
188 fn on_main_stack_empty(
189 &mut self,
190 this: &mut MiriInterpCx<'tcx>,
191 ) -> InterpResult<'tcx, Poll<()>> {
192 use MainThreadState::*;
193 match self {
194 GlobalCtors { ctor_state, entry_id, entry_type, argc, argv } => {
195 match ctor_state.on_stack_empty(this)? {
196 Poll::Pending => {} Poll::Ready(()) => {
198 call_main(this, *entry_id, *entry_type, argc.clone(), argv.clone())?;
199 *self = Running;
200 }
201 }
202 }
203 Running => {
204 *self = TlsDtors(Default::default());
205 }
206 TlsDtors(state) =>
207 match state.on_stack_empty(this)? {
208 Poll::Pending => {} Poll::Ready(()) => {
210 if this.machine.data_race.as_genmc_ref().is_some() {
211 *self = Done;
214 } else {
215 if this.machine.preemption_rate > 0.0 {
218 *self = Yield { remaining: MAIN_THREAD_YIELDS_AT_SHUTDOWN };
221 } else {
222 *self = Done;
225 }
226 }
227 }
228 },
229 Yield { remaining } =>
230 match remaining.checked_sub(1) {
231 None => *self = Done,
232 Some(new_remaining) => {
233 *remaining = new_remaining;
234 this.yield_active_thread();
235 }
236 },
237 Done => {
238 let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
240 let exit_code = this.read_target_isize(&ret_place)?;
241 let exit_code = i32::try_from(exit_code).unwrap_or(if exit_code >= 0 {
244 i32::MAX
245 } else {
246 i32::MIN
247 });
248 this.terminate_active_thread(TlsAllocAction::Leak)?;
251
252 if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
254 genmc_ctx.handle_exit(
256 ThreadId::MAIN_THREAD,
257 exit_code,
258 crate::concurrency::ExitType::MainThreadFinish,
259 )?;
260 } else {
261 throw_machine_stop!(TerminationInfo::Exit {
263 code: exit_code,
264 leak_check: true
265 });
266 }
267 }
268 }
269 interp_ok(Poll::Pending)
270 }
271}
272
273pub fn create_ecx<'tcx>(
276 tcx: TyCtxt<'tcx>,
277 entry_id: DefId,
278 entry_type: MiriEntryFnType,
279 config: &MiriConfig,
280 genmc_ctx: Option<Rc<GenmcCtx>>,
281) -> InterpResult<'tcx, InterpCx<'tcx, MiriMachine<'tcx>>> {
282 let typing_env = ty::TypingEnv::fully_monomorphized();
283 let layout_cx = LayoutCx::new(tcx, typing_env);
284 let mut ecx = InterpCx::new(
285 tcx,
286 rustc_span::DUMMY_SP,
287 typing_env,
288 MiriMachine::new(config, layout_cx, genmc_ctx),
289 );
290
291 let sentinel =
293 helpers::try_resolve_path(tcx, &["core", "ascii", "escape_default"], Namespace::ValueNS);
294 if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
295 tcx.dcx().fatal(
296 "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
297 Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
298 );
299 }
300
301 let argc =
303 ImmTy::from_int(i64::try_from(config.args.len()).unwrap(), ecx.machine.layouts.isize);
304 let argv = {
305 let mut argvs = Vec::<Immediate<Provenance>>::with_capacity(config.args.len());
307 for arg in config.args.iter() {
308 let size = u64::try_from(arg.len()).unwrap().strict_add(1);
310 let arg_type = Ty::new_array(tcx, tcx.types.u8, size);
311 let arg_place =
312 ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?;
313 ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr(), size)?;
314 ecx.mark_immutable(&arg_place);
315 argvs.push(arg_place.to_ref(&ecx));
316 }
317 let u8_ptr_type = Ty::new_imm_ptr(tcx, tcx.types.u8);
319 let u8_ptr_ptr_type = Ty::new_imm_ptr(tcx, u8_ptr_type);
320 let argvs_layout =
321 ecx.layout_of(Ty::new_array(tcx, u8_ptr_type, u64::try_from(argvs.len()).unwrap()))?;
322 let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
323 for (arg, idx) in argvs.into_iter().zip(0..) {
324 let place = ecx.project_index(&argvs_place, idx)?;
325 ecx.write_immediate(arg, &place)?;
326 }
327 ecx.mark_immutable(&argvs_place);
328 {
330 let argc_place =
331 ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
332 ecx.write_immediate(*argc, &argc_place)?;
333 ecx.mark_immutable(&argc_place);
334 ecx.machine.argc = Some(argc_place.ptr());
335
336 let argv_place =
337 ecx.allocate(ecx.layout_of(u8_ptr_ptr_type)?, MiriMemoryKind::Machine.into())?;
338 ecx.write_pointer(argvs_place.ptr(), &argv_place)?;
339 ecx.mark_immutable(&argv_place);
340 ecx.machine.argv = Some(argv_place.ptr());
341 }
342 if tcx.sess.target.os == Os::Windows {
344 let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
346
347 let cmd_type =
348 Ty::new_array(tcx, tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
349 let cmd_place =
350 ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
351 ecx.machine.cmd_line = Some(cmd_place.ptr());
352 for (&c, idx) in cmd_utf16.iter().zip(0..) {
354 let place = ecx.project_index(&cmd_place, idx)?;
355 ecx.write_scalar(Scalar::from_u16(c), &place)?;
356 }
357 ecx.mark_immutable(&cmd_place);
358 }
359 let imm = argvs_place.to_ref(&ecx);
360 let layout = ecx.layout_of(u8_ptr_ptr_type)?;
361 ImmTy::from_immediate(imm, layout)
362 };
363
364 MiriMachine::late_init(&mut ecx, config, {
366 let mut main_thread_state = MainThreadState::GlobalCtors {
367 entry_id,
368 entry_type,
369 argc,
370 argv,
371 ctor_state: global_ctor::GlobalCtorState::default(),
372 };
373
374 Box::new(move |m| main_thread_state.on_main_stack_empty(m))
378 })?;
379
380 interp_ok(ecx)
381}
382
383fn call_main<'tcx>(
385 ecx: &mut MiriInterpCx<'tcx>,
386 entry_id: DefId,
387 entry_type: MiriEntryFnType,
388 argc: ImmTy<'tcx>,
389 argv: ImmTy<'tcx>,
390) -> InterpResult<'tcx, ()> {
391 let tcx = ecx.tcx();
392
393 let entry_instance = ty::Instance::mono(tcx, entry_id);
395
396 let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
398 ecx.machine.main_fn_ret_place = Some(ret_place.clone());
399
400 match entry_type {
402 MiriEntryFnType::Rustc(EntryFnType::Main { .. }) => {
403 let start_id = tcx.lang_items().start_fn().unwrap_or_else(|| {
404 tcx.dcx().fatal("could not find start lang item");
405 });
406 let main_ret_ty = tcx.fn_sig(entry_id).no_bound_vars().unwrap().output();
407 let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
408 let start_instance = ty::Instance::try_resolve(
409 tcx,
410 ecx.typing_env(),
411 start_id,
412 tcx.mk_args(&[ty::GenericArg::from(main_ret_ty)]),
413 )
414 .unwrap()
415 .unwrap();
416
417 let main_ptr = ecx.fn_ptr(FnVal::Instance(entry_instance));
418
419 let sigpipe = rustc_session::config::sigpipe::DEFAULT;
422
423 ecx.call_function(
424 start_instance,
425 ExternAbi::Rust,
426 &[
427 ImmTy::from_scalar(
428 Scalar::from_pointer(main_ptr, ecx),
429 ecx.machine.layouts.const_raw_ptr,
431 ),
432 argc,
433 argv,
434 ImmTy::from_uint(sigpipe, ecx.machine.layouts.u8),
435 ],
436 Some(&ret_place),
437 ReturnContinuation::Stop { cleanup: true },
438 )?;
439 }
440 MiriEntryFnType::MiriStart => {
441 ecx.call_function(
442 entry_instance,
443 ExternAbi::Rust,
444 &[argc, argv],
445 Some(&ret_place),
446 ReturnContinuation::Stop { cleanup: true },
447 )?;
448 }
449 }
450
451 interp_ok(())
452}
453
454pub fn eval_entry<'tcx>(
458 tcx: TyCtxt<'tcx>,
459 entry_id: DefId,
460 entry_type: MiriEntryFnType,
461 config: &MiriConfig,
462 genmc_ctx: Option<Rc<GenmcCtx>>,
463) -> Result<(), NonZeroI32> {
464 let ignore_leaks = config.ignore_leaks;
466
467 let mut ecx = match create_ecx(tcx, entry_id, entry_type, config, genmc_ctx).report_err() {
468 Ok(v) => v,
469 Err(err) => {
470 let (kind, backtrace) = err.into_parts();
471 backtrace.print_backtrace();
472 panic!("Miri initialization error: {kind:?}")
473 }
474 };
475
476 let res: thread::Result<InterpResult<'_, !>> =
478 panic::catch_unwind(AssertUnwindSafe(|| ecx.run_threads()));
479 let res = res.unwrap_or_else(|panic_payload| {
480 ecx.handle_ice();
481 panic::resume_unwind(panic_payload)
482 });
483 let Err(res) = res.report_err();
486
487 'miri_error: {
489 let Some((return_code, leak_check)) = report_result(&ecx, res) else {
491 break 'miri_error;
492 };
493
494 if leak_check && !ignore_leaks {
497 if !ecx.have_all_terminated() {
499 tcx.dcx()
500 .err("the main thread terminated without waiting for all remaining threads");
501 tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
502 break 'miri_error;
503 }
504 info!("Additional static roots: {:?}", ecx.machine.static_roots);
506 let leaks = ecx.take_leaked_allocations(|ecx| &ecx.machine.static_roots);
507 if !leaks.is_empty() {
508 report_leaks(&ecx, leaks);
509 tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
510 break 'miri_error;
513 }
514 }
515
516 return match NonZeroI32::new(return_code) {
519 None => Ok(()),
520 Some(return_code) => Err(return_code),
521 };
522 }
523
524 assert!(tcx.dcx().has_errors().is_some());
526 Err(NonZeroI32::new(rustc_driver::EXIT_FAILURE).unwrap())
527}
528
529fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16>
540where
541 I: Iterator<Item = T>,
542 T: AsRef<str>,
543{
544 let mut cmd = {
546 let arg0 = if let Some(arg0) = args.next() {
547 arg0
548 } else {
549 return vec![0];
550 };
551 let arg0 = arg0.as_ref();
552 if arg0.contains('"') {
553 panic!("argv[0] cannot contain a doublequote (\") character");
554 } else {
555 let mut s = String::new();
557 s.push('"');
558 s.push_str(arg0);
559 s.push('"');
560 s
561 }
562 };
563
564 for arg in args {
566 let arg = arg.as_ref();
567 cmd.push(' ');
568 if arg.is_empty() {
569 cmd.push_str("\"\"");
570 } else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) {
571 cmd.push_str(arg);
573 } else {
574 cmd.push('"');
581 let mut chars = arg.chars().peekable();
582 loop {
583 let mut nslashes = 0;
584 while let Some(&'\\') = chars.peek() {
585 chars.next();
586 nslashes += 1;
587 }
588
589 match chars.next() {
590 Some('"') => {
591 cmd.extend(iter::repeat_n('\\', nslashes * 2 + 1));
592 cmd.push('"');
593 }
594 Some(c) => {
595 cmd.extend(iter::repeat_n('\\', nslashes));
596 cmd.push(c);
597 }
598 None => {
599 cmd.extend(iter::repeat_n('\\', nslashes * 2));
600 break;
601 }
602 }
603 }
604 cmd.push('"');
605 }
606 }
607
608 if cmd.contains('\0') {
609 panic!("interior null in command line arguments");
610 }
611 cmd.encode_utf16().chain(iter::once(0)).collect()
612}
613
614#[cfg(test)]
615mod tests {
616 use super::*;
617 #[test]
618 #[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")]
619 fn windows_argv0_panic_on_quote() {
620 args_to_utf16_command_string(["\""].iter());
621 }
622 #[test]
623 fn windows_argv0_no_escape() {
624 let cmd = String::from_utf16_lossy(&args_to_utf16_command_string(
626 [r"C:\Program Files\", "arg1", "arg 2", "arg \" 3"].iter(),
627 ));
628 assert_eq!(cmd.trim_end_matches('\0'), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#);
629 }
630}