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 #ifndef frontend_ObjLiteral_h
9 #define frontend_ObjLiteral_h
10 
11 #include "mozilla/BloomFilter.h"  // mozilla::BitBloomFilter
12 #include "mozilla/EnumSet.h"
13 #include "mozilla/Span.h"
14 
15 #include "frontend/ParserAtom.h"  // ParserAtomsTable, TaggedParserAtomIndex
16 #include "js/AllocPolicy.h"
17 #include "js/GCPolicyAPI.h"
18 #include "js/Value.h"
19 #include "js/Vector.h"
20 #include "util/EnumFlags.h"
21 
22 /*
23  * [SMDOC] ObjLiteral (Object Literal) Handling
24  * ============================================
25  *
26  * The `ObjLiteral*` family of classes defines an infastructure to handle
27  * object literals as they are encountered at parse time and translate them
28  * into objects that are attached to the bytecode.
29  *
30  * The object-literal "instructions", whose opcodes are defined in
31  * `ObjLiteralOpcode` below, each specify one key (atom property name, or
32  * numeric index) and one value. An `ObjLiteralWriter` buffers a linear
33  * sequence of such instructions, along with a side-table of atom references.
34  * The writer stores a compact binary format that is then interpreted by the
35  * `ObjLiteralReader` to construct an object according to the instructions.
36  *
37  * This may seem like an odd dance: create an intermediate data structure that
38  * specifies key/value pairs, then later build the object. Why not just do so
39  * directly, as we parse? In fact, we used to do this. However, for several
40  * good reasons, we want to avoid allocating or touching GC objects at all
41  * *during* the parse. We thus use a sequence of ObjLiteral instructions as an
42  * intermediate data structure to carry object literal contents from parse to
43  * the time at which we *can* allocate objects.
44  *
45  * (The original intent was to allow for ObjLiteral instructions to actually be
46  * invoked by a new JS opcode, JSOp::ObjLiteral, thus replacing the more
47  * general opcode sequences sometimes generated to fill in objects and removing
48  * the need to attach actual objects to JSOp::Object or JSOp::NewObject.
49  * However, this was far too invasive and led to performance regressions, so
50  * currently ObjLiteral only carries literals as far as the end of the parse
51  * pipeline, when all GC things are allocated.)
52  *
53  * ObjLiteral data structures are used to represent object literals whenever
54  * they are "compatible". See
55  * BytecodeEmitter::isPropertyListObjLiteralCompatible for the precise
56  * conditions; in brief, we can represent object literals with "primitive"
57  * (numeric, boolean, string, null/undefined) values, and "normal"
58  * (non-computed) object names. We can also represent arrays with the same
59  * value restrictions. We cannot represent nested objects. We use ObjLiteral in
60  * two different ways:
61  *
62  * - To build a template object, when we can support the properties but not the
63  *   keys.
64  * - To build the actual result object, when we support the properties and the
65  *   keys and this is a JSOp::Object case (see below).
66  *
67  * Design and Performance Considerations
68  * -------------------------------------
69  *
70  * As a brief overview, there are a number of opcodes that allocate objects:
71  *
72  * - JSOp::NewInit allocates a new empty `{}` object.
73  *
74  * - JSOp::NewObject, with an object as an argument (held by the script data
75  *   side-tables), allocates a new object with `undefined` property values but
76  *   with a defined set of properties. The given object is used as a
77  *   *template*.
78  *
79  * - JSOp::Object, with an object as argument, instructs the runtime to
80  *   literally return the object argument as the result. This is thus only an
81  *   "allocation" in the sense that the object was originally allocated when
82  *   the script data / bytecode was created. It is only used when we know for
83  *   sure that the script, and this program point within the script, will run
84  *   *once*. (See the `treatAsRunOnce` flag on JSScript.)
85  *
86  * An operation occurs in a "singleton context", according to the parser, if it
87  * will only ever execute once. In particular, this happens when (i) the script
88  * is a "run-once" script, which is usually the case for e.g. top-level scripts
89  * of web-pages (they run on page load, but no function or handle wraps or
90  * refers to the script so it can't be invoked again), and (ii) the operation
91  * itself is not within a loop or function in that run-once script.
92  *
93  * When we encounter an object literal, we decide which opcode to use, and we
94  * construct the ObjLiteral and the bytecode using its result appropriately:
95  *
96  * - If in a singleton context, and if we support the values, we use
97  *   JSOp::Object and we build the ObjLiteral instructions with values.
98  * - Otherwise, if we support the keys but not the values, or if we are not
99  *   in a singleton context, we use JSOp::NewObject. In this case, the initial
100  *   opcode only creates an object with empty values, so BytecodeEmitter then
101  *   generates bytecode to set the values appropriately.
102  * - Otherwise, we generate JSOp::NewInit and bytecode to add properties one at
103  *   a time. This will always work, but is the slowest and least
104  *   memory-efficient option.
105  */
106 
107 namespace js {
108 
109 class LifoAlloc;
110 class JSONPrinter;
111 
112 namespace frontend {
113 struct CompilationAtomCache;
114 struct CompilationStencil;
115 class StencilXDR;
116 }  // namespace frontend
117 
118 // Object-literal instruction opcodes. An object literal is constructed by a
119 // straight-line sequence of these ops, each adding one property to the
120 // object.
121 enum class ObjLiteralOpcode : uint8_t {
122   INVALID = 0,
123 
124   ConstValue = 1,  // numeric types only.
125   ConstAtom = 2,
126   Null = 3,
127   Undefined = 4,
128   True = 5,
129   False = 6,
130 
131   MAX = False,
132 };
133 
134 // Flags that are associated with a sequence of object-literal instructions.
135 // (These become bitflags by wrapping with EnumSet below.)
136 enum class ObjLiteralFlag : uint8_t {
137   // If set, this object is an array.
138   Array = 1 << 0,
139 
140   // If set, this is an object literal in a singleton context and property
141   // values are included. See also JSOp::Object.
142   Singleton = 1 << 1,
143 
144   // If set, this object contains index property, or duplicate non-index
145   // property.
146   // This flag is valid only if Array flag isn't set.
147   HasIndexOrDuplicatePropName = 1 << 2,
148 };
149 
150 using ObjLiteralFlags = EnumFlags<ObjLiteralFlag>;
151 
ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op)152 inline bool ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op) {
153   return op == ObjLiteralOpcode::ConstValue;
154 }
155 
ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op)156 inline bool ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op) {
157   return op == ObjLiteralOpcode::ConstAtom;
158 }
159 
160 struct ObjLiteralReaderBase;
161 
162 // Property name (as TaggedParserAtomIndex) or an integer index.  Only used for
163 // object-type literals; array literals do not require the index (the sequence
164 // is always dense, with no holes, so the index is implicit). For the latter
165 // case, we have a `None` placeholder.
166 struct ObjLiteralKey {
167  private:
168   uint32_t value_;
169 
170   enum ObjLiteralKeyType {
171     None,
172     AtomIndex,
173     ArrayIndex,
174   };
175 
176   ObjLiteralKeyType type_;
177 
ObjLiteralKeyObjLiteralKey178   ObjLiteralKey(uint32_t value, ObjLiteralKeyType ty)
179       : value_(value), type_(ty) {}
180 
181  public:
ObjLiteralKeyObjLiteralKey182   ObjLiteralKey() : ObjLiteralKey(0, None) {}
ObjLiteralKeyObjLiteralKey183   ObjLiteralKey(uint32_t value, bool isArrayIndex)
184       : ObjLiteralKey(value, isArrayIndex ? ArrayIndex : AtomIndex) {}
185   ObjLiteralKey(const ObjLiteralKey& other) = default;
186 
fromPropNameObjLiteralKey187   static ObjLiteralKey fromPropName(frontend::TaggedParserAtomIndex atomIndex) {
188     return ObjLiteralKey(atomIndex.rawData(), false);
189   }
fromArrayIndexObjLiteralKey190   static ObjLiteralKey fromArrayIndex(uint32_t index) {
191     return ObjLiteralKey(index, true);
192   }
noneObjLiteralKey193   static ObjLiteralKey none() { return ObjLiteralKey(); }
194 
isNoneObjLiteralKey195   bool isNone() const { return type_ == None; }
isAtomIndexObjLiteralKey196   bool isAtomIndex() const { return type_ == AtomIndex; }
isArrayIndexObjLiteralKey197   bool isArrayIndex() const { return type_ == ArrayIndex; }
198 
getAtomIndexObjLiteralKey199   frontend::TaggedParserAtomIndex getAtomIndex() const {
200     MOZ_ASSERT(isAtomIndex());
201     return frontend::TaggedParserAtomIndex::fromRaw(value_);
202   }
getArrayIndexObjLiteralKey203   uint32_t getArrayIndex() const {
204     MOZ_ASSERT(isArrayIndex());
205     return value_;
206   }
207 
rawIndexObjLiteralKey208   uint32_t rawIndex() const { return value_; }
209 };
210 
211 struct ObjLiteralWriterBase {
212  protected:
213   friend struct ObjLiteralReaderBase;  // for access to mask and shift.
214   static const uint32_t ATOM_INDEX_MASK = 0x7fffffff;
215   // If set, the atom index field is an array index, not an atom index.
216   static const uint32_t INDEXED_PROP = 0x80000000;
217 
218  public:
219   using CodeVector = Vector<uint8_t, 64, js::SystemAllocPolicy>;
220 
221  protected:
222   CodeVector code_;
223 
224  public:
225   ObjLiteralWriterBase() = default;
226 
curOffsetObjLiteralWriterBase227   uint32_t curOffset() const { return code_.length(); }
228 
229  private:
pushByteObjLiteralWriterBase230   [[nodiscard]] bool pushByte(JSContext* cx, uint8_t data) {
231     if (!code_.append(data)) {
232       js::ReportOutOfMemory(cx);
233       return false;
234     }
235     return true;
236   }
237 
prepareBytesObjLiteralWriterBase238   [[nodiscard]] bool prepareBytes(JSContext* cx, size_t len, uint8_t** p) {
239     size_t offset = code_.length();
240     if (!code_.growByUninitialized(len)) {
241       js::ReportOutOfMemory(cx);
242       return false;
243     }
244     *p = &code_[offset];
245     return true;
246   }
247 
248   template <typename T>
pushRawDataObjLiteralWriterBase249   [[nodiscard]] bool pushRawData(JSContext* cx, T data) {
250     uint8_t* p = nullptr;
251     if (!prepareBytes(cx, sizeof(T), &p)) {
252       return false;
253     }
254     memcpy(p, &data, sizeof(T));
255     return true;
256   }
257 
258  protected:
pushOpAndNameObjLiteralWriterBase259   [[nodiscard]] bool pushOpAndName(JSContext* cx, ObjLiteralOpcode op,
260                                    ObjLiteralKey key) {
261     uint8_t opdata = static_cast<uint8_t>(op);
262     uint32_t data = key.rawIndex() | (key.isArrayIndex() ? INDEXED_PROP : 0);
263     return pushByte(cx, opdata) && pushRawData(cx, data);
264   }
265 
pushValueArgObjLiteralWriterBase266   [[nodiscard]] bool pushValueArg(JSContext* cx, const JS::Value& value) {
267     MOZ_ASSERT(value.isNumber() || value.isNullOrUndefined() ||
268                value.isBoolean());
269     uint64_t data = value.asRawBits();
270     return pushRawData(cx, data);
271   }
272 
pushAtomArgObjLiteralWriterBase273   [[nodiscard]] bool pushAtomArg(JSContext* cx,
274                                  frontend::TaggedParserAtomIndex atomIndex) {
275     return pushRawData(cx, atomIndex.rawData());
276   }
277 };
278 
279 // An object-literal instruction writer. This class, held by the bytecode
280 // emitter, keeps a sequence of object-literal instructions emitted as object
281 // literal expressions are parsed. It allows the user to 'begin' and 'end'
282 // straight-line sequences, returning the offsets for this range of instructions
283 // within the writer.
284 struct ObjLiteralWriter : private ObjLiteralWriterBase {
285  public:
286   ObjLiteralWriter() = default;
287 
clearObjLiteralWriter288   void clear() { code_.clear(); }
289 
290   using CodeVector = typename ObjLiteralWriterBase::CodeVector;
291 
292   bool checkForDuplicatedNames(JSContext* cx);
getCodeObjLiteralWriter293   mozilla::Span<const uint8_t> getCode() const { return code_; }
getFlagsObjLiteralWriter294   ObjLiteralFlags getFlags() const { return flags_; }
getPropertyCountObjLiteralWriter295   uint32_t getPropertyCount() const { return propertyCount_; }
296 
beginObjectObjLiteralWriter297   void beginObject(ObjLiteralFlags flags) { flags_ = flags; }
setPropNameObjLiteralWriter298   bool setPropName(JSContext* cx, frontend::ParserAtomsTable& parserAtoms,
299                    const frontend::TaggedParserAtomIndex propName) {
300     // Only valid in object-mode.
301     setPropNameNoDuplicateCheck(parserAtoms, propName);
302 
303     if (flags_.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
304       return true;
305     }
306 
307     // OK to early return if we've already discovered a potential duplicate.
308     if (mightContainDuplicatePropertyNames_) {
309       return true;
310     }
311 
312     // Check bloom filter for duplicate, and add if not already represented.
313     if (propNamesFilter_.mightContain(propName.rawData())) {
314       mightContainDuplicatePropertyNames_ = true;
315     } else {
316       propNamesFilter_.add(propName.rawData());
317     }
318     return true;
319   }
setPropNameNoDuplicateCheckObjLiteralWriter320   void setPropNameNoDuplicateCheck(
321       frontend::ParserAtomsTable& parserAtoms,
322       const frontend::TaggedParserAtomIndex propName) {
323     // Only valid in object-mode.
324     MOZ_ASSERT(!flags_.hasFlag(ObjLiteralFlag::Array));
325     parserAtoms.markUsedByStencil(propName);
326     nextKey_ = ObjLiteralKey::fromPropName(propName);
327   }
setPropIndexObjLiteralWriter328   void setPropIndex(uint32_t propIndex) {
329     // Only valid in object-mode.
330     MOZ_ASSERT(!flags_.hasFlag(ObjLiteralFlag::Array));
331     MOZ_ASSERT(propIndex <= ATOM_INDEX_MASK);
332     nextKey_ = ObjLiteralKey::fromArrayIndex(propIndex);
333     flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
334   }
beginDenseArrayElementsObjLiteralWriter335   void beginDenseArrayElements() {
336     // Only valid in array-mode.
337     MOZ_ASSERT(flags_.hasFlag(ObjLiteralFlag::Array));
338     // Dense array element sequences do not use the keys; the indices are
339     // implicit.
340     nextKey_ = ObjLiteralKey::none();
341   }
342 
propWithConstNumericValueObjLiteralWriter343   [[nodiscard]] bool propWithConstNumericValue(JSContext* cx,
344                                                const JS::Value& value) {
345     propertyCount_++;
346     MOZ_ASSERT(value.isNumber());
347     return pushOpAndName(cx, ObjLiteralOpcode::ConstValue, nextKey_) &&
348            pushValueArg(cx, value);
349   }
propWithAtomValueObjLiteralWriter350   [[nodiscard]] bool propWithAtomValue(
351       JSContext* cx, frontend::ParserAtomsTable& parserAtoms,
352       const frontend::TaggedParserAtomIndex value) {
353     propertyCount_++;
354     parserAtoms.markUsedByStencil(value);
355     return pushOpAndName(cx, ObjLiteralOpcode::ConstAtom, nextKey_) &&
356            pushAtomArg(cx, value);
357   }
propWithNullValueObjLiteralWriter358   [[nodiscard]] bool propWithNullValue(JSContext* cx) {
359     propertyCount_++;
360     return pushOpAndName(cx, ObjLiteralOpcode::Null, nextKey_);
361   }
propWithUndefinedValueObjLiteralWriter362   [[nodiscard]] bool propWithUndefinedValue(JSContext* cx) {
363     propertyCount_++;
364     return pushOpAndName(cx, ObjLiteralOpcode::Undefined, nextKey_);
365   }
propWithTrueValueObjLiteralWriter366   [[nodiscard]] bool propWithTrueValue(JSContext* cx) {
367     propertyCount_++;
368     return pushOpAndName(cx, ObjLiteralOpcode::True, nextKey_);
369   }
propWithFalseValueObjLiteralWriter370   [[nodiscard]] bool propWithFalseValue(JSContext* cx) {
371     propertyCount_++;
372     return pushOpAndName(cx, ObjLiteralOpcode::False, nextKey_);
373   }
374 
arrayIndexInRangeObjLiteralWriter375   static bool arrayIndexInRange(int32_t i) {
376     return i >= 0 && static_cast<uint32_t>(i) <= ATOM_INDEX_MASK;
377   }
378 
379 #if defined(DEBUG) || defined(JS_JITSPEW)
380   void dump() const;
381   void dump(JSONPrinter& json,
382             const frontend::CompilationStencil* stencil) const;
383   void dumpFields(JSONPrinter& json,
384                   const frontend::CompilationStencil* stencil) const;
385 #endif
386 
387  private:
388   // Set to true if we've found possible duplicate names while building.
389   // This field is placed next to `flags_` field, to reduce padding.
390   bool mightContainDuplicatePropertyNames_ = false;
391 
392   ObjLiteralFlags flags_;
393   ObjLiteralKey nextKey_;
394   uint32_t propertyCount_ = 0;
395 
396   // Duplicate property names detection is performed in the following way:
397   //   * while emitting code, add each property names with
398   //     `propNamesFilter_`
399   //   * if possible duplicate property name is detected, set
400   //     `mightContainDuplicatePropertyNames_` to true
401   //   * in `checkForDuplicatedNames` method,
402   //     if `mightContainDuplicatePropertyNames_` is true,
403   //     check the duplicate property names with `HashSet`, and if it exists,
404   //     set HasIndexOrDuplicatePropName flag.
405   mozilla::BitBloomFilter<12, frontend::TaggedParserAtomIndex> propNamesFilter_;
406 };
407 
408 struct ObjLiteralReaderBase {
409  private:
410   mozilla::Span<const uint8_t> data_;
411   size_t cursor_;
412 
readByteObjLiteralReaderBase413   [[nodiscard]] bool readByte(uint8_t* b) {
414     if (cursor_ + 1 > data_.Length()) {
415       return false;
416     }
417     *b = *data_.From(cursor_).data();
418     cursor_ += 1;
419     return true;
420   }
421 
readBytesObjLiteralReaderBase422   [[nodiscard]] bool readBytes(size_t size, const uint8_t** p) {
423     if (cursor_ + size > data_.Length()) {
424       return false;
425     }
426     *p = data_.From(cursor_).data();
427     cursor_ += size;
428     return true;
429   }
430 
431   template <typename T>
readRawDataObjLiteralReaderBase432   [[nodiscard]] bool readRawData(T* data) {
433     const uint8_t* p = nullptr;
434     if (!readBytes(sizeof(T), &p)) {
435       return false;
436     }
437     memcpy(data, p, sizeof(T));
438     return true;
439   }
440 
441  public:
ObjLiteralReaderBaseObjLiteralReaderBase442   explicit ObjLiteralReaderBase(mozilla::Span<const uint8_t> data)
443       : data_(data), cursor_(0) {}
444 
readOpAndKeyObjLiteralReaderBase445   [[nodiscard]] bool readOpAndKey(ObjLiteralOpcode* op, ObjLiteralKey* key) {
446     uint8_t opbyte;
447     if (!readByte(&opbyte)) {
448       return false;
449     }
450     if (MOZ_UNLIKELY(opbyte > static_cast<uint8_t>(ObjLiteralOpcode::MAX))) {
451       return false;
452     }
453     *op = static_cast<ObjLiteralOpcode>(opbyte);
454 
455     uint32_t data;
456     if (!readRawData(&data)) {
457       return false;
458     }
459     bool isArray = data & ObjLiteralWriterBase::INDEXED_PROP;
460     uint32_t rawIndex = data & ~ObjLiteralWriterBase::INDEXED_PROP;
461     *key = ObjLiteralKey(rawIndex, isArray);
462     return true;
463   }
464 
readValueArgObjLiteralReaderBase465   [[nodiscard]] bool readValueArg(JS::Value* value) {
466     uint64_t data;
467     if (!readRawData(&data)) {
468       return false;
469     }
470     *value = JS::Value::fromRawBits(data);
471     return true;
472   }
473 
readAtomArgObjLiteralReaderBase474   [[nodiscard]] bool readAtomArg(frontend::TaggedParserAtomIndex* atomIndex) {
475     return readRawData(atomIndex->rawDataRef());
476   }
477 
cursorObjLiteralReaderBase478   size_t cursor() const { return cursor_; }
479 };
480 
481 // A single object-literal instruction, creating one property on an object.
482 struct ObjLiteralInsn {
483  private:
484   ObjLiteralOpcode op_;
485   ObjLiteralKey key_;
486   union Arg {
Arg(uint64_t raw_)487     explicit Arg(uint64_t raw_) : raw(raw_) {}
488 
489     JS::Value constValue;
490     frontend::TaggedParserAtomIndex atomIndex;
491     uint64_t raw;
492   } arg_;
493 
494  public:
ObjLiteralInsnObjLiteralInsn495   ObjLiteralInsn() : op_(ObjLiteralOpcode::INVALID), arg_(0) {}
ObjLiteralInsnObjLiteralInsn496   ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key)
497       : op_(op), key_(key), arg_(0) {
498     MOZ_ASSERT(!hasConstValue());
499     MOZ_ASSERT(!hasAtomIndex());
500   }
ObjLiteralInsnObjLiteralInsn501   ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, const JS::Value& value)
502       : op_(op), key_(key), arg_(0) {
503     MOZ_ASSERT(hasConstValue());
504     MOZ_ASSERT(!hasAtomIndex());
505     arg_.constValue = value;
506   }
ObjLiteralInsnObjLiteralInsn507   ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key,
508                  frontend::TaggedParserAtomIndex atomIndex)
509       : op_(op), key_(key), arg_(0) {
510     MOZ_ASSERT(!hasConstValue());
511     MOZ_ASSERT(hasAtomIndex());
512     arg_.atomIndex = atomIndex;
513   }
ObjLiteralInsnObjLiteralInsn514   ObjLiteralInsn(const ObjLiteralInsn& other) : ObjLiteralInsn() {
515     *this = other;
516   }
517   ObjLiteralInsn& operator=(const ObjLiteralInsn& other) {
518     op_ = other.op_;
519     key_ = other.key_;
520     arg_.raw = other.arg_.raw;
521     return *this;
522   }
523 
isValidObjLiteralInsn524   bool isValid() const {
525     return op_ > ObjLiteralOpcode::INVALID && op_ <= ObjLiteralOpcode::MAX;
526   }
527 
getOpObjLiteralInsn528   ObjLiteralOpcode getOp() const {
529     MOZ_ASSERT(isValid());
530     return op_;
531   }
getKeyObjLiteralInsn532   const ObjLiteralKey& getKey() const {
533     MOZ_ASSERT(isValid());
534     return key_;
535   }
536 
hasConstValueObjLiteralInsn537   bool hasConstValue() const {
538     MOZ_ASSERT(isValid());
539     return ObjLiteralOpcodeHasValueArg(op_);
540   }
hasAtomIndexObjLiteralInsn541   bool hasAtomIndex() const {
542     MOZ_ASSERT(isValid());
543     return ObjLiteralOpcodeHasAtomArg(op_);
544   }
545 
getConstValueObjLiteralInsn546   JS::Value getConstValue() const {
547     MOZ_ASSERT(isValid());
548     MOZ_ASSERT(hasConstValue());
549     return arg_.constValue;
550   }
getAtomIndexObjLiteralInsn551   frontend::TaggedParserAtomIndex getAtomIndex() const {
552     MOZ_ASSERT(isValid());
553     MOZ_ASSERT(hasAtomIndex());
554     return arg_.atomIndex;
555   };
556 };
557 
558 // A reader that parses a sequence of object-literal instructions out of the
559 // encoded form.
560 struct ObjLiteralReader : private ObjLiteralReaderBase {
561  public:
ObjLiteralReaderObjLiteralReader562   explicit ObjLiteralReader(mozilla::Span<const uint8_t> data)
563       : ObjLiteralReaderBase(data) {}
564 
readInsnObjLiteralReader565   [[nodiscard]] bool readInsn(ObjLiteralInsn* insn) {
566     ObjLiteralOpcode op;
567     ObjLiteralKey key;
568     if (!readOpAndKey(&op, &key)) {
569       return false;
570     }
571     if (ObjLiteralOpcodeHasValueArg(op)) {
572       JS::Value value;
573       if (!readValueArg(&value)) {
574         return false;
575       }
576       *insn = ObjLiteralInsn(op, key, value);
577       return true;
578     }
579     if (ObjLiteralOpcodeHasAtomArg(op)) {
580       frontend::TaggedParserAtomIndex atomIndex;
581       if (!readAtomArg(&atomIndex)) {
582         return false;
583       }
584       *insn = ObjLiteralInsn(op, key, atomIndex);
585       return true;
586     }
587     *insn = ObjLiteralInsn(op, key);
588     return true;
589   }
590 };
591 
592 // A class to modify the code, while keeping the structure.
593 struct ObjLiteralModifier : private ObjLiteralReaderBase {
594   mozilla::Span<uint8_t> mutableData_;
595 
596  public:
ObjLiteralModifierObjLiteralModifier597   explicit ObjLiteralModifier(mozilla::Span<uint8_t> data)
598       : ObjLiteralReaderBase(data), mutableData_(data) {}
599 
600  private:
601   // Map `atom` with `map`, and write to `atomCursor` of `mutableData_`.
602   template <typename MapT>
mapOneAtomObjLiteralModifier603   void mapOneAtom(MapT map, frontend::TaggedParserAtomIndex atom,
604                   size_t atomCursor) {
605     auto atomIndex = map(atom);
606     memcpy(mutableData_.data() + atomCursor, atomIndex.rawDataRef(),
607            sizeof(frontend::TaggedParserAtomIndex));
608   }
609 
610   // Map atoms in single instruction.
611   // Return true if it successfully maps.
612   // Return false if there's no more instruction.
613   template <typename MapT>
mapInsnAtomObjLiteralModifier614   bool mapInsnAtom(MapT map) {
615     ObjLiteralOpcode op;
616     ObjLiteralKey key;
617 
618     size_t opCursor = cursor();
619     if (!readOpAndKey(&op, &key)) {
620       return false;
621     }
622     if (key.isAtomIndex()) {
623       static constexpr size_t OpLength = 1;
624       size_t atomCursor = opCursor + OpLength;
625       mapOneAtom(map, key.getAtomIndex(), atomCursor);
626     }
627 
628     if (ObjLiteralOpcodeHasValueArg(op)) {
629       JS::Value value;
630       if (!readValueArg(&value)) {
631         return false;
632       }
633     } else if (ObjLiteralOpcodeHasAtomArg(op)) {
634       size_t atomCursor = cursor();
635 
636       frontend::TaggedParserAtomIndex atomIndex;
637       if (!readAtomArg(&atomIndex)) {
638         return false;
639       }
640 
641       mapOneAtom(map, atomIndex, atomCursor);
642     }
643 
644     return true;
645   }
646 
647  public:
648   // Map TaggedParserAtomIndex inside the code in place, with given function.
649   template <typename MapT>
mapAtomObjLiteralModifier650   void mapAtom(MapT map) {
651     while (mapInsnAtom(map)) {
652     }
653   }
654 };
655 
656 class ObjLiteralStencil {
657   friend class frontend::StencilXDR;
658 
659   mozilla::Span<uint8_t> code_;
660   ObjLiteralFlags flags_;
661   uint32_t propertyCount_ = 0;
662 
663  public:
664   ObjLiteralStencil() = default;
665 
ObjLiteralStencil(uint8_t * code,size_t length,const ObjLiteralFlags & flags,uint32_t propertyCount)666   ObjLiteralStencil(uint8_t* code, size_t length, const ObjLiteralFlags& flags,
667                     uint32_t propertyCount)
668       : code_(mozilla::Span(code, length)),
669         flags_(flags),
670         propertyCount_(propertyCount) {}
671 
672   JSObject* create(JSContext* cx,
673                    const frontend::CompilationAtomCache& atomCache) const;
674 
code()675   mozilla::Span<const uint8_t> code() const { return code_; }
flags()676   ObjLiteralFlags flags() const { return flags_; }
propertyCount()677   uint32_t propertyCount() const { return propertyCount_; }
678 
679 #ifdef DEBUG
680   bool isContainedIn(const LifoAlloc& alloc) const;
681 #endif
682 
683 #if defined(DEBUG) || defined(JS_JITSPEW)
684   void dump() const;
685   void dump(JSONPrinter& json,
686             const frontend::CompilationStencil* stencil) const;
687   void dumpFields(JSONPrinter& json,
688                   const frontend::CompilationStencil* stencil) const;
689 
690 #endif
691 };
692 
693 }  // namespace js
694 #endif  // frontend_ObjLiteral_h
695