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:
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/. */
8 #include "frontend/ObjLiteral.h"
10 #include "mozilla/DebugOnly.h" // mozilla::DebugOnly
11 #include "mozilla/HashTable.h" // mozilla::HashSet
13 #include "NamespaceImports.h" // ValueVector
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
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
36 bool ObjLiteralWriter::checkForDuplicatedNames(FrontendContext
* fc
) {
37 if (!mightContainDuplicatePropertyNames_
) {
41 // If possible duplicate property names are detected by bloom-filter,
42 // check again with hash-set.
44 mozilla::HashSet
<frontend::TaggedParserAtomIndex
,
45 frontend::TaggedParserAtomIndexHasher
>
48 if (!propNameSet
.reserve(propertyCount_
)) {
49 js::ReportOutOfMemory(fc
);
53 ObjLiteralReader
reader(getCode());
57 if (!reader
.readInsn(&insn
)) {
61 if (insn
.getKey().isArrayIndex()) {
65 auto propName
= insn
.getKey().getAtomIndex();
67 auto p
= propNameSet
.lookupForAdd(propName
);
69 flags_
.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName
);
73 // Already reserved above and doesn't fail.
74 MOZ_ALWAYS_TRUE(propNameSet
.add(p
, propName
));
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());
87 case ObjLiteralOpcode::ConstString
: {
88 frontend::TaggedParserAtomIndex index
= insn
.getAtomIndex();
89 JSString
* str
= atomCache
.getExistingStringAt(cx
, index
);
91 valOut
.setString(str
);
94 case ObjLiteralOpcode::Null
:
97 case ObjLiteralOpcode::Undefined
:
98 valOut
.setUndefined();
100 case ObjLiteralOpcode::True
:
101 valOut
.setBoolean(true);
103 case ObjLiteralOpcode::False
:
104 valOut
.setBoolean(false);
107 MOZ_CRASH("Unexpected object-literal instruction opcode");
111 enum class PropertySetKind
{
116 template <PropertySetKind kind
>
117 bool InterpretObjLiteralObj(JSContext
* cx
, Handle
<PlainObject
*> obj
,
118 const frontend::CompilationAtomCache
& atomCache
,
119 const mozilla::Span
<const uint8_t> literalInsns
) {
120 ObjLiteralReader
reader(literalInsns
);
123 RootedValue
propVal(cx
);
125 // Make sure `insn` doesn't live across GC.
127 if (!reader
.readInsn(&insn
)) {
130 MOZ_ASSERT(insn
.isValid());
131 MOZ_ASSERT_IF(kind
== PropertySetKind::UniqueNames
,
132 !insn
.getKey().isArrayIndex());
134 if (kind
== PropertySetKind::Normal
&& insn
.getKey().isArrayIndex()) {
135 propId
= PropertyKey::Int(insn
.getKey().getArrayIndex());
138 atomCache
.getExistingAtomAt(cx
, insn
.getKey().getAtomIndex());
140 propId
= AtomToId(jsatom
);
143 InterpretObjLiteralValue(cx
, atomCache
, insn
, &propVal
);
145 if constexpr (kind
== PropertySetKind::UniqueNames
) {
146 if (!AddDataPropertyToPlainObject(cx
, obj
, propId
, propVal
)) {
150 if (!NativeDefineDataProperty(cx
, obj
, propId
, propVal
,
159 static gc::AllocKind
AllocKindForObjectLiteral(uint32_t propCount
) {
160 // Use NewObjectGCKind for empty object literals to reserve some fixed slots
161 // for new properties. This improves performance for common patterns such as
162 // |Object.assign({}, ...)|.
163 return (propCount
== 0) ? NewObjectGCKind() : gc::GetGCObjectKind(propCount
);
166 static JSObject
* InterpretObjLiteralObj(
167 JSContext
* cx
, const frontend::CompilationAtomCache
& atomCache
,
168 const mozilla::Span
<const uint8_t> literalInsns
, ObjLiteralFlags flags
,
169 uint32_t propertyCount
) {
170 gc::AllocKind allocKind
= AllocKindForObjectLiteral(propertyCount
);
172 Rooted
<PlainObject
*> obj(
173 cx
, NewPlainObjectWithAllocKind(cx
, allocKind
, TenuredObject
));
178 if (!flags
.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName
)) {
179 if (!InterpretObjLiteralObj
<PropertySetKind::UniqueNames
>(
180 cx
, obj
, atomCache
, literalInsns
)) {
184 if (!InterpretObjLiteralObj
<PropertySetKind::Normal
>(cx
, obj
, atomCache
,
192 static JSObject
* InterpretObjLiteralArray(
193 JSContext
* cx
, const frontend::CompilationAtomCache
& atomCache
,
194 const mozilla::Span
<const uint8_t> literalInsns
, uint32_t propertyCount
) {
195 ObjLiteralReader
reader(literalInsns
);
198 Rooted
<ValueVector
> elements(cx
, ValueVector(cx
));
199 if (!elements
.reserve(propertyCount
)) {
203 RootedValue
propVal(cx
);
204 while (reader
.readInsn(&insn
)) {
205 MOZ_ASSERT(insn
.isValid());
207 InterpretObjLiteralValue(cx
, atomCache
, insn
, &propVal
);
208 elements
.infallibleAppend(propVal
);
211 return NewDenseCopiedArray(cx
, elements
.length(), elements
.begin(),
212 NewObjectKind::TenuredObject
);
215 // ES2023 draft rev ee74c9cb74dbfa23e62b486f5226102c345c678e
217 // GetTemplateObject ( templateLiteral )
218 // https://tc39.es/ecma262/#sec-gettemplateobject
221 static JSObject
* InterpretObjLiteralCallSiteObj(
222 JSContext
* cx
, const frontend::CompilationAtomCache
& atomCache
,
223 const mozilla::Span
<const uint8_t> literalInsns
, uint32_t propertyCount
) {
224 ObjLiteralReader
reader(literalInsns
);
227 // We have to read elements for two arrays. The 'cooked' values are followed
228 // by the 'raw' values. Both arrays have the same length.
229 MOZ_ASSERT((propertyCount
% 2) == 0);
230 uint32_t count
= propertyCount
/ 2;
232 Rooted
<ValueVector
> elements(cx
, ValueVector(cx
));
233 if (!elements
.reserve(count
)) {
237 RootedValue
propVal(cx
);
238 auto readElements
= [&](uint32_t count
) {
239 MOZ_ASSERT(elements
.empty());
241 for (size_t i
= 0; i
< count
; i
++) {
242 MOZ_ALWAYS_TRUE(reader
.readInsn(&insn
));
243 MOZ_ASSERT(insn
.isValid());
245 InterpretObjLiteralValue(cx
, atomCache
, insn
, &propVal
);
246 MOZ_ASSERT(propVal
.isString() || propVal
.isUndefined());
247 elements
.infallibleAppend(propVal
);
251 // Create cooked array.
253 Rooted
<ArrayObject
*> cso(
254 cx
, NewDenseCopiedArray(cx
, elements
.length(), elements
.begin(),
255 NewObjectKind::TenuredObject
));
263 Rooted
<ArrayObject
*> raw(
264 cx
, NewDenseCopiedArray(cx
, elements
.length(), elements
.begin(),
265 NewObjectKind::TenuredObject
));
270 // Define .raw property and freeze both arrays.
271 RootedValue
rawValue(cx
, ObjectValue(*raw
));
272 if (!DefineDataProperty(cx
, cso
, cx
->names().raw
, rawValue
, 0)) {
275 if (!FreezeObject(cx
, raw
)) {
278 if (!FreezeObject(cx
, cso
)) {
285 template <PropertySetKind kind
>
286 Shape
* InterpretObjLiteralShape(JSContext
* cx
,
287 const frontend::CompilationAtomCache
& atomCache
,
288 const mozilla::Span
<const uint8_t> literalInsns
,
289 uint32_t numFixedSlots
) {
290 ObjLiteralReader
reader(literalInsns
);
292 Rooted
<SharedPropMap
*> map(cx
);
293 uint32_t mapLength
= 0;
294 ObjectFlags objectFlags
;
299 // Make sure `insn` doesn't live across GC.
301 if (!reader
.readInsn(&insn
)) {
304 MOZ_ASSERT(insn
.isValid());
305 MOZ_ASSERT(!insn
.getKey().isArrayIndex());
306 MOZ_ASSERT(insn
.getOp() == ObjLiteralOpcode::Undefined
);
309 atomCache
.getExistingAtomAt(cx
, insn
.getKey().getAtomIndex());
311 propId
= AtomToId(jsatom
);
313 // Assert or check property names are unique.
314 if constexpr (kind
== PropertySetKind::UniqueNames
) {
315 mozilla::DebugOnly
<uint32_t> index
;
316 MOZ_ASSERT_IF(map
, !map
->lookupPure(mapLength
, propId
, &index
));
319 if (map
&& map
->lookupPure(mapLength
, propId
, &index
)) {
324 constexpr PropertyFlags propFlags
= PropertyFlags::defaultDataPropFlags
;
326 if (!SharedPropMap::addPropertyWithKnownSlot(cx
, &PlainObject::class_
, &map
,
327 &mapLength
, propId
, propFlags
,
328 slot
, &objectFlags
)) {
335 JSObject
* proto
= &cx
->global()->getObjectPrototype();
336 return SharedShape::getInitialOrPropMapShape(
337 cx
, &PlainObject::class_
, cx
->realm(), TaggedProto(proto
), numFixedSlots
,
338 map
, mapLength
, objectFlags
);
341 static Shape
* InterpretObjLiteralShape(
342 JSContext
* cx
, const frontend::CompilationAtomCache
& atomCache
,
343 const mozilla::Span
<const uint8_t> literalInsns
, ObjLiteralFlags flags
,
344 uint32_t propertyCount
) {
345 gc::AllocKind allocKind
= AllocKindForObjectLiteral(propertyCount
);
346 uint32_t numFixedSlots
= GetGCKindSlots(allocKind
);
348 if (!flags
.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName
)) {
349 return InterpretObjLiteralShape
<PropertySetKind::UniqueNames
>(
350 cx
, atomCache
, literalInsns
, numFixedSlots
);
352 return InterpretObjLiteralShape
<PropertySetKind::Normal
>(
353 cx
, atomCache
, literalInsns
, numFixedSlots
);
356 JS::GCCellPtr
ObjLiteralStencil::create(
357 JSContext
* cx
, const frontend::CompilationAtomCache
& atomCache
) const {
359 case ObjLiteralKind::Array
: {
361 InterpretObjLiteralArray(cx
, atomCache
, code_
, propertyCount_
);
363 return JS::GCCellPtr();
365 return JS::GCCellPtr(obj
);
367 case ObjLiteralKind::CallSiteObj
: {
369 InterpretObjLiteralCallSiteObj(cx
, atomCache
, code_
, propertyCount_
);
371 return JS::GCCellPtr();
373 return JS::GCCellPtr(obj
);
375 case ObjLiteralKind::Object
: {
377 InterpretObjLiteralObj(cx
, atomCache
, code_
, flags(), propertyCount_
);
379 return JS::GCCellPtr();
381 return JS::GCCellPtr(obj
);
383 case ObjLiteralKind::Shape
: {
384 Shape
* shape
= InterpretObjLiteralShape(cx
, atomCache
, code_
, flags(),
387 return JS::GCCellPtr();
389 return JS::GCCellPtr(shape
);
391 case ObjLiteralKind::Invalid
:
394 MOZ_CRASH("Invalid kind");
398 bool ObjLiteralStencil::isContainedIn(const LifoAlloc
& alloc
) const {
399 return alloc
.contains(code_
.data());
403 #if defined(DEBUG) || defined(JS_JITSPEW)
405 static void DumpObjLiteralFlagsItems(js::JSONPrinter
& json
,
406 ObjLiteralFlags flags
) {
407 if (flags
.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName
)) {
408 json
.value("HasIndexOrDuplicatePropName");
409 flags
.clearFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName
);
412 if (!flags
.isEmpty()) {
413 json
.value("Unknown(%x)", flags
.toRaw());
417 static const char* ObjLiteralKindToString(ObjLiteralKind kind
) {
419 case ObjLiteralKind::Object
:
421 case ObjLiteralKind::Array
:
423 case ObjLiteralKind::CallSiteObj
:
424 return "CallSiteObj";
425 case ObjLiteralKind::Shape
:
427 case ObjLiteralKind::Invalid
:
430 MOZ_CRASH("Invalid kind");
433 static void DumpObjLiteral(js::JSONPrinter
& json
,
434 const frontend::CompilationStencil
* stencil
,
435 mozilla::Span
<const uint8_t> code
,
436 ObjLiteralKind kind
, const ObjLiteralFlags
& flags
,
437 uint32_t propertyCount
) {
438 json
.property("kind", ObjLiteralKindToString(kind
));
440 json
.beginListProperty("flags");
441 DumpObjLiteralFlagsItems(json
, flags
);
444 json
.beginListProperty("code");
445 ObjLiteralReader
reader(code
);
447 while (reader
.readInsn(&insn
)) {
450 if (insn
.getKey().isNone()) {
451 json
.nullProperty("key");
452 } else if (insn
.getKey().isAtomIndex()) {
453 frontend::TaggedParserAtomIndex index
= insn
.getKey().getAtomIndex();
454 json
.beginObjectProperty("key");
455 DumpTaggedParserAtomIndex(json
, index
, stencil
);
457 } else if (insn
.getKey().isArrayIndex()) {
458 uint32_t index
= insn
.getKey().getArrayIndex();
459 json
.formatProperty("key", "ArrayIndex(%u)", index
);
462 switch (insn
.getOp()) {
463 case ObjLiteralOpcode::ConstValue
: {
464 const Value
& v
= insn
.getConstValue();
465 json
.formatProperty("op", "ConstValue(%f)", v
.toNumber());
468 case ObjLiteralOpcode::ConstString
: {
469 frontend::TaggedParserAtomIndex index
= insn
.getAtomIndex();
470 json
.beginObjectProperty("op");
471 DumpTaggedParserAtomIndex(json
, index
, stencil
);
475 case ObjLiteralOpcode::Null
:
476 json
.property("op", "Null");
478 case ObjLiteralOpcode::Undefined
:
479 json
.property("op", "Undefined");
481 case ObjLiteralOpcode::True
:
482 json
.property("op", "True");
484 case ObjLiteralOpcode::False
:
485 json
.property("op", "False");
488 json
.formatProperty("op", "Invalid(%x)", uint8_t(insn
.getOp()));
496 json
.property("propertyCount", propertyCount
);
499 void ObjLiteralWriter::dump() const {
500 js::Fprinter
out(stderr
);
501 js::JSONPrinter
json(out
);
505 void ObjLiteralWriter::dump(js::JSONPrinter
& json
,
506 const frontend::CompilationStencil
* stencil
) const {
508 dumpFields(json
, stencil
);
512 void ObjLiteralWriter::dumpFields(
513 js::JSONPrinter
& json
, const frontend::CompilationStencil
* stencil
) const {
514 DumpObjLiteral(json
, stencil
, getCode(), kind_
, flags_
, propertyCount_
);
517 void ObjLiteralStencil::dump() const {
518 js::Fprinter
out(stderr
);
519 js::JSONPrinter
json(out
);
523 void ObjLiteralStencil::dump(
524 js::JSONPrinter
& json
, const frontend::CompilationStencil
* stencil
) const {
526 dumpFields(json
, stencil
);
530 void ObjLiteralStencil::dumpFields(
531 js::JSONPrinter
& json
, const frontend::CompilationStencil
* stencil
) const {
532 DumpObjLiteral(json
, stencil
, code_
, kind(), flags(), propertyCount_
);
535 #endif // defined(DEBUG) || defined(JS_JITSPEW)