blob: 6e020b0ebb9231d60acd64770338e2362e2383e3 [file] [log] [blame]
/*
* Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
* Copyright (C) 2001 Peter Kelly (pmk@post.com)
* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2007 Eric Seidel (eric@webkit.org)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "JSObject.h"
#include "DatePrototype.h"
#include "ErrorConstructor.h"
#include "GetterSetter.h"
#include "JSFunction.h"
#include "JSGlobalObject.h"
#include "JSGlobalThis.h"
#include "Lookup.h"
#include "NativeErrorConstructor.h"
#include "Nodes.h"
#include "ObjectPrototype.h"
#include "Operations.h"
#include "PropertyDescriptor.h"
#include "PropertyNameArray.h"
#include <math.h>
#include <wtf/Assertions.h>
namespace JSC {
ASSERT_CLASS_FITS_IN_CELL(JSObject);
ASSERT_CLASS_FITS_IN_CELL(JSNonFinalObject);
ASSERT_CLASS_FITS_IN_CELL(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)
{
// Add properties from the static hashtables of properties
for (; classInfo; classInfo = classInfo->parentClass) {
const HashTable* table = classInfo->propHashTable(exec);
if (!table)
continue;
table->initializeIfNeeded(exec);
ASSERT(table->table);
int hashSizeMask = table->compactSize - 1;
const HashEntry* entry = table->table;
for (int i = 0; i <= hashSizeMask; ++i, ++entry) {
if (entry->key() && (!(entry->attributes() & DontEnum) || (mode == IncludeDontEnumProperties)))
propertyNames.add(entry->key());
}
}
}
void JSObject::finalize(JSCell* cell)
{
delete [] static_cast<JSObject*>(cell)->m_propertyStorage.get();
}
void JSObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
JSObject* thisObject = static_cast<JSObject*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info);
#ifndef NDEBUG
bool wasCheckingForDefaultMarkViolation = visitor.m_isCheckingForDefaultMarkViolation;
visitor.m_isCheckingForDefaultMarkViolation = false;
#endif
JSCell::visitChildren(thisObject, visitor);
PropertyStorage storage = thisObject->propertyStorage();
size_t storageSize = thisObject->structure()->propertyStorageSize();
visitor.appendValues(storage, storageSize);
if (thisObject->m_inheritorID)
visitor.append(&thisObject->m_inheritorID);
#ifndef NDEBUG
visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation;
#endif
}
UString JSObject::className(const JSObject* object)
{
const ClassInfo* info = object->classInfo();
ASSERT(info);
return info->className;
}
bool JSObject::getOwnPropertySlotByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, PropertySlot& slot)
{
JSObject* thisObject = static_cast<JSObject*>(cell);
return thisObject->methodTable()->getOwnPropertySlot(thisObject, exec, Identifier::from(exec, propertyName), slot);
}
static void throwSetterError(ExecState* exec)
{
throwError(exec, createTypeError(exec, "setting a property that has only a getter"));
}
// ECMA 8.6.2.2
void JSObject::put(JSCell* cell, ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
{
JSObject* thisObject = static_cast<JSObject*>(cell);
ASSERT(value);
ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
JSGlobalData& globalData = exec->globalData();
if (propertyName == exec->propertyNames().underscoreProto) {
// Setting __proto__ to a non-object, non-null value is silently ignored to match Mozilla.
if (!value.isObject() && !value.isNull())
return;
if (!thisObject->isExtensible()) {
if (slot.isStrictMode())
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return;
}
if (!thisObject->setPrototypeWithCycleCheck(globalData, value))
throwError(exec, createError(exec, "cyclic __proto__ value"));
return;
}
// Check if there are any setters or getters in the prototype chain
JSValue prototype;
for (JSObject* obj = thisObject; !obj->structure()->hasGetterSetterProperties(); obj = asObject(prototype)) {
prototype = obj->prototype();
if (prototype.isNull()) {
if (!thisObject->putDirectInternal(globalData, propertyName, value, 0, true, slot, getJSFunction(value)) && slot.isStrictMode())
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return;
}
}
unsigned attributes;
JSCell* specificValue;
if ((thisObject->structure()->get(globalData, propertyName, attributes, specificValue) != WTF::notFound) && attributes & ReadOnly) {
if (slot.isStrictMode())
throwError(exec, createTypeError(exec, StrictModeReadonlyPropertyWriteError));
return;
}
for (JSObject* obj = thisObject; ; obj = asObject(prototype)) {
if (JSValue gs = obj->getDirect(globalData, propertyName)) {
if (gs.isGetterSetter()) {
JSObject* setterFunc = asGetterSetter(gs)->setter();
if (!setterFunc) {
throwSetterError(exec);
return;
}
CallData callData;
CallType callType = setterFunc->methodTable()->getCallData(setterFunc, callData);
MarkedArgumentBuffer args;
args.append(value);
// If this is WebCore's global object then we need to substitute the shell.
call(exec, setterFunc, callType, callData, thisObject->methodTable()->toThisObject(thisObject, exec), args);
return;
}
// If there's an existing property on the object or one of its
// prototypes it should be replaced, so break here.
break;
}
prototype = obj->prototype();
if (prototype.isNull())
break;
}
if (!thisObject->putDirectInternal(globalData, propertyName, value, 0, true, slot, getJSFunction(value)) && slot.isStrictMode())
throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
return;
}
void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value)
{
PutPropertySlot slot;
JSObject* thisObject = static_cast<JSObject*>(cell);
thisObject->methodTable()->put(thisObject, exec, Identifier::from(exec, propertyName), value, slot);
}
void JSObject::putWithAttributes(JSGlobalData* globalData, const Identifier& propertyName, JSValue value, unsigned attributes, bool checkReadOnly, PutPropertySlot& slot)
{
putDirectInternal(*globalData, propertyName, value, attributes, checkReadOnly, slot, getJSFunction(value));
}
void JSObject::putWithAttributes(JSGlobalData* globalData, const Identifier& propertyName, JSValue value, unsigned attributes)
{
PutPropertySlot slot;
putDirectInternal(*globalData, propertyName, value, attributes, true, slot, getJSFunction(value));
}
void JSObject::putWithAttributes(JSGlobalData* globalData, unsigned propertyName, JSValue value, unsigned attributes)
{
putWithAttributes(globalData, Identifier::from(globalData, propertyName), value, attributes);
}
void JSObject::putWithAttributes(ExecState* exec, const Identifier& propertyName, JSValue value, unsigned attributes, bool checkReadOnly, PutPropertySlot& slot)
{
JSGlobalData& globalData = exec->globalData();
putDirectInternal(globalData, propertyName, value, attributes, checkReadOnly, slot, getJSFunction(value));
}
void JSObject::putWithAttributes(ExecState* exec, const Identifier& propertyName, JSValue value, unsigned attributes)
{
PutPropertySlot slot;
JSGlobalData& globalData = exec->globalData();
putDirectInternal(globalData, propertyName, value, attributes, true, slot, getJSFunction(value));
}
void JSObject::putWithAttributes(ExecState* exec, unsigned propertyName, JSValue value, unsigned attributes)
{
putWithAttributes(exec, Identifier::from(exec, propertyName), value, attributes);
}
bool JSObject::hasProperty(ExecState* exec, const Identifier& propertyName) const
{
PropertySlot slot;
return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
}
bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const
{
PropertySlot slot;
return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
}
// ECMA 8.6.2.5
bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, const Identifier& propertyName)
{
JSObject* thisObject = static_cast<JSObject*>(cell);
if (!thisObject->staticFunctionsReified())
thisObject->reifyStaticFunctionsForDelete(exec);
unsigned attributes;
JSCell* specificValue;
if (thisObject->structure()->get(exec->globalData(), propertyName, attributes, specificValue) != WTF::notFound) {
if ((attributes & DontDelete))
return false;
thisObject->removeDirect(exec->globalData(), propertyName);
return true;
}
// Look in the static hashtable of properties
const HashEntry* entry = thisObject->findPropertyHashEntry(exec, propertyName);
if (entry && entry->attributes() & DontDelete)
return false; // this builtin property can't be deleted
// FIXME: Should the code here actually do some deletion?
return true;
}
bool JSObject::hasOwnProperty(ExecState* exec, const Identifier& propertyName) const
{
PropertySlot slot;
return const_cast<JSObject*>(this)->methodTable()->getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
}
bool JSObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
{
JSObject* thisObject = static_cast<JSObject*>(cell);
return thisObject->methodTable()->deleteProperty(thisObject, exec, Identifier::from(exec, propertyName));
}
static ALWAYS_INLINE JSValue callDefaultValueFunction(ExecState* exec, const JSObject* object, const Identifier& propertyName)
{
JSValue function = object->get(exec, propertyName);
CallData callData;
CallType callType = getCallData(function, callData);
if (callType == CallTypeNone)
return exec->exception();
// Prevent "toString" and "valueOf" from observing execution if an exception
// is pending.
if (exec->hadException())
return exec->exception();
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()->defaultValue(this, exec, PreferNumber);
number = result.toNumber(exec);
return !result.isString();
}
// ECMA 8.6.2.6
JSValue JSObject::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint)
{
// Must call toString first for Date objects.
if ((hint == PreferString) || (hint != PreferNumber && object->prototype() == exec->lexicalGlobalObject()->datePrototype())) {
JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().toString);
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 throwError(exec, createTypeError(exec, "No default value"));
}
const HashEntry* JSObject::findPropertyHashEntry(ExecState* exec, const Identifier& propertyName) const
{
for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
if (const HashTable* propHashTable = info->propHashTable(exec)) {
if (const HashEntry* entry = propHashTable->entry(exec, propertyName))
return entry;
}
}
return 0;
}
void JSObject::defineGetter(JSObject* thisObject, ExecState* exec, const Identifier& propertyName, JSObject* getterFunction, unsigned attributes)
{
if (propertyName == exec->propertyNames().underscoreProto) {
// Defining a getter for __proto__ is silently ignored.
return;
}
JSValue object = thisObject->getDirect(exec->globalData(), propertyName);
if (object && object.isGetterSetter()) {
ASSERT(thisObject->structure()->hasGetterSetterProperties());
asGetterSetter(object)->setGetter(exec->globalData(), getterFunction);
return;
}
JSGlobalData& globalData = exec->globalData();
PutPropertySlot slot;
GetterSetter* getterSetter = GetterSetter::create(exec);
thisObject->putDirectInternal(globalData, propertyName, getterSetter, attributes | Getter, true, slot, 0);
// putDirect will change our Structure if we add a new property. For
// getters and setters, though, we also need to change our Structure
// if we override an existing non-getter or non-setter.
if (slot.type() != PutPropertySlot::NewProperty) {
if (!thisObject->structure()->isDictionary())
thisObject->setStructure(exec->globalData(), Structure::getterSetterTransition(globalData, thisObject->structure()));
}
thisObject->structure()->setHasGetterSetterProperties(true);
getterSetter->setGetter(globalData, getterFunction);
}
void JSObject::initializeGetterSetterProperty(ExecState* exec, const Identifier& propertyName, GetterSetter* getterSetter, unsigned attributes)
{
// Set an inital property on an object; the property must not already exist & the attribute flags must be set correctly.
ASSERT(structure()->get(exec->globalData(), propertyName) == WTF::notFound);
ASSERT(static_cast<bool>(getterSetter->getter()) == static_cast<bool>(attributes & Getter));
ASSERT(static_cast<bool>(getterSetter->setter()) == static_cast<bool>(attributes & Setter));
JSGlobalData& globalData = exec->globalData();
PutPropertySlot slot;
putDirectInternal(globalData, propertyName, getterSetter, attributes | Getter, true, slot, 0);
// putDirect will change our Structure if we add a new property. For
// getters and setters, though, we also need to change our Structure
// if we override an existing non-getter or non-setter.
if (slot.type() != PutPropertySlot::NewProperty) {
if (!structure()->isDictionary())
setStructure(exec->globalData(), Structure::getterSetterTransition(globalData, structure()));
}
structure()->setHasGetterSetterProperties(true);
}
void JSObject::defineSetter(JSObject* thisObject, ExecState* exec, const Identifier& propertyName, JSObject* setterFunction, unsigned attributes)
{
if (propertyName == exec->propertyNames().underscoreProto) {
// Defining a setter for __proto__ is silently ignored.
return;
}
JSValue object = thisObject->getDirect(exec->globalData(), propertyName);
if (object && object.isGetterSetter()) {
ASSERT(thisObject->structure()->hasGetterSetterProperties());
asGetterSetter(object)->setSetter(exec->globalData(), setterFunction);
return;
}
PutPropertySlot slot;
GetterSetter* getterSetter = GetterSetter::create(exec);
thisObject->putDirectInternal(exec->globalData(), propertyName, getterSetter, attributes | Setter, true, slot, 0);
// putDirect will change our Structure if we add a new property. For
// getters and setters, though, we also need to change our Structure
// if we override an existing non-getter or non-setter.
if (slot.type() != PutPropertySlot::NewProperty) {
if (!thisObject->structure()->isDictionary())
thisObject->setStructure(exec->globalData(), Structure::getterSetterTransition(exec->globalData(), thisObject->structure()));
}
thisObject->structure()->setHasGetterSetterProperties(true);
getterSetter->setSetter(exec->globalData(), setterFunction);
}
JSValue JSObject::lookupGetter(ExecState* exec, const Identifier& propertyName)
{
PropertyDescriptor descriptor;
if (!getPropertyDescriptor(exec, propertyName, descriptor))
return jsUndefined();
if (!descriptor.getterPresent())
return jsUndefined();
return descriptor.getter();
}
JSValue JSObject::lookupSetter(ExecState* exec, const Identifier& propertyName)
{
PropertyDescriptor descriptor;
if (!getPropertyDescriptor(exec, propertyName, descriptor))
return jsUndefined();
if (!descriptor.setterPresent())
return jsUndefined();
return descriptor.setter();
}
bool JSObject::hasInstance(JSObject*, ExecState* exec, JSValue value, JSValue proto)
{
if (!value.isObject())
return false;
if (!proto.isObject()) {
throwError(exec, createTypeError(exec, "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::propertyIsEnumerable(ExecState* exec, const Identifier& propertyName) const
{
PropertyDescriptor descriptor;
if (!const_cast<JSObject*>(this)->getOwnPropertyDescriptor(exec, propertyName, descriptor))
return false;
return descriptor.enumerable();
}
bool JSObject::getPropertySpecificValue(ExecState* exec, const Identifier& propertyName, JSCell*& specificValue) const
{
unsigned attributes;
if (structure()->get(exec->globalData(), propertyName, attributes, specificValue) != WTF::notFound)
return true;
// This could be a function within the static table? - should probably
// also look in the hash? This currently should not be a problem, since
// we've currently always call 'get' first, which should have populated
// the normal storage.
return false;
}
void JSObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
{
object->methodTable()->getOwnPropertyNames(object, exec, propertyNames, mode);
if (object->prototype().isNull())
return;
JSObject* prototype = asObject(object->prototype());
while(1) {
if (prototype->structure()->typeInfo().overridesGetPropertyNames()) {
prototype->methodTable()->getPropertyNames(prototype, exec, propertyNames, mode);
break;
}
prototype->methodTable()->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)
{
object->structure()->getPropertyNamesFromStructure(exec->globalData(), propertyNames, mode);
if (!object->staticFunctionsReified())
getClassPropertyNames(exec, object->classInfo(), propertyNames, mode);
}
bool JSObject::toBoolean(ExecState*) const
{
return true;
}
double JSObject::toNumber(ExecState* exec) const
{
JSValue primitive = toPrimitive(exec, PreferNumber);
if (exec->hadException()) // should be picked up soon in Nodes.cpp
return 0.0;
return primitive.toNumber(exec);
}
UString JSObject::toString(ExecState* exec) const
{
JSValue primitive = toPrimitive(exec, PreferString);
if (exec->hadException())
return "";
return primitive.toString(exec);
}
JSObject* JSObject::toThisObject(JSCell* cell, ExecState*)
{
return static_cast<JSObject*>(cell);
}
JSObject* JSObject::unwrappedObject()
{
if (isGlobalThis())
return static_cast<JSGlobalThis*>(this)->unwrappedObject();
return this;
}
void JSObject::seal(JSGlobalData& globalData)
{
if (isSealed(globalData))
return;
preventExtensions(globalData);
setStructure(globalData, Structure::sealTransition(globalData, structure()));
}
void JSObject::freeze(JSGlobalData& globalData)
{
if (isFrozen(globalData))
return;
preventExtensions(globalData);
setStructure(globalData, Structure::freezeTransition(globalData, structure()));
}
void JSObject::preventExtensions(JSGlobalData& globalData)
{
if (isExtensible())
setStructure(globalData, Structure::preventExtensionsTransition(globalData, structure()));
}
// This presently will flatten to an uncachable dictionary; this is suitable
// for use in delete, we may want to do something different elsewhere.
void JSObject::reifyStaticFunctionsForDelete(ExecState* exec)
{
ASSERT(!staticFunctionsReified());
JSGlobalData& globalData = exec->globalData();
// If this object's ClassInfo has no static properties, then nothing to reify!
// We can safely set the flag to avoid the expensive check again in the future.
if (!classInfo()->hasStaticProperties()) {
structure()->setStaticFunctionsReified();
return;
}
if (!structure()->isUncacheableDictionary())
setStructure(globalData, Structure::toUncacheableDictionaryTransition(globalData, structure()));
for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
const HashTable* hashTable = info->propHashTable(globalObject()->globalExec());
if (!hashTable)
continue;
PropertySlot slot;
for (HashTable::ConstIterator iter = hashTable->begin(globalData); iter != hashTable->end(globalData); ++iter) {
if (iter->attributes() & Function)
setUpStaticFunctionSlot(globalObject()->globalExec(), *iter, this, Identifier(&globalData, iter->key()), slot);
}
}
structure()->setStaticFunctionsReified();
}
void JSObject::removeDirect(JSGlobalData& globalData, const Identifier& propertyName)
{
if (structure()->get(globalData, propertyName) == WTF::notFound)
return;
size_t offset;
if (structure()->isUncacheableDictionary()) {
offset = structure()->removePropertyWithoutTransition(globalData, propertyName);
if (offset != WTF::notFound)
putUndefinedAtDirectOffset(offset);
return;
}
setStructure(globalData, Structure::removePropertyTransition(globalData, structure(), propertyName, offset));
if (offset != WTF::notFound)
putUndefinedAtDirectOffset(offset);
}
NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, WriteBarrierBase<Unknown>* location)
{
if (JSObject* getterFunction = asGetterSetter(location->get())->getter()) {
if (!structure()->isDictionary())
slot.setCacheableGetterSlot(this, getterFunction, offsetForLocation(location));
else
slot.setGetterSlot(getterFunction);
} else
slot.setUndefined();
}
Structure* JSObject::createInheritorID(JSGlobalData& globalData)
{
m_inheritorID.set(globalData, this, createEmptyObjectStructure(globalData, structure()->globalObject(), this));
ASSERT(m_inheritorID->isEmpty());
return m_inheritorID.get();
}
void JSObject::allocatePropertyStorage(JSGlobalData& globalData, size_t oldSize, size_t newSize)
{
ASSERT(newSize > oldSize);
// It's important that this function not rely on structure(), since
// we might be in the middle of a transition.
PropertyStorage newPropertyStorage = 0;
newPropertyStorage = new WriteBarrierBase<Unknown>[newSize];
PropertyStorage oldPropertyStorage = m_propertyStorage.get();
ASSERT(newPropertyStorage);
for (unsigned i = 0; i < oldSize; ++i)
newPropertyStorage[i] = oldPropertyStorage[i];
if (isUsingInlineStorage())
Heap::heap(this)->addFinalizer(this, &finalize);
else
delete [] oldPropertyStorage;
m_propertyStorage.set(globalData, this, newPropertyStorage);
}
bool JSObject::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
{
unsigned attributes = 0;
JSCell* cell = 0;
size_t offset = structure()->get(exec->globalData(), propertyName, attributes, cell);
if (offset == WTF::notFound)
return false;
descriptor.setDescriptor(getDirectOffset(offset), attributes);
return true;
}
bool JSObject::getPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor)
{
JSObject* object = this;
while (true) {
if (object->getOwnPropertyDescriptor(exec, propertyName, descriptor))
return true;
JSValue prototype = object->prototype();
if (!prototype.isObject())
return false;
object = asObject(prototype);
}
}
static bool putDescriptor(ExecState* exec, JSObject* target, const Identifier& propertyName, PropertyDescriptor& descriptor, unsigned attributes, const PropertyDescriptor& oldDescriptor)
{
if (descriptor.isGenericDescriptor() || descriptor.isDataDescriptor()) {
if (descriptor.isGenericDescriptor() && oldDescriptor.isAccessorDescriptor()) {
GetterSetter* accessor = GetterSetter::create(exec);
if (oldDescriptor.getter()) {
attributes |= Getter;
accessor->setGetter(exec->globalData(), asObject(oldDescriptor.getter()));
}
if (oldDescriptor.setter()) {
attributes |= Setter;
accessor->setSetter(exec->globalData(), asObject(oldDescriptor.setter()));
}
target->putWithAttributes(exec, propertyName, accessor, attributes);
return true;
}
JSValue newValue = jsUndefined();
if (descriptor.value())
newValue = descriptor.value();
else if (oldDescriptor.value())
newValue = oldDescriptor.value();
target->putWithAttributes(exec, propertyName, newValue, attributes & ~(Getter | Setter));
return true;
}
attributes &= ~ReadOnly;
if (descriptor.getter() && descriptor.getter().isObject())
target->methodTable()->defineGetter(target, exec, propertyName, asObject(descriptor.getter()), attributes);
if (exec->hadException())
return false;
if (descriptor.setter() && descriptor.setter().isObject())
target->methodTable()->defineSetter(target, exec, propertyName, asObject(descriptor.setter()), attributes);
return !exec->hadException();
}
bool JSObject::defineOwnProperty(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor, bool throwException)
{
// If we have a new property we can just put it on normally
PropertyDescriptor current;
if (!getOwnPropertyDescriptor(exec, propertyName, current)) {
// unless extensions are prevented!
if (!isExtensible()) {
if (throwException)
throwError(exec, createTypeError(exec, "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;
// Filter out invalid changes
if (!current.configurable()) {
if (descriptor.configurable()) {
if (throwException)
throwError(exec, createTypeError(exec, "Attempting to configurable attribute of unconfigurable property."));
return false;
}
if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable()) {
if (throwException)
throwError(exec, createTypeError(exec, "Attempting to change enumerable attribute of unconfigurable property."));
return false;
}
}
// A generic descriptor is simply changing the attributes of an existing property
if (descriptor.isGenericDescriptor()) {
if (!current.attributesEqual(descriptor)) {
methodTable()->deleteProperty(this, exec, propertyName);
putDescriptor(exec, this, propertyName, descriptor, current.attributesWithOverride(descriptor), current);
}
return true;
}
// Changing between a normal property or an accessor property
if (descriptor.isDataDescriptor() != current.isDataDescriptor()) {
if (!current.configurable()) {
if (throwException)
throwError(exec, createTypeError(exec, "Attempting to change access mechanism for an unconfigurable property."));
return false;
}
methodTable()->deleteProperty(this, exec, propertyName);
return putDescriptor(exec, this, propertyName, descriptor, current.attributesWithOverride(descriptor), current);
}
// Changing the value and attributes of an existing property
if (descriptor.isDataDescriptor()) {
if (!current.configurable()) {
if (!current.writable() && descriptor.writable()) {
if (throwException)
throwError(exec, createTypeError(exec, "Attempting to change writable attribute of unconfigurable property."));
return false;
}
if (!current.writable()) {
if (descriptor.value() || !JSValue::strictEqual(exec, current.value(), descriptor.value())) {
if (throwException)
throwError(exec, createTypeError(exec, "Attempting to change value of a readonly property."));
return false;
}
}
} else if (current.attributesEqual(descriptor)) {
if (!descriptor.value())
return true;
PutPropertySlot slot;
methodTable()->put(this, exec, propertyName, descriptor.value(), slot);
if (exec->hadException())
return false;
return true;
}
methodTable()->deleteProperty(this, exec, propertyName);
return putDescriptor(exec, this, propertyName, descriptor, current.attributesWithOverride(descriptor), current);
}
// Changing the accessor functions of an existing accessor property
ASSERT(descriptor.isAccessorDescriptor());
if (!current.configurable()) {
if (descriptor.setterPresent() && !(current.setterPresent() && JSValue::strictEqual(exec, current.setter(), descriptor.setter()))) {
if (throwException)
throwError(exec, createTypeError(exec, "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)
throwError(exec, createTypeError(exec, "Attempting to change the getter of an unconfigurable property."));
return false;
}
}
JSValue accessor = getDirect(exec->globalData(), propertyName);
if (!accessor)
return false;
GetterSetter* getterSetter = asGetterSetter(accessor);
if (current.attributesEqual(descriptor)) {
if (descriptor.setter())
getterSetter->setSetter(exec->globalData(), asObject(descriptor.setter()));
if (descriptor.getter())
getterSetter->setGetter(exec->globalData(), asObject(descriptor.getter()));
return true;
}
methodTable()->deleteProperty(this, exec, propertyName);
unsigned attrs = current.attributesWithOverride(descriptor);
if (descriptor.setter())
attrs |= Setter;
if (descriptor.getter())
attrs |= Getter;
putDirect(exec->globalData(), propertyName, getterSetter, attrs);
return true;
}
JSObject* throwTypeError(ExecState* exec, const UString& message)
{
return throwError(exec, createTypeError(exec, message));
}
} // namespace JSC