diff --git a/src/diagnostics.rs b/src/diagnostics.rs index 9ff4340211..2b53efe864 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -12,7 +12,8 @@ pub enum TerminationInfo { Exit(i64), Abort(Option), UnsupportedInIsolation(String), - ExperimentalUb { msg: String, url: String } + ExperimentalUb { msg: String, url: String }, + Deadlock, } impl fmt::Debug for TerminationInfo { @@ -29,6 +30,8 @@ impl fmt::Debug for TerminationInfo { write!(f, "{}", msg), ExperimentalUb { msg, .. } => write!(f, "{}", msg), + Deadlock => + write!(f, "the evaluated program deadlocked"), } } } @@ -60,6 +63,7 @@ pub fn report_error<'tcx, 'mir>( "unsupported operation", ExperimentalUb { .. } => "Undefined Behavior", + Deadlock => "deadlock", }; let helps = match info { UnsupportedInIsolation(_) => diff --git a/src/eval.rs b/src/eval.rs index 46e66bc0a8..c3510188e3 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,14 +1,14 @@ //! Main evaluator loop and setting up the initial stack frame. -use std::ffi::OsStr; use std::convert::TryFrom; +use std::ffi::OsStr; use rand::rngs::StdRng; use rand::SeedableRng; -use rustc_target::abi::LayoutOf; -use rustc_middle::ty::{self, TyCtxt}; use rustc_hir::def_id::DefId; +use rustc_middle::ty::{self, layout::LayoutCx, TyCtxt}; +use rustc_target::abi::LayoutOf; use crate::*; @@ -60,10 +60,13 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( main_id: DefId, config: MiriConfig, ) -> InterpResult<'tcx, (InterpCx<'mir, 'tcx, Evaluator<'tcx>>, MPlaceTy<'tcx, Tag>)> { + let tcx_at = tcx.at(rustc_span::source_map::DUMMY_SP); + let param_env = ty::ParamEnv::reveal_all(); + let layout_cx = LayoutCx { tcx, param_env }; let mut ecx = InterpCx::new( - tcx.at(rustc_span::source_map::DUMMY_SP), - ty::ParamEnv::reveal_all(), - Evaluator::new(config.communicate, config.validate), + tcx_at, + param_env, + Evaluator::new(config.communicate, config.validate, layout_cx), MemoryExtra::new( StdRng::seed_from_u64(config.seed.unwrap_or(0)), config.stacked_borrows, diff --git a/src/lib.rs b/src/lib.rs index c04fbfeab9..2f381b4a34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,7 @@ pub use crate::shims::fs::{DirHandler, EvalContextExt as FileEvalContextExt, Fil pub use crate::shims::intrinsics::EvalContextExt as IntrinsicsEvalContextExt; pub use crate::shims::os_str::EvalContextExt as OsStrEvalContextExt; pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as PanicEvalContextExt}; +pub use crate::shims::sync::{EvalContextExt as SyncEvalContextExt}; pub use crate::shims::time::EvalContextExt as TimeEvalContextExt; pub use crate::shims::tls::{EvalContextExt as TlsEvalContextExt, TlsData}; pub use crate::shims::EvalContextExt as ShimsEvalContextExt; diff --git a/src/machine.rs b/src/machine.rs index f794453228..26ff23511f 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -10,11 +10,18 @@ use std::time::Instant; use log::trace; use rand::rngs::StdRng; -use rustc_data_structures::fx::FxHashMap; -use rustc_middle::{mir, ty}; -use rustc_target::abi::{LayoutOf, Size}; use rustc_ast::attr; +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::{ + mir, + ty::{ + self, + layout::{LayoutCx, LayoutError, TyAndLayout}, + TyCtxt, + }, +}; use rustc_span::symbol::{sym, Symbol}; +use rustc_target::abi::{LayoutOf, Size}; use crate::*; @@ -146,6 +153,21 @@ impl MemoryExtra { } } +/// Precomputed layouts of primitive types +pub(crate) struct PrimitiveLayouts<'tcx> { + pub(crate) i32: TyAndLayout<'tcx>, + pub(crate) u32: TyAndLayout<'tcx>, +} + +impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> { + fn new(layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Result> { + Ok(Self { + i32: layout_cx.layout_of(layout_cx.tcx.types.i32)?, + u32: layout_cx.layout_of(layout_cx.tcx.types.u32)?, + }) + } +} + /// The machine itself. pub struct Evaluator<'tcx> { /// Environment variables set by `setenv`. @@ -182,10 +204,21 @@ pub struct Evaluator<'tcx> { /// The "time anchor" for this machine's monotone clock (for `Instant` simulation). pub(crate) time_anchor: Instant, + + /// Precomputed `TyLayout`s for primitive data types that are commonly used inside Miri. + /// FIXME: Search through the rest of the codebase for more layout_of() calls that + /// could be stored here. + pub(crate) layouts: PrimitiveLayouts<'tcx>, } impl<'tcx> Evaluator<'tcx> { - pub(crate) fn new(communicate: bool, validate: bool) -> Self { + pub(crate) fn new( + communicate: bool, + validate: bool, + layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>, + ) -> Self { + let layouts = PrimitiveLayouts::new(layout_cx) + .expect("Couldn't get layouts of primitive types"); Evaluator { // `env_vars` could be initialized properly here if `Memory` were available before // calling this method. @@ -201,6 +234,7 @@ impl<'tcx> Evaluator<'tcx> { dir_handler: Default::default(), panic_payload: None, time_anchor: Instant::now(), + layouts, } } } diff --git a/src/shims/foreign_items/posix.rs b/src/shims/foreign_items/posix.rs index c9fd59c693..3ececb9c20 100644 --- a/src/shims/foreign_items/posix.rs +++ b/src/shims/foreign_items/posix.rs @@ -233,6 +233,64 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.write_null(dest)?; } + // Synchronization primitives + "pthread_mutexattr_init" => { + let result = this.pthread_mutexattr_init(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutexattr_settype" => { + let result = this.pthread_mutexattr_settype(args[0], args[1])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutexattr_destroy" => { + let result = this.pthread_mutexattr_destroy(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_init" => { + let result = this.pthread_mutex_init(args[0], args[1])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_lock" => { + let result = this.pthread_mutex_lock(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_trylock" => { + let result = this.pthread_mutex_trylock(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_unlock" => { + let result = this.pthread_mutex_unlock(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_mutex_destroy" => { + let result = this.pthread_mutex_destroy(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_rdlock" => { + let result = this.pthread_rwlock_rdlock(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_tryrdlock" => { + let result = this.pthread_rwlock_tryrdlock(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_wrlock" => { + let result = this.pthread_rwlock_wrlock(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_trywrlock" => { + let result = this.pthread_rwlock_trywrlock(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_unlock" => { + let result = this.pthread_rwlock_unlock(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "pthread_rwlock_destroy" => { + let result = this.pthread_rwlock_destroy(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + // Better error for attempts to create a thread "pthread_create" => { throw_unsup_format!("Miri does not support threading"); @@ -255,25 +313,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.write_null(dest)?; } - // Incomplete shims that we "stub out" just to get pre-main initialziation code to work. + // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. | "pthread_attr_init" | "pthread_attr_destroy" | "pthread_self" - | "pthread_attr_setstacksize" if this.frame().instance.to_string().starts_with("std::sys::unix::") => { - this.write_null(dest)?; - } - | "pthread_mutexattr_init" - | "pthread_mutexattr_settype" - | "pthread_mutex_init" - | "pthread_mutexattr_destroy" - | "pthread_mutex_lock" - | "pthread_mutex_unlock" - | "pthread_mutex_destroy" - | "pthread_rwlock_rdlock" - | "pthread_rwlock_unlock" - | "pthread_rwlock_wrlock" - | "pthread_rwlock_destroy" + | "pthread_attr_setstacksize" | "pthread_condattr_init" | "pthread_condattr_setclock" | "pthread_cond_init" @@ -282,6 +327,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx => { this.write_null(dest)?; } + | "signal" | "sigaction" | "sigaltstack" diff --git a/src/shims/foreign_items/posix/linux.rs b/src/shims/foreign_items/posix/linux.rs index 286bd5798b..16c6c002b6 100644 --- a/src/shims/foreign_items/posix/linux.rs +++ b/src/shims/foreign_items/posix/linux.rs @@ -113,7 +113,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.write_scalar(Scalar::from_i32(-1), dest)?; } - // Incomplete shims that we "stub out" just to get pre-main initialziation code to work. + // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_getattr_np" if this.frame().instance.to_string().starts_with("std::sys::unix::") => { this.write_null(dest)?; diff --git a/src/shims/foreign_items/posix/macos.rs b/src/shims/foreign_items/posix/macos.rs index 44c45d90c1..9810a77ffd 100644 --- a/src/shims/foreign_items/posix/macos.rs +++ b/src/shims/foreign_items/posix/macos.rs @@ -88,7 +88,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx this.write_scalar(stack_size, dest)?; } - // Incomplete shims that we "stub out" just to get pre-main initialziation code to work. + // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "mmap" if this.frame().instance.to_string().starts_with("std::sys::unix::") => { // This is a horrible hack, but since the guard page mechanism calls mmap and expects a particular return value, we just give it that value. diff --git a/src/shims/foreign_items/windows.rs b/src/shims/foreign_items/windows.rs index cfc94bfd9b..1d17cbcefd 100644 --- a/src/shims/foreign_items/windows.rs +++ b/src/shims/foreign_items/windows.rs @@ -207,7 +207,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx throw_unsup_format!("Miri does not support threading"); } - // Incomplete shims that we "stub out" just to get pre-main initialziation code to work. + // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "GetProcessHeap" if this.frame().instance.to_string().starts_with("std::sys::windows::") => { // Just fake a HANDLE @@ -233,6 +233,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // (Windows locks are reentrant, and we have only 1 thread, // so not doing any futher checks here is at least not incorrect.) } + "TryEnterCriticalSection" if this.frame().instance.to_string().starts_with("std::sys::windows::") + => { + // There is only one thread, so this always succeeds and returns TRUE + this.write_scalar(Scalar::from_i32(1), dest)?; + } _ => throw_unsup_format!("can't call foreign function: {}", link_name), } diff --git a/src/shims/mod.rs b/src/shims/mod.rs index e5db537cff..764e404141 100644 --- a/src/shims/mod.rs +++ b/src/shims/mod.rs @@ -5,6 +5,7 @@ pub mod fs; pub mod intrinsics; pub mod os_str; pub mod panic; +pub mod sync; pub mod time; pub mod tls; diff --git a/src/shims/sync.rs b/src/shims/sync.rs new file mode 100644 index 0000000000..b03dcbfd89 --- /dev/null +++ b/src/shims/sync.rs @@ -0,0 +1,467 @@ +use rustc_middle::ty::{TyKind, TypeAndMut}; +use rustc_target::abi::{LayoutOf, Size}; + +use crate::stacked_borrows::Tag; +use crate::*; + +fn assert_ptr_target_min_size<'mir, 'tcx: 'mir>( + ecx: &MiriEvalContext<'mir, 'tcx>, + operand: OpTy<'tcx, Tag>, + min_size: u64, +) -> InterpResult<'tcx, ()> { + let target_ty = match operand.layout.ty.kind { + TyKind::RawPtr(TypeAndMut { ty, mutbl: _ }) => ty, + _ => panic!("Argument to pthread function was not a raw pointer"), + }; + let target_layout = ecx.layout_of(target_ty)?; + assert!(target_layout.size.bytes() >= min_size); + Ok(()) +} + +// pthread_mutexattr_t is either 4 or 8 bytes, depending on the platform. + +// Our chosen memory layout for emulation (does not have to match the platform layout!): +// store an i32 in the first four bytes equal to the corresponding libc mutex kind constant +// (e.g. PTHREAD_MUTEX_NORMAL). + +fn mutexattr_get_kind<'mir, 'tcx: 'mir>( + ecx: &MiriEvalContext<'mir, 'tcx>, + attr_op: OpTy<'tcx, Tag>, +) -> InterpResult<'tcx, ScalarMaybeUndef> { + // Ensure that the following read at an offset to the attr pointer is within bounds + assert_ptr_target_min_size(ecx, attr_op, 4)?; + let attr_place = ecx.deref_operand(attr_op)?; + let kind_place = + attr_place.offset(Size::ZERO, MemPlaceMeta::None, ecx.machine.layouts.i32, ecx)?; + ecx.read_scalar(kind_place.into()) +} + +fn mutexattr_set_kind<'mir, 'tcx: 'mir>( + ecx: &mut MiriEvalContext<'mir, 'tcx>, + attr_op: OpTy<'tcx, Tag>, + kind: impl Into>, +) -> InterpResult<'tcx, ()> { + // Ensure that the following write at an offset to the attr pointer is within bounds + assert_ptr_target_min_size(ecx, attr_op, 4)?; + let attr_place = ecx.deref_operand(attr_op)?; + let kind_place = + attr_place.offset(Size::ZERO, MemPlaceMeta::None, ecx.machine.layouts.i32, ecx)?; + ecx.write_scalar(kind.into(), kind_place.into()) +} + +// pthread_mutex_t is between 24 and 48 bytes, depending on the platform. + +// Our chosen memory layout for the emulated mutex (does not have to match the platform layout!): +// bytes 0-3: reserved for signature on macOS +// (need to avoid this because it is set by static initializer macros) +// bytes 4-7: count of how many times this mutex has been locked, as a u32 +// bytes 12-15 or 16-19 (depending on platform): mutex kind, as an i32 +// (the kind has to be at its offset for compatibility with static initializer macros) + +fn mutex_get_locked_count<'mir, 'tcx: 'mir>( + ecx: &MiriEvalContext<'mir, 'tcx>, + mutex_op: OpTy<'tcx, Tag>, +) -> InterpResult<'tcx, ScalarMaybeUndef> { + // Ensure that the following read at an offset to the mutex pointer is within bounds + assert_ptr_target_min_size(ecx, mutex_op, 20)?; + let mutex_place = ecx.deref_operand(mutex_op)?; + let locked_count_place = mutex_place.offset( + Size::from_bytes(4), + MemPlaceMeta::None, + ecx.machine.layouts.u32, + ecx, + )?; + ecx.read_scalar(locked_count_place.into()) +} + +fn mutex_set_locked_count<'mir, 'tcx: 'mir>( + ecx: &mut MiriEvalContext<'mir, 'tcx>, + mutex_op: OpTy<'tcx, Tag>, + locked_count: impl Into>, +) -> InterpResult<'tcx, ()> { + // Ensure that the following write at an offset to the mutex pointer is within bounds + assert_ptr_target_min_size(ecx, mutex_op, 20)?; + let mutex_place = ecx.deref_operand(mutex_op)?; + let locked_count_place = mutex_place.offset( + Size::from_bytes(4), + MemPlaceMeta::None, + ecx.machine.layouts.u32, + ecx, + )?; + ecx.write_scalar(locked_count.into(), locked_count_place.into()) +} + +fn mutex_get_kind<'mir, 'tcx: 'mir>( + ecx: &mut MiriEvalContext<'mir, 'tcx>, + mutex_op: OpTy<'tcx, Tag>, +) -> InterpResult<'tcx, ScalarMaybeUndef> { + // Ensure that the following read at an offset to the mutex pointer is within bounds + assert_ptr_target_min_size(ecx, mutex_op, 20)?; + let mutex_place = ecx.deref_operand(mutex_op)?; + let kind_offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; + let kind_place = mutex_place.offset( + Size::from_bytes(kind_offset), + MemPlaceMeta::None, + ecx.machine.layouts.i32, + ecx, + )?; + ecx.read_scalar(kind_place.into()) +} + +fn mutex_set_kind<'mir, 'tcx: 'mir>( + ecx: &mut MiriEvalContext<'mir, 'tcx>, + mutex_op: OpTy<'tcx, Tag>, + kind: impl Into>, +) -> InterpResult<'tcx, ()> { + // Ensure that the following write at an offset to the mutex pointer is within bounds + assert_ptr_target_min_size(ecx, mutex_op, 20)?; + let mutex_place = ecx.deref_operand(mutex_op)?; + let kind_offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; + let kind_place = mutex_place.offset( + Size::from_bytes(kind_offset), + MemPlaceMeta::None, + ecx.machine.layouts.i32, + ecx, + )?; + ecx.write_scalar(kind.into(), kind_place.into()) +} + +// pthread_rwlock_t is between 32 and 56 bytes, depending on the platform. + +// Our chosen memory layout for the emulated rwlock (does not have to match the platform layout!): +// bytes 0-3: reserved for signature on macOS +// (need to avoid this because it is set by static initializer macros) +// bytes 4-7: reader count, as a u32 +// bytes 8-11: writer count, as a u32 + +fn rwlock_get_readers<'mir, 'tcx: 'mir>( + ecx: &MiriEvalContext<'mir, 'tcx>, + rwlock_op: OpTy<'tcx, Tag>, +) -> InterpResult<'tcx, ScalarMaybeUndef> { + // Ensure that the following read at an offset to the rwlock pointer is within bounds + assert_ptr_target_min_size(ecx, rwlock_op, 12)?; + let rwlock_place = ecx.deref_operand(rwlock_op)?; + let readers_place = rwlock_place.offset( + Size::from_bytes(4), + MemPlaceMeta::None, + ecx.machine.layouts.u32, + ecx, + )?; + ecx.read_scalar(readers_place.into()) +} + +fn rwlock_set_readers<'mir, 'tcx: 'mir>( + ecx: &mut MiriEvalContext<'mir, 'tcx>, + rwlock_op: OpTy<'tcx, Tag>, + readers: impl Into>, +) -> InterpResult<'tcx, ()> { + // Ensure that the following write at an offset to the rwlock pointer is within bounds + assert_ptr_target_min_size(ecx, rwlock_op, 12)?; + let rwlock_place = ecx.deref_operand(rwlock_op)?; + let readers_place = rwlock_place.offset( + Size::from_bytes(4), + MemPlaceMeta::None, + ecx.machine.layouts.u32, + ecx, + )?; + ecx.write_scalar(readers.into(), readers_place.into()) +} + +fn rwlock_get_writers<'mir, 'tcx: 'mir>( + ecx: &MiriEvalContext<'mir, 'tcx>, + rwlock_op: OpTy<'tcx, Tag>, +) -> InterpResult<'tcx, ScalarMaybeUndef> { + // Ensure that the following read at an offset to the rwlock pointer is within bounds + assert_ptr_target_min_size(ecx, rwlock_op, 12)?; + let rwlock_place = ecx.deref_operand(rwlock_op)?; + let writers_place = rwlock_place.offset( + Size::from_bytes(8), + MemPlaceMeta::None, + ecx.machine.layouts.u32, + ecx, + )?; + ecx.read_scalar(writers_place.into()) +} + +fn rwlock_set_writers<'mir, 'tcx: 'mir>( + ecx: &mut MiriEvalContext<'mir, 'tcx>, + rwlock_op: OpTy<'tcx, Tag>, + writers: impl Into>, +) -> InterpResult<'tcx, ()> { + // Ensure that the following write at an offset to the rwlock pointer is within bounds + assert_ptr_target_min_size(ecx, rwlock_op, 12)?; + let rwlock_place = ecx.deref_operand(rwlock_op)?; + let writers_place = rwlock_place.offset( + Size::from_bytes(8), + MemPlaceMeta::None, + ecx.machine.layouts.u32, + ecx, + )?; + ecx.write_scalar(writers.into(), writers_place.into()) +} + +impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> { + fn pthread_mutexattr_init(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let default_kind = this.eval_libc("PTHREAD_MUTEX_DEFAULT")?; + mutexattr_set_kind(this, attr_op, default_kind)?; + + Ok(0) + } + + fn pthread_mutexattr_settype( + &mut self, + attr_op: OpTy<'tcx, Tag>, + kind_op: OpTy<'tcx, Tag>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let kind = this.read_scalar(kind_op)?.not_undef()?; + if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? + || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? + || kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? + { + mutexattr_set_kind(this, attr_op, kind)?; + } else { + let einval = this.eval_libc_i32("EINVAL")?; + return Ok(einval); + } + + Ok(0) + } + + fn pthread_mutexattr_destroy(&mut self, attr_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + mutexattr_set_kind(this, attr_op, ScalarMaybeUndef::Undef)?; + + Ok(0) + } + + fn pthread_mutex_init( + &mut self, + mutex_op: OpTy<'tcx, Tag>, + attr_op: OpTy<'tcx, Tag>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let attr = this.read_scalar(attr_op)?.not_undef()?; + let kind = if this.is_null(attr)? { + this.eval_libc("PTHREAD_MUTEX_DEFAULT")? + } else { + mutexattr_get_kind(this, attr_op)?.not_undef()? + }; + + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(0))?; + mutex_set_kind(this, mutex_op, kind)?; + + Ok(0) + } + + fn pthread_mutex_lock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let kind = mutex_get_kind(this, mutex_op)?.not_undef()?; + let locked_count = mutex_get_locked_count(this, mutex_op)?.to_u32()?; + + if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? { + if locked_count == 0 { + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(1))?; + Ok(0) + } else { + throw_machine_stop!(TerminationInfo::Deadlock); + } + } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? { + if locked_count == 0 { + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(1))?; + Ok(0) + } else { + this.eval_libc_i32("EDEADLK") + } + } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? { + match locked_count.checked_add(1) { + Some(new_count) => { + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(new_count))?; + Ok(0) + } + None => this.eval_libc_i32("EAGAIN"), + } + } else { + throw_ub_format!("called pthread_mutex_lock on an unsupported type of mutex"); + } + } + + fn pthread_mutex_trylock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let kind = mutex_get_kind(this, mutex_op)?.not_undef()?; + let locked_count = mutex_get_locked_count(this, mutex_op)?.to_u32()?; + + if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? + || kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? + { + if locked_count == 0 { + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(1))?; + Ok(0) + } else { + this.eval_libc_i32("EBUSY") + } + } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? { + match locked_count.checked_add(1) { + Some(new_count) => { + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(new_count))?; + Ok(0) + } + None => this.eval_libc_i32("EAGAIN"), + } + } else { + throw_ub_format!("called pthread_mutex_trylock on an unsupported type of mutex"); + } + } + + fn pthread_mutex_unlock(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let kind = mutex_get_kind(this, mutex_op)?.not_undef()?; + let locked_count = mutex_get_locked_count(this, mutex_op)?.to_u32()?; + + if kind == this.eval_libc("PTHREAD_MUTEX_NORMAL")? { + if locked_count != 0 { + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(0))?; + Ok(0) + } else { + throw_ub_format!("unlocked a PTHREAD_MUTEX_NORMAL mutex that was not locked"); + } + } else if kind == this.eval_libc("PTHREAD_MUTEX_ERRORCHECK")? { + if locked_count != 0 { + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(0))?; + Ok(0) + } else { + this.eval_libc_i32("EPERM") + } + } else if kind == this.eval_libc("PTHREAD_MUTEX_RECURSIVE")? { + match locked_count.checked_sub(1) { + Some(new_count) => { + mutex_set_locked_count(this, mutex_op, Scalar::from_u32(new_count))?; + Ok(0) + } + None => { + // locked_count was already zero + this.eval_libc_i32("EPERM") + } + } + } else { + throw_ub_format!("called pthread_mutex_unlock on an unsupported type of mutex"); + } + } + + fn pthread_mutex_destroy(&mut self, mutex_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + if mutex_get_locked_count(this, mutex_op)?.to_u32()? != 0 { + throw_ub_format!("destroyed a locked mutex"); + } + + mutex_set_kind(this, mutex_op, ScalarMaybeUndef::Undef)?; + mutex_set_locked_count(this, mutex_op, ScalarMaybeUndef::Undef)?; + + Ok(0) + } + + fn pthread_rwlock_rdlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?; + let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?; + if writers != 0 { + throw_machine_stop!(TerminationInfo::Deadlock); + } else { + match readers.checked_add(1) { + Some(new_readers) => { + rwlock_set_readers(this, rwlock_op, Scalar::from_u32(new_readers))?; + Ok(0) + } + None => this.eval_libc_i32("EAGAIN"), + } + } + } + + fn pthread_rwlock_tryrdlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?; + let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?; + if writers != 0 { + this.eval_libc_i32("EBUSY") + } else { + match readers.checked_add(1) { + Some(new_readers) => { + rwlock_set_readers(this, rwlock_op, Scalar::from_u32(new_readers))?; + Ok(0) + } + None => this.eval_libc_i32("EAGAIN"), + } + } + } + + fn pthread_rwlock_wrlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?; + let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?; + if readers != 0 { + throw_machine_stop!(TerminationInfo::Deadlock); + } else if writers != 0 { + throw_machine_stop!(TerminationInfo::Deadlock); + } else { + rwlock_set_writers(this, rwlock_op, Scalar::from_u32(1))?; + Ok(0) + } + } + + fn pthread_rwlock_trywrlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?; + let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?; + if readers != 0 || writers != 0 { + this.eval_libc_i32("EBUSY") + } else { + rwlock_set_writers(this, rwlock_op, Scalar::from_u32(1))?; + Ok(0) + } + } + + fn pthread_rwlock_unlock(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let readers = rwlock_get_readers(this, rwlock_op)?.to_u32()?; + let writers = rwlock_get_writers(this, rwlock_op)?.to_u32()?; + if let Some(new_readers) = readers.checked_sub(1) { + rwlock_set_readers(this, rwlock_op, Scalar::from_u32(new_readers))?; + Ok(0) + } else if writers != 0 { + rwlock_set_writers(this, rwlock_op, Scalar::from_u32(0))?; + Ok(0) + } else { + throw_ub_format!("unlocked an rwlock that was not locked"); + } + } + + fn pthread_rwlock_destroy(&mut self, rwlock_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + if rwlock_get_readers(this, rwlock_op)?.to_u32()? != 0 + || rwlock_get_writers(this, rwlock_op)?.to_u32()? != 0 + { + throw_ub_format!("destroyed a locked rwlock"); + } + + rwlock_set_readers(this, rwlock_op, ScalarMaybeUndef::Undef)?; + rwlock_set_writers(this, rwlock_op, ScalarMaybeUndef::Undef)?; + + Ok(0) + } +} diff --git a/tests/compile-fail/libc_pthread_mutex_destroy_locked.rs b/tests/compile-fail/libc_pthread_mutex_destroy_locked.rs new file mode 100644 index 0000000000..e7ed8ad296 --- /dev/null +++ b/tests/compile-fail/libc_pthread_mutex_destroy_locked.rs @@ -0,0 +1,16 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), 0); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + libc::pthread_mutex_destroy(&mut mutex as *mut _); //~ ERROR destroyed a locked mutex + } +} diff --git a/tests/compile-fail/libc_pthread_mutex_normal_deadlock.rs b/tests/compile-fail/libc_pthread_mutex_normal_deadlock.rs new file mode 100644 index 0000000000..7034bf64ec --- /dev/null +++ b/tests/compile-fail/libc_pthread_mutex_normal_deadlock.rs @@ -0,0 +1,16 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), 0); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + libc::pthread_mutex_lock(&mut mutex as *mut _); //~ ERROR deadlock + } +} diff --git a/tests/compile-fail/libc_pthread_mutex_normal_unlock_unlocked.rs b/tests/compile-fail/libc_pthread_mutex_normal_unlock_unlocked.rs new file mode 100644 index 0000000000..65de62484d --- /dev/null +++ b/tests/compile-fail/libc_pthread_mutex_normal_unlock_unlocked.rs @@ -0,0 +1,17 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), 0); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + libc::pthread_mutex_unlock(&mut mutex as *mut _); //~ ERROR was not locked + } +} diff --git a/tests/compile-fail/libc_pthread_rwlock_destroy_read_locked.rs b/tests/compile-fail/libc_pthread_rwlock_destroy_read_locked.rs new file mode 100644 index 0000000000..8750a7388f --- /dev/null +++ b/tests/compile-fail/libc_pthread_rwlock_destroy_read_locked.rs @@ -0,0 +1,13 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); + libc::pthread_rwlock_destroy(rw.get()); //~ ERROR destroyed a locked rwlock + } +} diff --git a/tests/compile-fail/libc_pthread_rwlock_destroy_write_locked.rs b/tests/compile-fail/libc_pthread_rwlock_destroy_write_locked.rs new file mode 100644 index 0000000000..aecccfa503 --- /dev/null +++ b/tests/compile-fail/libc_pthread_rwlock_destroy_write_locked.rs @@ -0,0 +1,13 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); + libc::pthread_rwlock_destroy(rw.get()); //~ ERROR destroyed a locked rwlock + } +} diff --git a/tests/compile-fail/libc_pthread_rwlock_read_write_deadlock.rs b/tests/compile-fail/libc_pthread_rwlock_read_write_deadlock.rs new file mode 100644 index 0000000000..dd4707d60e --- /dev/null +++ b/tests/compile-fail/libc_pthread_rwlock_read_write_deadlock.rs @@ -0,0 +1,13 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); + libc::pthread_rwlock_wrlock(rw.get()); //~ ERROR: deadlock + } +} diff --git a/tests/compile-fail/libc_pthread_rwlock_unlock_unlocked.rs b/tests/compile-fail/libc_pthread_rwlock_unlock_unlocked.rs new file mode 100644 index 0000000000..8b3de53828 --- /dev/null +++ b/tests/compile-fail/libc_pthread_rwlock_unlock_unlocked.rs @@ -0,0 +1,12 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + libc::pthread_rwlock_unlock(rw.get()); //~ ERROR was not locked + } +} diff --git a/tests/compile-fail/libc_pthread_rwlock_write_read_deadlock.rs b/tests/compile-fail/libc_pthread_rwlock_write_read_deadlock.rs new file mode 100644 index 0000000000..1b460e7174 --- /dev/null +++ b/tests/compile-fail/libc_pthread_rwlock_write_read_deadlock.rs @@ -0,0 +1,13 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); + libc::pthread_rwlock_rdlock(rw.get()); //~ ERROR: deadlock + } +} diff --git a/tests/compile-fail/libc_pthread_rwlock_write_write_deadlock.rs b/tests/compile-fail/libc_pthread_rwlock_write_write_deadlock.rs new file mode 100644 index 0000000000..cc327ec46b --- /dev/null +++ b/tests/compile-fail/libc_pthread_rwlock_write_write_deadlock.rs @@ -0,0 +1,13 @@ +// ignore-windows: No libc on Windows + +#![feature(rustc_private)] + +extern crate libc; + +fn main() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); + libc::pthread_rwlock_wrlock(rw.get()); //~ ERROR: deadlock + } +} diff --git a/tests/run-pass/libc.rs b/tests/run-pass/libc.rs index 064c00e81b..fc154c05c8 100644 --- a/tests/run-pass/libc.rs +++ b/tests/run-pass/libc.rs @@ -12,10 +12,10 @@ fn tmp() -> PathBuf { std::env::var("MIRI_TEMP").map(PathBuf::from).unwrap_or_else(|_| std::env::temp_dir()) } -#[cfg(not(target_os = "macos"))] +#[cfg(target_os = "linux")] fn test_posix_fadvise() { use std::convert::TryInto; - use std::fs::{File, remove_file}; + use std::fs::{remove_file, File}; use std::io::Write; use std::os::unix::io::AsRawFd; @@ -42,7 +42,116 @@ fn test_posix_fadvise() { assert_eq!(result, 0); } +fn test_mutex_libc_init_recursive() { + unsafe { + let mut attr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutexattr_init(&mut attr as *mut _), 0); + assert_eq!(libc::pthread_mutexattr_settype(&mut attr as *mut _, libc::PTHREAD_MUTEX_RECURSIVE), 0); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mut attr as *mut _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), libc::EPERM); + assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutexattr_destroy(&mut attr as *mut _), 0); + } +} + +fn test_mutex_libc_init_normal() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, 0x12345678), libc::EINVAL); + assert_eq!(libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_NORMAL), 0); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), libc::EBUSY); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); + } +} + +fn test_mutex_libc_init_errorcheck() { + unsafe { + let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutexattr_settype(&mut mutexattr as *mut _, libc::PTHREAD_MUTEX_ERRORCHECK), 0); + let mut mutex: libc::pthread_mutex_t = std::mem::zeroed(); + assert_eq!(libc::pthread_mutex_init(&mut mutex as *mut _, &mutexattr as *const _), 0); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), libc::EBUSY); + assert_eq!(libc::pthread_mutex_lock(&mut mutex as *mut _), libc::EDEADLK); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_trylock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), 0); + assert_eq!(libc::pthread_mutex_unlock(&mut mutex as *mut _), libc::EPERM); + assert_eq!(libc::pthread_mutex_destroy(&mut mutex as *mut _), 0); + } +} + +// Only linux provides PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, +// libc for macOS just has the default PTHREAD_MUTEX_INITIALIZER. +#[cfg(target_os = "linux")] +fn test_mutex_libc_static_initializer_recursive() { + let mutex = std::cell::UnsafeCell::new(libc::PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP); + unsafe { + assert_eq!(libc::pthread_mutex_lock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_trylock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_trylock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_lock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), 0); + assert_eq!(libc::pthread_mutex_unlock(mutex.get()), libc::EPERM); + assert_eq!(libc::pthread_mutex_destroy(mutex.get()), 0); + } +} + +// Testing the behavior of std::sync::RwLock does not fully exercise the pthread rwlock shims, we +// need to go a layer deeper and test the behavior of the libc functions, because +// std::sys::unix::rwlock::RWLock itself keeps track of write_locked and num_readers. +fn test_rwlock_libc_static_initializer() { + let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER); + unsafe { + assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_rdlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + + assert_eq!(libc::pthread_rwlock_wrlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + + assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), 0); + assert_eq!(libc::pthread_rwlock_tryrdlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_trywrlock(rw.get()), libc::EBUSY); + assert_eq!(libc::pthread_rwlock_unlock(rw.get()), 0); + + assert_eq!(libc::pthread_rwlock_destroy(rw.get()), 0); + } +} + fn main() { - #[cfg(not(target_os = "macos"))] + #[cfg(target_os = "linux")] test_posix_fadvise(); + + test_mutex_libc_init_recursive(); + test_mutex_libc_init_normal(); + test_mutex_libc_init_errorcheck(); + test_rwlock_libc_static_initializer(); + + #[cfg(target_os = "linux")] + test_mutex_libc_static_initializer_recursive(); } diff --git a/tests/run-pass/reentrant-println.rs b/tests/run-pass/reentrant-println.rs new file mode 100644 index 0000000000..e73e82b8ec --- /dev/null +++ b/tests/run-pass/reentrant-println.rs @@ -0,0 +1,17 @@ +use std::fmt::{Display, Error, Formatter}; + +// This test case exercises std::sys_common::remutex::ReentrantMutex +// by calling println!() from inside fmt. + +struct InterruptingCow; + +impl Display for InterruptingCow { + fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), Error> { + println!("Moo"); + Ok(()) + } +} + +fn main() { + println!("\"Knock knock\" \"Who's {} there?\"", InterruptingCow); +} diff --git a/tests/run-pass/reentrant-println.stdout b/tests/run-pass/reentrant-println.stdout new file mode 100644 index 0000000000..8a57d32f84 --- /dev/null +++ b/tests/run-pass/reentrant-println.stdout @@ -0,0 +1,2 @@ +"Knock knock" "Who's Moo + there?" diff --git a/tests/run-pass/sync.rs b/tests/run-pass/sync.rs index 54d79566ea..1ede5d42bb 100644 --- a/tests/run-pass/sync.rs +++ b/tests/run-pass/sync.rs @@ -1,18 +1,52 @@ -// Just instantiate some data structures to make sure we got all their foreign items covered. -// Requires full MIR on Windows. +#![feature(rustc_private)] -use std::sync; +use std::sync::{Mutex, TryLockError}; fn main() { - let m = sync::Mutex::new(0); - drop(m.lock()); + test_mutex_stdlib(); + #[cfg(not(target_os = "windows"))] // TODO: implement RwLock on Windows + { + test_rwlock_stdlib(); + } +} + +fn test_mutex_stdlib() { + let m = Mutex::new(0); + { + let _guard = m.lock(); + assert!(m.try_lock().unwrap_err().would_block()); + } + drop(m.try_lock().unwrap()); drop(m); +} - #[cfg(not(target_os = "windows"))] // TODO: implement RwLock on Windows +#[cfg(not(target_os = "windows"))] +fn test_rwlock_stdlib() { + use std::sync::RwLock; + let rw = RwLock::new(0); { - let rw = sync::RwLock::new(0); - drop(rw.read()); - drop(rw.write()); - drop(rw); + let _read_guard = rw.read().unwrap(); + drop(rw.read().unwrap()); + drop(rw.try_read().unwrap()); + assert!(rw.try_write().unwrap_err().would_block()); + } + + { + let _write_guard = rw.write().unwrap(); + assert!(rw.try_read().unwrap_err().would_block()); + assert!(rw.try_write().unwrap_err().would_block()); + } +} + +trait TryLockErrorExt { + fn would_block(&self) -> bool; +} + +impl TryLockErrorExt for TryLockError { + fn would_block(&self) -> bool { + match self { + TryLockError::WouldBlock => true, + TryLockError::Poisoned(_) => false, + } } }