tor-browser

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

PlainObject.cpp (10708B)


      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 /*
      8 * JS object implementation.
      9 */
     10 
     11 #include "vm/PlainObject-inl.h"
     12 
     13 #include "mozilla/Assertions.h"  // MOZ_ASSERT
     14 
     15 #include "jspubtd.h"  // JSProto_Object
     16 
     17 #include "ds/IdValuePair.h"  // js::IdValuePair
     18 #include "gc/AllocKind.h"    // js::gc::AllocKind
     19 #include "vm/JSContext.h"    // JSContext
     20 #include "vm/JSFunction.h"   // JSFunction
     21 #include "vm/JSObject.h"     // JSObject, js::GetPrototypeFromConstructor
     22 #include "vm/TaggedProto.h"  // js::TaggedProto
     23 
     24 #include "vm/JSFunction-inl.h"
     25 #include "vm/JSObject-inl.h"  // js::NewObjectWithGroup, js::NewObjectGCKind
     26 
     27 using namespace js;
     28 
     29 using JS::Handle;
     30 using JS::Rooted;
     31 
     32 static MOZ_ALWAYS_INLINE SharedShape* GetPlainObjectShapeWithProto(
     33    JSContext* cx, JSObject* proto, gc::AllocKind kind) {
     34  MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(&PlainObject::class_) == 0,
     35             "all slots can be used for properties");
     36  uint32_t nfixed = GetGCKindSlots(kind);
     37  return SharedShape::getInitialShape(cx, &PlainObject::class_, cx->realm(),
     38                                      TaggedProto(proto), nfixed);
     39 }
     40 
     41 SharedShape* js::ThisShapeForFunction(JSContext* cx, Handle<JSFunction*> callee,
     42                                      Handle<JSObject*> newTarget) {
     43  MOZ_ASSERT(cx->realm() == callee->realm());
     44  MOZ_ASSERT(!callee->constructorNeedsUninitializedThis());
     45 
     46  Rooted<JSObject*> proto(cx);
     47  if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Object, &proto)) {
     48    return nullptr;
     49  }
     50 
     51  js::gc::AllocKind allocKind = NewObjectGCKind();
     52  if (!JSFunction::getAllocKindForThis(cx, callee, allocKind)) {
     53    return nullptr;
     54  }
     55 
     56  SharedShape* res;
     57  if (proto && proto != cx->global()->maybeGetPrototype(JSProto_Object)) {
     58    res = GetPlainObjectShapeWithProto(cx, proto, allocKind);
     59  } else {
     60    res = GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind);
     61  }
     62 
     63  MOZ_ASSERT_IF(res, res->realm() == callee->realm());
     64 
     65  return res;
     66 }
     67 
     68 #ifdef DEBUG
     69 void PlainObject::assertHasNoNonWritableOrAccessorPropExclProto() const {
     70  // Check the most recent MaxCount properties to not slow down debug builds too
     71  // much.
     72  static constexpr size_t MaxCount = 8;
     73 
     74  size_t count = 0;
     75  PropertyName* protoName = runtimeFromMainThread()->commonNames->proto_;
     76 
     77  for (ShapePropertyIter<NoGC> iter(shape()); !iter.done(); iter++) {
     78    // __proto__ is always allowed.
     79    if (iter->key().isAtom(protoName)) {
     80      continue;
     81    }
     82 
     83    MOZ_ASSERT(iter->isDataProperty());
     84    MOZ_ASSERT(iter->writable());
     85 
     86    count++;
     87    if (count > MaxCount) {
     88      return;
     89    }
     90  }
     91 }
     92 #endif
     93 
     94 // static
     95 PlainObject* PlainObject::createWithTemplateFromDifferentRealm(
     96    JSContext* cx, Handle<PlainObject*> templateObject) {
     97  MOZ_ASSERT(cx->realm() != templateObject->realm(),
     98             "Use createWithTemplate() for same-realm objects");
     99 
    100  // Currently only implemented for null-proto.
    101  MOZ_ASSERT(templateObject->staticPrototype() == nullptr);
    102 
    103  // The object mustn't be in dictionary mode.
    104  MOZ_ASSERT(!templateObject->shape()->isDictionary());
    105 
    106  TaggedProto proto = TaggedProto(nullptr);
    107  SharedShape* templateShape = templateObject->sharedShape();
    108  Rooted<SharedPropMap*> map(cx, templateShape->propMap());
    109 
    110  Rooted<SharedShape*> shape(
    111      cx, SharedShape::getInitialOrPropMapShape(
    112              cx, &PlainObject::class_, cx->realm(), proto,
    113              templateShape->numFixedSlots(), map,
    114              templateShape->propMapLength(), templateShape->objectFlags()));
    115  if (!shape) {
    116    return nullptr;
    117  }
    118  return createWithShape(cx, shape);
    119 }
    120 
    121 // static
    122 SharedShape* GlobalObject::createPlainObjectShapeWithDefaultProto(
    123    JSContext* cx, gc::AllocKind kind) {
    124  PlainObjectSlotsKind slotsKind = PlainObjectSlotsKindFromAllocKind(kind);
    125  GCPtr<SharedShape*>& shapeRef =
    126      cx->global()->data().plainObjectShapesWithDefaultProto[slotsKind];
    127  MOZ_ASSERT(!shapeRef);
    128 
    129  JSObject* proto = &cx->global()->getObjectPrototype();
    130  SharedShape* shape = GetPlainObjectShapeWithProto(cx, proto, kind);
    131  if (!shape) {
    132    return nullptr;
    133  }
    134 
    135  shapeRef.init(shape);
    136  return shape;
    137 }
    138 
    139 PlainObject* js::NewPlainObject(JSContext* cx, NewObjectKind newKind) {
    140  constexpr gc::AllocKind allocKind = gc::AllocKind::OBJECT0;
    141  MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_) == allocKind);
    142 
    143  Rooted<SharedShape*> shape(
    144      cx, GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind));
    145  if (!shape) {
    146    return nullptr;
    147  }
    148 
    149  return PlainObject::createWithShape(cx, shape, allocKind, newKind);
    150 }
    151 
    152 PlainObject* js::NewPlainObjectWithAllocKind(JSContext* cx,
    153                                             gc::AllocKind allocKind,
    154                                             NewObjectKind newKind) {
    155  Rooted<SharedShape*> shape(
    156      cx, GlobalObject::getPlainObjectShapeWithDefaultProto(cx, allocKind));
    157  if (!shape) {
    158    return nullptr;
    159  }
    160 
    161  return PlainObject::createWithShape(cx, shape, allocKind, newKind);
    162 }
    163 
    164 PlainObject* js::NewPlainObjectWithProto(JSContext* cx, HandleObject proto,
    165                                         NewObjectKind newKind) {
    166  // Use a faster path if |proto| is %Object.prototype% (the common case).
    167  if (proto && proto == cx->global()->maybeGetPrototype(JSProto_Object)) {
    168    return NewPlainObject(cx, newKind);
    169  }
    170 
    171  constexpr gc::AllocKind allocKind = gc::AllocKind::OBJECT0;
    172  MOZ_ASSERT(gc::GetGCObjectKind(&PlainObject::class_) == allocKind);
    173 
    174  Rooted<SharedShape*> shape(
    175      cx, GetPlainObjectShapeWithProto(cx, proto, allocKind));
    176  if (!shape) {
    177    return nullptr;
    178  }
    179 
    180  return PlainObject::createWithShape(cx, shape, allocKind, newKind);
    181 }
    182 
    183 PlainObject* js::NewPlainObjectWithProtoAndAllocKind(JSContext* cx,
    184                                                     HandleObject proto,
    185                                                     gc::AllocKind allocKind,
    186                                                     NewObjectKind newKind) {
    187  // Use a faster path if |proto| is %Object.prototype% (the common case).
    188  if (proto && proto == cx->global()->maybeGetPrototype(JSProto_Object)) {
    189    return NewPlainObjectWithAllocKind(cx, allocKind, newKind);
    190  }
    191 
    192  Rooted<SharedShape*> shape(
    193      cx, GetPlainObjectShapeWithProto(cx, proto, allocKind));
    194  if (!shape) {
    195    return nullptr;
    196  }
    197 
    198  return PlainObject::createWithShape(cx, shape, allocKind, newKind);
    199 }
    200 
    201 void js::NewPlainObjectWithPropsCache::add(SharedShape* shape) {
    202  MOZ_ASSERT(shape);
    203  MOZ_ASSERT(shape->slotSpan() > 0);
    204  for (size_t i = NumEntries - 1; i > 0; i--) {
    205    entries_[i] = entries_[i - 1];
    206  }
    207  entries_[0] = shape;
    208 }
    209 
    210 static bool ShapeMatches(Handle<IdValueVector> properties, SharedShape* shape) {
    211  if (shape->slotSpan() != properties.length()) {
    212    return false;
    213  }
    214  SharedShapePropertyIter<NoGC> iter(shape);
    215  for (size_t i = properties.length(); i > 0; i--) {
    216    MOZ_ASSERT(iter->isDataProperty());
    217    MOZ_ASSERT(iter->flags() == PropertyFlags::defaultDataPropFlags);
    218    if (properties[i - 1].get().id != iter->key()) {
    219      return false;
    220    }
    221    iter++;
    222  }
    223  MOZ_ASSERT(iter.done());
    224  return true;
    225 }
    226 
    227 SharedShape* js::NewPlainObjectWithPropsCache::lookup(
    228    Handle<IdValueVector> properties) const {
    229  for (size_t i = 0; i < NumEntries; i++) {
    230    SharedShape* shape = entries_[i];
    231    if (shape && ShapeMatches(properties, shape)) {
    232      return shape;
    233    }
    234  }
    235  return nullptr;
    236 }
    237 
    238 enum class KeysKind { UniqueNames, Unknown };
    239 
    240 template <KeysKind Kind>
    241 static PlainObject* NewPlainObjectWithProperties(
    242    JSContext* cx, Handle<IdValueVector> properties, NewObjectKind newKind) {
    243  auto& cache = cx->realm()->newPlainObjectWithPropsCache;
    244 
    245  // If we recently created an object with these properties, we can use that
    246  // Shape directly.
    247  if (SharedShape* shape = cache.lookup(properties)) {
    248    Rooted<SharedShape*> shapeRoot(cx, shape);
    249    PlainObject* obj = PlainObject::createWithShape(cx, shapeRoot, newKind);
    250    if (!obj) {
    251      return nullptr;
    252    }
    253    MOZ_ASSERT(obj->slotSpan() == properties.length());
    254    for (size_t i = 0; i < properties.length(); i++) {
    255      obj->initSlot(i, properties[i].get().value);
    256    }
    257    return obj;
    258  }
    259 
    260  gc::AllocKind allocKind = gc::GetGCObjectKind(properties.length());
    261  Rooted<PlainObject*> obj(cx,
    262                           NewPlainObjectWithAllocKind(cx, allocKind, newKind));
    263  if (!obj) {
    264    return nullptr;
    265  }
    266 
    267  if (properties.empty()) {
    268    return obj;
    269  }
    270 
    271  Rooted<PropertyKey> key(cx);
    272  Rooted<Value> value(cx);
    273  bool canCache = true;
    274 
    275  for (const auto& prop : properties) {
    276    key = prop.id;
    277    value = prop.value;
    278 
    279    // Integer keys may need to be stored in dense elements. This is uncommon so
    280    // just fall back to NativeDefineDataProperty.
    281    if constexpr (Kind == KeysKind::Unknown) {
    282      if (MOZ_UNLIKELY(key.isInt())) {
    283        canCache = false;
    284        if (!NativeDefineDataProperty(cx, obj, key, value, JSPROP_ENUMERATE)) {
    285          return nullptr;
    286        }
    287        continue;
    288      }
    289    }
    290 
    291    MOZ_ASSERT(key.isAtom() || key.isSymbol());
    292 
    293    // Check for duplicate keys. In this case we must overwrite the earlier
    294    // property value.
    295    if constexpr (Kind == KeysKind::UniqueNames) {
    296      MOZ_ASSERT(!obj->containsPure(key));
    297    } else {
    298      mozilla::Maybe<PropertyInfo> prop = obj->lookup(cx, key);
    299      if (MOZ_UNLIKELY(prop)) {
    300        canCache = false;
    301        MOZ_ASSERT(prop->isDataProperty());
    302        obj->setSlot(prop->slot(), value);
    303        continue;
    304      }
    305    }
    306 
    307    if (!AddDataPropertyToPlainObject(cx, obj, key, value)) {
    308      return nullptr;
    309    }
    310  }
    311 
    312  if (canCache && !obj->inDictionaryMode()) {
    313    MOZ_ASSERT(obj->getDenseInitializedLength() == 0);
    314    MOZ_ASSERT(obj->slotSpan() == properties.length());
    315    cache.add(obj->sharedShape());
    316  }
    317 
    318  return obj;
    319 }
    320 
    321 PlainObject* js::NewPlainObjectWithUniqueNames(JSContext* cx,
    322                                               Handle<IdValueVector> properties,
    323                                               NewObjectKind newKind) {
    324  return NewPlainObjectWithProperties<KeysKind::UniqueNames>(cx, properties,
    325                                                             newKind);
    326 }
    327 
    328 PlainObject* js::NewPlainObjectWithMaybeDuplicateKeys(
    329    JSContext* cx, Handle<IdValueVector> properties, NewObjectKind newKind) {
    330  return NewPlainObjectWithProperties<KeysKind::Unknown>(cx, properties,
    331                                                         newKind);
    332 }