Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / frontend / ObjLiteral.cpp
blob62772c4fc7e4d0a57430edf896005c8df334a24f
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
34 namespace js {
36 bool ObjLiteralWriter::checkForDuplicatedNames(FrontendContext* fc) {
37 if (!mightContainDuplicatePropertyNames_) {
38 return true;
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>
46 propNameSet;
48 if (!propNameSet.reserve(propertyCount_)) {
49 js::ReportOutOfMemory(fc);
50 return false;
53 ObjLiteralReader reader(getCode());
55 while (true) {
56 ObjLiteralInsn insn;
57 if (!reader.readInsn(&insn)) {
58 break;
61 if (insn.getKey().isArrayIndex()) {
62 continue;
65 auto propName = insn.getKey().getAtomIndex();
67 auto p = propNameSet.lookupForAdd(propName);
68 if (p) {
69 flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
70 break;
73 // Already reserved above and doesn't fail.
74 MOZ_ALWAYS_TRUE(propNameSet.add(p, propName));
77 return true;
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;
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");
111 enum class PropertySetKind {
112 UniqueNames,
113 Normal,
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);
122 RootedId propId(cx);
123 RootedValue propVal(cx);
124 while (true) {
125 // Make sure `insn` doesn't live across GC.
126 ObjLiteralInsn insn;
127 if (!reader.readInsn(&insn)) {
128 break;
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());
136 } else {
137 JSAtom* jsatom =
138 atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex());
139 MOZ_ASSERT(jsatom);
140 propId = AtomToId(jsatom);
143 InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
145 if constexpr (kind == PropertySetKind::UniqueNames) {
146 if (!AddDataPropertyToPlainObject(cx, obj, propId, propVal)) {
147 return false;
149 } else {
150 if (!NativeDefineDataProperty(cx, obj, propId, propVal,
151 JSPROP_ENUMERATE)) {
152 return false;
156 return true;
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));
174 if (!obj) {
175 return nullptr;
178 if (!flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
179 if (!InterpretObjLiteralObj<PropertySetKind::UniqueNames>(
180 cx, obj, atomCache, literalInsns)) {
181 return nullptr;
183 } else {
184 if (!InterpretObjLiteralObj<PropertySetKind::Normal>(cx, obj, atomCache,
185 literalInsns)) {
186 return nullptr;
189 return obj;
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);
196 ObjLiteralInsn insn;
198 Rooted<ValueVector> elements(cx, ValueVector(cx));
199 if (!elements.reserve(propertyCount)) {
200 return nullptr;
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
220 // Steps 8-16.
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);
225 ObjLiteralInsn insn;
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)) {
234 return nullptr;
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.
252 readElements(count);
253 Rooted<ArrayObject*> cso(
254 cx, NewDenseCopiedArray(cx, elements.length(), elements.begin(),
255 NewObjectKind::TenuredObject));
256 if (!cso) {
257 return nullptr;
259 elements.clear();
261 // Create raw array.
262 readElements(count);
263 Rooted<ArrayObject*> raw(
264 cx, NewDenseCopiedArray(cx, elements.length(), elements.begin(),
265 NewObjectKind::TenuredObject));
266 if (!raw) {
267 return nullptr;
270 // Define .raw property and freeze both arrays.
271 RootedValue rawValue(cx, ObjectValue(*raw));
272 if (!DefineDataProperty(cx, cso, cx->names().raw, rawValue, 0)) {
273 return nullptr;
275 if (!FreezeObject(cx, raw)) {
276 return nullptr;
278 if (!FreezeObject(cx, cso)) {
279 return nullptr;
282 return 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;
296 uint32_t slot = 0;
297 RootedId propId(cx);
298 while (true) {
299 // Make sure `insn` doesn't live across GC.
300 ObjLiteralInsn insn;
301 if (!reader.readInsn(&insn)) {
302 break;
304 MOZ_ASSERT(insn.isValid());
305 MOZ_ASSERT(!insn.getKey().isArrayIndex());
306 MOZ_ASSERT(insn.getOp() == ObjLiteralOpcode::Undefined);
308 JSAtom* jsatom =
309 atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex());
310 MOZ_ASSERT(jsatom);
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));
317 } else {
318 uint32_t index;
319 if (map && map->lookupPure(mapLength, propId, &index)) {
320 continue;
324 constexpr PropertyFlags propFlags = PropertyFlags::defaultDataPropFlags;
326 if (!SharedPropMap::addPropertyWithKnownSlot(cx, &PlainObject::class_, &map,
327 &mapLength, propId, propFlags,
328 slot, &objectFlags)) {
329 return nullptr;
332 slot++;
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 {
358 switch (kind()) {
359 case ObjLiteralKind::Array: {
360 JSObject* obj =
361 InterpretObjLiteralArray(cx, atomCache, code_, propertyCount_);
362 if (!obj) {
363 return JS::GCCellPtr();
365 return JS::GCCellPtr(obj);
367 case ObjLiteralKind::CallSiteObj: {
368 JSObject* obj =
369 InterpretObjLiteralCallSiteObj(cx, atomCache, code_, propertyCount_);
370 if (!obj) {
371 return JS::GCCellPtr();
373 return JS::GCCellPtr(obj);
375 case ObjLiteralKind::Object: {
376 JSObject* obj =
377 InterpretObjLiteralObj(cx, atomCache, code_, flags(), propertyCount_);
378 if (!obj) {
379 return JS::GCCellPtr();
381 return JS::GCCellPtr(obj);
383 case ObjLiteralKind::Shape: {
384 Shape* shape = InterpretObjLiteralShape(cx, atomCache, code_, flags(),
385 propertyCount_);
386 if (!shape) {
387 return JS::GCCellPtr();
389 return JS::GCCellPtr(shape);
391 case ObjLiteralKind::Invalid:
392 break;
394 MOZ_CRASH("Invalid kind");
397 #ifdef DEBUG
398 bool ObjLiteralStencil::isContainedIn(const LifoAlloc& alloc) const {
399 return alloc.contains(code_.data());
401 #endif
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) {
418 switch (kind) {
419 case ObjLiteralKind::Object:
420 return "Object";
421 case ObjLiteralKind::Array:
422 return "Array";
423 case ObjLiteralKind::CallSiteObj:
424 return "CallSiteObj";
425 case ObjLiteralKind::Shape:
426 return "Shape";
427 case ObjLiteralKind::Invalid:
428 break;
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);
442 json.endList();
444 json.beginListProperty("code");
445 ObjLiteralReader reader(code);
446 ObjLiteralInsn insn;
447 while (reader.readInsn(&insn)) {
448 json.beginObject();
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);
456 json.endObject();
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());
466 break;
468 case ObjLiteralOpcode::ConstString: {
469 frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
470 json.beginObjectProperty("op");
471 DumpTaggedParserAtomIndex(json, index, stencil);
472 json.endObject();
473 break;
475 case ObjLiteralOpcode::Null:
476 json.property("op", "Null");
477 break;
478 case ObjLiteralOpcode::Undefined:
479 json.property("op", "Undefined");
480 break;
481 case ObjLiteralOpcode::True:
482 json.property("op", "True");
483 break;
484 case ObjLiteralOpcode::False:
485 json.property("op", "False");
486 break;
487 default:
488 json.formatProperty("op", "Invalid(%x)", uint8_t(insn.getOp()));
489 break;
492 json.endObject();
494 json.endList();
496 json.property("propertyCount", propertyCount);
499 void ObjLiteralWriter::dump() const {
500 js::Fprinter out(stderr);
501 js::JSONPrinter json(out);
502 dump(json, nullptr);
505 void ObjLiteralWriter::dump(js::JSONPrinter& json,
506 const frontend::CompilationStencil* stencil) const {
507 json.beginObject();
508 dumpFields(json, stencil);
509 json.endObject();
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);
520 dump(json, nullptr);
523 void ObjLiteralStencil::dump(
524 js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
525 json.beginObject();
526 dumpFields(json, stencil);
527 json.endObject();
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)
537 } // namespace js