]> git.lizzy.rs Git - rust.git/blob - src/shims/sync.rs
Use checked addition/subtraction on lock counts
[rust.git] / src / shims / sync.rs
1 use rustc_middle::ty::{TyKind, TypeAndMut};
2 use rustc_target::abi::{LayoutOf, Size};
3
4 use crate::stacked_borrows::Tag;
5 use crate::*;
6
7 impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
8 pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
9     fn pthread_mutexattr_init(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
10         let this = self.eval_context_mut();
11
12         let attr = this.read_scalar(attr_op)?.not_undef()?;
13         if this.is_null(attr)? {
14             return this.eval_libc_i32("EINVAL");
15         }
16
17         let default_kind = this.eval_libc("PTHREAD_MUTEX_DEFAULT")?;
18         mutexattr_set_kind(this, attr_op, default_kind)?;
19
20         Ok(0)
21     }
22
23     fn pthread_mutexattr_settype(
24         &mut self,
25         attr_op: OpTy<'tcx, Tag>,
26         kind_op: OpTy<'tcx, Tag>,
27     ) -> InterpResult<'tcx, i32> {
28         let this = self.eval_context_mut();
29
30         let attr = this.read_scalar(attr_op)?.not_undef()?;
31         if this.is_null(attr)? {
32             return this.eval_libc_i32("EINVAL");
33         }
34
35         let kind = this.read_scalar(kind_op)?.not_undef()?;
36         if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")?
37             || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
38             || kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")?
39         {
40             mutexattr_set_kind(this, attr_op, kind)?;
41         } else {
42             let einval = this.eval_libc_i32("EINVAL")?;
43             return Ok(einval);
44         }
45
46         Ok(0)
47     }
48
49     fn pthread_mutexattr_destroy(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
50         let this = self.eval_context_mut();
51
52         let attr = this.read_scalar(attr_op)?.not_undef()?;
53         if this.is_null(attr)? {
54             return this.eval_libc_i32("EINVAL");
55         }
56
57         mutexattr_set_kind(this, attr_op, ScalarMaybeUndef::Undef)?;
58
59         Ok(0)
60     }
61
62     fn pthread_mutex_init(
63         &mut self,
64         mutex_op: OpTy<'tcx, Tag>,
65         attr_op: OpTy<'tcx, Tag>,
66     ) -> InterpResult<'tcx, i32> {
67         let this = self.eval_context_mut();
68
69         let mutex = this.read_scalar(mutex_op)?.not_undef()?;
70         if this.is_null(mutex)? {
71             return this.eval_libc_i32("EINVAL");
72         }
73
74         let attr = this.read_scalar(attr_op)?.not_undef()?;
75         let kind = if this.is_null(attr)? {
76             this.eval_libc("PTHREAD_MUTEX_DEFAULT")?
77         } else {
78             mutexattr_get_kind(this, attr_op)?.not_undef()?
79         };
80
81         mutex_set_locked_count(this, mutex_op, Scalar::from_u32(0))?;
82         mutex_set_kind(this, mutex_op, kind)?;
83
84         Ok(0)
85     }
86
87     fn pthread_mutex_lock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
88         let this = self.eval_context_mut();
89
90         let mutex = this.read_scalar(mutex_op)?.not_undef()?;
91         if this.is_null(mutex)? {
92             return this.eval_libc_i32("EINVAL");
93         }
94
95         let kind = mutex_get_kind(this, mutex_op)?.not_undef()?;
96         let locked_count = mutex_get_locked_count(this, mutex_op)?.to_u32()?;
97
98         if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? {
99             if locked_count == 0 {
100                 mutex_set_locked_count(this, mutex_op, Scalar::from_u32(1))?;
101                 Ok(0)
102             } else {
103                 throw_unsup_format!("Deadlock due to locking a PTHREAD_MUTEX_NORMAL mutex twice");
104             }
105         } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? {
106             if locked_count == 0 {
107                 mutex_set_locked_count(this, mutex_op, Scalar::from_u32(1))?;
108                 Ok(0)
109             } else {
110                 this.eval_libc_i32("EDEADLK")
111             }
112         } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
113             match locked_count.checked_add(1) {
114                 Some(new_count) => {
115                     mutex_set_locked_count(this, mutex_op, Scalar::from_u32(new_count))?;
116                     Ok(0)
117                 }
118                 None => this.eval_libc_i32("EAGAIN"),
119             }
120         } else {
121             this.eval_libc_i32("EINVAL")
122         }
123     }
124
125     fn pthread_mutex_trylock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
126         let this = self.eval_context_mut();
127
128         let mutex = this.read_scalar(mutex_op)?.not_undef()?;
129         if this.is_null(mutex)? {
130             return this.eval_libc_i32("EINVAL");
131         }
132
133         let kind = mutex_get_kind(this, mutex_op)?.not_undef()?;
134         let locked_count = mutex_get_locked_count(this, mutex_op)?.to_u32()?;
135
136         if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")?
137             || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")?
138         {
139             if locked_count == 0 {
140                 mutex_set_locked_count(this, mutex_op, Scalar::from_u32(1))?;
141                 Ok(0)
142             } else {
143                 this.eval_libc_i32("EBUSY")
144             }
145         } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
146             match locked_count.checked_add(1) {
147                 Some(new_count) => {
148                     mutex_set_locked_count(this, mutex_op, Scalar::from_u32(new_count))?;
149                     Ok(0)
150                 }
151                 None => this.eval_libc_i32("EAGAIN"),
152             }
153         } else {
154             this.eval_libc_i32("EINVAL")
155         }
156     }
157
158     fn pthread_mutex_unlock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
159         let this = self.eval_context_mut();
160
161         let mutex = this.read_scalar(mutex_op)?.not_undef()?;
162         if this.is_null(mutex)? {
163             return this.eval_libc_i32("EINVAL");
164         }
165
166         let kind = mutex_get_kind(this, mutex_op)?.not_undef()?;
167         let locked_count = mutex_get_locked_count(this, mutex_op)?.to_u32()?;
168
169         if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? {
170             if locked_count == 1 {
171                 mutex_set_locked_count(this, mutex_op, Scalar::from_u32(0))?;
172                 Ok(0)
173             } else {
174                 throw_ub_format!(
175                     "Attempted to unlock a PTHREAD_MUTEX_NORMAL mutex that was not locked"
176                 );
177             }
178         } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? {
179             if locked_count == 1 {
180                 mutex_set_locked_count(this, mutex_op, Scalar::from_u32(0))?;
181                 Ok(0)
182             } else {
183                 this.eval_libc_i32("EPERM")
184             }
185         } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? {
186             match locked_count.checked_sub(1) {
187                 Some(new_count) => {
188                     mutex_set_locked_count(this, mutex_op, Scalar::from_u32(new_count))?;
189                     Ok(0)
190                 }
191                 None => {
192                     // locked_count was already zero
193                     this.eval_libc_i32("EPERM")
194                 }
195             }
196         } else {
197             this.eval_libc_i32("EINVAL")
198         }
199     }
200
201     fn pthread_mutex_destroy(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
202         let this = self.eval_context_mut();
203
204         let mutex = this.read_scalar(mutex_op)?.not_undef()?;
205         if this.is_null(mutex)? {
206             return this.eval_libc_i32("EINVAL");
207         }
208
209         if mutex_get_locked_count(this, mutex_op)?.to_u32()? != 0 {
210             return this.eval_libc_i32("EBUSY");
211         }
212
213         mutex_set_kind(this, mutex_op, ScalarMaybeUndef::Undef)?;
214         mutex_set_locked_count(this, mutex_op, ScalarMaybeUndef::Undef)?;
215
216         Ok(0)
217     }
218
219     fn pthread_rwlock_rdlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
220         let this = self.eval_context_mut();
221
222         let rwlock = this.read_scalar(rwlock_op)?.not_undef()?;
223         if this.is_null(rwlock)? {
224             return this.eval_libc_i32("EINVAL");
225         }
226
227         let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?;
228         let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?;
229         if writers != 0 {
230             throw_unsup_format!(
231                 "Deadlock due to read-locking a pthreads read-write lock while it is already write-locked"
232             );
233         } else {
234             match readers.checked_add(1) {
235                 Some(new_readers) => {
236                     rwlock_set_readers(this, rwlock_op, Scalar::from_u32(new_readers))?;
237                     Ok(0)
238                 }
239                 None => this.eval_libc_i32("EAGAIN"),
240             }
241         }
242     }
243
244     fn pthread_rwlock_tryrdlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
245         let this = self.eval_context_mut();
246
247         let rwlock = this.read_scalar(rwlock_op)?.not_undef()?;
248         if this.is_null(rwlock)? {
249             return this.eval_libc_i32("EINVAL");
250         }
251
252         let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?;
253         let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?;
254         if writers != 0 {
255             this.eval_libc_i32("EBUSY")
256         } else {
257             match readers.checked_add(1) {
258                 Some(new_readers) => {
259                     rwlock_set_readers(this, rwlock_op, Scalar::from_u32(new_readers))?;
260                     Ok(0)
261                 }
262                 None => this.eval_libc_i32("EAGAIN"),
263             }
264         }
265     }
266
267     fn pthread_rwlock_wrlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
268         let this = self.eval_context_mut();
269
270         let rwlock = this.read_scalar(rwlock_op)?.not_undef()?;
271         if this.is_null(rwlock)? {
272             return this.eval_libc_i32("EINVAL");
273         }
274
275         let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?;
276         let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?;
277         if readers != 0 {
278             throw_unsup_format!(
279                 "Deadlock due to write-locking a pthreads read-write lock while it is already read-locked"
280             );
281         } else if writers != 0 {
282             throw_unsup_format!(
283                 "Deadlock due to write-locking a pthreads read-write lock while it is already write-locked"
284             );
285         } else {
286             rwlock_set_writers(this, rwlock_op, Scalar::from_u32(1))?;
287             Ok(0)
288         }
289     }
290
291     fn pthread_rwlock_trywrlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
292         let this = self.eval_context_mut();
293
294         let rwlock = this.read_scalar(rwlock_op)?.not_undef()?;
295         if this.is_null(rwlock)? {
296             return this.eval_libc_i32("EINVAL");
297         }
298
299         let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?;
300         let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?;
301         if readers != 0 || writers != 0 {
302             this.eval_libc_i32("EBUSY")
303         } else {
304             rwlock_set_writers(this, rwlock_op, Scalar::from_u32(1))?;
305             Ok(0)
306         }
307     }
308
309     fn pthread_rwlock_unlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
310         let this = self.eval_context_mut();
311
312         let rwlock = this.read_scalar(rwlock_op)?.not_undef()?;
313         if this.is_null(rwlock)? {
314             return this.eval_libc_i32("EINVAL");
315         }
316
317         let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?;
318         let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?;
319         if let Some(new_readers) = readers.checked_sub(1) {
320             rwlock_set_readers(this, rwlock_op, Scalar::from_u32(new_readers))?;
321             Ok(0)
322         } else if writers != 0 {
323             rwlock_set_writers(this, rwlock_op, Scalar::from_u32(0))?;
324             Ok(0)
325         } else {
326             this.eval_libc_i32("EPERM")
327         }
328     }
329
330     fn pthread_rwlock_destroy(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
331         let this = self.eval_context_mut();
332
333         let rwlock = this.read_scalar(rwlock_op)?.not_undef()?;
334         if this.is_null(rwlock)? {
335             return this.eval_libc_i32("EINVAL");
336         }
337
338         if rwlock_get_readers(this, rwlock_op)?.to_u32()? != 0 {
339             return this.eval_libc_i32("EBUSY");
340         }
341         if rwlock_get_writers(this, rwlock_op)?.to_u32()? != 0 {
342             return this.eval_libc_i32("EBUSY");
343         }
344
345         rwlock_set_readers(this, rwlock_op, ScalarMaybeUndef::Undef)?;
346         rwlock_set_writers(this, rwlock_op, ScalarMaybeUndef::Undef)?;
347
348         Ok(0)
349     }
350 }
351
352 fn assert_ptr_target_min_size<'mir, 'tcx: 'mir>(
353     ecx: &MiriEvalContext<'mir, 'tcx>,
354     operand: OpTy<'tcx, Tag>,
355     min_size: u64,
356 ) -> InterpResult<'tcx, ()> {
357     let target_ty = match operand.layout.ty.kind {
358         TyKind::RawPtr(TypeAndMut { ty, mutbl: _ }) => ty,
359         _ => panic!("Argument to pthread function was not a raw pointer"),
360     };
361     let target_layout = ecx.layout_of(target_ty)?;
362     assert!(target_layout.size.bytes() >= min_size);
363     Ok(())
364 }
365
366 // pthread_mutexattr_t is either 4 or 8 bytes, depending on the platform
367 // memory layout: store an i32 in the first four bytes equal to the
368 // corresponding libc mutex kind constant (i.e. PTHREAD_MUTEX_NORMAL)
369
370 fn mutexattr_get_kind<'mir, 'tcx: 'mir>(
371     ecx: &MiriEvalContext<'mir, 'tcx>,
372     attr_op: OpTy<'tcx, Tag>,
373 ) -> InterpResult<'tcx, ScalarMaybeUndef<Tag>> {
374     // Ensure that the following read at an offset to the attr pointer is within bounds
375     assert_ptr_target_min_size(ecx, attr_op, 4)?;
376     let attr_place = ecx.deref_operand(attr_op)?;
377     let i32_layout = ecx.layout_of(ecx.tcx.types.i32)?;
378     let kind_place = attr_place.offset(Size::ZERO, MemPlaceMeta::None, i32_layout, ecx)?;
379     ecx.read_scalar(kind_place.into())
380 }
381
382 fn mutexattr_set_kind<'mir, 'tcx: 'mir>(
383     ecx: &mut MiriEvalContext<'mir, 'tcx>,
384     attr_op: OpTy<'tcx, Tag>,
385     kind: impl Into<ScalarMaybeUndef<Tag>>,
386 ) -> InterpResult<'tcx, ()> {
387     // Ensure that the following write at an offset to the attr pointer is within bounds
388     assert_ptr_target_min_size(ecx, attr_op, 4)?;
389     let attr_place = ecx.deref_operand(attr_op)?;
390     let i32_layout = ecx.layout_of(ecx.tcx.types.i32)?;
391     let kind_place = attr_place.offset(Size::ZERO, MemPlaceMeta::None, i32_layout, ecx)?;
392     ecx.write_scalar(kind.into(), kind_place.into())
393 }
394
395 // pthread_mutex_t is between 24 and 48 bytes, depending on the platform
396 // memory layout:
397 // bytes 0-3: reserved for signature on macOS
398 // bytes 4-7: count of how many times this mutex has been locked, as a u32
399 // bytes 12-15: mutex kind, as an i32
400 // (the kind should be at this offset for compatibility with the static
401 // initializer macro)
402
403 fn mutex_get_locked_count<'mir, 'tcx: 'mir>(
404     ecx: &MiriEvalContext<'mir, 'tcx>,
405     mutex_op: OpTy<'tcx, Tag>,
406 ) -> InterpResult<'tcx, ScalarMaybeUndef<Tag>> {
407     // Ensure that the following read at an offset to the mutex pointer is within bounds
408     assert_ptr_target_min_size(ecx, mutex_op, 16)?;
409     let mutex_place = ecx.deref_operand(mutex_op)?;
410     let u32_layout = ecx.layout_of(ecx.tcx.types.u32)?;
411     let locked_count_place =
412         mutex_place.offset(Size::from_bytes(4), MemPlaceMeta::None, u32_layout, ecx)?;
413     ecx.read_scalar(locked_count_place.into())
414 }
415
416 fn mutex_set_locked_count<'mir, 'tcx: 'mir>(
417     ecx: &mut MiriEvalContext<'mir, 'tcx>,
418     mutex_op: OpTy<'tcx, Tag>,
419     locked_count: impl Into<ScalarMaybeUndef<Tag>>,
420 ) -> InterpResult<'tcx, ()> {
421     // Ensure that the following write at an offset to the mutex pointer is within bounds
422     assert_ptr_target_min_size(ecx, mutex_op, 16)?;
423     let mutex_place = ecx.deref_operand(mutex_op)?;
424     let u32_layout = ecx.layout_of(ecx.tcx.types.u32)?;
425     let locked_count_place =
426         mutex_place.offset(Size::from_bytes(4), MemPlaceMeta::None, u32_layout, ecx)?;
427     ecx.write_scalar(locked_count.into(), locked_count_place.into())
428 }
429
430 fn mutex_get_kind<'mir, 'tcx: 'mir>(
431     ecx: &MiriEvalContext<'mir, 'tcx>,
432     mutex_op: OpTy<'tcx, Tag>,
433 ) -> InterpResult<'tcx, ScalarMaybeUndef<Tag>> {
434     // Ensure that the following read at an offset to the mutex pointer is within bounds
435     assert_ptr_target_min_size(ecx, mutex_op, 16)?;
436     let mutex_place = ecx.deref_operand(mutex_op)?;
437     let i32_layout = ecx.layout_of(ecx.tcx.types.i32)?;
438     let kind_place =
439         mutex_place.offset(Size::from_bytes(12), MemPlaceMeta::None, i32_layout, ecx)?;
440     ecx.read_scalar(kind_place.into())
441 }
442
443 fn mutex_set_kind<'mir, 'tcx: 'mir>(
444     ecx: &mut MiriEvalContext<'mir, 'tcx>,
445     mutex_op: OpTy<'tcx, Tag>,
446     kind: impl Into<ScalarMaybeUndef<Tag>>,
447 ) -> InterpResult<'tcx, ()> {
448     // Ensure that the following write at an offset to the mutex pointer is within bounds
449     assert_ptr_target_min_size(ecx, mutex_op, 16)?;
450     let mutex_place = ecx.deref_operand(mutex_op)?;
451     let i32_layout = ecx.layout_of(ecx.tcx.types.i32)?;
452     let kind_place =
453         mutex_place.offset(Size::from_bytes(12), MemPlaceMeta::None, i32_layout, ecx)?;
454     ecx.write_scalar(kind.into(), kind_place.into())
455 }
456
457 // pthread_rwlock_t is between 32 and 56 bytes, depending on the platform
458 // memory layout:
459 // bytes 0-3: reserved for signature on macOS
460 // bytes 4-7: reader count, as a u32
461 // bytes 8-11: writer count, as a u32
462
463 fn rwlock_get_readers<'mir, 'tcx: 'mir>(
464     ecx: &MiriEvalContext<'mir, 'tcx>,
465     rwlock_op: OpTy<'tcx, Tag>,
466 ) -> InterpResult<'tcx, ScalarMaybeUndef<Tag>> {
467     // Ensure that the following read at an offset to the rwlock pointer is within bounds
468     assert_ptr_target_min_size(ecx, rwlock_op, 12)?;
469     let rwlock_place = ecx.deref_operand(rwlock_op)?;
470     let u32_layout = ecx.layout_of(ecx.tcx.types.u32)?;
471     let readers_place =
472         rwlock_place.offset(Size::from_bytes(4), MemPlaceMeta::None, u32_layout, ecx)?;
473     ecx.read_scalar(readers_place.into())
474 }
475
476 fn rwlock_set_readers<'mir, 'tcx: 'mir>(
477     ecx: &mut MiriEvalContext<'mir, 'tcx>,
478     rwlock_op: OpTy<'tcx, Tag>,
479     readers: impl Into<ScalarMaybeUndef<Tag>>,
480 ) -> InterpResult<'tcx, ()> {
481     // Ensure that the following write at an offset to the rwlock pointer is within bounds
482     assert_ptr_target_min_size(ecx, rwlock_op, 12)?;
483     let rwlock_place = ecx.deref_operand(rwlock_op)?;
484     let u32_layout = ecx.layout_of(ecx.tcx.types.u32)?;
485     let readers_place =
486         rwlock_place.offset(Size::from_bytes(4), MemPlaceMeta::None, u32_layout, ecx)?;
487     ecx.write_scalar(readers.into(), readers_place.into())
488 }
489
490 fn rwlock_get_writers<'mir, 'tcx: 'mir>(
491     ecx: &MiriEvalContext<'mir, 'tcx>,
492     rwlock_op: OpTy<'tcx, Tag>,
493 ) -> InterpResult<'tcx, ScalarMaybeUndef<Tag>> {
494     // Ensure that the following read at an offset to the rwlock pointer is within bounds
495     assert_ptr_target_min_size(ecx, rwlock_op, 12)?;
496     let rwlock_place = ecx.deref_operand(rwlock_op)?;
497     let u32_layout = ecx.layout_of(ecx.tcx.types.u32)?;
498     let writers_place =
499         rwlock_place.offset(Size::from_bytes(8), MemPlaceMeta::None, u32_layout, ecx)?;
500     ecx.read_scalar(writers_place.into())
501 }
502
503 fn rwlock_set_writers<'mir, 'tcx: 'mir>(
504     ecx: &mut MiriEvalContext<'mir, 'tcx>,
505     rwlock_op: OpTy<'tcx, Tag>,
506     writers: impl Into<ScalarMaybeUndef<Tag>>,
507 ) -> InterpResult<'tcx, ()> {
508     // Ensure that the following write at an offset to the rwlock pointer is within bounds
509     assert_ptr_target_min_size(ecx, rwlock_op, 12)?;
510     let rwlock_place = ecx.deref_operand(rwlock_op)?;
511     let u32_layout = ecx.layout_of(ecx.tcx.types.u32)?;
512     let writers_place =
513         rwlock_place.offset(Size::from_bytes(8), MemPlaceMeta::None, u32_layout, ecx)?;
514     ecx.write_scalar(writers.into(), writers_place.into())
515 }