Skip to main content

miri/shims/unix/
sync.rs

1use rustc_abi::Size;
2use rustc_target::spec::Os;
3
4use crate::*;
5
6/// Check if `place` stores the initializer called `name`.
7///
8/// `prefix`, if set, indicates that only the first N bytes should be compared.
9fn is_initializer<'tcx>(
10    ecx: &MiriInterpCx<'tcx>,
11    place: &MPlaceTy<'tcx>,
12    name: &str,
13    prefix: Option<u64>,
14) -> InterpResult<'tcx, bool> {
15    let size = place.layout.size;
16    let cmp_size = prefix.map(Size::from_bytes).unwrap_or(size);
17
18    let init = &ecx.eval_path(&["libc", name]);
19    assert_eq!(size, init.layout.size);
20
21    let place_bytes = ecx.read_bytes_ptr_strip_provenance(place.ptr(), cmp_size)?;
22    let init_bytes = ecx.read_bytes_ptr_strip_provenance(init.ptr(), cmp_size)?;
23
24    if place_bytes == init_bytes {
25        // Ensure that the static initializers are different from the dummy data backing an already
26        // initialized object.
27        assert!(init_bytes.iter().any(|b| *b != crate::meta_obj::OPAQUE_OBJ_COOKIE));
28        interp_ok(true)
29    } else {
30        interp_ok(false)
31    }
32}
33
34// # pthread_mutexattr_t
35
36struct PthreadMutexAttr {
37    /// `None` represents that the kind has not yet been explicitly set.
38    kind: Option<MutexKind>,
39}
40
41/// The mutex kind.
42#[derive(Debug, Clone, Copy)]
43enum MutexKind {
44    Normal,
45    Default,
46    Recursive,
47    ErrorCheck,
48}
49
50impl MetaObj for PthreadMutexAttr {
51    fn opaque(&self) -> bool {
52        true
53    }
54}
55
56fn mutexattr_create<'tcx>(
57    ecx: &mut MiriInterpCx<'tcx>,
58    mutexattr_ptr: &OpTy<'tcx>,
59) -> InterpResult<'tcx> {
60    let mutexattr = ecx.deref_pointer(mutexattr_ptr)?;
61    let data = PthreadMutexAttr { kind: None };
62    ecx.insert_meta(&mutexattr, Box::new(data))?;
63    interp_ok(())
64}
65
66fn mutexattr_destroy<'tcx>(
67    ecx: &mut MiriInterpCx<'tcx>,
68    mutexattr_ptr: &OpTy<'tcx>,
69) -> InterpResult<'tcx> {
70    let mutexattr = ecx.deref_pointer(mutexattr_ptr)?;
71    ecx.remove_meta::<PthreadMutexAttr>(&mutexattr)?.ok_or_else(|| {
72        err_ub_format!("`pthread_mutexattr_t` has not been initialized or has been tampered with")
73    })?;
74    interp_ok(())
75}
76
77/// Returns the mutex attribute data stored at the address that `mutexattr_ptr` points to.
78/// Will raise an error if the mutex has been moved since its first use.
79fn mutexattr_get_data<'tcx, 'a>(
80    ecx: &'a mut MiriInterpCx<'tcx>,
81    mutexattr_ptr: &OpTy<'tcx>,
82) -> InterpResult<'tcx, &'a mut PthreadMutexAttr>
83where
84    'tcx: 'a,
85{
86    let mutexattr = ecx.deref_pointer(mutexattr_ptr)?;
87    ecx.get_or_insert_meta(&mutexattr, |_ecx| {
88        throw_ub_format!("`pthread_mutexattr_t` has not been initialized or has been tampered with")
89    })
90}
91
92/// Translates the mutex kind from what is stored in pthread_mutexattr_t to our enum.
93fn mutexattr_translate_kind<'tcx>(ecx: &MiriInterpCx<'tcx>, kind: i32) -> Option<MutexKind> {
94    Some(if kind == (ecx.eval_libc_i32("PTHREAD_MUTEX_NORMAL")) {
95        MutexKind::Normal
96    } else if kind == ecx.eval_libc_i32("PTHREAD_MUTEX_ERRORCHECK") {
97        MutexKind::ErrorCheck
98    } else if kind == ecx.eval_libc_i32("PTHREAD_MUTEX_RECURSIVE") {
99        MutexKind::Recursive
100    } else if kind == ecx.eval_libc_i32("PTHREAD_MUTEX_DEFAULT") {
101        // We check this *last* since PTHREAD_MUTEX_DEFAULT may be numerically equal to one of the
102        // others, and we want an explicit `mutexattr_settype` to work as expected.
103        MutexKind::Default
104    } else {
105        return None;
106    })
107}
108
109// # pthread_mutex_t
110
111#[derive(Debug, Clone)]
112struct PthreadMutex {
113    mutex_ref: MutexRef,
114    kind: MutexKind,
115}
116
117impl MetaObj for PthreadMutex {
118    fn on_access<'tcx>(&self) -> InterpResult<'tcx> {
119        if !self.mutex_ref.queue_is_empty() {
120            throw_ub_format!(
121                "accessing the memory backing a `pthread_mutex_t` is forbidden while the queue is non-empty"
122            );
123        }
124        interp_ok(())
125    }
126
127    fn opaque(&self) -> bool {
128        true
129    }
130}
131
132/// Eagerly create and initialize a new mutex.
133fn mutex_create<'tcx>(
134    ecx: &mut MiriInterpCx<'tcx>,
135    mutex_ptr: &OpTy<'tcx>,
136    kind: MutexKind,
137) -> InterpResult<'tcx> {
138    let mutex = ecx.deref_pointer(mutex_ptr)?;
139    let data = PthreadMutex { mutex_ref: MutexRef::new(), kind };
140    ecx.insert_meta(&mutex, Box::new(data))?;
141    interp_ok(())
142}
143
144/// Returns the mutex data stored at the address that `mutex_ptr` points to.
145/// Will raise an error if the mutex has been moved since its first use.
146fn mutex_get_data<'tcx, 'a>(
147    ecx: &'a mut MiriInterpCx<'tcx>,
148    mutex_ptr: &OpTy<'tcx>,
149) -> InterpResult<'tcx, &'a mut PthreadMutex>
150where
151    'tcx: 'a,
152{
153    let mutex = ecx.deref_pointer(mutex_ptr)?;
154    ecx.get_or_insert_meta(&mutex, |ecx| {
155        let kind = mutex_kind_from_static_initializer(ecx, &mutex)?;
156        interp_ok(PthreadMutex { mutex_ref: MutexRef::new(), kind })
157    })
158}
159
160/// Returns the kind of a static initializer.
161fn mutex_kind_from_static_initializer<'tcx>(
162    ecx: &MiriInterpCx<'tcx>,
163    mutex: &MPlaceTy<'tcx>,
164) -> InterpResult<'tcx, MutexKind> {
165    let prefix = match &ecx.tcx.sess.target.os {
166        // On android, there's a 4-byte `value` header followed by "padding", and some versions
167        // of libc leave that padding uninitialized. Only check the `value` bytes.
168        Os::Android => Some(4),
169        _ => None,
170    };
171    let is_initializer = |name| is_initializer(ecx, mutex, name, prefix);
172
173    // All the static initializers recognized here *must* be checked in `mutex_init_offset`!
174
175    // PTHREAD_MUTEX_INITIALIZER is recognized on all targets.
176    if is_initializer("PTHREAD_MUTEX_INITIALIZER")? {
177        return interp_ok(MutexKind::Default);
178    }
179    // Support additional platform-specific initializers.
180    match &ecx.tcx.sess.target.os {
181        Os::Linux =>
182            if is_initializer("PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP")? {
183                return interp_ok(MutexKind::Recursive);
184            } else if is_initializer("PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP")? {
185                return interp_ok(MutexKind::ErrorCheck);
186            },
187        _ => {}
188    }
189    throw_ub_format!(
190        "`pthread_mutex_t` was not properly initialized at this location, or it got overwritten"
191    );
192}
193
194// # pthread_rwlock_t
195
196#[derive(Debug, Clone)]
197struct PthreadRwLock {
198    rwlock_ref: RwLockRef,
199}
200
201impl MetaObj for PthreadRwLock {
202    fn on_access<'tcx>(&self) -> InterpResult<'tcx> {
203        if !self.rwlock_ref.queue_is_empty() {
204            throw_ub_format!(
205                "accessing the memory backing a `pthread_rwlock_t` is forbidden while the queue is non-empty"
206            );
207        }
208        interp_ok(())
209    }
210
211    fn opaque(&self) -> bool {
212        true
213    }
214}
215
216fn rwlock_get_data<'tcx, 'a>(
217    ecx: &'a mut MiriInterpCx<'tcx>,
218    rwlock_ptr: &OpTy<'tcx>,
219) -> InterpResult<'tcx, &'a mut PthreadRwLock>
220where
221    'tcx: 'a,
222{
223    let rwlock = ecx.deref_pointer(rwlock_ptr)?;
224    ecx.get_or_insert_meta(
225        &rwlock,
226        |ecx| {
227            let prefix = match &ecx.tcx.sess.target.os {
228                // On android, there's a 4-byte `value` header followed by "padding", and some
229                // versions of libc leave that uninitialized. Only check the `value` bytes.
230                Os::Android => Some(4),
231                _ => None,
232            };
233            if !is_initializer(
234                ecx,
235                &rwlock,
236                "PTHREAD_RWLOCK_INITIALIZER",
237                prefix,
238            )? {
239                throw_ub_format!(
240                    "`pthread_rwlock_t` was not properly initialized at this location, or it got overwritten"
241                );
242            }
243            interp_ok(PthreadRwLock { rwlock_ref: RwLockRef::new() })
244        },
245    )
246}
247
248// # pthread_condattr_t
249
250struct PthreadCondAttr {
251    clock: TimeoutClock,
252}
253
254impl MetaObj for PthreadCondAttr {
255    fn opaque(&self) -> bool {
256        true
257    }
258}
259
260fn condattr_create<'tcx>(
261    ecx: &mut MiriInterpCx<'tcx>,
262    condattr_ptr: &OpTy<'tcx>,
263) -> InterpResult<'tcx> {
264    let condattr = ecx.deref_pointer(condattr_ptr)?;
265    let data = PthreadCondAttr { clock: TimeoutClock::RealTime };
266    ecx.insert_meta(&condattr, Box::new(data))?;
267    interp_ok(())
268}
269
270fn condattr_destroy<'tcx>(
271    ecx: &mut MiriInterpCx<'tcx>,
272    condattr_ptr: &OpTy<'tcx>,
273) -> InterpResult<'tcx> {
274    let condattr = ecx.deref_pointer(condattr_ptr)?;
275    ecx.remove_meta::<PthreadCondAttr>(&condattr)?.ok_or_else(|| {
276        err_ub_format!("`pthread_condattr_t` has not been initialized or has been tampered with")
277    })?;
278    interp_ok(())
279}
280
281/// Returns the mutex attribute data stored at the address that `condattr_ptr` points to.
282/// Will raise an error if the mutex has been moved since its first use.
283fn condattr_get_data<'tcx, 'a>(
284    ecx: &'a mut MiriInterpCx<'tcx>,
285    condattr_ptr: &OpTy<'tcx>,
286) -> InterpResult<'tcx, &'a mut PthreadCondAttr>
287where
288    'tcx: 'a,
289{
290    let condattr = ecx.deref_pointer(condattr_ptr)?;
291    ecx.get_or_insert_meta(&condattr, |_ecx| {
292        throw_ub_format!("`pthread_condattr_t` has not been initialized or has been tampered with")
293    })
294}
295
296// # pthread_cond_t
297
298#[derive(Debug, Clone)]
299struct PthreadCondvar {
300    condvar_ref: CondvarRef,
301    clock: TimeoutClock,
302}
303
304impl MetaObj for PthreadCondvar {
305    fn on_access<'tcx>(&self) -> InterpResult<'tcx> {
306        if !self.condvar_ref.queue_is_empty() {
307            throw_ub_format!(
308                "accessing the memory backing a `pthread_cond_t` is forbidden while the queue is non-empty"
309            );
310        }
311        interp_ok(())
312    }
313
314    fn opaque(&self) -> bool {
315        true
316    }
317}
318
319fn cond_create<'tcx>(
320    ecx: &mut MiriInterpCx<'tcx>,
321    cond_ptr: &OpTy<'tcx>,
322    clock: TimeoutClock,
323) -> InterpResult<'tcx> {
324    let cond = ecx.deref_pointer(cond_ptr)?;
325    let data = PthreadCondvar { condvar_ref: CondvarRef::new(), clock };
326    ecx.insert_meta(&cond, Box::new(data))?;
327    interp_ok(())
328}
329
330fn cond_get_data<'tcx, 'a>(
331    ecx: &'a mut MiriInterpCx<'tcx>,
332    cond_ptr: &OpTy<'tcx>,
333) -> InterpResult<'tcx, &'a mut PthreadCondvar>
334where
335    'tcx: 'a,
336{
337    let cond = ecx.deref_pointer(cond_ptr)?;
338    ecx.get_or_insert_meta(
339        &cond,
340        |ecx| {
341            let prefix = match &ecx.tcx.sess.target.os {
342                // On android, there's a 4-byte `value` header followed by "padding", and some
343                // versions of libc leave that uninitialized. Only check the `value` bytes.
344                Os::Android => Some(4),
345                _ => None,
346            };
347            if !is_initializer(
348                ecx,
349                &cond,
350                "PTHREAD_COND_INITIALIZER",
351                prefix,
352            )? {
353                throw_ub_format!(
354                    "`pthread_cond_t` was not properly initialized at this location, or it got overwritten"
355                );
356            }
357            // This used the static initializer. The clock there is always CLOCK_REALTIME.
358            interp_ok(PthreadCondvar {
359                condvar_ref: CondvarRef::new(),
360                clock: TimeoutClock::RealTime,
361            })
362        },
363    )
364}
365
366// # sem_t
367
368#[derive(Debug, Clone)]
369struct PthreadSem {
370    sem_ref: SemaphoreRef,
371}
372
373impl MetaObj for PthreadSem {
374    fn on_access<'tcx>(&self) -> InterpResult<'tcx> {
375        if !self.sem_ref.queue_is_empty() {
376            throw_ub_format!(
377                "accessing the memory backing a `sem_t` is forbidden while the queue is non-empty"
378            );
379        }
380        interp_ok(())
381    }
382
383    fn opaque(&self) -> bool {
384        true
385    }
386}
387
388fn sem_create<'tcx>(
389    ecx: &mut MiriInterpCx<'tcx>,
390    sem_ptr: &OpTy<'tcx>,
391    value: u32,
392) -> InterpResult<'tcx> {
393    let sem = ecx.deref_pointer(sem_ptr)?;
394    let data = PthreadSem { sem_ref: SemaphoreRef::new(value.into()) };
395    ecx.insert_meta(&sem, Box::new(data))?;
396    interp_ok(())
397}
398
399fn sem_get_data<'tcx, 'a>(
400    ecx: &'a mut MiriInterpCx<'tcx>,
401    sem_ptr: &OpTy<'tcx>,
402) -> InterpResult<'tcx, &'a mut PthreadSem>
403where
404    'tcx: 'a,
405{
406    let sem = ecx.deref_pointer(sem_ptr)?;
407    ecx.get_or_insert_meta(&sem, |_ecx| {
408        throw_ub_format!("`sem_t` value has not been initialized with `sem_init`")
409    })
410}
411
412impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
413pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
414    fn pthread_mutexattr_init(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
415        let this = self.eval_context_mut();
416        mutexattr_create(this, attr_op)
417    }
418
419    fn pthread_mutexattr_settype(
420        &mut self,
421        attr_op: &OpTy<'tcx>,
422        kind_op: &OpTy<'tcx>,
423    ) -> InterpResult<'tcx, Scalar> {
424        let this = self.eval_context_mut();
425
426        let kind = this.read_scalar(kind_op)?.to_i32()?;
427        let Some(kind) = mutexattr_translate_kind(this, kind) else {
428            let einval = this.eval_libc_i32("EINVAL");
429            return interp_ok(Scalar::from_i32(einval));
430        };
431
432        let mutexattr = mutexattr_get_data(this, attr_op)?;
433        mutexattr.kind = Some(kind);
434
435        interp_ok(Scalar::from_i32(0))
436    }
437
438    fn pthread_mutexattr_destroy(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
439        let this = self.eval_context_mut();
440        mutexattr_destroy(this, attr_op)
441    }
442
443    fn pthread_mutex_init(
444        &mut self,
445        mutex_op: &OpTy<'tcx>,
446        attr_op: &OpTy<'tcx>,
447    ) -> InterpResult<'tcx, ()> {
448        let this = self.eval_context_mut();
449
450        let attr = this.read_pointer(attr_op)?;
451        let kind = if this.ptr_is_null(attr)? {
452            MutexKind::Default
453        } else {
454            let mutexattr = mutexattr_get_data(this, attr_op)?;
455            mutexattr.kind.unwrap_or(MutexKind::Default)
456        };
457
458        mutex_create(this, mutex_op, kind)?;
459
460        interp_ok(())
461    }
462
463    fn pthread_mutex_lock(
464        &mut self,
465        mutex_op: &OpTy<'tcx>,
466        dest: &MPlaceTy<'tcx>,
467    ) -> InterpResult<'tcx> {
468        let this = self.eval_context_mut();
469
470        let mutex = mutex_get_data(this, mutex_op)?.clone();
471
472        let ret = if let Some(owner_thread) = mutex.mutex_ref.owner() {
473            if owner_thread != this.active_thread() {
474                this.mutex_enqueue_and_block(
475                    mutex.mutex_ref,
476                    Some((Scalar::from_i32(0), dest.clone())),
477                );
478                return interp_ok(());
479            } else {
480                // Trying to acquire the same mutex again.
481                match mutex.kind {
482                    MutexKind::Default =>
483                        throw_ub_format!(
484                            "trying to acquire default mutex already locked by the current thread"
485                        ),
486                    MutexKind::Normal => throw_machine_stop!(TerminationInfo::LocalDeadlock),
487                    MutexKind::ErrorCheck => this.eval_libc_i32("EDEADLK"),
488                    MutexKind::Recursive => {
489                        this.mutex_lock(&mutex.mutex_ref)?;
490                        0
491                    }
492                }
493            }
494        } else {
495            // The mutex is unlocked. Let's lock it.
496            this.mutex_lock(&mutex.mutex_ref)?;
497            0
498        };
499        this.write_scalar(Scalar::from_i32(ret), dest)?;
500        interp_ok(())
501    }
502
503    fn pthread_mutex_trylock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
504        let this = self.eval_context_mut();
505
506        let mutex = mutex_get_data(this, mutex_op)?.clone();
507
508        interp_ok(Scalar::from_i32(if let Some(owner_thread) = mutex.mutex_ref.owner() {
509            if owner_thread != this.active_thread() {
510                this.eval_libc_i32("EBUSY")
511            } else {
512                match mutex.kind {
513                    MutexKind::Default | MutexKind::Normal | MutexKind::ErrorCheck =>
514                        this.eval_libc_i32("EBUSY"),
515                    MutexKind::Recursive => {
516                        this.mutex_lock(&mutex.mutex_ref)?;
517                        0
518                    }
519                }
520            }
521        } else {
522            // The mutex is unlocked. Let's lock it.
523            this.mutex_lock(&mutex.mutex_ref)?;
524            0
525        }))
526    }
527
528    fn pthread_mutex_unlock(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
529        let this = self.eval_context_mut();
530
531        let mutex = mutex_get_data(this, mutex_op)?.clone();
532
533        if let Some(_old_locked_count) = this.mutex_unlock(&mutex.mutex_ref)? {
534            // The mutex was locked by the current thread.
535            interp_ok(Scalar::from_i32(0))
536        } else {
537            // The mutex was locked by another thread or not locked at all. See
538            // the “Unlock When Not Owner” column in
539            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_unlock.html.
540            match mutex.kind {
541                MutexKind::Default =>
542                    throw_ub_format!(
543                        "unlocked a default mutex that was not locked by the current thread"
544                    ),
545                MutexKind::Normal =>
546                    throw_ub_format!(
547                        "unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked by the current thread"
548                    ),
549                MutexKind::ErrorCheck | MutexKind::Recursive =>
550                    interp_ok(Scalar::from_i32(this.eval_libc_i32("EPERM"))),
551            }
552        }
553    }
554
555    fn pthread_mutex_destroy(&mut self, mutex_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
556        let this = self.eval_context_mut();
557
558        // Reading the field also has the side-effect that we detect double-`destroy`
559        // since we make the field uninit below.
560        let mutex = mutex_get_data(this, mutex_op)?.clone();
561
562        if mutex.mutex_ref.owner().is_some() {
563            throw_ub_format!("destroyed a locked mutex");
564        }
565
566        // This write also deletes the interpreter state for this mutex.
567        // This might lead to false positives, see comment in pthread_mutexattr_destroy
568        let mutex_place = this.deref_pointer(mutex_op)?;
569        this.write_uninit(&mutex_place)?;
570
571        interp_ok(())
572    }
573
574    fn pthread_rwlock_rdlock(
575        &mut self,
576        rwlock_op: &OpTy<'tcx>,
577        dest: &MPlaceTy<'tcx>,
578    ) -> InterpResult<'tcx> {
579        let this = self.eval_context_mut();
580
581        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
582
583        if rwlock.rwlock_ref.is_write_locked() {
584            this.rwlock_enqueue_and_block_reader(
585                rwlock.rwlock_ref,
586                Scalar::from_i32(0),
587                dest.clone(),
588            );
589        } else {
590            this.rwlock_reader_lock(&rwlock.rwlock_ref)?;
591            this.write_int(0, dest)?;
592        }
593
594        interp_ok(())
595    }
596
597    fn pthread_rwlock_tryrdlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
598        let this = self.eval_context_mut();
599
600        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
601
602        if rwlock.rwlock_ref.is_write_locked() {
603            interp_ok(Scalar::from_i32(this.eval_libc_i32("EBUSY")))
604        } else {
605            this.rwlock_reader_lock(&rwlock.rwlock_ref)?;
606            interp_ok(Scalar::from_i32(0))
607        }
608    }
609
610    fn pthread_rwlock_wrlock(
611        &mut self,
612        rwlock_op: &OpTy<'tcx>,
613        dest: &MPlaceTy<'tcx>,
614    ) -> InterpResult<'tcx> {
615        let this = self.eval_context_mut();
616
617        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
618
619        if rwlock.rwlock_ref.is_locked() {
620            // Note: this will deadlock if the lock is already locked by this
621            // thread in any way.
622            //
623            // Relevant documentation:
624            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_rwlock_wrlock.html
625            // An in-depth discussion on this topic:
626            // https://github.com/rust-lang/rust/issues/53127
627            //
628            // FIXME: Detect and report the deadlock proactively. (We currently
629            // report the deadlock only when no thread can continue execution,
630            // but we could detect that this lock is already locked and report
631            // an error.)
632            this.rwlock_enqueue_and_block_writer(
633                rwlock.rwlock_ref,
634                Scalar::from_i32(0),
635                dest.clone(),
636            );
637        } else {
638            this.rwlock_writer_lock(&rwlock.rwlock_ref)?;
639            this.write_int(0, dest)?;
640        }
641
642        interp_ok(())
643    }
644
645    fn pthread_rwlock_trywrlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
646        let this = self.eval_context_mut();
647
648        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
649
650        if rwlock.rwlock_ref.is_locked() {
651            interp_ok(Scalar::from_i32(this.eval_libc_i32("EBUSY")))
652        } else {
653            this.rwlock_writer_lock(&rwlock.rwlock_ref)?;
654            interp_ok(Scalar::from_i32(0))
655        }
656    }
657
658    fn pthread_rwlock_unlock(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
659        let this = self.eval_context_mut();
660
661        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
662
663        if this.rwlock_reader_unlock(&rwlock.rwlock_ref)?
664            || this.rwlock_writer_unlock(&rwlock.rwlock_ref)?
665        {
666            interp_ok(())
667        } else {
668            throw_ub_format!("unlocked an rwlock that was not locked by the active thread");
669        }
670    }
671
672    fn pthread_rwlock_destroy(&mut self, rwlock_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
673        let this = self.eval_context_mut();
674
675        // Reading the field also has the side-effect that we detect double-`destroy`
676        // since we make the field uninit below.
677        let rwlock = rwlock_get_data(this, rwlock_op)?.clone();
678
679        if rwlock.rwlock_ref.is_locked() {
680            throw_ub_format!("destroyed a locked rwlock");
681        }
682
683        // This write also deletes the interpreter state for this rwlock.
684        // This might lead to false positives, see comment in pthread_mutexattr_destroy
685        let rwlock_place = this.deref_pointer(rwlock_op)?;
686        this.write_uninit(&rwlock_place)?;
687
688        interp_ok(())
689    }
690
691    fn pthread_condattr_init(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
692        let this = self.eval_context_mut();
693        condattr_create(this, attr_op)
694    }
695
696    fn pthread_condattr_setclock(
697        &mut self,
698        attr_op: &OpTy<'tcx>,
699        clock_id_op: &OpTy<'tcx>,
700    ) -> InterpResult<'tcx, Scalar> {
701        let this = self.eval_context_mut();
702
703        let clock_id = this.read_scalar(clock_id_op)?.to_i32()?;
704        let Some(clock) = this.parse_clockid(clock_id) else {
705            let einval = this.eval_libc_i32("EINVAL");
706            return interp_ok(Scalar::from_i32(einval));
707        };
708
709        let condattr = condattr_get_data(this, attr_op)?;
710        condattr.clock = clock;
711
712        interp_ok(Scalar::from_i32(0))
713    }
714
715    fn pthread_condattr_getclock(
716        &mut self,
717        attr_op: &OpTy<'tcx>,
718        clk_id_op: &OpTy<'tcx>,
719    ) -> InterpResult<'tcx, ()> {
720        let this = self.eval_context_mut();
721
722        let clock_id_dest = this.deref_pointer(clk_id_op)?;
723        let condattr = condattr_get_data(this, attr_op)?;
724
725        let clock_id = match condattr.clock {
726            TimeoutClock::Monotonic => this.eval_libc_i32("CLOCK_MONOTONIC"),
727            TimeoutClock::RealTime => this.eval_libc_i32("CLOCK_REALTIME"),
728        };
729        this.write_int(clock_id, &clock_id_dest)?;
730
731        interp_ok(())
732    }
733
734    fn pthread_condattr_destroy(&mut self, attr_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
735        let this = self.eval_context_mut();
736        condattr_destroy(this, attr_op)
737    }
738
739    fn pthread_cond_init(
740        &mut self,
741        cond_op: &OpTy<'tcx>,
742        attr_op: &OpTy<'tcx>,
743    ) -> InterpResult<'tcx, ()> {
744        let this = self.eval_context_mut();
745
746        let attr = this.read_pointer(attr_op)?;
747        // Default clock if `attr` is null, and on macOS where there is no clock attribute.
748        let clock = if this.ptr_is_null(attr)? {
749            TimeoutClock::RealTime
750        } else {
751            let condattr = condattr_get_data(this, attr_op)?;
752            condattr.clock
753        };
754
755        cond_create(this, cond_op, clock)?;
756
757        interp_ok(())
758    }
759
760    fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
761        let this = self.eval_context_mut();
762        let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
763        this.condvar_signal(&condvar)?;
764        interp_ok(())
765    }
766
767    fn pthread_cond_broadcast(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
768        let this = self.eval_context_mut();
769        let condvar = cond_get_data(this, cond_op)?.condvar_ref.clone();
770        while this.condvar_signal(&condvar)? {}
771        interp_ok(())
772    }
773
774    fn pthread_cond_wait(
775        &mut self,
776        cond_op: &OpTy<'tcx>,
777        mutex_op: &OpTy<'tcx>,
778        dest: &MPlaceTy<'tcx>,
779    ) -> InterpResult<'tcx> {
780        let this = self.eval_context_mut();
781
782        let data = cond_get_data(this, cond_op)?.clone();
783        let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
784
785        this.condvar_wait(
786            data.condvar_ref,
787            mutex_ref,
788            None, // no timeout
789            Scalar::from_i32(0),
790            Scalar::from_i32(0), // retval_timeout -- unused
791            dest.clone(),
792        )?;
793
794        interp_ok(())
795    }
796
797    fn pthread_cond_timedwait(
798        &mut self,
799        cond_op: &OpTy<'tcx>,
800        mutex_op: &OpTy<'tcx>,
801        timeout_op: &OpTy<'tcx>,
802        dest: &MPlaceTy<'tcx>,
803        macos_relative_np: bool,
804    ) -> InterpResult<'tcx> {
805        let this = self.eval_context_mut();
806
807        let data = cond_get_data(this, cond_op)?.clone();
808        let mutex_ref = mutex_get_data(this, mutex_op)?.mutex_ref.clone();
809
810        // Extract the timeout.
811        let Some(duration) = this.read_timespec(&this.deref_pointer(timeout_op)?)? else {
812            let einval = this.eval_libc("EINVAL");
813            this.write_scalar(einval, dest)?;
814            return interp_ok(());
815        };
816
817        let (clock, style) = if macos_relative_np {
818            // `pthread_cond_timedwait_relative_np` always measures time against the
819            // monotonic clock, regardless of the condvar clock.
820            (TimeoutClock::Monotonic, TimeoutStyle::Relative)
821        } else {
822            if data.clock == TimeoutClock::RealTime {
823                this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
824            }
825
826            (data.clock, TimeoutStyle::Absolute)
827        };
828
829        this.condvar_wait(
830            data.condvar_ref,
831            mutex_ref,
832            Some(this.machine.timeout(clock, style, duration)),
833            Scalar::from_i32(0),
834            this.eval_libc("ETIMEDOUT"), // retval_timeout
835            dest.clone(),
836        )?;
837
838        interp_ok(())
839    }
840
841    fn pthread_cond_destroy(&mut self, cond_op: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
842        let this = self.eval_context_mut();
843
844        // Reading the field also has the side-effect that we detect double-`destroy`
845        // since we make the field uninit below.
846        let condvar = &cond_get_data(this, cond_op)?.condvar_ref;
847        if !condvar.queue_is_empty() {
848            throw_ub_format!("destroying an awaited conditional variable");
849        }
850
851        // This write also deletes the interpreter state for this condvar.
852        // This might lead to false positives, see comment in pthread_mutexattr_destroy
853        let cond_place = this.deref_pointer(cond_op)?;
854        this.write_uninit(&cond_place)?;
855
856        interp_ok(())
857    }
858
859    fn pthread_sem_init(
860        &mut self,
861        sem_op: &OpTy<'tcx>,
862        pshared_op: &OpTy<'tcx>,
863        value_op: &OpTy<'tcx>,
864        dest: &MPlaceTy<'tcx>,
865    ) -> InterpResult<'tcx> {
866        let this = self.eval_context_mut();
867
868        let pshared = this.read_scalar(pshared_op)?.to_i32()?;
869        let value = this.read_scalar(value_op)?.to_u32()?;
870
871        // We can ignore `pshared` since memory cannot actually be shared with other processes.
872        let _ignore = pshared;
873
874        sem_create(this, sem_op, value)?;
875
876        this.write_int(0, dest)?; // we always succeed
877        interp_ok(())
878    }
879
880    fn pthread_sem_wait(
881        &mut self,
882        sem_op: &OpTy<'tcx>,
883        dest: &MPlaceTy<'tcx>,
884    ) -> InterpResult<'tcx> {
885        let this = self.eval_context_mut();
886
887        let data = sem_get_data(this, sem_op)?.clone();
888        let dest = dest.clone();
889        this.semaphore_decrement(
890            data.sem_ref,
891            /* deadline */ None,
892            callback!(
893                @capture<'tcx> {
894                    dest: MPlaceTy<'tcx>,
895                }
896                |ecx, unblock: UnblockKind| {
897                    assert_eq!(unblock, UnblockKind::Ready);
898                    ecx.write_int(0,&dest)?;
899                    interp_ok(())
900                }
901            ),
902        )?;
903
904        interp_ok(())
905    }
906
907    fn pthread_sem_timedwait(
908        &mut self,
909        sem_op: &OpTy<'tcx>,
910        abs_timeout_op: &OpTy<'tcx>,
911        dest: &MPlaceTy<'tcx>,
912    ) -> InterpResult<'tcx> {
913        let this = self.eval_context_mut();
914        this.check_no_isolation("`pthread_sem_timedwait`")?;
915
916        let abs_timeout = this.deref_pointer(abs_timeout_op)?;
917
918        let Some(duration) = this.read_timespec(&abs_timeout)? else {
919            return this.set_errno_and_return_neg1(LibcError("EINVAL"), dest);
920        };
921        let deadline =
922            this.machine.timeout(TimeoutClock::RealTime, TimeoutStyle::Absolute, duration);
923        let data = sem_get_data(this, sem_op)?.clone();
924        let dest = dest.clone();
925        this.semaphore_decrement(
926            data.sem_ref,
927            Some(deadline),
928            callback!(
929                @capture<'tcx> {
930                    dest: MPlaceTy<'tcx>,
931                }
932                |ecx, unblock: UnblockKind| {
933                    match unblock {
934                        UnblockKind::Ready =>
935                            ecx.write_int(0,&dest),
936                        UnblockKind::TimedOut =>
937                            ecx.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest),
938                    }
939                }
940            ),
941        )?;
942
943        interp_ok(())
944    }
945
946    fn pthread_sem_post(
947        &mut self,
948        sem_op: &OpTy<'tcx>,
949        dest: &MPlaceTy<'tcx>,
950    ) -> InterpResult<'tcx> {
951        let this = self.eval_context_mut();
952
953        let data = sem_get_data(this, sem_op)?.clone();
954        this.semaphore_increment(&data.sem_ref)?;
955
956        this.write_int(0, dest)?; // we always succeed
957        interp_ok(())
958    }
959
960    fn pthread_sem_destroy(
961        &mut self,
962        sem_op: &OpTy<'tcx>,
963        dest: &MPlaceTy<'tcx>,
964    ) -> InterpResult<'tcx> {
965        let this = self.eval_context_mut();
966
967        let data = sem_get_data(this, sem_op)?.clone();
968        if !data.sem_ref.queue_is_empty() {
969            throw_ub_format!("destroying an awaited semaphore");
970        }
971
972        // This write also deletes the interpreter state for this semaphore.
973        // This might lead to false positives, see comment in pthread_mutexattr_destroy
974        let sem_place = this.deref_pointer(sem_op)?;
975        this.write_uninit(&sem_place)?;
976
977        this.write_int(0, dest)?; // we always succeed
978        interp_ok(())
979    }
980}