1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/src/jsweakmap.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,517 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99: 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "jsweakmap.h" 1.11 + 1.12 +#include <string.h> 1.13 + 1.14 +#include "jsapi.h" 1.15 +#include "jscntxt.h" 1.16 +#include "jsfriendapi.h" 1.17 +#include "jsobj.h" 1.18 +#include "jswrapper.h" 1.19 + 1.20 +#include "vm/GlobalObject.h" 1.21 +#include "vm/WeakMapObject.h" 1.22 + 1.23 +#include "jsobjinlines.h" 1.24 + 1.25 +using namespace js; 1.26 + 1.27 +WeakMapBase::WeakMapBase(JSObject *memOf, JSCompartment *c) 1.28 + : memberOf(memOf), 1.29 + compartment(c), 1.30 + next(WeakMapNotInList) 1.31 +{ 1.32 + JS_ASSERT_IF(memberOf, memberOf->compartment() == c); 1.33 +} 1.34 + 1.35 +WeakMapBase::~WeakMapBase() 1.36 +{ 1.37 + JS_ASSERT(next == WeakMapNotInList); 1.38 +} 1.39 + 1.40 +bool 1.41 +WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer) 1.42 +{ 1.43 + bool markedAny = false; 1.44 + for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) { 1.45 + if (m->markIteratively(tracer)) 1.46 + markedAny = true; 1.47 + } 1.48 + return markedAny; 1.49 +} 1.50 + 1.51 +void 1.52 +WeakMapBase::sweepCompartment(JSCompartment *c) 1.53 +{ 1.54 + for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) 1.55 + m->sweep(); 1.56 +} 1.57 + 1.58 +void 1.59 +WeakMapBase::traceAllMappings(WeakMapTracer *tracer) 1.60 +{ 1.61 + JSRuntime *rt = tracer->runtime; 1.62 + for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { 1.63 + for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) 1.64 + m->traceMappings(tracer); 1.65 + } 1.66 +} 1.67 + 1.68 +void 1.69 +WeakMapBase::resetCompartmentWeakMapList(JSCompartment *c) 1.70 +{ 1.71 + JS_ASSERT(WeakMapNotInList != nullptr); 1.72 + 1.73 + WeakMapBase *m = c->gcWeakMapList; 1.74 + c->gcWeakMapList = nullptr; 1.75 + while (m) { 1.76 + WeakMapBase *n = m->next; 1.77 + m->next = WeakMapNotInList; 1.78 + m = n; 1.79 + } 1.80 +} 1.81 + 1.82 +bool 1.83 +WeakMapBase::saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector) 1.84 +{ 1.85 + WeakMapBase *m = c->gcWeakMapList; 1.86 + while (m) { 1.87 + if (!vector.append(m)) 1.88 + return false; 1.89 + m = m->next; 1.90 + } 1.91 + return true; 1.92 +} 1.93 + 1.94 +void 1.95 +WeakMapBase::restoreCompartmentWeakMapLists(WeakMapVector &vector) 1.96 +{ 1.97 + for (WeakMapBase **p = vector.begin(); p != vector.end(); p++) { 1.98 + WeakMapBase *m = *p; 1.99 + JS_ASSERT(m->next == WeakMapNotInList); 1.100 + JSCompartment *c = m->compartment; 1.101 + m->next = c->gcWeakMapList; 1.102 + c->gcWeakMapList = m; 1.103 + } 1.104 +} 1.105 + 1.106 +void 1.107 +WeakMapBase::removeWeakMapFromList(WeakMapBase *weakmap) 1.108 +{ 1.109 + JSCompartment *c = weakmap->compartment; 1.110 + for (WeakMapBase **p = &c->gcWeakMapList; *p; p = &(*p)->next) { 1.111 + if (*p == weakmap) { 1.112 + *p = (*p)->next; 1.113 + weakmap->next = WeakMapNotInList; 1.114 + break; 1.115 + } 1.116 + } 1.117 +} 1.118 + 1.119 +static JSObject * 1.120 +GetKeyArg(JSContext *cx, CallArgs &args) 1.121 +{ 1.122 + if (args[0].isPrimitive()) { 1.123 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); 1.124 + return nullptr; 1.125 + } 1.126 + return &args[0].toObject(); 1.127 +} 1.128 + 1.129 +MOZ_ALWAYS_INLINE bool 1.130 +IsWeakMap(HandleValue v) 1.131 +{ 1.132 + return v.isObject() && v.toObject().is<WeakMapObject>(); 1.133 +} 1.134 + 1.135 +MOZ_ALWAYS_INLINE bool 1.136 +WeakMap_has_impl(JSContext *cx, CallArgs args) 1.137 +{ 1.138 + JS_ASSERT(IsWeakMap(args.thisv())); 1.139 + 1.140 + if (args.length() < 1) { 1.141 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, 1.142 + "WeakMap.has", "0", "s"); 1.143 + return false; 1.144 + } 1.145 + JSObject *key = GetKeyArg(cx, args); 1.146 + if (!key) 1.147 + return false; 1.148 + 1.149 + if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) { 1.150 + if (map->has(key)) { 1.151 + args.rval().setBoolean(true); 1.152 + return true; 1.153 + } 1.154 + } 1.155 + 1.156 + args.rval().setBoolean(false); 1.157 + return true; 1.158 +} 1.159 + 1.160 +static bool 1.161 +WeakMap_has(JSContext *cx, unsigned argc, Value *vp) 1.162 +{ 1.163 + CallArgs args = CallArgsFromVp(argc, vp); 1.164 + return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args); 1.165 +} 1.166 + 1.167 +MOZ_ALWAYS_INLINE bool 1.168 +WeakMap_clear_impl(JSContext *cx, CallArgs args) 1.169 +{ 1.170 + JS_ASSERT(IsWeakMap(args.thisv())); 1.171 + 1.172 + // We can't js_delete the weakmap because the data gathered during GC 1.173 + // is used by the Cycle Collector 1.174 + if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) 1.175 + map->clear(); 1.176 + 1.177 + args.rval().setUndefined(); 1.178 + return true; 1.179 +} 1.180 + 1.181 +static bool 1.182 +WeakMap_clear(JSContext *cx, unsigned argc, Value *vp) 1.183 +{ 1.184 + CallArgs args = CallArgsFromVp(argc, vp); 1.185 + return CallNonGenericMethod<IsWeakMap, WeakMap_clear_impl>(cx, args); 1.186 +} 1.187 + 1.188 +MOZ_ALWAYS_INLINE bool 1.189 +WeakMap_get_impl(JSContext *cx, CallArgs args) 1.190 +{ 1.191 + JS_ASSERT(IsWeakMap(args.thisv())); 1.192 + 1.193 + if (args.length() < 1) { 1.194 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, 1.195 + "WeakMap.get", "0", "s"); 1.196 + return false; 1.197 + } 1.198 + JSObject *key = GetKeyArg(cx, args); 1.199 + if (!key) 1.200 + return false; 1.201 + 1.202 + if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) { 1.203 + if (ObjectValueMap::Ptr ptr = map->lookup(key)) { 1.204 + // Read barrier to prevent an incorrectly gray value from escaping the 1.205 + // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp 1.206 + ExposeValueToActiveJS(ptr->value().get()); 1.207 + 1.208 + args.rval().set(ptr->value()); 1.209 + return true; 1.210 + } 1.211 + } 1.212 + 1.213 + args.rval().set((args.length() > 1) ? args[1] : UndefinedValue()); 1.214 + return true; 1.215 +} 1.216 + 1.217 +static bool 1.218 +WeakMap_get(JSContext *cx, unsigned argc, Value *vp) 1.219 +{ 1.220 + CallArgs args = CallArgsFromVp(argc, vp); 1.221 + return CallNonGenericMethod<IsWeakMap, WeakMap_get_impl>(cx, args); 1.222 +} 1.223 + 1.224 +MOZ_ALWAYS_INLINE bool 1.225 +WeakMap_delete_impl(JSContext *cx, CallArgs args) 1.226 +{ 1.227 + JS_ASSERT(IsWeakMap(args.thisv())); 1.228 + 1.229 + if (args.length() < 1) { 1.230 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, 1.231 + "WeakMap.delete", "0", "s"); 1.232 + return false; 1.233 + } 1.234 + JSObject *key = GetKeyArg(cx, args); 1.235 + if (!key) 1.236 + return false; 1.237 + 1.238 + if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) { 1.239 + if (ObjectValueMap::Ptr ptr = map->lookup(key)) { 1.240 + map->remove(ptr); 1.241 + args.rval().setBoolean(true); 1.242 + return true; 1.243 + } 1.244 + } 1.245 + 1.246 + args.rval().setBoolean(false); 1.247 + return true; 1.248 +} 1.249 + 1.250 +static bool 1.251 +WeakMap_delete(JSContext *cx, unsigned argc, Value *vp) 1.252 +{ 1.253 + CallArgs args = CallArgsFromVp(argc, vp); 1.254 + return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args); 1.255 +} 1.256 + 1.257 +static bool 1.258 +TryPreserveReflector(JSContext *cx, HandleObject obj) 1.259 +{ 1.260 + if (obj->getClass()->ext.isWrappedNative || 1.261 + (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) || 1.262 + (obj->is<ProxyObject>() && 1.263 + obj->as<ProxyObject>().handler()->family() == GetDOMProxyHandlerFamily())) 1.264 + { 1.265 + JS_ASSERT(cx->runtime()->preserveWrapperCallback); 1.266 + if (!cx->runtime()->preserveWrapperCallback(cx, obj)) { 1.267 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY); 1.268 + return false; 1.269 + } 1.270 + } 1.271 + return true; 1.272 +} 1.273 + 1.274 +static inline void 1.275 +WeakMapPostWriteBarrier(JSRuntime *rt, ObjectValueMap *weakMap, JSObject *key) 1.276 +{ 1.277 +#ifdef JSGC_GENERATIONAL 1.278 + /* 1.279 + * Strip the barriers from the type before inserting into the store buffer. 1.280 + * This will automatically ensure that barriers do not fire during GC. 1.281 + * 1.282 + * Some compilers complain about instantiating the WeakMap class for 1.283 + * unbarriered type arguments, so we cast to a HashMap instead. Because of 1.284 + * WeakMap's multiple inheritace, We need to do this in two stages, first to 1.285 + * the HashMap base class and then to the unbarriered version. 1.286 + */ 1.287 + ObjectValueMap::Base *baseHashMap = static_cast<ObjectValueMap::Base *>(weakMap); 1.288 + 1.289 + typedef HashMap<JSObject *, Value> UnbarrieredMap; 1.290 + UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap); 1.291 + 1.292 + typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> Ref; 1.293 + if (key && IsInsideNursery(rt, key)) 1.294 + rt->gcStoreBuffer.putGeneric(Ref((unbarrieredMap), key)); 1.295 +#endif 1.296 +} 1.297 + 1.298 +MOZ_ALWAYS_INLINE bool 1.299 +SetWeakMapEntryInternal(JSContext *cx, Handle<WeakMapObject*> mapObj, 1.300 + HandleObject key, HandleValue value) 1.301 +{ 1.302 + ObjectValueMap *map = mapObj->getMap(); 1.303 + if (!map) { 1.304 + map = cx->new_<ObjectValueMap>(cx, mapObj.get()); 1.305 + if (!map) 1.306 + return false; 1.307 + if (!map->init()) { 1.308 + js_delete(map); 1.309 + JS_ReportOutOfMemory(cx); 1.310 + return false; 1.311 + } 1.312 + mapObj->setPrivate(map); 1.313 + } 1.314 + 1.315 + // Preserve wrapped native keys to prevent wrapper optimization. 1.316 + if (!TryPreserveReflector(cx, key)) 1.317 + return false; 1.318 + 1.319 + if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) { 1.320 + RootedObject delegate(cx, op(key)); 1.321 + if (delegate && !TryPreserveReflector(cx, delegate)) 1.322 + return false; 1.323 + } 1.324 + 1.325 + JS_ASSERT(key->compartment() == mapObj->compartment()); 1.326 + JS_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment()); 1.327 + if (!map->put(key, value)) { 1.328 + JS_ReportOutOfMemory(cx); 1.329 + return false; 1.330 + } 1.331 + WeakMapPostWriteBarrier(cx->runtime(), map, key.get()); 1.332 + return true; 1.333 +} 1.334 + 1.335 +MOZ_ALWAYS_INLINE bool 1.336 +WeakMap_set_impl(JSContext *cx, CallArgs args) 1.337 +{ 1.338 + JS_ASSERT(IsWeakMap(args.thisv())); 1.339 + 1.340 + if (args.length() < 1) { 1.341 + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, 1.342 + "WeakMap.set", "0", "s"); 1.343 + return false; 1.344 + } 1.345 + RootedObject key(cx, GetKeyArg(cx, args)); 1.346 + if (!key) 1.347 + return false; 1.348 + 1.349 + RootedValue value(cx, (args.length() > 1) ? args[1] : UndefinedValue()); 1.350 + Rooted<JSObject*> thisObj(cx, &args.thisv().toObject()); 1.351 + Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>()); 1.352 + 1.353 + args.rval().setUndefined(); 1.354 + return SetWeakMapEntryInternal(cx, map, key, value); 1.355 +} 1.356 + 1.357 +static bool 1.358 +WeakMap_set(JSContext *cx, unsigned argc, Value *vp) 1.359 +{ 1.360 + CallArgs args = CallArgsFromVp(argc, vp); 1.361 + return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args); 1.362 +} 1.363 + 1.364 +JS_FRIEND_API(bool) 1.365 +JS_NondeterministicGetWeakMapKeys(JSContext *cx, HandleObject objArg, MutableHandleObject ret) 1.366 +{ 1.367 + RootedObject obj(cx, objArg); 1.368 + obj = UncheckedUnwrap(obj); 1.369 + if (!obj || !obj->is<WeakMapObject>()) { 1.370 + ret.set(nullptr); 1.371 + return true; 1.372 + } 1.373 + RootedObject arr(cx, NewDenseEmptyArray(cx)); 1.374 + if (!arr) 1.375 + return false; 1.376 + ObjectValueMap *map = obj->as<WeakMapObject>().getMap(); 1.377 + if (map) { 1.378 + // Prevent GC from mutating the weakmap while iterating. 1.379 + gc::AutoSuppressGC suppress(cx); 1.380 + for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) { 1.381 + RootedObject key(cx, r.front().key()); 1.382 + if (!cx->compartment()->wrap(cx, &key)) 1.383 + return false; 1.384 + if (!NewbornArrayPush(cx, arr, ObjectValue(*key))) 1.385 + return false; 1.386 + } 1.387 + } 1.388 + ret.set(arr); 1.389 + return true; 1.390 +} 1.391 + 1.392 +static void 1.393 +WeakMap_mark(JSTracer *trc, JSObject *obj) 1.394 +{ 1.395 + if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap()) 1.396 + map->trace(trc); 1.397 +} 1.398 + 1.399 +static void 1.400 +WeakMap_finalize(FreeOp *fop, JSObject *obj) 1.401 +{ 1.402 + if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap()) { 1.403 + map->check(); 1.404 +#ifdef DEBUG 1.405 + map->~ObjectValueMap(); 1.406 + memset(static_cast<void *>(map), 0xdc, sizeof(*map)); 1.407 + fop->free_(map); 1.408 +#else 1.409 + fop->delete_(map); 1.410 +#endif 1.411 + } 1.412 +} 1.413 + 1.414 +JS_PUBLIC_API(JSObject*) 1.415 +JS::NewWeakMapObject(JSContext *cx) 1.416 +{ 1.417 + return NewBuiltinClassInstance(cx, &WeakMapObject::class_); 1.418 +} 1.419 + 1.420 +JS_PUBLIC_API(bool) 1.421 +JS::IsWeakMapObject(JSObject *obj) 1.422 +{ 1.423 + return obj->is<WeakMapObject>(); 1.424 +} 1.425 + 1.426 +JS_PUBLIC_API(bool) 1.427 +JS::GetWeakMapEntry(JSContext *cx, HandleObject mapObj, HandleObject key, 1.428 + MutableHandleValue rval) 1.429 +{ 1.430 + CHECK_REQUEST(cx); 1.431 + assertSameCompartment(cx, key); 1.432 + rval.setUndefined(); 1.433 + ObjectValueMap *map = mapObj->as<WeakMapObject>().getMap(); 1.434 + if (!map) 1.435 + return true; 1.436 + if (ObjectValueMap::Ptr ptr = map->lookup(key)) { 1.437 + // Read barrier to prevent an incorrectly gray value from escaping the 1.438 + // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp 1.439 + ExposeValueToActiveJS(ptr->value().get()); 1.440 + rval.set(ptr->value()); 1.441 + } 1.442 + return true; 1.443 +} 1.444 + 1.445 +JS_PUBLIC_API(bool) 1.446 +JS::SetWeakMapEntry(JSContext *cx, HandleObject mapObj, HandleObject key, 1.447 + HandleValue val) 1.448 +{ 1.449 + CHECK_REQUEST(cx); 1.450 + assertSameCompartment(cx, key, val); 1.451 + Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>()); 1.452 + return SetWeakMapEntryInternal(cx, rootedMap, key, val); 1.453 +} 1.454 + 1.455 +static bool 1.456 +WeakMap_construct(JSContext *cx, unsigned argc, Value *vp) 1.457 +{ 1.458 + CallArgs args = CallArgsFromVp(argc, vp); 1.459 + JSObject *obj = NewBuiltinClassInstance(cx, &WeakMapObject::class_); 1.460 + if (!obj) 1.461 + return false; 1.462 + 1.463 + args.rval().setObject(*obj); 1.464 + return true; 1.465 +} 1.466 + 1.467 +const Class WeakMapObject::class_ = { 1.468 + "WeakMap", 1.469 + JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | 1.470 + JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap), 1.471 + JS_PropertyStub, /* addProperty */ 1.472 + JS_DeletePropertyStub, /* delProperty */ 1.473 + JS_PropertyStub, /* getProperty */ 1.474 + JS_StrictPropertyStub, /* setProperty */ 1.475 + JS_EnumerateStub, 1.476 + JS_ResolveStub, 1.477 + JS_ConvertStub, 1.478 + WeakMap_finalize, 1.479 + nullptr, /* call */ 1.480 + nullptr, /* construct */ 1.481 + nullptr, /* xdrObject */ 1.482 + WeakMap_mark 1.483 +}; 1.484 + 1.485 +static const JSFunctionSpec weak_map_methods[] = { 1.486 + JS_FN("has", WeakMap_has, 1, 0), 1.487 + JS_FN("get", WeakMap_get, 2, 0), 1.488 + JS_FN("delete", WeakMap_delete, 1, 0), 1.489 + JS_FN("set", WeakMap_set, 2, 0), 1.490 + JS_FN("clear", WeakMap_clear, 0, 0), 1.491 + JS_FS_END 1.492 +}; 1.493 + 1.494 +JSObject * 1.495 +js_InitWeakMapClass(JSContext *cx, HandleObject obj) 1.496 +{ 1.497 + JS_ASSERT(obj->isNative()); 1.498 + 1.499 + Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); 1.500 + 1.501 + RootedObject weakMapProto(cx, global->createBlankPrototype(cx, &WeakMapObject::class_)); 1.502 + if (!weakMapProto) 1.503 + return nullptr; 1.504 + 1.505 + RootedFunction ctor(cx, global->createConstructor(cx, WeakMap_construct, 1.506 + cx->names().WeakMap, 0)); 1.507 + if (!ctor) 1.508 + return nullptr; 1.509 + 1.510 + if (!LinkConstructorAndPrototype(cx, ctor, weakMapProto)) 1.511 + return nullptr; 1.512 + 1.513 + if (!DefinePropertiesAndBrand(cx, weakMapProto, nullptr, weak_map_methods)) 1.514 + return nullptr; 1.515 + 1.516 + if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, weakMapProto)) 1.517 + return nullptr; 1.518 + return weakMapProto; 1.519 +} 1.520 +