1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "js/Array.h" // JS::GetArrayLength
10 #include "js/GlobalObject.h" // JS_NewGlobalObject
11 #include "js/PropertyAndElement.h" // JS_DefineProperty
12 #include "js/WeakMap.h"
13 #include "jsapi-tests/tests.h"
18 JSObject
* keyDelegate
= nullptr;
20 BEGIN_TEST(testWeakMap_basicOperations
) {
21 JS::RootedObject
map(cx
, JS::NewWeakMapObject(cx
));
22 CHECK(IsWeakMapObject(map
));
24 JS::RootedObject
key(cx
, newKey());
26 CHECK(!IsWeakMapObject(key
));
28 JS::RootedValue
r(cx
);
29 CHECK(GetWeakMapEntry(cx
, map
, key
, &r
));
30 CHECK(r
.isUndefined());
32 CHECK(checkSize(map
, 0));
34 JS::RootedValue
val(cx
, JS::Int32Value(1));
35 CHECK(SetWeakMapEntry(cx
, map
, key
, val
));
37 CHECK(GetWeakMapEntry(cx
, map
, key
, &r
));
39 CHECK(checkSize(map
, 1));
43 CHECK(GetWeakMapEntry(cx
, map
, key
, &r
));
45 CHECK(checkSize(map
, 1));
50 CHECK(checkSize(map
, 0));
55 JSObject
* newKey() { return JS_NewPlainObject(cx
); }
57 bool checkSize(JS::HandleObject map
, uint32_t expected
) {
58 JS::RootedObject
keys(cx
);
59 CHECK(JS_NondeterministicGetWeakMapKeys(cx
, map
, &keys
));
62 CHECK(JS::GetArrayLength(cx
, keys
, &length
));
63 CHECK(length
== expected
);
67 END_TEST(testWeakMap_basicOperations
)
69 BEGIN_TEST(testWeakMap_keyDelegates
) {
70 AutoLeaveZeal
nozeal(cx
);
72 AutoGCParameter
param(cx
, JSGC_INCREMENTAL_GC_ENABLED
, true);
74 JS::RootedObject
map(cx
, JS::NewWeakMapObject(cx
));
77 JS::RootedObject
delegate(cx
, newDelegate());
78 JS::RootedObject
key(cx
, delegate
);
79 if (!JS_WrapObject(cx
, &key
)) {
85 keyDelegate
= delegate
;
87 JS::RootedObject
delegateRoot(cx
);
89 JSAutoRealm
ar(cx
, delegate
);
90 delegateRoot
= JS_NewPlainObject(cx
);
92 JS::RootedValue
delegateValue(cx
, JS::ObjectValue(*delegate
));
93 CHECK(JS_DefineProperty(cx
, delegateRoot
, "delegate", delegateValue
, 0));
98 * Perform an incremental GC, introducing an unmarked CCW to force the map
99 * zone to finish marking before the delegate zone.
101 CHECK(newCCW(map
, delegateRoot
));
102 performIncrementalGC();
104 CHECK(map
->zone()->lastSweepGroupIndex() <
105 delegateRoot
->zone()->lastSweepGroupIndex());
108 /* Add our entry to the weakmap. */
109 JS::RootedValue
val(cx
, JS::Int32Value(1));
110 CHECK(SetWeakMapEntry(cx
, map
, key
, val
));
111 CHECK(checkSize(map
, 1));
114 * Check the delegate keeps the entry alive even if the key is not reachable.
117 CHECK(newCCW(map
, delegateRoot
));
118 performIncrementalGC();
119 CHECK(checkSize(map
, 1));
122 * Check that the zones finished marking at the same time, which is
123 * necessary because of the presence of the delegate and the CCW.
126 CHECK(map
->zone()->lastSweepGroupIndex() ==
127 delegateRoot
->zone()->lastSweepGroupIndex());
130 /* Check that when the delegate becomes unreachable the entry is removed. */
131 delegateRoot
= nullptr;
132 keyDelegate
= nullptr;
134 CHECK(checkSize(map
, 0));
139 static size_t DelegateObjectMoved(JSObject
* obj
, JSObject
* old
) {
141 return 0; // Object got moved before we set keyDelegate to point to it.
144 MOZ_RELEASE_ASSERT(keyDelegate
== old
);
150 static const JSClass keyClass
= {
151 "keyWithDelegate", JSCLASS_HAS_RESERVED_SLOTS(1),
152 JS_NULL_CLASS_OPS
, JS_NULL_CLASS_SPEC
,
153 JS_NULL_CLASS_EXT
, JS_NULL_OBJECT_OPS
};
155 JS::RootedObject
key(cx
, JS_NewObject(cx
, &keyClass
));
163 JSObject
* newCCW(JS::HandleObject sourceZone
, JS::HandleObject destZone
) {
165 * Now ensure that this zone will be swept first by adding a cross
166 * compartment wrapper to a new object in the same zone as the
169 JS::RootedObject
object(cx
);
171 JSAutoRealm
ar(cx
, destZone
);
172 object
= JS_NewPlainObject(cx
);
178 JSAutoRealm
ar(cx
, sourceZone
);
179 if (!JS_WrapObject(cx
, &object
)) {
184 // In order to test the SCC algorithm, we need the wrapper/wrappee to be
186 cx
->runtime()->gc
.evictNursery();
191 JSObject
* newDelegate() {
192 static const JSClassOps delegateClassOps
= {
193 nullptr, // addProperty
194 nullptr, // delProperty
195 nullptr, // enumerate
196 nullptr, // newEnumerate
198 nullptr, // mayResolve
201 nullptr, // construct
202 JS_GlobalObjectTraceHook
, // trace
205 static const js::ClassExtension delegateClassExtension
= {
206 DelegateObjectMoved
, // objectMovedOp
209 static const JSClass delegateClass
= {
211 JSCLASS_GLOBAL_FLAGS
| JSCLASS_HAS_RESERVED_SLOTS(1),
214 &delegateClassExtension
,
217 /* Create the global object. */
218 JS::RealmOptions options
;
219 JS::RootedObject
global(cx
,
220 JS_NewGlobalObject(cx
, &delegateClass
, nullptr,
221 JS::FireOnNewGlobalHook
, options
));
226 JS_SetReservedSlot(global
, 0, JS::Int32Value(42));
230 bool checkSize(JS::HandleObject map
, uint32_t expected
) {
231 JS::RootedObject
keys(cx
);
232 CHECK(JS_NondeterministicGetWeakMapKeys(cx
, map
, &keys
));
235 CHECK(JS::GetArrayLength(cx
, keys
, &length
));
236 CHECK(length
== expected
);
241 void performIncrementalGC() {
242 JSRuntime
* rt
= cx
->runtime();
243 js::SliceBudget
budget(js::WorkBudget(1000));
244 rt
->gc
.startDebugGC(JS::GCOptions::Normal
, budget
);
246 // Wait until we've started marking before finishing the GC
247 // non-incrementally.
248 while (rt
->gc
.state() == gc::State::Prepare
) {
249 rt
->gc
.debugGCSlice(budget
);
251 if (JS::IsIncrementalGCInProgress(cx
)) {
252 rt
->gc
.finishGC(JS::GCReason::DEBUG_GC
);
255 END_TEST(testWeakMap_keyDelegates
)