Skip to content

Instantly share code, notes, and snippets.

@hotsphink
Last active January 28, 2025 21:54
Show Gist options
  • Save hotsphink/90c77af0775ed2a998f2f07996f7fa2d to your computer and use it in GitHub Desktop.
Save hotsphink/90c77af0775ed2a998f2f07996f7fa2d to your computer and use it in GitHub Desktop.
D231442 with added tests
Commit ID: a7ee783e48150681ac62b8e89ca95ccf290f2cf3
Change ID: zvsrxnskvqpvzqwnswzkzuxvkprwzlmq
Author: Steve Fink <[email protected]> (2025-01-27 11:05:12)
Committer: Steve Fink <[email protected]> (2025-01-28 13:54:04)
Bug 1935738 - Implement GCPolicy<Heap<T>>::needsSweep.
Without a needsSweep() implementation for JS::GCPolicy<JS::Heap<T>>,
embedders cannot put heap pointers inside containers like JS::WeakCache.
(Firefox is not affected by this because it uses HeapPtr instead, which
is not available to embedders.)
diff --git a/js/public/GCPolicyAPI.h b/js/public/GCPolicyAPI.h
index e805939332..549e26330e 100644
--- a/js/public/GCPolicyAPI.h
+++ b/js/public/GCPolicyAPI.h
@@ -165,6 +165,10 @@
static bool traceWeak(JSTracer* trc, JS::Heap<T>* thingp) {
return !*thingp || js::gc::TraceWeakEdge(trc, thingp);
}
+ static bool needsSweep(JSTracer* trc, const JS::Heap<T>* thingp) {
+ JS::Heap<T> thing{*thingp};
+ return thing && !js::gc::TraceWeakEdge(trc, &thing);
+ }
};
// GCPolicy<UniquePtr<T>> forwards the contained pointer to GCPolicy<T>.
diff --git a/js/src/jsapi-tests/testGCWeakCache.cpp b/js/src/jsapi-tests/testGCWeakCache.cpp
index 332692e41e..baae3a91be 100644
--- a/js/src/jsapi-tests/testGCWeakCache.cpp
+++ b/js/src/jsapi-tests/testGCWeakCache.cpp
@@ -125,6 +125,82 @@
}
END_TEST(testWeakCacheMapWithUniquePtr)
+// Exercise WeakCache<GCHashSet> using Heap<T>.
+BEGIN_TEST(testWeakCacheSetWithJSHeap) {
+ // Create two objects tenured and two in the nursery. If zeal is on,
+ // this may fail and we'll get more tenured objects. That's fine:
+ // the test will continue to work, it will just not test as much.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectSet =
+ GCHashSet<JS::Heap<JSObject*>, js::StableCellHasher<JS::Heap<JSObject*>>,
+ SystemAllocPolicy>;
+ using Cache = JS::WeakCache<ObjectSet>;
+ Cache cache(JS::GetObjectZone(tenured1));
+
+ cache.put(tenured1);
+ cache.put(tenured2);
+ cache.put(nursery1);
+ cache.put(nursery2);
+
+ // Verify relocation and that we don't sweep too aggressively.
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(tenured2));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.has(nursery2));
+
+ // Unroot two entries and verify that they get removed.
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.count() == 2);
+
+ return true;
+}
+END_TEST(testWeakCacheSetWithJSHeap)
+
+// Exercise WeakCache<GCHashMap> using Heap<T> keys and values.
+BEGIN_TEST(testWeakCacheMapWithJSHeap) {
+ // Create two objects tenured and two in the nursery. If zeal is on,
+ // this may fail and we'll get more tenured objects. That's fine:
+ // the test will continue to work, it will just not test as much.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectMap = js::GCHashMap<JS::Heap<JSObject*>, JS::Heap<JSObject*>,
+ js::StableCellHasher<JS::Heap<JSObject*>>>;
+ using Cache = JS::WeakCache<ObjectMap>;
+ Cache cache(JS::GetObjectZone(tenured1), cx);
+
+ cache.put(tenured1, tenured1);
+ cache.put(tenured2, tenured2);
+ cache.put(nursery1, nursery2); // These do not map to themselves.
+ cache.put(nursery2, nursery1);
+
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.has(tenured2));
+ CHECK(cache.has(nursery1));
+ CHECK(cache.has(nursery2));
+
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.has(tenured1));
+ CHECK(cache.count() == 1); // nursery1 is gone because value is dead.
+
+ return true;
+}
+END_TEST(testWeakCacheMapWithJSHeap)
+
// Exercise WeakCache<GCVector>.
BEGIN_TEST(testWeakCacheGCVector) {
// Create two objects tenured and two in the nursery. If zeal is on,
@@ -161,6 +237,44 @@
}
END_TEST(testWeakCacheGCVector)
+// Exercise WeakCache<GCVector> using Heap<T> elements.
+BEGIN_TEST(testWeakCacheGCVectorWithJSHeap) {
+ // Create two objects tenured and two in the nursery.
+ JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
+ JS_GC(cx);
+ JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
+ JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
+
+ using ObjectVector = JS::WeakCache<GCVector<JS::Heap<JSObject*>>>;
+ ObjectVector cache(JS::GetObjectZone(tenured1), cx);
+
+ CHECK(cache.append(tenured1));
+ CHECK(cache.append(tenured2));
+ CHECK(cache.append(nursery1));
+ CHECK(cache.append(nursery2));
+ CHECK(cache.append(nullptr));
+ CHECK(cache.get().length() == 5);
+
+ JS_GC(cx);
+ CHECK(cache.get().length() == 5);
+ CHECK(cache.get()[0] == tenured1);
+ CHECK(cache.get()[1] == tenured2);
+ CHECK(cache.get()[2] == nursery1);
+ CHECK(cache.get()[3] == nursery2);
+ CHECK(cache.get()[4] == nullptr);
+
+ tenured2 = nursery2 = nullptr;
+ JS_GC(cx);
+ CHECK(cache.get().length() == 3);
+ CHECK(cache.get()[0] == tenured1);
+ CHECK(cache.get()[1] == nursery1);
+ CHECK(cache.get()[2] == nullptr);
+
+ return true;
+}
+END_TEST(testWeakCacheGCVectorWithJSHeap)
+
#ifdef JS_GC_ZEAL
// A simple structure that embeds an object pointer. We cripple the hash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment