Skip to content

Commit d7c4c0f

Browse files
committed
Fix race condition in feature cache on 32 platforms
If we observe that the second word is initialized, we can't really assume that the first is initialized as well. So check each word separately.
1 parent 654ce02 commit d7c4c0f

File tree

1 file changed

+52
-62
lines changed

1 file changed

+52
-62
lines changed

crates/std_detect/src/detect/cache.rs

+52-62
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55

66
use crate::sync::atomic::Ordering;
77

8-
#[cfg(target_pointer_width = "64")]
9-
use crate::sync::atomic::AtomicU64;
10-
11-
#[cfg(target_pointer_width = "32")]
12-
use crate::sync::atomic::AtomicU32;
8+
use crate::sync::atomic::AtomicUsize;
139

1410
/// Sets the `bit` of `x`.
1511
#[inline]
@@ -30,7 +26,7 @@ const fn unset_bit(x: u64, bit: u32) -> u64 {
3026
}
3127

3228
/// Maximum number of features that can be cached.
33-
const CACHE_CAPACITY: u32 = 63;
29+
const CACHE_CAPACITY: u32 = 62;
3430

3531
/// This type is used to initialize the cache
3632
#[derive(Copy, Clone)]
@@ -81,83 +77,44 @@ impl Initializer {
8177
}
8278

8379
/// This global variable is a cache of the features supported by the CPU.
80+
#[cfg(target_pointer_width = "64")]
8481
static CACHE: Cache = Cache::uninitialized();
8582

86-
/// Feature cache with capacity for `CACHE_CAPACITY` features.
83+
/// This global variable is a cache of the features supported by the CPU.
84+
#[cfg(target_pointer_width = "32")]
85+
static CACHE: [Cache; 2] = [Cache::uninitialized(), Cache::uninitialized()];
86+
87+
/// Feature cache with capacity for `usize::max_value() - 1` features.
8788
///
8889
/// Note: the last feature bit is used to represent an
8990
/// uninitialized cache.
90-
#[cfg(target_pointer_width = "64")]
91-
struct Cache(AtomicU64);
91+
struct Cache(AtomicUsize);
9292

93-
#[cfg(target_pointer_width = "64")]
94-
#[allow(clippy::use_self)]
9593
impl Cache {
9694
/// Creates an uninitialized cache.
9795
#[allow(clippy::declare_interior_mutable_const)]
9896
const fn uninitialized() -> Self {
99-
Cache(AtomicU64::new(u64::max_value()))
97+
Cache(AtomicUsize::new(usize::max_value()))
10098
}
10199
/// Is the cache uninitialized?
102100
#[inline]
103101
pub(crate) fn is_uninitialized(&self) -> bool {
104-
self.0.load(Ordering::Relaxed) == u64::max_value()
102+
self.0.load(Ordering::Relaxed) == usize::max_value()
105103
}
106104

107105
/// Is the `bit` in the cache set?
108106
#[inline]
109107
pub(crate) fn test(&self, bit: u32) -> bool {
110-
test_bit(CACHE.0.load(Ordering::Relaxed), bit)
108+
test_bit(self.0.load(Ordering::Relaxed) as u64, bit)
111109
}
112110

113111
/// Initializes the cache.
114112
#[inline]
115-
pub(crate) fn initialize(&self, value: Initializer) {
116-
self.0.store(value.0, Ordering::Relaxed);
113+
fn initialize(&self, value: usize) {
114+
self.0.store(value, Ordering::Relaxed);
117115
}
118116
}
119117

120-
/// Feature cache with capacity for `CACHE_CAPACITY` features.
121-
///
122-
/// Note: the last feature bit is used to represent an
123-
/// uninitialized cache.
124-
#[cfg(target_pointer_width = "32")]
125-
struct Cache(AtomicU32, AtomicU32);
126-
127-
#[cfg(target_pointer_width = "32")]
128-
impl Cache {
129-
/// Creates an uninitialized cache.
130-
const fn uninitialized() -> Self {
131-
Cache(
132-
AtomicU32::new(u32::max_value()),
133-
AtomicU32::new(u32::max_value()),
134-
)
135-
}
136-
/// Is the cache uninitialized?
137-
#[inline]
138-
pub(crate) fn is_uninitialized(&self) -> bool {
139-
self.1.load(Ordering::Relaxed) == u32::max_value()
140-
}
141-
142-
/// Is the `bit` in the cache set?
143-
#[inline]
144-
pub(crate) fn test(&self, bit: u32) -> bool {
145-
if bit < 32 {
146-
test_bit(CACHE.0.load(Ordering::Relaxed) as u64, bit)
147-
} else {
148-
test_bit(CACHE.1.load(Ordering::Relaxed) as u64, bit - 32)
149-
}
150-
}
151-
152-
/// Initializes the cache.
153-
#[inline]
154-
pub(crate) fn initialize(&self, value: Initializer) {
155-
let lo: u32 = value.0 as u32;
156-
let hi: u32 = (value.0 >> 32) as u32;
157-
self.0.store(lo, Ordering::Relaxed);
158-
self.1.store(hi, Ordering::Relaxed);
159-
}
160-
}
161118
cfg_if::cfg_if! {
162119
if #[cfg(feature = "std_detect_env_override")] {
163120
#[inline(never)]
@@ -167,12 +124,27 @@ cfg_if::cfg_if! {
167124
let _ = super::Feature::from_str(v).map(|v| value.unset(v as u32));
168125
}
169126
}
170-
CACHE.initialize(value);
127+
do_initialize(value);
171128
}
172129
} else {
173130
#[inline]
174131
fn initialize(value: Initializer) {
175-
CACHE.initialize(value);
132+
do_initialize(value);
133+
}
134+
}
135+
}
136+
137+
#[inline]
138+
fn do_initialize(value: Initializer) {
139+
cfg_if::cfg_if! {
140+
if #[cfg(target_pointer_width = "64")] {
141+
CACHE.initialize(value.0 as usize)
142+
} else if #[cfg(target_pointer_width = "32")] {
143+
const LOW_31_BIT: usize = 0x7FFF_FFFF;
144+
CACHE[0].initialize((value.0) as usize & LOW_31_BIT);
145+
CACHE[1].initialize((value.0 >> 31) as usize & LOW_31_BIT);
146+
} else {
147+
compile_error!("unsupported target_pointer_width");
176148
}
177149
}
178150
}
@@ -194,8 +166,26 @@ pub(crate) fn test<F>(bit: u32, f: F) -> bool
194166
where
195167
F: FnOnce() -> Initializer,
196168
{
197-
if CACHE.is_uninitialized() {
198-
initialize(f());
169+
cfg_if::cfg_if! {
170+
if #[cfg(target_pointer_width = "64")] {
171+
if CACHE.is_uninitialized() {
172+
initialize(f());
173+
}
174+
return CACHE.test(bit);
175+
} else if #[cfg(target_pointer_width = "32")] {
176+
if bit < 31 {
177+
if CACHE[0].is_uninitialized() {
178+
initialize(f())
179+
}
180+
return CACHE[0].test(bit)
181+
} else {
182+
if CACHE[1].is_uninitialized() {
183+
initialize(f())
184+
}
185+
return CACHE[1].test(bit - 31)
186+
}
187+
} else {
188+
compile_error!("unsupported target_pointer_width");
189+
}
199190
}
200-
CACHE.test(bit)
201191
}

0 commit comments

Comments
 (0)