tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 };