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/. */
13 #include "jsfriendapi.h"
15 #include "jswrapper.h"
18 #include "vm/GlobalObject.h"
19 #include "vm/WeakMapObject.h"
21 #include "jsobjinlines.h"
23 #include "vm/Interpreter-inl.h"
26 using namespace js::gc
;
28 WeakMapBase::WeakMapBase(JSObject
* memOf
, JSCompartment
* c
)
31 next(WeakMapNotInList
),
34 MOZ_ASSERT_IF(memberOf
, memberOf
->compartment() == c
);
37 WeakMapBase::~WeakMapBase()
39 MOZ_ASSERT(!isInList());
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
);
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
)
61 nonMarkingTraceValues(tracer
);
62 if (tracer
->eagerlyTraceWeakMaps() == TraceWeakMapKeysValues
)
63 nonMarkingTraceKeys(tracer
);
68 WeakMapBase::unmarkCompartment(JSCompartment
* c
)
70 for (WeakMapBase
* m
= c
->gcWeakMapList
; m
; m
= m
->next
)
75 WeakMapBase::markAll(JSCompartment
* c
, JSTracer
* tracer
)
77 MOZ_ASSERT(tracer
->eagerlyTraceWeakMaps() != DoNotTraceWeakMaps
);
78 for (WeakMapBase
* m
= c
->gcWeakMapList
; m
; m
= m
->next
) {
81 gc::MarkObject(tracer
, &m
->memberOf
, "memberOf");
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
))
97 WeakMapBase::findZoneEdgesForCompartment(JSCompartment
* c
)
99 for (WeakMapBase
* m
= c
->gcWeakMapList
; m
; m
= m
->next
) {
100 if (!m
->findZoneEdges())
107 WeakMapBase::sweepCompartment(JSCompartment
* c
)
109 WeakMapBase
** tailPtr
= &c
->gcWeakMapList
;
110 for (WeakMapBase
* m
= c
->gcWeakMapList
, *next
; m
; m
= next
) {
117 /* Destroy the hash map now to catch any use after this point. */
119 m
->next
= WeakMapNotInList
;
125 for (WeakMapBase
* m
= c
->gcWeakMapList
; m
; m
= m
->next
)
126 MOZ_ASSERT(m
->isInList() && m
->marked
);
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
);
144 WeakMapBase::saveCompartmentMarkedWeakMaps(JSCompartment
* c
, WeakMapSet
& markedWeakMaps
)
146 for (WeakMapBase
* m
= c
->gcWeakMapList
; m
; m
= m
->next
) {
147 if (m
->marked
&& !markedWeakMaps
.put(m
))
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
);
165 WeakMapBase::removeWeakMapFromList(WeakMapBase
* weakmap
)
167 JSCompartment
* c
= weakmap
->compartment
;
168 for (WeakMapBase
** p
= &c
->gcWeakMapList
; *p
; p
= &(*p
)->next
) {
171 weakmap
->next
= WeakMapNotInList
;
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
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
))
191 JSWeakmapKeyDelegateOp op
= key
->getClass()->ext
.weakmapKeyDelegateOp
;
194 JSObject
* delegate
= op(key
);
197 Zone
* delegateZone
= delegate
->zone();
198 if (delegateZone
== mapZone
)
200 if (!delegateZone
->gcZoneGroupEdges
.put(key
->zone()))
207 GetKeyArg(JSContext
* cx
, CallArgs
& args
)
209 if (args
[0].isPrimitive()) {
210 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, nullptr, JSMSG_NOT_NONNULL_OBJECT
);
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");
232 JSObject
* key
= GetKeyArg(cx
, args
);
236 if (ObjectValueMap
* map
= args
.thisv().toObject().as
<WeakMapObject
>().getMap()) {
238 args
.rval().setBoolean(true);
243 args
.rval().setBoolean(false);
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())
264 args
.rval().setUndefined();
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");
285 JSObject
* key
= GetKeyArg(cx
, args
);
289 if (ObjectValueMap
* map
= args
.thisv().toObject().as
<WeakMapObject
>().getMap()) {
290 if (ObjectValueMap::Ptr ptr
= map
->lookup(key
)) {
291 args
.rval().set(ptr
->value());
296 args
.rval().set((args
.length() > 1) ? args
[1] : UndefinedValue());
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");
317 JSObject
* key
= GetKeyArg(cx
, args
);
321 if (ObjectValueMap
* map
= args
.thisv().toObject().as
<WeakMapObject
>().getMap()) {
322 if (ObjectValueMap::Ptr ptr
= map
->lookup(key
)) {
324 args
.rval().setBoolean(true);
329 args
.rval().setBoolean(false);
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
);
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
);
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();
372 map
= cx
->new_
<ObjectValueMap
>(cx
, mapObj
.get());
377 JS_ReportOutOfMemory(cx
);
380 mapObj
->setPrivate(map
);
383 // Preserve wrapped native keys to prevent wrapper optimization.
384 if (!TryPreserveReflector(cx
, key
))
387 if (JSWeakmapKeyDelegateOp op
= key
->getClass()->ext
.weakmapKeyDelegateOp
) {
388 RootedObject
delegate(cx
, op(key
));
389 if (delegate
&& !TryPreserveReflector(cx
, delegate
))
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
);
399 WeakMapPostWriteBarrier(cx
->runtime(), map
, key
.get());
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");
413 RootedObject
key(cx
, GetKeyArg(cx
, args
));
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
))
423 args
.rval().set(args
.thisv());
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
);
435 JS_NondeterministicGetWeakMapKeys(JSContext
* cx
, HandleObject objArg
, MutableHandleObject ret
)
437 RootedObject
obj(cx
, objArg
);
438 obj
= UncheckedUnwrap(obj
);
439 if (!obj
|| !obj
->is
<WeakMapObject
>()) {
443 RootedObject
arr(cx
, NewDenseEmptyArray(cx
));
446 ObjectValueMap
* map
= obj
->as
<WeakMapObject
>().getMap();
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
))
455 if (!NewbornArrayPush(cx
, arr
, ObjectValue(*key
)))
464 WeakMap_mark(JSTracer
* trc
, JSObject
* obj
)
466 if (ObjectValueMap
* map
= obj
->as
<WeakMapObject
>().getMap())
471 WeakMap_finalize(FreeOp
* fop
, JSObject
* obj
)
473 if (ObjectValueMap
* map
= obj
->as
<WeakMapObject
>().getMap()) {
475 map
->~ObjectValueMap();
476 memset(static_cast<void*>(map
), 0xdc, sizeof(*map
));
484 JS_PUBLIC_API(JSObject
*)
485 JS::NewWeakMapObject(JSContext
* cx
)
487 return NewBuiltinClassInstance(cx
, &WeakMapObject::class_
);
491 JS::IsWeakMapObject(JSObject
* obj
)
493 return obj
->is
<WeakMapObject
>();
497 JS::GetWeakMapEntry(JSContext
* cx
, HandleObject mapObj
, HandleObject key
,
498 MutableHandleValue rval
)
501 assertSameCompartment(cx
, key
);
503 ObjectValueMap
* map
= mapObj
->as
<WeakMapObject
>().getMap();
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());
516 JS::SetWeakMapEntry(JSContext
* cx
, HandleObject mapObj
, HandleObject key
,
520 assertSameCompartment(cx
, key
, val
);
521 Rooted
<WeakMapObject
*> rootedMap(cx
, &mapObj
->as
<WeakMapObject
>());
522 return SetWeakMapEntryInternal(cx
, rootedMap
, key
, val
);
526 WeakMap_construct(JSContext
* cx
, unsigned argc
, Value
* vp
)
528 CallArgs args
= CallArgsFromVp(argc
, vp
);
529 RootedObject
obj(cx
, NewBuiltinClassInstance(cx
, &WeakMapObject::class_
));
533 // ES6 23.3.1.1 steps 5-6, 11.
534 if (!args
.get(0).isNullOrUndefined()) {
536 RootedValue
adderVal(cx
);
537 if (!JSObject::getProperty(cx
, obj
, obj
, cx
->names().set
, &adderVal
))
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();
550 JS::ForOfIterator
iter(cx
);
551 if (!iter
.init(args
[0]))
554 RootedValue
pairVal(cx
);
555 RootedObject
pairObject(cx
);
556 RootedValue
keyVal(cx
);
557 RootedObject
keyObject(cx
);
562 if (!iter
.next(&pairVal
, &done
))
568 if (!pairVal
.isObject()) {
569 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, nullptr,
570 JSMSG_INVALID_MAP_ITERABLE
, "WeakMap");
574 pairObject
= &pairVal
.toObject();
579 if (!JSObject::getElement(cx
, pairObject
, pairObject
, 0, &keyVal
))
583 if (!JSObject::getElement(cx
, pairObject
, pairObject
, 1, &val
))
587 if (isOriginalAdder
) {
588 if (keyVal
.isPrimitive()) {
589 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, nullptr, JSMSG_NOT_NONNULL_OBJECT
);
593 keyObject
= &keyVal
.toObject();
594 if (!SetWeakMapEntry(cx
, obj
, keyObject
, val
))
600 args2
.setCallee(adderVal
);
601 args2
.setThis(mapVal
);
602 args2
[0].set(keyVal
);
611 args
.rval().setObject(*obj
);
615 const Class
WeakMapObject::class_
= {
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 */
628 nullptr, /* hasInstance */
629 nullptr, /* construct */
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),
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_
));
653 RootedFunction
ctor(cx
, global
->createConstructor(cx
, WeakMap_construct
,
654 cx
->names().WeakMap
, 1));
658 if (!LinkConstructorAndPrototype(cx
, ctor
, weakMapProto
))
662 if (!DefinePropertiesAndFunctions(cx
, weakMapProto
, nullptr, weak_map_methods
))
666 if (!GlobalObject::initBuiltinConstructor(cx
, global
, JSProto_WeakMap
, ctor
, weakMapProto
))
672 js_InitWeakMapClass(JSContext
* cx
, HandleObject obj
)
674 return InitWeakMapClass(cx
, obj
, true);
678 js::InitBareWeakMapCtor(JSContext
* cx
, HandleObject obj
)
680 return InitWeakMapClass(cx
, obj
, false);