#include "config.h"
#include "JSObject.h"
#include "ButterflyInlines.h"
#include "CopiedSpaceInlines.h"
#include "CopyVisitor.h"
#include "CopyVisitorInlines.h"
#include "CustomGetterSetter.h"
#include "DatePrototype.h"
#include "ErrorConstructor.h"
#include "Executable.h"
#include "GetterSetter.h"
#include "IndexingHeaderInlines.h"
#include "JSFunction.h"
#include "JSGlobalObject.h"
#include "Lookup.h"
#include "NativeErrorConstructor.h"
#include "Nodes.h"
#include "ObjectPrototype.h"
#include "JSCInlines.h"
#include "PropertyDescriptor.h"
#include "PropertyNameArray.h"
#include "Reject.h"
#include "SlotVisitorInlines.h"
#include <math.h>
#include <wtf/Assertions.h>
namespace JSC {
static unsigned lastArraySize = 0;
JSCell* getCallableObjectSlow(JSCell* cell)
{
if (cell->type() == JSFunctionType)
return cell;
if (cell->structure()->classInfo()->isSubClassOf(InternalFunction::info()))
return cell;
return 0;
}
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSObject);
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSFinalObject);
const char* StrictModeReadonlyPropertyWriteError = "Attempted to assign to readonly property.";
const ClassInfo JSObject::s_info = { "Object", 0, 0, 0, CREATE_METHOD_TABLE(JSObject) };
const ClassInfo JSFinalObject::s_info = { "Object", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(JSFinalObject) };
static inline void getClassPropertyNames(ExecState* exec, const ClassInfo* classInfo, PropertyNameArray& propertyNames, EnumerationMode mode, bool didReify)
{
VM& vm = exec->vm();
for (; classInfo; classInfo = classInfo->parentClass) {
const HashTable* table = classInfo->propHashTable(vm);
if (!table)
continue;
for (auto iter = table->begin(vm); iter != table->end(vm); ++iter) {
if ((!(iter->attributes() & DontEnum) || (mode == IncludeDontEnumProperties)) && !((iter->attributes() & BuiltinOrFunction) && didReify))
propertyNames.add(Identifier(&vm, iter.key()));
}
}
}
ALWAYS_INLINE void JSObject::copyButterfly(CopyVisitor& visitor, Butterfly* butterfly, size_t storageSize)
{
ASSERT(butterfly);
Structure* structure = this->structure();
size_t propertyCapacity = structure->outOfLineCapacity();
size_t preCapacity;
size_t indexingPayloadSizeInBytes;
bool hasIndexingHeader = this->hasIndexingHeader();
if (UNLIKELY(hasIndexingHeader)) {
preCapacity = butterfly->indexingHeader()->preCapacity(structure);
indexingPayloadSizeInBytes = butterfly->indexingHeader()->indexingPayloadSizeInBytes(structure);
} else {
preCapacity = 0;
indexingPayloadSizeInBytes = 0;
}
size_t capacityInBytes = Butterfly::totalSize(preCapacity, propertyCapacity, hasIndexingHeader, indexingPayloadSizeInBytes);
if (visitor.checkIfShouldCopy(butterfly->base(preCapacity, propertyCapacity))) {
Butterfly* newButterfly = Butterfly::createUninitializedDuringCollection(visitor, preCapacity, propertyCapacity, hasIndexingHeader, indexingPayloadSizeInBytes);
PropertyStorage currentTarget = newButterfly->propertyStorage();
PropertyStorage currentSource = butterfly->propertyStorage();
for (size_t count = storageSize; count--;)
(--currentTarget)->setWithoutWriteBarrier((--currentSource)->get());
if (UNLIKELY(hasIndexingHeader)) {
*newButterfly->indexingHeader() = *butterfly->indexingHeader();
WriteBarrier<Unknown>* currentTarget;
WriteBarrier<Unknown>* currentSource;
size_t count;
switch (this->indexingType()) {
case ALL_UNDECIDED_INDEXING_TYPES:
case ALL_CONTIGUOUS_INDEXING_TYPES:
case ALL_INT32_INDEXING_TYPES:
case ALL_DOUBLE_INDEXING_TYPES: {
currentTarget = newButterfly->contiguous().data();
currentSource = butterfly->contiguous().data();
RELEASE_ASSERT(newButterfly->publicLength() <= newButterfly->vectorLength());
count = newButterfly->vectorLength();
break;
}
case ALL_ARRAY_STORAGE_INDEXING_TYPES: {
newButterfly->arrayStorage()->copyHeaderFromDuringGC(*butterfly->arrayStorage());
currentTarget = newButterfly->arrayStorage()->m_vector;
currentSource = butterfly->arrayStorage()->m_vector;
count = newButterfly->arrayStorage()->vectorLength();
break;
}
default:
currentTarget = 0;
currentSource = 0;
count = 0;
break;
}
memcpy(currentTarget, currentSource, count * sizeof(EncodedJSValue));
}
m_butterfly.setWithoutWriteBarrier(newButterfly);
visitor.didCopy(butterfly->base(preCapacity, propertyCapacity), capacityInBytes);
}
}
ALWAYS_INLINE void JSObject::visitButterfly(SlotVisitor& visitor, Butterfly* butterfly, size_t storageSize)
{
ASSERT(butterfly);
Structure* structure = this->structure(visitor.vm());
size_t propertyCapacity = structure->outOfLineCapacity();
size_t preCapacity;
size_t indexingPayloadSizeInBytes;
bool hasIndexingHeader = this->hasIndexingHeader();
if (UNLIKELY(hasIndexingHeader)) {
preCapacity = butterfly->indexingHeader()->preCapacity(structure);
indexingPayloadSizeInBytes = butterfly->indexingHeader()->indexingPayloadSizeInBytes(structure);
} else {
preCapacity = 0;
indexingPayloadSizeInBytes = 0;
}
size_t capacityInBytes = Butterfly::totalSize(preCapacity, propertyCapacity, hasIndexingHeader, indexingPayloadSizeInBytes);
visitor.appendValues(butterfly->propertyStorage() - storageSize, storageSize);
visitor.copyLater(
this, ButterflyCopyToken,
butterfly->base(preCapacity, propertyCapacity), capacityInBytes);
switch (this->indexingType()) {
case ALL_CONTIGUOUS_INDEXING_TYPES:
visitor.appendValues(butterfly->contiguous().data(), butterfly->publicLength());
break;
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
visitor.appendValues(butterfly->arrayStorage()->m_vector, butterfly->arrayStorage()->vectorLength());
if (butterfly->arrayStorage()->m_sparseMap)
visitor.append(&butterfly->arrayStorage()->m_sparseMap);
break;
default:
break;
}
}
void JSObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
#if !ASSERT_DISABLED
bool wasCheckingForDefaultMarkViolation = visitor.m_isCheckingForDefaultMarkViolation;
visitor.m_isCheckingForDefaultMarkViolation = false;
#endif
JSCell::visitChildren(thisObject, visitor);
Butterfly* butterfly = thisObject->butterfly();
if (butterfly)
thisObject->visitButterfly(visitor, butterfly, thisObject->structure(visitor.vm())->outOfLineSize());
#if !ASSERT_DISABLED
visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation;
#endif
}
void JSObject::copyBackingStore(JSCell* cell, CopyVisitor& visitor, CopyToken token)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
if (token != ButterflyCopyToken)
return;
Butterfly* butterfly = thisObject->butterfly();
if (butterfly)
thisObject->copyButterfly(visitor, butterfly, thisObject->structure()->outOfLineSize());
}
void JSFinalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
JSFinalObject* thisObject = jsCast<JSFinalObject*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
#if !ASSERT_DISABLED
bool wasCheckingForDefaultMarkViolation = visitor.m_isCheckingForDefaultMarkViolation;
visitor.m_isCheckingForDefaultMarkViolation = false;
#endif
JSCell::visitChildren(thisObject, visitor);
Structure* structure = thisObject->structure();
Butterfly* butterfly = thisObject->butterfly();
if (butterfly)
thisObject->visitButterfly(visitor, butterfly, structure->outOfLineSize());
size_t storageSize = structure->inlineSize();
visitor.appendValues(thisObject->inlineStorage(), storageSize);
#if !ASSERT_DISABLED
visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation;
#endif
}
String JSObject::className(const JSObject* object)
{
const ClassInfo* info = object->classInfo();
ASSERT(info);
return info->className;
}
bool JSObject::getOwnPropertySlotByIndex(JSObject* thisObject, ExecState* exec, unsigned i, PropertySlot& slot)
{
if (i > MAX_ARRAY_INDEX)
return thisObject->methodTable(exec->vm())->getOwnPropertySlot(thisObject, exec, Identifier::from(exec, 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()[i].get();
if (value) {
slot.setValue(thisObject, 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()[i];
if (value == value) {
slot.setValue(thisObject, 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, 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;
}
void JSObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
ASSERT(value);
ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
VM& vm = exec->vm();
unsigned i = propertyName.asIndex();
if (i != PropertyName::NotAnIndex) {
putByIndex(thisObject, exec, i, value, slot.isStrictMode());
return;
}
JSValue prototype;
if (propertyName != exec->propertyNames().underscoreProto) {
for (JSObject* obj = thisObject; !obj->structure(vm)->hasReadOnlyOrGetterSetterPropertiesExcludingProto(); obj = asObject(prototype)) {
prototype = obj->prototype();
if (prototype.isNull()) {
ASSERT(!thisObject->structure(vm)->prototypeChainMayInterceptStoreTo(exec->vm(), propertyName));
if (!thisObject->putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot, getCallableObject(value))
&& slot.isStrictMode())
throwTypeError(exec, ASCIILiteral(StrictModeReadonlyPropertyWriteError));
return;
}
}
}
JSObject* obj;
for (obj = thisObject; ; obj = asObject(prototype)) {
unsigned attributes;
JSCell* specificValue;
PropertyOffset offset = obj->structure(vm)->get(vm, propertyName, attributes, specificValue);
if (isValidOffset(offset)) {
if (attributes & ReadOnly) {
ASSERT(thisObject->structure(vm)->prototypeChainMayInterceptStoreTo(exec->vm(), propertyName) || obj == thisObject);
if (slot.isStrictMode())
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral(StrictModeReadonlyPropertyWriteError)));
return;
}
JSValue gs = obj->getDirect(offset);
if (gs.isGetterSetter()) {
callSetter(exec, cell, gs, value, slot.isStrictMode() ? StrictMode : NotStrictMode);
if (!thisObject->structure()->isDictionary())
slot.setCacheableSetter(obj, offset);
return;
}
if (gs.isCustomGetterSetter()) {
callCustomSetter(exec, gs, obj, slot.thisValue(), value);
slot.setCustomProperty(obj, jsCast<CustomGetterSetter*>(gs.asCell())->setter());
return;
}
ASSERT(!(attributes & Accessor));
break;
}
const ClassInfo* info = obj->classInfo();
if (info->hasStaticSetterOrReadonlyProperties(vm)) {
if (const HashTableValue* entry = obj->findPropertyHashEntry(vm, propertyName)) {
putEntry(exec, entry, obj, propertyName, value, slot);
return;
}
}
prototype = obj->prototype();
if (prototype.isNull())
break;
}
ASSERT(!thisObject->structure(vm)->prototypeChainMayInterceptStoreTo(exec->vm(), propertyName) || obj == thisObject);
if (!thisObject->putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot, getCallableObject(value)) && slot.isStrictMode())
throwTypeError(exec, ASCIILiteral(StrictModeReadonlyPropertyWriteError));
return;
}
void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
if (propertyName > MAX_ARRAY_INDEX) {
PutPropertySlot slot(cell, shouldThrow);
thisObject->methodTable()->put(thisObject, exec, Identifier::from(exec, propertyName), value, slot);
return;
}
switch (thisObject->indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
break;
case ALL_UNDECIDED_INDEXING_TYPES: {
thisObject->convertUndecidedForValue(exec->vm(), value);
putByIndex(cell, exec, propertyName, value, shouldThrow);
return;
}
case ALL_INT32_INDEXING_TYPES: {
if (!value.isInt32()) {
thisObject->convertInt32ForValue(exec->vm(), value);
putByIndex(cell, exec, propertyName, value, shouldThrow);
return;
}
FALLTHROUGH;
}
case ALL_CONTIGUOUS_INDEXING_TYPES: {
Butterfly* butterfly = thisObject->butterfly();
if (propertyName >= butterfly->vectorLength())
break;
butterfly->contiguous()[propertyName].set(exec->vm(), thisObject, value);
if (propertyName >= butterfly->publicLength())
butterfly->setPublicLength(propertyName + 1);
return;
}
case ALL_DOUBLE_INDEXING_TYPES: {
if (!value.isNumber()) {
thisObject->convertDoubleToContiguous(exec->vm());
putByIndex(cell, exec, propertyName, value, shouldThrow);
return;
}
double valueAsDouble = value.asNumber();
if (valueAsDouble != valueAsDouble) {
thisObject->convertDoubleToContiguous(exec->vm());
putByIndex(cell, exec, propertyName, value, shouldThrow);
return;
}
Butterfly* butterfly = thisObject->butterfly();
if (propertyName >= butterfly->vectorLength())
break;
butterfly->contiguousDouble()[propertyName] = valueAsDouble;
if (propertyName >= butterfly->publicLength())
butterfly->setPublicLength(propertyName + 1);
return;
}
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(exec->vm(), thisObject, value);
return;
}
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();
if (propertyName >= length) {
if (thisObject->attemptToInterceptPutByIndexOnHole(exec, propertyName, value, shouldThrow))
return;
length = propertyName + 1;
storage->setLength(length);
++storage->m_numValuesInVector;
} else if (!valueSlot) {
if (thisObject->attemptToInterceptPutByIndexOnHole(exec, propertyName, value, shouldThrow))
return;
++storage->m_numValuesInVector;
}
valueSlot.set(exec->vm(), thisObject, value);
return;
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
thisObject->putByIndexBeyondVectorLength(exec, 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.set(vm, this, value);
}
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);
setButterflyWithoutChangingStructure(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:
enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, ensureArrayStorageSlow(vm));
break;
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
enterDictionaryIndexingModeWhenArrayStorageAlreadyExists(vm, m_butterfly->arrayStorage());
break;
default:
break;
}
}
void JSObject::notifyPresenceOfIndexedAccessors(VM& vm)
{
if (mayInterceptIndexedAccesses())
return;
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AddIndexedAccessors));
if (!vm.prototypeMap.isPrototype(this))
return;
globalObject()->haveABadTime(vm);
}
Butterfly* JSObject::createInitialIndexedStorage(VM& vm, unsigned length, size_t elementSize)
{
ASSERT(length < MAX_ARRAY_INDEX);
IndexingType oldType = indexingType();
ASSERT_UNUSED(oldType, !hasIndexedProperties(oldType));
ASSERT(!structure()->needsSlowPutIndexing());
ASSERT(!indexingShouldBeSparse());
unsigned vectorLength = std::max(length, BASE_VECTOR_LEN);
Butterfly* newButterfly = Butterfly::createOrGrowArrayRight(
m_butterfly.get(), vm, this, structure(), structure()->outOfLineCapacity(), false, 0,
elementSize * 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, sizeof(EncodedJSValue));
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), AllocateUndecided);
setStructureAndButterfly(vm, newStructure, newButterfly);
return newButterfly;
}
ContiguousJSValues JSObject::createInitialInt32(VM& vm, unsigned length)
{
DeferGC deferGC(vm.heap);
Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(EncodedJSValue));
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), AllocateInt32);
setStructureAndButterfly(vm, newStructure, newButterfly);
return newButterfly->contiguousInt32();
}
ContiguousDoubles JSObject::createInitialDouble(VM& vm, unsigned length)
{
DeferGC deferGC(vm.heap);
Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(double));
for (unsigned i = newButterfly->vectorLength(); i--;)
newButterfly->contiguousDouble()[i] = PNaN;
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), AllocateDouble);
setStructureAndButterfly(vm, newStructure, newButterfly);
return newButterfly->contiguousDouble();
}
ContiguousJSValues JSObject::createInitialContiguous(VM& vm, unsigned length)
{
DeferGC deferGC(vm.heap);
Butterfly* newButterfly = createInitialIndexedStorage(vm, length, sizeof(EncodedJSValue));
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), AllocateContiguous);
setStructureAndButterfly(vm, newStructure, newButterfly);
return newButterfly->contiguous();
}
ArrayStorage* JSObject::createArrayStorage(VM& vm, unsigned length, unsigned vectorLength)
{
DeferGC deferGC(vm.heap);
Structure* structure = this->structure(vm);
IndexingType oldType = indexingType();
ASSERT_UNUSED(oldType, !hasIndexedProperties(oldType));
Butterfly* newButterfly = Butterfly::createOrGrowArrayRight(
m_butterfly.get(), vm, this, 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;
Structure* newStructure = Structure::nonPropertyTransition(vm, structure, structure->suggestedArrayStorageTransition());
setStructureAndButterfly(vm, newStructure, newButterfly);
return result;
}
ArrayStorage* JSObject::createInitialArrayStorage(VM& vm)
{
return createArrayStorage(vm, 0, BASE_VECTOR_LEN);
}
ContiguousJSValues JSObject::convertUndecidedToInt32(VM& vm)
{
ASSERT(hasUndecided(indexingType()));
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateInt32));
return m_butterfly->contiguousInt32();
}
ContiguousDoubles JSObject::convertUndecidedToDouble(VM& vm)
{
ASSERT(hasUndecided(indexingType()));
for (unsigned i = m_butterfly->vectorLength(); i--;)
m_butterfly->contiguousDouble()[i] = PNaN;
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateDouble));
return m_butterfly->contiguousDouble();
}
ContiguousJSValues JSObject::convertUndecidedToContiguous(VM& vm)
{
ASSERT(hasUndecided(indexingType()));
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), 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();
unsigned propertySize = structure->outOfLineSize();
Butterfly* newButterfly = Butterfly::createUninitialized(
vm, this, 0, propertyCapacity, true, ArrayStorage::sizeFor(neededLength));
memcpy(
newButterfly->propertyStorage() - propertySize,
m_butterfly->propertyStorage() - propertySize,
propertySize * 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, NonPropertyTransition transition, unsigned neededLength)
{
DeferGC deferGC(vm.heap);
ASSERT(hasUndecided(indexingType()));
ArrayStorage* storage = constructConvertedArrayStorageWithoutCopyingElements(vm, neededLength);
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), transition);
setStructureAndButterfly(vm, newStructure, storage->butterfly());
return storage;
}
ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm, NonPropertyTransition transition)
{
return convertUndecidedToArrayStorage(vm, transition, m_butterfly->vectorLength());
}
ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm)
{
return convertUndecidedToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition());
}
ContiguousDoubles JSObject::convertInt32ToDouble(VM& vm)
{
ASSERT(hasInt32(indexingType()));
for (unsigned i = m_butterfly->vectorLength(); i--;) {
WriteBarrier<Unknown>* current = &m_butterfly->contiguousInt32()[i];
double* currentAsDouble = bitwise_cast<double*>(current);
JSValue v = current->get();
if (!v) {
*currentAsDouble = PNaN;
continue;
}
ASSERT(v.isInt32());
*currentAsDouble = v.asInt32();
}
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateDouble));
return m_butterfly->contiguousDouble();
}
ContiguousJSValues JSObject::convertInt32ToContiguous(VM& vm)
{
ASSERT(hasInt32(indexingType()));
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateContiguous));
return m_butterfly->contiguous();
}
ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm, NonPropertyTransition transition, unsigned neededLength)
{
ASSERT(hasInt32(indexingType()));
DeferGC deferGC(vm.heap);
ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, neededLength);
for (unsigned i = m_butterfly->publicLength(); i--;) {
JSValue v = m_butterfly->contiguous()[i].get();
if (!v)
continue;
newStorage->m_vector[i].setWithoutWriteBarrier(v);
newStorage->m_numValuesInVector++;
}
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), transition);
setStructureAndButterfly(vm, newStructure, newStorage->butterfly());
return newStorage;
}
ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm, NonPropertyTransition transition)
{
return convertInt32ToArrayStorage(vm, transition, m_butterfly->vectorLength());
}
ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm)
{
return convertInt32ToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition());
}
template<JSObject::DoubleToContiguousMode mode>
ContiguousJSValues JSObject::genericConvertDoubleToContiguous(VM& vm)
{
ASSERT(hasDouble(indexingType()));
for (unsigned i = m_butterfly->vectorLength(); i--;) {
double* current = &m_butterfly->contiguousDouble()[i];
WriteBarrier<Unknown>* currentAsValue = bitwise_cast<WriteBarrier<Unknown>*>(current);
double value = *current;
if (value != value) {
currentAsValue->clear();
continue;
}
JSValue v;
switch (mode) {
case EncodeValueAsDouble:
v = JSValue(JSValue::EncodeAsDouble, value);
break;
case RageConvertDoubleToValue:
v = jsNumber(value);
break;
default:
v = JSValue();
RELEASE_ASSERT_NOT_REACHED();
break;
}
ASSERT(v.isNumber());
currentAsValue->setWithoutWriteBarrier(v);
}
setStructure(vm, Structure::nonPropertyTransition(vm, structure(vm), AllocateContiguous));
return m_butterfly->contiguous();
}
ContiguousJSValues JSObject::convertDoubleToContiguous(VM& vm)
{
return genericConvertDoubleToContiguous<EncodeValueAsDouble>(vm);
}
ContiguousJSValues JSObject::rageConvertDoubleToContiguous(VM& vm)
{
return genericConvertDoubleToContiguous<RageConvertDoubleToValue>(vm);
}
ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm, NonPropertyTransition transition, unsigned neededLength)
{
DeferGC deferGC(vm.heap);
ASSERT(hasDouble(indexingType()));
ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, neededLength);
for (unsigned i = m_butterfly->publicLength(); i--;) {
double value = m_butterfly->contiguousDouble()[i];
if (value != value)
continue;
newStorage->m_vector[i].setWithoutWriteBarrier(JSValue(JSValue::EncodeAsDouble, value));
newStorage->m_numValuesInVector++;
}
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), transition);
setStructureAndButterfly(vm, newStructure, newStorage->butterfly());
return newStorage;
}
ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm, NonPropertyTransition transition)
{
return convertDoubleToArrayStorage(vm, transition, m_butterfly->vectorLength());
}
ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm)
{
return convertDoubleToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition());
}
ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm, NonPropertyTransition transition, unsigned neededLength)
{
DeferGC deferGC(vm.heap);
ASSERT(hasContiguous(indexingType()));
ArrayStorage* newStorage = constructConvertedArrayStorageWithoutCopyingElements(vm, neededLength);
for (unsigned i = m_butterfly->publicLength(); i--;) {
JSValue v = m_butterfly->contiguous()[i].get();
if (!v)
continue;
newStorage->m_vector[i].setWithoutWriteBarrier(v);
newStorage->m_numValuesInVector++;
}
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), transition);
setStructureAndButterfly(vm, newStructure, newStorage->butterfly());
return newStorage;
}
ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm, NonPropertyTransition transition)
{
return convertContiguousToArrayStorage(vm, transition, m_butterfly->vectorLength());
}
ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm)
{
return convertContiguousToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition());
}
void JSObject::convertUndecidedForValue(VM& vm, JSValue value)
{
if (value.isInt32()) {
convertUndecidedToInt32(vm);
return;
}
if (value.isDouble() && value.asNumber() == value.asNumber()) {
convertUndecidedToDouble(vm);
return;
}
convertUndecidedToContiguous(vm);
}
void JSObject::createInitialForValueAndSet(VM& vm, unsigned index, JSValue value)
{
if (value.isInt32()) {
createInitialInt32(vm, index + 1)[index].set(vm, this, value);
return;
}
if (value.isDouble()) {
double doubleValue = value.asNumber();
if (doubleValue == doubleValue) {
createInitialDouble(vm, index + 1)[index] = doubleValue;
return;
}
}
createInitialContiguous(vm, index + 1)[index].set(vm, this, value);
}
void JSObject::convertInt32ForValue(VM& vm, JSValue value)
{
ASSERT(!value.isInt32());
if (value.isDouble()) {
convertInt32ToDouble(vm);
return;
}
convertInt32ToContiguous(vm);
}
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::ensureInt32Slow(VM& vm)
{
ASSERT(inherits(info()));
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing()))
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::ensureDoubleSlow(VM& vm)
{
ASSERT(inherits(info()));
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing()))
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::ensureContiguousSlow(VM& vm, DoubleToContiguousMode mode)
{
ASSERT(inherits(info()));
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing()))
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:
if (mode == RageConvertDoubleToValue)
return rageConvertDoubleToContiguous(vm);
return convertDoubleToContiguous(vm);
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
return ContiguousJSValues();
default:
CRASH();
return ContiguousJSValues();
}
}
ContiguousJSValues JSObject::ensureContiguousSlow(VM& vm)
{
return ensureContiguousSlow(vm, EncodeValueAsDouble);
}
ContiguousJSValues JSObject::rageEnsureContiguousSlow(VM& vm)
{
return ensureContiguousSlow(vm, RageConvertDoubleToValue);
}
ArrayStorage* JSObject::ensureArrayStorageSlow(VM& vm)
{
ASSERT(inherits(info()));
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
if (UNLIKELY(indexingShouldBeSparse()))
return ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm);
return createInitialArrayStorage(vm);
case ALL_UNDECIDED_INDEXING_TYPES:
ASSERT(!indexingShouldBeSparse());
ASSERT(!structure(vm)->needsSlowPutIndexing());
return convertUndecidedToArrayStorage(vm);
case ALL_INT32_INDEXING_TYPES:
ASSERT(!indexingShouldBeSparse());
ASSERT(!structure(vm)->needsSlowPutIndexing());
return convertInt32ToArrayStorage(vm);
case ALL_DOUBLE_INDEXING_TYPES:
ASSERT(!indexingShouldBeSparse());
ASSERT(!structure(vm)->needsSlowPutIndexing());
return convertDoubleToArrayStorage(vm);
case ALL_CONTIGUOUS_INDEXING_TYPES:
ASSERT(!indexingShouldBeSparse());
ASSERT(!structure(vm)->needsSlowPutIndexing());
return convertContiguousToArrayStorage(vm);
default:
RELEASE_ASSERT_NOT_REACHED();
return 0;
}
}
ArrayStorage* JSObject::ensureArrayStorageExistsAndEnterDictionaryIndexingMode(VM& 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 0;
}
}
void JSObject::switchToSlowPutArrayStorage(VM& vm)
{
switch (indexingType()) {
case ALL_UNDECIDED_INDEXING_TYPES:
convertUndecidedToArrayStorage(vm, AllocateSlowPutArrayStorage);
break;
case ALL_INT32_INDEXING_TYPES:
convertInt32ToArrayStorage(vm, AllocateSlowPutArrayStorage);
break;
case ALL_DOUBLE_INDEXING_TYPES:
convertDoubleToArrayStorage(vm, AllocateSlowPutArrayStorage);
break;
case ALL_CONTIGUOUS_INDEXING_TYPES:
convertContiguousToArrayStorage(vm, AllocateSlowPutArrayStorage);
break;
case NonArrayWithArrayStorage:
case ArrayWithArrayStorage: {
Structure* newStructure = Structure::nonPropertyTransition(vm, structure(vm), SwitchToSlowPutArrayStorage);
setStructure(vm, newStructure);
break;
}
default:
CRASH();
break;
}
}
void JSObject::setPrototype(VM& vm, JSValue prototype)
{
ASSERT(prototype);
if (prototype.isObject())
vm.prototypeMap.addPrototype(asObject(prototype));
Structure* newStructure = Structure::changePrototypeTransition(vm, structure(vm), prototype);
setStructure(vm, newStructure);
if (!newStructure->anyObjectInChainMayInterceptIndexedAccesses())
return;
if (vm.prototypeMap.isPrototype(this)) {
newStructure->globalObject()->haveABadTime(vm);
return;
}
if (!hasIndexedProperties(indexingType()))
return;
if (shouldUseSlowPut(indexingType()))
return;
switchToSlowPutArrayStorage(vm);
}
bool JSObject::setPrototypeWithCycleCheck(ExecState* exec, JSValue prototype)
{
ASSERT(methodTable(exec->vm())->toThis(this, exec, NotStrictMode) == this);
JSValue nextPrototype = prototype;
while (nextPrototype && nextPrototype.isObject()) {
if (nextPrototype == this)
return false;
nextPrototype = asObject(nextPrototype)->prototype();
}
setPrototype(exec->vm(), prototype);
return true;
}
bool JSObject::allowsAccessFrom(ExecState* exec)
{
JSGlobalObject* globalObject = this->globalObject();
return globalObject->globalObjectMethodTable()->allowsAccessFrom(globalObject, exec);
}
void JSObject::putDirectAccessor(ExecState* exec, PropertyName propertyName, JSValue value, unsigned attributes)
{
ASSERT(value.isGetterSetter() && (attributes & Accessor));
unsigned index = propertyName.asIndex();
if (index != PropertyName::NotAnIndex) {
putDirectIndex(exec, index, value, attributes, PutDirectIndexLikePutDirect);
return;
}
putDirectNonIndexAccessor(exec->vm(), propertyName, value, attributes);
}
void JSObject::putDirectCustomAccessor(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes)
{
ASSERT(propertyName.asIndex() == PropertyName::NotAnIndex);
PutPropertySlot slot(this);
putDirectInternal<PutModeDefineOwnProperty>(vm, propertyName, value, attributes, slot, getCallableObject(value));
ASSERT(slot.type() == PutPropertySlot::NewProperty);
Structure* structure = this->structure(vm);
if (attributes & ReadOnly)
structure->setContainsReadOnlyProperties();
structure->setHasCustomGetterSetterProperties(propertyName == vm.propertyNames->underscoreProto);
}
void JSObject::putDirectNonIndexAccessor(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes)
{
PutPropertySlot slot(this);
putDirectInternal<PutModeDefineOwnProperty>(vm, propertyName, value, attributes, slot, getCallableObject(value));
if (slot.type() != PutPropertySlot::NewProperty)
setStructure(vm, Structure::attributeChangeTransition(vm, structure(vm), propertyName, attributes));
Structure* structure = this->structure(vm);
if (attributes & ReadOnly)
structure->setContainsReadOnlyProperties();
structure->setHasGetterSetterProperties(propertyName == vm.propertyNames->underscoreProto);
}
bool JSObject::hasProperty(ExecState* exec, PropertyName propertyName) const
{
PropertySlot slot(this);
return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
}
bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const
{
PropertySlot slot(this);
return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
}
bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
unsigned i = propertyName.asIndex();
if (i != PropertyName::NotAnIndex)
return thisObject->methodTable(exec->vm())->deletePropertyByIndex(thisObject, exec, i);
if (!thisObject->staticFunctionsReified())
thisObject->reifyStaticFunctionsForDelete(exec);
unsigned attributes;
JSCell* specificValue;
VM& vm = exec->vm();
if (isValidOffset(thisObject->structure(vm)->get(vm, propertyName, attributes, specificValue))) {
if (attributes & DontDelete && !vm.isInDefineOwnProperty())
return false;
thisObject->removeDirect(vm, propertyName);
return true;
}
const HashTableValue* entry = thisObject->findPropertyHashEntry(vm, propertyName);
if (entry) {
if (entry->attributes() & DontDelete && !vm.isInDefineOwnProperty())
return false;
PutPropertySlot slot(thisObject);
putEntry(exec, entry, thisObject, propertyName, jsUndefined(), slot);
}
return true;
}
bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName) const
{
PropertySlot slot(this);
return const_cast<JSObject*>(this)->methodTable(exec->vm())->getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
}
bool JSObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned i)
{
JSObject* thisObject = jsCast<JSObject*>(cell);
if (i > MAX_ARRAY_INDEX)
return thisObject->methodTable(exec->vm())->deleteProperty(thisObject, exec, Identifier::from(exec, i));
switch (thisObject->indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
case ALL_UNDECIDED_INDEXING_TYPES:
return true;
case ALL_INT32_INDEXING_TYPES:
case ALL_CONTIGUOUS_INDEXING_TYPES: {
Butterfly* butterfly = thisObject->butterfly();
if (i >= butterfly->vectorLength())
return true;
butterfly->contiguous()[i].clear();
return true;
}
case ALL_DOUBLE_INDEXING_TYPES: {
Butterfly* butterfly = thisObject->butterfly();
if (i >= butterfly->vectorLength())
return true;
butterfly->contiguousDouble()[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 & DontDelete)
return false;
map->remove(it);
}
}
return true;
}
default:
RELEASE_ASSERT_NOT_REACHED();
return false;
}
}
static ALWAYS_INLINE JSValue callDefaultValueFunction(ExecState* exec, const JSObject* object, PropertyName propertyName)
{
JSValue function = object->get(exec, propertyName);
CallData callData;
CallType callType = getCallData(function, callData);
if (callType == CallTypeNone)
return exec->exception();
if (exec->hadException())
return exec->exception();
JSValue result = call(exec, function, callType, callData, const_cast<JSObject*>(object), exec->emptyList());
ASSERT(!result.isGetterSetter());
if (exec->hadException())
return exec->exception();
if (result.isObject())
return JSValue();
return result;
}
bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) const
{
result = methodTable(exec->vm())->defaultValue(this, exec, PreferNumber);
number = result.toNumber(exec);
return !result.isString();
}
JSValue JSObject::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint)
{
if ((hint == PreferString) || (hint != PreferNumber && object->prototype() == exec->lexicalGlobalObject()->datePrototype())) {
JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().toString);
if (value)
return value;
value = callDefaultValueFunction(exec, object, exec->propertyNames().valueOf);
if (value)
return value;
} else {
JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().valueOf);
if (value)
return value;
value = callDefaultValueFunction(exec, object, exec->propertyNames().toString);
if (value)
return value;
}
ASSERT(!exec->hadException());
return exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("No default value")));
}
const HashTableValue* JSObject::findPropertyHashEntry(VM& vm, PropertyName propertyName) const
{
for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
if (const HashTable* propHashTable = info->propHashTable(vm)) {
if (const HashTableValue* entry = propHashTable->entry(vm, propertyName))
return entry;
}
}
return 0;
}
bool JSObject::hasInstance(ExecState* exec, JSValue value)
{
VM& vm = exec->vm();
TypeInfo info = structure(vm)->typeInfo();
if (info.implementsDefaultHasInstance())
return defaultHasInstance(exec, value, get(exec, exec->propertyNames().prototype));
if (info.implementsHasInstance())
return methodTable(vm)->customHasInstance(this, exec, value);
vm.throwException(exec, createInvalidParameterError(exec, "instanceof" , this));
return false;
}
bool JSObject::defaultHasInstance(ExecState* exec, JSValue value, JSValue proto)
{
if (!value.isObject())
return false;
if (!proto.isObject()) {
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("instanceof called on an object with an invalid prototype property.")));
return false;
}
JSObject* object = asObject(value);
while ((object = object->prototype().getObject())) {
if (proto == object)
return true;
}
return false;
}
bool JSObject::getPropertySpecificValue(ExecState* exec, PropertyName propertyName, JSCell*& specificValue) const
{
VM& vm = exec->vm();
unsigned attributes;
if (isValidOffset(structure(vm)->get(vm, propertyName, attributes, specificValue)))
return true;
return false;
}
void JSObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
{
propertyNames.setBaseObject(object);
object->methodTable(exec->vm())->getOwnPropertyNames(object, exec, propertyNames, mode);
if (object->prototype().isNull())
return;
VM& vm = exec->vm();
JSObject* prototype = asObject(object->prototype());
while(1) {
if (prototype->structure(vm)->typeInfo().overridesGetPropertyNames()) {
prototype->methodTable(vm)->getPropertyNames(prototype, exec, propertyNames, mode);
break;
}
prototype->methodTable(vm)->getOwnPropertyNames(prototype, exec, propertyNames, mode);
JSValue nextProto = prototype->prototype();
if (nextProto.isNull())
break;
prototype = asObject(nextProto);
}
}
void JSObject::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
{
switch (object->indexingType()) {
case ALL_BLANK_INDEXING_TYPES:
case ALL_UNDECIDED_INDEXING_TYPES:
break;
case ALL_INT32_INDEXING_TYPES:
case ALL_CONTIGUOUS_INDEXING_TYPES: {
Butterfly* butterfly = object->butterfly();
unsigned usedLength = butterfly->publicLength();
for (unsigned i = 0; i < usedLength; ++i) {
if (!butterfly->contiguous()[i])
continue;
propertyNames.add(Identifier::from(exec, 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()[i];
if (value != value)
continue;
propertyNames.add(Identifier::from(exec, 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(Identifier::from(exec, 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 == IncludeDontEnumProperties || !(it->value.attributes & DontEnum))
keys.uncheckedAppend(static_cast<unsigned>(it->key));
}
std::sort(keys.begin(), keys.end());
for (unsigned i = 0; i < keys.size(); ++i)
propertyNames.add(Identifier::from(exec, keys[i]));
}
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
}
object->methodTable(exec->vm())->getOwnNonIndexPropertyNames(object, exec, propertyNames, mode);
}
void JSObject::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
{
getClassPropertyNames(exec, object->classInfo(), propertyNames, mode, object->staticFunctionsReified());
VM& vm = exec->vm();
bool canCachePropertiesFromStructure = !propertyNames.size();
object->structure(vm)->getPropertyNamesFromStructure(vm, propertyNames, mode);
if (canCachePropertiesFromStructure)
propertyNames.setNumCacheableSlotsForObject(object, propertyNames.size());
}
double JSObject::toNumber(ExecState* exec) const
{
JSValue primitive = toPrimitive(exec, PreferNumber);
if (exec->hadException()) return 0.0;
return primitive.toNumber(exec);
}
JSString* JSObject::toString(ExecState* exec) const
{
JSValue primitive = toPrimitive(exec, PreferString);
if (exec->hadException())
return jsEmptyString(exec);
return primitive.toString(exec);
}
JSValue JSObject::toThis(JSCell* cell, ExecState*, ECMAMode)
{
return jsCast<JSObject*>(cell);
}
void JSObject::seal(VM& vm)
{
if (isSealed(vm))
return;
preventExtensions(vm);
setStructure(vm, Structure::sealTransition(vm, structure(vm)));
}
void JSObject::freeze(VM& vm)
{
if (isFrozen(vm))
return;
preventExtensions(vm);
setStructure(vm, Structure::freezeTransition(vm, structure(vm)));
}
void JSObject::preventExtensions(VM& vm)
{
enterDictionaryIndexingMode(vm);
if (isExtensible())
setStructure(vm, Structure::preventExtensionsTransition(vm, structure(vm)));
}
void JSObject::reifyStaticFunctionsForDelete(ExecState* exec)
{
ASSERT(!staticFunctionsReified());
VM& vm = exec->vm();
if (!classInfo()->hasStaticProperties()) {
structure(vm)->setStaticFunctionsReified();
return;
}
if (!structure(vm)->isUncacheableDictionary())
setStructure(vm, Structure::toUncacheableDictionaryTransition(vm, structure(vm)));
for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
const HashTable* hashTable = info->propHashTable(vm);
if (!hashTable)
continue;
PropertySlot slot(this);
for (auto iter = hashTable->begin(vm); iter != hashTable->end(vm); ++iter) {
if (iter->attributes() & BuiltinOrFunction)
setUpStaticFunctionSlot(globalObject()->globalExec(), iter.value(), this, Identifier(&vm, iter.key()), slot);
}
}
structure(vm)->setStaticFunctionsReified();
}
bool JSObject::removeDirect(VM& vm, PropertyName propertyName)
{
Structure* structure = this->structure(vm);
if (!isValidOffset(structure->get(vm, propertyName)))
return false;
PropertyOffset offset;
if (structure->isUncacheableDictionary()) {
offset = structure->removePropertyWithoutTransition(vm, propertyName);
if (offset == invalidOffset)
return false;
putDirectUndefined(offset);
return true;
}
setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
if (offset == invalidOffset)
return false;
putDirectUndefined(offset);
return true;
}
NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, JSValue getterSetter, unsigned attributes, PropertyOffset offset)
{
if (structure()->isDictionary()) {
slot.setGetterSlot(this, attributes, jsCast<GetterSetter*>(getterSetter));
return;
}
slot.setCacheableGetterSlot(this, attributes, jsCast<GetterSetter*>(getterSetter), offset);
}
void JSObject::putIndexedDescriptor(ExecState* exec, SparseArrayEntry* entryInMap, const PropertyDescriptor& descriptor, PropertyDescriptor& oldDescriptor)
{
VM& vm = exec->vm();
if (descriptor.isDataDescriptor()) {
if (descriptor.value())
entryInMap->set(vm, this, descriptor.value());
else if (oldDescriptor.isAccessorDescriptor())
entryInMap->set(vm, this, jsUndefined());
entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor) & ~Accessor;
return;
}
if (descriptor.isAccessorDescriptor()) {
JSObject* getter = 0;
if (descriptor.getterPresent())
getter = descriptor.getterObject();
else if (oldDescriptor.isAccessorDescriptor())
getter = oldDescriptor.getterObject();
JSObject* setter = 0;
if (descriptor.setterPresent())
setter = descriptor.setterObject();
else if (oldDescriptor.isAccessorDescriptor())
setter = oldDescriptor.setterObject();
GetterSetter* accessor = GetterSetter::create(vm);
if (getter)
accessor->setGetter(vm, getter);
if (setter)
accessor->setSetter(vm, setter);
entryInMap->set(vm, this, accessor);
entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor) & ~ReadOnly;
return;
}
ASSERT(descriptor.isGenericDescriptor());
entryInMap->attributes = descriptor.attributesOverridingCurrent(oldDescriptor);
}
bool JSObject::defineOwnIndexedProperty(ExecState* exec, unsigned index, const PropertyDescriptor& descriptor, bool throwException)
{
ASSERT(index <= MAX_ARRAY_INDEX);
if (!inSparseIndexingMode()) {
if (!descriptor.attributes()) {
ASSERT(!descriptor.isAccessorDescriptor());
return putDirectIndex(exec, index, descriptor.value(), 0, throwException ? PutDirectIndexShouldThrow : PutDirectIndexShouldNotThrow);
}
ensureArrayStorageExistsAndEnterDictionaryIndexingMode(exec->vm());
}
if (descriptor.attributes() & (ReadOnly | Accessor))
notifyPresenceOfIndexedAccessors(exec->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 (!isExtensible()) {
map->remove(result.iterator);
return reject(exec, throwException, "Attempting to define property on object that is not extensible.");
}
PropertyDescriptor defaults;
entryInMap->setWithoutWriteBarrier(jsUndefined());
entryInMap->attributes = DontDelete | DontEnum | ReadOnly;
entryInMap->get(defaults);
putIndexedDescriptor(exec, entryInMap, descriptor, defaults);
if (index >= m_butterfly->arrayStorage()->length())
m_butterfly->arrayStorage()->setLength(index + 1);
return true;
}
PropertyDescriptor current;
entryInMap->get(current);
if (descriptor.isEmpty() || descriptor.equalTo(exec, current))
return true;
if (!current.configurable()) {
if (descriptor.configurablePresent() && descriptor.configurable())
return reject(exec, throwException, "Attempting to change configurable attribute of unconfigurable property.");
if (descriptor.enumerablePresent() && current.enumerable() != descriptor.enumerable())
return reject(exec, throwException, "Attempting to change enumerable attribute of unconfigurable property.");
}
if (!descriptor.isGenericDescriptor()) {
if (current.isDataDescriptor() != descriptor.isDataDescriptor()) {
if (!current.configurable())
return reject(exec, throwException, "Attempting to change access mechanism for an unconfigurable property.");
} else if (current.isDataDescriptor() && descriptor.isDataDescriptor()) {
if (!current.configurable() && !current.writable()) {
if (descriptor.writable())
return reject(exec, throwException, "Attempting to change writable attribute of unconfigurable property.");
if (descriptor.value() && !sameValue(exec, descriptor.value(), current.value()))
return reject(exec, throwException, "Attempting to change value of a readonly property.");
}
} else {
ASSERT(current.isAccessorDescriptor() && current.getterPresent() && current.setterPresent());
if (!current.configurable()) {
if (descriptor.setterPresent() && descriptor.setter() != current.setter())
return reject(exec, throwException, "Attempting to change the setter of an unconfigurable property.");
if (descriptor.getterPresent() && descriptor.getter() != current.getter())
return reject(exec, throwException, "Attempting to change the getter of an unconfigurable property.");
}
}
}
putIndexedDescriptor(exec, 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(ExecState* exec, JSValue thisValue, unsigned i, JSValue value, bool shouldThrow)
{
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 & (Accessor | ReadOnly))) {
iter->value.put(exec, thisValue, storage->m_sparseMap.get(), value, shouldThrow);
return true;
}
}
JSValue prototypeValue = current->prototype();
if (prototypeValue.isNull())
return false;
current = asObject(prototypeValue);
}
}
bool JSObject::attemptToInterceptPutByIndexOnHole(ExecState* exec, unsigned i, JSValue value, bool shouldThrow)
{
JSValue prototypeValue = prototype();
if (prototypeValue.isNull())
return false;
return asObject(prototypeValue)->attemptToInterceptPutByIndexOnHoleForPrototype(exec, this, i, value, shouldThrow);
}
template<IndexingType indexingShape>
void JSObject::putByIndexBeyondVectorLengthWithoutAttributes(ExecState* exec, unsigned i, JSValue value)
{
ASSERT((indexingType() & IndexingShapeMask) == indexingShape);
ASSERT(!indexingShouldBeSparse());
ASSERT(i >= m_butterfly->vectorLength());
VM& vm = exec->vm();
if (i >= MAX_ARRAY_INDEX - 1
|| (i >= MIN_SPARSE_ARRAY_INDEX
&& !isDenseEnoughForVector(i, countElements<indexingShape>(butterfly())))
|| indexIsSufficientlyBeyondLengthForSparseMap(i, m_butterfly->vectorLength())) {
ASSERT(i <= MAX_ARRAY_INDEX);
ensureArrayStorageSlow(vm);
SparseArrayValueMap* map = allocateSparseIndexMap(vm);
map->putEntry(exec, this, i, value, false);
ASSERT(i >= arrayStorage()->length());
arrayStorage()->setLength(i + 1);
return;
}
ensureLength(vm, i + 1);
RELEASE_ASSERT(i < m_butterfly->vectorLength());
switch (indexingShape) {
case Int32Shape:
ASSERT(value.isInt32());
m_butterfly->contiguousInt32()[i].setWithoutWriteBarrier(value);
break;
case DoubleShape: {
ASSERT(value.isNumber());
double valueAsDouble = value.asNumber();
ASSERT(valueAsDouble == valueAsDouble);
m_butterfly->contiguousDouble()[i] = valueAsDouble;
break;
}
case ContiguousShape:
m_butterfly->contiguous()[i].set(vm, this, value);
break;
default:
CRASH();
}
}
void JSObject::putByIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, unsigned i, JSValue value, bool shouldThrow, ArrayStorage* storage)
{
VM& vm = exec->vm();
ASSERT(i <= MAX_ARRAY_INDEX);
ASSERT(i >= storage->vectorLength());
SparseArrayValueMap* map = storage->m_sparseMap.get();
if (LIKELY(!map)) {
ASSERT(isExtensible());
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;
}
map = allocateSparseIndexMap(exec->vm());
map->putEntry(exec, this, i, value, shouldThrow);
return;
}
unsigned length = storage->length();
if (i >= length) {
if (map->lengthIsReadOnly() || !isExtensible()) {
if (shouldThrow)
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return;
}
length = i + 1;
storage->setLength(length);
}
unsigned numValuesInArray = storage->m_numValuesInVector + map->size();
if (map->sparseMode() || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(exec->vm(), length)) {
map->putEntry(exec, this, i, value, shouldThrow);
return;
}
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);
}
void JSObject::putByIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue value, bool shouldThrow)
{
VM& vm = exec->vm();
ASSERT(i <= MAX_ARRAY_INDEX);
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES: {
if (indexingShouldBeSparse()) {
putByIndexBeyondVectorLengthWithArrayStorage(
exec, i, value, shouldThrow,
ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
break;
}
if (indexIsSufficientlyBeyondLengthForSparseMap(i, 0) || i >= MIN_SPARSE_ARRAY_INDEX) {
putByIndexBeyondVectorLengthWithArrayStorage(
exec, i, value, shouldThrow, createArrayStorage(vm, 0, 0));
break;
}
if (structure(vm)->needsSlowPutIndexing()) {
ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, i + 1));
storage->m_vector[i].set(vm, this, value);
storage->m_numValuesInVector++;
break;
}
createInitialForValueAndSet(vm, i, value);
break;
}
case ALL_UNDECIDED_INDEXING_TYPES: {
CRASH();
break;
}
case ALL_INT32_INDEXING_TYPES: {
putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, i, value);
break;
}
case ALL_DOUBLE_INDEXING_TYPES: {
putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, i, value);
break;
}
case ALL_CONTIGUOUS_INDEXING_TYPES: {
putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, i, value);
break;
}
case NonArrayWithSlowPutArrayStorage:
case ArrayWithSlowPutArrayStorage: {
SparseArrayValueMap* map = arrayStorage()->m_sparseMap.get();
if (!(map && map->contains(i)) && attemptToInterceptPutByIndexOnHole(exec, i, value, shouldThrow))
return;
FALLTHROUGH;
}
case NonArrayWithArrayStorage:
case ArrayWithArrayStorage:
putByIndexBeyondVectorLengthWithArrayStorage(exec, i, value, shouldThrow, arrayStorage());
break;
default:
RELEASE_ASSERT_NOT_REACHED();
}
}
bool JSObject::putDirectIndexBeyondVectorLengthWithArrayStorage(ExecState* exec, unsigned i, JSValue value, unsigned attributes, PutDirectIndexMode mode, ArrayStorage* storage)
{
VM& vm = exec->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(isExtensible());
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(exec->vm());
return map->putDirect(exec, this, i, value, attributes, mode);
}
unsigned length = storage->length();
if (i >= length) {
if (mode != PutDirectIndexLikePutDirect) {
if (map->lengthIsReadOnly())
return reject(exec, mode == PutDirectIndexShouldThrow, StrictModeReadonlyPropertyWriteError);
if (!isExtensible())
return reject(exec, mode == PutDirectIndexShouldThrow, "Attempting to define property on object that is not extensible.");
}
length = i + 1;
storage->setLength(length);
}
unsigned numValuesInArray = storage->m_numValuesInVector + map->size();
if (map->sparseMode() || attributes || !isDenseEnoughForVector(length, numValuesInArray) || !increaseVectorLength(exec->vm(), length))
return map->putDirect(exec, 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::putDirectIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue value, unsigned attributes, PutDirectIndexMode mode)
{
VM& vm = exec->vm();
ASSERT(i <= MAX_ARRAY_INDEX);
if (attributes & (ReadOnly | Accessor))
notifyPresenceOfIndexedAccessors(vm);
switch (indexingType()) {
case ALL_BLANK_INDEXING_TYPES: {
if (indexingShouldBeSparse() || attributes) {
return putDirectIndexBeyondVectorLengthWithArrayStorage(
exec, i, value, attributes, mode,
ensureArrayStorageExistsAndEnterDictionaryIndexingMode(vm));
}
if (i >= MIN_SPARSE_ARRAY_INDEX) {
return putDirectIndexBeyondVectorLengthWithArrayStorage(
exec, i, value, attributes, mode, createArrayStorage(vm, 0, 0));
}
if (structure(vm)->needsSlowPutIndexing()) {
ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(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(exec->vm(), value);
return putDirectIndex(exec, i, value, attributes, mode);
}
case ALL_INT32_INDEXING_TYPES: {
if (attributes & (ReadOnly | Accessor)) {
return putDirectIndexBeyondVectorLengthWithArrayStorage(
exec, i, value, attributes, mode, convertInt32ToArrayStorage(vm));
}
if (!value.isInt32()) {
convertInt32ForValue(vm, value);
return putDirectIndexBeyondVectorLength(exec, i, value, attributes, mode);
}
putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(exec, i, value);
return true;
}
case ALL_DOUBLE_INDEXING_TYPES: {
if (attributes & (ReadOnly | Accessor)) {
return putDirectIndexBeyondVectorLengthWithArrayStorage(
exec, i, value, attributes, mode, convertDoubleToArrayStorage(vm));
}
if (!value.isNumber()) {
convertDoubleToContiguous(vm);
return putDirectIndexBeyondVectorLength(exec, i, value, attributes, mode);
}
double valueAsDouble = value.asNumber();
if (valueAsDouble != valueAsDouble) {
convertDoubleToContiguous(vm);
return putDirectIndexBeyondVectorLength(exec, i, value, attributes, mode);
}
putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(exec, i, value);
return true;
}
case ALL_CONTIGUOUS_INDEXING_TYPES: {
if (attributes & (ReadOnly | Accessor)) {
return putDirectIndexBeyondVectorLengthWithArrayStorage(
exec, i, value, attributes, mode, convertContiguousToArrayStorage(vm));
}
putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(exec, i, value);
return true;
}
case ALL_ARRAY_STORAGE_INDEXING_TYPES:
return putDirectIndexBeyondVectorLengthWithArrayStorage(exec, i, value, attributes, mode, arrayStorage());
default:
RELEASE_ASSERT_NOT_REACHED();
return false;
}
}
void 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);
putDirect(vm, propertyName, function, attributes);
}
JSFunction* JSObject::putDirectBuiltinFunction(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, FunctionExecutable* functionExecutable, unsigned attributes)
{
StringImpl* name = propertyName.publicName();
if (!name)
name = vm.propertyNames->anonymous.impl();
ASSERT(name);
JSFunction* function = JSFunction::createBuiltinFunction(vm, static_cast<FunctionExecutable*>(functionExecutable), globalObject);
putDirect(vm, propertyName, function, attributes);
return function;
}
JSFunction* JSObject::putDirectBuiltinFunctionWithoutTransition(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, FunctionExecutable* functionExecutable, unsigned attributes)
{
StringImpl* name = propertyName.publicName();
if (!name)
name = vm.propertyNames->anonymous.impl();
ASSERT(name);
JSFunction* function = JSFunction::createBuiltinFunction(vm, static_cast<FunctionExecutable*>(functionExecutable), globalObject);
putDirectWithoutTransition(vm, propertyName, function, attributes);
return function;
}
void JSObject::putDirectNativeFunctionWithoutTransition(VM& vm, JSGlobalObject* globalObject, const PropertyName& propertyName, unsigned functionLength, NativeFunction nativeFunction, Intrinsic intrinsic, unsigned attributes)
{
StringImpl* name = propertyName.publicName();
ASSERT(name);
JSFunction* function = JSFunction::create(vm, globalObject, functionLength, name, nativeFunction, intrinsic);
putDirectWithoutTransition(vm, propertyName, function, attributes);
}
ALWAYS_INLINE unsigned JSObject::getNewVectorLength(unsigned currentVectorLength, unsigned currentLength, unsigned desiredLength)
{
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_VECTOR_GROW);
return std::min(increasedLength, MAX_STORAGE_VECTOR_LENGTH);
}
ALWAYS_INLINE unsigned JSObject::getNewVectorLength(unsigned desiredLength)
{
unsigned vectorLength;
unsigned length;
if (hasIndexedProperties(indexingType())) {
vectorLength = m_butterfly->vectorLength();
length = m_butterfly->publicLength();
} else {
vectorLength = 0;
length = 0;
}
return getNewVectorLength(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()[i])
numValues++;
break;
case DoubleShape: {
double value = butterfly->contiguousDouble()[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)
{
if (newLength > MAX_STORAGE_VECTOR_LENGTH)
return false;
ArrayStorage* storage = arrayStorage();
if (newLength >= MIN_SPARSE_ARRAY_INDEX
&& !isDenseEnoughForVector(newLength, storage->m_numValuesInVector))
return false;
unsigned indexBias = storage->m_indexBias;
unsigned vectorLength = storage->vectorLength();
ASSERT(newLength > vectorLength);
unsigned newVectorLength = getNewVectorLength(newLength);
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;
newButterfly->arrayStorage()->setVectorLength(newVectorLength);
setButterflyWithoutChangingStructure(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;
newButterfly->arrayStorage()->setVectorLength(newVectorLength);
newButterfly->arrayStorage()->m_indexBias = newIndexBias;
setButterflyWithoutChangingStructure(vm, newButterfly);
return true;
}
void JSObject::ensureLengthSlow(VM& vm, unsigned length)
{
ASSERT(length < MAX_ARRAY_INDEX);
ASSERT(hasContiguous(indexingType()) || hasInt32(indexingType()) || hasDouble(indexingType()) || hasUndecided(indexingType()));
ASSERT(length > m_butterfly->vectorLength());
unsigned newVectorLength = std::min(
length << 1,
MAX_STORAGE_VECTOR_LENGTH);
unsigned oldVectorLength = m_butterfly->vectorLength();
DeferGC deferGC(vm.heap);
m_butterfly.set(vm, this, m_butterfly->growArrayRight(
vm, this, structure(), structure()->outOfLineCapacity(), true,
oldVectorLength * sizeof(EncodedJSValue),
newVectorLength * sizeof(EncodedJSValue)));
m_butterfly->setVectorLength(newVectorLength);
if (hasDouble(indexingType())) {
for (unsigned i = oldVectorLength; i < newVectorLength; ++i)
m_butterfly->contiguousDouble().data()[i] = PNaN;
}
}
Butterfly* JSObject::growOutOfLineStorage(VM& vm, size_t oldSize, size_t newSize)
{
ASSERT(newSize > oldSize);
return Butterfly::createOrGrowPropertyStorage(m_butterfly.get(), vm, this, structure(vm), oldSize, newSize);
}
bool JSObject::getOwnPropertyDescriptor(ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor)
{
JSC::PropertySlot slot(this);
if (!methodTable(exec->vm())->getOwnPropertySlot(this, exec, propertyName, slot))
return false;
if (slot.slotBase() != this && slot.slotBase() && slot.slotBase()->methodTable(exec->vm())->toThis(slot.slotBase(), exec, NotStrictMode) != this)
return false;
if (slot.isAccessor())
descriptor.setAccessorDescriptor(slot.getterSetter(), slot.attributes());
else if (slot.attributes() & CustomAccessor)
descriptor.setCustomDescriptor(slot.attributes());
else
descriptor.setDescriptor(slot.getValue(exec, propertyName), slot.attributes());
return true;
}
static bool putDescriptor(ExecState* exec, JSObject* target, PropertyName propertyName, const PropertyDescriptor& descriptor, unsigned attributes, const PropertyDescriptor& oldDescriptor)
{
VM& vm = exec->vm();
if (descriptor.isGenericDescriptor() || descriptor.isDataDescriptor()) {
if (descriptor.isGenericDescriptor() && oldDescriptor.isAccessorDescriptor()) {
GetterSetter* accessor = GetterSetter::create(vm);
if (oldDescriptor.getterPresent())
accessor->setGetter(vm, oldDescriptor.getterObject());
if (oldDescriptor.setterPresent())
accessor->setSetter(vm, oldDescriptor.setterObject());
target->putDirectAccessor(exec, propertyName, accessor, attributes | Accessor);
return true;
}
JSValue newValue = jsUndefined();
if (descriptor.value())
newValue = descriptor.value();
else if (oldDescriptor.value())
newValue = oldDescriptor.value();
target->putDirect(vm, propertyName, newValue, attributes & ~Accessor);
if (attributes & ReadOnly)
target->structure(vm)->setContainsReadOnlyProperties();
return true;
}
attributes &= ~ReadOnly;
GetterSetter* accessor = GetterSetter::create(vm);
if (descriptor.getterPresent())
accessor->setGetter(vm, descriptor.getterObject());
else if (oldDescriptor.getterPresent())
accessor->setGetter(vm, oldDescriptor.getterObject());
if (descriptor.setterPresent())
accessor->setSetter(vm, descriptor.setterObject());
else if (oldDescriptor.setterPresent())
accessor->setSetter(vm, oldDescriptor.setterObject());
target->putDirectAccessor(exec, propertyName, accessor, attributes | Accessor);
return true;
}
void JSObject::putDirectMayBeIndex(ExecState* exec, PropertyName propertyName, JSValue value)
{
unsigned asIndex = propertyName.asIndex();
if (asIndex == PropertyName::NotAnIndex)
putDirect(exec->vm(), propertyName, value);
else
putDirectIndex(exec, asIndex, value);
}
class DefineOwnPropertyScope {
public:
DefineOwnPropertyScope(ExecState* exec)
: m_vm(exec->vm())
{
m_vm.setInDefineOwnProperty(true);
}
~DefineOwnPropertyScope()
{
m_vm.setInDefineOwnProperty(false);
}
private:
VM& m_vm;
};
bool JSObject::defineOwnNonIndexProperty(ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
{
DefineOwnPropertyScope scope(exec);
PropertyDescriptor current;
if (!getOwnPropertyDescriptor(exec, propertyName, current)) {
if (!isExtensible()) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to define property on object that is not extensible.")));
return false;
}
PropertyDescriptor oldDescriptor;
oldDescriptor.setValue(jsUndefined());
return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributes(), oldDescriptor);
}
if (descriptor.isEmpty())
return true;
if (current.equalTo(exec, descriptor))
return true;
if (!current.configurable()) {
if (descriptor.configurable()) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to configurable attribute of unconfigurable property.")));
return false;
}
if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable()) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change enumerable attribute of unconfigurable property.")));
return false;
}
}
if (descriptor.isGenericDescriptor()) {
if (!current.attributesEqual(descriptor)) {
methodTable(exec->vm())->deleteProperty(this, exec, propertyName);
return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
}
return true;
}
if (descriptor.isDataDescriptor() != current.isDataDescriptor()) {
if (!current.configurable()) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change access mechanism for an unconfigurable property.")));
return false;
}
methodTable(exec->vm())->deleteProperty(this, exec, propertyName);
return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
}
if (descriptor.isDataDescriptor()) {
if (!current.configurable()) {
if (!current.writable() && descriptor.writable()) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change writable attribute of unconfigurable property.")));
return false;
}
if (!current.writable()) {
if (descriptor.value() && !sameValue(exec, current.value(), descriptor.value())) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change value of a readonly property.")));
return false;
}
}
}
if (current.attributesEqual(descriptor) && !descriptor.value())
return true;
methodTable(exec->vm())->deleteProperty(this, exec, propertyName);
return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
}
ASSERT(descriptor.isAccessorDescriptor());
if (!current.configurable()) {
if (descriptor.setterPresent() && !(current.setterPresent() && JSValue::strictEqual(exec, current.setter(), descriptor.setter()))) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change the setter of an unconfigurable property.")));
return false;
}
if (descriptor.getterPresent() && !(current.getterPresent() && JSValue::strictEqual(exec, current.getter(), descriptor.getter()))) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change the getter of an unconfigurable property.")));
return false;
}
if (current.attributes() & CustomAccessor) {
if (throwException)
exec->vm().throwException(exec, createTypeError(exec, ASCIILiteral("Attempting to change access mechanism for an unconfigurable property.")));
return false;
}
}
JSValue accessor = getDirect(exec->vm(), propertyName);
if (!accessor)
return false;
GetterSetter* getterSetter;
if (accessor.isCustomGetterSetter())
getterSetter = GetterSetter::create(exec->vm());
else {
ASSERT(accessor.isGetterSetter());
getterSetter = asGetterSetter(accessor);
}
if (descriptor.setterPresent())
getterSetter->setSetter(exec->vm(), descriptor.setterObject());
if (descriptor.getterPresent())
getterSetter->setGetter(exec->vm(), descriptor.getterObject());
if (current.attributesEqual(descriptor))
return true;
methodTable(exec->vm())->deleteProperty(this, exec, propertyName);
unsigned attrs = descriptor.attributesOverridingCurrent(current);
putDirectAccessor(exec, propertyName, getterSetter, attrs | Accessor);
return true;
}
bool JSObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool throwException)
{
unsigned index = propertyName.asIndex();
if (index != PropertyName::NotAnIndex) {
return object->defineOwnIndexedProperty(exec, index, descriptor, throwException);
}
return object->defineOwnNonIndexProperty(exec, propertyName, descriptor, throwException);
}
JSObject* throwTypeError(ExecState* exec, const String& message)
{
return exec->vm().throwException(exec, createTypeError(exec, message));
}
void JSObject::shiftButterflyAfterFlattening(VM& vm, size_t outOfLineCapacityBefore, size_t outOfLineCapacityAfter)
{
Butterfly* butterfly = this->butterfly();
size_t preCapacity = this->butterflyPreCapacity();
void* currentBase = butterfly->base(preCapacity, outOfLineCapacityAfter);
void* newBase = butterfly->base(preCapacity, outOfLineCapacityBefore);
memmove(newBase, currentBase, this->butterflyTotalSize());
setButterflyWithoutChangingStructure(vm, Butterfly::fromBase(newBase, preCapacity, outOfLineCapacityAfter));
}
}