]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/shims/os_str.rs
e42ebc187b4b08ba2cb5403040b3e483e2d621e6
[rust.git] / src / tools / miri / src / shims / os_str.rs
1 use std::borrow::Cow;
2 use std::ffi::{OsStr, OsString};
3 use std::path::{Path, PathBuf};
4
5 #[cfg(unix)]
6 use std::os::unix::ffi::{OsStrExt, OsStringExt};
7 #[cfg(windows)]
8 use std::os::windows::ffi::{OsStrExt, OsStringExt};
9
10 use rustc_middle::ty::layout::LayoutOf;
11
12 use crate::*;
13
14 /// Represent how path separator conversion should be done.
15 pub enum PathConversion {
16     HostToTarget,
17     TargetToHost,
18 }
19
20 #[cfg(unix)]
21 pub fn os_str_to_bytes<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, &[u8]> {
22     Ok(os_str.as_bytes())
23 }
24
25 #[cfg(not(unix))]
26 pub fn os_str_to_bytes<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, &[u8]> {
27     // On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the
28     // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
29     // valid.
30     os_str
31         .to_str()
32         .map(|s| s.as_bytes())
33         .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
34 }
35
36 #[cfg(unix)]
37 pub fn bytes_to_os_str<'tcx>(bytes: &[u8]) -> InterpResult<'tcx, &OsStr> {
38     Ok(OsStr::from_bytes(bytes))
39 }
40 #[cfg(not(unix))]
41 pub fn bytes_to_os_str<'tcx>(bytes: &[u8]) -> InterpResult<'tcx, &OsStr> {
42     let s = std::str::from_utf8(bytes)
43         .map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?;
44     Ok(OsStr::new(s))
45 }
46
47 impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
48 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
49     /// Helper function to read an OsString from a null-terminated sequence of bytes, which is what
50     /// the Unix APIs usually handle.
51     fn read_os_str_from_c_str<'a>(
52         &'a self,
53         ptr: Pointer<Option<Provenance>>,
54     ) -> InterpResult<'tcx, &'a OsStr>
55     where
56         'tcx: 'a,
57         'mir: 'a,
58     {
59         let this = self.eval_context_ref();
60         let bytes = this.read_c_str(ptr)?;
61         bytes_to_os_str(bytes)
62     }
63
64     /// Helper function to read an OsString from a 0x0000-terminated sequence of u16,
65     /// which is what the Windows APIs usually handle.
66     fn read_os_str_from_wide_str<'a>(
67         &'a self,
68         ptr: Pointer<Option<Provenance>>,
69     ) -> InterpResult<'tcx, OsString>
70     where
71         'tcx: 'a,
72         'mir: 'a,
73     {
74         #[cfg(windows)]
75         pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
76             Ok(OsString::from_wide(&u16_vec[..]))
77         }
78         #[cfg(not(windows))]
79         pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec<u16>) -> InterpResult<'tcx, OsString> {
80             let s = String::from_utf16(&u16_vec[..])
81                 .map_err(|_| err_unsup_format!("{:?} is not a valid utf-16 string", u16_vec))?;
82             Ok(s.into())
83         }
84
85         let u16_vec = self.eval_context_ref().read_wide_str(ptr)?;
86         u16vec_to_osstring(u16_vec)
87     }
88
89     /// Helper function to write an OsStr as a null-terminated sequence of bytes, which is what
90     /// the Unix APIs usually handle. This function returns `Ok((false, length))` without trying
91     /// to write if `size` is not large enough to fit the contents of `os_string` plus a null
92     /// terminator. It returns `Ok((true, length))` if the writing process was successful. The
93     /// string length returned does include the null terminator.
94     fn write_os_str_to_c_str(
95         &mut self,
96         os_str: &OsStr,
97         ptr: Pointer<Option<Provenance>>,
98         size: u64,
99     ) -> InterpResult<'tcx, (bool, u64)> {
100         let bytes = os_str_to_bytes(os_str)?;
101         self.eval_context_mut().write_c_str(bytes, ptr, size)
102     }
103
104     /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what
105     /// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying
106     /// to write if `size` is not large enough to fit the contents of `os_string` plus a null
107     /// terminator. It returns `Ok((true, length))` if the writing process was successful. The
108     /// string length returned does include the null terminator. Length is measured in units of
109     /// `u16.`
110     fn write_os_str_to_wide_str(
111         &mut self,
112         os_str: &OsStr,
113         ptr: Pointer<Option<Provenance>>,
114         size: u64,
115     ) -> InterpResult<'tcx, (bool, u64)> {
116         #[cfg(windows)]
117         fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
118             Ok(os_str.encode_wide().collect())
119         }
120         #[cfg(not(windows))]
121         fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec<u16>> {
122             // On non-Windows platforms the best we can do to transform Vec<u16> from/to OS strings is to do the
123             // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
124             // valid.
125             os_str
126                 .to_str()
127                 .map(|s| s.encode_utf16().collect())
128                 .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
129         }
130
131         let u16_vec = os_str_to_u16vec(os_str)?;
132         self.eval_context_mut().write_wide_str(&u16_vec, ptr, size)
133     }
134
135     /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes.
136     fn alloc_os_str_as_c_str(
137         &mut self,
138         os_str: &OsStr,
139         memkind: MemoryKind<MiriMemoryKind>,
140     ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
141         let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0` terminator.
142         let this = self.eval_context_mut();
143
144         let arg_type = this.tcx.mk_array(this.tcx.types.u8, size);
145         let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
146         assert!(self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap().0);
147         Ok(arg_place.ptr)
148     }
149
150     /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of `u16`.
151     fn alloc_os_str_as_wide_str(
152         &mut self,
153         os_str: &OsStr,
154         memkind: MemoryKind<MiriMemoryKind>,
155     ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
156         let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0x0000` terminator.
157         let this = self.eval_context_mut();
158
159         let arg_type = this.tcx.mk_array(this.tcx.types.u16, size);
160         let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?;
161         assert!(self.write_os_str_to_wide_str(os_str, arg_place.ptr, size).unwrap().0);
162         Ok(arg_place.ptr)
163     }
164
165     /// Read a null-terminated sequence of bytes, and perform path separator conversion if needed.
166     fn read_path_from_c_str<'a>(
167         &'a self,
168         ptr: Pointer<Option<Provenance>>,
169     ) -> InterpResult<'tcx, Cow<'a, Path>>
170     where
171         'tcx: 'a,
172         'mir: 'a,
173     {
174         let this = self.eval_context_ref();
175         let os_str = this.read_os_str_from_c_str(ptr)?;
176
177         Ok(match this.convert_path(Cow::Borrowed(os_str), PathConversion::TargetToHost) {
178             Cow::Borrowed(x) => Cow::Borrowed(Path::new(x)),
179             Cow::Owned(y) => Cow::Owned(PathBuf::from(y)),
180         })
181     }
182
183     /// Read a null-terminated sequence of `u16`s, and perform path separator conversion if needed.
184     fn read_path_from_wide_str(
185         &self,
186         ptr: Pointer<Option<Provenance>>,
187     ) -> InterpResult<'tcx, PathBuf> {
188         let this = self.eval_context_ref();
189         let os_str = this.read_os_str_from_wide_str(ptr)?;
190
191         Ok(this.convert_path(Cow::Owned(os_str), PathConversion::TargetToHost).into_owned().into())
192     }
193
194     /// Write a Path to the machine memory (as a null-terminated sequence of bytes),
195     /// adjusting path separators if needed.
196     fn write_path_to_c_str(
197         &mut self,
198         path: &Path,
199         ptr: Pointer<Option<Provenance>>,
200         size: u64,
201     ) -> InterpResult<'tcx, (bool, u64)> {
202         let this = self.eval_context_mut();
203         let os_str =
204             this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
205         this.write_os_str_to_c_str(&os_str, ptr, size)
206     }
207
208     /// Write a Path to the machine memory (as a null-terminated sequence of `u16`s),
209     /// adjusting path separators if needed.
210     fn write_path_to_wide_str(
211         &mut self,
212         path: &Path,
213         ptr: Pointer<Option<Provenance>>,
214         size: u64,
215     ) -> InterpResult<'tcx, (bool, u64)> {
216         let this = self.eval_context_mut();
217         let os_str =
218             this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
219         this.write_os_str_to_wide_str(&os_str, ptr, size)
220     }
221
222     /// Allocate enough memory to store a Path as a null-terminated sequence of bytes,
223     /// adjusting path separators if needed.
224     fn alloc_path_as_c_str(
225         &mut self,
226         path: &Path,
227         memkind: MemoryKind<MiriMemoryKind>,
228     ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
229         let this = self.eval_context_mut();
230         let os_str =
231             this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
232         this.alloc_os_str_as_c_str(&os_str, memkind)
233     }
234
235     fn convert_path<'a>(
236         &self,
237         os_str: Cow<'a, OsStr>,
238         direction: PathConversion,
239     ) -> Cow<'a, OsStr> {
240         let this = self.eval_context_ref();
241         let target_os = &this.tcx.sess.target.os;
242
243         #[cfg(windows)]
244         return if target_os == "windows" {
245             // Windows-on-Windows, all fine.
246             os_str
247         } else {
248             // Unix target, Windows host.
249             let (from, to) = match direction {
250                 PathConversion::HostToTarget => ('\\', '/'),
251                 PathConversion::TargetToHost => ('/', '\\'),
252             };
253             let mut converted = os_str
254                 .encode_wide()
255                 .map(|wchar| if wchar == from as u16 { to as u16 } else { wchar })
256                 .collect::<Vec<_>>();
257             // We also have to ensure that absolute paths remain absolute.
258             match direction {
259                 PathConversion::HostToTarget => {
260                     // If this is an absolute Windows path that starts with a drive letter (`C:/...`
261                     // after separator conversion), it would not be considered absolute by Unix
262                     // target code.
263                     if converted.get(1).copied() == Some(':' as u16)
264                         && converted.get(2).copied() == Some('/' as u16)
265                     {
266                         // We add a `/` at the beginning, to store the absolute Windows
267                         // path in something that looks like an absolute Unix path.
268                         converted.insert(0, '/' as u16);
269                     }
270                 }
271                 PathConversion::TargetToHost => {
272                     // If the path is `\C:\`, the leading backslash was probably added by the above code
273                     // and we should get rid of it again.
274                     if converted.get(0).copied() == Some('\\' as u16)
275                         && converted.get(2).copied() == Some(':' as u16)
276                         && converted.get(3).copied() == Some('\\' as u16)
277                     {
278                         converted.remove(0);
279                     }
280                 }
281             }
282             Cow::Owned(OsString::from_wide(&converted))
283         };
284         #[cfg(unix)]
285         return if target_os == "windows" {
286             // Windows target, Unix host.
287             let (from, to) = match direction {
288                 PathConversion::HostToTarget => ('/', '\\'),
289                 PathConversion::TargetToHost => ('\\', '/'),
290             };
291             let converted = os_str
292                 .as_bytes()
293                 .iter()
294                 .map(|&wchar| if wchar == from as u8 { to as u8 } else { wchar })
295                 .collect::<Vec<_>>();
296             // TODO: Once we actually support file system things on Windows targets, we'll probably
297             // have to also do something clever for absolute path preservation here, like above.
298             Cow::Owned(OsString::from_vec(converted))
299         } else {
300             // Unix-on-Unix, all is fine.
301             os_str
302         };
303     }
304 }