WeakMapObject.cpp (13758B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 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 "builtin/WeakMapObject-inl.h" 8 9 #include "builtin/WeakSetObject.h" 10 #include "gc/GC.h" 11 #include "gc/GCContext.h" 12 #include "jit/InlinableNatives.h" 13 #include "js/friend/ErrorMessages.h" // JSMSG_* 14 #include "js/PropertySpec.h" 15 #include "js/WeakMap.h" 16 #include "vm/Compartment.h" 17 #include "vm/JSContext.h" 18 #include "vm/SelfHosting.h" 19 20 #include "builtin/MapObject-inl.h" 21 #include "gc/GCContext-inl.h" 22 #include "gc/WeakMap-inl.h" 23 #include "vm/NativeObject-inl.h" 24 25 using namespace js; 26 27 /* static */ MOZ_ALWAYS_INLINE bool WeakMapObject::is(HandleValue v) { 28 return v.isObject() && v.toObject().is<WeakMapObject>(); 29 } 30 31 /* static */ MOZ_ALWAYS_INLINE bool WeakMapObject::has_impl( 32 JSContext* cx, const CallArgs& args) { 33 MOZ_ASSERT(is(args.thisv())); 34 35 if (!CanBeHeldWeakly(args.get(0))) { 36 args.rval().setBoolean(false); 37 return true; 38 } 39 40 if (Map* map = args.thisv().toObject().as<WeakMapObject>().getMap()) { 41 Value key = args[0]; 42 if (map->has(key)) { 43 args.rval().setBoolean(true); 44 return true; 45 } 46 } 47 48 args.rval().setBoolean(false); 49 return true; 50 } 51 52 /* static */ 53 bool WeakMapObject::has(JSContext* cx, unsigned argc, Value* vp) { 54 CallArgs args = CallArgsFromVp(argc, vp); 55 return CallNonGenericMethod<WeakMapObject::is, WeakMapObject::has_impl>(cx, 56 args); 57 } 58 59 // static 60 bool WeakMapObject::hasObject(WeakMapObject* weakMap, JSObject* obj) { 61 AutoUnsafeCallWithABI unsafe; 62 Map* map = weakMap->getMap(); 63 return map && map->has(ObjectValue(*obj)); 64 } 65 66 /* static */ MOZ_ALWAYS_INLINE bool WeakMapObject::get_impl( 67 JSContext* cx, const CallArgs& args) { 68 MOZ_ASSERT(WeakMapObject::is(args.thisv())); 69 70 if (!CanBeHeldWeakly(args.get(0))) { 71 args.rval().setUndefined(); 72 return true; 73 } 74 75 if (Map* map = args.thisv().toObject().as<WeakMapObject>().getMap()) { 76 Value key = args[0]; 77 if (Map::Ptr ptr = map->lookup(key)) { 78 args.rval().set(ptr->value()); 79 return true; 80 } 81 } 82 83 args.rval().setUndefined(); 84 return true; 85 } 86 87 /* static */ 88 bool WeakMapObject::get(JSContext* cx, unsigned argc, Value* vp) { 89 CallArgs args = CallArgsFromVp(argc, vp); 90 return CallNonGenericMethod<WeakMapObject::is, WeakMapObject::get_impl>(cx, 91 args); 92 } 93 94 // static 95 void WeakMapObject::getObject(WeakMapObject* weakMap, JSObject* obj, 96 Value* result) { 97 AutoUnsafeCallWithABI unsafe; 98 if (Map* map = weakMap->getMap()) { 99 if (Map::Ptr ptr = map->lookup(ObjectValue(*obj))) { 100 *result = ptr->value(); 101 return; 102 } 103 } 104 *result = UndefinedValue(); 105 } 106 107 /* static */ MOZ_ALWAYS_INLINE bool WeakMapObject::delete_impl( 108 JSContext* cx, const CallArgs& args) { 109 MOZ_ASSERT(WeakMapObject::is(args.thisv())); 110 111 if (!CanBeHeldWeakly(args.get(0))) { 112 args.rval().setBoolean(false); 113 return true; 114 } 115 116 if (Map* map = args.thisv().toObject().as<WeakMapObject>().getMap()) { 117 Value key = args[0]; 118 // The lookup here is only used for the removal, so we can skip the read 119 // barrier. This is not very important for performance, but makes it easier 120 // to test nonbarriered removal from internal weakmaps (eg Debugger maps.) 121 if (Map::Ptr ptr = map->lookupUnbarriered(key)) { 122 map->remove(ptr); 123 args.rval().setBoolean(true); 124 return true; 125 } 126 } 127 128 args.rval().setBoolean(false); 129 return true; 130 } 131 132 /* static */ 133 bool WeakMapObject::delete_(JSContext* cx, unsigned argc, Value* vp) { 134 CallArgs args = CallArgsFromVp(argc, vp); 135 return CallNonGenericMethod<WeakMapObject::is, WeakMapObject::delete_impl>( 136 cx, args); 137 } 138 139 static bool EnsureValidWeakMapKey(JSContext* cx, Handle<Value> keyVal) { 140 if (MOZ_UNLIKELY(!CanBeHeldWeakly(keyVal))) { 141 unsigned errorNum = GetErrorNumber(true); 142 ReportValueError(cx, errorNum, JSDVG_IGNORE_STACK, keyVal, nullptr); 143 return false; 144 } 145 return true; 146 } 147 148 static bool SetWeakMapEntryImpl(JSContext* cx, Handle<WeakMapObject*> mapObj, 149 Handle<Value> keyVal, Handle<Value> value) { 150 if (!EnsureValidWeakMapKey(cx, keyVal)) { 151 return false; 152 } 153 return WeakCollectionPutEntryInternal(cx, mapObj, keyVal, value); 154 } 155 156 /* static */ MOZ_ALWAYS_INLINE bool WeakMapObject::set_impl( 157 JSContext* cx, const CallArgs& args) { 158 MOZ_ASSERT(WeakMapObject::is(args.thisv())); 159 160 Rooted<WeakMapObject*> map(cx, &args.thisv().toObject().as<WeakMapObject>()); 161 if (!SetWeakMapEntryImpl(cx, map, args.get(0), args.get(1))) { 162 return false; 163 } 164 165 args.rval().set(args.thisv()); 166 return true; 167 } 168 169 /* static */ 170 bool WeakMapObject::set(JSContext* cx, unsigned argc, Value* vp) { 171 CallArgs args = CallArgsFromVp(argc, vp); 172 return CallNonGenericMethod<WeakMapObject::is, WeakMapObject::set_impl>(cx, 173 args); 174 } 175 176 static bool GetOrAddWeakMapEntry(JSContext* cx, Handle<WeakMapObject*> mapObj, 177 Handle<Value> key, Handle<Value> value, 178 MutableHandleValue rval) { 179 if (!EnsureValidWeakMapKey(cx, key)) { 180 return false; 181 } 182 183 if (!EnsureObjectHasWeakMap(cx, mapObj)) { 184 return false; 185 } 186 187 WeakCollectionObject::Map* map = mapObj->getMap(); 188 auto addPtr = map->lookupForAdd(key); 189 if (!addPtr) { 190 if (!PreserveReflectorAndAssertValidEntry(cx, mapObj, key, value)) { 191 return false; 192 } 193 if (!map->add(addPtr, key, value)) { 194 JS_ReportOutOfMemory(cx); 195 return false; 196 } 197 } 198 rval.set(addPtr->value()); 199 return true; 200 } 201 202 /* static */ MOZ_ALWAYS_INLINE bool WeakMapObject::getOrInsert_impl( 203 JSContext* cx, const CallArgs& args) { 204 MOZ_ASSERT(WeakMapObject::is(args.thisv())); 205 206 Rooted<WeakMapObject*> map(cx, &args.thisv().toObject().as<WeakMapObject>()); 207 return GetOrAddWeakMapEntry(cx, map, args.get(0), args.get(1), args.rval()); 208 } 209 210 /* static */ 211 bool WeakMapObject::getOrInsert(JSContext* cx, unsigned argc, Value* vp) { 212 CallArgs args = CallArgsFromVp(argc, vp); 213 return CallNonGenericMethod<WeakMapObject::is, 214 WeakMapObject::getOrInsert_impl>(cx, args); 215 } 216 217 size_t WeakCollectionObject::sizeOfExcludingThis( 218 mozilla::MallocSizeOf aMallocSizeOf) { 219 Map* map = getMap(); 220 return map ? map->sizeOfIncludingThis(aMallocSizeOf) : 0; 221 } 222 223 size_t WeakCollectionObject::nondeterministicGetSize() { 224 Map* map = getMap(); 225 if (!map) { 226 return 0; 227 } 228 229 return map->count(); 230 } 231 232 bool WeakCollectionObject::nondeterministicGetKeys( 233 JSContext* cx, Handle<WeakCollectionObject*> obj, MutableHandleObject ret) { 234 RootedObject arr(cx, NewDenseEmptyArray(cx)); 235 if (!arr) { 236 return false; 237 } 238 if (Map* map = obj->getMap()) { 239 // Prevent GC from mutating the weakmap while iterating. 240 gc::AutoSuppressGC suppress(cx); 241 for (Map::Range r = map->all(); !r.empty(); r.popFront()) { 242 const auto& key = r.front().key(); 243 MOZ_ASSERT(key.isObject() || key.isSymbol()); 244 JS::ExposeValueToActiveJS(key); 245 RootedValue keyVal(cx, key); 246 if (!cx->compartment()->wrap(cx, &keyVal)) { 247 return false; 248 } 249 if (!NewbornArrayPush(cx, arr, keyVal)) { 250 return false; 251 } 252 } 253 } 254 ret.set(arr); 255 return true; 256 } 257 258 JS_PUBLIC_API bool JS_NondeterministicGetWeakMapKeys(JSContext* cx, 259 HandleObject objArg, 260 MutableHandleObject ret) { 261 RootedObject obj(cx, UncheckedUnwrap(objArg)); 262 if (!obj || !obj->is<WeakMapObject>()) { 263 ret.set(nullptr); 264 return true; 265 } 266 return WeakCollectionObject::nondeterministicGetKeys( 267 cx, obj.as<WeakCollectionObject>(), ret); 268 } 269 270 /* static */ 271 void WeakCollectionObject::trace(JSTracer* trc, JSObject* obj) { 272 if (Map* map = obj->as<WeakCollectionObject>().getMap()) { 273 map->trace(trc); 274 } 275 } 276 277 /* static */ 278 void WeakCollectionObject::finalize(JS::GCContext* gcx, JSObject* obj) { 279 if (Map* map = obj->as<WeakCollectionObject>().getMap()) { 280 gcx->delete_(obj, map, MemoryUse::WeakMapObject); 281 } 282 } 283 284 JS_PUBLIC_API JSObject* JS::NewWeakMapObject(JSContext* cx) { 285 return NewBuiltinClassInstance<WeakMapObject>(cx); 286 } 287 288 JS_PUBLIC_API bool JS::IsWeakMapObject(JSObject* obj) { 289 return obj->is<WeakMapObject>(); 290 } 291 292 JS_PUBLIC_API bool JS::GetWeakMapEntry(JSContext* cx, HandleObject mapObj, 293 HandleValue key, 294 MutableHandleValue rval) { 295 CHECK_THREAD(cx); 296 cx->check(key); 297 rval.setUndefined(); 298 299 if (!CanBeHeldWeakly(key)) { 300 return true; 301 } 302 303 WeakMapObject::Map* map = mapObj->as<WeakMapObject>().getMap(); 304 if (!map) { 305 return true; 306 } 307 308 if (auto ptr = map->lookup(key)) { 309 rval.set(ptr->value()); 310 } 311 return true; 312 } 313 314 JS_PUBLIC_API bool JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, 315 HandleValue key, HandleValue val) { 316 CHECK_THREAD(cx); 317 cx->check(key, val); 318 return SetWeakMapEntryImpl(cx, mapObj.as<WeakMapObject>(), key, val); 319 } 320 321 // static 322 bool WeakMapObject::tryOptimizeCtorWithIterable(JSContext* cx, 323 Handle<WeakMapObject*> obj, 324 Handle<Value> iterableVal, 325 bool* optimized) { 326 MOZ_ASSERT(!iterableVal.isNullOrUndefined()); 327 MOZ_ASSERT(!*optimized); 328 329 if (!CanOptimizeMapOrSetCtorWithIterable<JSProto_WeakMap>(WeakMapObject::set, 330 obj, cx)) { 331 return true; 332 } 333 334 if (!iterableVal.isObject()) { 335 return true; 336 } 337 JSObject* iterable = &iterableVal.toObject(); 338 339 // Fast path for `new WeakMap(array)`. 340 if (IsOptimizableArrayForMapOrSetCtor<MapOrSet::Map>(iterable, cx)) { 341 RootedValue keyVal(cx); 342 RootedValue value(cx); 343 Rooted<ArrayObject*> array(cx, &iterable->as<ArrayObject>()); 344 uint32_t len = array->getDenseInitializedLength(); 345 346 for (uint32_t index = 0; index < len; index++) { 347 Value element = array->getDenseElement(index); 348 MOZ_ASSERT(IsPackedArray(&element.toObject())); 349 350 auto* elementArray = &element.toObject().as<ArrayObject>(); 351 keyVal.set(elementArray->getDenseElement(0)); 352 value.set(elementArray->getDenseElement(1)); 353 MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE)); 354 MOZ_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE)); 355 356 if (!SetWeakMapEntryImpl(cx, obj, keyVal, value)) { 357 return false; 358 } 359 } 360 361 *optimized = true; 362 return true; 363 } 364 365 return true; 366 } 367 368 /* static */ 369 bool WeakMapObject::construct(JSContext* cx, unsigned argc, Value* vp) { 370 CallArgs args = CallArgsFromVp(argc, vp); 371 372 // ES6 draft rev 31 (15 Jan 2015) 23.3.1.1 step 1. 373 if (!ThrowIfNotConstructing(cx, args, "WeakMap")) { 374 return false; 375 } 376 377 RootedObject proto(cx); 378 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WeakMap, &proto)) { 379 return false; 380 } 381 382 Rooted<WeakMapObject*> obj(cx, 383 NewObjectWithClassProto<WeakMapObject>(cx, proto)); 384 if (!obj) { 385 return false; 386 } 387 388 // Steps 5-6, 11. 389 if (!args.get(0).isNullOrUndefined()) { 390 Handle<Value> iterable = args[0]; 391 bool optimized = false; 392 if (!tryOptimizeCtorWithIterable(cx, obj, iterable, &optimized)) { 393 return false; 394 } 395 if (!optimized) { 396 FixedInvokeArgs<1> args2(cx); 397 args2[0].set(iterable); 398 399 RootedValue thisv(cx, ObjectValue(*obj)); 400 if (!CallSelfHostedFunction(cx, cx->names().WeakMapConstructorInit, thisv, 401 args2, args2.rval())) { 402 return false; 403 } 404 } 405 } 406 407 args.rval().setObject(*obj); 408 return true; 409 } 410 411 const JSClassOps WeakCollectionObject::classOps_ = { 412 nullptr, // addProperty 413 nullptr, // delProperty 414 nullptr, // enumerate 415 nullptr, // newEnumerate 416 nullptr, // resolve 417 nullptr, // mayResolve 418 &finalize, // finalize 419 nullptr, // call 420 nullptr, // construct 421 &trace, // trace 422 }; 423 424 const ClassSpec WeakMapObject::classSpec_ = { 425 GenericCreateConstructor<WeakMapObject::construct, 0, 426 gc::AllocKind::FUNCTION>, 427 GenericCreatePrototype<WeakMapObject>, 428 nullptr, 429 nullptr, 430 WeakMapObject::methods, 431 WeakMapObject::properties, 432 GenericFinishInit<WhichHasRealmFuseProperty::Proto>, 433 }; 434 435 const JSClass WeakMapObject::class_ = { 436 "WeakMap", 437 JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | 438 JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap) | JSCLASS_BACKGROUND_FINALIZE, 439 &WeakCollectionObject::classOps_, 440 &WeakMapObject::classSpec_, 441 }; 442 443 const JSClass WeakMapObject::protoClass_ = { 444 "WeakMap.prototype", 445 JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap), 446 JS_NULL_CLASS_OPS, 447 &WeakMapObject::classSpec_, 448 }; 449 450 const JSPropertySpec WeakMapObject::properties[] = { 451 JS_STRING_SYM_PS(toStringTag, "WeakMap", JSPROP_READONLY), 452 JS_PS_END, 453 }; 454 455 const JSFunctionSpec WeakMapObject::methods[] = { 456 JS_INLINABLE_FN("has", has, 1, 0, WeakMapHas), 457 JS_INLINABLE_FN("get", get, 1, 0, WeakMapGet), 458 JS_FN("delete", delete_, 1, 0), 459 JS_FN("set", set, 2, 0), 460 JS_FN("getOrInsert", getOrInsert, 2, 0), 461 JS_SELF_HOSTED_FN("getOrInsertComputed", "WeakMapGetOrInsertComputed", 2, 462 0), 463 JS_FS_END, 464 };