Skip to main content

miri/shims/unix/macos/
foreign_items.rs

1use rustc_middle::ty::Ty;
2use rustc_span::Symbol;
3use rustc_target::callconv::FnAbi;
4
5use super::sync::{EvalContextExt as _, MacOsFutexTimeout};
6use crate::shims::unix::*;
7use crate::*;
8
9pub fn is_dyn_sym(name: &str) -> bool {
10    match name {
11        // These only became available with macOS 11.0, so std looks them up dynamically.
12        "os_sync_wait_on_address"
13        | "os_sync_wait_on_address_with_deadline"
14        | "os_sync_wait_on_address_with_timeout"
15        | "os_sync_wake_by_address_any"
16        | "os_sync_wake_by_address_all" => true,
17        _ => false,
18    }
19}
20
21impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
22pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
23    fn emulate_foreign_item_inner(
24        &mut self,
25        link_name: Symbol,
26        abi: &FnAbi<'tcx, Ty<'tcx>>,
27        args: &[OpTy<'tcx>],
28        dest: &MPlaceTy<'tcx>,
29    ) -> InterpResult<'tcx, EmulateItemResult> {
30        let this = self.eval_context_mut();
31
32        // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
33
34        match link_name.as_str() {
35            // errno
36            "__error" => {
37                let [] = &this
38                    .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?;
39                let errno_place = this.last_error_place()?;
40                this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
41            }
42
43            // File related shims
44            "close$NOCANCEL" => {
45                let [fd] = &this
46                    .check_shim_sig(shim_sig!(extern "C" fn(i32) -> i32), (link_name, abi, args))?;
47                let fd = this.read_scalar(fd)?.to_i32()?;
48                let result = this.close(fd)?;
49                this.write_scalar(result, dest)?;
50            }
51            "stat$INODE64" => {
52                let [path, buf] = &this.check_shim_sig(
53                    shim_sig!(extern "C" fn(*_, *libc::stat) -> i32),
54                    (link_name, abi, args),
55                )?;
56                let result = this.stat(path, buf)?;
57                this.write_scalar(result, dest)?;
58            }
59            "lstat$INODE64" => {
60                let [path, buf] = &this.check_shim_sig(
61                    shim_sig!(extern "C" fn(*_, *libc::stat) -> i32),
62                    (link_name, abi, args),
63                )?;
64                let result = this.lstat(path, buf)?;
65                this.write_scalar(result, dest)?;
66            }
67            "fstat$INODE64" => {
68                let [fd, buf] = &this.check_shim_sig(
69                    shim_sig!(extern "C" fn(i32, *libc::stat) -> i32),
70                    (link_name, abi, args),
71                )?;
72                let result = this.fstat(fd, buf)?;
73                this.write_scalar(result, dest)?;
74            }
75            "fstatat$INODE64" => {
76                let [dirfd, path, buf, flags] = &this.check_shim_sig(
77                    shim_sig!(extern "C" fn(i32, *_, *libc::stat, i32) -> i32),
78                    (link_name, abi, args),
79                )?;
80                let result = this.fstatat(dirfd, path, buf, flags)?;
81                this.write_scalar(result, dest)?;
82            }
83            "opendir$INODE64" => {
84                let [name] = &this
85                    .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?;
86                let result = this.opendir(name)?;
87                this.write_scalar(result, dest)?;
88            }
89            "readdir$INODE64" => {
90                let [dirp] = &this
91                    .check_shim_sig(shim_sig!(extern "C" fn(*_) -> *_), (link_name, abi, args))?;
92                this.readdir(dirp, dest, "dirent")?;
93            }
94            "realpath$DARWIN_EXTSN" => {
95                let [path, resolved_path] = &this.check_shim_sig(
96                    shim_sig!(extern "C" fn(*_, *_) -> *_),
97                    (link_name, abi, args),
98                )?;
99                let result = this.realpath(path, resolved_path)?;
100                this.write_scalar(result, dest)?;
101            }
102
103            // Environment related shims
104            "_NSGetEnviron" => {
105                // FIXME: This does not have a direct test (#3179).
106                let [] = &this
107                    .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?;
108                let environ = this.machine.env_vars.unix().environ();
109                this.write_pointer(environ, dest)?;
110            }
111
112            // Random data generation
113            "CCRandomGenerateBytes" => {
114                let [bytes, count] = &this.check_shim_sig(
115                    shim_sig!(extern "C" fn(*_, usize) -> i32),
116                    (link_name, abi, args),
117                )?;
118                let bytes = this.read_pointer(bytes)?;
119                let count = this.read_target_usize(count)?;
120                let success = this.eval_libc_i32("kCCSuccess");
121                this.gen_random(bytes, count)?;
122                this.write_int(success, dest)?;
123            }
124
125            // Time related shims
126            "mach_absolute_time" => {
127                let [] = &this
128                    .check_shim_sig(shim_sig!(extern "C" fn() -> u64), (link_name, abi, args))?;
129                let result = this.mach_absolute_time()?;
130                this.write_scalar(result, dest)?;
131            }
132
133            "mach_timebase_info" => {
134                let [info] = &this.check_shim_sig(
135                    shim_sig!(extern "C" fn(*libc::mach_timebase_info) -> i32),
136                    (link_name, abi, args),
137                )?;
138                let result = this.mach_timebase_info(info)?;
139                this.write_scalar(result, dest)?;
140            }
141
142            "mach_wait_until" => {
143                // FIXME: This does not have a direct test (#3179).
144                let [deadline] = &this
145                    .check_shim_sig(shim_sig!(extern "C" fn(u64) -> i32), (link_name, abi, args))?;
146                let result = this.mach_wait_until(deadline)?;
147                this.write_scalar(result, dest)?;
148            }
149
150            // Access to command-line arguments
151            "_NSGetArgc" => {
152                // FIXME: This does not have a direct test (#3179).
153                let [] = &this
154                    .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?;
155                this.write_pointer(this.machine.argc.expect("machine must be initialized"), dest)?;
156            }
157            "_NSGetArgv" => {
158                // FIXME: This does not have a direct test (#3179).
159                let [] = &this
160                    .check_shim_sig(shim_sig!(extern "C" fn() -> *_), (link_name, abi, args))?;
161                this.write_pointer(this.machine.argv.expect("machine must be initialized"), dest)?;
162            }
163            "_NSGetExecutablePath" => {
164                // FIXME: This does not have a direct test (#3179).
165                let [buf, bufsize] = &this.check_shim_sig(
166                    shim_sig!(extern "C" fn(*_, *u32) -> i32),
167                    (link_name, abi, args),
168                )?;
169                this.check_no_isolation("`_NSGetExecutablePath`")?;
170
171                let buf_ptr = this.read_pointer(buf)?;
172                let bufsize = this.deref_pointer(bufsize)?;
173
174                let Some(path) = this.machine.current_exe.clone() else {
175                    throw_unsup_format!(
176                        "_NSGetExecutablePath only works when Miri is run on a file"
177                    )
178                };
179                let (written, size_needed) = this.write_path_to_c_str(
180                    &path,
181                    buf_ptr,
182                    this.read_scalar(&bufsize)?.to_u32()?.into(),
183                )?;
184
185                if written {
186                    this.write_int(0, dest)?;
187                } else {
188                    this.write_scalar(Scalar::from_u32(size_needed.try_into().unwrap()), &bufsize)?;
189                    this.write_int(-1, dest)?;
190                }
191            }
192
193            // Thread-local storage
194            "_tlv_atexit" => {
195                let [dtor, data] = &this.check_shim_sig(
196                    shim_sig!(extern "C" fn(fn(..) -> _, *_) -> ()),
197                    (link_name, abi, args),
198                )?;
199                let dtor = this.read_pointer(dtor)?;
200                let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
201                let data = this.read_scalar(data)?;
202                this.machine.tls.add_thread_atexit(
203                    this.active_thread(),
204                    dtor,
205                    data,
206                    this.machine.current_user_relevant_span(),
207                )?;
208            }
209
210            // Querying system information
211            "pthread_get_stackaddr_np" => {
212                // FIXME: This does not have a direct test (#3179).
213                let [thread] = &this.check_shim_sig(
214                    shim_sig!(extern "C" fn(libc::pthread_t) -> *_),
215                    (link_name, abi, args),
216                )?;
217                this.read_target_usize(thread)?;
218                let stack_addr = Scalar::from_uint(this.machine.stack_addr, this.pointer_size());
219                this.write_scalar(stack_addr, dest)?;
220            }
221            "pthread_get_stacksize_np" => {
222                // FIXME: This does not have a direct test (#3179).
223                let [thread] = &this.check_shim_sig(
224                    shim_sig!(extern "C" fn(libc::pthread_t) -> usize),
225                    (link_name, abi, args),
226                )?;
227                this.read_target_usize(thread)?;
228                let stack_size = Scalar::from_uint(this.machine.stack_size, this.pointer_size());
229                this.write_scalar(stack_size, dest)?;
230            }
231
232            // Threading
233            "pthread_setname_np" => {
234                let [name] = &this
235                    .check_shim_sig(shim_sig!(extern "C" fn(*_) -> i32), (link_name, abi, args))?;
236
237                // The real implementation has logic in two places:
238                // * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200,
239                // * in kernel at https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/kern/proc_info.c#L3218-L3227.
240                //
241                // The function in libc calls the kernel to validate
242                // the security policies and the input. If all of the requirements
243                // are met, then the name is set and 0 is returned. Otherwise, if
244                // the specified name is lomnger than MAXTHREADNAMESIZE, then
245                // ENAMETOOLONG is returned.
246                let thread = this.pthread_self()?;
247                let res = match this.pthread_setname_np(
248                    thread,
249                    this.read_scalar(name)?,
250                    this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?,
251                    /* truncate */ false,
252                )? {
253                    ThreadNameResult::Ok => Scalar::from_i32(0),
254                    ThreadNameResult::NameTooLong => {
255                        // Testing on native systems indicates that the error is returned via errno.
256                        this.set_errno_and_return_neg1_i32(LibcError("ENAMETOOLONG"))?
257                    }
258                };
259                // Contrary to the manpage, `pthread_setname_np` on macOS still
260                // returns an integer indicating success.
261                this.write_scalar(res, dest)?;
262            }
263            "pthread_getname_np" => {
264                let [thread, name, len] = &this.check_shim_sig(
265                    shim_sig!(extern "C" fn(libc::pthread_t, *_, usize) -> i32),
266                    (link_name, abi, args),
267                )?;
268
269                // The function's behavior isn't portable between platforms.
270                // In case of macOS, a truncated name (due to a too small buffer)
271                // does not lead to an error.
272                //
273                // For details, see the implementation at
274                // https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1160-L1175.
275                // The key part is the strlcpy, which truncates the resulting value,
276                // but always null terminates (except for zero sized buffers).
277                let res = match this.pthread_getname_np(
278                    this.read_scalar(thread)?,
279                    this.read_scalar(name)?,
280                    this.read_scalar(len)?,
281                    /* truncate */ true,
282                )? {
283                    ThreadNameResult::Ok => Scalar::from_i32(0),
284                    // `NameTooLong` is possible when the buffer is zero sized,
285                    ThreadNameResult::NameTooLong => Scalar::from_i32(0),
286                };
287                this.write_scalar(res, dest)?;
288            }
289            "pthread_threadid_np" => {
290                let [thread, tid_ptr] = &this.check_shim_sig(
291                    shim_sig!(extern "C" fn(libc::pthread_t, *u64) -> i32),
292                    (link_name, abi, args),
293                )?;
294                let res = this.apple_pthread_threadid_np(thread, tid_ptr)?;
295                this.write_scalar(res, dest)?;
296            }
297
298            // Synchronization primitives
299            "os_sync_wait_on_address" => {
300                let [addr_op, value_op, size_op, flags_op] = &this.check_shim_sig(
301                    shim_sig!(extern "C" fn(*_, u64, usize, u32) -> i32),
302                    (link_name, abi, args),
303                )?;
304                this.os_sync_wait_on_address(
305                    addr_op,
306                    value_op,
307                    size_op,
308                    flags_op,
309                    MacOsFutexTimeout::None,
310                    dest,
311                )?;
312            }
313            "os_sync_wait_on_address_with_deadline" => {
314                let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = &this
315                    .check_shim_sig(
316                        shim_sig!(extern "C" fn(*_, u64, usize, u32, u32, u64) -> i32),
317                        (link_name, abi, args),
318                    )?;
319                this.os_sync_wait_on_address(
320                    addr_op,
321                    value_op,
322                    size_op,
323                    flags_op,
324                    MacOsFutexTimeout::Absolute { clock_op, timeout_op },
325                    dest,
326                )?;
327            }
328            "os_sync_wait_on_address_with_timeout" => {
329                let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = &this
330                    .check_shim_sig(
331                        shim_sig!(extern "C" fn(*_, u64, usize, u32, u32, u64) -> i32),
332                        (link_name, abi, args),
333                    )?;
334                this.os_sync_wait_on_address(
335                    addr_op,
336                    value_op,
337                    size_op,
338                    flags_op,
339                    MacOsFutexTimeout::Relative { clock_op, timeout_op },
340                    dest,
341                )?;
342            }
343            "os_sync_wake_by_address_any" => {
344                let [addr_op, size_op, flags_op] = &this.check_shim_sig(
345                    shim_sig!(extern "C" fn(*_, usize, u32) -> i32),
346                    (link_name, abi, args),
347                )?;
348                this.os_sync_wake_by_address(
349                    addr_op, size_op, flags_op, /* all */ false, dest,
350                )?;
351            }
352            "os_sync_wake_by_address_all" => {
353                let [addr_op, size_op, flags_op] = &this.check_shim_sig(
354                    shim_sig!(extern "C" fn(*_, usize, u32) -> i32),
355                    (link_name, abi, args),
356                )?;
357                this.os_sync_wake_by_address(
358                    addr_op, size_op, flags_op, /* all */ true, dest,
359                )?;
360            }
361            "os_unfair_lock_lock" => {
362                let [lock_op] = &this.check_shim_sig(
363                    shim_sig!(extern "C" fn(*libc::os_unfair_lock_s) -> ()),
364                    (link_name, abi, args),
365                )?;
366                this.os_unfair_lock_lock(lock_op)?;
367            }
368            "os_unfair_lock_trylock" => {
369                let [lock_op] = &this.check_shim_sig(
370                    shim_sig!(extern "C" fn(*libc::os_unfair_lock_s) -> bool),
371                    (link_name, abi, args),
372                )?;
373                this.os_unfair_lock_trylock(lock_op, dest)?;
374            }
375            "os_unfair_lock_unlock" => {
376                let [lock_op] = &this.check_shim_sig(
377                    shim_sig!(extern "C" fn(*libc::os_unfair_lock_s) -> ()),
378                    (link_name, abi, args),
379                )?;
380                this.os_unfair_lock_unlock(lock_op)?;
381            }
382            "os_unfair_lock_assert_owner" => {
383                let [lock_op] = &this.check_shim_sig(
384                    shim_sig!(extern "C" fn(*libc::os_unfair_lock_s) -> ()),
385                    (link_name, abi, args),
386                )?;
387                this.os_unfair_lock_assert_owner(lock_op)?;
388            }
389            "os_unfair_lock_assert_not_owner" => {
390                let [lock_op] = &this.check_shim_sig(
391                    shim_sig!(extern "C" fn(*libc::os_unfair_lock_s) -> ()),
392                    (link_name, abi, args),
393                )?;
394                this.os_unfair_lock_assert_not_owner(lock_op)?;
395            }
396            "dispatch_semaphore_create" => {
397                let [value] = &this.check_shim_sig(
398                    shim_sig!(extern "C" fn(isize) -> *_),
399                    (link_name, abi, args),
400                )?;
401                this.dispatch_semaphore_create(value, dest)?;
402            }
403            "dispatch_semaphore_wait" => {
404                let [sem, timeout] = &this.check_shim_sig(
405                    shim_sig!(extern "C" fn(*_, u64) -> isize),
406                    (link_name, abi, args),
407                )?;
408                this.dispatch_semaphore_wait(sem, timeout, dest)?;
409            }
410            "dispatch_semaphore_signal" => {
411                let [sem] = &this.check_shim_sig(
412                    shim_sig!(extern "C" fn(*_) -> isize),
413                    (link_name, abi, args),
414                )?;
415                this.dispatch_semaphore_signal(sem, dest)?;
416            }
417            "dispatch_release" => {
418                let [sem] = &this
419                    .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?;
420                this.dispatch_release(sem)?;
421            }
422            "dispatch_time" => {
423                let [when, delta] = &this.check_shim_sig(
424                    shim_sig!(extern "C" fn(u64, i64) -> u64),
425                    (link_name, abi, args),
426                )?;
427                this.dispatch_time(when, delta, dest)?;
428            }
429
430            "pthread_cond_timedwait_relative_np" => {
431                let [cond, mutex, reltime] = &this.check_shim_sig(
432                    shim_sig!(extern "C" fn(*libc::pthread_cond_t, *libc::pthread_mutex_t, *libc::timespec) -> i32),
433                    (link_name, abi, args),
434                )?;
435                this.pthread_cond_timedwait(
436                    cond, mutex, reltime, dest, /* macos_relative_np */ true,
437                )?;
438            }
439
440            // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
441            // These shims are enabled only when the caller is in the standard library.
442            "confstr" => {
443                // FIXME: This does not have a direct test (#3179).
444                let [_key, _buf, _buflen] = &this.check_shim_sig(
445                    shim_sig!(extern "C" fn(i32, *_, usize) -> usize),
446                    (link_name, abi, args),
447                )?;
448                // We just pretend that no configuration key exists, and return EINVAL.
449                this.set_last_error(LibcError("EINVAL"))?;
450                this.write_int(0, dest)?;
451            }
452
453            _ => return interp_ok(EmulateItemResult::NotSupported),
454        };
455
456        interp_ok(EmulateItemResult::NeedsReturn)
457    }
458}