Skip to content

Commit 97e0396

Browse files
Implement lazy_static support (#93)
* Implement `lazy_static` support This change adds support for the `lazy_static` crate. It's mostly built around a `Once` cell for each static plus some global storage. * Generalize warnings and now warn on lazy_static drops
1 parent 2f52049 commit 97e0396

File tree

7 files changed

+466
-9
lines changed

7 files changed

+466
-9
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 0.6.0 (January 24, 2023)
2+
3+
This version renames the [`silence_atomic_ordering_warning` configuration option](https://docs.rs/shuttle/0.5.0/shuttle/struct.Config.html#structfield.silence_atomic_ordering_warning) to `silence_warnings`, as well as the corresponding environment variables, to enable future warnings to be controlled by the same mechanism.
4+
5+
* Implement `lazy_static` support (#93)
6+
17
# 0.5.0 (November 22, 2022)
28

39
This version updates the embedded `rand` library to v0.8.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "shuttle"
3-
version = "0.5.0"
3+
version = "0.6.0"
44
edition = "2021"
55
license = "Apache-2.0"
66
description = "A library for testing concurrent Rust code"

src/lazy_static.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//! Shuttle's implementation of the [`lazy_static`] crate, v1.4.0.
2+
//!
3+
//! Using this structure, it is possible to have `static`s that require code to be executed at
4+
//! runtime in order to be initialized. Lazy statics should be created with the
5+
//! [`lazy_static!`](crate::lazy_static!) macro.
6+
//!
7+
//! # Warning about drop behavior
8+
//!
9+
//! Shuttle's implementation of `lazy_static` will drop the static value at the end of an execution,
10+
//! and so run the value's [`Drop`] implementation. The actual `lazy_static` crate does not drop the
11+
//! static values, so this difference may cause false positives.
12+
//!
13+
//! To disable the warning printed about this issue, set the `SHUTTLE_SILENCE_WARNINGS` environment
14+
//! variable to any value, or set the [`silence_warnings`](crate::Config::silence_warnings) field of
15+
//! [`Config`](crate::Config) to true.
16+
//!
17+
//! [`lazy_static`]: https://crates.io/crates/lazy_static
18+
19+
use crate::runtime::execution::ExecutionState;
20+
use crate::runtime::storage::StorageKey;
21+
use crate::sync::Once;
22+
use std::marker::PhantomData;
23+
24+
/// Shuttle's implementation of `lazy_static::Lazy` (aka the unstable `std::lazy::Lazy`).
25+
// Sadly, the fields of this thing need to be public because function pointers in const fns are
26+
// unstable, so an explicit instantiation is the only way to construct this struct. User code should
27+
// not rely on these fields.
28+
pub struct Lazy<T: Sync> {
29+
#[doc(hidden)]
30+
pub cell: Once,
31+
#[doc(hidden)]
32+
pub init: fn() -> T,
33+
#[doc(hidden)]
34+
pub _p: PhantomData<T>,
35+
}
36+
37+
impl<T: Sync> std::fmt::Debug for Lazy<T> {
38+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39+
f.debug_struct("Lazy").finish_non_exhaustive()
40+
}
41+
}
42+
43+
impl<T: Sync> Lazy<T> {
44+
/// Get a reference to the lazy value, initializing it first if necessary.
45+
pub fn get(&'static self) -> &T {
46+
// Safety: see the usage below
47+
unsafe fn extend_lt<'a, T>(t: &'a T) -> &'static T {
48+
std::mem::transmute(t)
49+
}
50+
51+
// We implement lazy statics by using a `Once` to mediate initialization of the static, just
52+
// as the real implementation does. If two threads race on initializing the static, they
53+
// will race on the `Once::call_once`, and only one of them will initialize the static. Once
54+
// it's initialized, all future accesses can bypass the `Once` entirely and just access the
55+
// storage cell.
56+
57+
let initialize = ExecutionState::with(|state| state.get_storage::<_, DropGuard<T>>(self).is_none());
58+
59+
if initialize {
60+
// There's not yet a value for this static, so try to initialize it (possibly racing)
61+
self.cell.call_once(|| {
62+
let value = (self.init)();
63+
ExecutionState::with(|state| state.init_storage(self, DropGuard(value)));
64+
});
65+
}
66+
67+
// At this point we're guaranteed that a value exists for this static, so read it
68+
ExecutionState::with(|state| {
69+
let value: &DropGuard<T> = state.get_storage(self).expect("should be initialized");
70+
// Safety: this *isn't* safe. We are promoting to a `'static` lifetime here, but this
71+
// object does not actually live that long. It would be possible for this reference to
72+
// escape the client code and be used after it becomes invalid when the execution ends.
73+
// But there's not really any way around this -- the semantics of static values and
74+
// Shuttle executions are incompatible.
75+
//
76+
// In reality, this should be safe because any code that uses a Shuttle `lazy_static`
77+
// probably uses a regular `lazy_static` in its non-test version, and if that version
78+
// compiles then the Shuttle version is also safe. We choose this unsafe path because
79+
// it's intended to be used only in testing code, which we want to remain as compatible
80+
// with real-world code as possible and so needs to preserve the semantics of statics.
81+
//
82+
// See also https://github.com/tokio-rs/loom/pull/125
83+
unsafe { extend_lt(&value.0) }
84+
})
85+
}
86+
}
87+
88+
impl<T: Sync> From<&Lazy<T>> for StorageKey {
89+
fn from(lazy: &Lazy<T>) -> Self {
90+
StorageKey(lazy as *const _ as usize, 0x3)
91+
}
92+
}
93+
94+
/// Support trait for enabling a few common operation on lazy static values.
95+
///
96+
/// This is implemented by each defined lazy static, and used by the free functions in this crate.
97+
pub trait LazyStatic {
98+
#[doc(hidden)]
99+
fn initialize(lazy: &Self);
100+
}
101+
102+
/// Takes a shared reference to a lazy static and initializes it if it has not been already.
103+
///
104+
/// This can be used to control the initialization point of a lazy static.
105+
pub fn initialize<T: LazyStatic>(lazy: &T) {
106+
LazyStatic::initialize(lazy);
107+
}
108+
109+
static PRINTED_DROP_WARNING: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
110+
111+
fn maybe_warn_about_drop() {
112+
use owo_colors::OwoColorize;
113+
use std::sync::atomic::Ordering;
114+
115+
if PRINTED_DROP_WARNING
116+
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
117+
.is_ok()
118+
{
119+
if std::env::var("SHUTTLE_SILENCE_WARNINGS").is_ok() {
120+
return;
121+
}
122+
123+
if ExecutionState::with(|state| state.config.silence_warnings) {
124+
return;
125+
}
126+
127+
eprintln!(
128+
"{}: Shuttle runs the `Drop` method of `lazy_static` values at the end of an execution, \
129+
unlike the actual `lazy_static` implementation. This difference may cause false positives. \
130+
See https://docs.rs/shuttle/*/shuttle/lazy_static/index.html#warning-about-drop-behavior \
131+
for details or to disable this warning.",
132+
"WARNING".yellow(),
133+
);
134+
}
135+
}
136+
137+
/// Small wrapper to trigger the warning about drop behavior when a `lazy_static` value drops for
138+
/// the first time.
139+
#[derive(Debug)]
140+
struct DropGuard<T>(T);
141+
142+
impl<T> Drop for DropGuard<T> {
143+
fn drop(&mut self) {
144+
maybe_warn_about_drop();
145+
}
146+
}

src/lib.rs

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
183183
pub mod future;
184184
pub mod hint;
185+
pub mod lazy_static;
185186
pub mod rand;
186187
pub mod sync;
187188
pub mod thread;
@@ -213,9 +214,12 @@ pub struct Config {
213214
/// not abort a currently running test iteration; the limit is only checked between iterations.
214215
pub max_time: Option<std::time::Duration>,
215216

216-
/// Whether to enable warnings about [Shuttle's unsound implementation of
217-
/// `atomic`](crate::sync::atomic#warning-about-relaxed-behaviors).
218-
pub silence_atomic_ordering_warning: bool,
217+
/// Whether to silence warnings about Shuttle behaviors that may miss bugs or introduce false
218+
/// positives:
219+
/// 1. [Unsound implementation of `atomic`](crate::sync::atomic#warning-about-relaxed-behaviors)
220+
/// may miss bugs
221+
/// 2. [`lazy_static` values are dropped](mod@crate::lazy_static) at the end of an execution
222+
pub silence_warnings: bool,
219223
}
220224

221225
impl Config {
@@ -226,7 +230,7 @@ impl Config {
226230
failure_persistence: FailurePersistence::Print,
227231
max_steps: MaxSteps::FailAfter(1_000_000),
228232
max_time: None,
229-
silence_atomic_ordering_warning: false,
233+
silence_warnings: false,
230234
}
231235
}
232236
}
@@ -414,3 +418,69 @@ macro_rules! __thread_local_inner {
414418
};
415419
}
416420
}
421+
422+
/// Declare a new [lazy static value](crate::lazy_static::Lazy), like the `lazy_static` crate.
423+
// These macros are copied from the lazy_static crate.
424+
#[macro_export]
425+
macro_rules! lazy_static {
426+
($(#[$attr:meta])* static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
427+
// use `()` to explicitly forward the information about private items
428+
$crate::__lazy_static_internal!($(#[$attr])* () static ref $N : $T = $e; $($t)*);
429+
};
430+
($(#[$attr:meta])* pub static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
431+
$crate::__lazy_static_internal!($(#[$attr])* (pub) static ref $N : $T = $e; $($t)*);
432+
};
433+
($(#[$attr:meta])* pub ($($vis:tt)+) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
434+
$crate::__lazy_static_internal!($(#[$attr])* (pub ($($vis)+)) static ref $N : $T = $e; $($t)*);
435+
};
436+
() => ()
437+
}
438+
439+
#[macro_export]
440+
#[doc(hidden)]
441+
macro_rules! __lazy_static_internal {
442+
// optional visibility restrictions are wrapped in `()` to allow for
443+
// explicitly passing otherwise implicit information about private items
444+
($(#[$attr:meta])* ($($vis:tt)*) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
445+
$crate::__lazy_static_internal!(@MAKE TY, $(#[$attr])*, ($($vis)*), $N);
446+
$crate::__lazy_static_internal!(@TAIL, $N : $T = $e);
447+
$crate::lazy_static!($($t)*);
448+
};
449+
(@TAIL, $N:ident : $T:ty = $e:expr) => {
450+
impl ::std::ops::Deref for $N {
451+
type Target = $T;
452+
fn deref(&self) -> &$T {
453+
#[inline(always)]
454+
fn __static_ref_initialize() -> $T { $e }
455+
456+
#[inline(always)]
457+
fn __stability() -> &'static $T {
458+
static LAZY: $crate::lazy_static::Lazy<$T> =
459+
$crate::lazy_static::Lazy {
460+
cell: $crate::sync::Once::new(),
461+
init: __static_ref_initialize,
462+
_p: std::marker::PhantomData,
463+
};
464+
LAZY.get()
465+
}
466+
__stability()
467+
}
468+
}
469+
impl $crate::lazy_static::LazyStatic for $N {
470+
fn initialize(lazy: &Self) {
471+
let _ = &**lazy;
472+
}
473+
}
474+
};
475+
// `vis` is wrapped in `()` to prevent parsing ambiguity
476+
(@MAKE TY, $(#[$attr:meta])*, ($($vis:tt)*), $N:ident) => {
477+
#[allow(missing_copy_implementations)]
478+
#[allow(non_camel_case_types)]
479+
#[allow(dead_code)]
480+
$(#[$attr])*
481+
$($vis)* struct $N {__private_field: ()}
482+
#[doc(hidden)]
483+
$($vis)* static $N: $N = $N {__private_field: ()};
484+
};
485+
() => ()
486+
}

src/sync/atomic/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@
4444
//! correctness, the [Loom] crate provides support for reasoning about Acquire and Release orderings
4545
//! and partial support for Relaxed orderings.
4646
//!
47-
//! To disable the warning printed about this issue, set the `SHUTTLE_SILENCE_ORDERING_WARNING`
48-
//! environment variable to any value, or set the
49-
//! [`silence_atomic_ordering_warning`](crate::Config::silence_atomic_ordering_warning) field of
47+
//! To disable the warning printed about this issue, set the `SHUTTLE_SILENCE_WARNINGS` environment
48+
//! variable to any value, or set the [`silence_warnings`](crate::Config::silence_warnings) field of
5049
//! [`Config`](crate::Config) to true.
5150
//!
5251
//! [Loom]: https://crates.io/crates/loom
@@ -81,7 +80,7 @@ fn maybe_warn_about_ordering(order: Ordering) {
8180
return;
8281
}
8382

84-
if ExecutionState::with(|state| state.config.silence_atomic_ordering_warning) {
83+
if ExecutionState::with(|state| state.config.silence_warnings) {
8584
return;
8685
}
8786

0 commit comments

Comments
 (0)