/* * 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 "CopiedSpaceInlineMethods.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 #include namespace JSC { JSCell* getCallableObjectSlow(JSCell* cell) { Structure* structure = cell->structure(); if (structure->typeInfo().type() == JSFunctionType) return cell; if (structure->classInfo()->isSubClassOf(&InternalFunction::s_info)) return cell; return 0; } ASSERT_CLASS_FITS_IN_CELL(JSObject); ASSERT_CLASS_FITS_IN_CELL(JSNonFinalObject); ASSERT_CLASS_FITS_IN_CELL(JSFinalObject); ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSObject); ASSERT_HAS_TRIVIAL_DESTRUCTOR(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) { // 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)) && !((entry->attributes() & Function) && didReify)) propertyNames.add(entry->key()); } } } void JSObject::visitChildren(JSCell* cell, SlotVisitor& visitor) { JSObject* thisObject = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info); #if !ASSERT_DISABLED bool wasCheckingForDefaultMarkViolation = visitor.m_isCheckingForDefaultMarkViolation; visitor.m_isCheckingForDefaultMarkViolation = false; #endif JSCell::visitChildren(thisObject, visitor); PropertyStorage storage = thisObject->outOfLineStorage(); if (storage) { size_t storageSize = thisObject->structure()->outOfLineSizeForKnownNonFinalObject(); size_t capacity = thisObject->structure()->outOfLineCapacity(); // We have this extra temp here to slake GCC's thirst for the blood of those who dereference type-punned pointers. void* temp = storage - capacity - 1; visitor.copyAndAppend(&temp, capacity * sizeof(WriteBarrierBase), (storage - storageSize - 1)->slot(), storageSize); storage = static_cast(temp) + capacity + 1; thisObject->m_outOfLineStorage.set(storage, StorageBarrier::Unchecked); } if (thisObject->m_inheritorID) visitor.append(&thisObject->m_inheritorID); #if !ASSERT_DISABLED visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation; #endif } void JSFinalObject::visitChildren(JSCell* cell, SlotVisitor& visitor) { JSFinalObject* thisObject = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info); #if !ASSERT_DISABLED bool wasCheckingForDefaultMarkViolation = visitor.m_isCheckingForDefaultMarkViolation; visitor.m_isCheckingForDefaultMarkViolation = false; #endif JSCell::visitChildren(thisObject, visitor); PropertyStorage storage = thisObject->outOfLineStorage(); if (storage) { size_t storageSize = thisObject->structure()->outOfLineSizeForKnownFinalObject(); size_t capacity = thisObject->structure()->outOfLineCapacity(); // We have this extra temp here to slake GCC's thirst for the blood of those who dereference type-punned pointers. void* temp = storage - capacity - 1; visitor.copyAndAppend(&temp, thisObject->structure()->outOfLineCapacity() * sizeof(WriteBarrierBase), (storage - storageSize - 1)->slot(), storageSize); storage = static_cast(temp) + capacity + 1; thisObject->m_outOfLineStorage.set(storage, StorageBarrier::Unchecked); } if (thisObject->m_inheritorID) visitor.append(&thisObject->m_inheritorID); size_t storageSize = thisObject->structure()->inlineSizeForKnownFinalObject(); visitor.appendValues(thisObject->inlineStorage(), storageSize); #if !ASSERT_DISABLED 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 = jsCast(cell); return thisObject->methodTable()->getOwnPropertySlot(thisObject, exec, Identifier::from(exec, propertyName), slot); } // ECMA 8.6.2.2 void JSObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) { JSObject* thisObject = jsCast(cell); ASSERT(value); ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject)); JSGlobalData& globalData = exec->globalData(); // Check if there are any setters or getters in the prototype chain JSValue prototype; if (propertyName != exec->propertyNames().underscoreProto) { for (JSObject* obj = thisObject; !obj->structure()->hasReadOnlyOrGetterSetterPropertiesExcludingProto(); obj = asObject(prototype)) { prototype = obj->prototype(); if (prototype.isNull()) { if (!thisObject->putDirectInternal(globalData, propertyName, value, 0, slot, getCallableObject(value)) && slot.isStrictMode()) throwTypeError(exec, StrictModeReadonlyPropertyWriteError); return; } } } for (JSObject* obj = thisObject; ; obj = asObject(prototype)) { unsigned attributes; JSCell* specificValue; PropertyOffset offset = obj->structure()->get(globalData, propertyName, attributes, specificValue); if (offset != invalidOffset) { if (attributes & ReadOnly) { if (slot.isStrictMode()) throwError(exec, createTypeError(exec, StrictModeReadonlyPropertyWriteError)); return; } JSValue gs = obj->getDirectOffset(offset); if (gs.isGetterSetter()) { JSObject* setterFunc = asGetterSetter(gs)->setter(); if (!setterFunc) { if (slot.isStrictMode()) throwError(exec, createTypeError(exec, "setting a property that has only a getter")); 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, slot, getCallableObject(value)) && slot.isStrictMode()) throwTypeError(exec, StrictModeReadonlyPropertyWriteError); return; } void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow) { PutPropertySlot slot(shouldThrow); JSObject* thisObject = jsCast(cell); thisObject->methodTable()->put(thisObject, exec, Identifier::from(exec, propertyName), value, slot); } void JSObject::putDirectVirtual(JSObject* object, ExecState* exec, PropertyName propertyName, JSValue value, unsigned attributes) { ASSERT(!value.isGetterSetter() && !(attributes & Accessor)); PutPropertySlot slot; object->putDirectInternal(exec->globalData(), propertyName, value, attributes, slot, getCallableObject(value)); } bool JSObject::setPrototypeWithCycleCheck(JSGlobalData& globalData, JSValue prototype) { JSValue checkFor = this; if (this->isGlobalObject()) checkFor = jsCast(this)->globalExec()->thisValue(); JSValue nextPrototype = prototype; while (nextPrototype && nextPrototype.isObject()) { if (nextPrototype == checkFor) return false; nextPrototype = asObject(nextPrototype)->prototype(); } setPrototype(globalData, prototype); return true; } bool JSObject::allowsAccessFrom(ExecState* exec) { JSGlobalObject* globalObject = isGlobalThis() ? jsCast(this)->unwrappedObject() : this->globalObject(); return globalObject->globalObjectMethodTable()->allowsAccessFrom(globalObject, exec); } void JSObject::putDirectAccessor(JSGlobalData& globalData, PropertyName propertyName, JSValue value, unsigned attributes) { ASSERT(value.isGetterSetter() && (attributes & Accessor)); PutPropertySlot slot; putDirectInternal(globalData, propertyName, value, attributes, slot, getCallableObject(value)); // putDirect will change our Structure if we add a new property. For // getters and setters, though, we also need to change our Structure // if we override an existing non-getter or non-setter. if (slot.type() != PutPropertySlot::NewProperty) setStructure(globalData, Structure::attributeChangeTransition(globalData, structure(), propertyName, attributes)); if (attributes & ReadOnly) structure()->setContainsReadOnlyProperties(); structure()->setHasGetterSetterProperties(propertyName == globalData.propertyNames->underscoreProto); } bool JSObject::hasProperty(ExecState* exec, PropertyName propertyName) const { PropertySlot slot; return const_cast(this)->getPropertySlot(exec, propertyName, slot); } bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const { PropertySlot slot; return const_cast(this)->getPropertySlot(exec, propertyName, slot); } // ECMA 8.6.2.5 bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) { JSObject* thisObject = jsCast(cell); if (!thisObject->staticFunctionsReified()) thisObject->reifyStaticFunctionsForDelete(exec); unsigned attributes; JSCell* specificValue; if (isValidOffset(thisObject->structure()->get(exec->globalData(), propertyName, attributes, specificValue))) { if (attributes & DontDelete && !exec->globalData().isInDefineOwnProperty()) 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 && !exec->globalData().isInDefineOwnProperty()) 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, PropertyName propertyName) const { PropertySlot slot; return const_cast(this)->methodTable()->getOwnPropertySlot(const_cast(this), exec, propertyName, slot); } bool JSObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName) { JSObject* thisObject = jsCast(cell); return thisObject->methodTable()->deleteProperty(thisObject, exec, Identifier::from(exec, propertyName)); } 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(); // 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(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, PropertyName propertyName) const { for (const ClassInfo* info = classInfo(); info; info = info->parentClass) { if (const HashTable* propHashTable = info->propHashTable(exec)) { if (const HashEntry* entry = propHashTable->entry(exec, propertyName)) return entry; } } return 0; } 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(this)->methodTable()->getOwnPropertyDescriptor(const_cast(this), exec, propertyName, descriptor)) return false; return descriptor.enumerable(); } bool JSObject::getPropertySpecificValue(ExecState* exec, PropertyName propertyName, JSCell*& specificValue) const { unsigned attributes; if (isValidOffset(structure()->get(exec->globalData(), propertyName, attributes, specificValue))) 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) { getClassPropertyNames(exec, object->classInfo(), propertyNames, mode, object->staticFunctionsReified()); object->structure()->getPropertyNamesFromStructure(exec->globalData(), propertyNames, mode); } 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); } JSString* JSObject::toString(ExecState* exec) const { JSValue primitive = toPrimitive(exec, PreferString); if (exec->hadException()) return jsEmptyString(exec); return primitive.toString(exec); } JSObject* JSObject::toThisObject(JSCell* cell, ExecState*) { return jsCast(cell); } JSObject* JSObject::unwrappedObject() { if (isGlobalThis()) return jsCast(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 (isJSArray(this)) asArray(this)->enterDictionaryMode(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(); } bool JSObject::removeDirect(JSGlobalData& globalData, PropertyName propertyName) { if (!isValidOffset(structure()->get(globalData, propertyName))) return false; PropertyOffset offset; if (structure()->isUncacheableDictionary()) { offset = structure()->removePropertyWithoutTransition(globalData, propertyName); if (offset == invalidOffset) return false; putUndefinedAtDirectOffset(offset); return true; } setStructure(globalData, Structure::removePropertyTransition(globalData, structure(), propertyName, offset)); if (offset == invalidOffset) return false; putUndefinedAtDirectOffset(offset); return true; } NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, WriteBarrierBase* 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) { JSGlobalObject* globalObject; if (isGlobalThis()) globalObject = static_cast(this)->unwrappedObject(); else globalObject = structure()->globalObject(); ASSERT(globalObject); m_inheritorID.set(globalData, this, createEmptyObjectStructure(globalData, globalObject, this)); ASSERT(m_inheritorID->isEmpty()); return m_inheritorID.get(); } PropertyStorage JSObject::growOutOfLineStorage(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 oldPropertyStorage = m_outOfLineStorage.get(); PropertyStorage newPropertyStorage = 0; // We have this extra temp here to slake GCC's thirst for the blood of those who dereference type-punned pointers. void* temp = newPropertyStorage; if (!globalData.heap.tryAllocateStorage(sizeof(WriteBarrierBase) * newSize, &temp)) CRASH(); newPropertyStorage = static_cast(temp) + newSize + 1; memcpy(newPropertyStorage - oldSize - 1, oldPropertyStorage - oldSize - 1, sizeof(WriteBarrierBase) * oldSize); ASSERT(newPropertyStorage); return newPropertyStorage; } bool JSObject::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) { unsigned attributes = 0; JSCell* cell = 0; PropertyOffset offset = object->structure()->get(exec->globalData(), propertyName, attributes, cell); if (offset == invalidOffset) return false; descriptor.setDescriptor(object->getDirectOffset(offset), attributes); return true; } bool JSObject::getPropertyDescriptor(ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor) { JSObject* object = this; while (true) { if (object->methodTable()->getOwnPropertyDescriptor(object, exec, propertyName, descriptor)) return true; JSValue prototype = object->prototype(); if (!prototype.isObject()) return false; object = asObject(prototype); } } static bool putDescriptor(ExecState* exec, JSObject* target, PropertyName 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.getterPresent()) accessor->setGetter(exec->globalData(), oldDescriptor.getterObject()); if (oldDescriptor.setterPresent()) accessor->setSetter(exec->globalData(), oldDescriptor.setterObject()); target->putDirectAccessor(exec->globalData(), propertyName, accessor, attributes | Accessor); return true; } JSValue newValue = jsUndefined(); if (descriptor.value()) newValue = descriptor.value(); else if (oldDescriptor.value()) newValue = oldDescriptor.value(); target->putDirect(exec->globalData(), propertyName, newValue, attributes & ~Accessor); if (attributes & ReadOnly) target->structure()->setContainsReadOnlyProperties(); return true; } attributes &= ~ReadOnly; GetterSetter* accessor = GetterSetter::create(exec); if (descriptor.getterPresent()) accessor->setGetter(exec->globalData(), descriptor.getterObject()); else if (oldDescriptor.getterPresent()) accessor->setGetter(exec->globalData(), oldDescriptor.getterObject()); if (descriptor.setterPresent()) accessor->setSetter(exec->globalData(), descriptor.setterObject()); else if (oldDescriptor.setterPresent()) accessor->setSetter(exec->globalData(), oldDescriptor.setterObject()); target->putDirectAccessor(exec->globalData(), propertyName, accessor, attributes | Accessor); return true; } class DefineOwnPropertyScope { public: DefineOwnPropertyScope(ExecState* exec) : m_globalData(exec->globalData()) { m_globalData.setInDefineOwnProperty(true); } ~DefineOwnPropertyScope() { m_globalData.setInDefineOwnProperty(false); } private: JSGlobalData& m_globalData; }; bool JSObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor, bool throwException) { // Track on the globaldata that we're in define property. // Currently DefineOwnProperty uses delete to remove properties when they are being replaced // (particularly when changing attributes), however delete won't allow non-configurable (i.e. // DontDelete) properties to be deleted. For now, we can use this flag to make this work. DefineOwnPropertyScope scope(exec); // If we have a new property we can just put it on normally PropertyDescriptor current; if (!object->methodTable()->getOwnPropertyDescriptor(object, exec, propertyName, current)) { // unless extensions are prevented! if (!object->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, object, 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)) { object->methodTable()->deleteProperty(object, exec, propertyName); return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), 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; } object->methodTable()->deleteProperty(object, exec, propertyName); return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), 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() && !sameValue(exec, current.value(), descriptor.value())) { if (throwException) throwError(exec, createTypeError(exec, "Attempting to change value of a readonly property.")); return false; } } } if (current.attributesEqual(descriptor) && !descriptor.value()) return true; object->methodTable()->deleteProperty(object, exec, propertyName); return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), 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 = object->getDirect(exec->globalData(), propertyName); if (!accessor) return false; GetterSetter* getterSetter = asGetterSetter(accessor); if (descriptor.setterPresent()) getterSetter->setSetter(exec->globalData(), descriptor.setterObject()); if (descriptor.getterPresent()) getterSetter->setGetter(exec->globalData(), descriptor.getterObject()); if (current.attributesEqual(descriptor)) return true; object->methodTable()->deleteProperty(object, exec, propertyName); unsigned attrs = descriptor.attributesOverridingCurrent(current); object->putDirectAccessor(exec->globalData(), propertyName, getterSetter, attrs | Accessor); return true; } JSObject* throwTypeError(ExecState* exec, const UString& message) { return throwError(exec, createTypeError(exec, message)); } } // namespace JSC