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, mem, thread};
10
11use rustc_abi::ExternAbi;
12use rustc_data_structures::fx::{FxHashMap, FxHashSet};
13use rustc_errors::FatalErrorMarker;
14use rustc_hir::def::Namespace;
15use rustc_hir::def_id::{DefId, LOCAL_CRATE};
16use rustc_hir_analysis::check::check_function_signature;
17use rustc_middle::middle::exported_symbols::ExportedSymbol;
18use rustc_middle::traits::{ObligationCause, ObligationCauseCode};
19use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutCx};
20use rustc_middle::ty::{self, Ty, TyCtxt};
21use rustc_session::config::EntryFnType;
22use rustc_target::spec::Os;
23
24use crate::concurrency::GenmcCtx;
25use crate::diagnostics::report_leaks;
26use crate::helpers::is_no_core;
27use crate::shims::{global_ctor, tls};
28use crate::*;
29
30#[derive(Copy, Clone, Debug)]
31pub enum MiriEntryFnType {
32 MiriStart,
33 Rustc(EntryFnType),
34}
35
36pub fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, MiriEntryFnType) {
40 if let Some((def_id, entry_type)) = tcx.entry_fn(()) {
41 return (def_id, MiriEntryFnType::Rustc(entry_type));
42 }
43 let sym = tcx.exported_non_generic_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| {
45 if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None }
46 });
47 if let Some(ExportedSymbol::NonGeneric(id)) = sym {
48 let start_def_id = id.expect_local();
49 let start_span = tcx.def_span(start_def_id);
50
51 let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig_safe_rust_abi(
52 [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))],
53 tcx.types.isize,
54 ));
55
56 let correct_func_sig = check_function_signature(
57 tcx,
58 ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc),
59 *id,
60 expected_sig,
61 )
62 .is_ok();
63
64 if correct_func_sig {
65 (*id, MiriEntryFnType::MiriStart)
66 } else {
67 tcx.dcx().fatal(
68 "`miri_start` must have the following signature:\n\
69 fn miri_start(argc: isize, argv: *const *const u8) -> isize",
70 );
71 }
72 } else {
73 tcx.dcx().fatal(
74 "Miri can only run programs that have a main function.\n\
75 Alternatively, you can export a `miri_start` function:\n\
76 \n\
77 #[cfg(miri)]\n\
78 #[unsafe(no_mangle)]\n\
79 fn miri_start(argc: isize, argv: *const *const u8) -> isize {\
80 \n // Call the actual start function that your project implements, based on your target's conventions.\n\
81 }"
82 );
83 }
84}
85
86const THREAD_YIELDS_AT_EXIT: u32 = 256;
90
91#[derive(Clone)]
93pub struct MiriConfig {
94 pub env: Vec<(OsString, OsString)>,
97 pub validation: ValidationMode,
99 pub borrow_tracker: Option<BorrowTrackerMethod>,
101 pub check_alignment: AlignmentCheck,
103 pub isolated_op: IsolatedOp,
105 pub ignore_leaks: bool,
107 pub forwarded_env_vars: Vec<String>,
109 pub set_env_vars: FxHashMap<String, String>,
111 pub args: Vec<String>,
113 pub current_exe: Option<PathBuf>,
115 pub seed: Option<u64>,
117 pub tracked_pointer_tags: FxHashSet<BorTag>,
119 pub tracked_alloc_ids: FxHashSet<AllocId>,
121 pub track_alloc_accesses: bool,
123 pub data_race_detector: bool,
125 pub weak_memory_emulation: bool,
127 pub genmc_config: Option<GenmcConfig>,
129 pub track_outdated_loads: bool,
131 pub cmpxchg_weak_failure_rate: f64,
134 pub measureme_out: Option<String>,
137 pub backtrace_style: BacktraceStyle,
139 pub provenance_mode: ProvenanceMode,
141 pub mute_stdout_stderr: bool,
144 pub preemption_rate: f64,
146 pub report_progress: Option<u32>,
148 pub native_lib: Vec<PathBuf>,
150 pub native_lib_enable_tracing: bool,
152 pub gc_interval: u32,
154 pub num_cpus: u32,
156 pub page_size: Option<u64>,
158 pub collect_leak_backtraces: bool,
160 pub address_reuse_rate: f64,
162 pub address_reuse_cross_thread_rate: f64,
164 pub fixed_scheduling: bool,
166 pub float_nondet: bool,
168 pub float_rounding_error: FloatRoundingErrorMode,
170 pub short_fd_operations: bool,
172 pub user_relevant_crates: Vec<String>,
174}
175
176impl Default for MiriConfig {
177 fn default() -> MiriConfig {
178 MiriConfig {
179 env: vec![],
180 validation: ValidationMode::Shallow,
181 borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows),
182 check_alignment: AlignmentCheck::Int,
183 isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
184 ignore_leaks: false,
185 forwarded_env_vars: vec![],
186 set_env_vars: FxHashMap::default(),
187 args: vec![],
188 current_exe: None,
189 seed: None,
190 tracked_pointer_tags: FxHashSet::default(),
191 tracked_alloc_ids: FxHashSet::default(),
192 track_alloc_accesses: false,
193 data_race_detector: true,
194 weak_memory_emulation: true,
195 genmc_config: None,
196 track_outdated_loads: false,
197 cmpxchg_weak_failure_rate: 0.8, measureme_out: None,
199 backtrace_style: BacktraceStyle::Short,
200 provenance_mode: ProvenanceMode::Default,
201 mute_stdout_stderr: false,
202 preemption_rate: 0.01, report_progress: None,
204 native_lib: vec![],
205 native_lib_enable_tracing: false,
206 gc_interval: 10_000,
207 num_cpus: 1,
208 page_size: None,
209 collect_leak_backtraces: true,
210 address_reuse_rate: 0.5,
211 address_reuse_cross_thread_rate: 0.1,
212 fixed_scheduling: false,
213 float_nondet: true,
214 float_rounding_error: FloatRoundingErrorMode::Random,
215 short_fd_operations: true,
216 user_relevant_crates: vec![],
217 }
218 }
219}
220
221#[derive(Clone, Copy, Debug)]
222pub enum ExitType {
223 MainThreadFinish,
224 ExitCalled,
225}
226
227#[derive(Debug)]
229pub struct ProgramExitHandler<'tcx> {
230 state: ProgramExitState<'tcx>,
231 exit_code: i32,
232 exit_type: ExitType,
233}
234#[derive(Debug)]
235enum ProgramExitState<'tcx> {
236 TlsDtors(tls::TlsDtorsState<'tcx>),
237 Yield { remaining: u32 },
238 Done,
239 Unreachable,
240}
241
242impl<'tcx> ProgramExitHandler<'tcx> {
243 pub fn new(exit_code: i32, exit_type: ExitType) -> Self {
244 Self {
245 state: ProgramExitState::TlsDtors(tls::TlsDtorsState::new_for_exit()),
246 exit_code,
247 exit_type,
248 }
249 }
250 pub fn on_stack_empty(&mut self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Poll<()>> {
251 use ProgramExitState::*;
252 match &mut self.state {
253 TlsDtors(state) =>
254 match state.on_stack_empty(ecx)? {
255 Poll::Pending => {} Poll::Ready(()) => {
257 if ecx.machine.data_race.as_genmc_ref().is_some() {
258 self.state = Done;
262 } else {
263 self.state = Yield { remaining: THREAD_YIELDS_AT_EXIT };
266 }
267 }
268 },
269 Yield { remaining } =>
270 match remaining.checked_sub(1) {
271 None => self.state = Done,
272 Some(new_remaining) => {
273 *remaining = new_remaining;
274 ecx.yield_active_thread();
275 }
276 },
277 Done => {
278 ecx.terminate_active_thread(true)?;
280
281 self.state = Unreachable;
283
284 if let Some(genmc_ctx) = ecx.machine.data_race.as_genmc_ref() {
286 genmc_ctx.handle_exit(
288 ecx.machine.threads.active_thread(),
289 self.exit_code,
290 self.exit_type,
291 )?;
292 } else {
293 throw_machine_stop!(TerminationInfo::Exit {
295 exit_code: self.exit_code,
296 leak_check: matches!(self.exit_type, ExitType::MainThreadFinish),
297 });
298 }
299 }
300 Unreachable => unreachable!(),
301 }
302 interp_ok(Poll::Pending)
303 }
304}
305
306#[derive(Debug)]
308enum MainThreadState<'tcx> {
309 GlobalCtors {
310 ctor_state: global_ctor::GlobalCtorState<'tcx>,
311 entry_id: DefId,
313 entry_type: MiriEntryFnType,
314 argc: ImmTy<'tcx>,
316 argv: ImmTy<'tcx>,
317 },
318 Running,
319 Exiting(ProgramExitHandler<'tcx>),
320}
321
322impl<'tcx> MainThreadState<'tcx> {
323 fn on_main_stack_empty(
324 &mut self,
325 ecx: &mut MiriInterpCx<'tcx>,
326 ) -> InterpResult<'tcx, Poll<()>> {
327 use MainThreadState::*;
328 match self {
329 GlobalCtors { ctor_state, entry_id, entry_type, argc, argv } => {
330 match ctor_state.on_stack_empty(ecx)? {
331 Poll::Pending => {} Poll::Ready(()) => {
333 call_main(ecx, *entry_id, *entry_type, argc.clone(), argv.clone())?;
334 *self = Running;
335 }
336 }
337 }
338 Running => {
339 let old_exit = mem::replace(&mut ecx.machine.exit_called, true);
343 if old_exit {
344 throw_ub_format!("`exit` called more than once");
345 }
346
347 let ret_place = ecx.machine.main_fn_ret_place.clone().unwrap();
349 let exit_code = ecx.read_target_isize(&ret_place)?;
350 let Ok(exit_code) = i32::try_from(exit_code) else {
352 throw_ub_format!(
353 "lang_start item return an exit code that does not fit into `i32`"
354 );
355 };
356 *self = Exiting(ProgramExitHandler::new(exit_code, ExitType::MainThreadFinish));
357 }
358 Exiting(state) => {
359 return state.on_stack_empty(ecx);
361 }
362 }
363 interp_ok(Poll::Pending)
364 }
365}
366
367pub fn create_ecx<'tcx>(
370 tcx: TyCtxt<'tcx>,
371 entry_id: DefId,
372 entry_type: MiriEntryFnType,
373 config: &MiriConfig,
374 genmc_ctx: Option<Rc<GenmcCtx>>,
375) -> InterpResult<'tcx, InterpCx<'tcx, MiriMachine<'tcx>>> {
376 let typing_env = ty::TypingEnv::fully_monomorphized();
377 let layout_cx = LayoutCx::new(tcx, typing_env);
378 let mut ecx = InterpCx::new(
379 tcx,
380 rustc_span::DUMMY_SP,
381 typing_env,
382 MiriMachine::new(config, layout_cx, genmc_ctx),
383 );
384
385 if !is_no_core(tcx) {
388 let sentinel = helpers::try_resolve_path(
389 tcx,
390 &["core", "ascii", "escape_default"],
391 Namespace::ValueNS,
392 );
393 if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
394 tcx.dcx().fatal(
395 "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
396 Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
397 );
398 }
399 }
400
401 let argc =
403 ImmTy::from_int(i64::try_from(config.args.len()).unwrap(), ecx.machine.layouts.isize);
404 let argv = {
405 let mut argvs = Vec::<Immediate<Provenance>>::with_capacity(config.args.len());
407 for arg in config.args.iter() {
408 let size = u64::try_from(arg.len()).unwrap().strict_add(1);
410 let arg_type = Ty::new_array(tcx, tcx.types.u8, size);
411 let arg_place =
412 ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?;
413 ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr(), size)?;
414 ecx.mark_immutable(&arg_place);
415 argvs.push(arg_place.to_ref(&ecx));
416 }
417 let u8_ptr_type = Ty::new_imm_ptr(tcx, tcx.types.u8);
419 let u8_ptr_ptr_type = Ty::new_imm_ptr(tcx, u8_ptr_type);
420 let argvs_layout =
421 ecx.layout_of(Ty::new_array(tcx, u8_ptr_type, u64::try_from(argvs.len()).unwrap()))?;
422 let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
423 for (arg, idx) in argvs.into_iter().zip(0..) {
424 let place = ecx.project_index(&argvs_place, idx)?;
425 ecx.write_immediate(arg, &place)?;
426 }
427 ecx.mark_immutable(&argvs_place);
428 {
430 let argc_place =
431 ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
432 ecx.write_immediate(*argc, &argc_place)?;
433 ecx.mark_immutable(&argc_place);
434 ecx.machine.argc = Some(argc_place.ptr());
435
436 let argv_place =
437 ecx.allocate(ecx.layout_of(u8_ptr_ptr_type)?, MiriMemoryKind::Machine.into())?;
438 ecx.write_pointer(argvs_place.ptr(), &argv_place)?;
439 ecx.mark_immutable(&argv_place);
440 ecx.machine.argv = Some(argv_place.ptr());
441 }
442 if tcx.sess.target.os == Os::Windows {
444 let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
446
447 let cmd_type =
448 Ty::new_array(tcx, tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
449 let cmd_place =
450 ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
451 ecx.machine.cmd_line = Some(cmd_place.ptr());
452 for (&c, idx) in cmd_utf16.iter().zip(0..) {
454 let place = ecx.project_index(&cmd_place, idx)?;
455 ecx.write_scalar(Scalar::from_u16(c), &place)?;
456 }
457 ecx.mark_immutable(&cmd_place);
458 }
459 let imm = argvs_place.to_ref(&ecx);
460 let layout = ecx.layout_of(u8_ptr_ptr_type)?;
461 ImmTy::from_immediate(imm, layout)
462 };
463
464 MiriMachine::late_init(&mut ecx, config, {
466 let mut main_thread_state = MainThreadState::GlobalCtors {
467 entry_id,
468 entry_type,
469 argc,
470 argv,
471 ctor_state: global_ctor::GlobalCtorState::default(),
472 };
473
474 Box::new(move |m| main_thread_state.on_main_stack_empty(m))
478 })?;
479
480 interp_ok(ecx)
481}
482
483fn call_main<'tcx>(
485 ecx: &mut MiriInterpCx<'tcx>,
486 entry_id: DefId,
487 entry_type: MiriEntryFnType,
488 argc: ImmTy<'tcx>,
489 argv: ImmTy<'tcx>,
490) -> InterpResult<'tcx, ()> {
491 let tcx = ecx.tcx();
492
493 let entry_instance = ty::Instance::mono(tcx, entry_id);
495
496 let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
498 ecx.machine.main_fn_ret_place = Some(ret_place.clone());
499
500 match entry_type {
502 MiriEntryFnType::Rustc(EntryFnType::Main { .. }) => {
503 let entry_sig = tcx.fn_sig(entry_id).no_bound_vars().unwrap();
504 let main_ret_ty = entry_sig.output();
505 let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
506
507 let start_id = tcx.lang_items().start_fn().unwrap_or_else(|| {
508 tcx.dcx().fatal("could not find start lang item");
509 });
510 let start_instance = ty::Instance::try_resolve(
511 tcx,
512 ecx.typing_env(),
513 start_id,
514 tcx.mk_args(&[ty::GenericArg::from(main_ret_ty)]),
515 )
516 .unwrap()
517 .unwrap();
518
519 let main_ptr = ecx.fn_ptr(FnVal::Instance(entry_instance));
520
521 let sigpipe = rustc_session::config::sigpipe::DEFAULT;
524
525 ecx.call_function(
526 start_instance,
527 ExternAbi::Rust,
528 &[
529 ImmTy::from_scalar(
530 Scalar::from_pointer(main_ptr, ecx),
531 ecx.layout_of(Ty::new_fn_ptr(tcx, entry_sig)).unwrap(),
532 ),
533 argc,
534 argv,
535 ImmTy::from_uint(sigpipe, ecx.machine.layouts.u8),
536 ],
537 Some(&ret_place),
538 ReturnContinuation::Stop { cleanup: true },
539 )?;
540 }
541 MiriEntryFnType::MiriStart => {
542 ecx.call_function(
543 entry_instance,
544 ExternAbi::Rust,
545 &[argc, argv],
546 Some(&ret_place),
547 ReturnContinuation::Stop { cleanup: true },
548 )?;
549 }
550 }
551
552 interp_ok(())
553}
554
555pub fn eval_entry<'tcx>(
559 tcx: TyCtxt<'tcx>,
560 entry_id: DefId,
561 entry_type: MiriEntryFnType,
562 config: &MiriConfig,
563 genmc_ctx: Option<Rc<GenmcCtx>>,
564) -> Result<(), NonZeroI32> {
565 let ignore_leaks = config.ignore_leaks;
567
568 let mut ecx = match create_ecx(tcx, entry_id, entry_type, config, genmc_ctx).report_err() {
569 Ok(v) => v,
570 Err(err) => {
571 let (kind, backtrace) = err.into_parts();
572 backtrace.print_backtrace();
573 panic!("Miri initialization error: {kind:?}")
574 }
575 };
576
577 let res: thread::Result<InterpResult<'_, !>> =
579 panic::catch_unwind(AssertUnwindSafe(|| ecx.run_threads()));
580 let res = res.unwrap_or_else(|panic_payload| {
581 if !panic_payload.is::<FatalErrorMarker>() {
584 ecx.handle_ice();
585 }
586 panic::resume_unwind(panic_payload)
587 });
588 let Err(res) = res.report_err();
591
592 'miri_error: {
594 let Some((return_code, leak_check)) = report_result(&ecx, res) else {
596 break 'miri_error;
597 };
598
599 if leak_check && !ignore_leaks {
602 if !ecx.have_all_terminated() {
604 tcx.dcx()
605 .err("the main thread terminated without waiting for all remaining threads");
606 tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
607 break 'miri_error;
608 }
609 info!("Additional static roots: {:?}", ecx.machine.static_roots);
611 let leaks = ecx.take_leaked_allocations(|ecx| &ecx.machine.static_roots);
612 if !leaks.is_empty() {
613 report_leaks(&ecx, leaks);
614 tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
615 break 'miri_error;
618 }
619 }
620
621 return match NonZeroI32::new(return_code) {
624 None => Ok(()),
625 Some(return_code) => Err(return_code),
626 };
627 }
628
629 assert!(tcx.dcx().has_errors().is_some());
631 Err(NonZeroI32::new(rustc_driver::EXIT_FAILURE).unwrap())
632}
633
634fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16>
645where
646 I: Iterator<Item = T>,
647 T: AsRef<str>,
648{
649 let mut cmd = {
651 let Some(arg0) = args.next() else {
652 return vec![0];
653 };
654 let arg0 = arg0.as_ref();
655 if arg0.contains('"') {
656 panic!("argv[0] cannot contain a doublequote (\") character");
657 } else {
658 let mut s = String::new();
660 s.push('"');
661 s.push_str(arg0);
662 s.push('"');
663 s
664 }
665 };
666
667 for arg in args {
669 let arg = arg.as_ref();
670 cmd.push(' ');
671 if arg.is_empty() {
672 cmd.push_str("\"\"");
673 } else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) {
674 cmd.push_str(arg);
676 } else {
677 cmd.push('"');
684 let mut chars = arg.chars().peekable();
685 loop {
686 let mut nslashes = 0;
687 while let Some(&'\\') = chars.peek() {
688 chars.next();
689 nslashes += 1;
690 }
691
692 match chars.next() {
693 Some('"') => {
694 cmd.extend(iter::repeat_n('\\', nslashes * 2 + 1));
695 cmd.push('"');
696 }
697 Some(c) => {
698 cmd.extend(iter::repeat_n('\\', nslashes));
699 cmd.push(c);
700 }
701 None => {
702 cmd.extend(iter::repeat_n('\\', nslashes * 2));
703 break;
704 }
705 }
706 }
707 cmd.push('"');
708 }
709 }
710
711 if cmd.contains('\0') {
712 panic!("interior null in command line arguments");
713 }
714 cmd.encode_utf16().chain(iter::once(0)).collect()
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720 #[test]
721 #[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")]
722 fn windows_argv0_panic_on_quote() {
723 args_to_utf16_command_string(["\""].iter());
724 }
725 #[test]
726 fn windows_argv0_no_escape() {
727 let cmd = String::from_utf16_lossy(&args_to_utf16_command_string(
729 [r"C:\Program Files\", "arg1", "arg 2", "arg \" 3"].iter(),
730 ));
731 assert_eq!(cmd.trim_end_matches('\0'), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#);
732 }
733}