1use std::ffi::{OsStr, OsString};
4use std::panic::{self, AssertUnwindSafe};
5use std::path::PathBuf;
6use std::rc::Rc;
7use std::task::Poll;
8use std::{iter, thread};
9
10use rustc_abi::ExternAbi;
11use rustc_data_structures::fx::{FxHashMap, FxHashSet};
12use rustc_hir::def::Namespace;
13use rustc_hir::def_id::DefId;
14use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutCx};
15use rustc_middle::ty::{self, Ty, TyCtxt};
16use rustc_session::config::EntryFnType;
17
18use crate::concurrency::GenmcCtx;
19use crate::concurrency::thread::TlsAllocAction;
20use crate::diagnostics::report_leaks;
21use crate::shims::{global_ctor, tls};
22use crate::*;
23
24#[derive(Copy, Clone, Debug)]
25pub enum MiriEntryFnType {
26 MiriStart,
27 Rustc(EntryFnType),
28}
29
30const MAIN_THREAD_YIELDS_AT_SHUTDOWN: u32 = 256;
34
35#[derive(Clone)]
37pub struct MiriConfig {
38 pub env: Vec<(OsString, OsString)>,
41 pub validation: ValidationMode,
43 pub borrow_tracker: Option<BorrowTrackerMethod>,
45 pub check_alignment: AlignmentCheck,
47 pub isolated_op: IsolatedOp,
49 pub ignore_leaks: bool,
51 pub forwarded_env_vars: Vec<String>,
53 pub set_env_vars: FxHashMap<String, String>,
55 pub args: Vec<String>,
57 pub seed: Option<u64>,
59 pub tracked_pointer_tags: FxHashSet<BorTag>,
61 pub tracked_alloc_ids: FxHashSet<AllocId>,
63 pub track_alloc_accesses: bool,
65 pub data_race_detector: bool,
67 pub weak_memory_emulation: bool,
69 pub genmc_config: Option<GenmcConfig>,
71 pub track_outdated_loads: bool,
73 pub cmpxchg_weak_failure_rate: f64,
76 pub measureme_out: Option<String>,
79 pub backtrace_style: BacktraceStyle,
81 pub provenance_mode: ProvenanceMode,
83 pub mute_stdout_stderr: bool,
86 pub preemption_rate: f64,
88 pub report_progress: Option<u32>,
90 pub retag_fields: RetagFields,
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 retag_fields: RetagFields::Yes,
150 native_lib: vec![],
151 native_lib_enable_tracing: false,
152 gc_interval: 10_000,
153 num_cpus: 1,
154 page_size: None,
155 collect_leak_backtraces: true,
156 address_reuse_rate: 0.5,
157 address_reuse_cross_thread_rate: 0.1,
158 fixed_scheduling: false,
159 force_intrinsic_fallback: false,
160 float_nondet: true,
161 float_rounding_error: FloatRoundingErrorMode::Random,
162 short_fd_operations: true,
163 user_relevant_crates: vec![],
164 }
165 }
166}
167
168#[derive(Debug)]
170enum MainThreadState<'tcx> {
171 GlobalCtors {
172 ctor_state: global_ctor::GlobalCtorState<'tcx>,
173 entry_id: DefId,
175 entry_type: MiriEntryFnType,
176 argc: ImmTy<'tcx>,
178 argv: ImmTy<'tcx>,
179 },
180 Running,
181 TlsDtors(tls::TlsDtorsState<'tcx>),
182 Yield {
183 remaining: u32,
184 },
185 Done,
186}
187
188impl<'tcx> MainThreadState<'tcx> {
189 fn on_main_stack_empty(
190 &mut self,
191 this: &mut MiriInterpCx<'tcx>,
192 ) -> InterpResult<'tcx, Poll<()>> {
193 use MainThreadState::*;
194 match self {
195 GlobalCtors { ctor_state, entry_id, entry_type, argc, argv } => {
196 match ctor_state.on_stack_empty(this)? {
197 Poll::Pending => {} Poll::Ready(()) => {
199 call_main(this, *entry_id, *entry_type, argc.clone(), argv.clone())?;
200 *self = Running;
201 }
202 }
203 }
204 Running => {
205 *self = TlsDtors(Default::default());
206 }
207 TlsDtors(state) =>
208 match state.on_stack_empty(this)? {
209 Poll::Pending => {} Poll::Ready(()) => {
211 if this.machine.data_race.as_genmc_ref().is_some() {
212 *self = Done;
215 } else {
216 if this.machine.preemption_rate > 0.0 {
219 *self = Yield { remaining: MAIN_THREAD_YIELDS_AT_SHUTDOWN };
222 } else {
223 *self = Done;
226 }
227 }
228 }
229 },
230 Yield { remaining } =>
231 match remaining.checked_sub(1) {
232 None => *self = Done,
233 Some(new_remaining) => {
234 *remaining = new_remaining;
235 this.yield_active_thread();
236 }
237 },
238 Done => {
239 let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
241 let exit_code = this.read_target_isize(&ret_place)?;
242 let exit_code = i32::try_from(exit_code).unwrap_or(if exit_code >= 0 {
245 i32::MAX
246 } else {
247 i32::MIN
248 });
249 this.terminate_active_thread(TlsAllocAction::Leak)?;
252
253 if let Some(genmc_ctx) = this.machine.data_race.as_genmc_ref() {
255 genmc_ctx.handle_exit(
257 ThreadId::MAIN_THREAD,
258 exit_code,
259 crate::concurrency::ExitType::MainThreadFinish,
260 )?;
261 } else {
262 throw_machine_stop!(TerminationInfo::Exit {
264 code: exit_code,
265 leak_check: true
266 });
267 }
268 }
269 }
270 interp_ok(Poll::Pending)
271 }
272}
273
274pub fn create_ecx<'tcx>(
277 tcx: TyCtxt<'tcx>,
278 entry_id: DefId,
279 entry_type: MiriEntryFnType,
280 config: &MiriConfig,
281 genmc_ctx: Option<Rc<GenmcCtx>>,
282) -> InterpResult<'tcx, InterpCx<'tcx, MiriMachine<'tcx>>> {
283 let typing_env = ty::TypingEnv::fully_monomorphized();
284 let layout_cx = LayoutCx::new(tcx, typing_env);
285 let mut ecx = InterpCx::new(
286 tcx,
287 rustc_span::DUMMY_SP,
288 typing_env,
289 MiriMachine::new(config, layout_cx, genmc_ctx),
290 );
291
292 let sentinel =
294 helpers::try_resolve_path(tcx, &["core", "ascii", "escape_default"], Namespace::ValueNS);
295 if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
296 tcx.dcx().fatal(
297 "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
298 Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
299 );
300 }
301
302 let argc =
304 ImmTy::from_int(i64::try_from(config.args.len()).unwrap(), ecx.machine.layouts.isize);
305 let argv = {
306 let mut argvs = Vec::<Immediate<Provenance>>::with_capacity(config.args.len());
308 for arg in config.args.iter() {
309 let size = u64::try_from(arg.len()).unwrap().strict_add(1);
311 let arg_type = Ty::new_array(tcx, tcx.types.u8, size);
312 let arg_place =
313 ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?;
314 ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr(), size)?;
315 ecx.mark_immutable(&arg_place);
316 argvs.push(arg_place.to_ref(&ecx));
317 }
318 let u8_ptr_type = Ty::new_imm_ptr(tcx, tcx.types.u8);
320 let u8_ptr_ptr_type = Ty::new_imm_ptr(tcx, u8_ptr_type);
321 let argvs_layout =
322 ecx.layout_of(Ty::new_array(tcx, u8_ptr_type, u64::try_from(argvs.len()).unwrap()))?;
323 let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
324 for (arg, idx) in argvs.into_iter().zip(0..) {
325 let place = ecx.project_index(&argvs_place, idx)?;
326 ecx.write_immediate(arg, &place)?;
327 }
328 ecx.mark_immutable(&argvs_place);
329 {
331 let argc_place =
332 ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
333 ecx.write_immediate(*argc, &argc_place)?;
334 ecx.mark_immutable(&argc_place);
335 ecx.machine.argc = Some(argc_place.ptr());
336
337 let argv_place =
338 ecx.allocate(ecx.layout_of(u8_ptr_ptr_type)?, MiriMemoryKind::Machine.into())?;
339 ecx.write_pointer(argvs_place.ptr(), &argv_place)?;
340 ecx.mark_immutable(&argv_place);
341 ecx.machine.argv = Some(argv_place.ptr());
342 }
343 if tcx.sess.target.os == "windows" {
345 let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
347
348 let cmd_type =
349 Ty::new_array(tcx, tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
350 let cmd_place =
351 ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
352 ecx.machine.cmd_line = Some(cmd_place.ptr());
353 for (&c, idx) in cmd_utf16.iter().zip(0..) {
355 let place = ecx.project_index(&cmd_place, idx)?;
356 ecx.write_scalar(Scalar::from_u16(c), &place)?;
357 }
358 ecx.mark_immutable(&cmd_place);
359 }
360 let imm = argvs_place.to_ref(&ecx);
361 let layout = ecx.layout_of(u8_ptr_ptr_type)?;
362 ImmTy::from_immediate(imm, layout)
363 };
364
365 MiriMachine::late_init(&mut ecx, config, {
367 let mut main_thread_state = MainThreadState::GlobalCtors {
368 entry_id,
369 entry_type,
370 argc,
371 argv,
372 ctor_state: global_ctor::GlobalCtorState::default(),
373 };
374
375 Box::new(move |m| main_thread_state.on_main_stack_empty(m))
379 })?;
380
381 interp_ok(ecx)
382}
383
384fn call_main<'tcx>(
386 ecx: &mut MiriInterpCx<'tcx>,
387 entry_id: DefId,
388 entry_type: MiriEntryFnType,
389 argc: ImmTy<'tcx>,
390 argv: ImmTy<'tcx>,
391) -> InterpResult<'tcx, ()> {
392 let tcx = ecx.tcx();
393
394 let entry_instance = ty::Instance::mono(tcx, entry_id);
396
397 let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
399 ecx.machine.main_fn_ret_place = Some(ret_place.clone());
400
401 match entry_type {
403 MiriEntryFnType::Rustc(EntryFnType::Main { .. }) => {
404 let start_id = tcx.lang_items().start_fn().unwrap_or_else(|| {
405 tcx.dcx().fatal("could not find start lang item");
406 });
407 let main_ret_ty = tcx.fn_sig(entry_id).no_bound_vars().unwrap().output();
408 let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
409 let start_instance = ty::Instance::try_resolve(
410 tcx,
411 ecx.typing_env(),
412 start_id,
413 tcx.mk_args(&[ty::GenericArg::from(main_ret_ty)]),
414 )
415 .unwrap()
416 .unwrap();
417
418 let main_ptr = ecx.fn_ptr(FnVal::Instance(entry_instance));
419
420 let sigpipe = rustc_session::config::sigpipe::DEFAULT;
423
424 ecx.call_function(
425 start_instance,
426 ExternAbi::Rust,
427 &[
428 ImmTy::from_scalar(
429 Scalar::from_pointer(main_ptr, ecx),
430 ecx.machine.layouts.const_raw_ptr,
432 ),
433 argc,
434 argv,
435 ImmTy::from_uint(sigpipe, ecx.machine.layouts.u8),
436 ],
437 Some(&ret_place),
438 ReturnContinuation::Stop { cleanup: true },
439 )?;
440 }
441 MiriEntryFnType::MiriStart => {
442 ecx.call_function(
443 entry_instance,
444 ExternAbi::Rust,
445 &[argc, argv],
446 Some(&ret_place),
447 ReturnContinuation::Stop { cleanup: true },
448 )?;
449 }
450 }
451
452 interp_ok(())
453}
454
455pub fn eval_entry<'tcx>(
459 tcx: TyCtxt<'tcx>,
460 entry_id: DefId,
461 entry_type: MiriEntryFnType,
462 config: &MiriConfig,
463 genmc_ctx: Option<Rc<GenmcCtx>>,
464) -> Option<i32> {
465 let ignore_leaks = config.ignore_leaks;
467
468 let mut ecx = match create_ecx(tcx, entry_id, entry_type, config, genmc_ctx).report_err() {
469 Ok(v) => v,
470 Err(err) => {
471 let (kind, backtrace) = err.into_parts();
472 backtrace.print_backtrace();
473 panic!("Miri initialization error: {kind:?}")
474 }
475 };
476
477 let res: thread::Result<InterpResult<'_, !>> =
479 panic::catch_unwind(AssertUnwindSafe(|| ecx.run_threads()));
480 let res = res.unwrap_or_else(|panic_payload| {
481 ecx.handle_ice();
482 panic::resume_unwind(panic_payload)
483 });
484 let Err(err) = res.report_err();
487
488 let (return_code, leak_check) = report_error(&ecx, err)?;
490
491 if leak_check && !ignore_leaks {
495 if !ecx.have_all_terminated() {
497 tcx.dcx().err("the main thread terminated without waiting for all remaining threads");
498 tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
499 return None;
500 }
501 info!("Additional static roots: {:?}", ecx.machine.static_roots);
503 let leaks = ecx.take_leaked_allocations(|ecx| &ecx.machine.static_roots);
504 if !leaks.is_empty() {
505 report_leaks(&ecx, leaks);
506 tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
507 return None;
510 }
511 }
512 Some(return_code)
513}
514
515fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16>
526where
527 I: Iterator<Item = T>,
528 T: AsRef<str>,
529{
530 let mut cmd = {
532 let arg0 = if let Some(arg0) = args.next() {
533 arg0
534 } else {
535 return vec![0];
536 };
537 let arg0 = arg0.as_ref();
538 if arg0.contains('"') {
539 panic!("argv[0] cannot contain a doublequote (\") character");
540 } else {
541 let mut s = String::new();
543 s.push('"');
544 s.push_str(arg0);
545 s.push('"');
546 s
547 }
548 };
549
550 for arg in args {
552 let arg = arg.as_ref();
553 cmd.push(' ');
554 if arg.is_empty() {
555 cmd.push_str("\"\"");
556 } else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) {
557 cmd.push_str(arg);
559 } else {
560 cmd.push('"');
567 let mut chars = arg.chars().peekable();
568 loop {
569 let mut nslashes = 0;
570 while let Some(&'\\') = chars.peek() {
571 chars.next();
572 nslashes += 1;
573 }
574
575 match chars.next() {
576 Some('"') => {
577 cmd.extend(iter::repeat_n('\\', nslashes * 2 + 1));
578 cmd.push('"');
579 }
580 Some(c) => {
581 cmd.extend(iter::repeat_n('\\', nslashes));
582 cmd.push(c);
583 }
584 None => {
585 cmd.extend(iter::repeat_n('\\', nslashes * 2));
586 break;
587 }
588 }
589 }
590 cmd.push('"');
591 }
592 }
593
594 if cmd.contains('\0') {
595 panic!("interior null in command line arguments");
596 }
597 cmd.encode_utf16().chain(iter::once(0)).collect()
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603 #[test]
604 #[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")]
605 fn windows_argv0_panic_on_quote() {
606 args_to_utf16_command_string(["\""].iter());
607 }
608 #[test]
609 fn windows_argv0_no_escape() {
610 let cmd = String::from_utf16_lossy(&args_to_utf16_command_string(
612 [r"C:\Program Files\", "arg1", "arg 2", "arg \" 3"].iter(),
613 ));
614 assert_eq!(cmd.trim_end_matches('\0'), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#);
615 }
616}