]> git.lizzy.rs Git - rust.git/blob - src/eval.rs
a82c40a99e011bcd57971e6e113df2bde7eb914a
[rust.git] / src / eval.rs
1 //! Main evaluator loop and setting up the initial stack frame.
2
3 use std::ffi::OsStr;
4
5 use rand::rngs::StdRng;
6 use rand::SeedableRng;
7
8 use rustc::ty::layout::{LayoutOf, Size};
9 use rustc::ty::{self, TyCtxt};
10 use rustc_hir::def_id::DefId;
11
12 use crate::*;
13
14 /// Configuration needed to spawn a Miri instance.
15 #[derive(Clone)]
16 pub struct MiriConfig {
17     /// Determine if validity checking is enabled.
18     pub validate: bool,
19     /// Determines if Stacked Borrows is enabled.
20     pub stacked_borrows: bool,
21     /// Determines if communication with the host environment is enabled.
22     pub communicate: bool,
23     /// Determines if memory leaks should be ignored.
24     pub ignore_leaks: bool,
25     /// Environment variables that should always be isolated from the host.
26     pub excluded_env_vars: Vec<String>,
27     /// Command-line arguments passed to the interpreted program.
28     pub args: Vec<String>,
29     /// The seed to use when non-determinism or randomness are required (e.g. ptr-to-int cast, `getrandom()`).
30     pub seed: Option<u64>,
31     /// The stacked borrow id to report about
32     pub tracked_pointer_tag: Option<PtrId>,
33     /// The allocation id to report about.
34     pub tracked_alloc_id: Option<AllocId>,
35 }
36
37 impl Default for MiriConfig {
38     fn default() -> MiriConfig {
39         MiriConfig {
40             validate: true,
41             stacked_borrows: true,
42             communicate: false,
43             ignore_leaks: false,
44             excluded_env_vars: vec![],
45             args: vec![],
46             seed: None,
47             tracked_pointer_tag: None,
48             tracked_alloc_id: None,
49         }
50     }
51 }
52
53 /// Details of premature program termination.
54 pub enum TerminationInfo {
55     Exit(i64),
56     Abort,
57 }
58
59 /// Returns a freshly created `InterpCx`, along with an `MPlaceTy` representing
60 /// the location where the return value of the `start` lang item will be
61 /// written to.
62 /// Public because this is also used by `priroda`.
63 pub fn create_ecx<'mir, 'tcx: 'mir>(
64     tcx: TyCtxt<'tcx>,
65     main_id: DefId,
66     config: MiriConfig,
67 ) -> InterpResult<'tcx, (InterpCx<'mir, 'tcx, Evaluator<'tcx>>, MPlaceTy<'tcx, Tag>)> {
68     let mut ecx = InterpCx::new(
69         tcx.at(rustc_span::source_map::DUMMY_SP),
70         ty::ParamEnv::reveal_all(),
71         Evaluator::new(config.communicate, config.validate),
72         MemoryExtra::new(
73             StdRng::seed_from_u64(config.seed.unwrap_or(0)),
74             config.stacked_borrows,
75             config.tracked_pointer_tag,
76             config.tracked_alloc_id,
77         ),
78     );
79     // Complete initialization.
80     EnvVars::init(&mut ecx, config.excluded_env_vars)?;
81     MemoryExtra::init_extern_statics(&mut ecx)?;
82
83     // Setup first stack-frame
84     let main_instance = ty::Instance::mono(tcx, main_id);
85     let main_mir = ecx.load_mir(main_instance.def, None)?;
86     if main_mir.arg_count != 0 {
87         bug!("main function must not take any arguments");
88     }
89
90     let start_id = tcx.lang_items().start_fn().unwrap();
91     let main_ret_ty = tcx.fn_sig(main_id).output();
92     let main_ret_ty = main_ret_ty.no_bound_vars().unwrap();
93     let start_instance = ty::Instance::resolve(
94         tcx,
95         ty::ParamEnv::reveal_all(),
96         start_id,
97         tcx.mk_substs(::std::iter::once(ty::subst::GenericArg::from(main_ret_ty))),
98     )
99     .unwrap();
100
101     // First argument: pointer to `main()`.
102     let main_ptr = ecx.memory.create_fn_alloc(FnVal::Instance(main_instance));
103     // Second argument (argc): length of `config.args`.
104     let argc = Scalar::from_uint(config.args.len() as u128, ecx.pointer_size());
105     // Third argument (`argv`): created from `config.args`.
106     let argv = {
107         // Put each argument in memory, collect pointers.
108         let mut argvs = Vec::<Scalar<Tag>>::new();
109         for arg in config.args.iter() {
110             // Make space for `0` terminator.
111             let size = arg.len() as u64 + 1;
112             let arg_type = tcx.mk_array(tcx.types.u8, size);
113             let arg_place = ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into());
114             ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr, size)?;
115             argvs.push(arg_place.ptr);
116         }
117         // Make an array with all these pointers, in the Miri memory.
118         let argvs_layout =
119             ecx.layout_of(tcx.mk_array(tcx.mk_imm_ptr(tcx.types.u8), argvs.len() as u64))?;
120         let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into());
121         for (idx, arg) in argvs.into_iter().enumerate() {
122             let place = ecx.mplace_field(argvs_place, idx as u64)?;
123             ecx.write_scalar(arg, place.into())?;
124         }
125         ecx.memory.mark_immutable(argvs_place.ptr.assert_ptr().alloc_id)?;
126         // A pointer to that place is the 3rd argument for main.
127         let argv = argvs_place.ptr;
128         // Store `argc` and `argv` for macOS `_NSGetArg{c,v}`.
129         {
130             let argc_place =
131                 ecx.allocate(ecx.layout_of(tcx.types.isize)?, MiriMemoryKind::Machine.into());
132             ecx.write_scalar(argc, argc_place.into())?;
133             ecx.machine.argc = Some(argc_place.ptr);
134
135             let argv_place = ecx.allocate(
136                 ecx.layout_of(tcx.mk_imm_ptr(tcx.types.unit))?,
137                 MiriMemoryKind::Machine.into(),
138             );
139             ecx.write_scalar(argv, argv_place.into())?;
140             ecx.machine.argv = Some(argv_place.ptr);
141         }
142         // Store command line as UTF-16 for Windows `GetCommandLineW`.
143         {
144             // Construct a command string with all the aguments.
145             let mut cmd = String::new();
146             for arg in config.args.iter() {
147                 if !cmd.is_empty() {
148                     cmd.push(' ');
149                 }
150                 cmd.push_str(&*shell_escape::windows::escape(arg.as_str().into()));
151             }
152             // Don't forget `0` terminator.
153             cmd.push(std::char::from_u32(0).unwrap());
154
155             let cmd_utf16: Vec<u16> = cmd.encode_utf16().collect();
156             let cmd_type = tcx.mk_array(tcx.types.u16, cmd_utf16.len() as u64);
157             let cmd_place = ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into());
158             ecx.machine.cmd_line = Some(cmd_place.ptr);
159             // Store the UTF-16 string. We just allocated so we know the bounds are fine.
160             let char_size = Size::from_bytes(2);
161             for (idx, &c) in cmd_utf16.iter().enumerate() {
162                 let place = ecx.mplace_field(cmd_place, idx as u64)?;
163                 ecx.write_scalar(Scalar::from_uint(c, char_size), place.into())?;
164             }
165         }
166         argv
167     };
168
169     // Return place (in static memory so that it does not count as leak).
170     let ret_place = ecx.allocate(ecx.layout_of(tcx.types.isize)?, MiriMemoryKind::Machine.into());
171     // Call start function.
172     ecx.call_function(
173         start_instance,
174         &[main_ptr.into(), argc.into(), argv.into()],
175         Some(ret_place.into()),
176         StackPopCleanup::None { cleanup: true },
177     )?;
178
179     // Set the last_error to 0
180     let errno_layout = ecx.layout_of(tcx.types.u32)?;
181     let errno_place = ecx.allocate(errno_layout, MiriMemoryKind::Machine.into());
182     ecx.write_scalar(Scalar::from_u32(0), errno_place.into())?;
183     ecx.machine.last_error = Some(errno_place);
184
185     Ok((ecx, ret_place))
186 }
187
188 /// Evaluates the main function specified by `main_id`.
189 /// Returns `Some(return_code)` if program executed completed.
190 /// Returns `None` if an evaluation error occured.
191 pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) -> Option<i64> {
192     // FIXME: We always ignore leaks on some platforms where we do not
193     // correctly implement TLS destructors.
194     let target_os = tcx.sess.target.target.target_os.as_str();
195     let ignore_leaks = config.ignore_leaks || target_os == "windows" || target_os == "macos";
196
197     let (mut ecx, ret_place) = match create_ecx(tcx, main_id, config) {
198         Ok(v) => v,
199         Err(mut err) => {
200             err.print_backtrace();
201             panic!("Miri initialziation error: {}", err.kind)
202         }
203     };
204
205     // Perform the main execution.
206     let res: InterpResult<'_, i64> = (|| {
207         while ecx.step()? {
208             ecx.process_diagnostics();
209         }
210         // Read the return code pointer *before* we run TLS destructors, to assert
211         // that it was written to by the time that `start` lang item returned.
212         let return_code = ecx.read_scalar(ret_place.into())?.not_undef()?.to_machine_isize(&ecx)?;
213         ecx.run_tls_dtors()?;
214         Ok(return_code)
215     })();
216
217     // Process the result.
218     match res {
219         Ok(return_code) => {
220             if !ignore_leaks {
221                 let leaks = ecx.memory.leak_report();
222                 if leaks != 0 {
223                     tcx.sess.err("the evaluated program leaked memory");
224                     // Ignore the provided return code - let the reported error
225                     // determine the return code.
226                     return None;
227                 }
228             }
229             Some(return_code)
230         }
231         Err(e) => report_diagnostic(&ecx, e),
232     }
233 }