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