Bumping manifests a=b2g-bump
[gecko.git] / js / src / jsweakmap.cpp
blob9bce5a79c865a836429aab8bd28c5ea94d5cd67b
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/. */
7 #include "jsweakmap.h"
9 #include <string.h>
11 #include "jsapi.h"
12 #include "jscntxt.h"
13 #include "jsfriendapi.h"
14 #include "jsobj.h"
15 #include "jswrapper.h"
17 #include "js/GCAPI.h"
18 #include "vm/GlobalObject.h"
19 #include "vm/WeakMapObject.h"
21 #include "jsobjinlines.h"
23 #include "vm/Interpreter-inl.h"
25 using namespace js;
26 using namespace js::gc;
28 WeakMapBase::WeakMapBase(JSObject* memOf, JSCompartment* c)
29 : memberOf(memOf),
30 compartment(c),
31 next(WeakMapNotInList),
32 marked(false)
34 MOZ_ASSERT_IF(memberOf, memberOf->compartment() == c);
37 WeakMapBase::~WeakMapBase()
39 MOZ_ASSERT(!isInList());
42 void
43 WeakMapBase::trace(JSTracer* tracer)
45 MOZ_ASSERT(isInList());
46 if (IS_GC_MARKING_TRACER(tracer)) {
47 // We don't trace any of the WeakMap entries at this time, just record
48 // record the fact that the WeakMap has been marked. Enties are marked
49 // in the iterative marking phase by markAllIteratively(), which happens
50 // when many keys as possible have been marked already.
51 MOZ_ASSERT(tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps);
52 marked = true;
53 } else {
54 // If we're not actually doing garbage collection, the keys won't be marked
55 // nicely as needed by the true ephemeral marking algorithm --- custom tracers
56 // such as the cycle collector must use their own means for cycle detection.
57 // So here we do a conservative approximation: pretend all keys are live.
58 if (tracer->eagerlyTraceWeakMaps() == DoNotTraceWeakMaps)
59 return;
61 nonMarkingTraceValues(tracer);
62 if (tracer->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues)
63 nonMarkingTraceKeys(tracer);
67 void
68 WeakMapBase::unmarkCompartment(JSCompartment* c)
70 for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next)
71 m->marked = false;
74 void
75 WeakMapBase::markAll(JSCompartment* c, JSTracer* tracer)
77 MOZ_ASSERT(tracer->eagerlyTraceWeakMaps() != DoNotTraceWeakMaps);
78 for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) {
79 m->trace(tracer);
80 if (m->memberOf)
81 gc::MarkObject(tracer, &m->memberOf, "memberOf");
85 bool
86 WeakMapBase::markCompartmentIteratively(JSCompartment* c, JSTracer* tracer)
88 bool markedAny = false;
89 for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) {
90 if (m->marked && m->markIteratively(tracer))
91 markedAny = true;
93 return markedAny;
96 bool
97 WeakMapBase::findZoneEdgesForCompartment(JSCompartment* c)
99 for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) {
100 if (!m->findZoneEdges())
101 return false;
103 return true;
106 void
107 WeakMapBase::sweepCompartment(JSCompartment* c)
109 WeakMapBase** tailPtr = &c->gcWeakMapList;
110 for (WeakMapBase* m = c->gcWeakMapList, *next; m; m = next) {
111 next = m->next;
112 if (m->marked) {
113 m->sweep();
114 *tailPtr = m;
115 tailPtr = &m->next;
116 } else {
117 /* Destroy the hash map now to catch any use after this point. */
118 m->finish();
119 m->next = WeakMapNotInList;
122 *tailPtr = nullptr;
124 #ifdef DEBUG
125 for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next)
126 MOZ_ASSERT(m->isInList() && m->marked);
127 #endif
130 void
131 WeakMapBase::traceAllMappings(WeakMapTracer* tracer)
133 JSRuntime* rt = tracer->runtime;
134 for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
135 for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) {
136 // The WeakMapTracer callback is not allowed to GC.
137 JS::AutoSuppressGCAnalysis nogc;
138 m->traceMappings(tracer);
143 bool
144 WeakMapBase::saveCompartmentMarkedWeakMaps(JSCompartment* c, WeakMapSet& markedWeakMaps)
146 for (WeakMapBase* m = c->gcWeakMapList; m; m = m->next) {
147 if (m->marked && !markedWeakMaps.put(m))
148 return false;
150 return true;
153 void
154 WeakMapBase::restoreCompartmentMarkedWeakMaps(WeakMapSet& markedWeakMaps)
156 for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) {
157 WeakMapBase* map = r.front();
158 MOZ_ASSERT(map->compartment->zone()->isGCMarking());
159 MOZ_ASSERT(!map->marked);
160 map->marked = true;
164 void
165 WeakMapBase::removeWeakMapFromList(WeakMapBase* weakmap)
167 JSCompartment* c = weakmap->compartment;
168 for (WeakMapBase** p = &c->gcWeakMapList; *p; p = &(*p)->next) {
169 if (*p == weakmap) {
170 *p = (*p)->next;
171 weakmap->next = WeakMapNotInList;
172 break;
177 bool
178 ObjectValueMap::findZoneEdges()
181 * For unmarked weakmap keys with delegates in a different zone, add a zone
182 * edge to ensure that the delegate zone does finish marking after the key
183 * zone.
185 JS::AutoSuppressGCAnalysis nogc;
186 Zone* mapZone = compartment->zone();
187 for (Range r = all(); !r.empty(); r.popFront()) {
188 JSObject* key = r.front().key();
189 if (key->asTenured().isMarked(BLACK) && !key->asTenured().isMarked(GRAY))
190 continue;
191 JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp;
192 if (!op)
193 continue;
194 JSObject* delegate = op(key);
195 if (!delegate)
196 continue;
197 Zone* delegateZone = delegate->zone();
198 if (delegateZone == mapZone)
199 continue;
200 if (!delegateZone->gcZoneGroupEdges.put(key->zone()))
201 return false;
203 return true;
206 static JSObject*
207 GetKeyArg(JSContext* cx, CallArgs& args)
209 if (args[0].isPrimitive()) {
210 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
211 return nullptr;
213 return &args[0].toObject();
216 MOZ_ALWAYS_INLINE bool
217 IsWeakMap(HandleValue v)
219 return v.isObject() && v.toObject().is<WeakMapObject>();
222 MOZ_ALWAYS_INLINE bool
223 WeakMap_has_impl(JSContext* cx, CallArgs args)
225 MOZ_ASSERT(IsWeakMap(args.thisv()));
227 if (args.length() < 1) {
228 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
229 "WeakMap.has", "0", "s");
230 return false;
232 JSObject* key = GetKeyArg(cx, args);
233 if (!key)
234 return false;
236 if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
237 if (map->has(key)) {
238 args.rval().setBoolean(true);
239 return true;
243 args.rval().setBoolean(false);
244 return true;
247 bool
248 js::WeakMap_has(JSContext* cx, unsigned argc, Value* vp)
250 CallArgs args = CallArgsFromVp(argc, vp);
251 return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args);
254 MOZ_ALWAYS_INLINE bool
255 WeakMap_clear_impl(JSContext* cx, CallArgs args)
257 MOZ_ASSERT(IsWeakMap(args.thisv()));
259 // We can't js_delete the weakmap because the data gathered during GC is
260 // used by the Cycle Collector.
261 if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap())
262 map->clear();
264 args.rval().setUndefined();
265 return true;
268 bool
269 js::WeakMap_clear(JSContext* cx, unsigned argc, Value* vp)
271 CallArgs args = CallArgsFromVp(argc, vp);
272 return CallNonGenericMethod<IsWeakMap, WeakMap_clear_impl>(cx, args);
275 MOZ_ALWAYS_INLINE bool
276 WeakMap_get_impl(JSContext* cx, CallArgs args)
278 MOZ_ASSERT(IsWeakMap(args.thisv()));
280 if (args.length() < 1) {
281 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
282 "WeakMap.get", "0", "s");
283 return false;
285 JSObject* key = GetKeyArg(cx, args);
286 if (!key)
287 return false;
289 if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
290 if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
291 args.rval().set(ptr->value());
292 return true;
296 args.rval().set((args.length() > 1) ? args[1] : UndefinedValue());
297 return true;
300 bool
301 js::WeakMap_get(JSContext* cx, unsigned argc, Value* vp)
303 CallArgs args = CallArgsFromVp(argc, vp);
304 return CallNonGenericMethod<IsWeakMap, WeakMap_get_impl>(cx, args);
307 MOZ_ALWAYS_INLINE bool
308 WeakMap_delete_impl(JSContext* cx, CallArgs args)
310 MOZ_ASSERT(IsWeakMap(args.thisv()));
312 if (args.length() < 1) {
313 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
314 "WeakMap.delete", "0", "s");
315 return false;
317 JSObject* key = GetKeyArg(cx, args);
318 if (!key)
319 return false;
321 if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
322 if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
323 map->remove(ptr);
324 args.rval().setBoolean(true);
325 return true;
329 args.rval().setBoolean(false);
330 return true;
333 bool
334 js::WeakMap_delete(JSContext* cx, unsigned argc, Value* vp)
336 CallArgs args = CallArgsFromVp(argc, vp);
337 return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args);
340 static bool
341 TryPreserveReflector(JSContext* cx, HandleObject obj)
343 if (obj->getClass()->ext.isWrappedNative ||
344 (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) ||
345 (obj->is<ProxyObject>() &&
346 obj->as<ProxyObject>().handler()->family() == GetDOMProxyHandlerFamily()))
348 MOZ_ASSERT(cx->runtime()->preserveWrapperCallback);
349 if (!cx->runtime()->preserveWrapperCallback(cx, obj)) {
350 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY);
351 return false;
354 return true;
357 static inline void
358 WeakMapPostWriteBarrier(JSRuntime* rt, ObjectValueMap* weakMap, JSObject* key)
360 // Strip the barriers from the type before inserting into the store buffer.
361 // This will automatically ensure that barriers do not fire during GC.
362 if (key && IsInsideNursery(key))
363 rt->gc.storeBuffer.putGeneric(UnbarrieredRef(weakMap, key));
366 static MOZ_ALWAYS_INLINE bool
367 SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
368 HandleObject key, HandleValue value)
370 ObjectValueMap* map = mapObj->getMap();
371 if (!map) {
372 map = cx->new_<ObjectValueMap>(cx, mapObj.get());
373 if (!map)
374 return false;
375 if (!map->init()) {
376 js_delete(map);
377 JS_ReportOutOfMemory(cx);
378 return false;
380 mapObj->setPrivate(map);
383 // Preserve wrapped native keys to prevent wrapper optimization.
384 if (!TryPreserveReflector(cx, key))
385 return false;
387 if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) {
388 RootedObject delegate(cx, op(key));
389 if (delegate && !TryPreserveReflector(cx, delegate))
390 return false;
393 MOZ_ASSERT(key->compartment() == mapObj->compartment());
394 MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
395 if (!map->put(key, value)) {
396 JS_ReportOutOfMemory(cx);
397 return false;
399 WeakMapPostWriteBarrier(cx->runtime(), map, key.get());
400 return true;
403 MOZ_ALWAYS_INLINE bool
404 WeakMap_set_impl(JSContext* cx, CallArgs args)
406 MOZ_ASSERT(IsWeakMap(args.thisv()));
408 if (args.length() < 1) {
409 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
410 "WeakMap.set", "0", "s");
411 return false;
413 RootedObject key(cx, GetKeyArg(cx, args));
414 if (!key)
415 return false;
417 RootedValue value(cx, (args.length() > 1) ? args[1] : UndefinedValue());
418 Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
419 Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
421 if (!SetWeakMapEntryInternal(cx, map, key, value))
422 return false;
423 args.rval().set(args.thisv());
424 return true;
427 bool
428 js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
430 CallArgs args = CallArgsFromVp(argc, vp);
431 return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
434 JS_FRIEND_API(bool)
435 JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
437 RootedObject obj(cx, objArg);
438 obj = UncheckedUnwrap(obj);
439 if (!obj || !obj->is<WeakMapObject>()) {
440 ret.set(nullptr);
441 return true;
443 RootedObject arr(cx, NewDenseEmptyArray(cx));
444 if (!arr)
445 return false;
446 ObjectValueMap* map = obj->as<WeakMapObject>().getMap();
447 if (map) {
448 // Prevent GC from mutating the weakmap while iterating.
449 AutoSuppressGC suppress(cx);
450 for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
451 JS::ExposeObjectToActiveJS(r.front().key());
452 RootedObject key(cx, r.front().key());
453 if (!cx->compartment()->wrap(cx, &key))
454 return false;
455 if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
456 return false;
459 ret.set(arr);
460 return true;
463 static void
464 WeakMap_mark(JSTracer* trc, JSObject* obj)
466 if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap())
467 map->trace(trc);
470 static void
471 WeakMap_finalize(FreeOp* fop, JSObject* obj)
473 if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap()) {
474 #ifdef DEBUG
475 map->~ObjectValueMap();
476 memset(static_cast<void*>(map), 0xdc, sizeof(*map));
477 fop->free_(map);
478 #else
479 fop->delete_(map);
480 #endif
484 JS_PUBLIC_API(JSObject*)
485 JS::NewWeakMapObject(JSContext* cx)
487 return NewBuiltinClassInstance(cx, &WeakMapObject::class_);
490 JS_PUBLIC_API(bool)
491 JS::IsWeakMapObject(JSObject* obj)
493 return obj->is<WeakMapObject>();
496 JS_PUBLIC_API(bool)
497 JS::GetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
498 MutableHandleValue rval)
500 CHECK_REQUEST(cx);
501 assertSameCompartment(cx, key);
502 rval.setUndefined();
503 ObjectValueMap* map = mapObj->as<WeakMapObject>().getMap();
504 if (!map)
505 return true;
506 if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
507 // Read barrier to prevent an incorrectly gray value from escaping the
508 // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp
509 ExposeValueToActiveJS(ptr->value().get());
510 rval.set(ptr->value());
512 return true;
515 JS_PUBLIC_API(bool)
516 JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
517 HandleValue val)
519 CHECK_REQUEST(cx);
520 assertSameCompartment(cx, key, val);
521 Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
522 return SetWeakMapEntryInternal(cx, rootedMap, key, val);
525 static bool
526 WeakMap_construct(JSContext* cx, unsigned argc, Value* vp)
528 CallArgs args = CallArgsFromVp(argc, vp);
529 RootedObject obj(cx, NewBuiltinClassInstance(cx, &WeakMapObject::class_));
530 if (!obj)
531 return false;
533 // ES6 23.3.1.1 steps 5-6, 11.
534 if (!args.get(0).isNullOrUndefined()) {
535 // Steps 7a-b.
536 RootedValue adderVal(cx);
537 if (!JSObject::getProperty(cx, obj, obj, cx->names().set, &adderVal))
538 return false;
540 // Step 7c.
541 if (!IsCallable(adderVal))
542 return ReportIsNotFunction(cx, adderVal);
544 bool isOriginalAdder = IsNativeFunction(adderVal, WeakMap_set);
545 RootedValue mapVal(cx, ObjectValue(*obj));
546 FastInvokeGuard fig(cx, adderVal);
547 InvokeArgs& args2 = fig.args();
549 // Steps 7d-e.
550 JS::ForOfIterator iter(cx);
551 if (!iter.init(args[0]))
552 return false;
554 RootedValue pairVal(cx);
555 RootedObject pairObject(cx);
556 RootedValue keyVal(cx);
557 RootedObject keyObject(cx);
558 RootedValue val(cx);
559 while (true) {
560 // Steps 12a-e.
561 bool done;
562 if (!iter.next(&pairVal, &done))
563 return false;
564 if (done)
565 break;
567 // Step 12f.
568 if (!pairVal.isObject()) {
569 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
570 JSMSG_INVALID_MAP_ITERABLE, "WeakMap");
571 return false;
574 pairObject = &pairVal.toObject();
575 if (!pairObject)
576 return false;
578 // Steps 12g-h.
579 if (!JSObject::getElement(cx, pairObject, pairObject, 0, &keyVal))
580 return false;
582 // Steps 12i-j.
583 if (!JSObject::getElement(cx, pairObject, pairObject, 1, &val))
584 return false;
586 // Steps 12k-l.
587 if (isOriginalAdder) {
588 if (keyVal.isPrimitive()) {
589 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
590 return false;
593 keyObject = &keyVal.toObject();
594 if (!SetWeakMapEntry(cx, obj, keyObject, val))
595 return false;
596 } else {
597 if (!args2.init(2))
598 return false;
600 args2.setCallee(adderVal);
601 args2.setThis(mapVal);
602 args2[0].set(keyVal);
603 args2[1].set(val);
605 if (!fig.invoke(cx))
606 return false;
611 args.rval().setObject(*obj);
612 return true;
615 const Class WeakMapObject::class_ = {
616 "WeakMap",
617 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
618 JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap),
619 nullptr, /* addProperty */
620 nullptr, /* delProperty */
621 nullptr, /* getProperty */
622 nullptr, /* setProperty */
623 nullptr, /* enumerate */
624 nullptr, /* resolve */
625 nullptr, /* convert */
626 WeakMap_finalize,
627 nullptr, /* call */
628 nullptr, /* hasInstance */
629 nullptr, /* construct */
630 WeakMap_mark
633 static const JSFunctionSpec weak_map_methods[] = {
634 JS_FN("has", WeakMap_has, 1, 0),
635 JS_FN("get", WeakMap_get, 2, 0),
636 JS_FN("delete", WeakMap_delete, 1, 0),
637 JS_FN("set", WeakMap_set, 2, 0),
638 JS_FN("clear", WeakMap_clear, 0, 0),
639 JS_FS_END
642 static JSObject*
643 InitWeakMapClass(JSContext* cx, HandleObject obj, bool defineMembers)
645 MOZ_ASSERT(obj->isNative());
647 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
649 RootedObject weakMapProto(cx, global->createBlankPrototype(cx, &WeakMapObject::class_));
650 if (!weakMapProto)
651 return nullptr;
653 RootedFunction ctor(cx, global->createConstructor(cx, WeakMap_construct,
654 cx->names().WeakMap, 1));
655 if (!ctor)
656 return nullptr;
658 if (!LinkConstructorAndPrototype(cx, ctor, weakMapProto))
659 return nullptr;
661 if (defineMembers) {
662 if (!DefinePropertiesAndFunctions(cx, weakMapProto, nullptr, weak_map_methods))
663 return nullptr;
666 if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, weakMapProto))
667 return nullptr;
668 return weakMapProto;
671 JSObject*
672 js_InitWeakMapClass(JSContext* cx, HandleObject obj)
674 return InitWeakMapClass(cx, obj, true);
677 JSObject*
678 js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj)
680 return InitWeakMapClass(cx, obj, false);