Skip to main content

miri/shims/unix/
sync.rs

1use rustc_abi::Size;
2use rustc_target::spec::Os;
3
4use crate::concurrency::sync::{AccessKind, SyncObj};
5use crate::*;
6
7/// Do a bytewise comparison of the two places. This is used to check if
8/// a synchronization primitive matches its static initializer value.
9///
10/// `prefix`, if set, indicates that only the first N bytes should be compared.
11fn bytewise_equal<'tcx>(
12    ecx: &MiriInterpCx<'tcx>,
13    left: &MPlaceTy<'tcx>,
14    right: &MPlaceTy<'tcx>,
15    prefix: Option<u64>,
16) -> InterpResult<'tcx, bool> {
17    let size = left.layout.size;
18    assert_eq!(size, right.layout.size);
19    let cmp_size = prefix.map(Size::from_bytes).unwrap_or(size);
20
21    let left_bytes = ecx.read_bytes_ptr_strip_provenance(left.ptr(), cmp_size)?;
22    let right_bytes = ecx.read_bytes_ptr_strip_provenance(right.ptr(), cmp_size)?;
23
24    interp_ok(left_bytes == right_bytes)
25}
26
27// The in-memory marker values we use to indicate whether objects have been initialized.
28const PTHREAD_UNINIT: u8 = 0;
29const PTHREAD_INIT: u8 = 1;
30
31// # pthread_mutexattr_t
32// We store some data directly inside the type, ignoring the platform layout:
33// - kind: i32
34
35#[inline]
36fn mutexattr_kind_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, u64> {
37    interp_ok(match &ecx.tcx.sess.target.os {
38        Os::Linux
39        | Os::Illumos
40        | Os::Solaris
41        | Os::MacOs
42        | Os::FreeBsd
43        | Os::Android
44        | Os::NetBsd => 0,
45        os => throw_unsup_format!("`pthread_mutexattr` is not supported on {os}"),
46    })
47}
48
49fn mutexattr_get_kind<'tcx>(
50    ecx: &MiriInterpCx<'tcx>,
51    attr_ptr: &OpTy<'tcx>,
52) -> InterpResult<'tcx, i32> {
53    ecx.deref_pointer_and_read(
54        attr_ptr,
55        mutexattr_kind_offset(ecx)?,
56        ecx.libc_ty_layout("pthread_mutexattr_t"),
57        ecx.machine.layouts.i32,
58    )?
59    .to_i32()
60}
61
62fn mutexattr_set_kind<'tcx>(
63    ecx: &mut MiriInterpCx<'tcx>,
64    attr_ptr: &OpTy<'tcx>,
65    kind: i32,
66) -> InterpResult<'tcx, ()> {
67    ecx.deref_pointer_and_write(
68        attr_ptr,
69        mutexattr_kind_offset(ecx)?,
70        Scalar::from_i32(kind),
71        ecx.libc_ty_layout("pthread_mutexattr_t"),
72        ecx.machine.layouts.i32,
73    )
74}
75
76/// To differentiate "the mutex kind has not been changed" from
77/// "the mutex kind has been set to PTHREAD_MUTEX_DEFAULT and that is
78/// equal to some other mutex kind", we make the default value of this
79/// field *not* PTHREAD_MUTEX_DEFAULT but this special flag.
80const PTHREAD_MUTEX_KIND_UNCHANGED: i32 = 0x8000000;
81
82/// Translates the mutex kind from what is stored in pthread_mutexattr_t to our enum.
83fn mutexattr_translate_kind<'tcx>(
84    ecx: &MiriInterpCx<'tcx>,
85    kind: i32,
86) -> InterpResult<'tcx, MutexKind> {
87    interp_ok(if kind == (ecx.eval_libc_i32("PTHREAD_MUTEX_NORMAL")) {
88        MutexKind::Normal
89    } else if kind == ecx.eval_libc_i32("PTHREAD_MUTEX_ERRORCHECK") {
90        MutexKind::ErrorCheck
91    } else if kind == ecx.eval_libc_i32("PTHREAD_MUTEX_RECURSIVE") {
92        MutexKind::Recursive
93    } else if kind == ecx.eval_libc_i32("PTHREAD_MUTEX_DEFAULT")
94        || kind == PTHREAD_MUTEX_KIND_UNCHANGED
95    {
96        // We check this *last* since PTHREAD_MUTEX_DEFAULT may be numerically equal to one of the
97        // others, and we want an explicit `mutexattr_settype` to work as expected.
98        MutexKind::Default
99    } else {
100        throw_unsup_format!("unsupported type of mutex: {kind}");
101    })
102}
103
104// # pthread_mutex_t
105// We store some data directly inside the type, ignoring the platform layout:
106// - init: u8
107
108/// The mutex kind.
109#[derive(Debug, Clone, Copy)]
110enum MutexKind {
111    Normal,
112    Default,
113    Recursive,
114    ErrorCheck,
115}
116
117#[derive(Debug, Clone)]
118struct PthreadMutex {
119    mutex_ref: MutexRef,
120    kind: MutexKind,
121}
122
123impl SyncObj for PthreadMutex {
124    fn on_access<'tcx>(&self, access_kind: AccessKind) -> InterpResult<'tcx> {
125        if !self.mutex_ref.queue_is_empty() {
126            throw_ub_format!(
127                "{access_kind} of `pthread_mutex_t` is forbidden while the queue is non-empty"
128            );
129        }
130        interp_ok(())
131    }
132
133    fn delete_on_write(&self) -> bool {
134        true
135    }
136}
137
138/// To ensure an initialized mutex that was moved somewhere else can be distinguished from
139/// a statically initialized mutex that is used the first time, we pick some offset within
140/// `pthread_mutex_t` and use it as an "initialized" flag.
141fn mutex_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> {
142    let offset = match &ecx.tcx.sess.target.os {
143        Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0,
144        // macOS and NetBSD store a signature in the first bytes, so we move to offset 4.
145        Os::MacOs | Os::NetBsd => 4,
146        os => throw_unsup_format!("`pthread_mutex` is not supported on {os}"),
147    };
148    let offset = Size::from_bytes(offset);
149
150    // Sanity-check this against PTHREAD_MUTEX_INITIALIZER (but only once):
151    // the `init` field must start out not equal to INIT_COOKIE.
152    if !ecx.machine.pthread_mutex_sanity.replace(true) {
153        let check_static_initializer = |name| {
154            let static_initializer = ecx.eval_path(&["libc", name]);
155            let init_field =
156                static_initializer.offset(offset, ecx.machine.layouts.u8, ecx).unwrap();
157            let init = ecx.read_scalar(&init_field).unwrap().to_u8().unwrap();
158            assert_eq!(
159                init, PTHREAD_UNINIT,
160                "{name} is incompatible with our initialization logic"
161            );
162        };
163
164        check_static_initializer("PTHREAD_MUTEX_INITIALIZER");
165        // Check non-standard initializers.
166        match &ecx.tcx.sess.target.os {
167            Os::Linux => {
168                check_static_initializer("PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP");
169                check_static_initializer("PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP");
170                check_static_initializer("PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP");
171            }
172            Os::Illumos | Os::Solaris | Os::MacOs | Os::FreeBsd | Os::Android | Os::NetBsd => {
173                // No non-standard initializers.
174            }
175            os => throw_unsup_format!("`pthread_mutex` is not supported on {os}"),
176        }
177    }
178
179    interp_ok(offset)
180}
181
182/// Eagerly create and initialize a new mutex.
183fn mutex_create<'tcx>(
184    ecx: &mut MiriInterpCx<'tcx>,
185    mutex_ptr: &OpTy<'tcx>,
186    kind: MutexKind,
187) -> InterpResult<'tcx> {
188    let mutex = ecx.deref_pointer_as(mutex_ptr, ecx.libc_ty_layout("pthread_mutex_t"))?;
189    let data = PthreadMutex { mutex_ref: MutexRef::new(), kind };
190    ecx.init_immovable_sync(&mutex, mutex_init_offset(ecx)?, PTHREAD_INIT, data)?;
191    interp_ok(())
192}
193
194/// Returns the mutex data stored at the address that `mutex_ptr` points to.
195/// Will raise an error if the mutex has been moved since its first use.
196fn mutex_get_data<'tcx, 'a>(
197    ecx: &'a mut MiriInterpCx<'tcx>,
198    mutex_ptr: &OpTy<'tcx>,
199) -> InterpResult<'tcx, &'a PthreadMutex>
200where
201    'tcx: 'a,
202{
203    let mutex = ecx.deref_pointer_as(mutex_ptr, ecx.libc_ty_layout("pthread_mutex_t"))?;
204    ecx.get_immovable_sync_with_static_init(
205        &mutex,
206        mutex_init_offset(ecx)?,
207        PTHREAD_UNINIT,
208        PTHREAD_INIT,
209        |ecx| {
210            let kind = mutex_kind_from_static_initializer(ecx, &mutex)?;
211            interp_ok(PthreadMutex { mutex_ref: MutexRef::new(), kind })
212        },
213    )
214}
215
216/// Returns the kind of a static initializer.
217fn mutex_kind_from_static_initializer<'tcx>(
218    ecx: &MiriInterpCx<'tcx>,
219    mutex: &MPlaceTy<'tcx>,
220) -> InterpResult<'tcx, MutexKind> {
221    let prefix = match &ecx.tcx.sess.target.os {
222        // On android, there's a 4-byte `value` header followed by "padding", and some versions
223        // of libc leave that uninitialized. Only check the `value` bytes.
224        Os::Android => Some(4),
225        _ => None,
226    };
227    let is_initializer = |name| bytewise_equal(ecx, mutex, &ecx.eval_path(&["libc", name]), prefix);
228
229    // All the static initializers recognized here *must* be checked in `mutex_init_offset`!
230
231    // PTHREAD_MUTEX_INITIALIZER is recognized on all targets.
232    if is_initializer("PTHREAD_MUTEX_INITIALIZER")? {
233        return interp_ok(MutexKind::Default);
234    }
235    // Support additional platform-specific initializers.
236    match &ecx.tcx.sess.target.os {
237        Os::Linux =>
238            if is_initializer("PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP")? {
239                return interp_ok(MutexKind::Recursive);
240            } else if is_initializer("PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP")? {
241                return interp_ok(MutexKind::ErrorCheck);
242            },
243        _ => {}
244    }
245    throw_ub_format!(
246        "`pthread_mutex_t` was not properly initialized at this location, or it got overwritten"
247    );
248}
249
250// # pthread_rwlock_t
251// We store some data directly inside the type, ignoring the platform layout:
252// - init: u8
253
254#[derive(Debug, Clone)]
255struct PthreadRwLock {
256    rwlock_ref: RwLockRef,
257}
258
259impl SyncObj for PthreadRwLock {
260    fn on_access<'tcx>(&self, access_kind: AccessKind) -> InterpResult<'tcx> {
261        if !self.rwlock_ref.queue_is_empty() {
262            throw_ub_format!(
263                "{access_kind} of `pthread_rwlock_t` is forbidden while the queue is non-empty"
264            );
265        }
266        interp_ok(())
267    }
268
269    fn delete_on_write(&self) -> bool {
270        true
271    }
272}
273
274fn rwlock_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> {
275    let offset = match &ecx.tcx.sess.target.os {
276        Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0,
277        // macOS stores a signature in the first bytes, so we move to offset 4.
278        Os::MacOs => 4,
279        os => throw_unsup_format!("`pthread_rwlock` is not supported on {os}"),
280    };
281    let offset = Size::from_bytes(offset);
282
283    // Sanity-check this against PTHREAD_RWLOCK_INITIALIZER (but only once):
284    // the `init` field must start out not equal to LAZY_INIT_COOKIE.
285    if !ecx.machine.pthread_rwlock_sanity.replace(true) {
286        let static_initializer = ecx.eval_path(&["libc", "PTHREAD_RWLOCK_INITIALIZER"]);
287        let init_field = static_initializer.offset(offset, ecx.machine.layouts.u8, ecx).unwrap();
288        let init = ecx.read_scalar(&init_field).unwrap().to_u8().unwrap();
289        assert_eq!(
290            init, PTHREAD_UNINIT,
291            "PTHREAD_RWLOCK_INITIALIZER is incompatible with our initialization logic"
292        );
293    }
294
295    interp_ok(offset)
296}
297
298fn rwlock_get_data<'tcx, 'a>(
299    ecx: &'a mut MiriInterpCx<'tcx>,
300    rwlock_ptr: &OpTy<'tcx>,
301) -> InterpResult<'tcx, &'a PthreadRwLock>
302where
303    'tcx: 'a,
304{
305    let rwlock = ecx.deref_pointer_as(rwlock_ptr, ecx.libc_ty_layout("pthread_rwlock_t"))?;
306    ecx.get_immovable_sync_with_static_init(
307        &rwlock,
308        rwlock_init_offset(ecx)?,
309        PTHREAD_UNINIT,
310        PTHREAD_INIT,
311        |ecx| {
312            let prefix = match &ecx.tcx.sess.target.os {
313                // On android, there's a 4-byte `value` header followed by "padding", and some
314                // versions of libc leave that uninitialized. Only check the `value` bytes.
315                Os::Android => Some(4),
316                _ => None,
317            };
318            if !bytewise_equal(
319                ecx,
320                &rwlock,
321                &ecx.eval_path(&["libc", "PTHREAD_RWLOCK_INITIALIZER"]),
322                prefix,
323            )? {
324                throw_ub_format!(
325                    "`pthread_rwlock_t` was not properly initialized at this location, or it got overwritten"
326                );
327            }
328            interp_ok(PthreadRwLock { rwlock_ref: RwLockRef::new() })
329        },
330    )
331}
332
333// # pthread_condattr_t
334// We store some data directly inside the type, ignoring the platform layout:
335// - clock: i32
336
337#[inline]
338fn condattr_clock_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, u64> {
339    interp_ok(match &ecx.tcx.sess.target.os {
340        Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0,
341        // macOS does not have a clock attribute.
342        os => throw_unsup_format!("`pthread_condattr` clock field is not supported on {os}"),
343    })
344}
345
346fn condattr_get_clock_id<'tcx>(
347    ecx: &MiriInterpCx<'tcx>,
348    attr_ptr: &OpTy<'tcx>,
349) -> InterpResult<'tcx, Scalar> {
350    ecx.deref_pointer_and_read(
351        attr_ptr,
352        condattr_clock_offset(ecx)?,
353        ecx.libc_ty_layout("pthread_condattr_t"),
354        ecx.machine.layouts.i32,
355    )
356}
357
358fn condattr_set_clock_id<'tcx>(
359    ecx: &mut MiriInterpCx<'tcx>,
360    attr_ptr: &OpTy<'tcx>,
361    clock_id: i32,
362) -> InterpResult<'tcx, ()> {
363    ecx.deref_pointer_and_write(
364        attr_ptr,
365        condattr_clock_offset(ecx)?,
366        Scalar::from_i32(clock_id),
367        ecx.libc_ty_layout("pthread_condattr_t"),
368        ecx.machine.layouts.i32,
369    )
370}
371
372// # pthread_cond_t
373// We store some data directly inside the type, ignoring the platform layout:
374// - init: u8
375
376fn cond_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> {
377    let offset = match &ecx.tcx.sess.target.os {
378        Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0,
379        // macOS stores a signature in the first bytes, so we move to offset 4.
380        Os::MacOs => 4,
381        os => throw_unsup_format!("`pthread_cond` is not supported on {os}"),
382    };
383    let offset = Size::from_bytes(offset);
384
385    // Sanity-check this against PTHREAD_COND_INITIALIZER (but only once):
386    // the `init` field must start out not equal to LAZY_INIT_COOKIE.
387    if !ecx.machine.pthread_condvar_sanity.replace(true) {
388        let static_initializer = ecx.eval_path(&["libc", "PTHREAD_COND_INITIALIZER"]);
389        let init_field = static_initializer.offset(offset, ecx.machine.layouts.u8, ecx).unwrap();
390        let init = ecx.read_scalar(&init_field).unwrap().to_u8().unwrap();
391        assert_eq!(
392            init, PTHREAD_UNINIT,
393            "PTHREAD_COND_INITIALIZER is incompatible with our initialization logic"
394        );
395    }
396
397    interp_ok(offset)
398}
399
400#[derive(Debug, Clone)]
401struct PthreadCondvar {
402    condvar_ref: CondvarRef,
403    clock: TimeoutClock,
404}
405
406impl SyncObj for PthreadCondvar {
407    fn on_access<'tcx>(&self, access_kind: AccessKind) -> InterpResult<'tcx> {
408        if !self.condvar_ref.queue_is_empty() {
409            throw_ub_format!(
410                "{access_kind} of `pthread_cond_t` is forbidden while the queue is non-empty"
411            );
412        }
413        interp_ok(())
414    }
415
416    fn delete_on_write(&self) -> bool {
417        true
418    }
419}
420
421fn cond_create<'tcx>(
422    ecx: &mut MiriInterpCx<'tcx>,
423    cond_ptr: &OpTy<'tcx>,
424    clock: TimeoutClock,
425) -> InterpResult<'tcx> {
426    let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?;
427    let data = PthreadCondvar { condvar_ref: CondvarRef::new(), clock };
428    ecx.init_immovable_sync(&cond, cond_init_offset(ecx)?, PTHREAD_INIT, data)?;
429    interp_ok(())
430}
431
432fn cond_get_data<'tcx, 'a>(
433    ecx: &'a mut MiriInterpCx<'tcx>,
434    cond_ptr: &OpTy<'tcx>,
435) -> InterpResult<'tcx, &'a PthreadCondvar>
436where
437    'tcx: 'a,
438{
439    let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?;
440    ecx.get_immovable_sync_with_static_init(
441        &cond,
442        cond_init_offset(ecx)?,
443        PTHREAD_UNINIT,
444        PTHREAD_INIT,
445        |ecx| {
446            let prefix = match &ecx.tcx.sess.target.os {
447                // On android, there's a 4-byte `value` header followed by "padding", and some
448                // versions of libc leave that uninitialized. Only check the `value` bytes.
449                Os::Android => Some(4),
450                _ => None,
451            };
452            if !bytewise_equal(
453                ecx,
454                &cond,
455                &ecx.eval_path(&["libc", "PTHREAD_COND_INITIALIZER"]),
456                prefix,
457            )? {
458                throw_ub_format!(
459                    "`pthread_cond_t` was not properly initialized at this location, or it got overwritten"
460                );
461            }
462            // This used the static initializer. The clock there is always CLOCK_REALTIME.
463            interp_ok(PthreadCondvar {
464                condvar_ref: CondvarRef::new(),
465                clock: TimeoutClock::RealTime,
466            })
467        },
468    )
469}
470
471// # sem_t
472// We store some data directly inside the type, ignoring the platform layout:
473// - init: u8
474
475fn sem_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> {
476    let offset = match &ecx.tcx.sess.target.os {
477        Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0,
478        // Explicitly *not* supported on macOS!
479        os => throw_unsup_format!("`sem` is not supported on {os}"),
480    };
481    interp_ok(Size::from_bytes(offset))
482}
483
484#[derive(Debug, Clone)]
485struct PthreadSem {
486    sem_ref: SemaphoreRef,
487}
488
489impl SyncObj for PthreadSem {
490    fn on_access<'tcx>(&self, access_kind: AccessKind) -> InterpResult<'tcx> {
491        if !self.sem_ref.queue_is_empty() {
492            throw_ub_format!("{access_kind} of `sem_t` is forbidden while the queue is non-empty");
493        }
494        interp_ok(())
495    }
496
497    fn delete_on_write(&self) -> bool {
498        true
499    }
500}
501
502fn sem_create<'tcx>(
503    ecx: &mut MiriInterpCx<'tcx>,
504    sem_ptr: &OpTy<'tcx>,
505    value: u32,
506) -> InterpResult<'tcx> {
507    let sem = ecx.deref_pointer_as(sem_ptr, ecx.libc_ty_layout("sem_t"))?;
508    let data = PthreadSem { sem_ref: SemaphoreRef::new(value.into()) };
509    ecx.init_immovable_sync(&sem, sem_init_offset(ecx)?, PTHREAD_INIT, data)?;
510    interp_ok(())
511}
512
513fn sem_get_data<'tcx, 'a>(
514    ecx: &'a mut MiriInterpCx<'tcx>,
515    sem_ptr: &OpTy<'tcx>,
516) -> InterpResult<'tcx, &'a PthreadSem>
517where
518    'tcx: 'a,
519{
520    let sem = ecx.deref_pointer_as(sem_ptr, ecx.libc_ty_layout("sem_t"))?;
521    ecx.get_immovable_sync_with_static_init(
522        &sem,
523        sem_init_offset(ecx)?,
524        PTHREAD_UNINIT,
525        PTHREAD_INIT,
526        |_ecx| throw_ub_format!("`sem_t` value has not been initialized with `sem_init`"),
527    )
528}
529
530impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
531pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
532    fn pthread_mutexattr_init(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
533        let this = self.eval_context_mut();
534
535        mutexattr_set_kind(this, attr_op, PTHREAD_MUTEX_KIND_UNCHANGED)?;
536
537        interp_ok(())
538    }
539
540    fn pthread_mutexattr_settype(
541        &mut self,
542        attr_op: &OpTy<'tcx>,
543        kind_op: &OpTy<'tcx>,
544    ) -> InterpResult<'tcx, Scalar> {
545        let this = self.eval_context_mut();
546
547        let kind = this.read_scalar(kind_op)?.to_i32()?;
548        if kind == this.eval_libc_i32("PTHREAD_MUTEX_NORMAL")
549            || kind == this.eval_libc_i32("PTHREAD_MUTEX_DEFAULT")
550            || kind == this.eval_libc_i32("PTHREAD_MUTEX_ERRORCHECK")
551            || kind == this.eval_libc_i32("PTHREAD_MUTEX_RECURSIVE")
552        {
553            // Make sure we do not mix this up with the "unchanged" kind.
554            assert_ne!(kind, PTHREAD_MUTEX_KIND_UNCHANGED);
555            mutexattr_set_kind(this, attr_op, kind)?;
556        } else {
557            let einval = this.eval_libc_i32("EINVAL");
558            return interp_ok(Scalar::from_i32(einval));
559        }
560
561        interp_ok(Scalar::from_i32(0))
562    }
563
564    fn pthread_mutexattr_destroy(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
565        let this = self.eval_context_mut();
566
567        // Destroying an uninit pthread_mutexattr is UB, so check to make sure it's not uninit.
568        mutexattr_get_kind(this, attr_op)?;
569
570        // To catch double-destroys, we de-initialize the mutexattr.
571        // This is technically not right and might lead to false positives. For example, the below
572        // code is *likely* sound, even assuming uninit numbers are UB, but Miri complains.
573        //
574        // let mut x: MaybeUninit<libc::pthread_mutexattr_t> = MaybeUninit::zeroed();
575        // libc::pthread_mutexattr_init(x.as_mut_ptr());
576        // libc::pthread_mutexattr_destroy(x.as_mut_ptr());
577        // x.assume_init();
578        //
579        // However, the way libstd uses the pthread APIs works in our favor here, so we can get away with this.
580        // This can always be revisited to have some external state to catch double-destroys
581        // but not complain about the above code. See https://github.com/rust-lang/miri/pull/1933
582        this.write_uninit(
583            &this.deref_pointer_as(attr_op, this.libc_ty_layout("pthread_mutexattr_t"))?,
584        )?;
585
586        interp_ok(())
587    }
588
589    fn pthread_mutex_init(
590        &mut self,
591        mutex_op: &OpTy<'tcx>,
592        attr_op: &OpTy<'tcx>,
593    ) -> InterpResult<'tcx, ()> {
594        let this = self.eval_context_mut();
595
596        let attr = this.read_pointer(attr_op)?;
597        let kind = if this.ptr_is_null(attr)? {
598            MutexKind::Default
599        } else {
600            mutexattr_translate_kind(this, mutexattr_get_kind(this, attr_op)?)?
601        };
602
603        mutex_create(this, mutex_op, kind)?;
604
605        interp_ok(())
606    }
607
608    fn pthread_mutex_lock(
609        &mut self,
610        mutex_op: &OpTy<'tcx>,
611        dest: &MPlaceTy<'tcx>,
612    ) -> InterpResult<'tcx> {
613        let this = self.eval_context_mut();
614
615        let mutex = mutex_get_data(this, mutex_op)?.clone();
616
617        let ret = if let Some(owner_thread) = mutex.mutex_ref.owner() {
618            if owner_thread != this.active_thread() {
619                this.mutex_enqueue_and_block(
620                    mutex.mutex_ref,
621                    Some((Scalar::from_i32(0), dest.clone())),
622                );
623                return interp_ok(());
624            } else {
625                // Trying to acquire the same mutex again.
626                match mutex.kind {
627                    MutexKind::Default =>
628                        throw_ub_format!(
629                            "trying to acquire default mutex already locked by the current thread"
630                        ),
631                    MutexKind::Normal => throw_machine_stop!(TerminationInfo::LocalDeadlock),
632                    MutexKind::ErrorCheck => this.eval_libc_i32("EDEADLK"),
633                    MutexKind::Recursive => {
634                        this.mutex_lock(&mutex.mutex_ref)?;
635                        0
636                    }
637                }
638            }
639        } else {
640            // The mutex is unlocked. Let's lock it.
641            this.mutex_lock(&mutex.mutex_ref)?;
642            0
643        };
644        this.write_scalar(Scalar::from_i32(ret), dest)?;
645        interp_ok(())
646    }
647
648    fn pthread_mutex_trylock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
649        let this = self.eval_context_mut();
650
651        let mutex = mutex_get_data(this, mutex_op)?.clone();
652
653        interp_ok(Scalar::from_i32(if let Some(owner_thread) = mutex.mutex_ref.owner() {
654            if owner_thread != this.active_thread() {
655                this.eval_libc_i32("EBUSY")
656            } else {
657                match mutex.kind {
658                    MutexKind::Default | MutexKind::Normal | MutexKind::ErrorCheck =>
659                        this.eval_libc_i32("EBUSY"),
660                    MutexKind::Recursive => {
661                        this.mutex_lock(&mutex.mutex_ref)?;
662                        0
663                    }
664                }
665            }
666        } else {
667            // The mutex is unlocked. Let's lock it.
668            this.mutex_lock(&mutex.mutex_ref)?;
669            0
670        }))
671    }
672
673    fn pthread_mutex_unlock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
674        let this = self.eval_context_mut();
675
676        let mutex = mutex_get_data(this, mutex_op)?.clone();
677
678        if let Some(_old_locked_count) = this.mutex_unlock(&mutex.mutex_ref)? {
679            // The mutex was locked by the current thread.
680            interp_ok(Scalar::from_i32(0))
681        } else {
682            // The mutex was locked by another thread or not locked at all. See
683            // the “Unlock When Not Owner” column in
684            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_unlock.html.
685            match mutex.kind {
686                MutexKind::Default =>
687                    throw_ub_format!(
688                        "unlocked a default mutex that was not locked by the current thread"
689                    ),
690                MutexKind::Normal =>
691                    throw_ub_format!(
692                        "unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread"
693                    ),
694                MutexKind::ErrorCheck | MutexKind::Recursive =>
695                    interp_ok(Scalar::from_i32(this.eval_libc_i32("EPERM"))),
696            }
697        }
698    }
699
700    fn pthread_mutex_destroy(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
701        let this = self.eval_context_mut();
702
703        // Reading the field also has the side-effect that we detect double-`destroy`
704        // since we make the field uninit below.
705        let mutex = mutex_get_data(this, mutex_op)?.clone();
706
707        if mutex.mutex_ref.owner().is_some() {
708            throw_ub_format!("destroyed a locked mutex");
709        }
710
711        // This write also deletes the interpreter state for this mutex.
712        // This might lead to false positives, see comment in pthread_mutexattr_destroy
713        let mutex_place =
714            this.deref_pointer_as(mutex_op, this.libc_ty_layout("pthread_mutex_t"))?;
715        this.write_uninit(&mutex_place)?;
716
717        interp_ok(())
718    }
719
720    fn pthread_rwlock_rdlock(
721        &mut self,
722        rwlock_op: &OpTy<'tcx>,
723        dest: &MPlaceTy<'tcx>,
724    ) -> InterpResult<'tcx> {
725        let this = self.eval_context_mut();
726
727        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
728
729        if rwlock.rwlock_ref.is_write_locked() {
730            this.rwlock_enqueue_and_block_reader(
731                rwlock.rwlock_ref,
732                Scalar::from_i32(0),
733                dest.clone(),
734            );
735        } else {
736            this.rwlock_reader_lock(&rwlock.rwlock_ref)?;
737            this.write_null(dest)?;
738        }
739
740        interp_ok(())
741    }
742
743    fn pthread_rwlock_tryrdlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
744        let this = self.eval_context_mut();
745
746        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
747
748        if rwlock.rwlock_ref.is_write_locked() {
749            interp_ok(Scalar::from_i32(this.eval_libc_i32("EBUSY")))
750        } else {
751            this.rwlock_reader_lock(&rwlock.rwlock_ref)?;
752            interp_ok(Scalar::from_i32(0))
753        }
754    }
755
756    fn pthread_rwlock_wrlock(
757        &mut self,
758        rwlock_op: &OpTy<'tcx>,
759        dest: &MPlaceTy<'tcx>,
760    ) -> InterpResult<'tcx> {
761        let this = self.eval_context_mut();
762
763        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
764
765        if rwlock.rwlock_ref.is_locked() {
766            // Note: this will deadlock if the lock is already locked by this
767            // thread in any way.
768            //
769            // Relevant documentation:
770            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html
771            // An in-depth discussion on this topic:
772            // https://github.com/rust-lang/rust/issues/53127
773            //
774            // FIXME: Detect and report the deadlock proactively. (We currently
775            // report the deadlock only when no thread can continue execution,
776            // but we could detect that this lock is already locked and report
777            // an error.)
778            this.rwlock_enqueue_and_block_writer(
779                rwlock.rwlock_ref,
780                Scalar::from_i32(0),
781                dest.clone(),
782            );
783        } else {
784            this.rwlock_writer_lock(&rwlock.rwlock_ref)?;
785            this.write_null(dest)?;
786        }
787
788        interp_ok(())
789    }
790
791    fn pthread_rwlock_trywrlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
792        let this = self.eval_context_mut();
793
794        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
795
796        if rwlock.rwlock_ref.is_locked() {
797            interp_ok(Scalar::from_i32(this.eval_libc_i32("EBUSY")))
798        } else {
799            this.rwlock_writer_lock(&rwlock.rwlock_ref)?;
800            interp_ok(Scalar::from_i32(0))
801        }
802    }
803
804    fn pthread_rwlock_unlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
805        let this = self.eval_context_mut();
806
807        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
808
809        if this.rwlock_reader_unlock(&rwlock.rwlock_ref)?
810            || this.rwlock_writer_unlock(&rwlock.rwlock_ref)?
811        {
812            interp_ok(())
813        } else {
814            throw_ub_format!("unlocked an rwlock that was not locked by the active thread");
815        }
816    }
817
818    fn pthread_rwlock_destroy(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
819        let this = self.eval_context_mut();
820
821        // Reading the field also has the side-effect that we detect double-`destroy`
822        // since we make the field uninit below.
823        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
824
825        if rwlock.rwlock_ref.is_locked() {
826            throw_ub_format!("destroyed a locked rwlock");
827        }
828
829        // This write also deletes the interpreter state for this rwlock.
830        // This might lead to false positives, see comment in pthread_mutexattr_destroy
831        let rwlock_place =
832            this.deref_pointer_as(rwlock_op, this.libc_ty_layout("pthread_rwlock_t"))?;
833        this.write_uninit(&rwlock_place)?;
834
835        interp_ok(())
836    }
837
838    fn pthread_condattr_init(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
839        let this = self.eval_context_mut();
840
841        // no clock attribute on macOS
842        if this.tcx.sess.target.os != Os::MacOs {
843            // The default value of the clock attribute shall refer to the system
844            // clock.
845            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html
846            let default_clock_id = this.eval_libc_i32("CLOCK_REALTIME");
847            condattr_set_clock_id(this, attr_op, default_clock_id)?;
848        }
849
850        interp_ok(())
851    }
852
853    fn pthread_condattr_setclock(
854        &mut self,
855        attr_op: &OpTy<'tcx>,
856        clock_id_op: &OpTy<'tcx>,
857    ) -> InterpResult<'tcx, Scalar> {
858        let this = self.eval_context_mut();
859
860        let clock_id = this.read_scalar(clock_id_op)?;
861        if this.parse_clockid(clock_id).is_some() {
862            condattr_set_clock_id(this, attr_op, clock_id.to_i32()?)?;
863        } else {
864            let einval = this.eval_libc_i32("EINVAL");
865            return interp_ok(Scalar::from_i32(einval));
866        }
867
868        interp_ok(Scalar::from_i32(0))
869    }
870
871    fn pthread_condattr_getclock(
872        &mut self,
873        attr_op: &OpTy<'tcx>,
874        clk_id_op: &OpTy<'tcx>,
875    ) -> InterpResult<'tcx, ()> {
876        let this = self.eval_context_mut();
877
878        let clock_id = condattr_get_clock_id(this, attr_op)?;
879        this.write_scalar(
880            clock_id,
881            &this.deref_pointer_as(clk_id_op, this.libc_ty_layout("clockid_t"))?,
882        )?;
883
884        interp_ok(())
885    }
886
887    fn pthread_condattr_destroy(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
888        let this = self.eval_context_mut();
889
890        // Destroying an uninit pthread_condattr is UB, so check to make sure it's not uninit.
891        // There's no clock attribute on macOS.
892        if this.tcx.sess.target.os != Os::MacOs {
893            condattr_get_clock_id(this, attr_op)?;
894        }
895
896        // De-init the entire thing.
897        // This might lead to false positives, see comment in pthread_mutexattr_destroy
898        this.write_uninit(
899            &this.deref_pointer_as(attr_op, this.libc_ty_layout("pthread_condattr_t"))?,
900        )?;
901
902        interp_ok(())
903    }
904
905    fn pthread_cond_init(
906        &mut self,
907        cond_op: &OpTy<'tcx>,
908        attr_op: &OpTy<'tcx>,
909    ) -> InterpResult<'tcx, ()> {
910        let this = self.eval_context_mut();
911
912        let attr = this.read_pointer(attr_op)?;
913        // Default clock if `attr` is null, and on macOS where there is no clock attribute.
914        let clock_id = if this.ptr_is_null(attr)? || this.tcx.sess.target.os == Os::MacOs {
915            this.eval_libc("CLOCK_REALTIME")
916        } else {
917            condattr_get_clock_id(this, attr_op)?
918        };
919        let Some(clock) = this.parse_clockid(clock_id) else {
920            // This is UB since this situation cannot arise when using pthread_condattr_setclock.
921            throw_ub_format!("pthread_cond_init: invalid attributes (unsupported clock)")
922        };
923
924        cond_create(this, cond_op, clock)?;
925
926        interp_ok(())
927    }
928
929    fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
930        let this = self.eval_context_mut();
931        let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
932        this.condvar_signal(&condvar)?;
933        interp_ok(())
934    }
935
936    fn pthread_cond_broadcast(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
937        let this = self.eval_context_mut();
938        let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
939        while this.condvar_signal(&condvar)? {}
940        interp_ok(())
941    }
942
943    fn pthread_cond_wait(
944        &mut self,
945        cond_op: &OpTy<'tcx>,
946        mutex_op: &OpTy<'tcx>,
947        dest: &MPlaceTy<'tcx>,
948    ) -> InterpResult<'tcx> {
949        let this = self.eval_context_mut();
950
951        let data = cond_get_data(this, cond_op)?.clone();
952        let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
953
954        this.condvar_wait(
955            data.condvar_ref,
956            mutex_ref,
957            None, // no timeout
958            Scalar::from_i32(0),
959            Scalar::from_i32(0), // retval_timeout -- unused
960            dest.clone(),
961        )?;
962
963        interp_ok(())
964    }
965
966    fn pthread_cond_timedwait(
967        &mut self,
968        cond_op: &OpTy<'tcx>,
969        mutex_op: &OpTy<'tcx>,
970        timeout_op: &OpTy<'tcx>,
971        dest: &MPlaceTy<'tcx>,
972        macos_relative_np: bool,
973    ) -> InterpResult<'tcx> {
974        let this = self.eval_context_mut();
975
976        let data = cond_get_data(this, cond_op)?.clone();
977        let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
978
979        // Extract the timeout.
980        let Some(duration) = this
981            .read_timespec(&this.deref_pointer_as(timeout_op, this.libc_ty_layout("timespec"))?)?
982        else {
983            let einval = this.eval_libc("EINVAL");
984            this.write_scalar(einval, dest)?;
985            return interp_ok(());
986        };
987
988        let (clock, style) = if macos_relative_np {
989            // `pthread_cond_timedwait_relative_np` always measures time against the
990            // monotonic clock, regardless of the condvar clock.
991            (TimeoutClock::Monotonic, TimeoutStyle::Relative)
992        } else {
993            if data.clock == TimeoutClock::RealTime {
994                this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
995            }
996
997            (data.clock, TimeoutStyle::Absolute)
998        };
999
1000        this.condvar_wait(
1001            data.condvar_ref,
1002            mutex_ref,
1003            Some(this.machine.timeout(clock, style, duration)),
1004            Scalar::from_i32(0),
1005            this.eval_libc("ETIMEDOUT"), // retval_timeout
1006            dest.clone(),
1007        )?;
1008
1009        interp_ok(())
1010    }
1011
1012    fn pthread_cond_destroy(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
1013        let this = self.eval_context_mut();
1014
1015        // Reading the field also has the side-effect that we detect double-`destroy`
1016        // since we make the field uninit below.
1017        let condvar = &cond_get_data(this, cond_op)?.condvar_ref;
1018        if !condvar.queue_is_empty() {
1019            throw_ub_format!("destroying an awaited conditional variable");
1020        }
1021
1022        // This write also deletes the interpreter state for this condvar.
1023        // This might lead to false positives, see comment in pthread_mutexattr_destroy
1024        let cond_place = this.deref_pointer_as(cond_op, this.libc_ty_layout("pthread_cond_t"))?;
1025        this.write_uninit(&cond_place)?;
1026
1027        interp_ok(())
1028    }
1029
1030    fn pthread_sem_init(
1031        &mut self,
1032        sem_op: &OpTy<'tcx>,
1033        pshared_op: &OpTy<'tcx>,
1034        value_op: &OpTy<'tcx>,
1035        dest: &MPlaceTy<'tcx>,
1036    ) -> InterpResult<'tcx> {
1037        let this = self.eval_context_mut();
1038
1039        let pshared = this.read_scalar(pshared_op)?.to_i32()?;
1040        let value = this.read_scalar(value_op)?.to_u32()?;
1041
1042        // We can ignore `pshared` since memory cannot actually be shared with other processes.
1043        let _ignore = pshared;
1044
1045        sem_create(this, sem_op, value)?;
1046
1047        this.write_null(dest)?; // we always succeed
1048        interp_ok(())
1049    }
1050
1051    fn pthread_sem_wait(
1052        &mut self,
1053        sem_op: &OpTy<'tcx>,
1054        dest: &MPlaceTy<'tcx>,
1055    ) -> InterpResult<'tcx> {
1056        let this = self.eval_context_mut();
1057
1058        let data = sem_get_data(this, sem_op)?.clone();
1059        let dest = dest.clone();
1060        this.semaphore_decrement(
1061            data.sem_ref,
1062            /* deadline */ None,
1063            callback!(
1064                @capture<'tcx> {
1065                    dest: MPlaceTy<'tcx>,
1066                }
1067                |this, unblock: UnblockKind| {
1068                    assert_eq!(unblock, UnblockKind::Ready);
1069                    this.write_null(&dest)?;
1070                    interp_ok(())
1071                }
1072            ),
1073        )?;
1074
1075        interp_ok(())
1076    }
1077
1078    fn pthread_sem_timedwait(
1079        &mut self,
1080        sem_op: &OpTy<'tcx>,
1081        abs_timeout_op: &OpTy<'tcx>,
1082        dest: &MPlaceTy<'tcx>,
1083    ) -> InterpResult<'tcx> {
1084        let this = self.eval_context_mut();
1085        this.check_no_isolation("`pthread_sem_timedwait`")?;
1086
1087        let abs_timeout = this.deref_pointer_as(abs_timeout_op, this.libc_ty_layout("timespec"))?;
1088
1089        let Some(duration) = this.read_timespec(&abs_timeout)? else {
1090            return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
1091        };
1092        let deadline =
1093            this.machine.timeout(TimeoutClock::RealTime, TimeoutStyle::Absolute, duration);
1094        let data = sem_get_data(this, sem_op)?.clone();
1095        let dest = dest.clone();
1096        this.semaphore_decrement(
1097            data.sem_ref,
1098            Some(deadline),
1099            callback!(
1100                @capture<'tcx> {
1101                    dest: MPlaceTy<'tcx>,
1102                }
1103                |this, unblock: UnblockKind| {
1104                    match unblock {
1105                        UnblockKind::Ready =>
1106                            this.write_null(&dest),
1107                        UnblockKind::TimedOut =>
1108                            this.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest),
1109                    }
1110                }
1111            ),
1112        )?;
1113
1114        interp_ok(())
1115    }
1116
1117    fn pthread_sem_post(
1118        &mut self,
1119        sem_op: &OpTy<'tcx>,
1120        dest: &MPlaceTy<'tcx>,
1121    ) -> InterpResult<'tcx> {
1122        let this = self.eval_context_mut();
1123
1124        let data = sem_get_data(this, sem_op)?.clone();
1125        this.semaphore_increment(&data.sem_ref)?;
1126
1127        this.write_null(dest)?; // we always succeed
1128        interp_ok(())
1129    }
1130
1131    fn pthread_sem_destroy(
1132        &mut self,
1133        sem_op: &OpTy<'tcx>,
1134        dest: &MPlaceTy<'tcx>,
1135    ) -> InterpResult<'tcx> {
1136        let this = self.eval_context_mut();
1137
1138        let data = sem_get_data(this, sem_op)?.clone();
1139        if !data.sem_ref.queue_is_empty() {
1140            throw_ub_format!("destroying an awaited semaphore");
1141        }
1142
1143        // This write also deletes the interpreter state for this semaphore.
1144        // This might lead to false positives, see comment in pthread_mutexattr_destroy
1145        let sem_place = this.deref_pointer_as(sem_op, this.libc_ty_layout("sem_t"))?;
1146        this.write_uninit(&sem_place)?;
1147
1148        this.write_null(dest)?; // we always succeed
1149        interp_ok(())
1150    }
1151}