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:
3 */
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/. */
7
8 #include "gc/Zone.h"
9 #include "js/Array.h" // JS::GetArrayLength
10 #include "jsapi-tests/tests.h"
11 #include "vm/Realm.h"
12
13 using namespace js;
14
15 JSObject* keyDelegate = nullptr;
16
BEGIN_TEST(testWeakMap_basicOperations)17 BEGIN_TEST(testWeakMap_basicOperations) {
18 JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
19 CHECK(IsWeakMapObject(map));
20
21 JS::RootedObject key(cx, newKey());
22 CHECK(key);
23 CHECK(!IsWeakMapObject(key));
24
25 JS::RootedValue r(cx);
26 CHECK(GetWeakMapEntry(cx, map, key, &r));
27 CHECK(r.isUndefined());
28
29 CHECK(checkSize(map, 0));
30
31 JS::RootedValue val(cx, JS::Int32Value(1));
32 CHECK(SetWeakMapEntry(cx, map, key, val));
33
34 CHECK(GetWeakMapEntry(cx, map, key, &r));
35 CHECK(r == val);
36 CHECK(checkSize(map, 1));
37
38 JS_GC(cx);
39
40 CHECK(GetWeakMapEntry(cx, map, key, &r));
41 CHECK(r == val);
42 CHECK(checkSize(map, 1));
43
44 key = nullptr;
45 JS_GC(cx);
46
47 CHECK(checkSize(map, 0));
48
49 return true;
50 }
51
newKey()52 JSObject* newKey() { return JS_NewPlainObject(cx); }
53
checkSize(JS::HandleObject map,uint32_t expected)54 bool checkSize(JS::HandleObject map, uint32_t expected) {
55 JS::RootedObject keys(cx);
56 CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
57
58 uint32_t length;
59 CHECK(JS::GetArrayLength(cx, keys, &length));
60 CHECK(length == expected);
61
62 return true;
63 }
64 END_TEST(testWeakMap_basicOperations)
65
BEGIN_TEST(testWeakMap_keyDelegates)66 BEGIN_TEST(testWeakMap_keyDelegates) {
67 AutoLeaveZeal nozeal(cx);
68
69 AutoGCParameter param(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
70 JS_GC(cx);
71 JS::RootedObject map(cx, JS::NewWeakMapObject(cx));
72 CHECK(map);
73
74 JS::RootedObject delegate(cx, newDelegate());
75 JS::RootedObject key(cx, delegate);
76 if (!JS_WrapObject(cx, &key)) {
77 return false;
78 }
79 CHECK(key);
80 CHECK(delegate);
81
82 keyDelegate = delegate;
83
84 JS::RootedObject delegateRoot(cx);
85 {
86 JSAutoRealm ar(cx, delegate);
87 delegateRoot = JS_NewPlainObject(cx);
88 CHECK(delegateRoot);
89 JS::RootedValue delegateValue(cx, JS::ObjectValue(*delegate));
90 CHECK(JS_DefineProperty(cx, delegateRoot, "delegate", delegateValue, 0));
91 }
92 delegate = nullptr;
93
94 /*
95 * Perform an incremental GC, introducing an unmarked CCW to force the map
96 * zone to finish marking before the delegate zone.
97 */
98 CHECK(newCCW(map, delegateRoot));
99 performIncrementalGC();
100 #ifdef DEBUG
101 CHECK(map->zone()->lastSweepGroupIndex() <
102 delegateRoot->zone()->lastSweepGroupIndex());
103 #endif
104
105 /* Add our entry to the weakmap. */
106 JS::RootedValue val(cx, JS::Int32Value(1));
107 CHECK(SetWeakMapEntry(cx, map, key, val));
108 CHECK(checkSize(map, 1));
109
110 /*
111 * Check the delegate keeps the entry alive even if the key is not reachable.
112 */
113 key = nullptr;
114 CHECK(newCCW(map, delegateRoot));
115 performIncrementalGC();
116 CHECK(checkSize(map, 1));
117
118 /*
119 * Check that the zones finished marking at the same time, which is
120 * necessary because of the presence of the delegate and the CCW.
121 */
122 #ifdef DEBUG
123 CHECK(map->zone()->lastSweepGroupIndex() ==
124 delegateRoot->zone()->lastSweepGroupIndex());
125 #endif
126
127 /* Check that when the delegate becomes unreachable the entry is removed. */
128 delegateRoot = nullptr;
129 keyDelegate = nullptr;
130 JS_GC(cx);
131 CHECK(checkSize(map, 0));
132
133 return true;
134 }
135
DelegateObjectMoved(JSObject * obj,JSObject * old)136 static size_t DelegateObjectMoved(JSObject* obj, JSObject* old) {
137 if (!keyDelegate) {
138 return 0; // Object got moved before we set keyDelegate to point to it.
139 }
140
141 MOZ_RELEASE_ASSERT(keyDelegate == old);
142 keyDelegate = obj;
143 return 0;
144 }
145
newKey()146 JSObject* newKey() {
147 static const JSClass keyClass = {
148 "keyWithDelegate", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1),
149 JS_NULL_CLASS_OPS, JS_NULL_CLASS_SPEC,
150 JS_NULL_CLASS_EXT, JS_NULL_OBJECT_OPS};
151
152 JS::RootedObject key(cx, JS_NewObject(cx, &keyClass));
153 if (!key) {
154 return nullptr;
155 }
156
157 return key;
158 }
159
newCCW(JS::HandleObject sourceZone,JS::HandleObject destZone)160 JSObject* newCCW(JS::HandleObject sourceZone, JS::HandleObject destZone) {
161 /*
162 * Now ensure that this zone will be swept first by adding a cross
163 * compartment wrapper to a new object in the same zone as the
164 * delegate object.
165 */
166 JS::RootedObject object(cx);
167 {
168 JSAutoRealm ar(cx, destZone);
169 object = JS_NewPlainObject(cx);
170 if (!object) {
171 return nullptr;
172 }
173 }
174 {
175 JSAutoRealm ar(cx, sourceZone);
176 if (!JS_WrapObject(cx, &object)) {
177 return nullptr;
178 }
179 }
180
181 // In order to test the SCC algorithm, we need the wrapper/wrappee to be
182 // tenured.
183 cx->runtime()->gc.evictNursery();
184
185 return object;
186 }
187
newDelegate()188 JSObject* newDelegate() {
189 static const JSClassOps delegateClassOps = {
190 nullptr, // addProperty
191 nullptr, // delProperty
192 nullptr, // enumerate
193 nullptr, // newEnumerate
194 nullptr, // resolve
195 nullptr, // mayResolve
196 nullptr, // finalize
197 nullptr, // call
198 nullptr, // hasInstance
199 nullptr, // construct
200 JS_GlobalObjectTraceHook, // trace
201 };
202
203 static const js::ClassExtension delegateClassExtension = {
204 DelegateObjectMoved, // objectMovedOp
205 };
206
207 static const JSClass delegateClass = {
208 "delegate",
209 JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_RESERVED_SLOTS(1),
210 &delegateClassOps,
211 JS_NULL_CLASS_SPEC,
212 &delegateClassExtension,
213 JS_NULL_OBJECT_OPS};
214
215 /* Create the global object. */
216 JS::RealmOptions options;
217 JS::RootedObject global(cx,
218 JS_NewGlobalObject(cx, &delegateClass, nullptr,
219 JS::FireOnNewGlobalHook, options));
220 if (!global) {
221 return nullptr;
222 }
223
224 JS_SetReservedSlot(global, 0, JS::Int32Value(42));
225 return global;
226 }
227
checkSize(JS::HandleObject map,uint32_t expected)228 bool checkSize(JS::HandleObject map, uint32_t expected) {
229 JS::RootedObject keys(cx);
230 CHECK(JS_NondeterministicGetWeakMapKeys(cx, map, &keys));
231
232 uint32_t length;
233 CHECK(JS::GetArrayLength(cx, keys, &length));
234 CHECK(length == expected);
235
236 return true;
237 }
238
performIncrementalGC()239 void performIncrementalGC() {
240 JSRuntime* rt = cx->runtime();
241 js::SliceBudget budget(js::WorkBudget(1000));
242 rt->gc.startDebugGC(JS::GCOptions::Normal, budget);
243
244 // Wait until we've started marking before finishing the GC
245 // non-incrementally.
246 while (rt->gc.state() == gc::State::Prepare) {
247 rt->gc.debugGCSlice(budget);
248 }
249 if (JS::IsIncrementalGCInProgress(cx)) {
250 rt->gc.finishGC(JS::GCReason::DEBUG_GC);
251 }
252 }
253 END_TEST(testWeakMap_keyDelegates)
254