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, PthreadMutex> {
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.clone())?;
191    interp_ok(data)
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, PthreadCondvar> {
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.clone())?;
429    interp_ok(data)
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
471impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
472pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
473    fn pthread_mutexattr_init(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
474        let this = self.eval_context_mut();
475
476        mutexattr_set_kind(this, attr_op, PTHREAD_MUTEX_KIND_UNCHANGED)?;
477
478        interp_ok(())
479    }
480
481    fn pthread_mutexattr_settype(
482        &mut self,
483        attr_op: &OpTy<'tcx>,
484        kind_op: &OpTy<'tcx>,
485    ) -> InterpResult<'tcx, Scalar> {
486        let this = self.eval_context_mut();
487
488        let kind = this.read_scalar(kind_op)?.to_i32()?;
489        if kind == this.eval_libc_i32("PTHREAD_MUTEX_NORMAL")
490            || kind == this.eval_libc_i32("PTHREAD_MUTEX_DEFAULT")
491            || kind == this.eval_libc_i32("PTHREAD_MUTEX_ERRORCHECK")
492            || kind == this.eval_libc_i32("PTHREAD_MUTEX_RECURSIVE")
493        {
494            // Make sure we do not mix this up with the "unchanged" kind.
495            assert_ne!(kind, PTHREAD_MUTEX_KIND_UNCHANGED);
496            mutexattr_set_kind(this, attr_op, kind)?;
497        } else {
498            let einval = this.eval_libc_i32("EINVAL");
499            return interp_ok(Scalar::from_i32(einval));
500        }
501
502        interp_ok(Scalar::from_i32(0))
503    }
504
505    fn pthread_mutexattr_destroy(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
506        let this = self.eval_context_mut();
507
508        // Destroying an uninit pthread_mutexattr is UB, so check to make sure it's not uninit.
509        mutexattr_get_kind(this, attr_op)?;
510
511        // To catch double-destroys, we de-initialize the mutexattr.
512        // This is technically not right and might lead to false positives. For example, the below
513        // code is *likely* sound, even assuming uninit numbers are UB, but Miri complains.
514        //
515        // let mut x: MaybeUninit<libc::pthread_mutexattr_t> = MaybeUninit::zeroed();
516        // libc::pthread_mutexattr_init(x.as_mut_ptr());
517        // libc::pthread_mutexattr_destroy(x.as_mut_ptr());
518        // x.assume_init();
519        //
520        // However, the way libstd uses the pthread APIs works in our favor here, so we can get away with this.
521        // This can always be revisited to have some external state to catch double-destroys
522        // but not complain about the above code. See https://github.com/rust-lang/miri/pull/1933
523        this.write_uninit(
524            &this.deref_pointer_as(attr_op, this.libc_ty_layout("pthread_mutexattr_t"))?,
525        )?;
526
527        interp_ok(())
528    }
529
530    fn pthread_mutex_init(
531        &mut self,
532        mutex_op: &OpTy<'tcx>,
533        attr_op: &OpTy<'tcx>,
534    ) -> InterpResult<'tcx, ()> {
535        let this = self.eval_context_mut();
536
537        let attr = this.read_pointer(attr_op)?;
538        let kind = if this.ptr_is_null(attr)? {
539            MutexKind::Default
540        } else {
541            mutexattr_translate_kind(this, mutexattr_get_kind(this, attr_op)?)?
542        };
543
544        mutex_create(this, mutex_op, kind)?;
545
546        interp_ok(())
547    }
548
549    fn pthread_mutex_lock(
550        &mut self,
551        mutex_op: &OpTy<'tcx>,
552        dest: &MPlaceTy<'tcx>,
553    ) -> InterpResult<'tcx> {
554        let this = self.eval_context_mut();
555
556        let mutex = mutex_get_data(this, mutex_op)?.clone();
557
558        let ret = if let Some(owner_thread) = mutex.mutex_ref.owner() {
559            if owner_thread != this.active_thread() {
560                this.mutex_enqueue_and_block(
561                    mutex.mutex_ref,
562                    Some((Scalar::from_i32(0), dest.clone())),
563                );
564                return interp_ok(());
565            } else {
566                // Trying to acquire the same mutex again.
567                match mutex.kind {
568                    MutexKind::Default =>
569                        throw_ub_format!(
570                            "trying to acquire default mutex already locked by the current thread"
571                        ),
572                    MutexKind::Normal => throw_machine_stop!(TerminationInfo::LocalDeadlock),
573                    MutexKind::ErrorCheck => this.eval_libc_i32("EDEADLK"),
574                    MutexKind::Recursive => {
575                        this.mutex_lock(&mutex.mutex_ref)?;
576                        0
577                    }
578                }
579            }
580        } else {
581            // The mutex is unlocked. Let's lock it.
582            this.mutex_lock(&mutex.mutex_ref)?;
583            0
584        };
585        this.write_scalar(Scalar::from_i32(ret), dest)?;
586        interp_ok(())
587    }
588
589    fn pthread_mutex_trylock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
590        let this = self.eval_context_mut();
591
592        let mutex = mutex_get_data(this, mutex_op)?.clone();
593
594        interp_ok(Scalar::from_i32(if let Some(owner_thread) = mutex.mutex_ref.owner() {
595            if owner_thread != this.active_thread() {
596                this.eval_libc_i32("EBUSY")
597            } else {
598                match mutex.kind {
599                    MutexKind::Default | MutexKind::Normal | MutexKind::ErrorCheck =>
600                        this.eval_libc_i32("EBUSY"),
601                    MutexKind::Recursive => {
602                        this.mutex_lock(&mutex.mutex_ref)?;
603                        0
604                    }
605                }
606            }
607        } else {
608            // The mutex is unlocked. Let's lock it.
609            this.mutex_lock(&mutex.mutex_ref)?;
610            0
611        }))
612    }
613
614    fn pthread_mutex_unlock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
615        let this = self.eval_context_mut();
616
617        let mutex = mutex_get_data(this, mutex_op)?.clone();
618
619        if let Some(_old_locked_count) = this.mutex_unlock(&mutex.mutex_ref)? {
620            // The mutex was locked by the current thread.
621            interp_ok(Scalar::from_i32(0))
622        } else {
623            // The mutex was locked by another thread or not locked at all. See
624            // the “Unlock When Not Owner” column in
625            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_unlock.html.
626            match mutex.kind {
627                MutexKind::Default =>
628                    throw_ub_format!(
629                        "unlocked a default mutex that was not locked by the current thread"
630                    ),
631                MutexKind::Normal =>
632                    throw_ub_format!(
633                        "unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread"
634                    ),
635                MutexKind::ErrorCheck | MutexKind::Recursive =>
636                    interp_ok(Scalar::from_i32(this.eval_libc_i32("EPERM"))),
637            }
638        }
639    }
640
641    fn pthread_mutex_destroy(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
642        let this = self.eval_context_mut();
643
644        // Reading the field also has the side-effect that we detect double-`destroy`
645        // since we make the field uninit below.
646        let mutex = mutex_get_data(this, mutex_op)?.clone();
647
648        if mutex.mutex_ref.owner().is_some() {
649            throw_ub_format!("destroyed a locked mutex");
650        }
651
652        // This write also deletes the interpreter state for this mutex.
653        // This might lead to false positives, see comment in pthread_mutexattr_destroy
654        let mutex_place =
655            this.deref_pointer_as(mutex_op, this.libc_ty_layout("pthread_mutex_t"))?;
656        this.write_uninit(&mutex_place)?;
657
658        interp_ok(())
659    }
660
661    fn pthread_rwlock_rdlock(
662        &mut self,
663        rwlock_op: &OpTy<'tcx>,
664        dest: &MPlaceTy<'tcx>,
665    ) -> InterpResult<'tcx> {
666        let this = self.eval_context_mut();
667
668        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
669
670        if rwlock.rwlock_ref.is_write_locked() {
671            this.rwlock_enqueue_and_block_reader(
672                rwlock.rwlock_ref,
673                Scalar::from_i32(0),
674                dest.clone(),
675            );
676        } else {
677            this.rwlock_reader_lock(&rwlock.rwlock_ref)?;
678            this.write_null(dest)?;
679        }
680
681        interp_ok(())
682    }
683
684    fn pthread_rwlock_tryrdlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
685        let this = self.eval_context_mut();
686
687        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
688
689        if rwlock.rwlock_ref.is_write_locked() {
690            interp_ok(Scalar::from_i32(this.eval_libc_i32("EBUSY")))
691        } else {
692            this.rwlock_reader_lock(&rwlock.rwlock_ref)?;
693            interp_ok(Scalar::from_i32(0))
694        }
695    }
696
697    fn pthread_rwlock_wrlock(
698        &mut self,
699        rwlock_op: &OpTy<'tcx>,
700        dest: &MPlaceTy<'tcx>,
701    ) -> InterpResult<'tcx> {
702        let this = self.eval_context_mut();
703
704        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
705
706        if rwlock.rwlock_ref.is_locked() {
707            // Note: this will deadlock if the lock is already locked by this
708            // thread in any way.
709            //
710            // Relevant documentation:
711            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html
712            // An in-depth discussion on this topic:
713            // https://github.com/rust-lang/rust/issues/53127
714            //
715            // FIXME: Detect and report the deadlock proactively. (We currently
716            // report the deadlock only when no thread can continue execution,
717            // but we could detect that this lock is already locked and report
718            // an error.)
719            this.rwlock_enqueue_and_block_writer(
720                rwlock.rwlock_ref,
721                Scalar::from_i32(0),
722                dest.clone(),
723            );
724        } else {
725            this.rwlock_writer_lock(&rwlock.rwlock_ref)?;
726            this.write_null(dest)?;
727        }
728
729        interp_ok(())
730    }
731
732    fn pthread_rwlock_trywrlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
733        let this = self.eval_context_mut();
734
735        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
736
737        if rwlock.rwlock_ref.is_locked() {
738            interp_ok(Scalar::from_i32(this.eval_libc_i32("EBUSY")))
739        } else {
740            this.rwlock_writer_lock(&rwlock.rwlock_ref)?;
741            interp_ok(Scalar::from_i32(0))
742        }
743    }
744
745    fn pthread_rwlock_unlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
746        let this = self.eval_context_mut();
747
748        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
749
750        if this.rwlock_reader_unlock(&rwlock.rwlock_ref)?
751            || this.rwlock_writer_unlock(&rwlock.rwlock_ref)?
752        {
753            interp_ok(())
754        } else {
755            throw_ub_format!("unlocked an rwlock that was not locked by the active thread");
756        }
757    }
758
759    fn pthread_rwlock_destroy(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
760        let this = self.eval_context_mut();
761
762        // Reading the field also has the side-effect that we detect double-`destroy`
763        // since we make the field uninit below.
764        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
765
766        if rwlock.rwlock_ref.is_locked() {
767            throw_ub_format!("destroyed a locked rwlock");
768        }
769
770        // This write also deletes the interpreter state for this rwlock.
771        // This might lead to false positives, see comment in pthread_mutexattr_destroy
772        let rwlock_place =
773            this.deref_pointer_as(rwlock_op, this.libc_ty_layout("pthread_rwlock_t"))?;
774        this.write_uninit(&rwlock_place)?;
775
776        interp_ok(())
777    }
778
779    fn pthread_condattr_init(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
780        let this = self.eval_context_mut();
781
782        // no clock attribute on macOS
783        if this.tcx.sess.target.os != Os::MacOs {
784            // The default value of the clock attribute shall refer to the system
785            // clock.
786            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html
787            let default_clock_id = this.eval_libc_i32("CLOCK_REALTIME");
788            condattr_set_clock_id(this, attr_op, default_clock_id)?;
789        }
790
791        interp_ok(())
792    }
793
794    fn pthread_condattr_setclock(
795        &mut self,
796        attr_op: &OpTy<'tcx>,
797        clock_id_op: &OpTy<'tcx>,
798    ) -> InterpResult<'tcx, Scalar> {
799        let this = self.eval_context_mut();
800
801        let clock_id = this.read_scalar(clock_id_op)?;
802        if this.parse_clockid(clock_id).is_some() {
803            condattr_set_clock_id(this, attr_op, clock_id.to_i32()?)?;
804        } else {
805            let einval = this.eval_libc_i32("EINVAL");
806            return interp_ok(Scalar::from_i32(einval));
807        }
808
809        interp_ok(Scalar::from_i32(0))
810    }
811
812    fn pthread_condattr_getclock(
813        &mut self,
814        attr_op: &OpTy<'tcx>,
815        clk_id_op: &OpTy<'tcx>,
816    ) -> InterpResult<'tcx, ()> {
817        let this = self.eval_context_mut();
818
819        let clock_id = condattr_get_clock_id(this, attr_op)?;
820        this.write_scalar(
821            clock_id,
822            &this.deref_pointer_as(clk_id_op, this.libc_ty_layout("clockid_t"))?,
823        )?;
824
825        interp_ok(())
826    }
827
828    fn pthread_condattr_destroy(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
829        let this = self.eval_context_mut();
830
831        // Destroying an uninit pthread_condattr is UB, so check to make sure it's not uninit.
832        // There's no clock attribute on macOS.
833        if this.tcx.sess.target.os != Os::MacOs {
834            condattr_get_clock_id(this, attr_op)?;
835        }
836
837        // De-init the entire thing.
838        // This might lead to false positives, see comment in pthread_mutexattr_destroy
839        this.write_uninit(
840            &this.deref_pointer_as(attr_op, this.libc_ty_layout("pthread_condattr_t"))?,
841        )?;
842
843        interp_ok(())
844    }
845
846    fn pthread_cond_init(
847        &mut self,
848        cond_op: &OpTy<'tcx>,
849        attr_op: &OpTy<'tcx>,
850    ) -> InterpResult<'tcx, ()> {
851        let this = self.eval_context_mut();
852
853        let attr = this.read_pointer(attr_op)?;
854        // Default clock if `attr` is null, and on macOS where there is no clock attribute.
855        let clock_id = if this.ptr_is_null(attr)? || this.tcx.sess.target.os == Os::MacOs {
856            this.eval_libc("CLOCK_REALTIME")
857        } else {
858            condattr_get_clock_id(this, attr_op)?
859        };
860        let Some(clock) = this.parse_clockid(clock_id) else {
861            // This is UB since this situation cannot arise when using pthread_condattr_setclock.
862            throw_ub_format!("pthread_cond_init: invalid attributes (unsupported clock)")
863        };
864
865        cond_create(this, cond_op, clock)?;
866
867        interp_ok(())
868    }
869
870    fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
871        let this = self.eval_context_mut();
872        let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
873        this.condvar_signal(&condvar)?;
874        interp_ok(())
875    }
876
877    fn pthread_cond_broadcast(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
878        let this = self.eval_context_mut();
879        let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
880        while this.condvar_signal(&condvar)? {}
881        interp_ok(())
882    }
883
884    fn pthread_cond_wait(
885        &mut self,
886        cond_op: &OpTy<'tcx>,
887        mutex_op: &OpTy<'tcx>,
888        dest: &MPlaceTy<'tcx>,
889    ) -> InterpResult<'tcx> {
890        let this = self.eval_context_mut();
891
892        let data = cond_get_data(this, cond_op)?.clone();
893        let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
894
895        this.condvar_wait(
896            data.condvar_ref,
897            mutex_ref,
898            None, // no timeout
899            Scalar::from_i32(0),
900            Scalar::from_i32(0), // retval_timeout -- unused
901            dest.clone(),
902        )?;
903
904        interp_ok(())
905    }
906
907    fn pthread_cond_timedwait(
908        &mut self,
909        cond_op: &OpTy<'tcx>,
910        mutex_op: &OpTy<'tcx>,
911        timeout_op: &OpTy<'tcx>,
912        dest: &MPlaceTy<'tcx>,
913        macos_relative_np: bool,
914    ) -> InterpResult<'tcx> {
915        let this = self.eval_context_mut();
916
917        let data = cond_get_data(this, cond_op)?.clone();
918        let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
919
920        // Extract the timeout.
921        let Some(duration) = this
922            .read_timespec(&this.deref_pointer_as(timeout_op, this.libc_ty_layout("timespec"))?)?
923        else {
924            let einval = this.eval_libc("EINVAL");
925            this.write_scalar(einval, dest)?;
926            return interp_ok(());
927        };
928
929        let (clock, style) = if macos_relative_np {
930            // `pthread_cond_timedwait_relative_np` always measures time against the
931            // monotonic clock, regardless of the condvar clock.
932            (TimeoutClock::Monotonic, TimeoutStyle::Relative)
933        } else {
934            if data.clock == TimeoutClock::RealTime {
935                this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
936            }
937
938            (data.clock, TimeoutStyle::Absolute)
939        };
940
941        this.condvar_wait(
942            data.condvar_ref,
943            mutex_ref,
944            Some(this.machine.timeout(clock, style, duration)),
945            Scalar::from_i32(0),
946            this.eval_libc("ETIMEDOUT"), // retval_timeout
947            dest.clone(),
948        )?;
949
950        interp_ok(())
951    }
952
953    fn pthread_cond_destroy(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
954        let this = self.eval_context_mut();
955
956        // Reading the field also has the side-effect that we detect double-`destroy`
957        // since we make the field uninit below.
958        let condvar = &cond_get_data(this, cond_op)?.condvar_ref;
959        if !condvar.queue_is_empty() {
960            throw_ub_format!("destroying an awaited conditional variable");
961        }
962
963        // This write also deletes the interpreter state for this mutex.
964        // This might lead to false positives, see comment in pthread_mutexattr_destroy
965        let cond_place = this.deref_pointer_as(cond_op, this.libc_ty_layout("pthread_cond_t"))?;
966        this.write_uninit(&cond_place)?;
967
968        interp_ok(())
969    }
970}