Skip to content

Commit 197b736

Browse files
authored
Remove Clone requirement for Ephemeron::value (#5107)
This PR removes the `Clone` requirement we had for fetching `Ephemeron` values by introducing an `EphemeronValueRef` that derefs to `&V`. Soundness is preserved by storing the key of the ephemeron in the `EphemeronValueRef`, such that any collection won't be able to deallocate the value. This ensures the reference to `&V` is never invalid.
1 parent 419a8ed commit 197b736

File tree

8 files changed

+81
-52
lines changed

8 files changed

+81
-52
lines changed

core/engine/src/builtins/weak_map/mod.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ impl WeakMap {
157157
// ii. Set p.[[Value]] to empty.
158158
// iii. Return true.
159159
// 6. Return false.
160-
Ok(map.remove(key.inner()).is_some().into())
160+
Ok(map.remove(key.inner()).into())
161161
}
162162

163163
/// `WeakMap.prototype.get ( key )`
@@ -193,7 +193,13 @@ impl WeakMap {
193193
// 5. For each Record { [[Key]], [[Value]] } p of entries, do
194194
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
195195
// 6. Return undefined.
196-
Ok(map.get(key.inner()).unwrap_or_default())
196+
if let Some(entry) = map.get(key.inner())
197+
&& let Some(val) = entry.value()
198+
{
199+
Ok(val.clone())
200+
} else {
201+
Ok(JsValue::undefined())
202+
}
197203
}
198204

199205
/// `WeakMap.prototype.has ( key )`
@@ -318,9 +324,11 @@ impl WeakMap {
318324
};
319325

320326
// 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]]
321-
if let Some(existing) = map.borrow().data().get(key.inner()) {
327+
if let Some(existing) = map.borrow().data().get(key.inner())
328+
&& let Some(value) = existing.value()
329+
{
322330
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
323-
return Ok(existing);
331+
return Ok(value.clone());
324332
}
325333

326334
// 5-6. Insert the new record with provided value and return it.
@@ -378,9 +386,11 @@ impl WeakMap {
378386
};
379387

380388
// 5. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]]
381-
if let Some(existing) = map.borrow().data().get(key_obj.inner()) {
389+
if let Some(existing) = map.borrow().data().get(key_obj.inner())
390+
&& let Some(value) = existing.value()
391+
{
382392
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
383-
return Ok(existing);
393+
return Ok(value.clone());
384394
}
385395

386396
// 6. Let value be ? Call(callback, undefined, « key »).

core/engine/src/builtins/weak_set/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ impl WeakSet {
212212
// i. Replace the element of entries whose value is e with an element whose value is empty.
213213
// ii. Return true.
214214
// 6. Return false.
215-
Ok(set.remove(value.inner()).is_some().into())
215+
Ok(set.remove(value.inner()).into())
216216
}
217217

218218
/// `WeakSet.prototype.has( value )`

core/gc/src/internals/weak_map_box.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub(crate) trait ErasedWeakMapBox {
1717
unsafe fn trace(&self, tracer: &mut Tracer);
1818
}
1919

20-
impl<K: Trace + ?Sized, V: Trace + Clone> ErasedWeakMapBox for WeakMapBox<K, V> {
20+
impl<K: Trace + ?Sized, V: Trace> ErasedWeakMapBox for WeakMapBox<K, V> {
2121
fn clear_dead_entries(&self) {
2222
if let Some(map) = self.map.upgrade()
2323
&& let Ok(mut map) = map.try_borrow_mut()

core/gc/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl Allocator {
164164
})
165165
}
166166

167-
fn alloc_weak_map<K: Trace + ?Sized, V: Trace + Clone>() -> WeakMap<K, V> {
167+
fn alloc_weak_map<K: Trace + ?Sized, V: Trace>() -> WeakMap<K, V> {
168168
let weak_map = WeakMap {
169169
inner: Gc::new(GcRefCell::new(RawWeakMap::new())),
170170
};

core/gc/src/pointers/ephemeron.rs

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,27 @@ use crate::{
55
internals::EphemeronBox,
66
trace::{Finalize, Trace},
77
};
8-
use std::ptr::NonNull;
8+
use std::{ops::Deref, ptr::NonNull};
9+
10+
/// A reference to the value held by an [`Ephemeron`].
11+
///
12+
/// This reference can be thought as a `(Gc<K>, &V)` pair, where the
13+
/// returned `Gc<K>` ensures that the reference `&V` is always valid until
14+
/// the `Gc<K>` gets dropped.
15+
#[derive(Debug)]
16+
pub struct EphemeronValueRef<'a, K: Trace + ?Sized + 'static, V> {
17+
// Only required to maintain the reference `&V` alive.
18+
_key: Gc<K>,
19+
value: &'a V,
20+
}
21+
22+
impl<K: Trace + ?Sized, V> Deref for EphemeronValueRef<'_, K, V> {
23+
type Target = V;
24+
25+
fn deref(&self) -> &Self::Target {
26+
self.value
27+
}
28+
}
929

1030
/// A key-value pair where the value becomes inaccessible when the key is garbage collected.
1131
///
@@ -22,16 +42,12 @@ pub struct Ephemeron<K: Trace + ?Sized + 'static, V: Trace + 'static> {
2242
inner_ptr: NonNull<EphemeronBox<K, V>>,
2343
}
2444

25-
impl<K: Trace + ?Sized, V: Trace + Clone> Ephemeron<K, V> {
26-
/// Gets the stored value of this `Ephemeron`, or `None` if the key was already garbage collected.
27-
///
28-
/// This needs to return a clone of the value because holding a reference to it between
29-
/// garbage collection passes could drop the underlying allocation, causing an Use After Free.
45+
impl<K: Trace + ?Sized, V: Trace> Ephemeron<K, V> {
46+
/// Creates a new `Ephemeron`.
3047
#[must_use]
31-
pub fn value(&self) -> Option<V> {
32-
// SAFETY: this is safe because `Ephemeron` is tracked to always point to a valid pointer
33-
// `inner_ptr`.
34-
unsafe { self.inner_ptr.as_ref().value().cloned() }
48+
pub fn new(key: &Gc<K>, value: V) -> Self {
49+
let inner_ptr = Allocator::alloc_ephemeron(EphemeronBox::new(key, value));
50+
Self { inner_ptr }
3551
}
3652

3753
/// Gets the stored key of this `Ephemeron`, or `None` if the key was already garbage collected.
@@ -51,21 +67,24 @@ impl<K: Trace + ?Sized, V: Trace + Clone> Ephemeron<K, V> {
5167
Some(unsafe { Gc::from_raw(key_ptr) })
5268
}
5369

54-
/// Checks if the [`Ephemeron`] has a value.
70+
/// Gets the stored value of this `Ephemeron`, or `None` if the key was already garbage collected.
5571
#[must_use]
56-
pub fn has_value(&self) -> bool {
72+
pub fn value(&self) -> Option<EphemeronValueRef<'_, K, V>> {
73+
let key = self.key()?;
74+
5775
// SAFETY: this is safe because `Ephemeron` is tracked to always point to a valid pointer
5876
// `inner_ptr`.
59-
unsafe { self.inner_ptr.as_ref().value().is_some() }
77+
let value = unsafe { self.inner_ptr.as_ref().value()? };
78+
79+
Some(EphemeronValueRef { _key: key, value })
6080
}
61-
}
6281

63-
impl<K: Trace + ?Sized, V: Trace> Ephemeron<K, V> {
64-
/// Creates a new `Ephemeron`.
82+
/// Checks if the [`Ephemeron`] has a value.
6583
#[must_use]
66-
pub fn new(key: &Gc<K>, value: V) -> Self {
67-
let inner_ptr = Allocator::alloc_ephemeron(EphemeronBox::new(key, value));
68-
Self { inner_ptr }
84+
pub fn has_value(&self) -> bool {
85+
// SAFETY: this is safe because `Ephemeron` is tracked to always point to a valid pointer
86+
// `inner_ptr`.
87+
unsafe { self.inner_ptr.as_ref().value().is_some() }
6988
}
7089

7190
/// Returns `true` if the two `Ephemeron`s point to the same allocation.

core/gc/src/pointers/weak_map.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use hashbrown::{
66
hash_table::{Entry as RawEntry, Iter as RawIter},
77
};
88

9-
use crate::{Allocator, Ephemeron, Finalize, Gc, GcRefCell, Trace, custom_trace};
9+
use crate::{Allocator, Ephemeron, Finalize, Gc, GcRef, GcRefCell, Trace, custom_trace};
1010
use std::{fmt, hash::BuildHasher, marker::PhantomData};
1111

1212
/// A map that holds weak references to its keys and is traced by the garbage collector.
@@ -21,7 +21,7 @@ unsafe impl<K: Trace + ?Sized + 'static, V: Trace + 'static> Trace for WeakMap<K
2121
});
2222
}
2323

24-
impl<K: Trace + ?Sized, V: Trace + Clone> WeakMap<K, V> {
24+
impl<K: Trace + ?Sized, V: Trace> WeakMap<K, V> {
2525
/// Creates a new `WeakMap`.
2626
#[must_use]
2727
#[inline]
@@ -35,9 +35,10 @@ impl<K: Trace + ?Sized, V: Trace + Clone> WeakMap<K, V> {
3535
self.inner.borrow_mut().insert(key, value);
3636
}
3737

38-
/// Removes a key from the map, returning the value at the key if the key was previously in the map.
38+
/// Removes a key from the map, returning `true` if the key was previously in
39+
/// the map.
3940
#[inline]
40-
pub fn remove(&mut self, key: &Gc<K>) -> Option<V> {
41+
pub fn remove(&mut self, key: &Gc<K>) -> bool {
4142
self.inner.borrow_mut().remove(key)
4243
}
4344

@@ -48,11 +49,11 @@ impl<K: Trace + ?Sized, V: Trace + Clone> WeakMap<K, V> {
4849
self.inner.borrow().contains_key(key)
4950
}
5051

51-
/// Returns a reference to the value corresponding to the key.
52+
/// Returns a reference to the ephemeron corresponding to the key.
5253
#[must_use]
5354
#[inline]
54-
pub fn get(&self, key: &Gc<K>) -> Option<V> {
55-
self.inner.borrow().get(key)
55+
pub fn get<'a>(&'a self, key: &Gc<K>) -> Option<GcRef<'a, Ephemeron<K, V>>> {
56+
GcRef::try_map(self.inner.borrow(), |inner| inner.get(key))
5657
}
5758
}
5859

@@ -222,7 +223,7 @@ where
222223
impl<K, V, S> RawWeakMap<K, V, S>
223224
where
224225
K: Trace + ?Sized + 'static,
225-
V: Trace + Clone + 'static,
226+
V: Trace + 'static,
226227
S: BuildHasher,
227228
{
228229
/// Reserves capacity for at least `additional` more elements to be inserted
@@ -275,14 +276,13 @@ where
275276
.shrink_to(min_capacity, make_hasher::<_, V, S>(&self.hash_builder));
276277
}
277278

278-
/// Returns the value corresponding to the supplied key.
279-
// TODO: make this return a reference instead of cloning.
280-
pub(crate) fn get(&self, k: &Gc<K>) -> Option<V> {
279+
/// Returns the ephemeron corresponding to the supplied key.
280+
pub(crate) fn get(&self, k: &Gc<K>) -> Option<&Ephemeron<K, V>> {
281281
if self.table.is_empty() {
282282
None
283283
} else {
284284
let hash = make_hash_from_gc(&self.hash_builder, k);
285-
self.table.find(hash, equivalent_key(k))?.value()
285+
self.table.find(hash, equivalent_key(k))
286286
}
287287
}
288288

@@ -313,16 +313,16 @@ where
313313
old
314314
}
315315

316-
/// Removes a key from the map, returning the value at the key if the key
316+
/// Removes a key from the map, returning `true` if the key
317317
/// was previously in the map. Keeps the allocated memory for reuse.
318-
pub(crate) fn remove(&mut self, k: &Gc<K>) -> Option<V> {
318+
pub(crate) fn remove(&mut self, k: &Gc<K>) -> bool {
319319
let hash = make_hash_from_gc(&self.hash_builder, k);
320-
self.table
321-
.find_entry(hash, equivalent_key(k))
322-
.ok()?
323-
.remove()
324-
.0
325-
.value()
320+
if let Ok(entry) = self.table.find_entry(hash, equivalent_key(k)) {
321+
entry.remove();
322+
true
323+
} else {
324+
false
325+
}
326326
}
327327

328328
/// Clears all the expired keys in the map.

core/gc/src/test/weak.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ mod miri {
7676
drop(cloned_gc);
7777
force_collect();
7878
assert_eq!(wrap.upgrade().as_deref().map(String::as_str), Some("foo"));
79-
assert_eq!(eph.value(), Some(3));
79+
assert_eq!(&*eph.value().unwrap(), &3);
8080

8181
drop(gc_value);
8282
force_collect();
8383
assert!(wrap.upgrade().is_none());
84-
assert_eq!(eph.value(), Some(3));
84+
assert_eq!(&*eph.value().unwrap(), &3);
8585

8686
drop(wrap);
8787
force_collect();
@@ -99,7 +99,7 @@ mod miri {
9999
let eph = Ephemeron::new(&gc_value, 4);
100100
let _fourth = Gc::new("tail");
101101

102-
assert_eq!(eph.value(), Some(4));
102+
assert_eq!(&*eph.value().unwrap(), &4);
103103
});
104104
}
105105

core/gc/src/test/weak_map.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ mod miri {
126126
assert!(map.contains_key(&key));
127127
assert!(map.contains_key(&key_copy));
128128

129-
assert_eq!(map.remove(&key), Some(()));
129+
assert!(map.remove(&key));
130130

131131
map.insert(&key, ());
132132

0 commit comments

Comments
 (0)