1 //! Main evaluator loop and setting up the initial stack frame.
3 use std::convert::TryFrom;
9 use rustc_hir::def_id::DefId;
10 use rustc_middle::ty::{
12 layout::{LayoutCx, LayoutOf},
15 use rustc_target::spec::abi::Abi;
17 use rustc_session::config::EntryFnType;
21 #[derive(Copy, Clone, Debug, PartialEq)]
22 pub enum AlignmentCheck {
23 /// Do not check alignment.
25 /// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address.
27 /// Check alignment on the actual physical integer address.
31 #[derive(Copy, Clone, Debug, PartialEq)]
32 pub enum RejectOpWith {
33 /// Isolated op is rejected with an abort of the machine.
36 /// If not Abort, miri returns an error for an isolated op.
37 /// Following options determine if user should be warned about such error.
38 /// Do not print warning about rejected isolated op.
41 /// Print a warning about rejected isolated op, with backtrace.
44 /// Print a warning about rejected isolated op, without backtrace.
45 WarningWithoutBacktrace,
48 #[derive(Copy, Clone, Debug, PartialEq)]
50 /// Reject an op requiring communication with the host. By
51 /// default, miri rejects the op with an abort. If not, it returns
52 /// an error code, and prints a warning about it. Warning levels
53 /// are controlled by `RejectOpWith` enum.
56 /// Execute op requiring communication with the host, i.e. disable isolation.
60 /// Configuration needed to spawn a Miri instance.
62 pub struct MiriConfig {
63 /// Determine if validity checking is enabled.
65 /// Determines if Stacked Borrows is enabled.
66 pub stacked_borrows: bool,
67 /// Controls alignment checking.
68 pub check_alignment: AlignmentCheck,
69 /// Controls integer and float validity (e.g., initialization) checking.
70 pub check_number_validity: bool,
71 /// Controls function [ABI](Abi) checking.
73 /// Action for an op requiring communication with the host.
74 pub isolated_op: IsolatedOp,
75 /// Determines if memory leaks should be ignored.
76 pub ignore_leaks: bool,
77 /// Environment variables that should always be isolated from the host.
78 pub excluded_env_vars: Vec<String>,
79 /// Command-line arguments passed to the interpreted program.
80 pub args: Vec<String>,
81 /// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`).
82 pub seed: Option<u64>,
83 /// The stacked borrows pointer id to report about
84 pub tracked_pointer_tag: Option<PtrId>,
85 /// The stacked borrows call ID to report about
86 pub tracked_call_id: Option<CallId>,
87 /// The allocation id to report about.
88 pub tracked_alloc_id: Option<AllocId>,
89 /// Whether to track raw pointers in stacked borrows.
91 /// Determine if data race detection should be enabled
92 pub data_race_detector: bool,
93 /// Rate of spurious failures for compare_exchange_weak atomic operations,
94 /// between 0.0 and 1.0, defaulting to 0.8 (80% chance of failure).
95 pub cmpxchg_weak_failure_rate: f64,
96 /// If `Some`, enable the `measureme` profiler, writing results to a file
97 /// with the specified prefix.
98 pub measureme_out: Option<String>,
99 /// Panic when unsupported functionality is encountered
100 pub panic_on_unsupported: bool,
103 impl Default for MiriConfig {
104 fn default() -> MiriConfig {
107 stacked_borrows: true,
108 check_alignment: AlignmentCheck::Int,
109 check_number_validity: false,
111 isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
113 excluded_env_vars: vec![],
116 tracked_pointer_tag: None,
117 tracked_call_id: None,
118 tracked_alloc_id: None,
120 data_race_detector: true,
121 cmpxchg_weak_failure_rate: 0.8,
123 panic_on_unsupported: false,
128 /// Returns a freshly created `InterpCx`, along with an `MPlaceTy` representing
129 /// the location where the return value of the `start` function will be
131 /// Public because this is also used by `priroda`.
132 pub fn create_ecx<'mir, 'tcx: 'mir>(
135 entry_type: EntryFnType,
137 ) -> InterpResult<'tcx, (InterpCx<'mir, 'tcx, Evaluator<'mir, 'tcx>>, MPlaceTy<'tcx, Tag>)> {
138 let param_env = ty::ParamEnv::reveal_all();
139 let layout_cx = LayoutCx { tcx, param_env };
140 let mut ecx = InterpCx::new(
142 rustc_span::source_map::DUMMY_SP,
144 Evaluator::new(&config, layout_cx),
145 MemoryExtra::new(&config),
147 // Complete initialization.
148 EnvVars::init(&mut ecx, config.excluded_env_vars)?;
149 MemoryExtra::init_extern_statics(&mut ecx)?;
151 // Make sure we have MIR. We check MIR for some stable monomorphic function in libcore.
152 let sentinel = ecx.resolve_path(&["core", "ascii", "escape_default"]);
153 if !tcx.is_mir_available(sentinel.def.def_id()) {
154 tcx.sess.fatal("the current sysroot was built without `-Zalways-encode-mir`. Use `cargo miri setup` to prepare a sysroot that is suitable for Miri.");
157 // Setup first stack-frame
158 let entry_instance = ty::Instance::mono(tcx, entry_id);
160 // First argument is constructed later, because its skipped if the entry function uses #[start]
162 // Second argument (argc): length of `config.args`.
163 let argc = Scalar::from_machine_usize(u64::try_from(config.args.len()).unwrap(), &ecx);
164 // Third argument (`argv`): created from `config.args`.
166 // Put each argument in memory, collect pointers.
167 let mut argvs = Vec::<Immediate<Tag>>::new();
168 for arg in config.args.iter() {
169 // Make space for `0` terminator.
170 let size = u64::try_from(arg.len()).unwrap().checked_add(1).unwrap();
171 let arg_type = tcx.mk_array(tcx.types.u8, size);
173 ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?;
174 ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr, size)?;
175 ecx.mark_immutable(&*arg_place);
176 argvs.push(arg_place.to_ref(&ecx));
178 // Make an array with all these pointers, in the Miri memory.
179 let argvs_layout = ecx.layout_of(
180 tcx.mk_array(tcx.mk_imm_ptr(tcx.types.u8), u64::try_from(argvs.len()).unwrap()),
182 let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
183 for (idx, arg) in argvs.into_iter().enumerate() {
184 let place = ecx.mplace_field(&argvs_place, idx)?;
185 ecx.write_immediate(arg, &place.into())?;
187 ecx.mark_immutable(&*argvs_place);
188 // A pointer to that place is the 3rd argument for main.
189 let argv = argvs_place.to_ref(&ecx);
190 // Store `argc` and `argv` for macOS `_NSGetArg{c,v}`.
193 ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
194 ecx.write_scalar(argc, &argc_place.into())?;
195 ecx.mark_immutable(&*argc_place);
196 ecx.machine.argc = Some(*argc_place);
198 let argv_place = ecx.allocate(
199 ecx.layout_of(tcx.mk_imm_ptr(tcx.types.unit))?,
200 MiriMemoryKind::Machine.into(),
202 ecx.write_immediate(argv, &argv_place.into())?;
203 ecx.mark_immutable(&*argv_place);
204 ecx.machine.argv = Some(*argv_place);
206 // Store command line as UTF-16 for Windows `GetCommandLineW`.
208 // Construct a command string with all the aguments.
209 let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
211 let cmd_type = tcx.mk_array(tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
213 ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
214 ecx.machine.cmd_line = Some(*cmd_place);
215 // Store the UTF-16 string. We just allocated so we know the bounds are fine.
216 for (idx, &c) in cmd_utf16.iter().enumerate() {
217 let place = ecx.mplace_field(&cmd_place, idx)?;
218 ecx.write_scalar(Scalar::from_u16(c), &place.into())?;
220 ecx.mark_immutable(&*cmd_place);
225 // Return place (in static memory so that it does not count as leak).
226 let ret_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?;
227 // Call start function.
230 EntryFnType::Main => {
231 let start_id = tcx.lang_items().start_fn().unwrap();
232 let main_ret_ty = tcx.fn_sig(entry_id).output();
233 let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
234 let start_instance = ty::Instance::resolve(
236 ty::ParamEnv::reveal_all(),
238 tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(main_ret_ty))),
243 let main_ptr = ecx.memory.create_fn_alloc(FnVal::Instance(entry_instance));
248 &[Scalar::from_pointer(main_ptr, &ecx).into(), argc.into(), argv],
249 Some(&ret_place.into()),
250 StackPopCleanup::Root { cleanup: true },
253 EntryFnType::Start => {
257 &[argc.into(), argv],
258 Some(&ret_place.into()),
259 StackPopCleanup::Root { cleanup: true },
267 /// Evaluates the entry function specified by `entry_id`.
268 /// Returns `Some(return_code)` if program executed completed.
269 /// Returns `None` if an evaluation error occured.
270 pub fn eval_entry<'tcx>(
273 entry_type: EntryFnType,
276 // Copy setting before we move `config`.
277 let ignore_leaks = config.ignore_leaks;
279 let (mut ecx, ret_place) = match create_ecx(tcx, entry_id, entry_type, config) {
282 err.print_backtrace();
283 panic!("Miri initialization error: {}", err.kind())
287 // Perform the main execution.
288 let res: InterpResult<'_, i64> = (|| {
291 let info = ecx.preprocess_diagnostics();
292 match ecx.schedule()? {
293 SchedulingAction::ExecuteStep => {
294 assert!(ecx.step()?, "a terminated thread was scheduled for execution");
296 SchedulingAction::ExecuteTimeoutCallback => {
298 ecx.machine.communicate(),
299 "scheduler callbacks require disabled isolation, but the code \
300 that created the callback did not check it"
302 ecx.run_timeout_callback()?;
304 SchedulingAction::ExecuteDtors => {
305 // This will either enable the thread again (so we go back
306 // to `ExecuteStep`), or determine that this thread is done
308 ecx.schedule_next_tls_dtor_for_active_thread()?;
310 SchedulingAction::Stop => {
314 ecx.process_diagnostics(info);
316 let return_code = ecx.read_scalar(&ret_place.into())?.to_machine_isize(&ecx)?;
321 EnvVars::cleanup(&mut ecx).unwrap();
323 // Process the result.
327 // Check for thread leaks.
328 if !ecx.have_all_terminated() {
330 "the main thread terminated without waiting for all remaining threads",
332 tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
335 // Check for memory leaks.
336 info!("Additonal static roots: {:?}", ecx.machine.static_roots);
337 let leaks = ecx.memory.leak_report(&ecx.machine.static_roots);
339 tcx.sess.err("the evaluated program leaked memory");
340 tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
341 // Ignore the provided return code - let the reported error
342 // determine the return code.
348 Err(e) => report_error(&ecx, e),
352 /// Turns an array of arguments into a Windows command line string.
354 /// The string will be UTF-16 encoded and NUL terminated.
356 /// Panics if the zeroth argument contains the `"` character because doublequotes
357 /// in argv[0] cannot be encoded using the standard command line parsing rules.
360 /// * [Parsing C++ command-line arguments](https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-160#parsing-c-command-line-arguments)
361 /// * [The C/C++ Parameter Parsing Rules](https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES)
362 fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16>
364 I: Iterator<Item = T>,
367 // Parse argv[0]. Slashes aren't escaped. Literal double quotes are not allowed.
369 let arg0 = if let Some(arg0) = args.next() {
374 let arg0 = arg0.as_ref();
375 if arg0.contains('"') {
376 panic!("argv[0] cannot contain a doublequote (\") character");
378 // Always surround argv[0] with quotes.
379 let mut s = String::new();
387 // Build the other arguments.
389 let arg = arg.as_ref();
392 cmd.push_str("\"\"");
393 } else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) {
394 // No quote, tab, or space -- no escaping required.
397 // Spaces and tabs are escaped by surrounding them in quotes.
398 // Quotes are themselves escaped by using backslashes when in a
400 // Backslashes only need to be escaped when one or more are directly
401 // followed by a quote. Otherwise they are taken literally.
404 let mut chars = arg.chars().peekable();
406 let mut nslashes = 0;
407 while let Some(&'\\') = chars.peek() {
414 cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1));
418 cmd.extend(iter::repeat('\\').take(nslashes));
422 cmd.extend(iter::repeat('\\').take(nslashes * 2));
431 if cmd.contains('\0') {
432 panic!("interior null in command line arguments");
434 cmd.encode_utf16().chain(iter::once(0)).collect()
441 #[should_panic(expected = "argv[0] cannot contain a doublequote (\") character")]
442 fn windows_argv0_panic_on_quote() {
443 args_to_utf16_command_string(["\""].iter());
446 fn windows_argv0_no_escape() {
447 // Ensure that a trailing backslash in argv[0] is not escaped.
448 let cmd = String::from_utf16_lossy(&args_to_utf16_command_string(
449 [r"C:\Program Files\", "arg1", "arg 2", "arg \" 3"].iter(),
451 assert_eq!(cmd.trim_end_matches("\0"), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#);