tor-browser

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

ObjLiteral.cpp (17409B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sw=2 et tw=0 ft=c:
      3 *
      4 * This Source Code Form is subject to the terms of the Mozilla Public
      5 * License, v. 2.0. If a copy of the MPL was not distributed with this
      6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      7 
      8 #include "frontend/ObjLiteral.h"
      9 
     10 #include "mozilla/DebugOnly.h"  // mozilla::DebugOnly
     11 #include "mozilla/HashTable.h"  // mozilla::HashSet
     12 
     13 #include "NamespaceImports.h"  // ValueVector
     14 
     15 #include "builtin/Array.h"  // NewDenseCopiedArray
     16 #include "frontend/CompilationStencil.h"  // frontend::{CompilationStencil, CompilationAtomCache}
     17 #include "frontend/ParserAtom.h"                   // frontend::ParserAtomTable
     18 #include "frontend/TaggedParserAtomIndexHasher.h"  // TaggedParserAtomIndexHasher
     19 #include "gc/AllocKind.h"                          // gc::AllocKind
     20 #include "js/Id.h"                                 // JS::PropertyKey
     21 #include "js/Printer.h"                            // js::Fprinter
     22 #include "js/RootingAPI.h"                         // Rooted
     23 #include "js/TypeDecls.h"                          // RootedId, RootedValue
     24 #include "vm/JSObject.h"                           // TenuredObject
     25 #include "vm/JSONPrinter.h"                        // js::JSONPrinter
     26 #include "vm/NativeObject.h"                       // NativeDefineDataProperty
     27 #include "vm/PlainObject.h"                        // PlainObject
     28 
     29 #include "gc/ObjectKind-inl.h"    // gc::GetGCObjectKind
     30 #include "vm/JSAtomUtils-inl.h"   // AtomToId
     31 #include "vm/JSObject-inl.h"      // NewBuiltinClassInstance
     32 #include "vm/NativeObject-inl.h"  // AddDataPropertyNonDelegate
     33 
     34 namespace js {
     35 
     36 bool ObjLiteralWriter::checkForDuplicatedNames(FrontendContext* fc) {
     37  if (!mightContainDuplicatePropertyNames_) {
     38    return true;
     39  }
     40 
     41  // If possible duplicate property names are detected by bloom-filter,
     42  // check again with hash-set.
     43 
     44  mozilla::HashSet<frontend::TaggedParserAtomIndex,
     45                   frontend::TaggedParserAtomIndexHasher>
     46      propNameSet;
     47 
     48  if (!propNameSet.reserve(propertyCount_)) {
     49    js::ReportOutOfMemory(fc);
     50    return false;
     51  }
     52 
     53  ObjLiteralReader reader(getCode());
     54 
     55  while (true) {
     56    ObjLiteralInsn insn;
     57    if (!reader.readInsn(&insn)) {
     58      break;
     59    }
     60 
     61    if (insn.getKey().isArrayIndex()) {
     62      continue;
     63    }
     64 
     65    auto propName = insn.getKey().getAtomIndex();
     66 
     67    auto p = propNameSet.lookupForAdd(propName);
     68    if (p) {
     69      flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
     70      break;
     71    }
     72 
     73    // Already reserved above and doesn't fail.
     74    MOZ_ALWAYS_TRUE(propNameSet.add(p, propName));
     75  }
     76 
     77  return true;
     78 }
     79 
     80 static void InterpretObjLiteralValue(
     81    JSContext* cx, const frontend::CompilationAtomCache& atomCache,
     82    const ObjLiteralInsn& insn, MutableHandleValue valOut) {
     83  switch (insn.getOp()) {
     84    case ObjLiteralOpcode::ConstValue:
     85      valOut.set(insn.getConstValue());
     86      return;
     87    case ObjLiteralOpcode::ConstString: {
     88      frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
     89      JSString* str = atomCache.getExistingStringAt(cx, index);
     90      MOZ_ASSERT(str);
     91      valOut.setString(str);
     92      return;
     93    }
     94    case ObjLiteralOpcode::Null:
     95      valOut.setNull();
     96      return;
     97    case ObjLiteralOpcode::Undefined:
     98      valOut.setUndefined();
     99      return;
    100    case ObjLiteralOpcode::True:
    101      valOut.setBoolean(true);
    102      return;
    103    case ObjLiteralOpcode::False:
    104      valOut.setBoolean(false);
    105      return;
    106    default:
    107      MOZ_CRASH("Unexpected object-literal instruction opcode");
    108  }
    109 }
    110 
    111 static uint32_t CountNonIndexPropertiesUpTo(
    112    const mozilla::Span<const uint8_t> literalInsns, uint32_t limit) {
    113  ObjLiteralReader reader(literalInsns);
    114 
    115  uint32_t count = 0;
    116  while (count < limit) {
    117    // Make sure `insn` doesn't live across GC.
    118    ObjLiteralInsn insn;
    119    if (!reader.readInsn(&insn)) {
    120      break;
    121    }
    122    if (!insn.getKey().isArrayIndex()) {
    123      count++;
    124    }
    125  }
    126 
    127  return count;
    128 }
    129 
    130 enum class PropertySetKind {
    131  UniqueNames,
    132  Normal,
    133 };
    134 
    135 template <PropertySetKind kind>
    136 static gc::AllocKind AllocKindForObjectLiteral(
    137    const mozilla::Span<const uint8_t> literalInsns, uint32_t propCount) {
    138  // Use NewObjectGCKind for empty object literals to reserve some fixed slots
    139  // for new properties. This improves performance for common patterns such as
    140  // |Object.assign({}, ...)|.
    141  if (propCount == 0) {
    142    return NewObjectGCKind();
    143  }
    144 
    145  // Don't reserve object slots for index properties that don't use them.
    146  if (kind == PropertySetKind::Normal) {
    147    propCount =
    148        CountNonIndexPropertiesUpTo(literalInsns, gc::MaxGCObjectFixedSlots);
    149  }
    150 
    151  return gc::GetGCObjectKind(propCount);
    152 }
    153 
    154 template <PropertySetKind kind>
    155 JSObject* InterpretObjLiteralObj(
    156    JSContext* cx, const frontend::CompilationAtomCache& atomCache,
    157    const mozilla::Span<const uint8_t> literalInsns, uint32_t propertyCount) {
    158  gc::AllocKind allocKind =
    159      AllocKindForObjectLiteral<kind>(literalInsns, propertyCount);
    160 
    161  Rooted<PlainObject*> obj(
    162      cx, NewPlainObjectWithAllocKind(cx, allocKind, TenuredObject));
    163  if (!obj) {
    164    return nullptr;
    165  }
    166 
    167  ObjLiteralReader reader(literalInsns);
    168 
    169  RootedId propId(cx);
    170  RootedValue propVal(cx);
    171  while (true) {
    172    // Make sure `insn` doesn't live across GC.
    173    ObjLiteralInsn insn;
    174    if (!reader.readInsn(&insn)) {
    175      break;
    176    }
    177    MOZ_ASSERT(insn.isValid());
    178    MOZ_ASSERT_IF(kind == PropertySetKind::UniqueNames,
    179                  !insn.getKey().isArrayIndex());
    180 
    181    if (kind == PropertySetKind::Normal && insn.getKey().isArrayIndex()) {
    182      propId = PropertyKey::Int(insn.getKey().getArrayIndex());
    183    } else {
    184      JSAtom* jsatom =
    185          atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex());
    186      MOZ_ASSERT(jsatom);
    187      propId = AtomToId(jsatom);
    188    }
    189 
    190    InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
    191 
    192    if constexpr (kind == PropertySetKind::UniqueNames) {
    193      if (!AddDataPropertyToPlainObject(cx, obj, propId, propVal)) {
    194        return nullptr;
    195      }
    196    } else {
    197      if (!NativeDefineDataProperty(cx, obj, propId, propVal,
    198                                    JSPROP_ENUMERATE)) {
    199        return nullptr;
    200      }
    201    }
    202  }
    203 
    204  return obj;
    205 }
    206 
    207 static JSObject* InterpretObjLiteralObj(
    208    JSContext* cx, const frontend::CompilationAtomCache& atomCache,
    209    const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags,
    210    uint32_t propertyCount) {
    211  if (!flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
    212    return InterpretObjLiteralObj<PropertySetKind::UniqueNames>(
    213        cx, atomCache, literalInsns, propertyCount);
    214  }
    215 
    216  return InterpretObjLiteralObj<PropertySetKind::Normal>(
    217      cx, atomCache, literalInsns, propertyCount);
    218 }
    219 
    220 static JSObject* InterpretObjLiteralArray(
    221    JSContext* cx, const frontend::CompilationAtomCache& atomCache,
    222    const mozilla::Span<const uint8_t> literalInsns, uint32_t propertyCount) {
    223  ObjLiteralReader reader(literalInsns);
    224  ObjLiteralInsn insn;
    225 
    226  Rooted<ValueVector> elements(cx, ValueVector(cx));
    227  if (!elements.reserve(propertyCount)) {
    228    return nullptr;
    229  }
    230 
    231  RootedValue propVal(cx);
    232  while (reader.readInsn(&insn)) {
    233    MOZ_ASSERT(insn.isValid());
    234 
    235    InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
    236    elements.infallibleAppend(propVal);
    237  }
    238 
    239  return NewDenseCopiedArray(cx, elements.length(), elements.begin(),
    240                             NewObjectKind::TenuredObject);
    241 }
    242 
    243 // ES2023 draft rev ee74c9cb74dbfa23e62b486f5226102c345c678e
    244 //
    245 // GetTemplateObject ( templateLiteral )
    246 // https://tc39.es/ecma262/#sec-gettemplateobject
    247 //
    248 // Steps 8-16.
    249 static JSObject* InterpretObjLiteralCallSiteObj(
    250    JSContext* cx, const frontend::CompilationAtomCache& atomCache,
    251    const mozilla::Span<const uint8_t> literalInsns, uint32_t propertyCount) {
    252  ObjLiteralReader reader(literalInsns);
    253  ObjLiteralInsn insn;
    254 
    255  // We have to read elements for two arrays. The 'cooked' values are followed
    256  // by the 'raw' values. Both arrays have the same length.
    257  MOZ_ASSERT((propertyCount % 2) == 0);
    258  uint32_t count = propertyCount / 2;
    259 
    260  Rooted<ValueVector> elements(cx, ValueVector(cx));
    261  if (!elements.reserve(count)) {
    262    return nullptr;
    263  }
    264 
    265  RootedValue propVal(cx);
    266  auto readElements = [&](uint32_t count) {
    267    MOZ_ASSERT(elements.empty());
    268 
    269    for (size_t i = 0; i < count; i++) {
    270      MOZ_ALWAYS_TRUE(reader.readInsn(&insn));
    271      MOZ_ASSERT(insn.isValid());
    272 
    273      InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
    274      MOZ_ASSERT(propVal.isString() || propVal.isUndefined());
    275      elements.infallibleAppend(propVal);
    276    }
    277  };
    278 
    279  // Create cooked array.
    280  readElements(count);
    281  Rooted<ArrayObject*> cso(
    282      cx, NewDenseCopiedArray(cx, elements.length(), elements.begin(),
    283                              NewObjectKind::TenuredObject));
    284  if (!cso) {
    285    return nullptr;
    286  }
    287  elements.clear();
    288 
    289  // Create raw array.
    290  readElements(count);
    291  Rooted<ArrayObject*> raw(
    292      cx, NewDenseCopiedArray(cx, elements.length(), elements.begin(),
    293                              NewObjectKind::TenuredObject));
    294  if (!raw) {
    295    return nullptr;
    296  }
    297 
    298  // Define .raw property and freeze both arrays.
    299  RootedValue rawValue(cx, ObjectValue(*raw));
    300  if (!DefineDataProperty(cx, cso, cx->names().raw, rawValue, 0)) {
    301    return nullptr;
    302  }
    303  if (!FreezeObject(cx, raw)) {
    304    return nullptr;
    305  }
    306  if (!FreezeObject(cx, cso)) {
    307    return nullptr;
    308  }
    309 
    310  return cso;
    311 }
    312 
    313 template <PropertySetKind kind>
    314 Shape* InterpretObjLiteralShape(JSContext* cx,
    315                                const frontend::CompilationAtomCache& atomCache,
    316                                const mozilla::Span<const uint8_t> literalInsns,
    317                                uint32_t propertyCount) {
    318  gc::AllocKind allocKind =
    319      AllocKindForObjectLiteral<kind>(literalInsns, propertyCount);
    320  uint32_t numFixedSlots = GetGCKindSlots(allocKind);
    321 
    322  ObjLiteralReader reader(literalInsns);
    323 
    324  Rooted<SharedPropMap*> map(cx);
    325  uint32_t mapLength = 0;
    326  ObjectFlags objectFlags;
    327 
    328  uint32_t slot = 0;
    329  RootedId propId(cx);
    330  while (true) {
    331    // Make sure `insn` doesn't live across GC.
    332    ObjLiteralInsn insn;
    333    if (!reader.readInsn(&insn)) {
    334      break;
    335    }
    336    MOZ_ASSERT(insn.isValid());
    337    MOZ_ASSERT(!insn.getKey().isArrayIndex());
    338    MOZ_ASSERT(insn.getOp() == ObjLiteralOpcode::Undefined);
    339 
    340    JSAtom* jsatom =
    341        atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex());
    342    MOZ_ASSERT(jsatom);
    343    propId = AtomToId(jsatom);
    344 
    345    // Assert or check property names are unique.
    346    if constexpr (kind == PropertySetKind::UniqueNames) {
    347      mozilla::DebugOnly<uint32_t> index;
    348      MOZ_ASSERT_IF(map, !map->lookupPure(mapLength, propId, &index));
    349    } else {
    350      uint32_t index;
    351      if (map && map->lookupPure(mapLength, propId, &index)) {
    352        continue;
    353      }
    354    }
    355 
    356    constexpr PropertyFlags propFlags = PropertyFlags::defaultDataPropFlags;
    357 
    358    if (!SharedPropMap::addPropertyWithKnownSlot(cx, &PlainObject::class_, &map,
    359                                                 &mapLength, propId, propFlags,
    360                                                 slot, &objectFlags)) {
    361      return nullptr;
    362    }
    363 
    364    slot++;
    365  }
    366 
    367  JSObject* proto = &cx->global()->getObjectPrototype();
    368  return SharedShape::getInitialOrPropMapShape(
    369      cx, &PlainObject::class_, cx->realm(), TaggedProto(proto), numFixedSlots,
    370      map, mapLength, objectFlags);
    371 }
    372 
    373 static Shape* InterpretObjLiteralShape(
    374    JSContext* cx, const frontend::CompilationAtomCache& atomCache,
    375    const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags,
    376    uint32_t propertyCount) {
    377  if (!flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
    378    return InterpretObjLiteralShape<PropertySetKind::UniqueNames>(
    379        cx, atomCache, literalInsns, propertyCount);
    380  }
    381  return InterpretObjLiteralShape<PropertySetKind::Normal>(
    382      cx, atomCache, literalInsns, propertyCount);
    383 }
    384 
    385 JS::GCCellPtr ObjLiteralStencil::create(
    386    JSContext* cx, const frontend::CompilationAtomCache& atomCache) const {
    387  switch (kind()) {
    388    case ObjLiteralKind::Array: {
    389      JSObject* obj =
    390          InterpretObjLiteralArray(cx, atomCache, code_, propertyCount_);
    391      if (!obj) {
    392        return JS::GCCellPtr();
    393      }
    394      return JS::GCCellPtr(obj);
    395    }
    396    case ObjLiteralKind::CallSiteObj: {
    397      JSObject* obj =
    398          InterpretObjLiteralCallSiteObj(cx, atomCache, code_, propertyCount_);
    399      if (!obj) {
    400        return JS::GCCellPtr();
    401      }
    402      return JS::GCCellPtr(obj);
    403    }
    404    case ObjLiteralKind::Object: {
    405      JSObject* obj =
    406          InterpretObjLiteralObj(cx, atomCache, code_, flags(), propertyCount_);
    407      if (!obj) {
    408        return JS::GCCellPtr();
    409      }
    410      return JS::GCCellPtr(obj);
    411    }
    412    case ObjLiteralKind::Shape: {
    413      Shape* shape = InterpretObjLiteralShape(cx, atomCache, code_, flags(),
    414                                              propertyCount_);
    415      if (!shape) {
    416        return JS::GCCellPtr();
    417      }
    418      return JS::GCCellPtr(shape);
    419    }
    420    case ObjLiteralKind::Invalid:
    421      break;
    422  }
    423  MOZ_CRASH("Invalid kind");
    424 }
    425 
    426 #ifdef DEBUG
    427 bool ObjLiteralStencil::isContainedIn(const LifoAlloc& alloc) const {
    428  return alloc.contains(code_.data());
    429 }
    430 #endif
    431 
    432 #if defined(DEBUG) || defined(JS_JITSPEW)
    433 
    434 static void DumpObjLiteralFlagsItems(js::JSONPrinter& json,
    435                                     ObjLiteralFlags flags) {
    436  if (flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
    437    json.value("HasIndexOrDuplicatePropName");
    438    flags.clearFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
    439  }
    440 
    441  if (!flags.isEmpty()) {
    442    json.value("Unknown(%x)", flags.toRaw());
    443  }
    444 }
    445 
    446 static const char* ObjLiteralKindToString(ObjLiteralKind kind) {
    447  switch (kind) {
    448    case ObjLiteralKind::Object:
    449      return "Object";
    450    case ObjLiteralKind::Array:
    451      return "Array";
    452    case ObjLiteralKind::CallSiteObj:
    453      return "CallSiteObj";
    454    case ObjLiteralKind::Shape:
    455      return "Shape";
    456    case ObjLiteralKind::Invalid:
    457      break;
    458  }
    459  MOZ_CRASH("Invalid kind");
    460 }
    461 
    462 static void DumpObjLiteral(js::JSONPrinter& json,
    463                           const frontend::CompilationStencil* stencil,
    464                           mozilla::Span<const uint8_t> code,
    465                           ObjLiteralKind kind, const ObjLiteralFlags& flags,
    466                           uint32_t propertyCount) {
    467  json.property("kind", ObjLiteralKindToString(kind));
    468 
    469  json.beginListProperty("flags");
    470  DumpObjLiteralFlagsItems(json, flags);
    471  json.endList();
    472 
    473  json.beginListProperty("code");
    474  ObjLiteralReader reader(code);
    475  ObjLiteralInsn insn;
    476  while (reader.readInsn(&insn)) {
    477    json.beginObject();
    478 
    479    if (insn.getKey().isNone()) {
    480      json.nullProperty("key");
    481    } else if (insn.getKey().isAtomIndex()) {
    482      frontend::TaggedParserAtomIndex index = insn.getKey().getAtomIndex();
    483      json.beginObjectProperty("key");
    484      DumpTaggedParserAtomIndex(json, index, stencil);
    485      json.endObject();
    486    } else if (insn.getKey().isArrayIndex()) {
    487      uint32_t index = insn.getKey().getArrayIndex();
    488      json.formatProperty("key", "ArrayIndex(%u)", index);
    489    }
    490 
    491    switch (insn.getOp()) {
    492      case ObjLiteralOpcode::ConstValue: {
    493        const Value& v = insn.getConstValue();
    494        json.formatProperty("op", "ConstValue(%f)", v.toNumber());
    495        break;
    496      }
    497      case ObjLiteralOpcode::ConstString: {
    498        frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
    499        json.beginObjectProperty("op");
    500        DumpTaggedParserAtomIndex(json, index, stencil);
    501        json.endObject();
    502        break;
    503      }
    504      case ObjLiteralOpcode::Null:
    505        json.property("op", "Null");
    506        break;
    507      case ObjLiteralOpcode::Undefined:
    508        json.property("op", "Undefined");
    509        break;
    510      case ObjLiteralOpcode::True:
    511        json.property("op", "True");
    512        break;
    513      case ObjLiteralOpcode::False:
    514        json.property("op", "False");
    515        break;
    516      default:
    517        json.formatProperty("op", "Invalid(%x)", uint8_t(insn.getOp()));
    518        break;
    519    }
    520 
    521    json.endObject();
    522  }
    523  json.endList();
    524 
    525  json.property("propertyCount", propertyCount);
    526 }
    527 
    528 void ObjLiteralWriter::dump() const {
    529  js::Fprinter out(stderr);
    530  js::JSONPrinter json(out);
    531  dump(json, nullptr);
    532 }
    533 
    534 void ObjLiteralWriter::dump(js::JSONPrinter& json,
    535                            const frontend::CompilationStencil* stencil) const {
    536  json.beginObject();
    537  dumpFields(json, stencil);
    538  json.endObject();
    539 }
    540 
    541 void ObjLiteralWriter::dumpFields(
    542    js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
    543  DumpObjLiteral(json, stencil, getCode(), kind_, flags_, propertyCount_);
    544 }
    545 
    546 void ObjLiteralStencil::dump() const {
    547  js::Fprinter out(stderr);
    548  js::JSONPrinter json(out);
    549  dump(json, nullptr);
    550 }
    551 
    552 void ObjLiteralStencil::dump(
    553    js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
    554  json.beginObject();
    555  dumpFields(json, stencil);
    556  json.endObject();
    557 }
    558 
    559 void ObjLiteralStencil::dumpFields(
    560    js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
    561  DumpObjLiteral(json, stencil, code_, kind(), flags(), propertyCount_);
    562 }
    563 
    564 #endif  // defined(DEBUG) || defined(JS_JITSPEW)
    565 
    566 }  // namespace js