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