michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jsweakmap.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jscntxt.h" michael@0: #include "jsfriendapi.h" michael@0: #include "jsobj.h" michael@0: #include "jswrapper.h" michael@0: michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/WeakMapObject.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: using namespace js; michael@0: michael@0: WeakMapBase::WeakMapBase(JSObject *memOf, JSCompartment *c) michael@0: : memberOf(memOf), michael@0: compartment(c), michael@0: next(WeakMapNotInList) michael@0: { michael@0: JS_ASSERT_IF(memberOf, memberOf->compartment() == c); michael@0: } michael@0: michael@0: WeakMapBase::~WeakMapBase() michael@0: { michael@0: JS_ASSERT(next == WeakMapNotInList); michael@0: } michael@0: michael@0: bool michael@0: WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer) michael@0: { michael@0: bool markedAny = false; michael@0: for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) { michael@0: if (m->markIteratively(tracer)) michael@0: markedAny = true; michael@0: } michael@0: return markedAny; michael@0: } michael@0: michael@0: void michael@0: WeakMapBase::sweepCompartment(JSCompartment *c) michael@0: { michael@0: for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) michael@0: m->sweep(); michael@0: } michael@0: michael@0: void michael@0: WeakMapBase::traceAllMappings(WeakMapTracer *tracer) michael@0: { michael@0: JSRuntime *rt = tracer->runtime; michael@0: for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { michael@0: for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) michael@0: m->traceMappings(tracer); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WeakMapBase::resetCompartmentWeakMapList(JSCompartment *c) michael@0: { michael@0: JS_ASSERT(WeakMapNotInList != nullptr); michael@0: michael@0: WeakMapBase *m = c->gcWeakMapList; michael@0: c->gcWeakMapList = nullptr; michael@0: while (m) { michael@0: WeakMapBase *n = m->next; michael@0: m->next = WeakMapNotInList; michael@0: m = n; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: WeakMapBase::saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector) michael@0: { michael@0: WeakMapBase *m = c->gcWeakMapList; michael@0: while (m) { michael@0: if (!vector.append(m)) michael@0: return false; michael@0: m = m->next; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WeakMapBase::restoreCompartmentWeakMapLists(WeakMapVector &vector) michael@0: { michael@0: for (WeakMapBase **p = vector.begin(); p != vector.end(); p++) { michael@0: WeakMapBase *m = *p; michael@0: JS_ASSERT(m->next == WeakMapNotInList); michael@0: JSCompartment *c = m->compartment; michael@0: m->next = c->gcWeakMapList; michael@0: c->gcWeakMapList = m; michael@0: } michael@0: } michael@0: michael@0: void michael@0: WeakMapBase::removeWeakMapFromList(WeakMapBase *weakmap) michael@0: { michael@0: JSCompartment *c = weakmap->compartment; michael@0: for (WeakMapBase **p = &c->gcWeakMapList; *p; p = &(*p)->next) { michael@0: if (*p == weakmap) { michael@0: *p = (*p)->next; michael@0: weakmap->next = WeakMapNotInList; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: static JSObject * michael@0: GetKeyArg(JSContext *cx, CallArgs &args) michael@0: { michael@0: if (args[0].isPrimitive()) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); michael@0: return nullptr; michael@0: } michael@0: return &args[0].toObject(); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsWeakMap(HandleValue v) michael@0: { michael@0: return v.isObject() && v.toObject().is(); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: WeakMap_has_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsWeakMap(args.thisv())); michael@0: michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "WeakMap.has", "0", "s"); michael@0: return false; michael@0: } michael@0: JSObject *key = GetKeyArg(cx, args); michael@0: if (!key) michael@0: return false; michael@0: michael@0: if (ObjectValueMap *map = args.thisv().toObject().as().getMap()) { michael@0: if (map->has(key)) { michael@0: args.rval().setBoolean(true); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: WeakMap_has(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: WeakMap_clear_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsWeakMap(args.thisv())); michael@0: michael@0: // We can't js_delete the weakmap because the data gathered during GC michael@0: // is used by the Cycle Collector michael@0: if (ObjectValueMap *map = args.thisv().toObject().as().getMap()) michael@0: map->clear(); michael@0: michael@0: args.rval().setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: WeakMap_clear(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: WeakMap_get_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsWeakMap(args.thisv())); michael@0: michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "WeakMap.get", "0", "s"); michael@0: return false; michael@0: } michael@0: JSObject *key = GetKeyArg(cx, args); michael@0: if (!key) michael@0: return false; michael@0: michael@0: if (ObjectValueMap *map = args.thisv().toObject().as().getMap()) { michael@0: if (ObjectValueMap::Ptr ptr = map->lookup(key)) { michael@0: // Read barrier to prevent an incorrectly gray value from escaping the michael@0: // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp michael@0: ExposeValueToActiveJS(ptr->value().get()); michael@0: michael@0: args.rval().set(ptr->value()); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: args.rval().set((args.length() > 1) ? args[1] : UndefinedValue()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: WeakMap_get(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: WeakMap_delete_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsWeakMap(args.thisv())); michael@0: michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "WeakMap.delete", "0", "s"); michael@0: return false; michael@0: } michael@0: JSObject *key = GetKeyArg(cx, args); michael@0: if (!key) michael@0: return false; michael@0: michael@0: if (ObjectValueMap *map = args.thisv().toObject().as().getMap()) { michael@0: if (ObjectValueMap::Ptr ptr = map->lookup(key)) { michael@0: map->remove(ptr); michael@0: args.rval().setBoolean(true); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: WeakMap_delete(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static bool michael@0: TryPreserveReflector(JSContext *cx, HandleObject obj) michael@0: { michael@0: if (obj->getClass()->ext.isWrappedNative || michael@0: (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) || michael@0: (obj->is() && michael@0: obj->as().handler()->family() == GetDOMProxyHandlerFamily())) michael@0: { michael@0: JS_ASSERT(cx->runtime()->preserveWrapperCallback); michael@0: if (!cx->runtime()->preserveWrapperCallback(cx, obj)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static inline void michael@0: WeakMapPostWriteBarrier(JSRuntime *rt, ObjectValueMap *weakMap, JSObject *key) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: /* michael@0: * Strip the barriers from the type before inserting into the store buffer. michael@0: * This will automatically ensure that barriers do not fire during GC. michael@0: * michael@0: * Some compilers complain about instantiating the WeakMap class for michael@0: * unbarriered type arguments, so we cast to a HashMap instead. Because of michael@0: * WeakMap's multiple inheritace, We need to do this in two stages, first to michael@0: * the HashMap base class and then to the unbarriered version. michael@0: */ michael@0: ObjectValueMap::Base *baseHashMap = static_cast(weakMap); michael@0: michael@0: typedef HashMap UnbarrieredMap; michael@0: UnbarrieredMap *unbarrieredMap = reinterpret_cast(baseHashMap); michael@0: michael@0: typedef gc::HashKeyRef Ref; michael@0: if (key && IsInsideNursery(rt, key)) michael@0: rt->gcStoreBuffer.putGeneric(Ref((unbarrieredMap), key)); michael@0: #endif michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: SetWeakMapEntryInternal(JSContext *cx, Handle mapObj, michael@0: HandleObject key, HandleValue value) michael@0: { michael@0: ObjectValueMap *map = mapObj->getMap(); michael@0: if (!map) { michael@0: map = cx->new_(cx, mapObj.get()); michael@0: if (!map) michael@0: return false; michael@0: if (!map->init()) { michael@0: js_delete(map); michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: mapObj->setPrivate(map); michael@0: } michael@0: michael@0: // Preserve wrapped native keys to prevent wrapper optimization. michael@0: if (!TryPreserveReflector(cx, key)) michael@0: return false; michael@0: michael@0: if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) { michael@0: RootedObject delegate(cx, op(key)); michael@0: if (delegate && !TryPreserveReflector(cx, delegate)) michael@0: return false; michael@0: } michael@0: michael@0: JS_ASSERT(key->compartment() == mapObj->compartment()); michael@0: JS_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment()); michael@0: if (!map->put(key, value)) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: WeakMapPostWriteBarrier(cx->runtime(), map, key.get()); michael@0: return true; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: WeakMap_set_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsWeakMap(args.thisv())); michael@0: michael@0: if (args.length() < 1) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, michael@0: "WeakMap.set", "0", "s"); michael@0: return false; michael@0: } michael@0: RootedObject key(cx, GetKeyArg(cx, args)); michael@0: if (!key) michael@0: return false; michael@0: michael@0: RootedValue value(cx, (args.length() > 1) ? args[1] : UndefinedValue()); michael@0: Rooted thisObj(cx, &args.thisv().toObject()); michael@0: Rooted map(cx, &thisObj->as()); michael@0: michael@0: args.rval().setUndefined(); michael@0: return SetWeakMapEntryInternal(cx, map, key, value); michael@0: } michael@0: michael@0: static bool michael@0: WeakMap_set(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: JS_FRIEND_API(bool) michael@0: JS_NondeterministicGetWeakMapKeys(JSContext *cx, HandleObject objArg, MutableHandleObject ret) michael@0: { michael@0: RootedObject obj(cx, objArg); michael@0: obj = UncheckedUnwrap(obj); michael@0: if (!obj || !obj->is()) { michael@0: ret.set(nullptr); michael@0: return true; michael@0: } michael@0: RootedObject arr(cx, NewDenseEmptyArray(cx)); michael@0: if (!arr) michael@0: return false; michael@0: ObjectValueMap *map = obj->as().getMap(); michael@0: if (map) { michael@0: // Prevent GC from mutating the weakmap while iterating. michael@0: gc::AutoSuppressGC suppress(cx); michael@0: for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) { michael@0: RootedObject key(cx, r.front().key()); michael@0: if (!cx->compartment()->wrap(cx, &key)) michael@0: return false; michael@0: if (!NewbornArrayPush(cx, arr, ObjectValue(*key))) michael@0: return false; michael@0: } michael@0: } michael@0: ret.set(arr); michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: WeakMap_mark(JSTracer *trc, JSObject *obj) michael@0: { michael@0: if (ObjectValueMap *map = obj->as().getMap()) michael@0: map->trace(trc); michael@0: } michael@0: michael@0: static void michael@0: WeakMap_finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: if (ObjectValueMap *map = obj->as().getMap()) { michael@0: map->check(); michael@0: #ifdef DEBUG michael@0: map->~ObjectValueMap(); michael@0: memset(static_cast(map), 0xdc, sizeof(*map)); michael@0: fop->free_(map); michael@0: #else michael@0: fop->delete_(map); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: JS_PUBLIC_API(JSObject*) michael@0: JS::NewWeakMapObject(JSContext *cx) michael@0: { michael@0: return NewBuiltinClassInstance(cx, &WeakMapObject::class_); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS::IsWeakMapObject(JSObject *obj) michael@0: { michael@0: return obj->is(); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS::GetWeakMapEntry(JSContext *cx, HandleObject mapObj, HandleObject key, michael@0: MutableHandleValue rval) michael@0: { michael@0: CHECK_REQUEST(cx); michael@0: assertSameCompartment(cx, key); michael@0: rval.setUndefined(); michael@0: ObjectValueMap *map = mapObj->as().getMap(); michael@0: if (!map) michael@0: return true; michael@0: if (ObjectValueMap::Ptr ptr = map->lookup(key)) { michael@0: // Read barrier to prevent an incorrectly gray value from escaping the michael@0: // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp michael@0: ExposeValueToActiveJS(ptr->value().get()); michael@0: rval.set(ptr->value()); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: JS::SetWeakMapEntry(JSContext *cx, HandleObject mapObj, HandleObject key, michael@0: HandleValue val) michael@0: { michael@0: CHECK_REQUEST(cx); michael@0: assertSameCompartment(cx, key, val); michael@0: Rooted rootedMap(cx, &mapObj->as()); michael@0: return SetWeakMapEntryInternal(cx, rootedMap, key, val); michael@0: } michael@0: michael@0: static bool michael@0: WeakMap_construct(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: JSObject *obj = NewBuiltinClassInstance(cx, &WeakMapObject::class_); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: const Class WeakMapObject::class_ = { michael@0: "WeakMap", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: WeakMap_finalize, michael@0: nullptr, /* call */ michael@0: nullptr, /* construct */ michael@0: nullptr, /* xdrObject */ michael@0: WeakMap_mark michael@0: }; michael@0: michael@0: static const JSFunctionSpec weak_map_methods[] = { michael@0: JS_FN("has", WeakMap_has, 1, 0), michael@0: JS_FN("get", WeakMap_get, 2, 0), michael@0: JS_FN("delete", WeakMap_delete, 1, 0), michael@0: JS_FN("set", WeakMap_set, 2, 0), michael@0: JS_FN("clear", WeakMap_clear, 0, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: JSObject * michael@0: js_InitWeakMapClass(JSContext *cx, HandleObject obj) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: Rooted global(cx, &obj->as()); michael@0: michael@0: RootedObject weakMapProto(cx, global->createBlankPrototype(cx, &WeakMapObject::class_)); michael@0: if (!weakMapProto) michael@0: return nullptr; michael@0: michael@0: RootedFunction ctor(cx, global->createConstructor(cx, WeakMap_construct, michael@0: cx->names().WeakMap, 0)); michael@0: if (!ctor) michael@0: return nullptr; michael@0: michael@0: if (!LinkConstructorAndPrototype(cx, ctor, weakMapProto)) michael@0: return nullptr; michael@0: michael@0: if (!DefinePropertiesAndBrand(cx, weakMapProto, nullptr, weak_map_methods)) michael@0: return nullptr; michael@0: michael@0: if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, weakMapProto)) michael@0: return nullptr; michael@0: return weakMapProto; michael@0: } michael@0: