]> git.lizzy.rs Git - rust.git/blob - src/eval.rs
Auto merge of #1956 - RalfJung:stack-pop-cleanup, r=RalfJung
[rust.git] / src / eval.rs
1 //! Main evaluator loop and setting up the initial stack frame.
2
3 use std::convert::TryFrom;
4 use std::ffi::OsStr;
5 use std::iter;
6
7 use log::info;
8
9 use rustc_hir::def_id::DefId;
10 use rustc_middle::ty::{
11     self,
12     layout::{LayoutCx, LayoutOf},
13     TyCtxt,
14 };
15 use rustc_target::spec::abi::Abi;
16
17 use rustc_session::config::EntryFnType;
18
19 use crate::*;
20
21 #[derive(Copy, Clone, Debug, PartialEq)]
22 pub enum AlignmentCheck {
23     /// Do not check alignment.
24     None,
25     /// Check alignment "symbolically", i.e., using only the requested alignment for an allocation and not its real base address.
26     Symbolic,
27     /// Check alignment on the actual physical integer address.
28     Int,
29 }
30
31 #[derive(Copy, Clone, Debug, PartialEq)]
32 pub enum RejectOpWith {
33     /// Isolated op is rejected with an abort of the machine.
34     Abort,
35
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.
39     NoWarning,
40
41     /// Print a warning about rejected isolated op, with backtrace.
42     Warning,
43
44     /// Print a warning about rejected isolated op, without backtrace.
45     WarningWithoutBacktrace,
46 }
47
48 #[derive(Copy, Clone, Debug, PartialEq)]
49 pub enum IsolatedOp {
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.
54     Reject(RejectOpWith),
55
56     /// Execute op requiring communication with the host, i.e. disable isolation.
57     Allow,
58 }
59
60 /// Configuration needed to spawn a Miri instance.
61 #[derive(Clone)]
62 pub struct MiriConfig {
63     /// Determine if validity checking is enabled.
64     pub validate: bool,
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.
72     pub check_abi: bool,
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.
90     pub tag_raw: bool,
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,
101 }
102
103 impl Default for MiriConfig {
104     fn default() -> MiriConfig {
105         MiriConfig {
106             validate: true,
107             stacked_borrows: true,
108             check_alignment: AlignmentCheck::Int,
109             check_number_validity: false,
110             check_abi: true,
111             isolated_op: IsolatedOp::Reject(RejectOpWith::Abort),
112             ignore_leaks: false,
113             excluded_env_vars: vec![],
114             args: vec![],
115             seed: None,
116             tracked_pointer_tag: None,
117             tracked_call_id: None,
118             tracked_alloc_id: None,
119             tag_raw: false,
120             data_race_detector: true,
121             cmpxchg_weak_failure_rate: 0.8,
122             measureme_out: None,
123             panic_on_unsupported: false,
124         }
125     }
126 }
127
128 /// Returns a freshly created `InterpCx`, along with an `MPlaceTy` representing
129 /// the location where the return value of the `start` function will be
130 /// written to.
131 /// Public because this is also used by `priroda`.
132 pub fn create_ecx<'mir, 'tcx: 'mir>(
133     tcx: TyCtxt<'tcx>,
134     entry_id: DefId,
135     entry_type: EntryFnType,
136     config: MiriConfig,
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(
141         tcx,
142         rustc_span::source_map::DUMMY_SP,
143         param_env,
144         Evaluator::new(&config, layout_cx),
145         MemoryExtra::new(&config),
146     );
147     // Complete initialization.
148     EnvVars::init(&mut ecx, config.excluded_env_vars)?;
149     MemoryExtra::init_extern_statics(&mut ecx)?;
150
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.");
155     }
156
157     // Setup first stack-frame
158     let entry_instance = ty::Instance::mono(tcx, entry_id);
159
160     // First argument is constructed later, because its skipped if the entry function uses #[start]
161
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`.
165     let argv = {
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);
172             let arg_place =
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));
177         }
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()),
181         )?;
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())?;
186         }
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}`.
191         {
192             let argc_place =
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);
197
198             let argv_place = ecx.allocate(
199                 ecx.layout_of(tcx.mk_imm_ptr(tcx.types.unit))?,
200                 MiriMemoryKind::Machine.into(),
201             )?;
202             ecx.write_immediate(argv, &argv_place.into())?;
203             ecx.mark_immutable(&*argv_place);
204             ecx.machine.argv = Some(*argv_place);
205         }
206         // Store command line as UTF-16 for Windows `GetCommandLineW`.
207         {
208             // Construct a command string with all the aguments.
209             let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
210
211             let cmd_type = tcx.mk_array(tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
212             let cmd_place =
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())?;
219             }
220             ecx.mark_immutable(&*cmd_place);
221         }
222         argv
223     };
224
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.
228
229     match entry_type {
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(
235                 tcx,
236                 ty::ParamEnv::reveal_all(),
237                 start_id,
238                 tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(main_ret_ty))),
239             )
240             .unwrap()
241             .unwrap();
242
243             let main_ptr = ecx.memory.create_fn_alloc(FnVal::Instance(entry_instance));
244
245             ecx.call_function(
246                 start_instance,
247                 Abi::Rust,
248                 &[Scalar::from_pointer(main_ptr, &ecx).into(), argc.into(), argv],
249                 Some(&ret_place.into()),
250                 StackPopCleanup::Root { cleanup: true },
251             )?;
252         }
253         EntryFnType::Start => {
254             ecx.call_function(
255                 entry_instance,
256                 Abi::Rust,
257                 &[argc.into(), argv],
258                 Some(&ret_place.into()),
259                 StackPopCleanup::Root { cleanup: true },
260             )?;
261         }
262     }
263
264     Ok((ecx, ret_place))
265 }
266
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>(
271     tcx: TyCtxt<'tcx>,
272     entry_id: DefId,
273     entry_type: EntryFnType,
274     config: MiriConfig,
275 ) -> Option<i64> {
276     // Copy setting before we move `config`.
277     let ignore_leaks = config.ignore_leaks;
278
279     let (mut ecx, ret_place) = match create_ecx(tcx, entry_id, entry_type, config) {
280         Ok(v) => v,
281         Err(err) => {
282             err.print_backtrace();
283             panic!("Miri initialization error: {}", err.kind())
284         }
285     };
286
287     // Perform the main execution.
288     let res: InterpResult<'_, i64> = (|| {
289         // Main loop.
290         loop {
291             let info = ecx.preprocess_diagnostics();
292             match ecx.schedule()? {
293                 SchedulingAction::ExecuteStep => {
294                     assert!(ecx.step()?, "a terminated thread was scheduled for execution");
295                 }
296                 SchedulingAction::ExecuteTimeoutCallback => {
297                     assert!(
298                         ecx.machine.communicate(),
299                         "scheduler callbacks require disabled isolation, but the code \
300                         that created the callback did not check it"
301                     );
302                     ecx.run_timeout_callback()?;
303                 }
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
307                     // for good.
308                     ecx.schedule_next_tls_dtor_for_active_thread()?;
309                 }
310                 SchedulingAction::Stop => {
311                     break;
312                 }
313             }
314             ecx.process_diagnostics(info);
315         }
316         let return_code = ecx.read_scalar(&ret_place.into())?.to_machine_isize(&ecx)?;
317         Ok(return_code)
318     })();
319
320     // Machine cleanup.
321     EnvVars::cleanup(&mut ecx).unwrap();
322
323     // Process the result.
324     match res {
325         Ok(return_code) => {
326             if !ignore_leaks {
327                 // Check for thread leaks.
328                 if !ecx.have_all_terminated() {
329                     tcx.sess.err(
330                         "the main thread terminated without waiting for all remaining threads",
331                     );
332                     tcx.sess.note_without_error("pass `-Zmiri-ignore-leaks` to disable this check");
333                     return None;
334                 }
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);
338                 if leaks != 0 {
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.
343                     return None;
344                 }
345             }
346             Some(return_code)
347         }
348         Err(e) => report_error(&ecx, e),
349     }
350 }
351
352 /// Turns an array of arguments into a Windows command line string.
353 ///
354 /// The string will be UTF-16 encoded and NUL terminated.
355 ///
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.
358 ///
359 /// Further reading:
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>
363 where
364     I: Iterator<Item = T>,
365     T: AsRef<str>,
366 {
367     // Parse argv[0]. Slashes aren't escaped. Literal double quotes are not allowed.
368     let mut cmd = {
369         let arg0 = if let Some(arg0) = args.next() {
370             arg0
371         } else {
372             return vec![0];
373         };
374         let arg0 = arg0.as_ref();
375         if arg0.contains('"') {
376             panic!("argv[0] cannot contain a doublequote (\") character");
377         } else {
378             // Always surround argv[0] with quotes.
379             let mut s = String::new();
380             s.push('"');
381             s.push_str(arg0);
382             s.push('"');
383             s
384         }
385     };
386
387     // Build the other arguments.
388     for arg in args {
389         let arg = arg.as_ref();
390         cmd.push(' ');
391         if arg.is_empty() {
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.
395             cmd.push_str(arg);
396         } else {
397             // Spaces and tabs are escaped by surrounding them in quotes.
398             // Quotes are themselves escaped by using backslashes when in a
399             // quoted block.
400             // Backslashes only need to be escaped when one or more are directly
401             // followed by a quote. Otherwise they are taken literally.
402
403             cmd.push('"');
404             let mut chars = arg.chars().peekable();
405             loop {
406                 let mut nslashes = 0;
407                 while let Some(&'\\') = chars.peek() {
408                     chars.next();
409                     nslashes += 1;
410                 }
411
412                 match chars.next() {
413                     Some('"') => {
414                         cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1));
415                         cmd.push('"');
416                     }
417                     Some(c) => {
418                         cmd.extend(iter::repeat('\\').take(nslashes));
419                         cmd.push(c);
420                     }
421                     None => {
422                         cmd.extend(iter::repeat('\\').take(nslashes * 2));
423                         break;
424                     }
425                 }
426             }
427             cmd.push('"');
428         }
429     }
430
431     if cmd.contains('\0') {
432         panic!("interior null in command line arguments");
433     }
434     cmd.encode_utf16().chain(iter::once(0)).collect()
435 }
436
437 #[cfg(test)]
438 mod tests {
439     use super::*;
440     #[test]
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());
444     }
445     #[test]
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(),
450         ));
451         assert_eq!(cmd.trim_end_matches("\0"), r#""C:\Program Files\" arg1 "arg 2" "arg \" 3""#);
452     }
453 }