]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/shims/os_str.rs
Auto merge of #102935 - ajtribick:display-float-0.5-fixed-0, r=scottmcm
[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<'a, 'tcx>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
22     Ok(os_str.as_bytes())
23 }
24
25 #[cfg(not(unix))]
26 pub fn os_str_to_bytes<'a, 'tcx>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [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<'a, 'tcx>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
38     Ok(OsStr::from_bytes(bytes))
39 }
40 #[cfg(not(unix))]
41 pub fn bytes_to_os_str<'a, 'tcx>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a 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_separator(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
192             .convert_path_separator(Cow::Owned(os_str), PathConversion::TargetToHost)
193             .into_owned()
194             .into())
195     }
196
197     /// Write a Path to the machine memory (as a null-terminated sequence of bytes),
198     /// adjusting path separators if needed.
199     fn write_path_to_c_str(
200         &mut self,
201         path: &Path,
202         ptr: Pointer<Option<Provenance>>,
203         size: u64,
204     ) -> InterpResult<'tcx, (bool, u64)> {
205         let this = self.eval_context_mut();
206         let os_str = this
207             .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
208         this.write_os_str_to_c_str(&os_str, ptr, size)
209     }
210
211     /// Write a Path to the machine memory (as a null-terminated sequence of `u16`s),
212     /// adjusting path separators if needed.
213     fn write_path_to_wide_str(
214         &mut self,
215         path: &Path,
216         ptr: Pointer<Option<Provenance>>,
217         size: u64,
218     ) -> InterpResult<'tcx, (bool, u64)> {
219         let this = self.eval_context_mut();
220         let os_str = this
221             .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
222         this.write_os_str_to_wide_str(&os_str, ptr, size)
223     }
224
225     /// Allocate enough memory to store a Path as a null-terminated sequence of bytes,
226     /// adjusting path separators if needed.
227     fn alloc_path_as_c_str(
228         &mut self,
229         path: &Path,
230         memkind: MemoryKind<MiriMemoryKind>,
231     ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
232         let this = self.eval_context_mut();
233         let os_str = this
234             .convert_path_separator(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget);
235         this.alloc_os_str_as_c_str(&os_str, memkind)
236     }
237
238     fn convert_path_separator<'a>(
239         &self,
240         os_str: Cow<'a, OsStr>,
241         direction: PathConversion,
242     ) -> Cow<'a, OsStr> {
243         let this = self.eval_context_ref();
244         let target_os = &this.tcx.sess.target.os;
245         #[cfg(windows)]
246         return if target_os == "windows" {
247             // Windows-on-Windows, all fine.
248             os_str
249         } else {
250             // Unix target, Windows host.
251             let (from, to) = match direction {
252                 PathConversion::HostToTarget => ('\\', '/'),
253                 PathConversion::TargetToHost => ('/', '\\'),
254             };
255             let converted = os_str
256                 .encode_wide()
257                 .map(|wchar| if wchar == from as u16 { to as u16 } else { wchar })
258                 .collect::<Vec<_>>();
259             Cow::Owned(OsString::from_wide(&converted))
260         };
261         #[cfg(unix)]
262         return if target_os == "windows" {
263             // Windows target, Unix host.
264             let (from, to) = match direction {
265                 PathConversion::HostToTarget => ('/', '\\'),
266                 PathConversion::TargetToHost => ('\\', '/'),
267             };
268             let converted = os_str
269                 .as_bytes()
270                 .iter()
271                 .map(|&wchar| if wchar == from as u8 { to as u8 } else { wchar })
272                 .collect::<Vec<_>>();
273             Cow::Owned(OsString::from_vec(converted))
274         } else {
275             // Unix-on-Unix, all is fine.
276             os_str
277         };
278     }
279 }