#include "config.h"
#include "JSObject.h"
#include "ArrayConstructor.h"
#include "CatchScope.h"
#include "CustomGetterSetter.h"
#include "Exception.h"
#include "GCDeferralContextInlines.h"
#include "GetterSetter.h"
#include "HeapAnalyzer.h"
#include "IndexingHeaderInlines.h"
#include "JSCInlines.h"
#include "JSCustomGetterSetterFunction.h"
#include "JSFunction.h"
#include "JSImmutableButterfly.h"
#include "Lookup.h"
#include "PropertyDescriptor.h"
#include "PropertyNameArray.h"
#include "ProxyObject.h"
#include "TypeError.h"
#include "VMInlines.h"
#include <wtf/Assertions.h>
#if PLATFORM(IOS)
#include <wtf/spi/darwin/dyldSPI.h>
#endif
namespace JSC {
static unsigned lastArraySize = 0;
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSObject);
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSFinalObject);
const ASCIILiteral NonExtensibleObjectPropertyDefineError { "Attempting to define property on object that is not extensible."_s };
const ASCIILiteral ReadonlyPropertyWriteError { "Attempted to assign to readonly property."_s };
const ASCIILiteral ReadonlyPropertyChangeError { "Attempting to change value of a readonly property."_s };
const ASCIILiteral UnableToDeletePropertyError { "Unable to delete property."_s };
const ASCIILiteral UnconfigurablePropertyChangeAccessMechanismError { "Attempting to change access mechanism for an unconfigurable property."_s };
const ASCIILiteral UnconfigurablePropertyChangeConfigurabilityError { "Attempting to change configurable attribute of unconfigurable property."_s };
const ASCIILiteral UnconfigurablePropertyChangeEnumerabilityError { "Attempting to change enumerable attribute of unconfigurable property."_s };
const ASCIILiteral UnconfigurablePropertyChangeWritabilityError { "Attempting to change writable attribute of unconfigurable property."_s };
const ClassInfo JSObject::s_info = { "Object", nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(JSObject) };
const ClassInfo JSFinalObject::s_info = { "Object", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSFinalObject) };
ALWAYS_INLINE void JSObject::getNonReifiedStaticPropertyNames(VM& vm, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode)
{
if (staticPropertiesReified(vm))
return;
for (const ClassInfo* info = classInfo(vm); info; info = info->parentClass) {
const HashTable* table = info->staticPropHashTable;
if (!table)
continue;
for (auto iter = table->begin(); iter != table->end(); ++iter) {
if (mode == DontEnumPropertiesMode::Include || !(iter->attributes() & PropertyAttribute::DontEnum))
propertyNames.add(Identifier::fromString(vm, iter.key()));
}
}
}
ALWAYS_INLINE void JSObject::markAuxiliaryAndVisitOutOfLineProperties(SlotVisitor& visitor, Butterfly* butterfly, Structure* structure, PropertyOffset maxOffset)
{
ASSERT(structure);
if (!butterfly)
return;
if (isCopyOnWrite(structure->indexingMode())) {
visitor.append(bitwise_cast<WriteBarrier<JSCell>>(JSImmutableButterfly::fromButterfly(butterfly)));
return;
}
bool hasIndexingHeader = structure->hasIndexingHeader(this);
size_t preCapacity;
if (hasIndexingHeader)
preCapacity = butterfly->indexingHeader()->preCapacity(structure);
else
preCapacity = 0;
HeapCell* base = bitwise_cast<HeapCell*>(
butterfly->base(preCapacity, Structure::outOfLineCapacity(maxOffset)));
ASSERT(Heap::heap(base) == visitor.heap());
visitor.markAuxiliary(base);
unsigned outOfLineSize = Structure::outOfLineSize(maxOffset);
visitor.appendValuesHidden(butterfly->propertyStorage() - outOfLineSize, outOfLineSize);
}
ALWAYS_INLINE Structure* JSObject::visitButterfly(SlotVisitor& visitor)
{
static const char* const raceReason = "JSObject::visitButterfly";
Structure* result = visitButterflyImpl(visitor);
if (!result)
visitor.didRace(this, raceReason);
return result;
}
ALWAYS_INLINE Structure* JSObject::visitButterflyImpl(SlotVisitor& visitor)
{
VM& vm = visitor.vm();
Butterfly* butterfly;
Structure* structure;
PropertyOffset maxOffset;
auto visitElements = [&] (IndexingType indexingMode) {
switch (indexingMode) {
case ALL_WRITABLE_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;
}
};
if (visitor.mutatorIsStopped()) {
butterfly = this->butterfly();
structure = this->structure(vm);
maxOffset = structure->maxOffset();
markAuxiliaryAndVisitOutOfLineProperties(visitor, butterfly, structure, maxOffset);
visitElements(structure->indexingMode());
return structure;
}
StructureID structureID = this->structureID();
if (isNuked(structureID))
return nullptr;
structure = vm.getStructure(structureID);
maxOffset = structure->maxOffset();
IndexingType indexingMode = structure->indexingMode();
Dependency indexingModeDependency = Dependency::fence(indexingMode);
Locker<JSCellLock> locker(NoLockingNecessary);
switch (indexingMode) {
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
locker = holdLock(cellLock());
break;
default:
break;
}
butterfly = indexingModeDependency.consume(this)->butterfly();
Dependency butterflyDependency = Dependency::fence(butterfly);
if (!butterfly)
return structure;
if (butterflyDependency.consume(this)->structureID() != structureID)
return nullptr;
if (butterflyDependency.consume(structure)->maxOffset() != maxOffset)
return nullptr;
markAuxiliaryAndVisitOutOfLineProperties(visitor, butterfly, structure, maxOffset);
ASSERT(indexingMode == structure->indexingMode());
visitElements(indexingMode);
return structure;
}
size_t JSObject::estimatedSize(JSCell* cell, VM& vm)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
size_t butterflyOutOfLineSize = thisObject->m_butterfly ? thisObject->structure(vm)->outOfLineSize() : 0;
return Base::estimatedSize(cell, vm) + butterflyOutOfLineSize;
}
void JSObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
#if ASSERT_ENABLED
bool wasCheckingForDefaultMarkViolation = visitor.m_isCheckingForDefaultMarkViolation;
visitor.m_isCheckingForDefaultMarkViolation = false;
#endif
JSCell::visitChildren(thisObject, visitor);
thisObject->visitButterfly(visitor);
#if ASSERT_ENABLED
visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation;
#endif
}
void JSObject::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
Base::analyzeHeap(cell, analyzer);
Structure* structure = thisObject->structure();
for (auto& entry : structure->getPropertiesConcurrently()) {
JSValue toValue = thisObject->getDirect(entry.offset);
if (toValue && toValue.isCell())
analyzer.analyzePropertyNameEdge(thisObject, toValue.asCell(), entry.key);
}
Butterfly* butterfly = thisObject->butterfly();
if (butterfly) {
WriteBarrier<Unknown>* 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())
analyzer.analyzeIndexEdge(thisObject, toValue.asCell(), i);
}
}
}
void JSFinalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
JSFinalObject* thisObject = jsCast<JSFinalObject*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
#if ASSERT_ENABLED
bool wasCheckingForDefaultMarkViolation = visitor.m_isCheckingForDefaultMarkViolation;
visitor.m_isCheckingForDefaultMarkViolation = false;
#endif
JSCell::visitChildren(thisObject, visitor);
if (Structure* structure = thisObject->visitButterfly(visitor)) {
if (unsigned storageSize = structure->inlineSize())
visitor.appendValuesHidden(thisObject->inlineStorage(), storageSize);
}
#if ASSERT_ENABLED
visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation;
#endif
}
String JSObject::className(const JSObject* object, VM& vm)
{
const ClassInfo* info = object->classInfo(vm);
ASSERT(info);
return info->className;
}
#if PLATFORM(IOS)
inline static bool isPokerBros()
{
auto bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
return bundleID
&& CFEqual(bundleID, CFSTR("com.kpgame.PokerBros"))
&& dyld_get_program_sdk_version() < DYLD_IOS_VERSION_14_0;
}
#endif
String JSObject::toStringName(const JSObject* object, JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
#if PLATFORM(IOS)
static bool needsOldStringName = isPokerBros();
if (UNLIKELY(needsOldStringName)) {
const ClassInfo* info = object->classInfo(vm);
ASSERT(info);
return info->className;
}
#endif
auto scope = DECLARE_THROW_SCOPE(vm);
bool objectIsArray = isArray(globalObject, object);
RETURN_IF_EXCEPTION(scope, String());
if (objectIsArray)
return "Array"_s;
if (TypeInfo::isArgumentsType(object->type()))
return "Arguments"_s;
if (const_cast<JSObject*>(object)->isCallable(vm))
return "Function"_s;
return "Object"_s;
}
String JSObject::calculatedClassName(JSObject* object)
{
String constructorFunctionName;
auto* structure = object->structure();
auto* globalObject = structure->globalObject();
VM& vm = globalObject->vm();
auto scope = DECLARE_CATCH_SCOPE(vm);
PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry, &vm);
if (object->getOwnPropertySlot(object, globalObject, vm.propertyNames->constructor, slot)) {
EXCEPTION_ASSERT(!scope.exception());
if (slot.isValue()) {
if (JSObject* ctorObject = jsDynamicCast<JSObject*>(vm, slot.getValue(globalObject, vm.propertyNames->constructor))) {
if (JSFunction* constructorFunction = jsDynamicCast<JSFunction*>(vm, ctorObject))
constructorFunctionName = constructorFunction->calculatedDisplayName(vm);
else if (InternalFunction* constructorFunction = jsDynamicCast<InternalFunction*>(vm, ctorObject))
constructorFunctionName = constructorFunction->calculatedDisplayName(vm);
}
}
}
EXCEPTION_ASSERT(!scope.exception() || constructorFunctionName.isNull());
if (UNLIKELY(scope.exception()))
scope.clearException();
if (constructorFunctionName.isNull()) {
if (LIKELY(!structure->typeInfo().overridesGetPrototype())) {
JSValue protoValue = object->getPrototypeDirect(vm);
if (protoValue.isObject()) {
JSObject* protoObject = asObject(protoValue);
PropertySlot slot(protoValue, PropertySlot::InternalMethodType::VMInquiry, &vm);
if (protoObject->getPropertySlot(globalObject, vm.propertyNames->constructor, slot)) {
EXCEPTION_ASSERT(!scope.exception());
if (slot.isValue()) {
if (JSObject* ctorObject = jsDynamicCast<JSObject*>(vm, slot.getValue(globalObject, vm.propertyNames->constructor))) {
if (JSFunction* constructorFunction = jsDynamicCast<JSFunction*>(vm, ctorObject))
constructorFunctionName = constructorFunction->calculatedDisplayName(vm);
else if (InternalFunction* constructorFunction = jsDynamicCast<InternalFunction*>(vm, ctorObject))
constructorFunctionName = constructorFunction->calculatedDisplayName(vm);
}
}
}
}
}
}
EXCEPTION_ASSERT(!scope.exception() || constructorFunctionName.isNull());
if (UNLIKELY(scope.exception()))
scope.clearException();
if (constructorFunctionName.isNull() || constructorFunctionName == "Object") {
String tableClassName = object->methodTable(vm)->className(object, vm);
if (!tableClassName.isNull() && tableClassName != "Object")
return tableClassName;
String classInfoName = object->classInfo(vm)->className;
if (!classInfoName.isNull())
return classInfoName;
if (constructorFunctionName.isNull())
return "Object"_s;
}
return constructorFunctionName;
}
bool JSObject::getOwnPropertySlotByIndex(JSObject* thisObject, JSGlobalObject* globalObject, unsigned i, PropertySlot& slot)
{
VM& vm = globalObject->vm();
if (i > MAX_ARRAY_INDEX)
return thisObject->methodTable(vm)->getOwnPropertySlot(thisObject, globalObject, Identifier::from(vm, i), slot);
switch (thisObject->indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
case ALL_UNDECIDED_INDEXING_TYPES:
break;
case ALL_INT32_INDEXING_TYPES:
case ALL_CONTIGUOUS_INDEXING_TYPES: {
Butterfly* butterfly = thisObject->butterfly();
if (i >= butterfly->vectorLength())
return false;
JSValue value = butterfly->contiguous().at(thisObject, i).get();
if (value) {
slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::None), value);
return true;
}
return false;
}
case ALL_DOUBLE_INDEXING_TYPES: {
Butterfly* butterfly = thisObject->butterfly();
if (i >= butterfly->vectorLength())
return false;
double value = butterfly->contiguousDouble().at(thisObject, i);
if (value == value) {
slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::None), JSValue(JSValue::EncodeAsDouble, value));
return true;
}
return false;
}
case ALL_ARRAY_STORAGE_INDEXING_TYPES: {
ArrayStorage* storage = thisObject->m_butterfly->arrayStorage();
if (i >= storage->length())
return false;
if (i < storage->vectorLength()) {
JSValue value = storage->m_vector[i].get();
if (value) {
slot.setValue(thisObject, static_cast<unsigned>(PropertyAttribute::None), value);
return true;
}
} else if (SparseArrayValueMap* map = storage->m_sparseMap.get()) {
SparseArrayValueMap::iterator it = map->find(i);
if (it != map->notFound()) {
it->value.get(thisObject, slot);
return true;
}
}
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
break;
}
return false;
}
#if ASSERT_ENABLED
bool JSObject::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
{
return getOwnPropertySlotImpl(object, globalObject, propertyName, slot);
}
void JSObject::doPutPropertySecurityCheck(JSObject*, JSGlobalObject*, PropertyName, PutPropertySlot&)
{
}
#endif // ASSERT_ENABLED
bool ordinarySetSlow(JSGlobalObject* globalObject, JSObject* object, PropertyName propertyName, JSValue value, JSValue receiver, bool shouldThrow)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* current = object;
PropertyDescriptor ownDescriptor;
while (true) {
if (current->type() == ProxyObjectType) {
auto* proxy = jsCast<ProxyObject*>(current);
PutPropertySlot slot(receiver, shouldThrow);
RELEASE_AND_RETURN(scope, proxy->ProxyObject::put(proxy, globalObject, propertyName, value, slot));
}
bool ownDescriptorFound = current->getOwnPropertyDescriptor(globalObject, propertyName, ownDescriptor);
RETURN_IF_EXCEPTION(scope, false);
if (!ownDescriptorFound) {
JSValue prototype = current->getPrototype(vm, globalObject);
RETURN_IF_EXCEPTION(scope, false);
if (!prototype.isNull()) {
current = asObject(prototype);
continue;
}
ownDescriptor = PropertyDescriptor(jsUndefined(), static_cast<unsigned>(PropertyAttribute::None));
}
break;
}
if (ownDescriptor.isDataDescriptor()) {
if (!ownDescriptor.writable())
return typeError(globalObject, scope, shouldThrow, ReadonlyPropertyWriteError);
if (!receiver.isObject())
return typeError(globalObject, scope, shouldThrow, ReadonlyPropertyWriteError);
JSObject* receiverObject = asObject(receiver);
PropertyDescriptor existingDescriptor;
bool existingDescriptorFound = receiverObject->getOwnPropertyDescriptor(globalObject, propertyName, existingDescriptor);
RETURN_IF_EXCEPTION(scope, false);
if (existingDescriptorFound) {
if (existingDescriptor.isAccessorDescriptor())
return typeError(globalObject, scope, shouldThrow, ReadonlyPropertyWriteError);
if (!existingDescriptor.writable())
return typeError(globalObject, scope, shouldThrow, ReadonlyPropertyWriteError);
PropertyDescriptor valueDescriptor;
valueDescriptor.setValue(value);
RELEASE_AND_RETURN(scope, receiverObject->methodTable(vm)->defineOwnProperty(receiverObject, globalObject, propertyName, valueDescriptor, shouldThrow));
}
RELEASE_AND_RETURN(scope, receiverObject->methodTable(vm)->defineOwnProperty(receiverObject, globalObject, propertyName, PropertyDescriptor(value, static_cast<unsigned>(PropertyAttribute::None)), shouldThrow));
}
ASSERT(ownDescriptor.isAccessorDescriptor());
JSValue setter = ownDescriptor.setter();
if (!setter.isObject())
return typeError(globalObject, scope, shouldThrow, ReadonlyPropertyWriteError);
JSObject* setterObject = asObject(setter);
MarkedArgumentBuffer args;
args.append(value);
ASSERT(!args.hasOverflowed());
auto callData = getCallData(vm, setterObject);
scope.release();
call(globalObject, setterObject, callData, receiver, args);
return true;
}
bool JSObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
return putInlineForJSObject(cell, globalObject, propertyName, value, slot);
}
bool JSObject::putInlineSlow(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
ASSERT(!isThisValueAltered(slot, this));
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* obj = this;
for (;;) {
Structure* structure = obj->structure(vm);
if (UNLIKELY(structure->typeInfo().hasPutPropertySecurityCheck())) {
obj->methodTable(vm)->doPutPropertySecurityCheck(obj, globalObject, propertyName, slot);
RETURN_IF_EXCEPTION(scope, false);
}
unsigned attributes;
PropertyOffset offset = structure->get(vm, propertyName, attributes);
if (isValidOffset(offset)) {
if (attributes & PropertyAttribute::ReadOnly) {
ASSERT(this->prototypeChainMayInterceptStoreTo(vm, propertyName) || obj == this);
return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
}
JSValue gs = obj->getDirect(offset);
if (gs.isGetterSetter()) {
if (!this->structure(vm)->isUncacheableDictionary())
slot.setCacheableSetter(obj, offset);
bool result = callSetter(globalObject, slot.thisValue(), gs, value, slot.isStrictMode() ? ECMAMode::strict() : ECMAMode::sloppy());
RETURN_IF_EXCEPTION(scope, false);
return result;
}
if (gs.isCustomGetterSetter()) {
bool isAccessor = attributes & PropertyAttribute::CustomAccessor;
auto setter = jsCast<CustomGetterSetter*>(gs.asCell())->setter();
if (isAccessor)
slot.setCustomAccessor(obj, setter);
else
slot.setCustomValue(obj, setter);
auto result = callCustomSetter(globalObject, setter, isAccessor, obj, slot.thisValue(), value);
RETURN_IF_EXCEPTION(scope, false);
if (result != TriState::Indeterminate)
return result == TriState::True;
}
ASSERT(!(attributes & PropertyAttribute::Accessor));
break;
}
if (!obj->staticPropertiesReified(vm)) {
if (obj->classInfo(vm)->hasStaticSetterOrReadonlyProperties()) {
if (auto entry = obj->findPropertyHashEntry(vm, propertyName))
RELEASE_AND_RETURN(scope, putEntry(globalObject, entry->table->classForThis, entry->value, obj, this, propertyName, value, slot));
}
}
if (obj->type() == ProxyObjectType) {
auto* proxy = jsCast<ProxyObject*>(obj);
RELEASE_AND_RETURN(scope, proxy->ProxyObject::put(proxy, globalObject, propertyName, value, slot));
}
JSValue prototype = obj->getPrototype(vm, globalObject);
RETURN_IF_EXCEPTION(scope, false);
if (prototype.isNull())
break;
obj = asObject(prototype);
}
if (!putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot))
return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
return true;
}
bool JSObject::putByIndex(JSCell* cell, JSGlobalObject* globalObject, unsigned propertyName, JSValue value, bool shouldThrow)
{
VM& vm = globalObject->vm();
JSObject* thisObject = jsCast<JSObject*>(cell);
if (propertyName > MAX_ARRAY_INDEX) {
PutPropertySlot slot(cell, shouldThrow);
return thisObject->methodTable(vm)->put(thisObject, globalObject, Identifier::from(vm, propertyName), value, slot);
}
thisObject->ensureWritable(vm);
switch (thisObject->indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
break;
case ALL_UNDECIDED_INDEXING_TYPES: {
thisObject->convertUndecidedForValue(vm, value);
return putByIndex(cell, globalObject, propertyName, value, shouldThrow);
}
case ALL_INT32_INDEXING_TYPES: {
if (!value.isInt32()) {
thisObject->convertInt32ForValue(vm, value);
return putByIndex(cell, globalObject, propertyName, value, shouldThrow);
}
FALLTHROUGH;
}
case ALL_CONTIGUOUS_INDEXING_TYPES: {
Butterfly* butterfly = thisObject->butterfly();
if (propertyName >= butterfly->vectorLength())
break;
butterfly->contiguous().at(thisObject, propertyName).setWithoutWriteBarrier(value);
if (propertyName >= butterfly->publicLength())
butterfly->setPublicLength(propertyName + 1);
vm.heap.writeBarrier(thisObject, value);
return true;
}
case ALL_DOUBLE_INDEXING_TYPES: {
if (!value.isNumber()) {
thisObject->convertDoubleToContiguous(vm);
return putByIndex(cell, globalObject, propertyName, value, shouldThrow);
}
double valueAsDouble = value.asNumber();
if (valueAsDouble != valueAsDouble) {
thisObject->convertDoubleToContiguous(vm);
return putByIndex(cell, globalObject, propertyName, value, shouldThrow);
}
Butterfly* butterfly = thisObject->butterfly();
if (propertyName >= butterfly->vectorLength())
break;
butterfly->contiguousDouble().at(thisObject, propertyName) = valueAsDouble;
if (propertyName >= butterfly->publicLength())
butterfly->setPublicLength(propertyName + 1);
return true;
}
case NonArrayWithArrayStorage:
case ArrayWithArrayStorage: {
ArrayStorage* storage = thisObject->m_butterfly->arrayStorage();
if (propertyName >= storage->vectorLength())
break;
WriteBarrier<Unknown>& valueSlot = storage->m_vector[propertyName];
unsigned length = storage->length();
if (propertyName >= length) {
length = propertyName + 1;
storage->setLength(length);
++storage->m_numValuesInVector;
} else if (!valueSlot)
++storage->m_numValuesInVector;
valueSlot.set(vm, thisObject, value);
return true;
}
case NonArrayWithSlowPutArrayStorage:
case ArrayWithSlowPutArrayStorage: {
ArrayStorage* storage = thisObject->m_butterfly->arrayStorage();
if (propertyName >= storage->vectorLength())
break;
WriteBarrier<Unknown>& valueSlot = storage->m_vector[propertyName];
unsigned length = storage->length();
auto scope = DECLARE_THROW_SCOPE(vm);
if (propertyName >= length) {
bool putResult = false;
bool result = thisObject->attemptToInterceptPutByIndexOnHole(globalObject, propertyName, value, shouldThrow, putResult);
RETURN_IF_EXCEPTION(scope, false);
if (result)
return putResult;
length = propertyName + 1;
storage->setLength(length);
++storage->m_numValuesInVector;
} else if (!valueSlot) {
bool putResult = false;
bool result = thisObject->attemptToInterceptPutByIndexOnHole(globalObject, propertyName, value, shouldThrow, putResult);
RETURN_IF_EXCEPTION(scope, false);
if (result)
return putResult;
++storage->m_numValuesInVector;
}
valueSlot.set(vm, thisObject, value);
return true;
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
return thisObject->putByIndexBeyondVectorLength(globalObject, propertyName, value, shouldThrow);
}
ArrayStorage* JSObject::enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(VM& vm, ArrayStorage* storage)
{
SparseArrayValueMap* map = storage->m_sparseMap.get();
if (!map)
map = allocateSparseIndexMap(vm);
if (map->sparseMode())
return storage;
map->setSparseMode();
unsigned usedVectorLength = std::min(storage->length(), storage->vectorLength());
for (unsigned i = 0; i < usedVectorLength; ++i) {
JSValue value = storage->m_vector[i].get();
if (value)
map->add(this, i).iterator->value.forceSet(vm, map, value, 0);
}
DeferGC deferGC(vm.heap);
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);
setButterfly(vm, newButterfly);
return newButterfly->arrayStorage();
}
void JSObject::enterDictionaryIndexingMode(VM& vm)
{
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:
if (ArrayStorage* storage = ensureArrayStorageSlow(vm))
enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, storage);
break;
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, m_butterfly->arrayStorage());
break;
default:
break;
}
}
void JSObject::notifyPresenceOfIndexedAccessors(VM& vm)
{
if (mayInterceptIndexedAccesses(vm))
return;
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), TransitionKind::AddIndexedAccessors));
if (!mayBePrototype())
return;
globalObject(vm)->haveABadTime(vm);
}
Butterfly* JSObject::createInitialIndexedStorage(VM& vm, unsigned length)
{
ASSERT(length <= MAX_STORAGE_VECTOR_LENGTH);
IndexingType oldType = indexingType();
ASSERT_UNUSED(oldType, !hasIndexedProperties(oldType));
ASSERT(!needsSlowPutIndexing(vm));
ASSERT(!indexingShouldBeSparse(vm));
Structure* structure = this->structure(vm);
unsigned propertyCapacity = structure->outOfLineCapacity();
unsigned vectorLength = Butterfly::optimalContiguousVectorLength(propertyCapacity, length);
Butterfly* newButterfly = Butterfly::createOrGrowArrayRight(
butterfly(), vm, this, structure, propertyCapacity, false, 0,
sizeof(EncodedJSValue) * vectorLength);
newButterfly->setPublicLength(length);
newButterfly->setVectorLength(vectorLength);
return newButterfly;
}
Butterfly* JSObject::createInitialUndecided(VM& vm, unsigned length)
{
DeferGC deferGC(vm.heap);
Butterfly* newButterfly = createInitialIndexedStorage(vm, length);
StructureID oldStructureID = this->structureID();
Structure* oldStructure = vm.getStructure(oldStructureID);
Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, TransitionKind::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);
for (unsigned i = newButterfly->vectorLength(); i--;)
newButterfly->contiguous().at(this, i).setWithoutWriteBarrier(JSValue());
StructureID oldStructureID = this->structureID();
Structure* oldStructure = vm.getStructure(oldStructureID);
Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, TransitionKind::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);
for (unsigned i = newButterfly->vectorLength(); i--;)
newButterfly->contiguousDouble().at(this, i) = PNaN;
StructureID oldStructureID = this->structureID();
Structure* oldStructure = vm.getStructure(oldStructureID);
Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, TransitionKind::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);
for (unsigned i = newButterfly->vectorLength(); i--;)
newButterfly->contiguous().at(this, i).setWithoutWriteBarrier(JSValue());
StructureID oldStructureID = this->structureID();
Structure* oldStructure = vm.getStructure(oldStructureID);
Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, TransitionKind::AllocateContiguous);
nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly);
setStructure(vm, newStructure);
return newButterfly->contiguous();
}
Butterfly* JSObject::createArrayStorageButterfly(VM& vm, JSObject* intendedOwner, Structure* structure, unsigned length, unsigned vectorLength, Butterfly* oldButterfly)
{
Butterfly* newButterfly = Butterfly::createOrGrowArrayRight(
oldButterfly, vm, intendedOwner, structure, structure->outOfLineCapacity(), false, 0,
ArrayStorage::sizeFor(vectorLength));
RELEASE_ASSERT(newButterfly);
ArrayStorage* result = newButterfly->arrayStorage();
result->setLength(length);
result->setVectorLength(vectorLength);
result->m_sparseMap.clear();
result->m_numValuesInVector = 0;
result->m_indexBias = 0;
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, butterfly());
ArrayStorage* result = newButterfly->arrayStorage();
Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, suggestedArrayStorageTransition(vm));
nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly);
setStructure(vm, newStructure);
return result;
}
ArrayStorage* JSObject::createInitialArrayStorage(VM& vm)
{
return createArrayStorage(
vm, 0, ArrayStorage::optimalVectorLength(0, structure(vm)->outOfLineCapacity(), 0));
}
ContiguousJSValues JSObject::convertUndecidedToInt32(VM& vm)
{
ASSERT(hasUndecided(indexingType()));
Butterfly* butterfly = this->butterfly();
for (unsigned i = butterfly->vectorLength(); i--;)
butterfly->contiguous().at(this, i).setWithoutWriteBarrier(JSValue());
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), TransitionKind::AllocateInt32));
return m_butterfly->contiguousInt32();
}
ContiguousDoubles JSObject::convertUndecidedToDouble(VM& vm)
{
ASSERT(hasUndecided(indexingType()));
Butterfly* butterfly = m_butterfly.get();
for (unsigned i = butterfly->vectorLength(); i--;)
butterfly->contiguousDouble().at(this, i) = PNaN;
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), TransitionKind::AllocateDouble));
return m_butterfly->contiguousDouble();
}
ContiguousJSValues JSObject::convertUndecidedToContiguous(VM& vm)
{
ASSERT(hasUndecided(indexingType()));
Butterfly* butterfly = m_butterfly.get();
for (unsigned i = butterfly->vectorLength(); i--;)
butterfly->contiguous().at(this, i).setWithoutWriteBarrier(JSValue());
WTF::storeStoreFence();
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), TransitionKind::AllocateContiguous));
return m_butterfly->contiguous();
}
ArrayStorage* JSObject::constructConvertedArrayStorageWithoutCopyingElements(VM& vm, unsigned neededLength)
{
Structure* structure = this->structure(vm);
unsigned publicLength = m_butterfly->publicLength();
unsigned propertyCapacity = structure->outOfLineCapacity();
Butterfly* newButterfly = Butterfly::createUninitialized(vm, this, 0, propertyCapacity, true, ArrayStorage::sizeFor(neededLength));
gcSafeMemcpy(
static_cast<JSValue*>(newButterfly->base(0, propertyCapacity)),
static_cast<JSValue*>(m_butterfly->base(0, propertyCapacity)),
propertyCapacity * sizeof(EncodedJSValue));
ArrayStorage* newStorage = newButterfly->arrayStorage();
newStorage->setVectorLength(neededLength);
newStorage->setLength(publicLength);
newStorage->m_sparseMap.clear();
newStorage->m_indexBias = 0;
newStorage->m_numValuesInVector = 0;
return newStorage;
}
ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm, TransitionKind transition)
{
DeferGC deferGC(vm.heap);
ASSERT(hasUndecided(indexingType()));
unsigned vectorLength = m_butterfly->vectorLength();
ArrayStorage* storage = constructConvertedArrayStorageWithoutCopyingElements(vm, vectorLength);
for (unsigned i = vectorLength; i--;)
storage->m_vector[i].setWithoutWriteBarrier(JSValue());
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)
{
return convertUndecidedToArrayStorage(vm, suggestedArrayStorageTransition(vm));
}
ContiguousDoubles JSObject::convertInt32ToDouble(VM& vm)
{
ASSERT(hasInt32(indexingType()));
ASSERT(!isCopyOnWrite(indexingMode()));
Butterfly* butterfly = m_butterfly.get();
for (unsigned i = butterfly->vectorLength(); i--;) {
WriteBarrier<Unknown>* current = &butterfly->contiguous().atUnsafe(i);
double* currentAsDouble = bitwise_cast<double*>(current);
JSValue v = current->get();
if (!v.isInt32()) {
*currentAsDouble = PNaN;
continue;
}
*currentAsDouble = v.asInt32();
}
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), TransitionKind::AllocateDouble));
return m_butterfly->contiguousDouble();
}
ContiguousJSValues JSObject::convertInt32ToContiguous(VM& vm)
{
ASSERT(hasInt32(indexingType()));
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), TransitionKind::AllocateContiguous));
return m_butterfly->contiguous();
}
ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm, TransitionKind transition)
{
DeferGC deferGC(vm.heap);
ASSERT(hasInt32(indexingType()));
unsigned vectorLength = m_butterfly->vectorLength();
ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, vectorLength);
Butterfly* butterfly = m_butterfly.get();
for (unsigned i = 0; i < vectorLength; i++) {
JSValue v = butterfly->contiguous().at(this, i).get();
newStorage->m_vector[i].setWithoutWriteBarrier(v);
if (v)
newStorage->m_numValuesInVector++;
}
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)
{
return convertInt32ToArrayStorage(vm, suggestedArrayStorageTransition(vm));
}
ContiguousJSValues JSObject::convertDoubleToContiguous(VM& vm)
{
ASSERT(hasDouble(indexingType()));
ASSERT(!isCopyOnWrite(indexingMode()));
Butterfly* butterfly = m_butterfly.get();
for (unsigned i = butterfly->vectorLength(); i--;) {
double* current = &butterfly->contiguousDouble().atUnsafe(i);
WriteBarrier<Unknown>* currentAsValue = bitwise_cast<WriteBarrier<Unknown>*>(current);
double value = *current;
if (value != value) {
currentAsValue->clear();
continue;
}
JSValue v = JSValue(JSValue::EncodeAsDouble, value);
currentAsValue->setWithoutWriteBarrier(v);
}
WTF::storeStoreFence();
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), TransitionKind::AllocateContiguous));
return m_butterfly->contiguous();
}
ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm, TransitionKind transition)
{
DeferGC deferGC(vm.heap);
ASSERT(hasDouble(indexingType()));
unsigned vectorLength = m_butterfly->vectorLength();
ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, vectorLength);
Butterfly* butterfly = m_butterfly.get();
for (unsigned i = 0; i < vectorLength; i++) {
double value = butterfly->contiguousDouble().at(this, i);
if (value != value) {
newStorage->m_vector[i].clear();
continue;
}
newStorage->m_vector[i].setWithoutWriteBarrier(JSValue(JSValue::EncodeAsDouble, value));
newStorage->m_numValuesInVector++;
}
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)
{
return convertDoubleToArrayStorage(vm, suggestedArrayStorageTransition(vm));
}
ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm, TransitionKind transition)
{
DeferGC deferGC(vm.heap);
ASSERT(hasContiguous(indexingType()));
unsigned vectorLength = m_butterfly->vectorLength();
ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, vectorLength);
Butterfly* butterfly = m_butterfly.get();
for (unsigned i = 0; i < vectorLength; i++) {
JSValue v = butterfly->contiguous().at(this, i).get();
newStorage->m_vector[i].setWithoutWriteBarrier(v);
if (v)
newStorage->m_numValuesInVector++;
}
ASSERT(newStorage->butterfly() != butterfly);
StructureID oldStructureID = this->structureID();
Structure* oldStructure = vm.getStructure(oldStructureID);
Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, transition);
if (isX86() || vm.heap.mutatorShouldBeFenced())
WTF::storeStoreFence();
nukeStructureAndSetButterfly(vm, oldStructureID, newStorage->butterfly());
setStructure(vm, newStructure);
return newStorage;
}
ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm)
{
return convertContiguousToArrayStorage(vm, suggestedArrayStorageTransition(vm));
}
void JSObject::convertUndecidedForValue(VM& vm, JSValue value)
{
IndexingType type = indexingTypeForValue(value);
if (type == Int32Shape) {
convertUndecidedToInt32(vm);
return;
}
if (type == DoubleShape) {
convertUndecidedToDouble(vm);
return;
}
ASSERT(type == ContiguousShape);
convertUndecidedToContiguous(vm);
}
void JSObject::createInitialForValueAndSet(VM& vm, unsigned index, JSValue value)
{
if (value.isInt32()) {
createInitialInt32(vm, index + 1).at(this, index).set(vm, this, value);
return;
}
if (value.isDouble()) {
double doubleValue = value.asNumber();
if (doubleValue == doubleValue) {
createInitialDouble(vm, index + 1).at(this, index) = doubleValue;
return;
}
}
createInitialContiguous(vm, index + 1).at(this, index).set(vm, this, value);
}
void JSObject::convertInt32ForValue(VM& vm, JSValue value)
{
ASSERT(!value.isInt32());
if (value.isDouble() && !std::isnan(value.asDouble())) {
convertInt32ToDouble(vm);
return;
}
convertInt32ToContiguous(vm);
}
void JSObject::convertFromCopyOnWrite(VM& vm)
{
ASSERT(isCopyOnWrite(indexingMode()));
ASSERT(structure(vm)->indexingMode() == indexingMode());
const bool hasIndexingHeader = true;
Butterfly* oldButterfly = butterfly();
size_t propertyCapacity = 0;
unsigned newVectorLength = Butterfly::optimalContiguousVectorLength(propertyCapacity, std::min(oldButterfly->vectorLength() * 2, MAX_STORAGE_VECTOR_LENGTH));
Butterfly* newButterfly = Butterfly::createUninitialized(vm, this, 0, propertyCapacity, hasIndexingHeader, newVectorLength * sizeof(JSValue));
gcSafeMemcpy(newButterfly->propertyStorage(), oldButterfly->propertyStorage(), oldButterfly->vectorLength() * sizeof(JSValue) + sizeof(IndexingHeader));
WTF::storeStoreFence();
TransitionKind transition = ([&] () {
switch (indexingType()) {
case ArrayWithInt32:
return TransitionKind::AllocateInt32;
case ArrayWithDouble:
return TransitionKind::AllocateDouble;
case ArrayWithContiguous:
return TransitionKind::AllocateContiguous;
default:
RELEASE_ASSERT_NOT_REACHED();
return TransitionKind::AllocateContiguous;
}
})();
StructureID oldStructureID = structureID();
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), transition);
nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly);
setStructure(vm, newStructure);
}
void JSObject::setIndexQuicklyToUndecided(VM& vm, unsigned index, JSValue value)
{
ASSERT(index < m_butterfly->publicLength());
ASSERT(index < m_butterfly->vectorLength());
convertUndecidedForValue(vm, value);
setIndexQuickly(vm, index, value);
}
void JSObject::convertInt32ToDoubleOrContiguousWhilePerformingSetIndex(VM& vm, unsigned index, JSValue value)
{
ASSERT(!value.isInt32());
convertInt32ForValue(vm, value);
setIndexQuickly(vm, index, value);
}
void JSObject::convertDoubleToContiguousWhilePerformingSetIndex(VM& vm, unsigned index, JSValue value)
{
ASSERT(!value.isNumber() || value.asNumber() != value.asNumber());
convertDoubleToContiguous(vm);
setIndexQuickly(vm, index, value);
}
ContiguousJSValues JSObject::tryMakeWritableInt32Slow(VM& vm)
{
ASSERT(inherits(vm, info()));
if (isCopyOnWrite(indexingMode())) {
if (leastUpperBoundOfIndexingTypes(indexingType() & IndexingShapeMask, Int32Shape) == Int32Shape) {
ASSERT(hasInt32(indexingMode()));
convertFromCopyOnWrite(vm);
return butterfly()->contiguousInt32();
}
return ContiguousJSValues();
}
if (structure(vm)->hijacksIndexingHeader())
return ContiguousJSValues();
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
if (UNLIKELY(indexingShouldBeSparse(vm) || needsSlowPutIndexing(vm)))
return ContiguousJSValues();
return createInitialInt32(vm, 0);
case ALL_UNDECIDED_INDEXING_TYPES:
return convertUndecidedToInt32(vm);
case ALL_DOUBLE_INDEXING_TYPES:
case ALL_CONTIGUOUS_INDEXING_TYPES:
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
return ContiguousJSValues();
default:
CRASH();
return ContiguousJSValues();
}
}
ContiguousDoubles JSObject::tryMakeWritableDoubleSlow(VM& vm)
{
ASSERT(inherits(vm, info()));
if (isCopyOnWrite(indexingMode())) {
if (leastUpperBoundOfIndexingTypes(indexingType() & IndexingShapeMask, DoubleShape) == DoubleShape) {
convertFromCopyOnWrite(vm);
if (hasDouble(indexingMode()))
return butterfly()->contiguousDouble();
ASSERT(hasInt32(indexingMode()));
} else
return ContiguousDoubles();
}
if (structure(vm)->hijacksIndexingHeader())
return ContiguousDoubles();
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
if (UNLIKELY(indexingShouldBeSparse(vm) || needsSlowPutIndexing(vm)))
return ContiguousDoubles();
return createInitialDouble(vm, 0);
case ALL_UNDECIDED_INDEXING_TYPES:
return convertUndecidedToDouble(vm);
case ALL_INT32_INDEXING_TYPES:
return convertInt32ToDouble(vm);
case ALL_CONTIGUOUS_INDEXING_TYPES:
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
return ContiguousDoubles();
default:
CRASH();
return ContiguousDoubles();
}
}
ContiguousJSValues JSObject::tryMakeWritableContiguousSlow(VM& vm)
{
ASSERT(inherits(vm, info()));
if (isCopyOnWrite(indexingMode())) {
if (leastUpperBoundOfIndexingTypes(indexingType() & IndexingShapeMask, ContiguousShape) == ContiguousShape) {
convertFromCopyOnWrite(vm);
if (hasContiguous(indexingMode()))
return butterfly()->contiguous();
ASSERT(hasInt32(indexingMode()) || hasDouble(indexingMode()));
} else
return ContiguousJSValues();
}
if (structure(vm)->hijacksIndexingHeader())
return ContiguousJSValues();
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
if (UNLIKELY(indexingShouldBeSparse(vm) || needsSlowPutIndexing(vm)))
return ContiguousJSValues();
return createInitialContiguous(vm, 0);
case ALL_UNDECIDED_INDEXING_TYPES:
return convertUndecidedToContiguous(vm);
case ALL_INT32_INDEXING_TYPES:
return convertInt32ToContiguous(vm);
case ALL_DOUBLE_INDEXING_TYPES:
return convertDoubleToContiguous(vm);
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
return ContiguousJSValues();
default:
CRASH();
return ContiguousJSValues();
}
}
ArrayStorage* JSObject::ensureArrayStorageSlow(VM& vm)
{
ASSERT(inherits(vm, info()));
if (structure(vm)->hijacksIndexingHeader())
return nullptr;
ensureWritable(vm);
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
if (UNLIKELY(indexingShouldBeSparse(vm)))
return ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm);
return createInitialArrayStorage(vm);
case ALL_UNDECIDED_INDEXING_TYPES:
ASSERT(!indexingShouldBeSparse(vm));
ASSERT(!needsSlowPutIndexing(vm));
return convertUndecidedToArrayStorage(vm);
case ALL_INT32_INDEXING_TYPES:
ASSERT(!indexingShouldBeSparse(vm));
ASSERT(!needsSlowPutIndexing(vm));
return convertInt32ToArrayStorage(vm);
case ALL_DOUBLE_INDEXING_TYPES:
ASSERT(!indexingShouldBeSparse(vm));
ASSERT(!needsSlowPutIndexing(vm));
return convertDoubleToArrayStorage(vm);
case ALL_CONTIGUOUS_INDEXING_TYPES:
ASSERT(!indexingShouldBeSparse(vm));
ASSERT(!needsSlowPutIndexing(vm));
return convertContiguousToArrayStorage(vm);
default:
RELEASE_ASSERT_NOT_REACHED();
return nullptr;
}
}
ArrayStorage* JSObject::ensureArrayStorageExistsAndEnterDictionaryIndexingMode(VM& vm)
{
ensureWritable(vm);
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES: {
createArrayStorage(vm, 0, 0);
SparseArrayValueMap* map = allocateSparseIndexMap(vm);
map->setSparseMode();
return arrayStorage();
}
case ALL_UNDECIDED_INDEXING_TYPES:
return enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, convertUndecidedToArrayStorage(vm));
case ALL_INT32_INDEXING_TYPES:
return enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, convertInt32ToArrayStorage(vm));
case ALL_DOUBLE_INDEXING_TYPES:
return enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, convertDoubleToArrayStorage(vm));
case ALL_CONTIGUOUS_INDEXING_TYPES:
return enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, convertContiguousToArrayStorage(vm));
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
return enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, m_butterfly->arrayStorage());
default:
CRASH();
return nullptr;
}
}
void JSObject::switchToSlowPutArrayStorage(VM& vm)
{
ensureWritable(vm);
switch (indexingType()) {
case ArrayClass:
ensureArrayStorage(vm);
RELEASE_ASSERT(hasAnyArrayStorage(indexingType()));
if (hasSlowPutArrayStorage(indexingType()))
return;
switchToSlowPutArrayStorage(vm);
break;
case ALL_UNDECIDED_INDEXING_TYPES:
convertUndecidedToArrayStorage(vm, TransitionKind::AllocateSlowPutArrayStorage);
break;
case ALL_INT32_INDEXING_TYPES:
convertInt32ToArrayStorage(vm, TransitionKind::AllocateSlowPutArrayStorage);
break;
case ALL_DOUBLE_INDEXING_TYPES:
convertDoubleToArrayStorage(vm, TransitionKind::AllocateSlowPutArrayStorage);
break;
case ALL_CONTIGUOUS_INDEXING_TYPES:
convertContiguousToArrayStorage(vm, TransitionKind::AllocateSlowPutArrayStorage);
break;
case NonArrayWithArrayStorage:
case ArrayWithArrayStorage: {
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), TransitionKind::SwitchToSlowPutArrayStorage);
setStructure(vm, newStructure);
break;
}
default:
CRASH();
break;
}
}
void JSObject::setPrototypeDirect(VM& vm, JSValue prototype)
{
ASSERT(prototype);
if (prototype.isObject())
asObject(prototype)->didBecomePrototype();
if (structure(vm)->hasMonoProto()) {
DeferredStructureTransitionWatchpointFire deferred(vm, structure(vm));
Structure* newStructure = Structure::changePrototypeTransition(vm, structure(vm), prototype, deferred);
setStructure(vm, newStructure);
} else
putDirect(vm, knownPolyProtoOffset, prototype);
if (!anyObjectInChainMayInterceptIndexedAccesses(vm))
return;
if (mayBePrototype()) {
structure(vm)->globalObject()->haveABadTime(vm);
return;
}
if (!hasIndexedProperties(indexingType()))
return;
if (shouldUseSlowPut(indexingType()))
return;
switchToSlowPutArrayStorage(vm);
}
bool JSObject::setPrototypeWithCycleCheck(VM& vm, JSGlobalObject* globalObject, JSValue prototype, bool shouldThrowIfCantSet)
{
auto scope = DECLARE_THROW_SCOPE(vm);
if (this->structure(vm)->isImmutablePrototypeExoticObject()) {
if (this->getPrototype(vm, globalObject) == prototype)
return true;
return typeError(globalObject, scope, shouldThrowIfCantSet, "Cannot set prototype of immutable prototype object"_s);
}
ASSERT(methodTable(vm)->toThis(this, globalObject, ECMAMode::sloppy()) == this);
if (this->getPrototypeDirect(vm) == prototype)
return true;
bool isExtensible = this->isExtensible(globalObject);
RETURN_IF_EXCEPTION(scope, false);
if (!isExtensible)
return typeError(globalObject, scope, shouldThrowIfCantSet, ReadonlyPropertyWriteError);
JSValue nextPrototype = prototype;
while (nextPrototype && nextPrototype.isObject()) {
if (nextPrototype == this)
return typeError(globalObject, scope, shouldThrowIfCantSet, "cyclic __proto__ value"_s);
if (UNLIKELY(asObject(nextPrototype)->type() == ProxyObjectType))
break; nextPrototype = asObject(nextPrototype)->getPrototypeDirect(vm);
}
setPrototypeDirect(vm, prototype);
return true;
}
bool JSObject::setPrototype(JSObject* object, JSGlobalObject* globalObject, JSValue prototype, bool shouldThrowIfCantSet)
{
return object->setPrototypeWithCycleCheck(globalObject->vm(), globalObject, prototype, shouldThrowIfCantSet);
}
JSValue JSObject::getPrototype(JSObject* object, JSGlobalObject* globalObject)
{
return object->getPrototypeDirect(globalObject->vm());
}
bool JSObject::setPrototype(VM& vm, JSGlobalObject* globalObject, JSValue prototype, bool shouldThrowIfCantSet)
{
return methodTable(vm)->setPrototype(this, globalObject, prototype, shouldThrowIfCantSet);
}
bool JSObject::putGetter(JSGlobalObject* globalObject, PropertyName propertyName, JSValue getter, unsigned attributes)
{
PropertyDescriptor descriptor;
descriptor.setGetter(getter);
ASSERT(attributes & PropertyAttribute::Accessor);
if (!(attributes & PropertyAttribute::ReadOnly))
descriptor.setConfigurable(true);
if (!(attributes & PropertyAttribute::DontEnum))
descriptor.setEnumerable(true);
return defineOwnProperty(this, globalObject, propertyName, descriptor, true);
}
bool JSObject::putSetter(JSGlobalObject* globalObject, PropertyName propertyName, JSValue setter, unsigned attributes)
{
PropertyDescriptor descriptor;
descriptor.setSetter(setter);
ASSERT(attributes & PropertyAttribute::Accessor);
if (!(attributes & PropertyAttribute::ReadOnly))
descriptor.setConfigurable(true);
if (!(attributes & PropertyAttribute::DontEnum))
descriptor.setEnumerable(true);
return defineOwnProperty(this, globalObject, propertyName, descriptor, true);
}
bool JSObject::putDirectAccessor(JSGlobalObject* globalObject, PropertyName propertyName, GetterSetter* accessor, unsigned attributes)
{
ASSERT(attributes & PropertyAttribute::Accessor);
if (Optional<uint32_t> index = parseIndex(propertyName))
return putDirectIndex(globalObject, index.value(), accessor, attributes, PutDirectIndexLikePutDirect);
return putDirectNonIndexAccessor(globalObject->vm(), propertyName, accessor, attributes);
}
bool JSObject::putDirectCustomAccessor(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes)
{
ASSERT(!parseIndex(propertyName));
ASSERT(value.isCustomGetterSetter());
if (!(attributes & PropertyAttribute::CustomAccessor))
attributes |= PropertyAttribute::CustomValue;
PutPropertySlot slot(this);
bool result = putDirectInternal<PutModeDefineOwnProperty>(vm, propertyName, value, attributes, slot);
ASSERT(slot.type() == PutPropertySlot::NewProperty);
Structure* structure = this->structure(vm);
if (attributes & PropertyAttribute::ReadOnly)
structure->setContainsReadOnlyProperties();
structure->setHasCustomGetterSetterPropertiesWithProtoCheck(propertyName == vm.propertyNames->underscoreProto);
return result;
}
bool JSObject::putDirectNonIndexAccessor(VM& vm, PropertyName propertyName, GetterSetter* accessor, unsigned attributes)
{
ASSERT(attributes & PropertyAttribute::Accessor);
PutPropertySlot slot(this);
bool result = putDirectInternal<PutModeDefineOwnProperty>(vm, propertyName, accessor, attributes, slot);
Structure* structure = this->structure(vm);
if (attributes & PropertyAttribute::ReadOnly)
structure->setContainsReadOnlyProperties();
structure->setHasGetterSetterPropertiesWithProtoCheck(propertyName == vm.propertyNames->underscoreProto);
return result;
}
void JSObject::putDirectNonIndexAccessorWithoutTransition(VM& vm, PropertyName propertyName, GetterSetter* accessor, unsigned attributes)
{
ASSERT(attributes & PropertyAttribute::Accessor);
StructureID structureID = this->structureID();
Structure* structure = vm.heap.structureIDTable().get(structureID);
PropertyOffset offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
putDirect(vm, offset, accessor);
if (attributes & PropertyAttribute::ReadOnly)
structure->setContainsReadOnlyProperties();
structure->setHasGetterSetterPropertiesWithProtoCheck(propertyName == vm.propertyNames->underscoreProto);
}
bool JSObject::hasProperty(JSGlobalObject* globalObject, PropertyName propertyName) const
{
PropertySlot slot(this, PropertySlot::InternalMethodType::HasProperty);
return const_cast<JSObject*>(this)->getPropertySlot(globalObject, propertyName, slot);
}
bool JSObject::hasProperty(JSGlobalObject* globalObject, unsigned propertyName) const
{
PropertySlot slot(this, PropertySlot::InternalMethodType::HasProperty);
return const_cast<JSObject*>(this)->getPropertySlot(globalObject, propertyName, slot);
}
bool JSObject::hasProperty(JSGlobalObject* globalObject, uint64_t propertyName) const
{
if (LIKELY(propertyName <= MAX_ARRAY_INDEX))
return hasProperty(globalObject, static_cast<uint32_t>(propertyName));
ASSERT(propertyName <= maxSafeInteger());
return hasProperty(globalObject, Identifier::from(globalObject->vm(), propertyName));
}
bool JSObject::hasEnumerableProperty(JSGlobalObject* globalObject, PropertyName propertyName) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
bool hasProperty = const_cast<JSObject*>(this)->getPropertySlot(globalObject, propertyName, slot);
RETURN_IF_EXCEPTION(scope, false);
if (!hasProperty)
return false;
return !(slot.attributes() & PropertyAttribute::DontEnum) || (slot.slotBase() && slot.slotBase()->structure(vm)->typeInfo().getOwnPropertySlotMayBeWrongAboutDontEnum());
}
bool JSObject::hasEnumerableProperty(JSGlobalObject* globalObject, unsigned propertyName) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
bool hasProperty = const_cast<JSObject*>(this)->getPropertySlot(globalObject, propertyName, slot);
RETURN_IF_EXCEPTION(scope, false);
if (!hasProperty)
return false;
return !(slot.attributes() & PropertyAttribute::DontEnum) || (slot.slotBase() && slot.slotBase()->structure(vm)->typeInfo().getOwnPropertySlotMayBeWrongAboutDontEnum());
}
bool JSObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, DeletePropertySlot& slot)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
VM& vm = globalObject->vm();
if (Optional<uint32_t> index = parseIndex(propertyName))
return thisObject->methodTable(vm)->deletePropertyByIndex(thisObject, globalObject, index.value());
unsigned attributes;
if (!thisObject->staticPropertiesReified(vm)) {
if (auto entry = thisObject->findPropertyHashEntry(vm, propertyName)) {
if (entry->value->attributes() & PropertyAttribute::DontDelete && vm.deletePropertyMode() != VM::DeletePropertyMode::IgnoreConfigurable) {
ASSERT(!isValidOffset(thisObject->structure(vm)->get(vm, propertyName, attributes)) || attributes & PropertyAttribute::DontDelete);
return false;
}
thisObject->reifyAllStaticProperties(globalObject);
}
}
Structure* structure = thisObject->structure(vm);
bool propertyIsPresent = isValidOffset(structure->get(vm, propertyName, attributes));
if (propertyIsPresent) {
if (attributes & PropertyAttribute::DontDelete && vm.deletePropertyMode() != VM::DeletePropertyMode::IgnoreConfigurable) {
slot.setNonconfigurable();
return false;
}
DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
PropertyOffset offset = invalidOffset;
if (structure->isUncacheableDictionary())
offset = structure->removePropertyWithoutTransition(vm, propertyName, [] (const GCSafeConcurrentJSLocker&, PropertyOffset, PropertyOffset) { });
else {
structure = Structure::removePropertyTransition(vm, structure, propertyName, offset, &deferredWatchpointFire);
slot.setHit(offset);
ASSERT(structure->outOfLineCapacity() || !thisObject->structure(vm)->outOfLineCapacity());
thisObject->setStructure(vm, structure);
}
ASSERT(!isValidOffset(structure->get(vm, propertyName, attributes)));
if (offset != invalidOffset)
thisObject->locationForOffset(offset)->clear();
} else
slot.setConfigurableMiss();
return true;
}
bool JSObject::deletePropertyByIndex(JSCell* cell, JSGlobalObject* globalObject, unsigned i)
{
VM& vm = globalObject->vm();
JSObject* thisObject = jsCast<JSObject*>(cell);
if (i > MAX_ARRAY_INDEX)
return JSCell::deleteProperty(thisObject, globalObject, Identifier::from(vm, i));
switch (thisObject->indexingMode()) {
case ALL_BLANK_INDEXING_TYPES:
case ALL_UNDECIDED_INDEXING_TYPES:
return true;
case CopyOnWriteArrayWithInt32:
case CopyOnWriteArrayWithContiguous: {
Butterfly* butterfly = thisObject->butterfly();
if (i >= butterfly->vectorLength())
return true;
thisObject->convertFromCopyOnWrite(vm);
FALLTHROUGH;
}
case ALL_WRITABLE_INT32_INDEXING_TYPES:
case ALL_WRITABLE_CONTIGUOUS_INDEXING_TYPES: {
Butterfly* butterfly = thisObject->butterfly();
if (i >= butterfly->vectorLength())
return true;
butterfly->contiguous().at(thisObject, i).clear();
return true;
}
case CopyOnWriteArrayWithDouble: {
Butterfly* butterfly = thisObject->butterfly();
if (i >= butterfly->vectorLength())
return true;
thisObject->convertFromCopyOnWrite(vm);
FALLTHROUGH;
}
case ALL_WRITABLE_DOUBLE_INDEXING_TYPES: {
Butterfly* butterfly = thisObject->butterfly();
if (i >= butterfly->vectorLength())
return true;
butterfly->contiguousDouble().at(thisObject, i) = PNaN;
return true;
}
case ALL_ARRAY_STORAGE_INDEXING_TYPES: {
ArrayStorage* storage = thisObject->m_butterfly->arrayStorage();
if (i < storage->vectorLength()) {
WriteBarrier<Unknown>& valueSlot = storage->m_vector[i];
if (valueSlot) {
valueSlot.clear();
--storage->m_numValuesInVector;
}
} else if (SparseArrayValueMap* map = storage->m_sparseMap.get()) {
SparseArrayValueMap::iterator it = map->find(i);
if (it != map->notFound()) {
if (it->value.attributes() & PropertyAttribute::DontDelete)
return false;
map->remove(it);
}
}
return true;
}
default:
RELEASE_ASSERT_NOT_REACHED();
return false;
}
}
template<CachedSpecialPropertyKey key>
static ALWAYS_INLINE JSValue callToPrimitiveFunction(JSGlobalObject* globalObject, const JSObject* object, PropertyName propertyName, PreferredPrimitiveType hint)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue function = object->structure(vm)->cachedSpecialProperty(key);
if (!function) {
PropertySlot slot(object, PropertySlot::InternalMethodType::Get);
constexpr bool debugNullStructure = key == CachedSpecialPropertyKey::ToPrimitive;
bool hasProperty = const_cast<JSObject*>(object)->getPropertySlot<debugNullStructure>(globalObject, propertyName, slot);
RETURN_IF_EXCEPTION(scope, scope.exception());
function = hasProperty ? slot.getValue(globalObject, propertyName) : jsUndefined();
RETURN_IF_EXCEPTION(scope, scope.exception());
object->structure(vm)->cacheSpecialProperty(globalObject, vm, function, key, slot);
RETURN_IF_EXCEPTION(scope, scope.exception());
}
if (function.isUndefinedOrNull())
return JSValue();
if constexpr (key == CachedSpecialPropertyKey::ToString) {
if (function == globalObject->objectProtoToStringFunction()) {
if (auto result = object->structure(vm)->cachedSpecialProperty(CachedSpecialPropertyKey::ToStringTag))
return result;
}
}
auto callData = getCallData(vm, function);
if (callData.type == CallData::Type::None) {
if constexpr (key == CachedSpecialPropertyKey::ToPrimitive)
throwTypeError(globalObject, scope, "Symbol.toPrimitive is not a function, undefined, or null"_s);
return scope.exception();
}
MarkedArgumentBuffer callArgs;
if constexpr (key == CachedSpecialPropertyKey::ToPrimitive) {
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);
} else {
UNUSED_PARAM(hint);
}
ASSERT(!callArgs.hasOverflowed());
JSValue result = call(globalObject, function, callData, const_cast<JSObject*>(object), callArgs);
RETURN_IF_EXCEPTION(scope, scope.exception());
ASSERT(!result.isGetterSetter());
if (result.isObject()) {
if constexpr (key == CachedSpecialPropertyKey::ToPrimitive)
return throwTypeError(globalObject, scope, "Symbol.toPrimitive returned an object"_s);
return JSValue();
}
return result;
}
JSValue JSObject::ordinaryToPrimitive(JSGlobalObject* globalObject, PreferredPrimitiveType hint) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
for (const JSObject* object = this; object; object = object->structure(vm)->storedPrototypeObject(object))
object->structure(vm)->startWatchingInternalPropertiesIfNecessary(vm);
JSValue value;
if (hint == PreferString) {
value = callToPrimitiveFunction<CachedSpecialPropertyKey::ToString>(globalObject, this, vm.propertyNames->toString, hint);
EXCEPTION_ASSERT(!scope.exception() || scope.exception() == value.asCell());
if (value)
return value;
value = callToPrimitiveFunction<CachedSpecialPropertyKey::ValueOf>(globalObject, this, vm.propertyNames->valueOf, hint);
EXCEPTION_ASSERT(!scope.exception() || scope.exception() == value.asCell());
if (value)
return value;
} else {
value = callToPrimitiveFunction<CachedSpecialPropertyKey::ValueOf>(globalObject, this, vm.propertyNames->valueOf, hint);
EXCEPTION_ASSERT(!scope.exception() || scope.exception() == value.asCell());
if (value)
return value;
value = callToPrimitiveFunction<CachedSpecialPropertyKey::ToString>(globalObject, this, vm.propertyNames->toString, hint);
EXCEPTION_ASSERT(!scope.exception() || scope.exception() == value.asCell());
if (value)
return value;
}
scope.assertNoException();
return throwTypeError(globalObject, scope, "No default value"_s);
}
JSValue JSObject::defaultValue(const JSObject* object, JSGlobalObject* globalObject, PreferredPrimitiveType hint)
{
return object->ordinaryToPrimitive(globalObject, hint);
}
JSValue JSObject::toPrimitive(JSGlobalObject* globalObject, PreferredPrimitiveType preferredType) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue value = callToPrimitiveFunction<CachedSpecialPropertyKey::ToPrimitive>(globalObject, this, vm.propertyNames->toPrimitiveSymbol, preferredType);
RETURN_IF_EXCEPTION(scope, { });
if (value)
return value;
RELEASE_AND_RETURN(scope, this->methodTable(vm)->defaultValue(this, globalObject, preferredType));
}
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->classForThis, *table, this, propertyName, slot))
return true;
}
}
return false;
}
Optional<Structure::PropertyHashEntry> JSObject::findPropertyHashEntry(VM& vm, PropertyName propertyName) const
{
return structure(vm)->findPropertyHashEntry(propertyName);
}
bool JSObject::hasInstance(JSGlobalObject* globalObject, JSValue value, JSValue hasInstanceValue)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!hasInstanceValue.isUndefinedOrNull() && hasInstanceValue != globalObject->functionProtoHasInstanceSymbolFunction()) {
auto callData = JSC::getCallData(vm, hasInstanceValue);
if (callData.type == CallData::Type::None) {
throwException(globalObject, scope, createInvalidInstanceofParameterErrorHasInstanceValueNotFunction(globalObject, this));
return false;
}
MarkedArgumentBuffer args;
args.append(value);
ASSERT(!args.hasOverflowed());
JSValue result = call(globalObject, hasInstanceValue, callData, this, args);
RETURN_IF_EXCEPTION(scope, false);
return result.toBoolean(globalObject);
}
TypeInfo info = structure(vm)->typeInfo();
if (info.implementsDefaultHasInstance()) {
JSValue prototype = get(globalObject, vm.propertyNames->prototype);
RETURN_IF_EXCEPTION(scope, false);
RELEASE_AND_RETURN(scope, defaultHasInstance(globalObject, value, prototype));
}
if (info.implementsHasInstance()) {
if (UNLIKELY(!vm.isSafeToRecurseSoft())) {
throwStackOverflowError(globalObject, scope);
return false;
}
RELEASE_AND_RETURN(scope, methodTable(vm)->customHasInstance(this, globalObject, value));
}
throwException(globalObject, scope, createInvalidInstanceofParameterErrorNotFunction(globalObject, this));
return false;
}
bool JSObject::hasInstance(JSGlobalObject* globalObject, JSValue value)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue hasInstanceValue = get(globalObject, vm.propertyNames->hasInstanceSymbol);
RETURN_IF_EXCEPTION(scope, false);
RELEASE_AND_RETURN(scope, hasInstance(globalObject, value, hasInstanceValue));
}
bool JSObject::defaultHasInstance(JSGlobalObject* globalObject, JSValue value, JSValue proto)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!value.isObject())
return false;
if (!proto.isObject()) {
throwTypeError(globalObject, scope, "instanceof called on an object with an invalid prototype property."_s);
return false;
}
JSObject* object = asObject(value);
while (true) {
JSValue objectValue = object->getPrototype(vm, globalObject);
RETURN_IF_EXCEPTION(scope, false);
if (!objectValue.isObject())
return false;
object = asObject(objectValue);
if (proto == object)
return true;
}
ASSERT_NOT_REACHED();
}
JSC_DEFINE_HOST_FUNCTION(objectPrivateFuncInstanceOf, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
JSValue value = callFrame->uncheckedArgument(0);
JSValue proto = callFrame->uncheckedArgument(1);
return JSValue::encode(jsBoolean(JSObject::defaultHasInstance(globalObject, value, proto)));
}
void JSObject::getPropertyNames(JSGlobalObject* globalObject, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* object = this;
unsigned prototypeCount = 0;
while (true) {
object->methodTable(vm)->getOwnPropertyNames(object, globalObject, propertyNames, mode);
RETURN_IF_EXCEPTION(scope, void());
JSValue prototype = object->getPrototype(vm, globalObject);
RETURN_IF_EXCEPTION(scope, void());
if (prototype.isNull())
break;
if (UNLIKELY(++prototypeCount > maximumPrototypeChainDepth)) {
throwStackOverflowError(globalObject, scope);
return;
}
object = asObject(prototype);
}
}
void JSObject::getOwnPropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode)
{
object->getOwnIndexedPropertyNames(globalObject, propertyNames, mode);
object->getOwnNonIndexPropertyNames(globalObject, propertyNames, mode);
}
void JSObject::getOwnSpecialPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArray&, DontEnumPropertiesMode)
{
}
void JSObject::getOwnIndexedPropertyNames(JSGlobalObject*, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode)
{
JSObject* object = this;
if (propertyNames.includeStringProperties()) {
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().at(object, i))
continue;
propertyNames.add(i);
}
break;
}
case ALL_DOUBLE_INDEXING_TYPES: {
Butterfly* butterfly = object->butterfly();
unsigned usedLength = butterfly->publicLength();
for (unsigned i = 0; i < usedLength; ++i) {
double value = butterfly->contiguousDouble().at(object, i);
if (value != value)
continue;
propertyNames.add(i);
}
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(i);
}
if (SparseArrayValueMap* map = storage->m_sparseMap.get()) {
Vector<unsigned, 0, UnsafeVectorOverflow> keys;
keys.reserveInitialCapacity(map->size());
SparseArrayValueMap::const_iterator end = map->end();
for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it) {
if (mode == DontEnumPropertiesMode::Include || !(it->value.attributes() & PropertyAttribute::DontEnum))
keys.uncheckedAppend(static_cast<unsigned>(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();
}
}
}
void JSObject::getOwnNonIndexPropertyNames(JSGlobalObject* globalObject, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
methodTable(vm)->getOwnSpecialPropertyNames(this, globalObject, propertyNames, mode);
RETURN_IF_EXCEPTION(scope, void());
getNonReifiedStaticPropertyNames(vm, propertyNames, mode);
structure(vm)->getPropertyNamesFromStructure(vm, propertyNames, mode);
scope.assertNoException();
}
double JSObject::toNumber(JSGlobalObject* globalObject) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue primitive = toPrimitive(globalObject, PreferNumber);
RETURN_IF_EXCEPTION(scope, 0.0); RELEASE_AND_RETURN(scope, primitive.toNumber(globalObject));
}
JSString* JSObject::toString(JSGlobalObject* globalObject) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue primitive = toPrimitive(globalObject, PreferString);
RETURN_IF_EXCEPTION(scope, jsEmptyString(vm));
RELEASE_AND_RETURN(scope, primitive.toString(globalObject));
}
JSValue JSObject::toThis(JSCell* cell, JSGlobalObject*, ECMAMode)
{
return jsCast<JSObject*>(cell);
}
void JSObject::seal(VM& vm)
{
if (isSealed(vm))
return;
enterDictionaryIndexingMode(vm);
setStructure(vm, Structure::sealTransition(vm, structure(vm)));
}
void JSObject::freeze(VM& vm)
{
if (isFrozen(vm))
return;
enterDictionaryIndexingMode(vm);
setStructure(vm, Structure::freezeTransition(vm, structure(vm)));
}
bool JSObject::preventExtensions(JSObject* object, JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
if (!object->isStructureExtensible(vm)) {
return true;
}
object->enterDictionaryIndexingMode(vm);
object->setStructure(vm, Structure::preventExtensionsTransition(vm, object->structure(vm)));
return true;
}
bool JSObject::isExtensible(JSObject* obj, JSGlobalObject* globalObject)
{
return obj->isStructureExtensible(globalObject->vm());
}
bool JSObject::isExtensible(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
return methodTable(vm)->isExtensible(this, globalObject);
}
void JSObject::reifyAllStaticProperties(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
ASSERT(!staticPropertiesReified(vm));
if (!TypeInfo::hasStaticPropertyTable(inlineTypeFlags())) {
structure(vm)->setStaticPropertiesReified(true);
return;
}
if (!structure(vm)->isDictionary())
setStructure(vm, Structure::toCacheableDictionaryTransition(vm, structure(vm)));
for (const ClassInfo* info = classInfo(vm); info; info = info->parentClass) {
const HashTable* hashTable = info->staticPropHashTable;
if (!hashTable)
continue;
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, hashTable->classForThis, key, value, *this);
}
}
structure(vm)->setStaticPropertiesReified(true);
}
NEVER_INLINE void JSObject::fillGetterPropertySlot(VM& vm, PropertySlot& slot, JSCell* getterSetter, unsigned attributes, PropertyOffset offset)
{
if (structure(vm)->isUncacheableDictionary()) {
slot.setGetterSlot(this, attributes, jsCast<GetterSetter*>(getterSetter));
return;
}
slot.setCacheableGetterSlot(this, attributes, jsCast<GetterSetter*>(getterSetter), offset);
}
static bool putIndexedDescriptor(JSGlobalObject* globalObject, SparseArrayValueMap* map, SparseArrayEntry* entryInMap, const PropertyDescriptor& descriptor, PropertyDescriptor& oldDescriptor)
{
VM& vm = globalObject->vm();
if (descriptor.isDataDescriptor()) {
unsigned attributes = descriptor.attributesOverridingCurrent(oldDescriptor) & ~PropertyAttribute::Accessor;
if (descriptor.value())
entryInMap->forceSet(vm, map, descriptor.value(), attributes);
else if (oldDescriptor.isAccessorDescriptor())
entryInMap->forceSet(vm, map, jsUndefined(), attributes);
else
entryInMap->forceSet(attributes);
return true;
}
if (descriptor.isAccessorDescriptor()) {
JSObject* getter = nullptr;
if (descriptor.getterPresent())
getter = descriptor.getterObject();
else if (oldDescriptor.isAccessorDescriptor())
getter = oldDescriptor.getterObject();
JSObject* setter = nullptr;
if (descriptor.setterPresent())
setter = descriptor.setterObject();
else if (oldDescriptor.isAccessorDescriptor())
setter = oldDescriptor.setterObject();
GetterSetter* accessor = GetterSetter::create(vm, globalObject, getter, setter);
entryInMap->forceSet(vm, map, accessor, descriptor.attributesOverridingCurrent(oldDescriptor) & ~PropertyAttribute::ReadOnly);
return true;
}
ASSERT(descriptor.isGenericDescriptor());
entryInMap->forceSet(descriptor.attributesOverridingCurrent(oldDescriptor));
return true;
}
ALWAYS_INLINE static bool canDoFastPutDirectIndex(VM& vm, JSObject* object)
{
if (TypeInfo::isArgumentsType(object->type()))
return true;
if (object->inSparseIndexingMode())
return false;
return (isJSArray(object) && !isCopyOnWrite(object->indexingMode()))
|| jsDynamicCast<JSFinalObject*>(vm, object);
}
bool JSObject::defineOwnIndexedProperty(JSGlobalObject* globalObject, unsigned index, const PropertyDescriptor& descriptor, bool throwException)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(index <= MAX_ARRAY_INDEX);
ensureWritable(vm);
if (!inSparseIndexingMode()) {
const PropertyDescriptor emptyAttributesDescriptor(jsUndefined(), static_cast<unsigned>(PropertyAttribute::None));
ASSERT(emptyAttributesDescriptor.attributes() == static_cast<unsigned>(PropertyAttribute::None));
#if ASSERT_ENABLED
if (canGetIndexQuickly(index) && canDoFastPutDirectIndex(vm, this)) {
PropertyDescriptor currentDescriptor;
ASSERT(getOwnPropertyDescriptor(globalObject, Identifier::from(vm, index), currentDescriptor));
scope.assertNoException();
ASSERT(currentDescriptor.attributes() == emptyAttributesDescriptor.attributes());
}
#endif
if (descriptor.value()
&& (!descriptor.attributes() || (canGetIndexQuickly(index) && !descriptor.attributesOverridingCurrent(emptyAttributesDescriptor)))
&& canDoFastPutDirectIndex(vm, this)) {
ASSERT(!descriptor.isAccessorDescriptor());
RELEASE_AND_RETURN(scope, putDirectIndex(globalObject, index, descriptor.value(), 0, throwException ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow));
}
ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm);
}
if (descriptor.attributes() & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor))
notifyPresenceOfIndexedAccessors(vm);
SparseArrayValueMap* map = m_butterfly->arrayStorage()->m_sparseMap.get();
RELEASE_ASSERT(map);
SparseArrayValueMap::AddResult result = map->add(this, index);
SparseArrayEntry* entryInMap = &result.iterator->value;
if (result.isNewEntry) {
if (!isStructureExtensible(vm)) {
map->remove(result.iterator);
return typeError(globalObject, scope, throwException, NonExtensibleObjectPropertyDefineError);
}
PropertyDescriptor defaults(jsUndefined(), PropertyAttribute::DontDelete | PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly);
putIndexedDescriptor(globalObject, map, entryInMap, descriptor, defaults);
Butterfly* butterfly = m_butterfly.get();
if (index >= butterfly->arrayStorage()->length())
butterfly->arrayStorage()->setLength(index + 1);
return true;
}
PropertyDescriptor current;
entryInMap->get(current);
bool isEmptyOrEqual = descriptor.isEmpty() || descriptor.equalTo(globalObject, current);
RETURN_IF_EXCEPTION(scope, false);
if (isEmptyOrEqual)
return true;
if (!current.configurable()) {
if (descriptor.configurablePresent() && descriptor.configurable())
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeConfigurabilityError);
if (descriptor.enumerablePresent() && current.enumerable() != descriptor.enumerable())
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeEnumerabilityError);
}
if (!descriptor.isGenericDescriptor()) {
if (current.isDataDescriptor() != descriptor.isDataDescriptor()) {
if (!current.configurable())
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeAccessMechanismError);
} else if (current.isDataDescriptor() && descriptor.isDataDescriptor()) {
if (!current.configurable() && !current.writable()) {
if (descriptor.writable())
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeWritabilityError);
if (descriptor.value()) {
bool isSame = sameValue(globalObject, descriptor.value(), current.value());
RETURN_IF_EXCEPTION(scope, false);
if (!isSame)
return typeError(globalObject, scope, throwException, ReadonlyPropertyChangeError);
}
}
} else {
ASSERT(current.isAccessorDescriptor() && current.getterPresent() && current.setterPresent());
if (!current.configurable()) {
if (descriptor.setterPresent() && descriptor.setter() != current.setter())
return typeError(globalObject, scope, throwException, "Attempting to change the setter of an unconfigurable property."_s);
if (descriptor.getterPresent() && descriptor.getter() != current.getter())
return typeError(globalObject, scope, throwException, "Attempting to change the getter of an unconfigurable property."_s);
}
}
}
putIndexedDescriptor(globalObject, map, entryInMap, descriptor, current);
return true;
}
SparseArrayValueMap* JSObject::allocateSparseIndexMap(VM& vm)
{
SparseArrayValueMap* result = SparseArrayValueMap::create(vm);
arrayStorage()->m_sparseMap.set(vm, this, result);
return result;
}
void JSObject::deallocateSparseIndexMap()
{
if (ArrayStorage* arrayStorage = arrayStorageOrNull())
arrayStorage->m_sparseMap.clear();
}
bool JSObject::attemptToInterceptPutByIndexOnHoleForPrototype(JSGlobalObject* globalObject, JSValue thisValue, unsigned i, JSValue value, bool shouldThrow, bool& putResult)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
for (JSObject* current = this; ;) {
ArrayStorage* storage = current->arrayStorageOrNull();
if (storage && storage->m_sparseMap) {
SparseArrayValueMap::iterator iter = storage->m_sparseMap->find(i);
if (iter != storage->m_sparseMap->notFound() && (iter->value.attributes() & (PropertyAttribute::Accessor | PropertyAttribute::ReadOnly))) {
scope.release();
putResult = iter->value.put(globalObject, thisValue, storage->m_sparseMap.get(), value, shouldThrow);
return true;
}
}
if (current->type() == ProxyObjectType) {
scope.release();
auto* proxy = jsCast<ProxyObject*>(current);
putResult = proxy->putByIndexCommon(globalObject, thisValue, i, value, shouldThrow);
return true;
}
JSValue prototypeValue = current->getPrototype(vm, globalObject);
RETURN_IF_EXCEPTION(scope, false);
if (prototypeValue.isNull())
return false;
current = asObject(prototypeValue);
}
}
bool JSObject::attemptToInterceptPutByIndexOnHole(JSGlobalObject* globalObject, unsigned i, JSValue value, bool shouldThrow, bool& putResult)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue prototypeValue = getPrototype(vm, globalObject);
RETURN_IF_EXCEPTION(scope, false);
if (prototypeValue.isNull())
return false;
RELEASE_AND_RETURN(scope, asObject(prototypeValue)->attemptToInterceptPutByIndexOnHoleForPrototype(globalObject, this, i, value, shouldThrow, putResult));
}
template<IndexingType indexingShape>
bool JSObject::putByIndexBeyondVectorLengthWithoutAttributes(JSGlobalObject* globalObject, unsigned i, JSValue value)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!isCopyOnWrite(indexingMode()));
ASSERT((indexingType() & IndexingShapeMask) == indexingShape);
ASSERT(!indexingShouldBeSparse(vm));
Butterfly* butterfly = m_butterfly.get();
ASSERT(i >= butterfly->vectorLength());
if (i > MAX_STORAGE_VECTOR_INDEX
|| (i >= MIN_SPARSE_ARRAY_INDEX && !isDenseEnoughForVector(i, countElements<indexingShape>(butterfly)))
|| indexIsSufficientlyBeyondLengthForSparseMap(i, butterfly->vectorLength())) {
ASSERT(i <= MAX_ARRAY_INDEX);
ensureArrayStorageSlow(vm);
SparseArrayValueMap* map = allocateSparseIndexMap(vm);
bool result = map->putEntry(globalObject, this, i, value, false);
RETURN_IF_EXCEPTION(scope, false);
ASSERT(i >= arrayStorage()->length());
arrayStorage()->setLength(i + 1);
return result;
}
if (!ensureLength(vm, i + 1)) {
throwOutOfMemoryError(globalObject, scope);
return false;
}
butterfly = m_butterfly.get();
RELEASE_ASSERT(i < butterfly->vectorLength());
switch (indexingShape) {
case Int32Shape:
ASSERT(value.isInt32());
butterfly->contiguous().at(this, i).setWithoutWriteBarrier(value);
return true;
case DoubleShape: {
ASSERT(value.isNumber());
double valueAsDouble = value.asNumber();
ASSERT(valueAsDouble == valueAsDouble);
butterfly->contiguousDouble().at(this, i) = valueAsDouble;
return true;
}
case ContiguousShape:
butterfly->contiguous().at(this, i).set(vm, this, value);
return true;
default:
CRASH();
return false;
}
}
template bool JSObject::putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(JSGlobalObject*, unsigned, JSValue);
template bool JSObject::putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(JSGlobalObject*, unsigned, JSValue);
template bool JSObject::putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(JSGlobalObject*, unsigned, JSValue);
bool JSObject::putByIndexBeyondVectorLengthWithArrayStorage(JSGlobalObject* globalObject, unsigned i, JSValue value, bool shouldThrow, ArrayStorage* storage)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(!isCopyOnWrite(indexingMode()));
ASSERT(i <= MAX_ARRAY_INDEX);
ASSERT(i >= storage->vectorLength());
SparseArrayValueMap* map = storage->m_sparseMap.get();
if (LIKELY(!map)) {
ASSERT(isStructureExtensible(vm));
if (i >= storage->length())
storage->setLength(i + 1);
if (LIKELY(!indexIsSufficientlyBeyondLengthForSparseMap(i, storage->vectorLength())
&& isDenseEnoughForVector(i, storage->m_numValuesInVector)
&& increaseVectorLength(vm, i + 1))) {
storage = arrayStorage();
storage->m_vector[i].set(vm, this, value);
++storage->m_numValuesInVector;
return true;
}
map = allocateSparseIndexMap(vm);
RELEASE_AND_RETURN(scope, map->putEntry(globalObject, this, i, value, shouldThrow));
}
unsigned length = storage->length();
if (i >= length) {
if (map->lengthIsReadOnly() || !isStructureExtensible(vm))
return typeError(globalObject, scope, shouldThrow, ReadonlyPropertyWriteError);
length = i + 1;
storage->setLength(length);
}
unsigned numValuesInArray = storage->m_numValuesInVector + map->size();
if (map->sparseMode() || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(vm, length))
RELEASE_AND_RETURN(scope, map->putEntry(globalObject, this, i, value, shouldThrow));
storage = arrayStorage();
storage->m_numValuesInVector = numValuesInArray;
WriteBarrier<Unknown>* vector = storage->m_vector;
SparseArrayValueMap::const_iterator end = map->end();
for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it)
vector[it->key].set(vm, this, it->value.getNonSparseMode());
deallocateSparseIndexMap();
WriteBarrier<Unknown>& valueSlot = vector[i];
if (!valueSlot)
++storage->m_numValuesInVector;
valueSlot.set(vm, this, value);
return true;
}
bool JSObject::putByIndexBeyondVectorLength(JSGlobalObject* globalObject, unsigned i, JSValue value, bool shouldThrow)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!isCopyOnWrite(indexingMode()));
ASSERT(i <= MAX_ARRAY_INDEX);
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES: {
if (indexingShouldBeSparse(vm)) {
RELEASE_AND_RETURN(scope, putByIndexBeyondVectorLengthWithArrayStorage(
globalObject, i, value, shouldThrow,
ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm)));
}
if (indexIsSufficientlyBeyondLengthForSparseMap(i, 0) || i >= MIN_SPARSE_ARRAY_INDEX) {
RELEASE_AND_RETURN(scope, putByIndexBeyondVectorLengthWithArrayStorage(globalObject, i, value, shouldThrow, createArrayStorage(vm, 0, 0)));
}
if (needsSlowPutIndexing(vm)) {
createArrayStorage(vm, i + 1, getNewVectorLength(vm, 0, 0, 0, i + 1));
RELEASE_AND_RETURN(scope, putByIndex(this, globalObject, i, value, shouldThrow));
}
createInitialForValueAndSet(vm, i, value);
return true;
}
case ALL_UNDECIDED_INDEXING_TYPES: {
CRASH();
break;
}
case ALL_INT32_INDEXING_TYPES:
RELEASE_AND_RETURN(scope, putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(globalObject, i, value));
case ALL_DOUBLE_INDEXING_TYPES:
RELEASE_AND_RETURN(scope, putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(globalObject, i, value));
case ALL_CONTIGUOUS_INDEXING_TYPES:
RELEASE_AND_RETURN(scope, putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(globalObject, i, value));
case NonArrayWithSlowPutArrayStorage:
case ArrayWithSlowPutArrayStorage: {
SparseArrayValueMap* map = arrayStorage()->m_sparseMap.get();
bool putResult = false;
if (!(map && map->contains(i))) {
bool result = attemptToInterceptPutByIndexOnHole(globalObject, i, value, shouldThrow, putResult);
RETURN_IF_EXCEPTION(scope, false);
if (result)
return putResult;
}
FALLTHROUGH;
}
case NonArrayWithArrayStorage:
case ArrayWithArrayStorage:
RELEASE_AND_RETURN(scope, putByIndexBeyondVectorLengthWithArrayStorage(globalObject, i, value, shouldThrow, arrayStorage()));
default:
RELEASE_ASSERT_NOT_REACHED();
}
return false;
}
bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(JSGlobalObject* globalObject, unsigned i, JSValue value, unsigned attributes, PutDirectIndexMode mode, ArrayStorage* storage)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(hasAnyArrayStorage(indexingType()));
ASSERT(arrayStorage() == storage);
ASSERT(i >= storage->vectorLength() || attributes);
ASSERT(i <= MAX_ARRAY_INDEX);
SparseArrayValueMap* map = storage->m_sparseMap.get();
if (LIKELY(!map)) {
ASSERT(isStructureExtensible(vm));
if (i >= storage->length())
storage->setLength(i + 1);
if (LIKELY(
!attributes
&& (isDenseEnoughForVector(i, storage->m_numValuesInVector))
&& !indexIsSufficientlyBeyondLengthForSparseMap(i, storage->vectorLength()))
&& increaseVectorLength(vm, i + 1)) {
storage = arrayStorage();
storage->m_vector[i].set(vm, this, value);
++storage->m_numValuesInVector;
return true;
}
map = allocateSparseIndexMap(vm);
RELEASE_AND_RETURN(scope, map->putDirect(globalObject, this, i, value, attributes, mode));
}
unsigned length = storage->length();
if (i >= length) {
if (mode != PutDirectIndexLikePutDirect) {
if (map->lengthIsReadOnly())
return typeError(globalObject, scope, mode == PutDirectIndexShouldThrow, ReadonlyPropertyWriteError);
if (!isStructureExtensible(vm))
return typeError(globalObject, scope, mode == PutDirectIndexShouldThrow, NonExtensibleObjectPropertyDefineError);
}
length = i + 1;
storage->setLength(length);
}
unsigned numValuesInArray = storage->m_numValuesInVector + map->size();
if (map->sparseMode() || attributes || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(vm, length))
RELEASE_AND_RETURN(scope, map->putDirect(globalObject, this, i, value, attributes, mode));
storage = arrayStorage();
storage->m_numValuesInVector = numValuesInArray;
WriteBarrier<Unknown>* vector = storage->m_vector;
SparseArrayValueMap::const_iterator end = map->end();
for (SparseArrayValueMap::const_iterator it = map->begin(); it != end; ++it)
vector[it->key].set(vm, this, it->value.getNonSparseMode());
deallocateSparseIndexMap();
WriteBarrier<Unknown>& valueSlot = vector[i];
if (!valueSlot)
++storage->m_numValuesInVector;
valueSlot.set(vm, this, value);
return true;
}
bool JSObject::putDirectIndexSlowOrBeyondVectorLength(JSGlobalObject* globalObject, unsigned i, JSValue value, unsigned attributes, PutDirectIndexMode mode)
{
VM& vm = globalObject->vm();
ASSERT(!value.isCustomGetterSetter());
if (!canDoFastPutDirectIndex(vm, this)) {
PropertyDescriptor descriptor;
descriptor.setDescriptor(value, attributes);
return methodTable(vm)->defineOwnProperty(this, globalObject, Identifier::from(vm, i), descriptor, mode == PutDirectIndexShouldThrow);
}
ASSERT(i <= MAX_ARRAY_INDEX);
if (attributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor))
notifyPresenceOfIndexedAccessors(vm);
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES: {
if (indexingShouldBeSparse(vm) || attributes) {
return putDirectIndexBeyondVectorLengthWithArrayStorage(
globalObject, i, value, attributes, mode,
ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
}
if (indexIsSufficientlyBeyondLengthForSparseMap(i, 0) || i >= MIN_SPARSE_ARRAY_INDEX) {
return putDirectIndexBeyondVectorLengthWithArrayStorage(
globalObject, i, value, attributes, mode, createArrayStorage(vm, 0, 0));
}
if (needsSlowPutIndexing(vm)) {
ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(vm, 0, 0, 0, i + 1));
storage->m_vector[i].set(vm, this, value);
storage->m_numValuesInVector++;
return true;
}
createInitialForValueAndSet(vm, i, value);
return true;
}
case ALL_UNDECIDED_INDEXING_TYPES: {
convertUndecidedForValue(vm, value);
return putDirectIndex(globalObject, i, value, attributes, mode);
}
case ALL_INT32_INDEXING_TYPES: {
ASSERT(!indexingShouldBeSparse(vm));
if (attributes)
return putDirectIndexBeyondVectorLengthWithArrayStorage(globalObject, i, value, attributes, mode, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
if (!value.isInt32()) {
convertInt32ForValue(vm, value);
return putDirectIndexSlowOrBeyondVectorLength(globalObject, i, value, attributes, mode);
}
putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(globalObject, i, value);
return true;
}
case ALL_DOUBLE_INDEXING_TYPES: {
ASSERT(!indexingShouldBeSparse(vm));
if (attributes)
return putDirectIndexBeyondVectorLengthWithArrayStorage(globalObject, i, value, attributes, mode, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
if (!value.isNumber()) {
convertDoubleToContiguous(vm);
return putDirectIndexSlowOrBeyondVectorLength(globalObject, i, value, attributes, mode);
}
double valueAsDouble = value.asNumber();
if (valueAsDouble != valueAsDouble) {
convertDoubleToContiguous(vm);
return putDirectIndexSlowOrBeyondVectorLength(globalObject, i, value, attributes, mode);
}
putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(globalObject, i, value);
return true;
}
case ALL_CONTIGUOUS_INDEXING_TYPES: {
ASSERT(!indexingShouldBeSparse(vm));
if (attributes)
return putDirectIndexBeyondVectorLengthWithArrayStorage(globalObject, i, value, attributes, mode, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(globalObject, i, value);
return true;
}
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
if (attributes)
return putDirectIndexBeyondVectorLengthWithArrayStorage(globalObject, i, value, attributes, mode, ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
return putDirectIndexBeyondVectorLengthWithArrayStorage(globalObject, i, value, attributes, mode, arrayStorage());
default:
RELEASE_ASSERT_NOT_REACHED();
return false;
}
}
bool JSObject::putDirectNativeIntrinsicGetter(VM& vm, JSGlobalObject* globalObject, Identifier name, NativeFunction nativeFunction, Intrinsic intrinsic, unsigned attributes)
{
JSFunction* function = JSFunction::create(vm, globalObject, 0, makeString("get ", name.string()), nativeFunction, intrinsic);
GetterSetter* accessor = GetterSetter::create(vm, globalObject, function, nullptr);
return putDirectNonIndexAccessor(vm, name, accessor, attributes);
}
void JSObject::putDirectNativeIntrinsicGetterWithoutTransition(VM& vm, JSGlobalObject* globalObject, Identifier name, NativeFunction nativeFunction, Intrinsic intrinsic, unsigned attributes)
{
JSFunction* function = JSFunction::create(vm, globalObject, 0, makeString("get ", name.string()), nativeFunction, intrinsic);
GetterSetter* accessor = GetterSetter::create(vm, globalObject, function, nullptr);
putDirectNonIndexAccessorWithoutTransition(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)
name = vm.propertyNames->anonymous.impl();
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);
}
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);
}
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::create(vm, static_cast<FunctionExecutable*>(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::create(vm, static_cast<FunctionExecutable*>(functionExecutable), globalObject);
putDirectWithoutTransition(vm, propertyName, function, attributes);
return function;
}
ALWAYS_INLINE unsigned JSObject::getNewVectorLength(VM& vm, unsigned indexBias, unsigned currentVectorLength, unsigned currentLength, unsigned desiredLength)
{
ASSERT(desiredLength <= MAX_STORAGE_VECTOR_LENGTH);
unsigned increasedLength;
unsigned maxInitLength = std::min(currentLength, 100000U);
if (desiredLength < maxInitLength)
increasedLength = maxInitLength;
else if (!currentVectorLength)
increasedLength = std::max(desiredLength, lastArraySize);
else {
increasedLength = timesThreePlusOneDividedByTwo(desiredLength);
}
ASSERT(increasedLength >= desiredLength);
lastArraySize = std::min(increasedLength, FIRST_ARRAY_STORAGE_VECTOR_GROW);
return ArrayStorage::optimalVectorLength(
indexBias, structure(vm)->outOfLineCapacity(),
std::min(increasedLength, MAX_STORAGE_VECTOR_LENGTH));
}
ALWAYS_INLINE unsigned JSObject::getNewVectorLength(VM& vm, unsigned desiredLength)
{
unsigned indexBias = 0;
unsigned vectorLength = 0;
unsigned length = 0;
if (hasIndexedProperties(indexingType())) {
if (ArrayStorage* storage = arrayStorageOrNull())
indexBias = storage->m_indexBias;
vectorLength = m_butterfly->vectorLength();
length = m_butterfly->publicLength();
}
return getNewVectorLength(vm, indexBias, vectorLength, length, desiredLength);
}
template<IndexingType indexingShape>
unsigned JSObject::countElements(Butterfly* butterfly)
{
unsigned numValues = 0;
for (unsigned i = butterfly->publicLength(); i--;) {
switch (indexingShape) {
case Int32Shape:
case ContiguousShape:
if (butterfly->contiguous().at(this, i))
numValues++;
break;
case DoubleShape: {
double value = butterfly->contiguousDouble().at(this, i);
if (value == value)
numValues++;
break;
}
default:
CRASH();
}
}
return numValues;
}
unsigned JSObject::countElements()
{
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
case ALL_UNDECIDED_INDEXING_TYPES:
return 0;
case ALL_INT32_INDEXING_TYPES:
return countElements<Int32Shape>(butterfly());
case ALL_DOUBLE_INDEXING_TYPES:
return countElements<DoubleShape>(butterfly());
case ALL_CONTIGUOUS_INDEXING_TYPES:
return countElements<ContiguousShape>(butterfly());
default:
CRASH();
return 0;
}
}
bool JSObject::increaseVectorLength(VM& vm, unsigned newLength)
{
ArrayStorage* storage = arrayStorage();
unsigned vectorLength = storage->vectorLength();
unsigned availableVectorLength = storage->availableVectorLength(structure(vm), vectorLength);
if (availableVectorLength >= newLength) {
for (unsigned i = vectorLength; i < availableVectorLength; ++i)
storage->m_vector[i].clear();
storage->setVectorLength(availableVectorLength);
return true;
}
if (newLength > MAX_STORAGE_VECTOR_LENGTH)
return false;
if (newLength >= MIN_SPARSE_ARRAY_INDEX
&& !isDenseEnoughForVector(newLength, storage->m_numValuesInVector))
return false;
unsigned indexBias = storage->m_indexBias;
ASSERT(newLength > vectorLength);
unsigned newVectorLength = getNewVectorLength(vm, newLength);
Structure* structure = this->structure(vm);
if (LIKELY(!indexBias)) {
DeferGC deferGC(vm.heap);
Butterfly* newButterfly = storage->butterfly()->growArrayRight(
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);
setButterfly(vm, newButterfly);
return true;
}
DeferGC deferGC(vm.heap);
unsigned newIndexBias = std::min(indexBias >> 1, MAX_STORAGE_VECTOR_LENGTH - newVectorLength);
Butterfly* newButterfly = storage->butterfly()->resizeArray(
vm, this,
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;
setButterfly(vm, newButterfly);
return true;
}
bool JSObject::ensureLengthSlow(VM& vm, unsigned length)
{
if (isCopyOnWrite(indexingMode())) {
convertFromCopyOnWrite(vm);
if (m_butterfly->vectorLength() >= length)
return true;
}
Butterfly* butterfly = this->butterfly();
ASSERT(length <= MAX_STORAGE_VECTOR_LENGTH);
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();
GCDeferralContext deferralContext(vm.heap);
DisallowGC disallowGC;
unsigned availableOldLength =
Butterfly::availableContiguousVectorLength(propertyCapacity, oldVectorLength);
Butterfly* newButterfly = nullptr;
if (availableOldLength >= length) {
newVectorLength = availableOldLength;
} else {
newVectorLength = Butterfly::optimalContiguousVectorLength(
propertyCapacity, std::min(length * 2, MAX_STORAGE_VECTOR_LENGTH));
butterfly = butterfly->reallocArrayRightIfPossible(
vm, deferralContext, this, structure, propertyCapacity, true,
oldVectorLength * sizeof(EncodedJSValue),
newVectorLength * sizeof(EncodedJSValue));
if (!butterfly)
return false;
newButterfly = butterfly;
}
if (hasDouble(indexingType())) {
for (unsigned i = oldVectorLength; i < newVectorLength; ++i)
butterfly->indexingPayload<double>()[i] = PNaN;
} else {
for (unsigned i = oldVectorLength; i < newVectorLength; ++i)
butterfly->indexingPayload<WriteBarrier<Unknown>>()[i].clear();
}
if (newButterfly) {
butterfly->setVectorLength(newVectorLength);
WTF::storeStoreFence();
m_butterfly.set(vm, this, newButterfly);
} else {
WTF::storeStoreFence();
butterfly->setVectorLength(newVectorLength);
}
return true;
}
void JSObject::reallocateAndShrinkButterfly(VM& vm, unsigned length)
{
ASSERT(length <= MAX_STORAGE_VECTOR_LENGTH);
ASSERT(hasContiguous(indexingType()) || hasInt32(indexingType()) || hasDouble(indexingType()) || hasUndecided(indexingType()));
ASSERT(m_butterfly->vectorLength() > length);
ASSERT(m_butterfly->publicLength() >= length);
ASSERT(!m_butterfly->indexingHeader()->preCapacity(structure(vm)));
DeferGC deferGC(vm.heap);
Butterfly* newButterfly = butterfly()->resizeArray(vm, this, structure(vm), 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);
return Butterfly::createOrGrowPropertyStorage(butterfly(), vm, this, structure(vm), oldSize, newSize);
}
static JSCustomGetterSetterFunction* getCustomGetterSetterFunctionForGetterSetter(JSGlobalObject* globalObject, PropertyName propertyName, CustomGetterSetter* getterSetter, JSCustomGetterSetterFunction::Type type)
{
VM& vm = globalObject->vm();
auto key = std::make_pair(getterSetter, (int)type);
JSCustomGetterSetterFunction* customGetterSetterFunction = vm.customGetterSetterFunctionMap.get(key);
if (!customGetterSetterFunction) {
customGetterSetterFunction = JSCustomGetterSetterFunction::create(vm, globalObject, getterSetter, type, propertyName.publicName());
vm.customGetterSetterFunctionMap.set(key, customGetterSetterFunction);
}
return customGetterSetterFunction;
}
bool JSObject::getOwnPropertyDescriptor(JSGlobalObject* globalObject, PropertyName propertyName, PropertyDescriptor& descriptor)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
bool result = methodTable(vm)->getOwnPropertySlot(this, globalObject, propertyName, slot);
EXCEPTION_ASSERT(!scope.exception() || !result);
if (!result)
return false;
if (slot.isAccessor())
descriptor.setAccessorDescriptor(slot.getterSetter(), slot.attributes());
else if (slot.attributes() & PropertyAttribute::CustomAccessor) {
CustomGetterSetter* getterSetter;
if (slot.isCustomAccessor())
getterSetter = slot.customGetterSetter();
else {
ASSERT(slot.slotBase());
JSObject* thisObject = slot.slotBase();
JSValue maybeGetterSetter = thisObject->getDirect(vm, propertyName);
if (!maybeGetterSetter) {
thisObject->reifyAllStaticProperties(globalObject);
maybeGetterSetter = thisObject->getDirect(vm, propertyName);
}
ASSERT(maybeGetterSetter);
getterSetter = jsDynamicCast<CustomGetterSetter*>(vm, maybeGetterSetter);
}
ASSERT(getterSetter);
if (!getterSetter)
return false;
descriptor.setCustomDescriptor(slot.attributes());
if (getterSetter->getter())
descriptor.setGetter(getCustomGetterSetterFunctionForGetterSetter(globalObject, propertyName, getterSetter, JSCustomGetterSetterFunction::Type::Getter));
if (getterSetter->setter())
descriptor.setSetter(getCustomGetterSetterFunctionForGetterSetter(globalObject, propertyName, getterSetter, JSCustomGetterSetterFunction::Type::Setter));
} else {
JSValue value = slot.getValue(globalObject, propertyName);
RETURN_IF_EXCEPTION(scope, false);
descriptor.setDescriptor(value, slot.attributes());
}
return true;
}
bool JSObject::putDirectMayBeIndex(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value)
{
if (Optional<uint32_t> index = parseIndex(propertyName))
return putDirectIndex(globalObject, index.value(), value);
return putDirect(globalObject->vm(), propertyName, value);
}
bool validateAndApplyPropertyDescriptor(JSGlobalObject* globalObject, JSObject* object, PropertyName propertyName, bool isExtensible,
const PropertyDescriptor& descriptor, bool isCurrentDefined, const PropertyDescriptor& current, bool throwException)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!isCurrentDefined) {
if (!isExtensible)
return typeError(globalObject, scope, throwException, NonExtensibleObjectPropertyDefineError);
if (object) {
if (descriptor.isAccessorDescriptor()) {
unsigned attributes = (descriptor.attributes() | PropertyAttribute::Accessor) & ~PropertyAttribute::ReadOnly;
object->putDirectAccessor(globalObject, propertyName, descriptor.slowGetterSetter(globalObject), attributes);
} else {
ASSERT(descriptor.isGenericDescriptor() || descriptor.isDataDescriptor());
JSValue value = descriptor.value() ? descriptor.value() : jsUndefined();
object->putDirect(vm, propertyName, value, descriptor.attributes() & ~PropertyAttribute::Accessor);
}
}
return true;
}
if (descriptor.isEmpty())
return true;
bool isEqual = current.equalTo(globalObject, descriptor);
RETURN_IF_EXCEPTION(scope, false);
if (isEqual)
return true;
if (!current.configurable()) {
if (descriptor.configurable())
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeConfigurabilityError);
if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable())
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeEnumerabilityError);
}
if (descriptor.isGenericDescriptor()) {
} else if (current.isDataDescriptor() != descriptor.isDataDescriptor()) {
if (!current.configurable())
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeAccessMechanismError);
} else if (current.isDataDescriptor() && descriptor.isDataDescriptor()) {
if (!current.configurable() && !current.writable()) {
if (descriptor.writable())
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeWritabilityError);
if (descriptor.value() && !sameValue(globalObject, current.value(), descriptor.value()))
return typeError(globalObject, scope, throwException, ReadonlyPropertyChangeError);
return true;
}
} else {
ASSERT(descriptor.isAccessorDescriptor());
if (!current.configurable()) {
if (descriptor.setterPresent() && descriptor.setter() != current.setter())
return typeError(globalObject, scope, throwException, "Attempting to change the setter of an unconfigurable property."_s);
if (descriptor.getterPresent() && descriptor.getter() != current.getter())
return typeError(globalObject, scope, throwException, "Attempting to change the getter of an unconfigurable property."_s);
if (current.attributes() & PropertyAttribute::CustomAccessor)
return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeAccessMechanismError);
return true;
}
}
if (!object)
return true;
unsigned attributes = descriptor.attributesOverridingCurrent(current);
if (descriptor.isAccessorDescriptor() || (current.isAccessorDescriptor() && !descriptor.isDataDescriptor())) {
ASSERT(attributes & PropertyAttribute::Accessor);
JSObject* getter = descriptor.getterPresent() ? descriptor.getterObject() : (current.getterPresent() ? current.getterObject() : nullptr);
JSObject* setter = descriptor.setterPresent() ? descriptor.setterObject() : (current.setterPresent() ? current.setterObject() : nullptr);
GetterSetter* getterSetter = GetterSetter::create(vm, globalObject, getter, setter);
object->putDirectAccessor(globalObject, propertyName, getterSetter, attributes & ~PropertyAttribute::ReadOnly);
} else {
ASSERT(descriptor.isGenericDescriptor() || descriptor.isDataDescriptor());
JSValue value = descriptor.value() ? descriptor.value() : (current.value() ? current.value() : jsUndefined());
object->putDirect(vm, propertyName, value, attributes & ~PropertyAttribute::Accessor);
}
return true;
}
bool JSObject::defineOwnNonIndexProperty(JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
{
VM& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
PropertyDescriptor current;
bool isCurrentDefined = getOwnPropertyDescriptor(globalObject, propertyName, current);
RETURN_IF_EXCEPTION(throwScope, false);
bool isExtensible = this->isExtensible(globalObject);
RETURN_IF_EXCEPTION(throwScope, false);
RELEASE_AND_RETURN(throwScope, validateAndApplyPropertyDescriptor(globalObject, this, propertyName, isExtensible, descriptor, isCurrentDefined, current, throwException));
}
bool JSObject::defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
{
if (Optional<uint32_t> index = parseIndex(propertyName)) {
return object->defineOwnIndexedProperty(globalObject, index.value(), descriptor, throwException);
}
return object->defineOwnNonIndexProperty(globalObject, propertyName, descriptor, throwException);
}
void JSObject::convertToDictionary(VM& vm)
{
DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure(vm));
setStructure(
vm, Structure::toCacheableDictionaryTransition(vm, structure(vm), &deferredWatchpointFire));
}
void JSObject::convertToUncacheableDictionary(VM& vm)
{
if (structure(vm)->isUncacheableDictionary())
return;
DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure(vm));
setStructure(
vm, Structure::toUncacheableDictionaryTransition(vm, structure(vm), &deferredWatchpointFire));
}
void JSObject::shiftButterflyAfterFlattening(const GCSafeConcurrentJSLocker&, VM& vm, Structure* structure, size_t outOfLineCapacityAfter)
{
Butterfly* oldButterfly = this->butterfly();
size_t preCapacity;
size_t indexingPayloadSizeInBytes;
bool hasIndexingHeader = this->hasIndexingHeader(vm);
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);
void* currentBase = oldButterfly->base(0, outOfLineCapacityAfter);
void* newBase = newButterfly->base(0, outOfLineCapacityAfter);
gcSafeMemcpy(static_cast<JSValue*>(newBase), static_cast<JSValue*>(currentBase), Butterfly::totalSize(0, outOfLineCapacityAfter, hasIndexingHeader, indexingPayloadSizeInBytes));
setButterfly(vm, newButterfly);
}
uint32_t JSObject::getEnumerableLength(JSGlobalObject* globalObject, JSObject* object)
{
VM& vm = globalObject->vm();
Structure* structure = object->structure(vm);
if (structure->holesMustForwardToPrototype(vm, object))
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().at(object, 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().at(object, i);
if (value != value)
return 0;
}
return usedLength;
}
case ALL_ARRAY_STORAGE_INDEXING_TYPES: {
ArrayStorage* storage = object->m_butterfly->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;
}
}
JSValue JSObject::getMethod(JSGlobalObject* globalObject, CallData& callData, const Identifier& ident, const String& errorMessage)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue method = get(globalObject, ident);
RETURN_IF_EXCEPTION(scope, JSValue());
if (!method.isCell()) {
if (method.isUndefinedOrNull())
return jsUndefined();
throwVMTypeError(globalObject, scope, errorMessage);
return jsUndefined();
}
callData = JSC::getCallData(vm, method);
if (callData.type == CallData::Type::None) {
throwVMTypeError(globalObject, scope, errorMessage);
return jsUndefined();
}
return method;
}
bool JSObject::anyObjectInChainMayInterceptIndexedAccesses(VM& vm) const
{
for (const JSObject* current = this; ;) {
if (current->structure(vm)->mayInterceptIndexedAccesses())
return true;
JSValue prototype = current->getPrototypeDirect(vm);
if (prototype.isNull())
return false;
current = asObject(prototype);
}
}
bool JSObject::prototypeChainMayInterceptStoreTo(VM& vm, PropertyName propertyName)
{
if (parseIndex(propertyName))
return anyObjectInChainMayInterceptIndexedAccesses(vm);
for (JSObject* current = this; ;) {
JSValue prototype = current->getPrototypeDirect(vm);
if (prototype.isNull())
return false;
current = asObject(prototype);
unsigned attributes;
PropertyOffset offset = current->structure(vm)->get(vm, propertyName, attributes);
if (!JSC::isValidOffset(offset))
continue;
if (attributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor))
return true;
return false;
}
}
bool JSObject::needsSlowPutIndexing(VM& vm) const
{
return anyObjectInChainMayInterceptIndexedAccesses(vm) || globalObject(vm)->isHavingABadTime();
}
TransitionKind JSObject::suggestedArrayStorageTransition(VM& vm) const
{
if (needsSlowPutIndexing(vm))
return TransitionKind::AllocateSlowPutArrayStorage;
return TransitionKind::AllocateArrayStorage;
}
}