|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "jsweakmap.h" |
|
8 |
|
9 #include <string.h> |
|
10 |
|
11 #include "jsapi.h" |
|
12 #include "jscntxt.h" |
|
13 #include "jsfriendapi.h" |
|
14 #include "jsobj.h" |
|
15 #include "jswrapper.h" |
|
16 |
|
17 #include "vm/GlobalObject.h" |
|
18 #include "vm/WeakMapObject.h" |
|
19 |
|
20 #include "jsobjinlines.h" |
|
21 |
|
22 using namespace js; |
|
23 |
|
24 WeakMapBase::WeakMapBase(JSObject *memOf, JSCompartment *c) |
|
25 : memberOf(memOf), |
|
26 compartment(c), |
|
27 next(WeakMapNotInList) |
|
28 { |
|
29 JS_ASSERT_IF(memberOf, memberOf->compartment() == c); |
|
30 } |
|
31 |
|
32 WeakMapBase::~WeakMapBase() |
|
33 { |
|
34 JS_ASSERT(next == WeakMapNotInList); |
|
35 } |
|
36 |
|
37 bool |
|
38 WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer) |
|
39 { |
|
40 bool markedAny = false; |
|
41 for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) { |
|
42 if (m->markIteratively(tracer)) |
|
43 markedAny = true; |
|
44 } |
|
45 return markedAny; |
|
46 } |
|
47 |
|
48 void |
|
49 WeakMapBase::sweepCompartment(JSCompartment *c) |
|
50 { |
|
51 for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) |
|
52 m->sweep(); |
|
53 } |
|
54 |
|
55 void |
|
56 WeakMapBase::traceAllMappings(WeakMapTracer *tracer) |
|
57 { |
|
58 JSRuntime *rt = tracer->runtime; |
|
59 for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { |
|
60 for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) |
|
61 m->traceMappings(tracer); |
|
62 } |
|
63 } |
|
64 |
|
65 void |
|
66 WeakMapBase::resetCompartmentWeakMapList(JSCompartment *c) |
|
67 { |
|
68 JS_ASSERT(WeakMapNotInList != nullptr); |
|
69 |
|
70 WeakMapBase *m = c->gcWeakMapList; |
|
71 c->gcWeakMapList = nullptr; |
|
72 while (m) { |
|
73 WeakMapBase *n = m->next; |
|
74 m->next = WeakMapNotInList; |
|
75 m = n; |
|
76 } |
|
77 } |
|
78 |
|
79 bool |
|
80 WeakMapBase::saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector) |
|
81 { |
|
82 WeakMapBase *m = c->gcWeakMapList; |
|
83 while (m) { |
|
84 if (!vector.append(m)) |
|
85 return false; |
|
86 m = m->next; |
|
87 } |
|
88 return true; |
|
89 } |
|
90 |
|
91 void |
|
92 WeakMapBase::restoreCompartmentWeakMapLists(WeakMapVector &vector) |
|
93 { |
|
94 for (WeakMapBase **p = vector.begin(); p != vector.end(); p++) { |
|
95 WeakMapBase *m = *p; |
|
96 JS_ASSERT(m->next == WeakMapNotInList); |
|
97 JSCompartment *c = m->compartment; |
|
98 m->next = c->gcWeakMapList; |
|
99 c->gcWeakMapList = m; |
|
100 } |
|
101 } |
|
102 |
|
103 void |
|
104 WeakMapBase::removeWeakMapFromList(WeakMapBase *weakmap) |
|
105 { |
|
106 JSCompartment *c = weakmap->compartment; |
|
107 for (WeakMapBase **p = &c->gcWeakMapList; *p; p = &(*p)->next) { |
|
108 if (*p == weakmap) { |
|
109 *p = (*p)->next; |
|
110 weakmap->next = WeakMapNotInList; |
|
111 break; |
|
112 } |
|
113 } |
|
114 } |
|
115 |
|
116 static JSObject * |
|
117 GetKeyArg(JSContext *cx, CallArgs &args) |
|
118 { |
|
119 if (args[0].isPrimitive()) { |
|
120 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); |
|
121 return nullptr; |
|
122 } |
|
123 return &args[0].toObject(); |
|
124 } |
|
125 |
|
126 MOZ_ALWAYS_INLINE bool |
|
127 IsWeakMap(HandleValue v) |
|
128 { |
|
129 return v.isObject() && v.toObject().is<WeakMapObject>(); |
|
130 } |
|
131 |
|
132 MOZ_ALWAYS_INLINE bool |
|
133 WeakMap_has_impl(JSContext *cx, CallArgs args) |
|
134 { |
|
135 JS_ASSERT(IsWeakMap(args.thisv())); |
|
136 |
|
137 if (args.length() < 1) { |
|
138 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
139 "WeakMap.has", "0", "s"); |
|
140 return false; |
|
141 } |
|
142 JSObject *key = GetKeyArg(cx, args); |
|
143 if (!key) |
|
144 return false; |
|
145 |
|
146 if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) { |
|
147 if (map->has(key)) { |
|
148 args.rval().setBoolean(true); |
|
149 return true; |
|
150 } |
|
151 } |
|
152 |
|
153 args.rval().setBoolean(false); |
|
154 return true; |
|
155 } |
|
156 |
|
157 static bool |
|
158 WeakMap_has(JSContext *cx, unsigned argc, Value *vp) |
|
159 { |
|
160 CallArgs args = CallArgsFromVp(argc, vp); |
|
161 return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args); |
|
162 } |
|
163 |
|
164 MOZ_ALWAYS_INLINE bool |
|
165 WeakMap_clear_impl(JSContext *cx, CallArgs args) |
|
166 { |
|
167 JS_ASSERT(IsWeakMap(args.thisv())); |
|
168 |
|
169 // We can't js_delete the weakmap because the data gathered during GC |
|
170 // is used by the Cycle Collector |
|
171 if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) |
|
172 map->clear(); |
|
173 |
|
174 args.rval().setUndefined(); |
|
175 return true; |
|
176 } |
|
177 |
|
178 static bool |
|
179 WeakMap_clear(JSContext *cx, unsigned argc, Value *vp) |
|
180 { |
|
181 CallArgs args = CallArgsFromVp(argc, vp); |
|
182 return CallNonGenericMethod<IsWeakMap, WeakMap_clear_impl>(cx, args); |
|
183 } |
|
184 |
|
185 MOZ_ALWAYS_INLINE bool |
|
186 WeakMap_get_impl(JSContext *cx, CallArgs args) |
|
187 { |
|
188 JS_ASSERT(IsWeakMap(args.thisv())); |
|
189 |
|
190 if (args.length() < 1) { |
|
191 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
192 "WeakMap.get", "0", "s"); |
|
193 return false; |
|
194 } |
|
195 JSObject *key = GetKeyArg(cx, args); |
|
196 if (!key) |
|
197 return false; |
|
198 |
|
199 if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) { |
|
200 if (ObjectValueMap::Ptr ptr = map->lookup(key)) { |
|
201 // Read barrier to prevent an incorrectly gray value from escaping the |
|
202 // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp |
|
203 ExposeValueToActiveJS(ptr->value().get()); |
|
204 |
|
205 args.rval().set(ptr->value()); |
|
206 return true; |
|
207 } |
|
208 } |
|
209 |
|
210 args.rval().set((args.length() > 1) ? args[1] : UndefinedValue()); |
|
211 return true; |
|
212 } |
|
213 |
|
214 static bool |
|
215 WeakMap_get(JSContext *cx, unsigned argc, Value *vp) |
|
216 { |
|
217 CallArgs args = CallArgsFromVp(argc, vp); |
|
218 return CallNonGenericMethod<IsWeakMap, WeakMap_get_impl>(cx, args); |
|
219 } |
|
220 |
|
221 MOZ_ALWAYS_INLINE bool |
|
222 WeakMap_delete_impl(JSContext *cx, CallArgs args) |
|
223 { |
|
224 JS_ASSERT(IsWeakMap(args.thisv())); |
|
225 |
|
226 if (args.length() < 1) { |
|
227 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
228 "WeakMap.delete", "0", "s"); |
|
229 return false; |
|
230 } |
|
231 JSObject *key = GetKeyArg(cx, args); |
|
232 if (!key) |
|
233 return false; |
|
234 |
|
235 if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) { |
|
236 if (ObjectValueMap::Ptr ptr = map->lookup(key)) { |
|
237 map->remove(ptr); |
|
238 args.rval().setBoolean(true); |
|
239 return true; |
|
240 } |
|
241 } |
|
242 |
|
243 args.rval().setBoolean(false); |
|
244 return true; |
|
245 } |
|
246 |
|
247 static bool |
|
248 WeakMap_delete(JSContext *cx, unsigned argc, Value *vp) |
|
249 { |
|
250 CallArgs args = CallArgsFromVp(argc, vp); |
|
251 return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args); |
|
252 } |
|
253 |
|
254 static bool |
|
255 TryPreserveReflector(JSContext *cx, HandleObject obj) |
|
256 { |
|
257 if (obj->getClass()->ext.isWrappedNative || |
|
258 (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) || |
|
259 (obj->is<ProxyObject>() && |
|
260 obj->as<ProxyObject>().handler()->family() == GetDOMProxyHandlerFamily())) |
|
261 { |
|
262 JS_ASSERT(cx->runtime()->preserveWrapperCallback); |
|
263 if (!cx->runtime()->preserveWrapperCallback(cx, obj)) { |
|
264 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY); |
|
265 return false; |
|
266 } |
|
267 } |
|
268 return true; |
|
269 } |
|
270 |
|
271 static inline void |
|
272 WeakMapPostWriteBarrier(JSRuntime *rt, ObjectValueMap *weakMap, JSObject *key) |
|
273 { |
|
274 #ifdef JSGC_GENERATIONAL |
|
275 /* |
|
276 * Strip the barriers from the type before inserting into the store buffer. |
|
277 * This will automatically ensure that barriers do not fire during GC. |
|
278 * |
|
279 * Some compilers complain about instantiating the WeakMap class for |
|
280 * unbarriered type arguments, so we cast to a HashMap instead. Because of |
|
281 * WeakMap's multiple inheritace, We need to do this in two stages, first to |
|
282 * the HashMap base class and then to the unbarriered version. |
|
283 */ |
|
284 ObjectValueMap::Base *baseHashMap = static_cast<ObjectValueMap::Base *>(weakMap); |
|
285 |
|
286 typedef HashMap<JSObject *, Value> UnbarrieredMap; |
|
287 UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap); |
|
288 |
|
289 typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> Ref; |
|
290 if (key && IsInsideNursery(rt, key)) |
|
291 rt->gcStoreBuffer.putGeneric(Ref((unbarrieredMap), key)); |
|
292 #endif |
|
293 } |
|
294 |
|
295 MOZ_ALWAYS_INLINE bool |
|
296 SetWeakMapEntryInternal(JSContext *cx, Handle<WeakMapObject*> mapObj, |
|
297 HandleObject key, HandleValue value) |
|
298 { |
|
299 ObjectValueMap *map = mapObj->getMap(); |
|
300 if (!map) { |
|
301 map = cx->new_<ObjectValueMap>(cx, mapObj.get()); |
|
302 if (!map) |
|
303 return false; |
|
304 if (!map->init()) { |
|
305 js_delete(map); |
|
306 JS_ReportOutOfMemory(cx); |
|
307 return false; |
|
308 } |
|
309 mapObj->setPrivate(map); |
|
310 } |
|
311 |
|
312 // Preserve wrapped native keys to prevent wrapper optimization. |
|
313 if (!TryPreserveReflector(cx, key)) |
|
314 return false; |
|
315 |
|
316 if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) { |
|
317 RootedObject delegate(cx, op(key)); |
|
318 if (delegate && !TryPreserveReflector(cx, delegate)) |
|
319 return false; |
|
320 } |
|
321 |
|
322 JS_ASSERT(key->compartment() == mapObj->compartment()); |
|
323 JS_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment()); |
|
324 if (!map->put(key, value)) { |
|
325 JS_ReportOutOfMemory(cx); |
|
326 return false; |
|
327 } |
|
328 WeakMapPostWriteBarrier(cx->runtime(), map, key.get()); |
|
329 return true; |
|
330 } |
|
331 |
|
332 MOZ_ALWAYS_INLINE bool |
|
333 WeakMap_set_impl(JSContext *cx, CallArgs args) |
|
334 { |
|
335 JS_ASSERT(IsWeakMap(args.thisv())); |
|
336 |
|
337 if (args.length() < 1) { |
|
338 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, |
|
339 "WeakMap.set", "0", "s"); |
|
340 return false; |
|
341 } |
|
342 RootedObject key(cx, GetKeyArg(cx, args)); |
|
343 if (!key) |
|
344 return false; |
|
345 |
|
346 RootedValue value(cx, (args.length() > 1) ? args[1] : UndefinedValue()); |
|
347 Rooted<JSObject*> thisObj(cx, &args.thisv().toObject()); |
|
348 Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>()); |
|
349 |
|
350 args.rval().setUndefined(); |
|
351 return SetWeakMapEntryInternal(cx, map, key, value); |
|
352 } |
|
353 |
|
354 static bool |
|
355 WeakMap_set(JSContext *cx, unsigned argc, Value *vp) |
|
356 { |
|
357 CallArgs args = CallArgsFromVp(argc, vp); |
|
358 return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args); |
|
359 } |
|
360 |
|
361 JS_FRIEND_API(bool) |
|
362 JS_NondeterministicGetWeakMapKeys(JSContext *cx, HandleObject objArg, MutableHandleObject ret) |
|
363 { |
|
364 RootedObject obj(cx, objArg); |
|
365 obj = UncheckedUnwrap(obj); |
|
366 if (!obj || !obj->is<WeakMapObject>()) { |
|
367 ret.set(nullptr); |
|
368 return true; |
|
369 } |
|
370 RootedObject arr(cx, NewDenseEmptyArray(cx)); |
|
371 if (!arr) |
|
372 return false; |
|
373 ObjectValueMap *map = obj->as<WeakMapObject>().getMap(); |
|
374 if (map) { |
|
375 // Prevent GC from mutating the weakmap while iterating. |
|
376 gc::AutoSuppressGC suppress(cx); |
|
377 for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) { |
|
378 RootedObject key(cx, r.front().key()); |
|
379 if (!cx->compartment()->wrap(cx, &key)) |
|
380 return false; |
|
381 if (!NewbornArrayPush(cx, arr, ObjectValue(*key))) |
|
382 return false; |
|
383 } |
|
384 } |
|
385 ret.set(arr); |
|
386 return true; |
|
387 } |
|
388 |
|
389 static void |
|
390 WeakMap_mark(JSTracer *trc, JSObject *obj) |
|
391 { |
|
392 if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap()) |
|
393 map->trace(trc); |
|
394 } |
|
395 |
|
396 static void |
|
397 WeakMap_finalize(FreeOp *fop, JSObject *obj) |
|
398 { |
|
399 if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap()) { |
|
400 map->check(); |
|
401 #ifdef DEBUG |
|
402 map->~ObjectValueMap(); |
|
403 memset(static_cast<void *>(map), 0xdc, sizeof(*map)); |
|
404 fop->free_(map); |
|
405 #else |
|
406 fop->delete_(map); |
|
407 #endif |
|
408 } |
|
409 } |
|
410 |
|
411 JS_PUBLIC_API(JSObject*) |
|
412 JS::NewWeakMapObject(JSContext *cx) |
|
413 { |
|
414 return NewBuiltinClassInstance(cx, &WeakMapObject::class_); |
|
415 } |
|
416 |
|
417 JS_PUBLIC_API(bool) |
|
418 JS::IsWeakMapObject(JSObject *obj) |
|
419 { |
|
420 return obj->is<WeakMapObject>(); |
|
421 } |
|
422 |
|
423 JS_PUBLIC_API(bool) |
|
424 JS::GetWeakMapEntry(JSContext *cx, HandleObject mapObj, HandleObject key, |
|
425 MutableHandleValue rval) |
|
426 { |
|
427 CHECK_REQUEST(cx); |
|
428 assertSameCompartment(cx, key); |
|
429 rval.setUndefined(); |
|
430 ObjectValueMap *map = mapObj->as<WeakMapObject>().getMap(); |
|
431 if (!map) |
|
432 return true; |
|
433 if (ObjectValueMap::Ptr ptr = map->lookup(key)) { |
|
434 // Read barrier to prevent an incorrectly gray value from escaping the |
|
435 // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp |
|
436 ExposeValueToActiveJS(ptr->value().get()); |
|
437 rval.set(ptr->value()); |
|
438 } |
|
439 return true; |
|
440 } |
|
441 |
|
442 JS_PUBLIC_API(bool) |
|
443 JS::SetWeakMapEntry(JSContext *cx, HandleObject mapObj, HandleObject key, |
|
444 HandleValue val) |
|
445 { |
|
446 CHECK_REQUEST(cx); |
|
447 assertSameCompartment(cx, key, val); |
|
448 Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>()); |
|
449 return SetWeakMapEntryInternal(cx, rootedMap, key, val); |
|
450 } |
|
451 |
|
452 static bool |
|
453 WeakMap_construct(JSContext *cx, unsigned argc, Value *vp) |
|
454 { |
|
455 CallArgs args = CallArgsFromVp(argc, vp); |
|
456 JSObject *obj = NewBuiltinClassInstance(cx, &WeakMapObject::class_); |
|
457 if (!obj) |
|
458 return false; |
|
459 |
|
460 args.rval().setObject(*obj); |
|
461 return true; |
|
462 } |
|
463 |
|
464 const Class WeakMapObject::class_ = { |
|
465 "WeakMap", |
|
466 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | |
|
467 JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap), |
|
468 JS_PropertyStub, /* addProperty */ |
|
469 JS_DeletePropertyStub, /* delProperty */ |
|
470 JS_PropertyStub, /* getProperty */ |
|
471 JS_StrictPropertyStub, /* setProperty */ |
|
472 JS_EnumerateStub, |
|
473 JS_ResolveStub, |
|
474 JS_ConvertStub, |
|
475 WeakMap_finalize, |
|
476 nullptr, /* call */ |
|
477 nullptr, /* construct */ |
|
478 nullptr, /* xdrObject */ |
|
479 WeakMap_mark |
|
480 }; |
|
481 |
|
482 static const JSFunctionSpec weak_map_methods[] = { |
|
483 JS_FN("has", WeakMap_has, 1, 0), |
|
484 JS_FN("get", WeakMap_get, 2, 0), |
|
485 JS_FN("delete", WeakMap_delete, 1, 0), |
|
486 JS_FN("set", WeakMap_set, 2, 0), |
|
487 JS_FN("clear", WeakMap_clear, 0, 0), |
|
488 JS_FS_END |
|
489 }; |
|
490 |
|
491 JSObject * |
|
492 js_InitWeakMapClass(JSContext *cx, HandleObject obj) |
|
493 { |
|
494 JS_ASSERT(obj->isNative()); |
|
495 |
|
496 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); |
|
497 |
|
498 RootedObject weakMapProto(cx, global->createBlankPrototype(cx, &WeakMapObject::class_)); |
|
499 if (!weakMapProto) |
|
500 return nullptr; |
|
501 |
|
502 RootedFunction ctor(cx, global->createConstructor(cx, WeakMap_construct, |
|
503 cx->names().WeakMap, 0)); |
|
504 if (!ctor) |
|
505 return nullptr; |
|
506 |
|
507 if (!LinkConstructorAndPrototype(cx, ctor, weakMapProto)) |
|
508 return nullptr; |
|
509 |
|
510 if (!DefinePropertiesAndBrand(cx, weakMapProto, nullptr, weak_map_methods)) |
|
511 return nullptr; |
|
512 |
|
513 if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, weakMapProto)) |
|
514 return nullptr; |
|
515 return weakMapProto; |
|
516 } |
|
517 |