From 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Tue, 27 Jun 2017 06:07:23 +0000 Subject: webkitgtk-2.16.5 --- Source/JavaScriptCore/runtime/JSObject.cpp | 2780 +++++++++++++++++++--------- 1 file changed, 1872 insertions(+), 908 deletions(-) (limited to 'Source/JavaScriptCore/runtime/JSObject.cpp') diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp index 6910b7e04..2ce1f22f7 100644 --- a/Source/JavaScriptCore/runtime/JSObject.cpp +++ b/Source/JavaScriptCore/runtime/JSObject.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) * Copyright (C) 2001 Peter Kelly (pmk@post.com) - * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2012, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2003-2017 Apple Inc. All rights reserved. * Copyright (C) 2007 Eric Seidel (eric@webkit.org) * * This library is free software; you can redistribute it and/or @@ -25,25 +25,27 @@ #include "JSObject.h" #include "ButterflyInlines.h" -#include "CopiedSpaceInlines.h" -#include "CopyVisitor.h" -#include "CopyVisitorInlines.h" +#include "CustomGetterSetter.h" #include "DatePrototype.h" #include "ErrorConstructor.h" -#include "Executable.h" +#include "Exception.h" #include "GetterSetter.h" +#include "HeapSnapshotBuilder.h" #include "IndexingHeaderInlines.h" +#include "JSCInlines.h" +#include "JSCustomGetterSetterFunction.h" #include "JSFunction.h" #include "JSGlobalObject.h" #include "Lookup.h" #include "NativeErrorConstructor.h" #include "Nodes.h" #include "ObjectPrototype.h" -#include "Operations.h" #include "PropertyDescriptor.h" #include "PropertyNameArray.h" -#include "Reject.h" +#include "PrototypeMapInlines.h" +#include "ProxyObject.h" #include "SlotVisitorInlines.h" +#include "TypeError.h" #include #include @@ -55,153 +57,379 @@ namespace JSC { // ArrayConventions.h. static unsigned lastArraySize = 0; -JSCell* getCallableObjectSlow(JSCell* cell) -{ - Structure* structure = cell->structure(); - if (structure->typeInfo().type() == JSFunctionType) - return cell; - if (structure->classInfo()->isSubClassOf(InternalFunction::info())) - return cell; - return 0; -} - STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSObject); STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSFinalObject); -const char* StrictModeReadonlyPropertyWriteError = "Attempted to assign to readonly property."; +const char* const NonExtensibleObjectPropertyDefineError = "Attempting to define property on object that is not extensible."; +const char* const ReadonlyPropertyWriteError = "Attempted to assign to readonly property."; +const char* const ReadonlyPropertyChangeError = "Attempting to change value of a readonly property."; +const char* const UnableToDeletePropertyError = "Unable to delete property."; +const char* const UnconfigurablePropertyChangeAccessMechanismError = "Attempting to change access mechanism for an unconfigurable property."; +const char* const UnconfigurablePropertyChangeConfigurabilityError = "Attempting to change configurable attribute of unconfigurable property."; +const char* const UnconfigurablePropertyChangeEnumerabilityError = "Attempting to change enumerable attribute of unconfigurable property."; +const char* const UnconfigurablePropertyChangeWritabilityError = "Attempting to change writable attribute of unconfigurable property."; -const ClassInfo JSObject::s_info = { "Object", 0, 0, 0, CREATE_METHOD_TABLE(JSObject) }; +const ClassInfo JSObject::s_info = { "Object", 0, 0, CREATE_METHOD_TABLE(JSObject) }; -const ClassInfo JSFinalObject::s_info = { "Object", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(JSFinalObject) }; +const ClassInfo JSFinalObject::s_info = { "Object", &Base::s_info, 0, CREATE_METHOD_TABLE(JSFinalObject) }; -static inline void getClassPropertyNames(ExecState* exec, const ClassInfo* classInfo, PropertyNameArray& propertyNames, EnumerationMode mode, bool didReify) +static inline void getClassPropertyNames(ExecState* exec, const ClassInfo* classInfo, PropertyNameArray& propertyNames, EnumerationMode mode) { + VM& vm = exec->vm(); + // Add properties from the static hashtables of properties for (; classInfo; classInfo = classInfo->parentClass) { - const HashTable* table = classInfo->propHashTable(exec); + const HashTable* table = classInfo->staticPropHashTable; if (!table) continue; - table->initializeIfNeeded(exec); - ASSERT(table->table); - int hashSizeMask = table->compactSize - 1; - const HashEntry* entry = table->table; - for (int i = 0; i <= hashSizeMask; ++i, ++entry) { - if (entry->key() && (!(entry->attributes() & DontEnum) || (mode == IncludeDontEnumProperties)) && !((entry->attributes() & Function) && didReify)) - propertyNames.add(entry->key()); + for (auto iter = table->begin(); iter != table->end(); ++iter) { + if (!(iter->attributes() & DontEnum) || mode.includeDontEnumProperties()) + propertyNames.add(Identifier::fromString(&vm, iter.key())); } } } -ALWAYS_INLINE void JSObject::copyButterfly(CopyVisitor& visitor, Butterfly* butterfly, size_t storageSize) +ALWAYS_INLINE void JSObject::markAuxiliaryAndVisitOutOfLineProperties(SlotVisitor& visitor, Butterfly* butterfly, Structure* structure, PropertyOffset lastOffset) { - ASSERT(butterfly); + // We call this when we found everything without races. + ASSERT(structure); - Structure* structure = this->structure(); + if (!butterfly) + return; - size_t propertyCapacity = structure->outOfLineCapacity(); + bool hasIndexingHeader = structure->hasIndexingHeader(this); size_t preCapacity; - size_t indexingPayloadSizeInBytes; - bool hasIndexingHeader = this->hasIndexingHeader(); - if (UNLIKELY(hasIndexingHeader)) { + if (hasIndexingHeader) preCapacity = butterfly->indexingHeader()->preCapacity(structure); - indexingPayloadSizeInBytes = butterfly->indexingHeader()->indexingPayloadSizeInBytes(structure); - } else { + else preCapacity = 0; - indexingPayloadSizeInBytes = 0; - } - size_t capacityInBytes = Butterfly::totalSize(preCapacity, propertyCapacity, hasIndexingHeader, indexingPayloadSizeInBytes); - if (visitor.checkIfShouldCopy(butterfly->base(preCapacity, propertyCapacity))) { - Butterfly* newButterfly = Butterfly::createUninitializedDuringCollection(visitor, preCapacity, propertyCapacity, hasIndexingHeader, indexingPayloadSizeInBytes); - - // Copy the properties. - PropertyStorage currentTarget = newButterfly->propertyStorage(); - PropertyStorage currentSource = butterfly->propertyStorage(); - for (size_t count = storageSize; count--;) - (--currentTarget)->setWithoutWriteBarrier((--currentSource)->get()); - - if (UNLIKELY(hasIndexingHeader)) { - *newButterfly->indexingHeader() = *butterfly->indexingHeader(); - - // Copy the array if appropriate. - - WriteBarrier* currentTarget; - WriteBarrier* currentSource; - size_t count; - - switch (structure->indexingType()) { - case ALL_UNDECIDED_INDEXING_TYPES: - case ALL_CONTIGUOUS_INDEXING_TYPES: - case ALL_INT32_INDEXING_TYPES: - case ALL_DOUBLE_INDEXING_TYPES: { - currentTarget = newButterfly->contiguous().data(); - currentSource = butterfly->contiguous().data(); - RELEASE_ASSERT(newButterfly->publicLength() <= newButterfly->vectorLength()); - count = newButterfly->vectorLength(); - break; - } - - case ALL_ARRAY_STORAGE_INDEXING_TYPES: { - newButterfly->arrayStorage()->copyHeaderFromDuringGC(*butterfly->arrayStorage()); - currentTarget = newButterfly->arrayStorage()->m_vector; - currentSource = butterfly->arrayStorage()->m_vector; - count = newButterfly->arrayStorage()->vectorLength(); - break; - } - - default: - currentTarget = 0; - currentSource = 0; - count = 0; - break; - } + + HeapCell* base = bitwise_cast( + butterfly->base(preCapacity, Structure::outOfLineCapacity(lastOffset))); + + ASSERT(Heap::heap(base) == visitor.heap()); + + visitor.markAuxiliary(base); + + unsigned outOfLineSize = Structure::outOfLineSize(lastOffset); + visitor.appendValuesHidden(butterfly->propertyStorage() - outOfLineSize, outOfLineSize); +} - memcpy(currentTarget, currentSource, count * sizeof(EncodedJSValue)); - } - - m_butterfly.setWithoutWriteBarrier(newButterfly); - visitor.didCopy(butterfly->base(preCapacity, propertyCapacity), capacityInBytes); - } +ALWAYS_INLINE Structure* JSObject::visitButterfly(SlotVisitor& visitor) +{ + static const char* raceReason = "JSObject::visitButterfly"; + Structure* result = visitButterflyImpl(visitor); + if (!result) + visitor.didRace(this, raceReason); + return result; } -ALWAYS_INLINE void JSObject::visitButterfly(SlotVisitor& visitor, Butterfly* butterfly, size_t storageSize) +ALWAYS_INLINE Structure* JSObject::visitButterflyImpl(SlotVisitor& visitor) { - ASSERT(butterfly); + VM& vm = visitor.vm(); - Structure* structure = this->structure(); + Butterfly* butterfly; + Structure* structure; + PropertyOffset lastOffset; - size_t propertyCapacity = structure->outOfLineCapacity(); - size_t preCapacity; - size_t indexingPayloadSizeInBytes; - bool hasIndexingHeader = this->hasIndexingHeader(); - if (UNLIKELY(hasIndexingHeader)) { - preCapacity = butterfly->indexingHeader()->preCapacity(structure); - indexingPayloadSizeInBytes = butterfly->indexingHeader()->indexingPayloadSizeInBytes(structure); - } else { - preCapacity = 0; - indexingPayloadSizeInBytes = 0; + if (visitor.mutatorIsStopped()) { + butterfly = this->butterfly(); + structure = this->structure(vm); + lastOffset = structure->lastOffset(); + + markAuxiliaryAndVisitOutOfLineProperties(visitor, butterfly, structure, lastOffset); + + switch (structure->indexingType()) { + case ALL_CONTIGUOUS_INDEXING_TYPES: + visitor.appendValuesHidden(butterfly->contiguous().data(), butterfly->publicLength()); + break; + case ALL_ARRAY_STORAGE_INDEXING_TYPES: + visitor.appendValuesHidden(butterfly->arrayStorage()->m_vector, butterfly->arrayStorage()->vectorLength()); + if (butterfly->arrayStorage()->m_sparseMap) + visitor.append(butterfly->arrayStorage()->m_sparseMap); + break; + default: + break; + } + return structure; + } + + // We want to ensure that we only scan the butterfly if we have an exactly matched structure and an + // exactly matched size. The mutator is required to perform the following shenanigans when + // reallocating the butterfly with a concurrent collector, with all fencing necessary to ensure + // that this executes as if under sequential consistency: + // + // object->structure = nuke(object->structure) + // object->butterfly = newButterfly + // structure->m_offset = newLastOffset + // object->structure = newStructure + // + // It's OK to skip this when reallocating the butterfly in a way that does not affect the m_offset. + // We have other protocols in place for that. + // + // Note that the m_offset can change without the structure changing, but in that case the mutator + // will still store null to the structure. + // + // The collector will ensure that it always sees a matched butterfly/structure by reading the + // structure before and after reading the butterfly. For simplicity, let's first consider the case + // where the only way to change the outOfLineCapacity is to change the structure. This works + // because the mutator performs the following steps sequentially: + // + // NukeStructure ChangeButterfly PutNewStructure + // + // Meanwhile the collector performs the following steps sequentially: + // + // ReadStructureEarly ReadButterfly ReadStructureLate + // + // The collector is allowed to do any of these three things: + // + // BEFORE: Scan the object with the structure and butterfly *before* the mutator's transition. + // AFTER: Scan the object with the structure and butterfly *after* the mutator's transition. + // IGNORE: Ignore the butterfly and call didRace to schedule us to be revisted again in the future. + // + // In other words, the collector will never see any torn structure/butterfly mix. It will + // always see the structure/butterfly before the transition or after but not in between. + // + // We can prove that this is correct by exhaustively considering all interleavings: + // + // NukeStructure ChangeButterfly PutNewStructure ReadStructureEarly ReadButterfly ReadStructureLate: AFTER, trivially. + // NukeStructure ChangeButterfly ReadStructureEarly PutNewStructure ReadButterfly ReadStructureLate: IGNORE, because nuked structure read early + // NukeStructure ChangeButterfly ReadStructureEarly ReadButterfly PutNewStructure ReadStructureLate: IGNORE, because nuked structure read early + // NukeStructure ChangeButterfly ReadStructureEarly ReadButterfly ReadStructureLate PutNewStructure: IGNORE, because nuked structure read early + // NukeStructure ReadStructureEarly ChangeButterfly PutNewStructure ReadButterfly ReadStructureLate: IGNORE, because nuked structure read early + // NukeStructure ReadStructureEarly ChangeButterfly ReadButterfly PutNewStructure ReadStructureLate: IGNORE, because nuked structure read early + // NukeStructure ReadStructureEarly ChangeButterfly ReadButterfly ReadStructureLate PutNewStructure: IGNORE, because nuked structure read early + // NukeStructure ReadStructureEarly ReadButterfly ChangeButterfly PutNewStructure ReadStructureLate: IGNORE, because nuked structure read early + // NukeStructure ReadStructureEarly ReadButterfly ChangeButterfly ReadStructureLate PutNewStructure: IGNORE, because nuked structure read early + // NukeStructure ReadStructureEarly ReadButterfly ReadStructureLate ChangeButterfly PutNewStructure: IGNORE, because nuked structure read early + // ReadStructureEarly NukeStructure ChangeButterfly PutNewStructure ReadButterfly ReadStructureLate: IGNORE, because early and late structures don't match + // ReadStructureEarly NukeStructure ChangeButterfly ReadButterfly PutNewStructure ReadStructureLate: IGNORE, because early and late structures don't match + // ReadStructureEarly NukeStructure ChangeButterfly ReadButterfly ReadStructureLate PutNewStructure: IGNORE, because nuked structure read late + // ReadStructureEarly NukeStructure ReadButterfly ChangeButterfly PutNewStructure ReadStructureLate: IGNORE, because early and late structures don't match + // ReadStructureEarly NukeStructure ReadButterfly ChangeButterfly ReadStructureLate PutNewStructure: IGNORE, because nuked structure read late + // ReadStructureEarly NukeStructure ReadButterfly ReadStructureLate ChangeButterfly PutNewStructure: IGNORE, because nuked structure read late + // ReadStructureEarly ReadButterfly NukeStructure ChangeButterfly PutNewStructure ReadStructureLate: IGNORE, because early and late structures don't match + // ReadStructureEarly ReadButterfly NukeStructure ChangeButterfly ReadStructureLate PutNewStructure: IGNORE, because nuked structure read late + // ReadStructureEarly ReadButterfly NukeStructure ReadStructureLate ChangeButterfly PutNewStructure: IGNORE, because nuked structure read late + // ReadStructureEarly ReadButterfly ReadStructureLate NukeStructure ChangeButterfly PutNewStructure: BEFORE, trivially. + // + // But we additionally have to worry about the size changing. We make this work by requiring that + // the collector reads the size early and late as well. Lets consider the interleaving of the + // mutator changing the size without changing the structure: + // + // NukeStructure ChangeButterfly ChangeLastOffset RestoreStructure + // + // Meanwhile the collector does: + // + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate + // + // The collector can detect races by not only comparing the early structure to the late structure + // (which will be the same before and after the algorithm runs) but also by comparing the early and + // late lastOffsets. Note: the IGNORE proofs do not cite all of the reasons why the collector will + // ignore the case, since we only need to identify one to say that we're in the ignore case. + // + // NukeStructure ChangeButterfly ChangeLastOffset RestoreStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate: AFTER, trivially + // NukeStructure ChangeButterfly ChangeLastOffset ReadStructureEarly RestoreStructure ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ChangeLastOffset ReadStructureEarly ReadLastOffsetEarly RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ChangeLastOffset ReadStructureEarly ReadLastOffsetEarly ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ChangeLastOffset ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ChangeLastOffset ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ChangeLastOffset RestoreStructure ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ChangeLastOffset ReadLastOffsetEarly RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ChangeLastOffset ReadLastOffsetEarly ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ChangeLastOffset ReadLastOffsetEarly ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ChangeLastOffset ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ChangeLastOffset RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ChangeLastOffset ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ChangeLastOffset ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ChangeLastOffset ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ChangeButterfly ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ChangeLastOffset RestoreStructure ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ChangeLastOffset ReadLastOffsetEarly RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ChangeLastOffset ReadLastOffsetEarly ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ChangeLastOffset ReadLastOffsetEarly ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ChangeLastOffset ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ChangeLastOffset RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ChangeLastOffset ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ChangeLastOffset ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ChangeLastOffset ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ReadButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ReadButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ReadButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ChangeButterfly ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ChangeLastOffset RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ChangeLastOffset ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ChangeLastOffset ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ChangeLastOffset ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ReadButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ReadButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ReadButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ReadButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ReadButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ChangeButterfly ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ChangeButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeButterfly ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeButterfly ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeButterfly ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure early + // NukeStructure ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeButterfly ChangeLastOffset RestoreStructure: IGNORE, read nuked structure early + // ReadStructureEarly NukeStructure ChangeButterfly ChangeLastOffset RestoreStructure ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate: AFTER, the ReadStructureEarly sees the same structure as after and everything else runs after. + // ReadStructureEarly NukeStructure ChangeButterfly ChangeLastOffset ReadLastOffsetEarly RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: AFTER, as above and the ReadLastOffsetEarly sees the lastOffset after. + // ReadStructureEarly NukeStructure ChangeButterfly ChangeLastOffset ReadLastOffsetEarly ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: AFTER, as above and the ReadButterfly sees the right butterfly after. + // ReadStructureEarly NukeStructure ChangeButterfly ChangeLastOffset ReadLastOffsetEarly ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure late + // ReadStructureEarly NukeStructure ChangeButterfly ChangeLastOffset ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ChangeLastOffset RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ChangeLastOffset ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ChangeLastOffset ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ChangeLastOffset ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ReadButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ReadButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ReadButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ChangeButterfly ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ChangeLastOffset RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ChangeLastOffset ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ChangeLastOffset ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ChangeLastOffset ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ReadButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ReadButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ReadButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ReadButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ReadButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ChangeButterfly ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ChangeButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ChangeButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ChangeButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ChangeButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ChangeButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ChangeButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeButterfly ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeButterfly ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ReadStructureLate ChangeButterfly ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly NukeStructure ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeButterfly ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ChangeLastOffset RestoreStructure ReadButterfly ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ChangeLastOffset ReadButterfly RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ChangeLastOffset ReadButterfly ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ChangeLastOffset ReadButterfly ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ReadButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ReadButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ReadButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ReadButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ReadButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ChangeButterfly ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ChangeButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ChangeButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ChangeButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ChangeButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ChangeButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ChangeButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ReadStructureLate ChangeButterfly ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ReadStructureLate ChangeButterfly ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ReadStructureLate ChangeButterfly ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly NukeStructure ReadButterfly ReadStructureLate ReadLastOffsetLate ChangeButterfly ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ChangeButterfly ChangeLastOffset RestoreStructure ReadStructureLate ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ChangeButterfly ChangeLastOffset ReadStructureLate RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ChangeButterfly ChangeLastOffset ReadStructureLate ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ChangeButterfly ReadStructureLate ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ChangeButterfly ReadStructureLate ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ChangeButterfly ReadStructureLate ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ReadStructureLate ChangeButterfly ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ReadStructureLate ChangeButterfly ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ReadStructureLate ChangeButterfly ReadLastOffsetLate ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly NukeStructure ReadStructureLate ReadLastOffsetLate ChangeButterfly ChangeLastOffset RestoreStructure: IGNORE, read nuked structure late + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate NukeStructure ChangeButterfly ChangeLastOffset RestoreStructure ReadLastOffsetLate: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate NukeStructure ChangeButterfly ChangeLastOffset ReadLastOffsetLate RestoreStructure: IGNORE, read different offsets + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate NukeStructure ChangeButterfly ReadLastOffsetLate ChangeLastOffset RestoreStructure: BEFORE, reads the offset before, everything else happens before + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate NukeStructure ReadLastOffsetLate ChangeButterfly ChangeLastOffset RestoreStructure: BEFORE, reads the offset before, everything else happens before + // ReadStructureEarly ReadLastOffsetEarly ReadButterfly ReadStructureLate ReadLastOffsetLate NukeStructure ChangeButterfly ChangeLastOffset RestoreStructure: BEFORE, trivially + // + // Whew. + // + // What the collector is doing is just the "double collect" snapshot from "The Unbounded Single- + // Writer Algorithm" from Yehuda Afek et al's "Atomic Snapshots of Shared Memory" in JACM 1993, + // also available here: + // + // http://people.csail.mit.edu/shanir/publications/AADGMS.pdf + // + // Unlike Afek et al's algorithm, ours does not require extra hacks to force wait-freedom (see + // "Observation 2" in the paper). This simplifies the whole algorithm. Instead we are happy with + // obstruction-freedom, and like any good obstruction-free algorithm, we ensure progress using + // scheduling. We also only collect the butterfly once instead of twice; this optimization seems + // to hold up in my proofs above and I'm not sure it's part of Afek et al's algos. + // + // For more background on this kind of madness, I like this paper; it's where I learned about + // both the snapshot algorithm and obstruction-freedom: + // + // Lunchangco, Moir, Shavit. "Nonblocking k-compare-single-swap." SPAA '03 + // https://pdfs.semanticscholar.org/343f/7182cde7669ca2a7de3dc01127927f384ef7.pdf + + StructureID structureID = this->structureID(); + if (isNuked(structureID)) + return nullptr; + structure = vm.getStructure(structureID); + lastOffset = structure->lastOffset(); + IndexingType indexingType = structure->indexingType(); + Locker locker(NoLockingNecessary); + switch (indexingType) { + case ALL_CONTIGUOUS_INDEXING_TYPES: + case ALL_ARRAY_STORAGE_INDEXING_TYPES: + // We need to hold this lock to protect against changes to the innards of the butterfly + // that can happen when the butterfly is used for array storage. We conservatively + // assume that a contiguous butterfly may transform into an array storage one, though + // this is probably more conservative than necessary. + locker = Locker(*this); + break; + default: + break; } - size_t capacityInBytes = Butterfly::totalSize(preCapacity, propertyCapacity, hasIndexingHeader, indexingPayloadSizeInBytes); - - // Mark the properties. - visitor.appendValues(butterfly->propertyStorage() - storageSize, storageSize); - visitor.copyLater( - this, ButterflyCopyToken, - butterfly->base(preCapacity, propertyCapacity), capacityInBytes); + WTF::loadLoadFence(); + butterfly = this->butterfly(); + if (!butterfly) + return structure; + WTF::loadLoadFence(); + if (this->structureID() != structureID) + return nullptr; + if (structure->lastOffset() != lastOffset) + return nullptr; + + markAuxiliaryAndVisitOutOfLineProperties(visitor, butterfly, structure, lastOffset); + + ASSERT(indexingType == structure->indexingType()); - // Mark the array if appropriate. - switch (structure->indexingType()) { + switch (indexingType) { case ALL_CONTIGUOUS_INDEXING_TYPES: - visitor.appendValues(butterfly->contiguous().data(), butterfly->publicLength()); + visitor.appendValuesHidden(butterfly->contiguous().data(), butterfly->publicLength()); break; case ALL_ARRAY_STORAGE_INDEXING_TYPES: - visitor.appendValues(butterfly->arrayStorage()->m_vector, butterfly->arrayStorage()->vectorLength()); + visitor.appendValuesHidden(butterfly->arrayStorage()->m_vector, butterfly->arrayStorage()->vectorLength()); if (butterfly->arrayStorage()->m_sparseMap) - visitor.append(&butterfly->arrayStorage()->m_sparseMap); + visitor.append(butterfly->arrayStorage()->m_sparseMap); break; default: break; } + + return structure; +} + +size_t JSObject::estimatedSize(JSCell* cell) +{ + JSObject* thisObject = jsCast(cell); + size_t butterflyOutOfLineSize = thisObject->m_butterfly ? thisObject->structure()->outOfLineSize() : 0; + return Base::estimatedSize(cell) + butterflyOutOfLineSize; } void JSObject::visitChildren(JSCell* cell, SlotVisitor& visitor) @@ -214,27 +442,50 @@ void JSObject::visitChildren(JSCell* cell, SlotVisitor& visitor) #endif JSCell::visitChildren(thisObject, visitor); - - Butterfly* butterfly = thisObject->butterfly(); - if (butterfly) - thisObject->visitButterfly(visitor, butterfly, thisObject->structure()->outOfLineSize()); - + + thisObject->visitButterfly(visitor); + #if !ASSERT_DISABLED visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation; #endif } -void JSObject::copyBackingStore(JSCell* cell, CopyVisitor& visitor, CopyToken token) +void JSObject::heapSnapshot(JSCell* cell, HeapSnapshotBuilder& builder) { JSObject* thisObject = jsCast(cell); - ASSERT_GC_OBJECT_INHERITS(thisObject, info()); - - if (token != ButterflyCopyToken) - return; - - Butterfly* butterfly = thisObject->butterfly(); - if (butterfly) - thisObject->copyButterfly(visitor, butterfly, thisObject->structure()->outOfLineSize()); + Base::heapSnapshot(cell, builder); + + Structure* structure = thisObject->structure(); + for (auto& entry : structure->getPropertiesConcurrently()) { + JSValue toValue = thisObject->getDirect(entry.offset); + if (toValue && toValue.isCell()) + builder.appendPropertyNameEdge(thisObject, toValue.asCell(), entry.key); + } + + Butterfly* butterfly = thisObject->m_butterfly.get(); + if (butterfly) { + WriteBarrier* data = nullptr; + uint32_t count = 0; + + switch (thisObject->indexingType()) { + case ALL_CONTIGUOUS_INDEXING_TYPES: + data = butterfly->contiguous().data(); + count = butterfly->publicLength(); + break; + case ALL_ARRAY_STORAGE_INDEXING_TYPES: + data = butterfly->arrayStorage()->m_vector; + count = butterfly->arrayStorage()->vectorLength(); + break; + default: + break; + } + + for (uint32_t i = 0; i < count; ++i) { + JSValue toValue = data[i].get(); + if (toValue && toValue.isCell()) + builder.appendIndexEdge(thisObject, toValue.asCell(), i); + } + } } void JSFinalObject::visitChildren(JSCell* cell, SlotVisitor& visitor) @@ -247,14 +498,12 @@ void JSFinalObject::visitChildren(JSCell* cell, SlotVisitor& visitor) #endif JSCell::visitChildren(thisObject, visitor); - - Butterfly* butterfly = thisObject->butterfly(); - if (butterfly) - thisObject->visitButterfly(visitor, butterfly, thisObject->structure()->outOfLineSize()); - - size_t storageSize = thisObject->structure()->inlineSize(); - visitor.appendValues(thisObject->inlineStorage(), storageSize); - + + if (Structure* structure = thisObject->visitButterfly(visitor)) { + if (unsigned storageSize = structure->inlineSize()) + visitor.appendValuesHidden(thisObject->inlineStorage(), storageSize); + } + #if !ASSERT_DISABLED visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation; #endif @@ -262,11 +511,63 @@ void JSFinalObject::visitChildren(JSCell* cell, SlotVisitor& visitor) String JSObject::className(const JSObject* object) { - const ClassInfo* info = object->classInfo(); + const ClassInfo* info = object->classInfo(*object->vm()); ASSERT(info); return info->className; } +String JSObject::toStringName(const JSObject* object, ExecState*) +{ + const ClassInfo* info = object->classInfo(*object->vm()); + ASSERT(info); + return info->methodTable.className(object); +} + +String JSObject::calculatedClassName(JSObject* object) +{ + String prototypeFunctionName; + auto globalObject = object->globalObject(); + VM& vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + + ExecState* exec = globalObject->globalExec(); + PropertySlot slot(object->getPrototypeDirect(), PropertySlot::InternalMethodType::VMInquiry); + PropertyName constructor(exec->propertyNames().constructor); + if (object->getPropertySlot(exec, constructor, slot)) { + if (slot.isValue()) { + JSValue constructorValue = slot.getValue(exec, constructor); + if (constructorValue.isCell()) { + if (JSCell* constructorCell = constructorValue.asCell()) { + if (JSObject* ctorObject = constructorCell->getObject()) { + if (JSFunction* constructorFunction = jsDynamicCast(vm, ctorObject)) + prototypeFunctionName = constructorFunction->calculatedDisplayName(vm); + else if (InternalFunction* constructorFunction = jsDynamicCast(vm, ctorObject)) + prototypeFunctionName = constructorFunction->calculatedDisplayName(vm); + } + } + } + } + } + ASSERT(!scope.exception() || prototypeFunctionName.isNull()); + if (UNLIKELY(scope.exception())) + scope.clearException(); + + if (prototypeFunctionName.isNull() || prototypeFunctionName == "Object") { + String tableClassName = object->methodTable()->className(object); + if (!tableClassName.isNull() && tableClassName != "Object") + return tableClassName; + + String classInfoName = object->classInfo(vm)->className; + if (!classInfoName.isNull()) + return classInfoName; + + if (prototypeFunctionName.isNull()) + return ASCIILiteral("Object"); + } + + return prototypeFunctionName; +} + bool JSObject::getOwnPropertySlotByIndex(JSObject* thisObject, ExecState* exec, unsigned i, PropertySlot& slot) { // NB. The fact that we're directly consulting our indexed storage implies that it is not @@ -274,9 +575,9 @@ bool JSObject::getOwnPropertySlotByIndex(JSObject* thisObject, ExecState* exec, // getOwnPropertySlotByIndex(). if (i > MAX_ARRAY_INDEX) - return thisObject->methodTable()->getOwnPropertySlot(thisObject, exec, Identifier::from(exec, i), slot); + return thisObject->methodTable(exec->vm())->getOwnPropertySlot(thisObject, exec, Identifier::from(exec, i), slot); - switch (thisObject->structure()->indexingType()) { + switch (thisObject->indexingType()) { case ALL_BLANK_INDEXING_TYPES: case ALL_UNDECIDED_INDEXING_TYPES: break; @@ -311,7 +612,7 @@ bool JSObject::getOwnPropertySlotByIndex(JSObject* thisObject, ExecState* exec, } case ALL_ARRAY_STORAGE_INDEXING_TYPES: { - ArrayStorage* storage = thisObject->m_butterfly->arrayStorage(); + ArrayStorage* storage = thisObject->m_butterfly.get()->arrayStorage(); if (i >= storage->length()) return false; @@ -339,105 +640,203 @@ bool JSObject::getOwnPropertySlotByIndex(JSObject* thisObject, ExecState* exec, return false; } -// ECMA 8.6.2.2 -void JSObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +// https://tc39.github.io/ecma262/#sec-ordinaryset +bool ordinarySetSlow(ExecState* exec, JSObject* object, PropertyName propertyName, JSValue value, JSValue receiver, bool shouldThrow) { - JSObject* thisObject = jsCast(cell); - ASSERT(value); - ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject)); + // If we find the receiver is not the same to the object, we fall to this slow path. + // Currently, there are 3 candidates. + // 1. Reflect.set can alter the receiver with an arbitrary value. + // 2. Window Proxy. + // 3. ES6 Proxy. + VM& vm = exec->vm(); - - // Try indexed put first. This is required for correctness, since loads on property names that appear like - // valid indices will never look in the named property storage. - unsigned i = propertyName.asIndex(); - if (i != PropertyName::NotAnIndex) { - putByIndex(thisObject, exec, i, value, slot.isStrictMode()); - return; - } - - // Check if there are any setters or getters in the prototype chain - JSValue prototype; - if (propertyName != exec->propertyNames().underscoreProto) { - for (JSObject* obj = thisObject; !obj->structure()->hasReadOnlyOrGetterSetterPropertiesExcludingProto(); obj = asObject(prototype)) { - prototype = obj->prototype(); - if (prototype.isNull()) { - ASSERT(!thisObject->structure()->prototypeChainMayInterceptStoreTo(exec->vm(), propertyName)); - if (!thisObject->putDirectInternal(vm, propertyName, value, 0, slot, getCallableObject(value)) - && slot.isStrictMode()) - throwTypeError(exec, ASCIILiteral(StrictModeReadonlyPropertyWriteError)); - return; + auto scope = DECLARE_THROW_SCOPE(vm); + JSObject* current = object; + PropertyDescriptor ownDescriptor; + while (true) { + if (current->type() == ProxyObjectType && propertyName != vm.propertyNames->underscoreProto) { + ProxyObject* proxy = jsCast(current); + PutPropertySlot slot(receiver, shouldThrow); + return proxy->ProxyObject::put(proxy, exec, propertyName, value, slot); + } + + // 9.1.9.1-2 Let ownDesc be ? O.[[GetOwnProperty]](P). + bool ownDescriptorFound = current->getOwnPropertyDescriptor(exec, propertyName, ownDescriptor); + RETURN_IF_EXCEPTION(scope, false); + + if (!ownDescriptorFound) { + // 9.1.9.1-3-a Let parent be ? O.[[GetPrototypeOf]](). + JSValue prototype = current->getPrototype(vm, exec); + RETURN_IF_EXCEPTION(scope, false); + + // 9.1.9.1-3-b If parent is not null, then + if (!prototype.isNull()) { + // 9.1.9.1-3-b-i Return ? parent.[[Set]](P, V, Receiver). + current = asObject(prototype); + continue; } + // 9.1.9.1-3-c-i Let ownDesc be the PropertyDescriptor{[[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}. + ownDescriptor = PropertyDescriptor(jsUndefined(), None); } + break; + } + + // 9.1.9.1-4 If IsDataDescriptor(ownDesc) is true, then + if (ownDescriptor.isDataDescriptor()) { + // 9.1.9.1-4-a If ownDesc.[[Writable]] is false, return false. + if (!ownDescriptor.writable()) + return typeError(exec, scope, shouldThrow, ASCIILiteral(ReadonlyPropertyWriteError)); + + // 9.1.9.1-4-b If Type(Receiver) is not Object, return false. + if (!receiver.isObject()) + return typeError(exec, scope, shouldThrow, ASCIILiteral(ReadonlyPropertyWriteError)); + + // In OrdinarySet, the receiver may not be the same to the object. + // So, we perform [[GetOwnProperty]] onto the receiver while we already perform [[GetOwnProperty]] onto the object. + + // 9.1.9.1-4-c Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). + JSObject* receiverObject = asObject(receiver); + PropertyDescriptor existingDescriptor; + bool existingDescriptorFound = receiverObject->getOwnPropertyDescriptor(exec, propertyName, existingDescriptor); + RETURN_IF_EXCEPTION(scope, false); + + // 9.1.9.1-4-d If existingDescriptor is not undefined, then + if (existingDescriptorFound) { + // 9.1.9.1-4-d-i If IsAccessorDescriptor(existingDescriptor) is true, return false. + if (existingDescriptor.isAccessorDescriptor()) + return typeError(exec, scope, shouldThrow, ASCIILiteral(ReadonlyPropertyWriteError)); + + // 9.1.9.1-4-d-ii If existingDescriptor.[[Writable]] is false, return false. + if (!existingDescriptor.writable()) + return typeError(exec, scope, shouldThrow, ASCIILiteral(ReadonlyPropertyWriteError)); + + // 9.1.9.1-4-d-iii Let valueDesc be the PropertyDescriptor{[[Value]]: V}. + PropertyDescriptor valueDescriptor; + valueDescriptor.setValue(value); + + // 9.1.9.1-4-d-iv Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). + return receiverObject->methodTable(vm)->defineOwnProperty(receiverObject, exec, propertyName, valueDescriptor, shouldThrow); + } + + // 9.1.9.1-4-e Else Receiver does not currently have a property P, + // 9.1.9.1-4-e-i Return ? CreateDataProperty(Receiver, P, V). + return receiverObject->methodTable(vm)->defineOwnProperty(receiverObject, exec, propertyName, PropertyDescriptor(value, None), shouldThrow); } - JSObject* obj; - for (obj = thisObject; ; obj = asObject(prototype)) { + // 9.1.9.1-5 Assert: IsAccessorDescriptor(ownDesc) is true. + ASSERT(ownDescriptor.isAccessorDescriptor()); + + // 9.1.9.1-6 Let setter be ownDesc.[[Set]]. + // 9.1.9.1-7 If setter is undefined, return false. + JSValue setter = ownDescriptor.setter(); + if (!setter.isObject()) + return typeError(exec, scope, shouldThrow, ASCIILiteral(ReadonlyPropertyWriteError)); + + // 9.1.9.1-8 Perform ? Call(setter, Receiver, << V >>). + JSObject* setterObject = asObject(setter); + MarkedArgumentBuffer args; + args.append(value); + + CallData callData; + CallType callType = setterObject->methodTable(vm)->getCallData(setterObject, callData); + call(exec, setterObject, callType, callData, receiver, args); + + // 9.1.9.1-9 Return true. + return true; +} + +// ECMA 8.6.2.2 +bool JSObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +{ + return putInline(cell, exec, propertyName, value, slot); +} + +bool JSObject::putInlineSlow(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +{ + ASSERT(!isThisValueAltered(slot, this)); + + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSObject* obj = this; + for (;;) { unsigned attributes; - JSCell* specificValue; - PropertyOffset offset = obj->structure()->get(vm, propertyName, attributes, specificValue); + PropertyOffset offset = obj->structure(vm)->get(vm, propertyName, attributes); if (isValidOffset(offset)) { if (attributes & ReadOnly) { - ASSERT(thisObject->structure()->prototypeChainMayInterceptStoreTo(exec->vm(), propertyName) || obj == thisObject); - if (slot.isStrictMode()) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral(StrictModeReadonlyPropertyWriteError))); - return; + ASSERT(structure(vm)->prototypeChainMayInterceptStoreTo(vm, propertyName) || obj == this); + return typeError(exec, scope, slot.isStrictMode(), ASCIILiteral(ReadonlyPropertyWriteError)); } JSValue gs = obj->getDirect(offset); if (gs.isGetterSetter()) { - callSetter(exec, cell, gs, value, slot.isStrictMode() ? StrictMode : NotStrictMode); - return; - } else - ASSERT(!(attributes & Accessor)); + bool result = callSetter(exec, slot.thisValue(), gs, value, slot.isStrictMode() ? StrictMode : NotStrictMode); + if (!structure()->isDictionary()) + slot.setCacheableSetter(obj, offset); + return result; + } + if (gs.isCustomGetterSetter()) { + bool result = callCustomSetter(exec, gs, attributes & CustomAccessor, obj, slot.thisValue(), value); + if (attributes & CustomAccessor) + slot.setCustomAccessor(obj, jsCast(gs.asCell())->setter()); + else + slot.setCustomValue(obj, jsCast(gs.asCell())->setter()); + return result; + } + ASSERT(!(attributes & Accessor)); // If there's an existing property on the object or one of its // prototypes it should be replaced, so break here. break; } - const ClassInfo* info = obj->classInfo(); - if (info->hasStaticSetterOrReadonlyProperties(vm)) { - if (const HashEntry* entry = obj->findPropertyHashEntry(exec, propertyName)) { - putEntry(exec, entry, obj, propertyName, value, slot); - return; + if (!obj->staticPropertiesReified()) { + if (obj->classInfo(vm)->hasStaticSetterOrReadonlyProperties()) { + if (auto* entry = obj->findPropertyHashEntry(vm, propertyName)) + return putEntry(exec, entry, obj, this, propertyName, value, slot); } } - prototype = obj->prototype(); + if (obj->type() == ProxyObjectType && propertyName != vm.propertyNames->underscoreProto) { + // FIXME: We shouldn't unconditionally perform [[Set]] here. + // We need to do more because this is observable behavior. + // https://bugs.webkit.org/show_bug.cgi?id=155012 + ProxyObject* proxy = jsCast(obj); + return proxy->ProxyObject::put(proxy, exec, propertyName, value, slot); + } + JSValue prototype = obj->getPrototypeDirect(); if (prototype.isNull()) break; + obj = asObject(prototype); } - - ASSERT(!thisObject->structure()->prototypeChainMayInterceptStoreTo(exec->vm(), propertyName) || obj == thisObject); - if (!thisObject->putDirectInternal(vm, propertyName, value, 0, slot, getCallableObject(value)) && slot.isStrictMode()) - throwTypeError(exec, ASCIILiteral(StrictModeReadonlyPropertyWriteError)); - return; + + ASSERT(!structure(vm)->prototypeChainMayInterceptStoreTo(vm, propertyName) || obj == this); + if (!putDirectInternal(vm, propertyName, value, 0, slot)) + return typeError(exec, scope, slot.isStrictMode(), ASCIILiteral(ReadonlyPropertyWriteError)); + return true; } -void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow) +bool JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow) { JSObject* thisObject = jsCast(cell); if (propertyName > MAX_ARRAY_INDEX) { PutPropertySlot slot(cell, shouldThrow); - thisObject->methodTable()->put(thisObject, exec, Identifier::from(exec, propertyName), value, slot); - return; + return thisObject->methodTable()->put(thisObject, exec, Identifier::from(exec, propertyName), value, slot); } - switch (thisObject->structure()->indexingType()) { + switch (thisObject->indexingType()) { case ALL_BLANK_INDEXING_TYPES: break; case ALL_UNDECIDED_INDEXING_TYPES: { thisObject->convertUndecidedForValue(exec->vm(), value); // Reloop. - putByIndex(cell, exec, propertyName, value, shouldThrow); - return; + return putByIndex(cell, exec, propertyName, value, shouldThrow); } case ALL_INT32_INDEXING_TYPES: { if (!value.isInt32()) { thisObject->convertInt32ForValue(exec->vm(), value); - putByIndex(cell, exec, propertyName, value, shouldThrow); - return; + return putByIndex(cell, exec, propertyName, value, shouldThrow); } FALLTHROUGH; } @@ -449,22 +848,20 @@ void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, butterfly->contiguous()[propertyName].set(exec->vm(), thisObject, value); if (propertyName >= butterfly->publicLength()) butterfly->setPublicLength(propertyName + 1); - return; + return true; } case ALL_DOUBLE_INDEXING_TYPES: { if (!value.isNumber()) { thisObject->convertDoubleToContiguous(exec->vm()); // Reloop. - putByIndex(cell, exec, propertyName, value, shouldThrow); - return; + return putByIndex(cell, exec, propertyName, value, shouldThrow); } double valueAsDouble = value.asNumber(); if (valueAsDouble != valueAsDouble) { thisObject->convertDoubleToContiguous(exec->vm()); // Reloop. - putByIndex(cell, exec, propertyName, value, shouldThrow); - return; + return putByIndex(cell, exec, propertyName, value, shouldThrow); } Butterfly* butterfly = thisObject->butterfly(); if (propertyName >= butterfly->vectorLength()) @@ -472,12 +869,12 @@ void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, butterfly->contiguousDouble()[propertyName] = valueAsDouble; if (propertyName >= butterfly->publicLength()) butterfly->setPublicLength(propertyName + 1); - return; + return true; } case NonArrayWithArrayStorage: case ArrayWithArrayStorage: { - ArrayStorage* storage = thisObject->m_butterfly->arrayStorage(); + ArrayStorage* storage = thisObject->m_butterfly.get()->arrayStorage(); if (propertyName >= storage->vectorLength()) break; @@ -494,12 +891,12 @@ void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, ++storage->m_numValuesInVector; valueSlot.set(exec->vm(), thisObject, value); - return; + return true; } case NonArrayWithSlowPutArrayStorage: case ArrayWithSlowPutArrayStorage: { - ArrayStorage* storage = thisObject->m_butterfly->arrayStorage(); + ArrayStorage* storage = thisObject->m_butterfly.get()->arrayStorage(); if (propertyName >= storage->vectorLength()) break; @@ -509,26 +906,28 @@ void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, // Update length & m_numValuesInVector as necessary. if (propertyName >= length) { - if (thisObject->attemptToInterceptPutByIndexOnHole(exec, propertyName, value, shouldThrow)) - return; + bool putResult = false; + if (thisObject->attemptToInterceptPutByIndexOnHole(exec, propertyName, value, shouldThrow, putResult)) + return putResult; length = propertyName + 1; storage->setLength(length); ++storage->m_numValuesInVector; } else if (!valueSlot) { - if (thisObject->attemptToInterceptPutByIndexOnHole(exec, propertyName, value, shouldThrow)) - return; + bool putResult = false; + if (thisObject->attemptToInterceptPutByIndexOnHole(exec, propertyName, value, shouldThrow, putResult)) + return putResult; ++storage->m_numValuesInVector; } valueSlot.set(exec->vm(), thisObject, value); - return; + return true; } default: RELEASE_ASSERT_NOT_REACHED(); } - thisObject->putByIndexBeyondVectorLength(exec, propertyName, value, shouldThrow); + return thisObject->putByIndexBeyondVectorLength(exec, propertyName, value, shouldThrow); } ArrayStorage* JSObject::enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(VM& vm, ArrayStorage* storage) @@ -549,34 +948,37 @@ ArrayStorage* JSObject::enterDictionaryIndexingModeWhenArrayStorageAlreadyExists // This will always be a new entry in the map, so no need to check we can write, // and attributes are default so no need to set them. if (value) - map->add(this, i).iterator->value.set(vm, this, value); + map->add(this, i).iterator->value.set(vm, map, value); } DeferGC deferGC(vm.heap); - Butterfly* newButterfly = storage->butterfly()->resizeArray(vm, this, structure(), 0, ArrayStorage::sizeFor(0)); + Butterfly* newButterfly = storage->butterfly()->resizeArray(vm, this, structure(vm), 0, ArrayStorage::sizeFor(0)); RELEASE_ASSERT(newButterfly); newButterfly->arrayStorage()->m_indexBias = 0; newButterfly->arrayStorage()->setVectorLength(0); newButterfly->arrayStorage()->m_sparseMap.set(vm, this, map); - setButterflyWithoutChangingStructure(vm, newButterfly); + setButterfly(vm, newButterfly); return newButterfly->arrayStorage(); } void JSObject::enterDictionaryIndexingMode(VM& vm) { - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: case ALL_UNDECIDED_INDEXING_TYPES: case ALL_INT32_INDEXING_TYPES: case ALL_DOUBLE_INDEXING_TYPES: case ALL_CONTIGUOUS_INDEXING_TYPES: // NOTE: this is horribly inefficient, as it will perform two conversions. We could optimize - // this case if we ever cared. - enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, ensureArrayStorageSlow(vm)); + // this case if we ever cared. Note that ensureArrayStorage() can return null if the object + // doesn't support traditional indexed properties. At the time of writing, this just affects + // typed arrays. + if (ArrayStorage* storage = ensureArrayStorageSlow(vm)) + enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, storage); break; case ALL_ARRAY_STORAGE_INDEXING_TYPES: - enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, m_butterfly->arrayStorage()); + enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, m_butterfly.get()->arrayStorage()); break; default: @@ -589,25 +991,27 @@ void JSObject::notifyPresenceOfIndexedAccessors(VM& vm) if (mayInterceptIndexedAccesses()) return; - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AddIndexedAccessors)); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AddIndexedAccessors)); if (!vm.prototypeMap.isPrototype(this)) return; - globalObject()->haveABadTime(vm); + globalObject(vm)->haveABadTime(vm); } -Butterfly* JSObject::createInitialIndexedStorage(VM& vm, unsigned length, size_t elementSize) +Butterfly* JSObject::createInitialIndexedStorage(VM& vm, unsigned length) { ASSERT(length < MAX_ARRAY_INDEX); - IndexingType oldType = structure()->indexingType(); + IndexingType oldType = indexingType(); ASSERT_UNUSED(oldType, !hasIndexedProperties(oldType)); ASSERT(!structure()->needsSlowPutIndexing()); ASSERT(!indexingShouldBeSparse()); - unsigned vectorLength = std::max(length, BASE_VECTOR_LEN); + Structure* structure = this->structure(vm); + unsigned propertyCapacity = structure->outOfLineCapacity(); + unsigned vectorLength = Butterfly::optimalContiguousVectorLength(propertyCapacity, length); Butterfly* newButterfly = Butterfly::createOrGrowArrayRight( - m_butterfly.get(), vm, this, structure(), structure()->outOfLineCapacity(), false, 0, - elementSize * vectorLength); + m_butterfly.get(), vm, this, structure, propertyCapacity, false, 0, + sizeof(EncodedJSValue) * vectorLength); newButterfly->setPublicLength(length); newButterfly->setVectorLength(vectorLength); return newButterfly; @@ -616,48 +1020,61 @@ Butterfly* JSObject::createInitialIndexedStorage(VM& vm, unsigned length, size_t Butterfly* JSObject::createInitialUndecided(VM& vm, unsigned length) { DeferGC deferGC(vm.heap); - Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(EncodedJSValue)); - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), AllocateUndecided); - setStructureAndButterfly(vm, newStructure, newButterfly); + Butterfly* newButterfly = createInitialIndexedStorage(vm, length); + StructureID oldStructureID = this->structureID(); + Structure* oldStructure = vm.getStructure(oldStructureID); + Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, NonPropertyTransition::AllocateUndecided); + nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly); + setStructure(vm, newStructure); return newButterfly; } ContiguousJSValues JSObject::createInitialInt32(VM& vm, unsigned length) { DeferGC deferGC(vm.heap); - Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(EncodedJSValue)); - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), AllocateInt32); - setStructureAndButterfly(vm, newStructure, newButterfly); + Butterfly* newButterfly = createInitialIndexedStorage(vm, length); + for (unsigned i = newButterfly->vectorLength(); i--;) + newButterfly->contiguousInt32()[i].setWithoutWriteBarrier(JSValue()); + StructureID oldStructureID = this->structureID(); + Structure* oldStructure = vm.getStructure(oldStructureID); + Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, NonPropertyTransition::AllocateInt32); + nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly); + setStructure(vm, newStructure); return newButterfly->contiguousInt32(); } ContiguousDoubles JSObject::createInitialDouble(VM& vm, unsigned length) { DeferGC deferGC(vm.heap); - Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(double)); + Butterfly* newButterfly = createInitialIndexedStorage(vm, length); for (unsigned i = newButterfly->vectorLength(); i--;) - newButterfly->contiguousDouble()[i] = QNaN; - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), AllocateDouble); - setStructureAndButterfly(vm, newStructure, newButterfly); + newButterfly->contiguousDouble()[i] = PNaN; + StructureID oldStructureID = this->structureID(); + Structure* oldStructure = vm.getStructure(oldStructureID); + Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, NonPropertyTransition::AllocateDouble); + nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly); + setStructure(vm, newStructure); return newButterfly->contiguousDouble(); } ContiguousJSValues JSObject::createInitialContiguous(VM& vm, unsigned length) { DeferGC deferGC(vm.heap); - Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(EncodedJSValue)); - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), AllocateContiguous); - setStructureAndButterfly(vm, newStructure, newButterfly); + Butterfly* newButterfly = createInitialIndexedStorage(vm, length); + for (unsigned i = newButterfly->vectorLength(); i--;) + newButterfly->contiguous()[i].setWithoutWriteBarrier(JSValue()); + StructureID oldStructureID = this->structureID(); + Structure* oldStructure = vm.getStructure(oldStructureID); + Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, NonPropertyTransition::AllocateContiguous); + nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly); + setStructure(vm, newStructure); return newButterfly->contiguous(); } -ArrayStorage* JSObject::createArrayStorage(VM& vm, unsigned length, unsigned vectorLength) +Butterfly* JSObject::createArrayStorageButterfly(VM& vm, JSCell* intendedOwner, Structure* structure, unsigned length, unsigned vectorLength, Butterfly* oldButterfly) { - DeferGC deferGC(vm.heap); - IndexingType oldType = structure()->indexingType(); - ASSERT_UNUSED(oldType, !hasIndexedProperties(oldType)); Butterfly* newButterfly = Butterfly::createOrGrowArrayRight( - m_butterfly.get(), vm, this, structure(), structure()->outOfLineCapacity(), false, 0, + oldButterfly, vm, intendedOwner, structure, structure->outOfLineCapacity(), false, 0, ArrayStorage::sizeFor(vectorLength)); RELEASE_ASSERT(newButterfly); @@ -667,53 +1084,84 @@ ArrayStorage* JSObject::createArrayStorage(VM& vm, unsigned length, unsigned vec result->m_sparseMap.clear(); result->m_numValuesInVector = 0; result->m_indexBias = 0; - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), structure()->suggestedArrayStorageTransition()); - setStructureAndButterfly(vm, newStructure, newButterfly); + for (size_t i = vectorLength; i--;) + result->m_vector[i].setWithoutWriteBarrier(JSValue()); + + return newButterfly; +} + +ArrayStorage* JSObject::createArrayStorage(VM& vm, unsigned length, unsigned vectorLength) +{ + DeferGC deferGC(vm.heap); + StructureID oldStructureID = this->structureID(); + Structure* oldStructure = vm.getStructure(oldStructureID); + IndexingType oldType = indexingType(); + ASSERT_UNUSED(oldType, !hasIndexedProperties(oldType)); + + Butterfly* newButterfly = createArrayStorageButterfly(vm, this, oldStructure, length, vectorLength, m_butterfly.get()); + ArrayStorage* result = newButterfly->arrayStorage(); + Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, oldStructure->suggestedArrayStorageTransition()); + nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly); + setStructure(vm, newStructure); return result; } ArrayStorage* JSObject::createInitialArrayStorage(VM& vm) { - return createArrayStorage(vm, 0, BASE_VECTOR_LEN); + return createArrayStorage( + vm, 0, ArrayStorage::optimalVectorLength(0, structure(vm)->outOfLineCapacity(), 0)); } ContiguousJSValues JSObject::convertUndecidedToInt32(VM& vm) { - ASSERT(hasUndecided(structure()->indexingType())); - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateInt32)); - return m_butterfly->contiguousInt32(); + ASSERT(hasUndecided(indexingType())); + + Butterfly* butterfly = m_butterfly.get(); + for (unsigned i = butterfly->vectorLength(); i--;) + butterfly->contiguousInt32()[i].setWithoutWriteBarrier(JSValue()); + + setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateInt32)); + return m_butterfly.get()->contiguousInt32(); } ContiguousDoubles JSObject::convertUndecidedToDouble(VM& vm) { - ASSERT(hasUndecided(structure()->indexingType())); - - for (unsigned i = m_butterfly->vectorLength(); i--;) - m_butterfly->contiguousDouble()[i] = QNaN; + ASSERT(hasUndecided(indexingType())); + + Butterfly* butterfly = m_butterfly.get(); + for (unsigned i = butterfly->vectorLength(); i--;) + butterfly->contiguousDouble()[i] = PNaN; - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateDouble)); - return m_butterfly->contiguousDouble(); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateDouble)); + return m_butterfly.get()->contiguousDouble(); } ContiguousJSValues JSObject::convertUndecidedToContiguous(VM& vm) { - ASSERT(hasUndecided(structure()->indexingType())); - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous)); - return m_butterfly->contiguous(); + ASSERT(hasUndecided(indexingType())); + + Butterfly* butterfly = m_butterfly.get(); + for (unsigned i = butterfly->vectorLength(); i--;) + butterfly->contiguous()[i].setWithoutWriteBarrier(JSValue()); + + WTF::storeStoreFence(); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateContiguous)); + return m_butterfly.get()->contiguous(); } ArrayStorage* JSObject::constructConvertedArrayStorageWithoutCopyingElements(VM& vm, unsigned neededLength) { - unsigned publicLength = m_butterfly->publicLength(); - unsigned propertyCapacity = structure()->outOfLineCapacity(); - unsigned propertySize = structure()->outOfLineSize(); + Structure* structure = this->structure(vm); + unsigned publicLength = m_butterfly.get()->publicLength(); + unsigned propertyCapacity = structure->outOfLineCapacity(); + unsigned propertySize = structure->outOfLineSize(); Butterfly* newButterfly = Butterfly::createUninitialized( vm, this, 0, propertyCapacity, true, ArrayStorage::sizeFor(neededLength)); memcpy( newButterfly->propertyStorage() - propertySize, - m_butterfly->propertyStorage() - propertySize, + m_butterfly.get()->propertyStorage() - propertySize, propertySize * sizeof(EncodedJSValue)); ArrayStorage* newStorage = newButterfly->arrayStorage(); @@ -726,196 +1174,204 @@ ArrayStorage* JSObject::constructConvertedArrayStorageWithoutCopyingElements(VM& return newStorage; } -ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm, NonPropertyTransition transition, unsigned neededLength) +ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm, NonPropertyTransition transition) { DeferGC deferGC(vm.heap); - ASSERT(hasUndecided(structure()->indexingType())); + ASSERT(hasUndecided(indexingType())); + + unsigned vectorLength = m_butterfly.get()->vectorLength(); + ArrayStorage* storage = constructConvertedArrayStorageWithoutCopyingElements(vm, vectorLength); - ArrayStorage* storage = constructConvertedArrayStorageWithoutCopyingElements(vm, neededLength); - // No need to copy elements. + for (unsigned i = vectorLength; i--;) + storage->m_vector[i].setWithoutWriteBarrier(JSValue()); - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), transition); - setStructureAndButterfly(vm, newStructure, storage->butterfly()); + StructureID oldStructureID = this->structureID(); + Structure* oldStructure = vm.getStructure(oldStructureID); + Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, transition); + nukeStructureAndSetButterfly(vm, oldStructureID, storage->butterfly()); + setStructure(vm, newStructure); return storage; } -ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm, NonPropertyTransition transition) -{ - return convertUndecidedToArrayStorage(vm, transition, m_butterfly->vectorLength()); -} - ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm) { - return convertUndecidedToArrayStorage(vm, structure()->suggestedArrayStorageTransition()); + return convertUndecidedToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition()); } ContiguousDoubles JSObject::convertInt32ToDouble(VM& vm) { - ASSERT(hasInt32(structure()->indexingType())); - - for (unsigned i = m_butterfly->vectorLength(); i--;) { - WriteBarrier* current = &m_butterfly->contiguousInt32()[i]; + ASSERT(hasInt32(indexingType())); + + Butterfly* butterfly = m_butterfly.get(); + for (unsigned i = butterfly->vectorLength(); i--;) { + WriteBarrier* current = &butterfly->contiguousInt32()[i]; double* currentAsDouble = bitwise_cast(current); JSValue v = current->get(); - if (!v) { - *currentAsDouble = QNaN; + // NOTE: Since this may be used during initialization, v could be garbage. If it's garbage, + // that means it will be overwritten later. + if (!v.isInt32()) { + *currentAsDouble = PNaN; continue; } - ASSERT(v.isInt32()); *currentAsDouble = v.asInt32(); } - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateDouble)); - return m_butterfly->contiguousDouble(); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateDouble)); + return m_butterfly.get()->contiguousDouble(); } ContiguousJSValues JSObject::convertInt32ToContiguous(VM& vm) { - ASSERT(hasInt32(structure()->indexingType())); + ASSERT(hasInt32(indexingType())); - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous)); - return m_butterfly->contiguous(); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateContiguous)); + return m_butterfly.get()->contiguous(); } -ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm, NonPropertyTransition transition, unsigned neededLength) +ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm, NonPropertyTransition transition) { - ASSERT(hasInt32(structure()->indexingType())); - DeferGC deferGC(vm.heap); - ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, neededLength); - for (unsigned i = m_butterfly->publicLength(); i--;) { - JSValue v = m_butterfly->contiguous()[i].get(); - if (!v) - continue; + ASSERT(hasInt32(indexingType())); + + unsigned vectorLength = m_butterfly.get()->vectorLength(); + ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, vectorLength); + Butterfly* butterfly = m_butterfly.get(); + for (unsigned i = 0; i < vectorLength; i++) { + JSValue v = butterfly->contiguous()[i].get(); newStorage->m_vector[i].setWithoutWriteBarrier(v); - newStorage->m_numValuesInVector++; + if (v) + newStorage->m_numValuesInVector++; } - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), transition); - setStructureAndButterfly(vm, newStructure, newStorage->butterfly()); + StructureID oldStructureID = this->structureID(); + Structure* oldStructure = vm.getStructure(oldStructureID); + Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, transition); + nukeStructureAndSetButterfly(vm, oldStructureID, newStorage->butterfly()); + setStructure(vm, newStructure); return newStorage; } -ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm, NonPropertyTransition transition) -{ - return convertInt32ToArrayStorage(vm, transition, m_butterfly->vectorLength()); -} - ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm) { - return convertInt32ToArrayStorage(vm, structure()->suggestedArrayStorageTransition()); + return convertInt32ToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition()); } -template -ContiguousJSValues JSObject::genericConvertDoubleToContiguous(VM& vm) +ContiguousJSValues JSObject::convertDoubleToContiguous(VM& vm) { - ASSERT(hasDouble(structure()->indexingType())); - - for (unsigned i = m_butterfly->vectorLength(); i--;) { - double* current = &m_butterfly->contiguousDouble()[i]; + ASSERT(hasDouble(indexingType())); + + Butterfly* butterfly = m_butterfly.get(); + for (unsigned i = butterfly->vectorLength(); i--;) { + double* current = &butterfly->contiguousDouble()[i]; WriteBarrier* currentAsValue = bitwise_cast*>(current); double value = *current; if (value != value) { currentAsValue->clear(); continue; } - JSValue v; - switch (mode) { - case EncodeValueAsDouble: - v = JSValue(JSValue::EncodeAsDouble, value); - break; - case RageConvertDoubleToValue: - v = jsNumber(value); - break; - } - ASSERT(v.isNumber()); + JSValue v = JSValue(JSValue::EncodeAsDouble, value); currentAsValue->setWithoutWriteBarrier(v); } - setStructure(vm, Structure::nonPropertyTransition(vm, structure(), AllocateContiguous)); - return m_butterfly->contiguous(); -} - -ContiguousJSValues JSObject::convertDoubleToContiguous(VM& vm) -{ - return genericConvertDoubleToContiguous(vm); -} - -ContiguousJSValues JSObject::rageConvertDoubleToContiguous(VM& vm) -{ - return genericConvertDoubleToContiguous(vm); + WTF::storeStoreFence(); + setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::AllocateContiguous)); + return m_butterfly.get()->contiguous(); } -ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm, NonPropertyTransition transition, unsigned neededLength) +ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm, NonPropertyTransition transition) { DeferGC deferGC(vm.heap); - ASSERT(hasDouble(structure()->indexingType())); - - ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, neededLength); - for (unsigned i = m_butterfly->publicLength(); i--;) { - double value = m_butterfly->contiguousDouble()[i]; - if (value != value) + ASSERT(hasDouble(indexingType())); + + unsigned vectorLength = m_butterfly.get()->vectorLength(); + ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, vectorLength); + Butterfly* butterfly = m_butterfly.get(); + for (unsigned i = 0; i < vectorLength; i++) { + double value = butterfly->contiguousDouble()[i]; + if (value != value) { + newStorage->m_vector[i].clear(); continue; + } newStorage->m_vector[i].setWithoutWriteBarrier(JSValue(JSValue::EncodeAsDouble, value)); newStorage->m_numValuesInVector++; } - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), transition); - setStructureAndButterfly(vm, newStructure, newStorage->butterfly()); + StructureID oldStructureID = this->structureID(); + Structure* oldStructure = vm.getStructure(oldStructureID); + Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, transition); + nukeStructureAndSetButterfly(vm, oldStructureID, newStorage->butterfly()); + setStructure(vm, newStructure); return newStorage; } -ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm, NonPropertyTransition transition) -{ - return convertDoubleToArrayStorage(vm, transition, m_butterfly->vectorLength()); -} - ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm) { - return convertDoubleToArrayStorage(vm, structure()->suggestedArrayStorageTransition()); + return convertDoubleToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition()); } -ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm, NonPropertyTransition transition, unsigned neededLength) +ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm, NonPropertyTransition transition) { DeferGC deferGC(vm.heap); - ASSERT(hasContiguous(structure()->indexingType())); - - ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, neededLength); - for (unsigned i = m_butterfly->publicLength(); i--;) { - JSValue v = m_butterfly->contiguous()[i].get(); - if (!v) - continue; + ASSERT(hasContiguous(indexingType())); + + unsigned vectorLength = m_butterfly.get()->vectorLength(); + ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, vectorLength); + Butterfly* butterfly = m_butterfly.get(); + for (unsigned i = 0; i < vectorLength; i++) { + JSValue v = butterfly->contiguous()[i].get(); newStorage->m_vector[i].setWithoutWriteBarrier(v); - newStorage->m_numValuesInVector++; + if (v) + newStorage->m_numValuesInVector++; + } + + Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), transition); + + // This has a crazy race with the garbage collector. When changing the butterfly and structure, + // the mutator always sets the structure last. The collector will always read the structure + // first. We probably have to follow that convention here as well. This means that the collector + // will sometimes see the new butterfly (the one with ArrayStorage) with the only structure (the + // one that says that the butterfly is Contiguous). When scanning Contiguous, the collector will + // mark word at addresses greater than or equal to the butterfly pointer, up to the publicLength + // in the butterfly. But an ArrayStorage has some non-pointer header data at low positive + // offsets from the butterfly - so when this race happens, the collector will surely crash + // because it will fail to decode two consecutive int32s as if it was a JSValue. + // + // Fortunately, we have the JSCell lock for this purpose! + + if (vm.heap.mutatorShouldBeFenced()) { + auto locker = holdLock(*this); + setStructureIDDirectly(nuke(structureID())); + WTF::storeStoreFence(); + m_butterfly.set(vm, this, newStorage->butterfly()); + WTF::storeStoreFence(); + setStructure(vm, newStructure); + } else { + m_butterfly.set(vm, this, newStorage->butterfly()); + setStructure(vm, newStructure); } - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), transition); - setStructureAndButterfly(vm, newStructure, newStorage->butterfly()); return newStorage; } -ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm, NonPropertyTransition transition) -{ - return convertContiguousToArrayStorage(vm, transition, m_butterfly->vectorLength()); -} - ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm) { - return convertContiguousToArrayStorage(vm, structure()->suggestedArrayStorageTransition()); + return convertContiguousToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition()); } void JSObject::convertUndecidedForValue(VM& vm, JSValue value) { - if (value.isInt32()) { + IndexingType type = indexingTypeForValue(value); + if (type == Int32Shape) { convertUndecidedToInt32(vm); return; } - if (value.isDouble() && value.asNumber() == value.asNumber()) { + if (type == DoubleShape) { convertUndecidedToDouble(vm); return; } - + + ASSERT(type == ContiguousShape); convertUndecidedToContiguous(vm); } @@ -941,7 +1397,7 @@ void JSObject::convertInt32ForValue(VM& vm, JSValue value) { ASSERT(!value.isInt32()); - if (value.isDouble()) { + if (value.isDouble() && !std::isnan(value.asDouble())) { convertInt32ToDouble(vm); return; } @@ -951,8 +1407,8 @@ void JSObject::convertInt32ForValue(VM& vm, JSValue value) void JSObject::setIndexQuicklyToUndecided(VM& vm, unsigned index, JSValue value) { - ASSERT(index < m_butterfly->publicLength()); - ASSERT(index < m_butterfly->vectorLength()); + ASSERT(index < m_butterfly.get()->publicLength()); + ASSERT(index < m_butterfly.get()->vectorLength()); convertUndecidedForValue(vm, value); setIndexQuickly(vm, index, value); } @@ -973,11 +1429,14 @@ void JSObject::convertDoubleToContiguousWhilePerformingSetIndex(VM& vm, unsigned ContiguousJSValues JSObject::ensureInt32Slow(VM& vm) { - ASSERT(inherits(info())); + ASSERT(inherits(vm, info())); - switch (structure()->indexingType()) { + if (structure(vm)->hijacksIndexingHeader()) + return ContiguousJSValues(); + + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: - if (UNLIKELY(indexingShouldBeSparse() || structure()->needsSlowPutIndexing())) + if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing())) return ContiguousJSValues(); return createInitialInt32(vm, 0); @@ -997,11 +1456,14 @@ ContiguousJSValues JSObject::ensureInt32Slow(VM& vm) ContiguousDoubles JSObject::ensureDoubleSlow(VM& vm) { - ASSERT(inherits(info())); + ASSERT(inherits(vm, info())); + + if (structure(vm)->hijacksIndexingHeader()) + return ContiguousDoubles(); - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: - if (UNLIKELY(indexingShouldBeSparse() || structure()->needsSlowPutIndexing())) + if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing())) return ContiguousDoubles(); return createInitialDouble(vm, 0); @@ -1021,13 +1483,16 @@ ContiguousDoubles JSObject::ensureDoubleSlow(VM& vm) } } -ContiguousJSValues JSObject::ensureContiguousSlow(VM& vm, DoubleToContiguousMode mode) +ContiguousJSValues JSObject::ensureContiguousSlow(VM& vm) { - ASSERT(inherits(info())); + ASSERT(inherits(vm, info())); + + if (structure(vm)->hijacksIndexingHeader()) + return ContiguousJSValues(); - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: - if (UNLIKELY(indexingShouldBeSparse() || structure()->needsSlowPutIndexing())) + if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing())) return ContiguousJSValues(); return createInitialContiguous(vm, 0); @@ -1038,8 +1503,6 @@ ContiguousJSValues JSObject::ensureContiguousSlow(VM& vm, DoubleToContiguousMode return convertInt32ToContiguous(vm); case ALL_DOUBLE_INDEXING_TYPES: - if (mode == RageConvertDoubleToValue) - return rageConvertDoubleToContiguous(vm); return convertDoubleToContiguous(vm); case ALL_ARRAY_STORAGE_INDEXING_TYPES: @@ -1051,21 +1514,14 @@ ContiguousJSValues JSObject::ensureContiguousSlow(VM& vm, DoubleToContiguousMode } } -ContiguousJSValues JSObject::ensureContiguousSlow(VM& vm) +ArrayStorage* JSObject::ensureArrayStorageSlow(VM& vm) { - return ensureContiguousSlow(vm, EncodeValueAsDouble); -} + ASSERT(inherits(vm, info())); -ContiguousJSValues JSObject::rageEnsureContiguousSlow(VM& vm) -{ - return ensureContiguousSlow(vm, RageConvertDoubleToValue); -} - -ArrayStorage* JSObject::ensureArrayStorageSlow(VM& vm) -{ - ASSERT(inherits(info())); + if (structure(vm)->hijacksIndexingHeader()) + return nullptr; - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: if (UNLIKELY(indexingShouldBeSparse())) return ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm); @@ -1073,22 +1529,22 @@ ArrayStorage* JSObject::ensureArrayStorageSlow(VM& vm) case ALL_UNDECIDED_INDEXING_TYPES: ASSERT(!indexingShouldBeSparse()); - ASSERT(!structure()->needsSlowPutIndexing()); + ASSERT(!structure(vm)->needsSlowPutIndexing()); return convertUndecidedToArrayStorage(vm); case ALL_INT32_INDEXING_TYPES: ASSERT(!indexingShouldBeSparse()); - ASSERT(!structure()->needsSlowPutIndexing()); + ASSERT(!structure(vm)->needsSlowPutIndexing()); return convertInt32ToArrayStorage(vm); case ALL_DOUBLE_INDEXING_TYPES: ASSERT(!indexingShouldBeSparse()); - ASSERT(!structure()->needsSlowPutIndexing()); + ASSERT(!structure(vm)->needsSlowPutIndexing()); return convertDoubleToArrayStorage(vm); case ALL_CONTIGUOUS_INDEXING_TYPES: ASSERT(!indexingShouldBeSparse()); - ASSERT(!structure()->needsSlowPutIndexing()); + ASSERT(!structure(vm)->needsSlowPutIndexing()); return convertContiguousToArrayStorage(vm); default: @@ -1099,7 +1555,7 @@ ArrayStorage* JSObject::ensureArrayStorageSlow(VM& vm) ArrayStorage* JSObject::ensureArrayStorageExistsAndEnterDictionaryIndexingMode(VM& vm) { - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: { createArrayStorage(vm, 0, 0); SparseArrayValueMap* map = allocateSparseIndexMap(vm); @@ -1120,7 +1576,7 @@ ArrayStorage* JSObject::ensureArrayStorageExistsAndEnterDictionaryIndexingMode(V return enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, convertContiguousToArrayStorage(vm)); case ALL_ARRAY_STORAGE_INDEXING_TYPES: - return enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, m_butterfly->arrayStorage()); + return enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, m_butterfly.get()->arrayStorage()); default: CRASH(); @@ -1130,26 +1586,26 @@ ArrayStorage* JSObject::ensureArrayStorageExistsAndEnterDictionaryIndexingMode(V void JSObject::switchToSlowPutArrayStorage(VM& vm) { - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_UNDECIDED_INDEXING_TYPES: - convertUndecidedToArrayStorage(vm, AllocateSlowPutArrayStorage); + convertUndecidedToArrayStorage(vm, NonPropertyTransition::AllocateSlowPutArrayStorage); break; case ALL_INT32_INDEXING_TYPES: - convertInt32ToArrayStorage(vm, AllocateSlowPutArrayStorage); + convertInt32ToArrayStorage(vm, NonPropertyTransition::AllocateSlowPutArrayStorage); break; case ALL_DOUBLE_INDEXING_TYPES: - convertDoubleToArrayStorage(vm, AllocateSlowPutArrayStorage); + convertDoubleToArrayStorage(vm, NonPropertyTransition::AllocateSlowPutArrayStorage); break; case ALL_CONTIGUOUS_INDEXING_TYPES: - convertContiguousToArrayStorage(vm, AllocateSlowPutArrayStorage); + convertContiguousToArrayStorage(vm, NonPropertyTransition::AllocateSlowPutArrayStorage); break; case NonArrayWithArrayStorage: case ArrayWithArrayStorage: { - Structure* newStructure = Structure::nonPropertyTransition(vm, structure(), SwitchToSlowPutArrayStorage); + Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), NonPropertyTransition::SwitchToSlowPutArrayStorage); setStructure(vm, newStructure); break; } @@ -1160,13 +1616,13 @@ void JSObject::switchToSlowPutArrayStorage(VM& vm) } } -void JSObject::setPrototype(VM& vm, JSValue prototype) +void JSObject::setPrototypeDirect(VM& vm, JSValue prototype) { ASSERT(prototype); if (prototype.isObject()) vm.prototypeMap.addPrototype(asObject(prototype)); - Structure* newStructure = Structure::changePrototypeTransition(vm, structure(), prototype); + Structure* newStructure = Structure::changePrototypeTransition(vm, structure(vm), prototype); setStructure(vm, newStructure); if (!newStructure->anyObjectInChainMayInterceptIndexedAccesses()) @@ -1177,73 +1633,151 @@ void JSObject::setPrototype(VM& vm, JSValue prototype) return; } - if (!hasIndexedProperties(structure()->indexingType())) + if (!hasIndexedProperties(indexingType())) return; - if (shouldUseSlowPut(structure()->indexingType())) + if (shouldUseSlowPut(indexingType())) return; switchToSlowPutArrayStorage(vm); } -bool JSObject::setPrototypeWithCycleCheck(ExecState* exec, JSValue prototype) +bool JSObject::setPrototypeWithCycleCheck(VM& vm, ExecState* exec, JSValue prototype, bool shouldThrowIfCantSet) { - ASSERT(methodTable()->toThis(this, exec, NotStrictMode) == this); + auto scope = DECLARE_THROW_SCOPE(vm); + ASSERT(methodTable(vm)->toThis(this, exec, NotStrictMode) == this); + + if (this->getPrototypeDirect() == prototype) + return true; + + if (this->structure(vm)->isImmutablePrototypeExoticObject()) + return typeError(exec, scope, shouldThrowIfCantSet, ASCIILiteral("Cannot set prototype of immutable prototype object")); + + bool isExtensible = this->isExtensible(exec); + RETURN_IF_EXCEPTION(scope, false); + + if (!isExtensible) + return typeError(exec, scope, shouldThrowIfCantSet, ASCIILiteral(ReadonlyPropertyWriteError)); + JSValue nextPrototype = prototype; while (nextPrototype && nextPrototype.isObject()) { if (nextPrototype == this) - return false; - nextPrototype = asObject(nextPrototype)->prototype(); - } - setPrototype(exec->vm(), prototype); + return typeError(exec, scope, shouldThrowIfCantSet, ASCIILiteral("cyclic __proto__ value")); + // FIXME: The specification currently says we should check if the [[GetPrototypeOf]] internal method of nextPrototype + // is not the ordinary object internal method. However, we currently restrict this to Proxy objects as it would allow + // for cycles with certain HTML objects (WindowProxy, Location) otherwise. + // https://bugs.webkit.org/show_bug.cgi?id=161534 + if (UNLIKELY(asObject(nextPrototype)->type() == ProxyObjectType)) + break; // We're done. Set the prototype. + nextPrototype = asObject(nextPrototype)->getPrototypeDirect(); + } + setPrototypeDirect(vm, prototype); return true; } -bool JSObject::allowsAccessFrom(ExecState* exec) +bool JSObject::setPrototype(JSObject* object, ExecState* exec, JSValue prototype, bool shouldThrowIfCantSet) +{ + return object->setPrototypeWithCycleCheck(exec->vm(), exec, prototype, shouldThrowIfCantSet); +} + +JSValue JSObject::getPrototype(JSObject* object, ExecState*) +{ + return object->getPrototypeDirect(); +} + +bool JSObject::setPrototype(VM& vm, ExecState* exec, JSValue prototype, bool shouldThrowIfCantSet) +{ + return methodTable(vm)->setPrototype(this, exec, prototype, shouldThrowIfCantSet); +} + +bool JSObject::putGetter(ExecState* exec, PropertyName propertyName, JSValue getter, unsigned attributes) +{ + PropertyDescriptor descriptor; + descriptor.setGetter(getter); + + ASSERT(attributes & Accessor); + if (!(attributes & ReadOnly)) + descriptor.setConfigurable(true); + if (!(attributes & DontEnum)) + descriptor.setEnumerable(true); + + return defineOwnProperty(this, exec, propertyName, descriptor, false); +} + +bool JSObject::putSetter(ExecState* exec, PropertyName propertyName, JSValue setter, unsigned attributes) { - JSGlobalObject* globalObject = this->globalObject(); - return globalObject->globalObjectMethodTable()->allowsAccessFrom(globalObject, exec); + PropertyDescriptor descriptor; + descriptor.setSetter(setter); + + ASSERT(attributes & Accessor); + if (!(attributes & ReadOnly)) + descriptor.setConfigurable(true); + if (!(attributes & DontEnum)) + descriptor.setEnumerable(true); + + return defineOwnProperty(this, exec, propertyName, descriptor, false); } -void JSObject::putDirectAccessor(ExecState* exec, PropertyName propertyName, JSValue value, unsigned attributes) +bool JSObject::putDirectAccessor(ExecState* exec, PropertyName propertyName, JSValue value, unsigned attributes) { ASSERT(value.isGetterSetter() && (attributes & Accessor)); - unsigned index = propertyName.asIndex(); - if (index != PropertyName::NotAnIndex) { - putDirectIndex(exec, index, value, attributes, PutDirectIndexLikePutDirect); - return; - } + if (std::optional index = parseIndex(propertyName)) + return putDirectIndex(exec, index.value(), value, attributes, PutDirectIndexLikePutDirect); - putDirectNonIndexAccessor(exec->vm(), propertyName, value, attributes); + return putDirectNonIndexAccessor(exec->vm(), propertyName, value, attributes); } -void JSObject::putDirectNonIndexAccessor(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes) +bool JSObject::putDirectCustomAccessor(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes) { + ASSERT(!parseIndex(propertyName)); + PutPropertySlot slot(this); - putDirectInternal(vm, propertyName, value, attributes, slot, getCallableObject(value)); + bool result = putDirectInternal(vm, propertyName, value, attributes, slot); - // putDirect will change our Structure if we add a new property. For - // getters and setters, though, we also need to change our Structure - // if we override an existing non-getter or non-setter. - if (slot.type() != PutPropertySlot::NewProperty) - setStructure(vm, Structure::attributeChangeTransition(vm, structure(), propertyName, attributes)); + ASSERT(slot.type() == PutPropertySlot::NewProperty); + Structure* structure = this->structure(vm); if (attributes & ReadOnly) - structure()->setContainsReadOnlyProperties(); + structure->setContainsReadOnlyProperties(); + structure->setHasCustomGetterSetterPropertiesWithProtoCheck(propertyName == vm.propertyNames->underscoreProto); + return result; +} - structure()->setHasGetterSetterProperties(propertyName == vm.propertyNames->underscoreProto); +bool JSObject::putDirectNonIndexAccessor(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes) +{ + PutPropertySlot slot(this); + bool result = putDirectInternal(vm, propertyName, value, attributes, slot); + + Structure* structure = this->structure(vm); + if (attributes & ReadOnly) + structure->setContainsReadOnlyProperties(); + + structure->setHasGetterSetterPropertiesWithProtoCheck(propertyName == vm.propertyNames->underscoreProto); + return result; } +// HasProperty(O, P) from Section 7.3.10 of the spec. +// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-hasproperty bool JSObject::hasProperty(ExecState* exec, PropertyName propertyName) const { - PropertySlot slot(this); - return const_cast(this)->getPropertySlot(exec, propertyName, slot); + return hasPropertyGeneric(exec, propertyName, PropertySlot::InternalMethodType::HasProperty); } bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const { - PropertySlot slot(this); + return hasPropertyGeneric(exec, propertyName, PropertySlot::InternalMethodType::HasProperty); +} + +bool JSObject::hasPropertyGeneric(ExecState* exec, PropertyName propertyName, PropertySlot::InternalMethodType internalMethodType) const +{ + PropertySlot slot(this, internalMethodType); + return const_cast(this)->getPropertySlot(exec, propertyName, slot); +} + +bool JSObject::hasPropertyGeneric(ExecState* exec, unsigned propertyName, PropertySlot::InternalMethodType internalMethodType) const +{ + PropertySlot slot(this, internalMethodType); return const_cast(this)->getPropertySlot(exec, propertyName, slot); } @@ -1251,50 +1785,54 @@ bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) { JSObject* thisObject = jsCast(cell); + VM& vm = exec->vm(); - unsigned i = propertyName.asIndex(); - if (i != PropertyName::NotAnIndex) - return thisObject->methodTable()->deletePropertyByIndex(thisObject, exec, i); - - if (!thisObject->staticFunctionsReified()) - thisObject->reifyStaticFunctionsForDelete(exec); + if (std::optional index = parseIndex(propertyName)) + return thisObject->methodTable(vm)->deletePropertyByIndex(thisObject, exec, index.value()); unsigned attributes; - JSCell* specificValue; - if (isValidOffset(thisObject->structure()->get(exec->vm(), propertyName, attributes, specificValue))) { - if (attributes & DontDelete && !exec->vm().isInDefineOwnProperty()) - return false; - thisObject->removeDirect(exec->vm(), propertyName); - return true; + + if (!thisObject->staticPropertiesReified()) { + if (auto* entry = thisObject->findPropertyHashEntry(vm, propertyName)) { + // If the static table contains a non-configurable (DontDelete) property then we can return early; + // if there is a property in the storage array it too must be non-configurable (the language does + // not allow repacement of a non-configurable property with a configurable one). + if (entry->attributes() & DontDelete && vm.deletePropertyMode() != VM::DeletePropertyMode::IgnoreConfigurable) { + ASSERT(!isValidOffset(thisObject->structure(vm)->get(vm, propertyName, attributes)) || attributes & DontDelete); + return false; + } + thisObject->reifyAllStaticProperties(exec); + } } - // Look in the static hashtable of properties - const HashEntry* entry = thisObject->findPropertyHashEntry(exec, propertyName); - if (entry) { - if (entry->attributes() & DontDelete && !exec->vm().isInDefineOwnProperty()) - return false; // this builtin property can't be deleted + Structure* structure = thisObject->structure(vm); + + bool propertyIsPresent = isValidOffset(structure->get(vm, propertyName, attributes)); + if (propertyIsPresent) { + if (attributes & DontDelete && vm.deletePropertyMode() != VM::DeletePropertyMode::IgnoreConfigurable) + return false; - PutPropertySlot slot(thisObject); - putEntry(exec, entry, thisObject, propertyName, jsUndefined(), slot); + PropertyOffset offset; + if (structure->isUncacheableDictionary()) + offset = structure->removePropertyWithoutTransition(vm, propertyName, [] (const ConcurrentJSLocker&, PropertyOffset) { }); + else + thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset)); + + if (offset != invalidOffset) + thisObject->putDirectUndefined(offset); } return true; } -bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName) const -{ - PropertySlot slot(this); - return const_cast(this)->methodTable()->getOwnPropertySlot(const_cast(this), exec, propertyName, slot); -} - bool JSObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned i) { JSObject* thisObject = jsCast(cell); if (i > MAX_ARRAY_INDEX) - return thisObject->methodTable()->deleteProperty(thisObject, exec, Identifier::from(exec, i)); + return thisObject->methodTable(exec->vm())->deleteProperty(thisObject, exec, Identifier::from(exec, i)); - switch (thisObject->structure()->indexingType()) { + switch (thisObject->indexingType()) { case ALL_BLANK_INDEXING_TYPES: case ALL_UNDECIDED_INDEXING_TYPES: return true; @@ -1312,12 +1850,12 @@ bool JSObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned i) Butterfly* butterfly = thisObject->butterfly(); if (i >= butterfly->vectorLength()) return true; - butterfly->contiguousDouble()[i] = QNaN; + butterfly->contiguousDouble()[i] = PNaN; return true; } case ALL_ARRAY_STORAGE_INDEXING_TYPES: { - ArrayStorage* storage = thisObject->m_butterfly->arrayStorage(); + ArrayStorage* storage = thisObject->m_butterfly.get()->arrayStorage(); if (i < storage->vectorLength()) { WriteBarrier& valueSlot = storage->m_vector[i]; @@ -1343,129 +1881,224 @@ bool JSObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned i) } } -static ALWAYS_INLINE JSValue callDefaultValueFunction(ExecState* exec, const JSObject* object, PropertyName propertyName) +enum class TypeHintMode { TakesHint, DoesNotTakeHint }; + +template +static ALWAYS_INLINE JSValue callToPrimitiveFunction(ExecState* exec, const JSObject* object, PropertyName propertyName, PreferredPrimitiveType hint) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue function = object->get(exec, propertyName); + RETURN_IF_EXCEPTION(scope, scope.exception()); + if (function.isUndefined() && mode == TypeHintMode::TakesHint) + return JSValue(); CallData callData; CallType callType = getCallData(function, callData); - if (callType == CallTypeNone) - return exec->exception(); - - // Prevent "toString" and "valueOf" from observing execution if an exception - // is pending. - if (exec->hadException()) - return exec->exception(); + if (callType == CallType::None) + return scope.exception(); + + MarkedArgumentBuffer callArgs; + if (mode == TypeHintMode::TakesHint) { + JSString* hintString = nullptr; + switch (hint) { + case NoPreference: + hintString = vm.smallStrings.defaultString(); + break; + case PreferNumber: + hintString = vm.smallStrings.numberString(); + break; + case PreferString: + hintString = vm.smallStrings.stringString(); + break; + } + callArgs.append(hintString); + } - JSValue result = call(exec, function, callType, callData, const_cast(object), exec->emptyList()); + JSValue result = call(exec, function, callType, callData, const_cast(object), callArgs); + RETURN_IF_EXCEPTION(scope, scope.exception()); ASSERT(!result.isGetterSetter()); - if (exec->hadException()) - return exec->exception(); if (result.isObject()) - return JSValue(); + return mode == TypeHintMode::DoesNotTakeHint ? JSValue() : throwTypeError(exec, scope, ASCIILiteral("Symbol.toPrimitive returned an object")); return result; } -bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) const +// ECMA 7.1.1 +JSValue JSObject::ordinaryToPrimitive(ExecState* exec, PreferredPrimitiveType hint) const { - result = methodTable()->defaultValue(this, exec, PreferNumber); - number = result.toNumber(exec); - return !result.isString(); -} + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); -// ECMA 8.6.2.6 -JSValue JSObject::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint) -{ - // Must call toString first for Date objects. - if ((hint == PreferString) || (hint != PreferNumber && object->prototype() == exec->lexicalGlobalObject()->datePrototype())) { - JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().toString); + // Make sure that whatever default value methods there are on object's prototype chain are + // being watched. + this->structure()->startWatchingInternalPropertiesIfNecessaryForEntireChain(vm); + + JSValue value; + if (hint == PreferString) { + value = callToPrimitiveFunction(exec, this, exec->propertyNames().toString, hint); + ASSERT(!scope.exception() || scope.exception() == value.asCell()); if (value) return value; - value = callDefaultValueFunction(exec, object, exec->propertyNames().valueOf); + value = callToPrimitiveFunction(exec, this, exec->propertyNames().valueOf, hint); + ASSERT(!scope.exception() || scope.exception() == value.asCell()); if (value) return value; } else { - JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().valueOf); + value = callToPrimitiveFunction(exec, this, exec->propertyNames().valueOf, hint); + ASSERT(!scope.exception() || scope.exception() == value.asCell()); if (value) return value; - value = callDefaultValueFunction(exec, object, exec->propertyNames().toString); + value = callToPrimitiveFunction(exec, this, exec->propertyNames().toString, hint); + ASSERT(!scope.exception() || scope.exception() == value.asCell()); if (value) return value; } - ASSERT(!exec->hadException()); + ASSERT(!scope.exception()); + + return throwTypeError(exec, scope, ASCIILiteral("No default value")); +} + +JSValue JSObject::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint) +{ + return object->ordinaryToPrimitive(exec, hint); +} + +JSValue JSObject::toPrimitive(ExecState* exec, PreferredPrimitiveType preferredType) const +{ + JSValue value = callToPrimitiveFunction(exec, this, exec->propertyNames().toPrimitiveSymbol, preferredType); + if (value) + return value; + + return this->methodTable(exec->vm())->defaultValue(this, exec, preferredType); +} + +bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) const +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + result = toPrimitive(exec, PreferNumber); + RETURN_IF_EXCEPTION(scope, false); + scope.release(); + number = result.toNumber(exec); + return !result.isString(); +} - return exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("No default value"))); +bool JSObject::getOwnStaticPropertySlot(VM& vm, PropertyName propertyName, PropertySlot& slot) +{ + for (auto* info = classInfo(vm); info; info = info->parentClass) { + if (auto* table = info->staticPropHashTable) { + if (getStaticPropertySlotFromTable(vm, *table, this, propertyName, slot)) + return true; + } + } + return false; } -const HashEntry* JSObject::findPropertyHashEntry(ExecState* exec, PropertyName propertyName) const +const HashTableValue* JSObject::findPropertyHashEntry(VM& vm, PropertyName propertyName) const { - for (const ClassInfo* info = classInfo(); info; info = info->parentClass) { - if (const HashTable* propHashTable = info->propHashTable(exec)) { - if (const HashEntry* entry = propHashTable->entry(exec, propertyName)) + for (const ClassInfo* info = classInfo(vm); info; info = info->parentClass) { + if (const HashTable* propHashTable = info->staticPropHashTable) { + if (const HashTableValue* entry = propHashTable->entry(propertyName)) return entry; } } return 0; } -bool JSObject::hasInstance(ExecState* exec, JSValue value) +bool JSObject::hasInstance(ExecState* exec, JSValue value, JSValue hasInstanceValue) { - TypeInfo info = structure()->typeInfo(); + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + + if (!hasInstanceValue.isUndefinedOrNull() && hasInstanceValue != exec->lexicalGlobalObject()->functionProtoHasInstanceSymbolFunction()) { + CallData callData; + CallType callType = JSC::getCallData(hasInstanceValue, callData); + if (callType == CallType::None) { + throwException(exec, scope, createInvalidInstanceofParameterErrorhasInstanceValueNotFunction(exec, this)); + return false; + } + + MarkedArgumentBuffer args; + args.append(value); + JSValue result = call(exec, hasInstanceValue, callType, callData, this, args); + return result.toBoolean(exec); + } + + TypeInfo info = structure(vm)->typeInfo(); if (info.implementsDefaultHasInstance()) return defaultHasInstance(exec, value, get(exec, exec->propertyNames().prototype)); if (info.implementsHasInstance()) - return methodTable()->customHasInstance(this, exec, value); - exec->vm().throwException(exec, createInvalidParameterError(exec, "instanceof" , this)); + return methodTable(vm)->customHasInstance(this, exec, value); + throwException(exec, scope, createInvalidInstanceofParameterErrorNotFunction(exec, this)); return false; } +bool JSObject::hasInstance(ExecState* exec, JSValue value) +{ + JSValue hasInstanceValue = get(exec, exec->propertyNames().hasInstanceSymbol); + + return hasInstance(exec, value, hasInstanceValue); +} + bool JSObject::defaultHasInstance(ExecState* exec, JSValue value, JSValue proto) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (!value.isObject()) return false; if (!proto.isObject()) { - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("instanceof called on an object with an invalid prototype property."))); + throwTypeError(exec, scope, ASCIILiteral("instanceof called on an object with an invalid prototype property.")); return false; } JSObject* object = asObject(value); - while ((object = object->prototype().getObject())) { + while (true) { + JSValue objectValue = object->getPrototype(vm, exec); + RETURN_IF_EXCEPTION(scope, false); + if (!objectValue.isObject()) + return false; + object = asObject(objectValue); if (proto == object) return true; } - return false; + ASSERT_NOT_REACHED(); } -bool JSObject::getPropertySpecificValue(ExecState* exec, PropertyName propertyName, JSCell*& specificValue) const +EncodedJSValue JSC_HOST_CALL objectPrivateFuncInstanceOf(ExecState* exec) { - unsigned attributes; - if (isValidOffset(structure()->get(exec->vm(), propertyName, attributes, specificValue))) - return true; + JSValue value = exec->uncheckedArgument(0); + JSValue proto = exec->uncheckedArgument(1); - // This could be a function within the static table? - should probably - // also look in the hash? This currently should not be a problem, since - // we've currently always call 'get' first, which should have populated - // the normal storage. - return false; + return JSValue::encode(jsBoolean(JSObject::defaultHasInstance(exec, value, proto))); } void JSObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { - propertyNames.setBaseObject(object); - object->methodTable()->getOwnPropertyNames(object, exec, propertyNames, mode); + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + object->methodTable(vm)->getOwnPropertyNames(object, exec, propertyNames, mode); + RETURN_IF_EXCEPTION(scope, void()); - if (object->prototype().isNull()) + JSValue nextProto = object->getPrototype(vm, exec); + RETURN_IF_EXCEPTION(scope, void()); + if (nextProto.isNull()) return; - JSObject* prototype = asObject(object->prototype()); + JSObject* prototype = asObject(nextProto); while(1) { - if (prototype->structure()->typeInfo().overridesGetPropertyNames()) { - prototype->methodTable()->getPropertyNames(prototype, exec, propertyNames, mode); + if (prototype->structure(vm)->typeInfo().overridesGetPropertyNames()) { + prototype->methodTable(vm)->getPropertyNames(prototype, exec, propertyNames, mode); break; } - prototype->methodTable()->getOwnPropertyNames(prototype, exec, propertyNames, mode); - JSValue nextProto = prototype->prototype(); + prototype->methodTable(vm)->getOwnPropertyNames(prototype, exec, propertyNames, mode); + RETURN_IF_EXCEPTION(scope, void()); + nextProto = prototype->getPrototype(vm, exec); + RETURN_IF_EXCEPTION(scope, void()); if (nextProto.isNull()) break; prototype = asObject(nextProto); @@ -1474,96 +2107,107 @@ void JSObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameA void JSObject::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { - // Add numeric properties first. That appears to be the accepted convention. - // FIXME: Filling PropertyNameArray with an identifier for every integer - // is incredibly inefficient for large arrays. We need a different approach, - // which almost certainly means a different structure for PropertyNameArray. - switch (object->structure()->indexingType()) { - case ALL_BLANK_INDEXING_TYPES: - case ALL_UNDECIDED_INDEXING_TYPES: - break; - - case ALL_INT32_INDEXING_TYPES: - case ALL_CONTIGUOUS_INDEXING_TYPES: { - Butterfly* butterfly = object->butterfly(); - unsigned usedLength = butterfly->publicLength(); - for (unsigned i = 0; i < usedLength; ++i) { - if (!butterfly->contiguous()[i]) - continue; - propertyNames.add(Identifier::from(exec, i)); - } - break; + if (!mode.includeJSObjectProperties()) { + // We still have to get non-indexed properties from any subclasses of JSObject that have them. + object->methodTable(exec->vm())->getOwnNonIndexPropertyNames(object, exec, propertyNames, mode); + return; } - - case ALL_DOUBLE_INDEXING_TYPES: { - Butterfly* butterfly = object->butterfly(); - unsigned usedLength = butterfly->publicLength(); - for (unsigned i = 0; i < usedLength; ++i) { - double value = butterfly->contiguousDouble()[i]; - if (value != value) - continue; - propertyNames.add(Identifier::from(exec, i)); + + if (propertyNames.includeStringProperties()) { + // Add numeric properties first. That appears to be the accepted convention. + // FIXME: Filling PropertyNameArray with an identifier for every integer + // is incredibly inefficient for large arrays. We need a different approach, + // which almost certainly means a different structure for PropertyNameArray. + switch (object->indexingType()) { + case ALL_BLANK_INDEXING_TYPES: + case ALL_UNDECIDED_INDEXING_TYPES: + break; + + case ALL_INT32_INDEXING_TYPES: + case ALL_CONTIGUOUS_INDEXING_TYPES: { + Butterfly* butterfly = object->butterfly(); + unsigned usedLength = butterfly->publicLength(); + for (unsigned i = 0; i < usedLength; ++i) { + if (!butterfly->contiguous()[i]) + continue; + propertyNames.add(i); + } + break; } - break; - } - - case ALL_ARRAY_STORAGE_INDEXING_TYPES: { - ArrayStorage* storage = object->m_butterfly->arrayStorage(); - - unsigned usedVectorLength = std::min(storage->length(), storage->vectorLength()); - for (unsigned i = 0; i < usedVectorLength; ++i) { - if (storage->m_vector[i]) - propertyNames.add(Identifier::from(exec, i)); + + case ALL_DOUBLE_INDEXING_TYPES: { + Butterfly* butterfly = object->butterfly(); + unsigned usedLength = butterfly->publicLength(); + for (unsigned i = 0; i < usedLength; ++i) { + double value = butterfly->contiguousDouble()[i]; + if (value != value) + continue; + propertyNames.add(i); + } + break; } - - if (SparseArrayValueMap* map = storage->m_sparseMap.get()) { - Vector keys; - keys.reserveInitialCapacity(map->size()); - SparseArrayValueMap::const_iterator end = map->end(); - for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it) { - if (mode == IncludeDontEnumProperties || !(it->value.attributes & DontEnum)) - keys.uncheckedAppend(static_cast(it->key)); + case ALL_ARRAY_STORAGE_INDEXING_TYPES: { + ArrayStorage* storage = object->m_butterfly.get()->arrayStorage(); + + unsigned usedVectorLength = std::min(storage->length(), storage->vectorLength()); + for (unsigned i = 0; i < usedVectorLength; ++i) { + if (storage->m_vector[i]) + propertyNames.add(i); } - std::sort(keys.begin(), keys.end()); - for (unsigned i = 0; i < keys.size(); ++i) - propertyNames.add(Identifier::from(exec, keys[i])); + if (SparseArrayValueMap* map = storage->m_sparseMap.get()) { + Vector keys; + keys.reserveInitialCapacity(map->size()); + + SparseArrayValueMap::const_iterator end = map->end(); + for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it) { + if (mode.includeDontEnumProperties() || !(it->value.attributes & DontEnum)) + keys.uncheckedAppend(static_cast(it->key)); + } + + std::sort(keys.begin(), keys.end()); + for (unsigned i = 0; i < keys.size(); ++i) + propertyNames.add(keys[i]); + } + break; + } + + default: + RELEASE_ASSERT_NOT_REACHED(); } - break; - } - - default: - RELEASE_ASSERT_NOT_REACHED(); } - - object->methodTable()->getOwnNonIndexPropertyNames(object, exec, propertyNames, mode); + + object->methodTable(exec->vm())->getOwnNonIndexPropertyNames(object, exec, propertyNames, mode); } void JSObject::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { - getClassPropertyNames(exec, object->classInfo(), propertyNames, mode, object->staticFunctionsReified()); - - bool canCachePropertiesFromStructure = !propertyNames.size(); - object->structure()->getPropertyNamesFromStructure(exec->vm(), propertyNames, mode); + VM& vm = exec->vm(); + if (!object->staticPropertiesReified()) + getClassPropertyNames(exec, object->classInfo(vm), propertyNames, mode); - if (canCachePropertiesFromStructure) - propertyNames.setNumCacheableSlotsForObject(object, propertyNames.size()); + if (!mode.includeJSObjectProperties()) + return; + + object->structure(vm)->getPropertyNamesFromStructure(vm, propertyNames, mode); } double JSObject::toNumber(ExecState* exec) const { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); JSValue primitive = toPrimitive(exec, PreferNumber); - if (exec->hadException()) // should be picked up soon in Nodes.cpp - return 0.0; + RETURN_IF_EXCEPTION(scope, 0.0); // should be picked up soon in Nodes.cpp return primitive.toNumber(exec); } JSString* JSObject::toString(ExecState* exec) const { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); JSValue primitive = toPrimitive(exec, PreferString); - if (exec->hadException()) - return jsEmptyString(exec); + RETURN_IF_EXCEPTION(scope, jsEmptyString(exec)); return primitive.toString(exec); } @@ -1576,98 +2220,100 @@ void JSObject::seal(VM& vm) { if (isSealed(vm)) return; - preventExtensions(vm); - setStructure(vm, Structure::sealTransition(vm, structure())); + enterDictionaryIndexingMode(vm); + setStructure(vm, Structure::sealTransition(vm, structure(vm))); } void JSObject::freeze(VM& vm) { if (isFrozen(vm)) return; - preventExtensions(vm); - setStructure(vm, Structure::freezeTransition(vm, structure())); + enterDictionaryIndexingMode(vm); + setStructure(vm, Structure::freezeTransition(vm, structure(vm))); } -void JSObject::preventExtensions(VM& vm) +bool JSObject::preventExtensions(JSObject* object, ExecState* exec) { - enterDictionaryIndexingMode(vm); - if (isExtensible()) - setStructure(vm, Structure::preventExtensionsTransition(vm, structure())); + if (!object->isStructureExtensible()) { + // We've already set the internal [[PreventExtensions]] field to false. + // We don't call the methodTable isExtensible here because it's not defined + // that way in the specification. We are just doing an optimization here. + return true; + } + + VM& vm = exec->vm(); + object->enterDictionaryIndexingMode(vm); + object->setStructure(vm, Structure::preventExtensionsTransition(vm, object->structure(vm))); + return true; +} + +bool JSObject::isExtensible(JSObject* obj, ExecState*) +{ + return obj->isExtensibleImpl(); } -// This presently will flatten to an uncachable dictionary; this is suitable -// for use in delete, we may want to do something different elsewhere. -void JSObject::reifyStaticFunctionsForDelete(ExecState* exec) +bool JSObject::isExtensible(ExecState* exec) +{ + VM& vm = exec->vm(); + return methodTable(vm)->isExtensible(this, exec); +} + +void JSObject::reifyAllStaticProperties(ExecState* exec) { - ASSERT(!staticFunctionsReified()); + ASSERT(!staticPropertiesReified()); VM& vm = exec->vm(); // If this object's ClassInfo has no static properties, then nothing to reify! // We can safely set the flag to avoid the expensive check again in the future. - if (!classInfo()->hasStaticProperties()) { - structure()->setStaticFunctionsReified(); + if (!TypeInfo::hasStaticPropertyTable(inlineTypeFlags())) { + structure(vm)->setStaticPropertiesReified(true); return; } - if (!structure()->isUncacheableDictionary()) - setStructure(vm, Structure::toUncacheableDictionaryTransition(vm, structure())); + if (!structure(vm)->isDictionary()) + setStructure(vm, Structure::toCacheableDictionaryTransition(vm, structure(vm))); - for (const ClassInfo* info = classInfo(); info; info = info->parentClass) { - const HashTable* hashTable = info->propHashTable(globalObject()->globalExec()); + for (const ClassInfo* info = classInfo(vm); info; info = info->parentClass) { + const HashTable* hashTable = info->staticPropHashTable; if (!hashTable) continue; - PropertySlot slot(this); - for (HashTable::ConstIterator iter = hashTable->begin(vm); iter != hashTable->end(vm); ++iter) { - if (iter->attributes() & Function) - setUpStaticFunctionSlot(globalObject()->globalExec(), *iter, this, Identifier(&vm, iter->key()), slot); - } - } - - structure()->setStaticFunctionsReified(); -} -bool JSObject::removeDirect(VM& vm, PropertyName propertyName) -{ - if (!isValidOffset(structure()->get(vm, propertyName))) - return false; - - PropertyOffset offset; - if (structure()->isUncacheableDictionary()) { - offset = structure()->removePropertyWithoutTransition(vm, propertyName); - if (offset == invalidOffset) - return false; - putDirectUndefined(offset); - return true; + for (auto& value : *hashTable) { + unsigned attributes; + auto key = Identifier::fromString(&vm, value.m_key); + PropertyOffset offset = getDirectOffset(vm, key, attributes); + if (!isValidOffset(offset)) + reifyStaticProperty(vm, key, value, *this); + } } - setStructure(vm, Structure::removePropertyTransition(vm, structure(), propertyName, offset)); - if (offset == invalidOffset) - return false; - putDirectUndefined(offset); - return true; + structure(vm)->setStaticPropertiesReified(true); } NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, JSValue getterSetter, unsigned attributes, PropertyOffset offset) { - if (structure()->isDictionary()) { + if (structure()->isUncacheableDictionary()) { slot.setGetterSlot(this, attributes, jsCast(getterSetter)); return; } + // This access is cacheable because Structure requires an attributeChangedTransition + // if this property stops being an accessor. slot.setCacheableGetterSlot(this, attributes, jsCast(getterSetter), offset); } -void JSObject::putIndexedDescriptor(ExecState* exec, SparseArrayEntry* entryInMap, const PropertyDescriptor& descriptor, PropertyDescriptor& oldDescriptor) +bool JSObject::putIndexedDescriptor(ExecState* exec, SparseArrayEntry* entryInMap, const PropertyDescriptor& descriptor, PropertyDescriptor& oldDescriptor) { VM& vm = exec->vm(); + auto map = m_butterfly.get()->arrayStorage()->m_sparseMap.get(); if (descriptor.isDataDescriptor()) { if (descriptor.value()) - entryInMap->set(vm, this, descriptor.value()); + entryInMap->set(vm, map, descriptor.value()); else if (oldDescriptor.isAccessorDescriptor()) - entryInMap->set(vm, this, jsUndefined()); + entryInMap->set(vm, map, jsUndefined()); entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor) & ~Accessor; - return; + return true; } if (descriptor.isAccessorDescriptor()) { @@ -1682,45 +2328,49 @@ void JSObject::putIndexedDescriptor(ExecState* exec, SparseArrayEntry* entryInMa else if (oldDescriptor.isAccessorDescriptor()) setter = oldDescriptor.setterObject(); - GetterSetter* accessor = GetterSetter::create(vm); + GetterSetter* accessor = GetterSetter::create(vm, exec->lexicalGlobalObject()); if (getter) - accessor->setGetter(vm, getter); + accessor->setGetter(vm, exec->lexicalGlobalObject(), getter); if (setter) - accessor->setSetter(vm, setter); + accessor->setSetter(vm, exec->lexicalGlobalObject(), setter); - entryInMap->set(vm, this, accessor); + entryInMap->set(vm, map, accessor); entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor) & ~ReadOnly; - return; + return true; } ASSERT(descriptor.isGenericDescriptor()); entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor); + return true; } // Defined in ES5.1 8.12.9 bool JSObject::defineOwnIndexedProperty(ExecState* exec, unsigned index, const PropertyDescriptor& descriptor, bool throwException) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + ASSERT(index <= MAX_ARRAY_INDEX); - + if (!inSparseIndexingMode()) { // Fast case: we're putting a regular property to a regular array // FIXME: this will pessimistically assume that if attributes are missing then they'll default to false // however if the property currently exists missing attributes will override from their current 'true' // state (i.e. defineOwnProperty could be used to set a value without needing to entering 'SparseMode'). - if (!descriptor.attributes()) { + if (!descriptor.attributes() && descriptor.value()) { ASSERT(!descriptor.isAccessorDescriptor()); return putDirectIndex(exec, index, descriptor.value(), 0, throwException ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow); } - ensureArrayStorageExistsAndEnterDictionaryIndexingMode(exec->vm()); + ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm); } if (descriptor.attributes() & (ReadOnly | Accessor)) - notifyPresenceOfIndexedAccessors(exec->vm()); + notifyPresenceOfIndexedAccessors(vm); - SparseArrayValueMap* map = m_butterfly->arrayStorage()->m_sparseMap.get(); + SparseArrayValueMap* map = m_butterfly.get()->arrayStorage()->m_sparseMap.get(); RELEASE_ASSERT(map); - + // 1. Let current be the result of calling the [[GetOwnProperty]] internal method of O with property name P. SparseArrayValueMap::AddResult result = map->add(this, index); SparseArrayEntry* entryInMap = &result.iterator->value; @@ -1729,9 +2379,9 @@ bool JSObject::defineOwnIndexedProperty(ExecState* exec, unsigned index, const P // 3. If current is undefined and extensible is false, then Reject. // 4. If current is undefined and extensible is true, then if (result.isNewEntry) { - if (!isExtensible()) { + if (!isStructureExtensible()) { map->remove(result.iterator); - return reject(exec, throwException, "Attempting to define property on object that is not extensible."); + return typeError(exec, scope, throwException, ASCIILiteral(NonExtensibleObjectPropertyDefineError)); } // 4.a. If IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then create an own data property @@ -1750,8 +2400,9 @@ bool JSObject::defineOwnIndexedProperty(ExecState* exec, unsigned index, const P entryInMap->get(defaults); putIndexedDescriptor(exec, entryInMap, descriptor, defaults); - if (index >= m_butterfly->arrayStorage()->length()) - m_butterfly->arrayStorage()->setLength(index + 1); + Butterfly* butterfly = m_butterfly.get(); + if (index >= butterfly->arrayStorage()->length()) + butterfly->arrayStorage()->setLength(index + 1); return true; } @@ -1766,10 +2417,10 @@ bool JSObject::defineOwnIndexedProperty(ExecState* exec, unsigned index, const P if (!current.configurable()) { // 7.a. Reject, if the [[Configurable]] field of Desc is true. if (descriptor.configurablePresent() && descriptor.configurable()) - return reject(exec, throwException, "Attempting to change configurable attribute of unconfigurable property."); + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeConfigurabilityError)); // 7.b. Reject, if the [[Enumerable]] field of Desc is present and the [[Enumerable]] fields of current and Desc are the Boolean negation of each other. if (descriptor.enumerablePresent() && current.enumerable() != descriptor.enumerable()) - return reject(exec, throwException, "Attempting to change enumerable attribute of unconfigurable property."); + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeEnumerabilityError)); } // 8. If IsGenericDescriptor(Desc) is true, then no further validation is required. @@ -1778,7 +2429,7 @@ bool JSObject::defineOwnIndexedProperty(ExecState* exec, unsigned index, const P if (current.isDataDescriptor() != descriptor.isDataDescriptor()) { // 9.a. Reject, if the [[Configurable]] field of current is false. if (!current.configurable()) - return reject(exec, throwException, "Attempting to change access mechanism for an unconfigurable property."); + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeAccessMechanismError)); // 9.b. If IsDataDescriptor(current) is true, then convert the property named P of object O from a // data property to an accessor property. Preserve the existing values of the converted property's // [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to @@ -1792,11 +2443,11 @@ bool JSObject::defineOwnIndexedProperty(ExecState* exec, unsigned index, const P if (!current.configurable() && !current.writable()) { // 10.a.i. Reject, if the [[Writable]] field of current is false and the [[Writable]] field of Desc is true. if (descriptor.writable()) - return reject(exec, throwException, "Attempting to change writable attribute of unconfigurable property."); + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeWritabilityError)); // 10.a.ii. If the [[Writable]] field of current is false, then // 10.a.ii.1. Reject, if the [[Value]] field of Desc is present and SameValue(Desc.[[Value]], current.[[Value]]) is false. if (descriptor.value() && !sameValue(exec, descriptor.value(), current.value())) - return reject(exec, throwException, "Attempting to change value of a readonly property."); + return typeError(exec, scope, throwException, ASCIILiteral(ReadonlyPropertyChangeError)); } // 10.b. else, the [[Configurable]] field of current is true, so any change is acceptable. } else { @@ -1805,10 +2456,10 @@ bool JSObject::defineOwnIndexedProperty(ExecState* exec, unsigned index, const P if (!current.configurable()) { // 11.i. Reject, if the [[Set]] field of Desc is present and SameValue(Desc.[[Set]], current.[[Set]]) is false. if (descriptor.setterPresent() && descriptor.setter() != current.setter()) - return reject(exec, throwException, "Attempting to change the setter of an unconfigurable property."); + return typeError(exec, scope, throwException, ASCIILiteral("Attempting to change the setter of an unconfigurable property.")); // 11.ii. Reject, if the [[Get]] field of Desc is present and SameValue(Desc.[[Get]], current.[[Get]]) is false. if (descriptor.getterPresent() && descriptor.getter() != current.getter()) - return reject(exec, throwException, "Attempting to change the getter of an unconfigurable property."); + return typeError(exec, scope, throwException, ASCIILiteral("Attempting to change the getter of an unconfigurable property.")); } } } @@ -1832,7 +2483,7 @@ void JSObject::deallocateSparseIndexMap() arrayStorage->m_sparseMap.clear(); } -bool JSObject::attemptToInterceptPutByIndexOnHoleForPrototype(ExecState* exec, JSValue thisValue, unsigned i, JSValue value, bool shouldThrow) +bool JSObject::attemptToInterceptPutByIndexOnHoleForPrototype(ExecState* exec, JSValue thisValue, unsigned i, JSValue value, bool shouldThrow, bool& putResult) { for (JSObject* current = this; ;) { // This has the same behavior with respect to prototypes as JSObject::put(). It only @@ -1844,12 +2495,18 @@ bool JSObject::attemptToInterceptPutByIndexOnHoleForPrototype(ExecState* exec, J if (storage && storage->m_sparseMap) { SparseArrayValueMap::iterator iter = storage->m_sparseMap->find(i); if (iter != storage->m_sparseMap->notFound() && (iter->value.attributes & (Accessor | ReadOnly))) { - iter->value.put(exec, thisValue, storage->m_sparseMap.get(), value, shouldThrow); + putResult = iter->value.put(exec, thisValue, storage->m_sparseMap.get(), value, shouldThrow); return true; } } + + if (current->type() == ProxyObjectType) { + ProxyObject* proxy = jsCast(current); + putResult = proxy->putByIndexCommon(exec, thisValue, i, value, shouldThrow); + return true; + } - JSValue prototypeValue = current->prototype(); + JSValue prototypeValue = current->getPrototypeDirect(); if (prototypeValue.isNull()) return false; @@ -1857,69 +2514,82 @@ bool JSObject::attemptToInterceptPutByIndexOnHoleForPrototype(ExecState* exec, J } } -bool JSObject::attemptToInterceptPutByIndexOnHole(ExecState* exec, unsigned i, JSValue value, bool shouldThrow) +bool JSObject::attemptToInterceptPutByIndexOnHole(ExecState* exec, unsigned i, JSValue value, bool shouldThrow, bool& putResult) { - JSValue prototypeValue = prototype(); + JSValue prototypeValue = getPrototypeDirect(); if (prototypeValue.isNull()) return false; - return asObject(prototypeValue)->attemptToInterceptPutByIndexOnHoleForPrototype(exec, this, i, value, shouldThrow); + return asObject(prototypeValue)->attemptToInterceptPutByIndexOnHoleForPrototype(exec, this, i, value, shouldThrow, putResult); } template -void JSObject::putByIndexBeyondVectorLengthWithoutAttributes(ExecState* exec, unsigned i, JSValue value) +bool JSObject::putByIndexBeyondVectorLengthWithoutAttributes(ExecState* exec, unsigned i, JSValue value) { - ASSERT((structure()->indexingType() & IndexingShapeMask) == indexingShape); + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + ASSERT((indexingType() & IndexingShapeMask) == indexingShape); ASSERT(!indexingShouldBeSparse()); + + Butterfly* butterfly = m_butterfly.get(); // For us to get here, the index is either greater than the public length, or greater than // or equal to the vector length. - ASSERT(i >= m_butterfly->vectorLength()); + ASSERT(i >= butterfly->vectorLength()); - VM& vm = exec->vm(); - - if (i >= MAX_ARRAY_INDEX - 1 - || (i >= MIN_SPARSE_ARRAY_INDEX - && !isDenseEnoughForVector(i, countElements(butterfly()))) - || indexIsSufficientlyBeyondLengthForSparseMap(i, m_butterfly->vectorLength())) { + if (i > MAX_STORAGE_VECTOR_INDEX + || (i >= MIN_SPARSE_ARRAY_INDEX && !isDenseEnoughForVector(i, countElements(butterfly))) + || indexIsSufficientlyBeyondLengthForSparseMap(i, butterfly->vectorLength())) { ASSERT(i <= MAX_ARRAY_INDEX); ensureArrayStorageSlow(vm); SparseArrayValueMap* map = allocateSparseIndexMap(vm); - map->putEntry(exec, this, i, value, false); + bool result = map->putEntry(exec, this, i, value, false); ASSERT(i >= arrayStorage()->length()); arrayStorage()->setLength(i + 1); - return; + return result; } - ensureLength(vm, i + 1); + if (!ensureLength(vm, i + 1)) { + throwOutOfMemoryError(exec, scope); + return false; + } + butterfly = m_butterfly.get(); - RELEASE_ASSERT(i < m_butterfly->vectorLength()); + RELEASE_ASSERT(i < butterfly->vectorLength()); switch (indexingShape) { case Int32Shape: ASSERT(value.isInt32()); - m_butterfly->contiguousInt32()[i].setWithoutWriteBarrier(value); - break; + butterfly->contiguousInt32()[i].setWithoutWriteBarrier(value); + return true; case DoubleShape: { ASSERT(value.isNumber()); double valueAsDouble = value.asNumber(); ASSERT(valueAsDouble == valueAsDouble); - m_butterfly->contiguousDouble()[i] = valueAsDouble; - break; + butterfly->contiguousDouble()[i] = valueAsDouble; + return true; } case ContiguousShape: - m_butterfly->contiguous()[i].set(vm, this, value); - break; + butterfly->contiguous()[i].set(vm, this, value); + return true; default: CRASH(); + return false; } } -void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, unsigned i, JSValue value, bool shouldThrow, ArrayStorage* storage) +// Explicit instantiations needed by JSArray.cpp. +template bool JSObject::putByIndexBeyondVectorLengthWithoutAttributes(ExecState*, unsigned, JSValue); +template bool JSObject::putByIndexBeyondVectorLengthWithoutAttributes(ExecState*, unsigned, JSValue); +template bool JSObject::putByIndexBeyondVectorLengthWithoutAttributes(ExecState*, unsigned, JSValue); + +bool JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, unsigned i, JSValue value, bool shouldThrow, ArrayStorage* storage) { VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); // i should be a valid array index that is outside of the current vector. ASSERT(i <= MAX_ARRAY_INDEX); @@ -1930,7 +2600,7 @@ void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, uns // First, handle cases where we don't currently have a sparse map. if (LIKELY(!map)) { // If the array is not extensible, we should have entered dictionary mode, and created the sparse map. - ASSERT(isExtensible()); + ASSERT(isStructureExtensible()); // Update m_length if necessary. if (i >= storage->length()) @@ -1944,23 +2614,19 @@ void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, uns storage = arrayStorage(); storage->m_vector[i].set(vm, this, value); ++storage->m_numValuesInVector; - return; + return true; } // We don't want to, or can't use a vector to hold this property - allocate a sparse map & add the value. - map = allocateSparseIndexMap(exec->vm()); - map->putEntry(exec, this, i, value, shouldThrow); - return; + map = allocateSparseIndexMap(vm); + return map->putEntry(exec, this, i, value, shouldThrow); } // Update m_length if necessary. unsigned length = storage->length(); if (i >= length) { // Prohibit growing the array if length is not writable. - if (map->lengthIsReadOnly() || !isExtensible()) { - if (shouldThrow) - throwTypeError(exec, StrictModeReadonlyPropertyWriteError); - return; - } + if (map->lengthIsReadOnly() || !isStructureExtensible()) + return typeError(exec, scope, shouldThrow, ASCIILiteral(ReadonlyPropertyWriteError)); length = i + 1; storage->setLength(length); } @@ -1968,10 +2634,8 @@ void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, uns // We are currently using a map - check whether we still want to be doing so. // We will continue to use a sparse map if SparseMode is set, a vector would be too sparse, or if allocation fails. unsigned numValuesInArray = storage->m_numValuesInVector + map->size(); - if (map->sparseMode() || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(exec->vm(), length)) { - map->putEntry(exec, this, i, value, shouldThrow); - return; - } + if (map->sparseMode() || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(vm, length)) + return map->putEntry(exec, this, i, value, shouldThrow); // Reread m_storage after increaseVectorLength, update m_numValuesInVector. storage = arrayStorage(); @@ -1989,37 +2653,35 @@ void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, uns if (!valueSlot) ++storage->m_numValuesInVector; valueSlot.set(vm, this, value); + return true; } -void JSObject::putByIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue value, bool shouldThrow) +bool JSObject::putByIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue value, bool shouldThrow) { VM& vm = exec->vm(); // i should be a valid array index that is outside of the current vector. ASSERT(i <= MAX_ARRAY_INDEX); - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: { if (indexingShouldBeSparse()) { - putByIndexBeyondVectorLengthWithArrayStorage( + return putByIndexBeyondVectorLengthWithArrayStorage( exec, i, value, shouldThrow, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm)); - break; } if (indexIsSufficientlyBeyondLengthForSparseMap(i, 0) || i >= MIN_SPARSE_ARRAY_INDEX) { - putByIndexBeyondVectorLengthWithArrayStorage( + return putByIndexBeyondVectorLengthWithArrayStorage( exec, i, value, shouldThrow, createArrayStorage(vm, 0, 0)); - break; } - if (structure()->needsSlowPutIndexing()) { - ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, i + 1)); - storage->m_vector[i].set(vm, this, value); - storage->m_numValuesInVector++; - break; + if (structure(vm)->needsSlowPutIndexing()) { + // Convert the indexing type to the SlowPutArrayStorage and retry. + createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, 0, i + 1)); + return putByIndex(this, exec, i, value, shouldThrow); } createInitialForValueAndSet(vm, i, value); - break; + return true; } case ALL_UNDECIDED_INDEXING_TYPES: { @@ -2027,46 +2689,42 @@ void JSObject::putByIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue break; } - case ALL_INT32_INDEXING_TYPES: { - putByIndexBeyondVectorLengthWithoutAttributes(exec, i, value); - break; - } + case ALL_INT32_INDEXING_TYPES: + return putByIndexBeyondVectorLengthWithoutAttributes(exec, i, value); - case ALL_DOUBLE_INDEXING_TYPES: { - putByIndexBeyondVectorLengthWithoutAttributes(exec, i, value); - break; - } + case ALL_DOUBLE_INDEXING_TYPES: + return putByIndexBeyondVectorLengthWithoutAttributes(exec, i, value); - case ALL_CONTIGUOUS_INDEXING_TYPES: { - putByIndexBeyondVectorLengthWithoutAttributes(exec, i, value); - break; - } + case ALL_CONTIGUOUS_INDEXING_TYPES: + return putByIndexBeyondVectorLengthWithoutAttributes(exec, i, value); case NonArrayWithSlowPutArrayStorage: case ArrayWithSlowPutArrayStorage: { // No own property present in the vector, but there might be in the sparse map! SparseArrayValueMap* map = arrayStorage()->m_sparseMap.get(); - if (!(map && map->contains(i)) && attemptToInterceptPutByIndexOnHole(exec, i, value, shouldThrow)) - return; + bool putResult = false; + if (!(map && map->contains(i)) && attemptToInterceptPutByIndexOnHole(exec, i, value, shouldThrow, putResult)) + return putResult; FALLTHROUGH; } case NonArrayWithArrayStorage: case ArrayWithArrayStorage: - putByIndexBeyondVectorLengthWithArrayStorage(exec, i, value, shouldThrow, arrayStorage()); - break; + return putByIndexBeyondVectorLengthWithArrayStorage(exec, i, value, shouldThrow, arrayStorage()); default: RELEASE_ASSERT_NOT_REACHED(); } + return false; } bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, unsigned i, JSValue value, unsigned attributes, PutDirectIndexMode mode, ArrayStorage* storage) { VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); // i should be a valid array index that is outside of the current vector. - ASSERT(hasArrayStorage(structure()->indexingType())); + ASSERT(hasAnyArrayStorage(indexingType())); ASSERT(arrayStorage() == storage); ASSERT(i >= storage->vectorLength() || attributes); ASSERT(i <= MAX_ARRAY_INDEX); @@ -2076,7 +2734,7 @@ bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, // First, handle cases where we don't currently have a sparse map. if (LIKELY(!map)) { // If the array is not extensible, we should have entered dictionary mode, and created the spare map. - ASSERT(isExtensible()); + ASSERT(isStructureExtensible()); // Update m_length if necessary. if (i >= storage->length()) @@ -2095,7 +2753,7 @@ bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, return true; } // We don't want to, or can't use a vector to hold this property - allocate a sparse map & add the value. - map = allocateSparseIndexMap(exec->vm()); + map = allocateSparseIndexMap(vm); return map->putDirect(exec, this, i, value, attributes, mode); } @@ -2105,9 +2763,9 @@ bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, if (mode != PutDirectIndexLikePutDirect) { // Prohibit growing the array if length is not writable. if (map->lengthIsReadOnly()) - return reject(exec, mode == PutDirectIndexShouldThrow, StrictModeReadonlyPropertyWriteError); - if (!isExtensible()) - return reject(exec, mode == PutDirectIndexShouldThrow, "Attempting to define property on object that is not extensible."); + return typeError(exec, scope, mode == PutDirectIndexShouldThrow, ASCIILiteral(ReadonlyPropertyWriteError)); + if (!isStructureExtensible()) + return typeError(exec, scope, mode == PutDirectIndexShouldThrow, ASCIILiteral(NonExtensibleObjectPropertyDefineError)); } length = i + 1; storage->setLength(length); @@ -2116,7 +2774,7 @@ bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, // We are currently using a map - check whether we still want to be doing so. // We will continue to use a sparse map if SparseMode is set, a vector would be too sparse, or if allocation fails. unsigned numValuesInArray = storage->m_numValuesInVector + map->size(); - if (map->sparseMode() || attributes || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(exec->vm(), length)) + if (map->sparseMode() || attributes || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(vm, length)) return map->putDirect(exec, this, i, value, attributes, mode); // Reread m_storage after increaseVectorLength, update m_numValuesInVector. @@ -2148,7 +2806,7 @@ bool JSObject::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSV if (attributes & (ReadOnly | Accessor)) notifyPresenceOfIndexedAccessors(vm); - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: { if (indexingShouldBeSparse() || attributes) { return putDirectIndexBeyondVectorLengthWithArrayStorage( @@ -2159,8 +2817,8 @@ bool JSObject::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSV return putDirectIndexBeyondVectorLengthWithArrayStorage( exec, i, value, attributes, mode, createArrayStorage(vm, 0, 0)); } - if (structure()->needsSlowPutIndexing()) { - ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, i + 1)); + if (structure(vm)->needsSlowPutIndexing()) { + ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, 0, i + 1)); storage->m_vector[i].set(vm, this, value); storage->m_numValuesInVector++; return true; @@ -2177,9 +2835,10 @@ bool JSObject::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSV } case ALL_INT32_INDEXING_TYPES: { - if (attributes & (ReadOnly | Accessor)) { - return putDirectIndexBeyondVectorLengthWithArrayStorage( - exec, i, value, attributes, mode, convertInt32ToArrayStorage(vm)); + if (attributes) { + if (i < m_butterfly.get()->vectorLength()) + return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm)); + return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, convertInt32ToArrayStorage(vm)); } if (!value.isInt32()) { convertInt32ForValue(vm, value); @@ -2190,9 +2849,10 @@ bool JSObject::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSV } case ALL_DOUBLE_INDEXING_TYPES: { - if (attributes & (ReadOnly | Accessor)) { - return putDirectIndexBeyondVectorLengthWithArrayStorage( - exec, i, value, attributes, mode, convertDoubleToArrayStorage(vm)); + if (attributes) { + if (i < m_butterfly.get()->vectorLength()) + return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm)); + return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, convertDoubleToArrayStorage(vm)); } if (!value.isNumber()) { convertDoubleToContiguous(vm); @@ -2208,15 +2868,20 @@ bool JSObject::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSV } case ALL_CONTIGUOUS_INDEXING_TYPES: { - if (attributes & (ReadOnly | Accessor)) { - return putDirectIndexBeyondVectorLengthWithArrayStorage( - exec, i, value, attributes, mode, convertContiguousToArrayStorage(vm)); + if (attributes) { + if (i < m_butterfly.get()->vectorLength()) + return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm)); + return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, convertContiguousToArrayStorage(vm)); } putByIndexBeyondVectorLengthWithoutAttributes(exec, i, value); return true; } case ALL_ARRAY_STORAGE_INDEXING_TYPES: + if (attributes) { + if (i < m_butterfly.get()->vectorLength()) + return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm)); + } return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, arrayStorage()); default: @@ -2225,7 +2890,15 @@ bool JSObject::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSV } } -void JSObject::putDirectNativeFunction(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, unsigned functionLength, NativeFunction nativeFunction, Intrinsic intrinsic, unsigned attributes) +bool JSObject::putDirectNativeIntrinsicGetter(VM& vm, JSGlobalObject* globalObject, Identifier name, NativeFunction nativeFunction, Intrinsic intrinsic, unsigned attributes) +{ + GetterSetter* accessor = GetterSetter::create(vm, globalObject); + JSFunction* function = JSFunction::create(vm, globalObject, 0, makeString("get ", name.string()), nativeFunction, intrinsic); + accessor->setGetter(vm, globalObject, function); + return putDirectNonIndexAccessor(vm, name, accessor, attributes); +} + +bool JSObject::putDirectNativeFunction(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, unsigned functionLength, NativeFunction nativeFunction, Intrinsic intrinsic, unsigned attributes) { StringImpl* name = propertyName.publicName(); if (!name) @@ -2233,19 +2906,50 @@ void JSObject::putDirectNativeFunction(VM& vm, JSGlobalObject* globalObject, con ASSERT(name); JSFunction* function = JSFunction::create(vm, globalObject, functionLength, name, nativeFunction, intrinsic); + return putDirect(vm, propertyName, function, attributes); +} + +bool JSObject::putDirectNativeFunction(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, unsigned functionLength, NativeFunction nativeFunction, Intrinsic intrinsic, const DOMJIT::Signature* signature, unsigned attributes) +{ + StringImpl* name = propertyName.publicName(); + if (!name) + name = vm.propertyNames->anonymous.impl(); + ASSERT(name); + + JSFunction* function = JSFunction::create(vm, globalObject, functionLength, name, nativeFunction, intrinsic, callHostFunctionAsConstructor, signature); + return putDirect(vm, propertyName, function, attributes); +} + +JSFunction* JSObject::putDirectBuiltinFunction(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, FunctionExecutable* functionExecutable, unsigned attributes) +{ + StringImpl* name = propertyName.publicName(); + if (!name) + name = vm.propertyNames->anonymous.impl(); + ASSERT(name); + JSFunction* function = JSFunction::createBuiltinFunction(vm, static_cast(functionExecutable), globalObject); putDirect(vm, propertyName, function, attributes); + return function; +} + +JSFunction* JSObject::putDirectBuiltinFunctionWithoutTransition(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, FunctionExecutable* functionExecutable, unsigned attributes) +{ + JSFunction* function = JSFunction::createBuiltinFunction(vm, static_cast(functionExecutable), globalObject); + putDirectWithoutTransition(vm, propertyName, function, attributes); + return function; } void JSObject::putDirectNativeFunctionWithoutTransition(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, unsigned functionLength, NativeFunction nativeFunction, Intrinsic intrinsic, unsigned attributes) { StringImpl* name = propertyName.publicName(); + if (!name) + name = vm.propertyNames->anonymous.impl(); ASSERT(name); - JSFunction* function = JSFunction::create(vm, globalObject, functionLength, name, nativeFunction, intrinsic); putDirectWithoutTransition(vm, propertyName, function, attributes); } -ALWAYS_INLINE unsigned JSObject::getNewVectorLength(unsigned currentVectorLength, unsigned currentLength, unsigned desiredLength) +// NOTE: This method is for ArrayStorage vectors. +ALWAYS_INLINE unsigned JSObject::getNewVectorLength(unsigned indexBias, unsigned currentVectorLength, unsigned currentLength, unsigned desiredLength) { ASSERT(desiredLength <= MAX_STORAGE_VECTOR_LENGTH); @@ -2262,25 +2966,27 @@ ALWAYS_INLINE unsigned JSObject::getNewVectorLength(unsigned currentVectorLength ASSERT(increasedLength >= desiredLength); - lastArraySize = std::min(increasedLength, FIRST_VECTOR_GROW); + lastArraySize = std::min(increasedLength, FIRST_ARRAY_STORAGE_VECTOR_GROW); - return std::min(increasedLength, MAX_STORAGE_VECTOR_LENGTH); + return ArrayStorage::optimalVectorLength( + indexBias, structure()->outOfLineCapacity(), + std::min(increasedLength, MAX_STORAGE_VECTOR_LENGTH)); } ALWAYS_INLINE unsigned JSObject::getNewVectorLength(unsigned desiredLength) { - unsigned vectorLength; - unsigned length; + unsigned indexBias = 0; + unsigned vectorLength = 0; + unsigned length = 0; - if (hasIndexedProperties(structure()->indexingType())) { - vectorLength = m_butterfly->vectorLength(); - length = m_butterfly->publicLength(); - } else { - vectorLength = 0; - length = 0; + if (hasIndexedProperties(indexingType())) { + if (ArrayStorage* storage = arrayStorageOrNull()) + indexBias = storage->m_indexBias; + vectorLength = m_butterfly.get()->vectorLength(); + length = m_butterfly.get()->publicLength(); } - return getNewVectorLength(vectorLength, length, desiredLength); + return getNewVectorLength(indexBias, vectorLength, length, desiredLength); } template @@ -2311,7 +3017,7 @@ unsigned JSObject::countElements(Butterfly* butterfly) unsigned JSObject::countElements() { - switch (structure()->indexingType()) { + switch (indexingType()) { case ALL_BLANK_INDEXING_TYPES: case ALL_UNDECIDED_INDEXING_TYPES: return 0; @@ -2333,32 +3039,44 @@ unsigned JSObject::countElements() bool JSObject::increaseVectorLength(VM& vm, unsigned newLength) { + ArrayStorage* storage = arrayStorage(); + + unsigned vectorLength = storage->vectorLength(); + unsigned availableVectorLength = storage->availableVectorLength(structure(vm), vectorLength); + if (availableVectorLength >= newLength) { + // The cell was already big enough for the desired length! + for (unsigned i = vectorLength; i < availableVectorLength; ++i) + storage->m_vector[i].clear(); + storage->setVectorLength(availableVectorLength); + return true; + } + // This function leaves the array in an internally inconsistent state, because it does not move any values from sparse value map // to the vector. Callers have to account for that, because they can do it more efficiently. if (newLength > MAX_STORAGE_VECTOR_LENGTH) return false; - ArrayStorage* storage = arrayStorage(); - if (newLength >= MIN_SPARSE_ARRAY_INDEX && !isDenseEnoughForVector(newLength, storage->m_numValuesInVector)) return false; unsigned indexBias = storage->m_indexBias; - unsigned vectorLength = storage->vectorLength(); ASSERT(newLength > vectorLength); unsigned newVectorLength = getNewVectorLength(newLength); // Fast case - there is no precapacity. In these cases a realloc makes sense. + Structure* structure = this->structure(vm); if (LIKELY(!indexBias)) { DeferGC deferGC(vm.heap); Butterfly* newButterfly = storage->butterfly()->growArrayRight( - vm, this, structure(), structure()->outOfLineCapacity(), true, + vm, this, structure, structure->outOfLineCapacity(), true, ArrayStorage::sizeFor(vectorLength), ArrayStorage::sizeFor(newVectorLength)); if (!newButterfly) return false; + for (unsigned i = vectorLength; i < newVectorLength; ++i) + newButterfly->arrayStorage()->m_vector[i].clear(); newButterfly->arrayStorage()->setVectorLength(newVectorLength); - setButterflyWithoutChangingStructure(vm, newButterfly); + setButterfly(vm, newButterfly); return true; } @@ -2367,61 +3085,160 @@ bool JSObject::increaseVectorLength(VM& vm, unsigned newLength) unsigned newIndexBias = std::min(indexBias >> 1, MAX_STORAGE_VECTOR_LENGTH - newVectorLength); Butterfly* newButterfly = storage->butterfly()->resizeArray( vm, this, - structure()->outOfLineCapacity(), true, ArrayStorage::sizeFor(vectorLength), + structure->outOfLineCapacity(), true, ArrayStorage::sizeFor(vectorLength), newIndexBias, true, ArrayStorage::sizeFor(newVectorLength)); if (!newButterfly) return false; + for (unsigned i = vectorLength; i < newVectorLength; ++i) + newButterfly->arrayStorage()->m_vector[i].clear(); newButterfly->arrayStorage()->setVectorLength(newVectorLength); newButterfly->arrayStorage()->m_indexBias = newIndexBias; - setButterflyWithoutChangingStructure(vm, newButterfly); + setButterfly(vm, newButterfly); return true; } -void JSObject::ensureLengthSlow(VM& vm, unsigned length) +bool JSObject::ensureLengthSlow(VM& vm, unsigned length) { - ASSERT(length < MAX_ARRAY_INDEX); - ASSERT(hasContiguous(structure()->indexingType()) || hasInt32(structure()->indexingType()) || hasDouble(structure()->indexingType()) || hasUndecided(structure()->indexingType())); - ASSERT(length > m_butterfly->vectorLength()); + Butterfly* butterfly = m_butterfly.get(); - unsigned newVectorLength = std::min( - length << 1, - MAX_STORAGE_VECTOR_LENGTH); - unsigned oldVectorLength = m_butterfly->vectorLength(); - DeferGC deferGC(vm.heap); - m_butterfly.set(vm, this, m_butterfly->growArrayRight( - vm, this, structure(), structure()->outOfLineCapacity(), true, - oldVectorLength * sizeof(EncodedJSValue), - newVectorLength * sizeof(EncodedJSValue))); - - m_butterfly->setVectorLength(newVectorLength); + ASSERT(length < MAX_ARRAY_INDEX); + ASSERT(hasContiguous(indexingType()) || hasInt32(indexingType()) || hasDouble(indexingType()) || hasUndecided(indexingType())); + ASSERT(length > butterfly->vectorLength()); + + unsigned oldVectorLength = butterfly->vectorLength(); + unsigned newVectorLength; + + Structure* structure = this->structure(vm); + unsigned propertyCapacity = structure->outOfLineCapacity(); + + unsigned availableOldLength = + Butterfly::availableContiguousVectorLength(propertyCapacity, oldVectorLength); + Butterfly* newButterfly = nullptr; + if (availableOldLength >= length) { + // This is the case where someone else selected a vector length that caused internal + // fragmentation. If we did our jobs right, this would never happen. But I bet we will mess + // this up, so this defense should stay. + newVectorLength = availableOldLength; + } else { + newVectorLength = Butterfly::optimalContiguousVectorLength( + propertyCapacity, std::min(length << 1, MAX_STORAGE_VECTOR_LENGTH)); + butterfly = butterfly->growArrayRight( + vm, this, structure, propertyCapacity, true, + oldVectorLength * sizeof(EncodedJSValue), + newVectorLength * sizeof(EncodedJSValue)); + if (!butterfly) + return false; + newButterfly = butterfly; + } - if (hasDouble(structure()->indexingType())) { + if (hasDouble(indexingType())) { for (unsigned i = oldVectorLength; i < newVectorLength; ++i) - m_butterfly->contiguousDouble().data()[i] = QNaN; + butterfly->indexingPayload()[i] = PNaN; + } else { + for (unsigned i = oldVectorLength; i < newVectorLength; ++i) + butterfly->indexingPayload>()[i].clear(); } + + if (newButterfly) { + butterfly->setVectorLength(newVectorLength); + WTF::storeStoreFence(); + m_butterfly.set(vm, this, newButterfly); + } else { + WTF::storeStoreFence(); + butterfly->setVectorLength(newVectorLength); + } + + return true; } -Butterfly* JSObject::growOutOfLineStorage(VM& vm, size_t oldSize, size_t newSize) +void JSObject::reallocateAndShrinkButterfly(VM& vm, unsigned length) +{ + ASSERT(length < MAX_ARRAY_INDEX); + ASSERT(length < MAX_STORAGE_VECTOR_LENGTH); + ASSERT(hasContiguous(indexingType()) || hasInt32(indexingType()) || hasDouble(indexingType()) || hasUndecided(indexingType())); + ASSERT(m_butterfly.get()->vectorLength() > length); + ASSERT(!m_butterfly.get()->indexingHeader()->preCapacity(structure())); + + DeferGC deferGC(vm.heap); + Butterfly* newButterfly = m_butterfly.get()->resizeArray(vm, this, structure(), 0, ArrayStorage::sizeFor(length)); + newButterfly->setVectorLength(length); + newButterfly->setPublicLength(length); + WTF::storeStoreFence(); + m_butterfly.set(vm, this, newButterfly); +} + +Butterfly* JSObject::allocateMoreOutOfLineStorage(VM& vm, size_t oldSize, size_t newSize) { ASSERT(newSize > oldSize); // It's important that this function not rely on structure(), for the property // capacity, since we might have already mutated the structure in-place. - - return m_butterfly->growPropertyStorage(vm, this, structure(), oldSize, newSize); + + return Butterfly::createOrGrowPropertyStorage(m_butterfly.get(), vm, this, structure(vm), oldSize, newSize); +} + +static JSCustomGetterSetterFunction* getCustomGetterSetterFunctionForGetterSetter(ExecState* exec, PropertyName propertyName, CustomGetterSetter* getterSetter, JSCustomGetterSetterFunction::Type type) +{ + auto key = std::make_pair(getterSetter, (int)type); + JSCustomGetterSetterFunction* customGetterSetterFunction = exec->vm().customGetterSetterFunctionMap.get(key); + if (!customGetterSetterFunction) { + customGetterSetterFunction = JSCustomGetterSetterFunction::create(exec->vm(), exec->lexicalGlobalObject(), getterSetter, type, propertyName.publicName()); + exec->vm().customGetterSetterFunctionMap.set(key, customGetterSetterFunction); + } + return customGetterSetterFunction; } bool JSObject::getOwnPropertyDescriptor(ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) { - JSC::PropertySlot slot(this); - if (!methodTable()->getOwnPropertySlot(this, exec, propertyName, slot)) - return false; - /* Workaround, JSDOMWindow::getOwnPropertySlot searches the prototype chain. :-( */ - if (slot.slotBase() != this && slot.slotBase() && slot.slotBase()->methodTable()->toThis(slot.slotBase(), exec, NotStrictMode) != this) + VM& vm = exec->vm(); + JSC::PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty); + if (!methodTable(vm)->getOwnPropertySlot(this, exec, propertyName, slot)) return false; + + // DebuggerScope::getOwnPropertySlot() (and possibly others) may return attributes from the prototype chain + // but getOwnPropertyDescriptor() should only work for 'own' properties so we exit early if we detect that + // the property is not an own property. + if (slot.slotBase() != this && slot.slotBase()) { + JSProxy* jsProxy = jsDynamicCast(vm, this); + if (!jsProxy || jsProxy->target() != slot.slotBase()) { + // Try ProxyObject. + ProxyObject* proxyObject = jsDynamicCast(vm, this); + if (!proxyObject || proxyObject->target() != slot.slotBase()) + return false; + } + } + if (slot.isAccessor()) descriptor.setAccessorDescriptor(slot.getterSetter(), slot.attributes()); - else + else if (slot.attributes() & CustomAccessor) { + descriptor.setCustomDescriptor(slot.attributes()); + + JSObject* thisObject = this; + if (auto* proxy = jsDynamicCast(vm, this)) + thisObject = proxy->target(); + + CustomGetterSetter* getterSetter; + if (slot.isCustomAccessor()) + getterSetter = slot.customGetterSetter(); + else { + JSValue maybeGetterSetter = thisObject->getDirect(exec->vm(), propertyName); + if (!maybeGetterSetter) { + thisObject->reifyAllStaticProperties(exec); + maybeGetterSetter = thisObject->getDirect(exec->vm(), propertyName); + } + + ASSERT(maybeGetterSetter); + getterSetter = jsDynamicCast(vm, maybeGetterSetter); + } + ASSERT(getterSetter); + if (!getterSetter) + return false; + + if (getterSetter->getter()) + descriptor.setGetter(getCustomGetterSetterFunctionForGetterSetter(exec, propertyName, getterSetter, JSCustomGetterSetterFunction::Type::Getter)); + if (getterSetter->setter()) + descriptor.setSetter(getCustomGetterSetterFunctionForGetterSetter(exec, propertyName, getterSetter, JSCustomGetterSetterFunction::Type::Setter)); + } else descriptor.setDescriptor(slot.getValue(exec, propertyName), slot.attributes()); return true; } @@ -2431,11 +3248,11 @@ static bool putDescriptor(ExecState* exec, JSObject* target, PropertyName proper VM& vm = exec->vm(); if (descriptor.isGenericDescriptor() || descriptor.isDataDescriptor()) { if (descriptor.isGenericDescriptor() && oldDescriptor.isAccessorDescriptor()) { - GetterSetter* accessor = GetterSetter::create(vm); + GetterSetter* accessor = GetterSetter::create(vm, exec->lexicalGlobalObject()); if (oldDescriptor.getterPresent()) - accessor->setGetter(vm, oldDescriptor.getterObject()); + accessor->setGetter(vm, exec->lexicalGlobalObject(), oldDescriptor.getterObject()); if (oldDescriptor.setterPresent()) - accessor->setSetter(vm, oldDescriptor.setterObject()); + accessor->setSetter(vm, exec->lexicalGlobalObject(), oldDescriptor.setterObject()); target->putDirectAccessor(exec, propertyName, accessor, attributes | Accessor); return true; } @@ -2446,193 +3263,340 @@ static bool putDescriptor(ExecState* exec, JSObject* target, PropertyName proper newValue = oldDescriptor.value(); target->putDirect(vm, propertyName, newValue, attributes & ~Accessor); if (attributes & ReadOnly) - target->structure()->setContainsReadOnlyProperties(); + target->structure(vm)->setContainsReadOnlyProperties(); return true; } attributes &= ~ReadOnly; - GetterSetter* accessor = GetterSetter::create(vm); + GetterSetter* accessor = GetterSetter::create(vm, exec->lexicalGlobalObject()); if (descriptor.getterPresent()) - accessor->setGetter(vm, descriptor.getterObject()); + accessor->setGetter(vm, exec->lexicalGlobalObject(), descriptor.getterObject()); else if (oldDescriptor.getterPresent()) - accessor->setGetter(vm, oldDescriptor.getterObject()); + accessor->setGetter(vm, exec->lexicalGlobalObject(), oldDescriptor.getterObject()); if (descriptor.setterPresent()) - accessor->setSetter(vm, descriptor.setterObject()); + accessor->setSetter(vm, exec->lexicalGlobalObject(), descriptor.setterObject()); else if (oldDescriptor.setterPresent()) - accessor->setSetter(vm, oldDescriptor.setterObject()); + accessor->setSetter(vm, exec->lexicalGlobalObject(), oldDescriptor.setterObject()); target->putDirectAccessor(exec, propertyName, accessor, attributes | Accessor); return true; } -void JSObject::putDirectMayBeIndex(ExecState* exec, PropertyName propertyName, JSValue value) +bool JSObject::putDirectMayBeIndex(ExecState* exec, PropertyName propertyName, JSValue value) { - unsigned asIndex = propertyName.asIndex(); - if (asIndex == PropertyName::NotAnIndex) - putDirect(exec->vm(), propertyName, value); - else - putDirectIndex(exec, asIndex, value); + if (std::optional index = parseIndex(propertyName)) + return putDirectIndex(exec, index.value(), value); + return putDirect(exec->vm(), propertyName, value); } -class DefineOwnPropertyScope { -public: - DefineOwnPropertyScope(ExecState* exec) - : m_vm(exec->vm()) - { - m_vm.setInDefineOwnProperty(true); - } - - ~DefineOwnPropertyScope() - { - m_vm.setInDefineOwnProperty(false); - } - -private: - VM& m_vm; -}; - -bool JSObject::defineOwnNonIndexProperty(ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException) +// 9.1.6.3 of the spec +// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-validateandapplypropertydescriptor +bool validateAndApplyPropertyDescriptor(ExecState* exec, JSObject* object, PropertyName propertyName, bool isExtensible, + const PropertyDescriptor& descriptor, bool isCurrentDefined, const PropertyDescriptor& current, bool throwException) { - // Track on the globaldata that we're in define property. - // Currently DefineOwnProperty uses delete to remove properties when they are being replaced - // (particularly when changing attributes), however delete won't allow non-configurable (i.e. - // DontDelete) properties to be deleted. For now, we can use this flag to make this work. - DefineOwnPropertyScope scope(exec); - + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + // If we have a new property we can just put it on normally - PropertyDescriptor current; - if (!getOwnPropertyDescriptor(exec, propertyName, current)) { + // Step 2. + if (!isCurrentDefined) { // unless extensions are prevented! - if (!isExtensible()) { - if (throwException) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to define property on object that is not extensible."))); - return false; - } + // Step 2.a + if (!isExtensible) + return typeError(exec, scope, throwException, ASCIILiteral(NonExtensibleObjectPropertyDefineError)); + if (!object) + return true; + // Step 2.c/d PropertyDescriptor oldDescriptor; oldDescriptor.setValue(jsUndefined()); - return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributes(), oldDescriptor); + // FIXME: spec says to always return true here. + return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributes(), oldDescriptor); } - + // Step 3. if (descriptor.isEmpty()) return true; - + // Step 4. if (current.equalTo(exec, descriptor)) return true; + // Step 5. // Filter out invalid changes if (!current.configurable()) { - if (descriptor.configurable()) { - if (throwException) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to configurable attribute of unconfigurable property."))); - return false; - } - if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable()) { - if (throwException) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change enumerable attribute of unconfigurable property."))); - return false; - } + if (descriptor.configurable()) + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeConfigurabilityError)); + if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable()) + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeEnumerabilityError)); } - + + // Step 6. // A generic descriptor is simply changing the attributes of an existing property if (descriptor.isGenericDescriptor()) { - if (!current.attributesEqual(descriptor)) { - methodTable()->deleteProperty(this, exec, propertyName); - return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current); + if (!current.attributesEqual(descriptor) && object) { + object->methodTable(vm)->deleteProperty(object, exec, propertyName); + return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current); } return true; } - + + // Step 7. // Changing between a normal property or an accessor property if (descriptor.isDataDescriptor() != current.isDataDescriptor()) { - if (!current.configurable()) { - if (throwException) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change access mechanism for an unconfigurable property."))); - return false; - } - methodTable()->deleteProperty(this, exec, propertyName); - return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current); + if (!current.configurable()) + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeAccessMechanismError)); + + if (!object) + return true; + + object->methodTable(vm)->deleteProperty(object, exec, propertyName); + return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current); } + // Step 8. // Changing the value and attributes of an existing property if (descriptor.isDataDescriptor()) { if (!current.configurable()) { - if (!current.writable() && descriptor.writable()) { - if (throwException) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change writable attribute of unconfigurable property."))); - return false; - } + if (!current.writable() && descriptor.writable()) + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeWritabilityError)); if (!current.writable()) { - if (descriptor.value() && !sameValue(exec, current.value(), descriptor.value())) { - if (throwException) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change value of a readonly property."))); - return false; - } + if (descriptor.value() && !sameValue(exec, current.value(), descriptor.value())) + return typeError(exec, scope, throwException, ASCIILiteral(ReadonlyPropertyChangeError)); } } if (current.attributesEqual(descriptor) && !descriptor.value()) return true; - methodTable()->deleteProperty(this, exec, propertyName); - return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current); + if (!object) + return true; + object->methodTable(vm)->deleteProperty(object, exec, propertyName); + return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current); } + // Step 9. // Changing the accessor functions of an existing accessor property ASSERT(descriptor.isAccessorDescriptor()); if (!current.configurable()) { - if (descriptor.setterPresent() && !(current.setterPresent() && JSValue::strictEqual(exec, current.setter(), descriptor.setter()))) { - if (throwException) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change the setter of an unconfigurable property."))); - return false; - } - if (descriptor.getterPresent() && !(current.getterPresent() && JSValue::strictEqual(exec, current.getter(), descriptor.getter()))) { - if (throwException) - exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change the getter of an unconfigurable property."))); - return false; - } + if (descriptor.setterPresent() && !(current.setterPresent() && JSValue::strictEqual(exec, current.setter(), descriptor.setter()))) + return typeError(exec, scope, throwException, ASCIILiteral("Attempting to change the setter of an unconfigurable property.")); + if (descriptor.getterPresent() && !(current.getterPresent() && JSValue::strictEqual(exec, current.getter(), descriptor.getter()))) + return typeError(exec, scope, throwException, ASCIILiteral("Attempting to change the getter of an unconfigurable property.")); + if (current.attributes() & CustomAccessor) + return typeError(exec, scope, throwException, ASCIILiteral(UnconfigurablePropertyChangeAccessMechanismError)); } - JSValue accessor = getDirect(exec->vm(), propertyName); + + // Step 10/11. + if (!object) + return true; + JSValue accessor = object->getDirect(vm, propertyName); if (!accessor) return false; - GetterSetter* getterSetter = asGetterSetter(accessor); - if (descriptor.setterPresent()) - getterSetter->setSetter(exec->vm(), descriptor.setterObject()); - if (descriptor.getterPresent()) - getterSetter->setGetter(exec->vm(), descriptor.getterObject()); - if (current.attributesEqual(descriptor)) + GetterSetter* getterSetter; + bool getterSetterChanged = false; + if (accessor.isCustomGetterSetter()) { + getterSetter = GetterSetter::create(vm, exec->lexicalGlobalObject()); + auto* customGetterSetter = jsCast(accessor); + if (customGetterSetter->setter()) + getterSetter->setSetter(vm, exec->lexicalGlobalObject(), getCustomGetterSetterFunctionForGetterSetter(exec, propertyName, customGetterSetter, JSCustomGetterSetterFunction::Type::Setter)); + if (customGetterSetter->getter()) + getterSetter->setGetter(vm, exec->lexicalGlobalObject(), getCustomGetterSetterFunctionForGetterSetter(exec, propertyName, customGetterSetter, JSCustomGetterSetterFunction::Type::Getter)); + } else { + ASSERT(accessor.isGetterSetter()); + getterSetter = asGetterSetter(accessor); + } + if (descriptor.setterPresent()) { + getterSetter = getterSetter->withSetter(vm, exec->lexicalGlobalObject(), descriptor.setterObject()); + getterSetterChanged = true; + } + if (descriptor.getterPresent()) { + getterSetter = getterSetter->withGetter(vm, exec->lexicalGlobalObject(), descriptor.getterObject()); + getterSetterChanged = true; + } + if (current.attributesEqual(descriptor) && !getterSetterChanged) return true; - methodTable()->deleteProperty(this, exec, propertyName); + object->methodTable(vm)->deleteProperty(object, exec, propertyName); unsigned attrs = descriptor.attributesOverridingCurrent(current); - putDirectAccessor(exec, propertyName, getterSetter, attrs | Accessor); + object->putDirectAccessor(exec, propertyName, getterSetter, attrs | Accessor); return true; } +bool JSObject::defineOwnNonIndexProperty(ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException) +{ + VM& vm = exec->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + // Track on the globaldata that we're in define property. + // Currently DefineOwnProperty uses delete to remove properties when they are being replaced + // (particularly when changing attributes), however delete won't allow non-configurable (i.e. + // DontDelete) properties to be deleted. For now, we can use this flag to make this work. + VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable); + PropertyDescriptor current; + bool isCurrentDefined = getOwnPropertyDescriptor(exec, propertyName, current); + bool isExtensible = this->isExtensible(exec); + RETURN_IF_EXCEPTION(throwScope, false); + return validateAndApplyPropertyDescriptor(exec, this, propertyName, isExtensible, descriptor, isCurrentDefined, current, throwException); +} + bool JSObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException) { // If it's an array index, then use the indexed property storage. - unsigned index = propertyName.asIndex(); - if (index != PropertyName::NotAnIndex) { + if (std::optional index = parseIndex(propertyName)) { // c. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (8.12.9) on A passing P, Desc, and false as arguments. // d. Reject if succeeded is false. // e. If index >= oldLen // e.i. Set oldLenDesc.[[Value]] to index + 1. // e.ii. Call the default [[DefineOwnProperty]] internal method (8.12.9) on A passing "length", oldLenDesc, and false as arguments. This call will always return true. // f. Return true. - return object->defineOwnIndexedProperty(exec, index, descriptor, throwException); + return object->defineOwnIndexedProperty(exec, index.value(), descriptor, throwException); } return object->defineOwnNonIndexProperty(exec, propertyName, descriptor, throwException); } -bool JSObject::getOwnPropertySlotSlow(ExecState* exec, PropertyName propertyName, PropertySlot& slot) +void JSObject::convertToDictionary(VM& vm) { - unsigned i = propertyName.asIndex(); - if (i != PropertyName::NotAnIndex) - return getOwnPropertySlotByIndex(this, exec, i, slot); - return false; + DeferredStructureTransitionWatchpointFire deferredWatchpointFire; + setStructure( + vm, Structure::toCacheableDictionaryTransition(vm, structure(vm), &deferredWatchpointFire)); +} + +void JSObject::shiftButterflyAfterFlattening(const GCSafeConcurrentJSLocker&, VM& vm, Structure* structure, size_t outOfLineCapacityAfter) +{ + // This could interleave visitChildren because some old structure could have been a non + // dictionary structure. We have to be crazy careful. But, we are guaranteed to be holding + // the structure's lock right now, and that helps a bit. + + Butterfly* oldButterfly = this->butterfly(); + size_t preCapacity; + size_t indexingPayloadSizeInBytes; + bool hasIndexingHeader = this->hasIndexingHeader(); + if (UNLIKELY(hasIndexingHeader)) { + preCapacity = oldButterfly->indexingHeader()->preCapacity(structure); + indexingPayloadSizeInBytes = oldButterfly->indexingHeader()->indexingPayloadSizeInBytes(structure); + } else { + preCapacity = 0; + indexingPayloadSizeInBytes = 0; + } + + Butterfly* newButterfly = Butterfly::createUninitialized(vm, this, preCapacity, outOfLineCapacityAfter, hasIndexingHeader, indexingPayloadSizeInBytes); + + // No need to copy the precapacity. + void* currentBase = oldButterfly->base(0, outOfLineCapacityAfter); + void* newBase = newButterfly->base(0, outOfLineCapacityAfter); + + memcpy(newBase, currentBase, Butterfly::totalSize(0, outOfLineCapacityAfter, hasIndexingHeader, indexingPayloadSizeInBytes)); + + setButterfly(vm, newButterfly); +} + +uint32_t JSObject::getEnumerableLength(ExecState* exec, JSObject* object) +{ + VM& vm = exec->vm(); + Structure* structure = object->structure(vm); + if (structure->holesMustForwardToPrototype(vm)) + return 0; + switch (object->indexingType()) { + case ALL_BLANK_INDEXING_TYPES: + case ALL_UNDECIDED_INDEXING_TYPES: + return 0; + + case ALL_INT32_INDEXING_TYPES: + case ALL_CONTIGUOUS_INDEXING_TYPES: { + Butterfly* butterfly = object->butterfly(); + unsigned usedLength = butterfly->publicLength(); + for (unsigned i = 0; i < usedLength; ++i) { + if (!butterfly->contiguous()[i]) + return 0; + } + return usedLength; + } + + case ALL_DOUBLE_INDEXING_TYPES: { + Butterfly* butterfly = object->butterfly(); + unsigned usedLength = butterfly->publicLength(); + for (unsigned i = 0; i < usedLength; ++i) { + double value = butterfly->contiguousDouble()[i]; + if (value != value) + return 0; + } + return usedLength; + } + + case ALL_ARRAY_STORAGE_INDEXING_TYPES: { + ArrayStorage* storage = object->m_butterfly.get()->arrayStorage(); + if (storage->m_sparseMap.get()) + return 0; + + unsigned usedVectorLength = std::min(storage->length(), storage->vectorLength()); + for (unsigned i = 0; i < usedVectorLength; ++i) { + if (!storage->m_vector[i]) + return 0; + } + return usedVectorLength; + } + + default: + RELEASE_ASSERT_NOT_REACHED(); + return 0; + } +} + +void JSObject::getStructurePropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) +{ + VM& vm = exec->vm(); + object->structure(vm)->getPropertyNamesFromStructure(vm, propertyNames, mode); } -JSObject* throwTypeError(ExecState* exec, const String& message) +void JSObject::getGenericPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { - return exec->vm().throwException(exec, createTypeError(exec, message)); + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + object->methodTable(vm)->getOwnPropertyNames(object, exec, propertyNames, EnumerationMode(mode, JSObjectPropertiesMode::Exclude)); + RETURN_IF_EXCEPTION(scope, void()); + + JSValue nextProto = object->getPrototype(vm, exec); + RETURN_IF_EXCEPTION(scope, void()); + if (nextProto.isNull()) + return; + + JSObject* prototype = asObject(nextProto); + while (true) { + if (prototype->structure(vm)->typeInfo().overridesGetPropertyNames()) { + prototype->methodTable(vm)->getPropertyNames(prototype, exec, propertyNames, mode); + break; + } + prototype->methodTable(vm)->getOwnPropertyNames(prototype, exec, propertyNames, mode); + RETURN_IF_EXCEPTION(scope, void()); + nextProto = prototype->getPrototype(vm, exec); + RETURN_IF_EXCEPTION(scope, void()); + if (nextProto.isNull()) + break; + prototype = asObject(nextProto); + } +} + +// Implements GetMethod(O, P) in section 7.3.9 of the spec. +// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-getmethod +JSValue JSObject::getMethod(ExecState* exec, CallData& callData, CallType& callType, const Identifier& ident, const String& errorMessage) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue method = get(exec, ident); + RETURN_IF_EXCEPTION(scope, JSValue()); + + if (!method.isCell()) { + if (method.isUndefinedOrNull()) + return jsUndefined(); + + throwVMTypeError(exec, scope, errorMessage); + return jsUndefined(); + } + + callType = method.asCell()->methodTable()->getCallData(method.asCell(), callData); + if (callType == CallType::None) { + throwVMTypeError(exec, scope, errorMessage); + return jsUndefined(); + } + + return method; } } // namespace JSC -- cgit v1.2.1