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}
119
120impl Default for MiriConfig {
121 fn default() -> MiriConfig {
122 MiriConfig {
123 env: vec![],
124 validation: ValidationMode::Shallow,
125 borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows),
126 check_alignment: AlignmentCheck::Int,
127 isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
128 ignore_leaks: false,
129 forwarded_env_vars: vec![],
130 set_env_vars: FxHashMap::default(),
131 args: vec![],
132 seed: None,
133 tracked_pointer_tags: FxHashSet::default(),
134 tracked_alloc_ids: FxHashSet::default(),
135 track_alloc_accesses: false,
136 data_race_detector: true,
137 weak_memory_emulation: true,
138 genmc_config: None,
139 track_outdated_loads: false,
140 cmpxchg_weak_failure_rate: 0.8, measureme_out: None,
142 backtrace_style: BacktraceStyle::Short,
143 provenance_mode: ProvenanceMode::Default,
144 mute_stdout_stderr: false,
145 preemption_rate: 0.01, report_progress: None,
147 retag_fields: RetagFields::Yes,
148 native_lib: vec![],
149 native_lib_enable_tracing: false,
150 gc_interval: 10_000,
151 num_cpus: 1,
152 page_size: None,
153 collect_leak_backtraces: true,
154 address_reuse_rate: 0.5,
155 address_reuse_cross_thread_rate: 0.1,
156 fixed_scheduling: false,
157 force_intrinsic_fallback: false,
158 float_nondet: true,
159 float_rounding_error: FloatRoundingErrorMode::Random,
160 short_fd_operations: true,
161 }
162 }
163}
164
165#[derive(Debug)]
167enum MainThreadState<'tcx> {
168 GlobalCtors {
169 ctor_state: global_ctor::GlobalCtorState<'tcx>,
170 entry_id: DefId,
172 entry_type: MiriEntryFnType,
173 argc: ImmTy<'tcx>,
175 argv: ImmTy<'tcx>,
176 },
177 Running,
178 TlsDtors(tls::TlsDtorsState<'tcx>),
179 Yield {
180 remaining: u32,
181 },
182 Done,
183}
184
185impl<'tcx> MainThreadState<'tcx> {
186 fn on_main_stack_empty(
187 &mut self,
188 this: &mut MiriInterpCx<'tcx>,
189 ) -> InterpResult<'tcx, Poll<()>> {
190 use MainThreadState::*;
191 match self {
192 GlobalCtors { ctor_state, entry_id, entry_type, argc, argv } => {
193 match ctor_state.on_stack_empty(this)? {
194 Poll::Pending => {} Poll::Ready(()) => {
196 call_main(this, *entry_id, *entry_type, argc.clone(), argv.clone())?;
197 *self = Running;
198 }
199 }
200 }
201 Running => {
202 *self = TlsDtors(Default::default());
203 }
204 TlsDtors(state) =>
205 match state.on_stack_empty(this)? {
206 Poll::Pending => {} Poll::Ready(()) => {
208 if this.machine.data_race.as_genmc_ref().is_some() {
209 *self = Done;
212 } else {
213 if this.machine.preemption_rate > 0.0 {
216 *self = Yield { remaining: MAIN_THREAD_YIELDS_AT_SHUTDOWN };
219 } else {
220 *self = Done;
223 }
224 }
225 }
226 },
227 Yield { remaining } =>
228 match remaining.checked_sub(1) {
229 None => *self = Done,
230 Some(new_remaining) => {
231 *remaining = new_remaining;
232 this.yield_active_thread();
233 }
234 },
235 Done => {
236 let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
238 let exit_code = this.read_target_isize(&ret_place)?;
239 let exit_code = i32::try_from(exit_code).unwrap_or(if exit_code >= 0 {
242 i32::MAX
243 } else {
244 i32::MIN
245 });
246 this.terminate_active_thread(TlsAllocAction::Leak)?;
249
250 throw_machine_stop!(TerminationInfo::Exit { code: exit_code, leak_check: true });
252 }
253 }
254 interp_ok(Poll::Pending)
255 }
256}
257
258pub fn create_ecx<'tcx>(
261 tcx: TyCtxt<'tcx>,
262 entry_id: DefId,
263 entry_type: MiriEntryFnType,
264 config: &MiriConfig,
265 genmc_ctx: Option<Rc<GenmcCtx>>,
266) -> InterpResult<'tcx, InterpCx<'tcx, MiriMachine<'tcx>>> {
267 let typing_env = ty::TypingEnv::fully_monomorphized();
268 let layout_cx = LayoutCx::new(tcx, typing_env);
269 let mut ecx = InterpCx::new(
270 tcx,
271 rustc_span::DUMMY_SP,
272 typing_env,
273 MiriMachine::new(config, layout_cx, genmc_ctx),
274 );
275
276 let sentinel =
278 helpers::try_resolve_path(tcx, &["core", "ascii", "escape_default"], Namespace::ValueNS);
279 if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
280 tcx.dcx().fatal(
281 "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
282 Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
283 );
284 }
285
286 let argc =
288 ImmTy::from_int(i64::try_from(config.args.len()).unwrap(), ecx.machine.layouts.isize);
289 let argv = {
290 let mut argvs = Vec::<Immediate<Provenance>>::with_capacity(config.args.len());
292 for arg in config.args.iter() {
293 let size = u64::try_from(arg.len()).unwrap().strict_add(1);
295 let arg_type = Ty::new_array(tcx, tcx.types.u8, size);
296 let arg_place =
297 ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?;
298 ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr(), size)?;
299 ecx.mark_immutable(&arg_place);
300 argvs.push(arg_place.to_ref(&ecx));
301 }
302 let u8_ptr_type = Ty::new_imm_ptr(tcx, tcx.types.u8);
304 let u8_ptr_ptr_type = Ty::new_imm_ptr(tcx, u8_ptr_type);
305 let argvs_layout =
306 ecx.layout_of(Ty::new_array(tcx, u8_ptr_type, u64::try_from(argvs.len()).unwrap()))?;
307 let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
308 for (arg, idx) in argvs.into_iter().zip(0..) {
309 let place = ecx.project_index(&argvs_place, idx)?;
310 ecx.write_immediate(arg, &place)?;
311 }
312 ecx.mark_immutable(&argvs_place);
313 {
315 let argc_place =
316 ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
317 ecx.write_immediate(*argc, &argc_place)?;
318 ecx.mark_immutable(&argc_place);
319 ecx.machine.argc = Some(argc_place.ptr());
320
321 let argv_place =
322 ecx.allocate(ecx.layout_of(u8_ptr_ptr_type)?, MiriMemoryKind::Machine.into())?;
323 ecx.write_pointer(argvs_place.ptr(), &argv_place)?;
324 ecx.mark_immutable(&argv_place);
325 ecx.machine.argv = Some(argv_place.ptr());
326 }
327 if tcx.sess.target.os == "windows" {
329 let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
331
332 let cmd_type =
333 Ty::new_array(tcx, tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
334 let cmd_place =
335 ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
336 ecx.machine.cmd_line = Some(cmd_place.ptr());
337 for (&c, idx) in cmd_utf16.iter().zip(0..) {
339 let place = ecx.project_index(&cmd_place, idx)?;
340 ecx.write_scalar(Scalar::from_u16(c), &place)?;
341 }
342 ecx.mark_immutable(&cmd_place);
343 }
344 let imm = argvs_place.to_ref(&ecx);
345 let layout = ecx.layout_of(u8_ptr_ptr_type)?;
346 ImmTy::from_immediate(imm, layout)
347 };
348
349 MiriMachine::late_init(&mut ecx, config, {
351 let mut main_thread_state = MainThreadState::GlobalCtors {
352 entry_id,
353 entry_type,
354 argc,
355 argv,
356 ctor_state: global_ctor::GlobalCtorState::default(),
357 };
358
359 Box::new(move |m| main_thread_state.on_main_stack_empty(m))
363 })?;
364
365 interp_ok(ecx)
366}
367
368fn call_main<'tcx>(
370 ecx: &mut MiriInterpCx<'tcx>,
371 entry_id: DefId,
372 entry_type: MiriEntryFnType,
373 argc: ImmTy<'tcx>,
374 argv: ImmTy<'tcx>,
375) -> InterpResult<'tcx, ()> {
376 let tcx = ecx.tcx();
377
378 let entry_instance = ty::Instance::mono(tcx, entry_id);
380
381 let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
383 ecx.machine.main_fn_ret_place = Some(ret_place.clone());
384
385 match entry_type {
387 MiriEntryFnType::Rustc(EntryFnType::Main { .. }) => {
388 let start_id = tcx.lang_items().start_fn().unwrap_or_else(|| {
389 tcx.dcx().fatal("could not find start lang item");
390 });
391 let main_ret_ty = tcx.fn_sig(entry_id).no_bound_vars().unwrap().output();
392 let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
393 let start_instance = ty::Instance::try_resolve(
394 tcx,
395 ecx.typing_env(),
396 start_id,
397 tcx.mk_args(&[ty::GenericArg::from(main_ret_ty)]),
398 )
399 .unwrap()
400 .unwrap();
401
402 let main_ptr = ecx.fn_ptr(FnVal::Instance(entry_instance));
403
404 let sigpipe = rustc_session::config::sigpipe::DEFAULT;
407
408 ecx.call_function(
409 start_instance,
410 ExternAbi::Rust,
411 &[
412 ImmTy::from_scalar(
413 Scalar::from_pointer(main_ptr, ecx),
414 ecx.machine.layouts.const_raw_ptr,
416 ),
417 argc,
418 argv,
419 ImmTy::from_uint(sigpipe, ecx.machine.layouts.u8),
420 ],
421 Some(&ret_place),
422 ReturnContinuation::Stop { cleanup: true },
423 )?;
424 }
425 MiriEntryFnType::MiriStart => {
426 ecx.call_function(
427 entry_instance,
428 ExternAbi::Rust,
429 &[argc, argv],
430 Some(&ret_place),
431 ReturnContinuation::Stop { cleanup: true },
432 )?;
433 }
434 }
435
436 interp_ok(())
437}
438
439pub fn eval_entry<'tcx>(
443 tcx: TyCtxt<'tcx>,
444 entry_id: DefId,
445 entry_type: MiriEntryFnType,
446 config: &MiriConfig,
447 genmc_ctx: Option<Rc<GenmcCtx>>,
448) -> Option<i32> {
449 let ignore_leaks = config.ignore_leaks;
451
452 if let Some(genmc_ctx) = &genmc_ctx {
453 genmc_ctx.handle_execution_start();
454 }
455
456 let mut ecx = match create_ecx(tcx, entry_id, entry_type, config, genmc_ctx).report_err() {
457 Ok(v) => v,
458 Err(err) => {
459 let (kind, backtrace) = err.into_parts();
460 backtrace.print_backtrace();
461 panic!("Miri initialization error: {kind:?}")
462 }
463 };
464
465 let res: thread::Result<InterpResult<'_, !>> =
467 panic::catch_unwind(AssertUnwindSafe(|| ecx.run_threads()));
468 let res = res.unwrap_or_else(|panic_payload| {
469 ecx.handle_ice();
470 panic::resume_unwind(panic_payload)
471 });
472 let Err(err) = res.report_err();
475
476 let (return_code, leak_check) = report_error(&ecx, err)?;
478
479 if let Some(genmc_ctx) = ecx.machine.data_race.as_genmc_ref()
481 && let Err(error) = genmc_ctx.handle_execution_end(&ecx)
482 {
483 tcx.dcx().err(format!("GenMC returned an error: \"{error}\""));
485 return None;
486 }
487
488 if leak_check && !ignore_leaks {
492 if !ecx.have_all_terminated() {
494 tcx.dcx().err("the main thread terminated without waiting for all remaining threads");
495 tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
496 return None;
497 }
498 info!("Additional static roots: {:?}", ecx.machine.static_roots);
500 let leaks = ecx.take_leaked_allocations(|ecx| &ecx.machine.static_roots);
501 if !leaks.is_empty() {
502 report_leaks(&ecx, leaks);
503 tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");
504 return None;
507 }
508 }
509 Some(return_code)
510}
511
512fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16>
523where
524 I: Iterator<Item = T>,
525 T: AsRef<str>,
526{
527 let mut cmd = {
529 let arg0 = if let Some(arg0) = args.next() {
530 arg0
531 } else {
532 return vec![0];
533 };
534 let arg0 = arg0.as_ref();
535 if arg0.contains('"') {
536 panic!("argv[0] cannot contain a doublequote (\") character");
537 } else {
538 let mut s = String::new();
540 s.push('"');
541 s.push_str(arg0);
542 s.push('"');
543 s
544 }
545 };
546
547 for arg in args {
549 let arg = arg.as_ref();
550 cmd.push(' ');
551 if arg.is_empty() {
552 cmd.push_str("\"\"");
553 } else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) {
554 cmd.push_str(arg);
556 } else {
557 cmd.push('"');
564 let mut chars = arg.chars().peekable();
565 loop {
566 let mut nslashes = 0;
567 while let Some(&'\\') = chars.peek() {
568 chars.next();
569 nslashes += 1;
570 }
571
572 match chars.next() {
573 Some('"') => {
574 cmd.extend(iter::repeat_n('\\', nslashes * 2 + 1));
575 cmd.push('"');
576 }
577 Some(c) => {
578 cmd.extend(iter::repeat_n('\\', nslashes));
579 cmd.push(c);
580 }
581 None => {
582 cmd.extend(iter::repeat_n('\\', nslashes * 2));
583 break;
584 }
585 }
586 }
587 cmd.push('"');
588 }
589 }
590
591 if cmd.contains('\0') {
592 panic!("interior null in command line arguments");
593 }
594 cmd.encode_utf16().chain(iter::once(0)).collect()
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 #[test]
601 #[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")]
602 fn windows_argv0_panic_on_quote() {
603 args_to_utf16_command_string(["\""].iter());
604 }
605 #[test]
606 fn windows_argv0_no_escape() {
607 let cmd = String::from_utf16_lossy(&args_to_utf16_command_string(
609 [r"C:\Program Files\", "arg1", "arg 2", "arg \" 3"].iter(),
610 ));
611 assert_eq!(cmd.trim_end_matches('\0'), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#);
612 }
613}