X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/9dae56ea45a0f5f8136a5c93d6f3a7f99399ca73..f9bf01c6616d5ddcf65b13b33cedf9e387ff7a63:/runtime/JSObject.cpp diff --git a/runtime/JSObject.cpp b/runtime/JSObject.cpp index e9e95f6..d9500aa 100644 --- a/runtime/JSObject.cpp +++ b/runtime/JSObject.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) * Copyright (C) 2001 Peter Kelly (pmk@post.com) - * Copyright (C) 2003, 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. + * 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 @@ -30,6 +30,7 @@ #include "JSGlobalObject.h" #include "NativeErrorConstructor.h" #include "ObjectPrototype.h" +#include "PropertyDescriptor.h" #include "PropertyNameArray.h" #include "Lookup.h" #include "Nodes.h" @@ -37,46 +38,41 @@ #include #include -#define JSOBJECT_MARK_TRACING 0 - -#if JSOBJECT_MARK_TRACING - -#define JSOBJECT_MARK_BEGIN() \ - static int markStackDepth = 0; \ - for (int i = 0; i < markStackDepth; i++) \ - putchar('-'); \ - printf("%s (%p)\n", className().UTF8String().c_str(), this); \ - markStackDepth++; \ - -#define JSOBJECT_MARK_END() \ - markStackDepth--; - -#else // JSOBJECT_MARK_TRACING - -#define JSOBJECT_MARK_BEGIN() -#define JSOBJECT_MARK_END() - -#endif // JSOBJECT_MARK_TRACING - namespace JSC { ASSERT_CLASS_FITS_IN_CELL(JSObject); -void JSObject::mark() -{ - JSOBJECT_MARK_BEGIN(); +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()); + } + } +} - JSCell::mark(); - m_structure->mark(); +void JSObject::markChildren(MarkStack& markStack) +{ +#ifndef NDEBUG + bool wasCheckingForDefaultMarkViolation = markStack.m_isCheckingForDefaultMarkViolation; + markStack.m_isCheckingForDefaultMarkViolation = false; +#endif - size_t storageSize = m_structure->propertyStorageSize(); - for (size_t i = 0; i < storageSize; ++i) { - JSValuePtr v = m_propertyStorage[i]; - if (!v.marked()) - v.mark(); - } + markChildrenDirect(markStack); - JSOBJECT_MARK_END(); +#ifndef NDEBUG + markStack.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation; +#endif } UString JSObject::className() const @@ -98,7 +94,7 @@ static void throwSetterError(ExecState* exec) } // ECMA 8.6.2.2 -void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValuePtr value, PutPropertySlot& slot) +void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot) { ASSERT(value); ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(this)); @@ -108,7 +104,7 @@ void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValuePtr v if (!value.isObject() && !value.isNull()) return; - JSValuePtr nextPrototypeValue = value; + JSValue nextPrototypeValue = value; while (nextPrototypeValue && nextPrototypeValue.isObject()) { JSObject* nextPrototype = asObject(nextPrototypeValue)->unwrappedObject(); if (nextPrototype == this) { @@ -123,21 +119,22 @@ void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValuePtr v } // Check if there are any setters or getters in the prototype chain - JSValuePtr prototype; + JSValue prototype; for (JSObject* obj = this; !obj->structure()->hasGetterSetterProperties(); obj = asObject(prototype)) { prototype = obj->prototype(); if (prototype.isNull()) { - putDirect(propertyName, value, 0, true, slot); + putDirectInternal(exec->globalData(), propertyName, value, 0, true, slot); return; } } unsigned attributes; - if ((m_structure->get(propertyName, attributes) != WTF::notFound) && attributes & ReadOnly) + JSCell* specificValue; + if ((m_structure->get(propertyName, attributes, specificValue) != WTF::notFound) && attributes & ReadOnly) return; for (JSObject* obj = this; ; obj = asObject(prototype)) { - if (JSValuePtr gs = obj->getDirect(propertyName)) { + if (JSValue gs = obj->getDirect(propertyName)) { if (gs.isGetterSetter()) { JSObject* setterFunc = asGetterSetter(gs)->setter(); if (!setterFunc) { @@ -147,7 +144,7 @@ void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValuePtr v CallData callData; CallType callType = setterFunc->getCallData(callData); - ArgList args; + MarkedArgumentBuffer args; args.append(value); call(exec, setterFunc, callType, callData, this, args); return; @@ -163,22 +160,27 @@ void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValuePtr v break; } - putDirect(propertyName, value, 0, true, slot); + putDirectInternal(exec->globalData(), propertyName, value, 0, true, slot); return; } -void JSObject::put(ExecState* exec, unsigned propertyName, JSValuePtr value) +void JSObject::put(ExecState* exec, unsigned propertyName, JSValue value) { PutPropertySlot slot; put(exec, Identifier::from(exec, propertyName), value, slot); } -void JSObject::putWithAttributes(ExecState*, const Identifier& propertyName, JSValuePtr value, unsigned attributes) +void JSObject::putWithAttributes(ExecState* exec, const Identifier& propertyName, JSValue value, unsigned attributes, bool checkReadOnly, PutPropertySlot& slot) { - putDirect(propertyName, value, attributes); + putDirectInternal(exec->globalData(), propertyName, value, attributes, checkReadOnly, slot); } -void JSObject::putWithAttributes(ExecState* exec, unsigned propertyName, JSValuePtr value, unsigned attributes) +void JSObject::putWithAttributes(ExecState* exec, const Identifier& propertyName, JSValue value, unsigned attributes) +{ + putDirectInternal(exec->globalData(), propertyName, value, attributes); +} + +void JSObject::putWithAttributes(ExecState* exec, unsigned propertyName, JSValue value, unsigned attributes) { putWithAttributes(exec, Identifier::from(exec, propertyName), value, attributes); } @@ -199,7 +201,8 @@ bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const bool JSObject::deleteProperty(ExecState* exec, const Identifier& propertyName) { unsigned attributes; - if (m_structure->get(propertyName, attributes) != WTF::notFound) { + JSCell* specificValue; + if (m_structure->get(propertyName, attributes, specificValue) != WTF::notFound) { if ((attributes & DontDelete)) return false; removeDirect(propertyName); @@ -226,9 +229,9 @@ bool JSObject::deleteProperty(ExecState* exec, unsigned propertyName) return deleteProperty(exec, Identifier::from(exec, propertyName)); } -static ALWAYS_INLINE JSValuePtr callDefaultValueFunction(ExecState* exec, const JSObject* object, const Identifier& propertyName) +static ALWAYS_INLINE JSValue callDefaultValueFunction(ExecState* exec, const JSObject* object, const Identifier& propertyName) { - JSValuePtr function = object->get(exec, propertyName); + JSValue function = object->get(exec, propertyName); CallData callData; CallType callType = function.getCallData(callData); if (callType == CallTypeNone) @@ -239,16 +242,16 @@ static ALWAYS_INLINE JSValuePtr callDefaultValueFunction(ExecState* exec, const if (exec->hadException()) return exec->exception(); - JSValuePtr result = call(exec, function, callType, callData, const_cast(object), exec->emptyList()); + 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 noValue(); + return JSValue(); return result; } -bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValuePtr& result) +bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) { result = defaultValue(exec, PreferNumber); number = result.toNumber(exec); @@ -256,18 +259,18 @@ bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValuePtr& r } // ECMA 8.6.2.6 -JSValuePtr JSObject::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const +JSValue JSObject::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const { // Must call toString first for Date objects. if ((hint == PreferString) || (hint != PreferNumber && prototype() == exec->lexicalGlobalObject()->datePrototype())) { - JSValuePtr value = callDefaultValueFunction(exec, this, exec->propertyNames().toString); + JSValue value = callDefaultValueFunction(exec, this, exec->propertyNames().toString); if (value) return value; value = callDefaultValueFunction(exec, this, exec->propertyNames().valueOf); if (value) return value; } else { - JSValuePtr value = callDefaultValueFunction(exec, this, exec->propertyNames().valueOf); + JSValue value = callDefaultValueFunction(exec, this, exec->propertyNames().valueOf); if (value) return value; value = callDefaultValueFunction(exec, this, exec->propertyNames().toString); @@ -291,9 +294,9 @@ const HashEntry* JSObject::findPropertyHashEntry(ExecState* exec, const Identifi return 0; } -void JSObject::defineGetter(ExecState* exec, const Identifier& propertyName, JSObject* getterFunction) +void JSObject::defineGetter(ExecState* exec, const Identifier& propertyName, JSObject* getterFunction, unsigned attributes) { - JSValuePtr object = getDirect(propertyName); + JSValue object = getDirect(propertyName); if (object && object.isGetterSetter()) { ASSERT(m_structure->hasGetterSetterProperties()); asGetterSetter(object)->setGetter(getterFunction); @@ -301,8 +304,8 @@ void JSObject::defineGetter(ExecState* exec, const Identifier& propertyName, JSO } PutPropertySlot slot; - GetterSetter* getterSetter = new (exec) GetterSetter; - putDirect(propertyName, getterSetter, None, true, slot); + GetterSetter* getterSetter = new (exec) GetterSetter(exec); + putDirectInternal(exec->globalData(), propertyName, getterSetter, attributes | Getter, true, slot); // putDirect will change our Structure if we add a new property. For // getters and setters, though, we also need to change our Structure @@ -318,9 +321,9 @@ void JSObject::defineGetter(ExecState* exec, const Identifier& propertyName, JSO getterSetter->setGetter(getterFunction); } -void JSObject::defineSetter(ExecState* exec, const Identifier& propertyName, JSObject* setterFunction) +void JSObject::defineSetter(ExecState* exec, const Identifier& propertyName, JSObject* setterFunction, unsigned attributes) { - JSValuePtr object = getDirect(propertyName); + JSValue object = getDirect(propertyName); if (object && object.isGetterSetter()) { ASSERT(m_structure->hasGetterSetterProperties()); asGetterSetter(object)->setSetter(setterFunction); @@ -328,8 +331,8 @@ void JSObject::defineSetter(ExecState* exec, const Identifier& propertyName, JSO } PutPropertySlot slot; - GetterSetter* getterSetter = new (exec) GetterSetter; - putDirect(propertyName, getterSetter, None, true, slot); + GetterSetter* getterSetter = new (exec) GetterSetter(exec); + putDirectInternal(exec->globalData(), propertyName, getterSetter, attributes | Setter, true, slot); // putDirect will change our Structure if we add a new property. For // getters and setters, though, we also need to change our Structure @@ -345,11 +348,11 @@ void JSObject::defineSetter(ExecState* exec, const Identifier& propertyName, JSO getterSetter->setSetter(setterFunction); } -JSValuePtr JSObject::lookupGetter(ExecState*, const Identifier& propertyName) +JSValue JSObject::lookupGetter(ExecState*, const Identifier& propertyName) { JSObject* object = this; while (true) { - if (JSValuePtr value = object->getDirect(propertyName)) { + if (JSValue value = object->getDirect(propertyName)) { if (!value.isGetterSetter()) return jsUndefined(); JSObject* functionObject = asGetterSetter(value)->getter(); @@ -364,11 +367,11 @@ JSValuePtr JSObject::lookupGetter(ExecState*, const Identifier& propertyName) } } -JSValuePtr JSObject::lookupSetter(ExecState*, const Identifier& propertyName) +JSValue JSObject::lookupSetter(ExecState*, const Identifier& propertyName) { JSObject* object = this; while (true) { - if (JSValuePtr value = object->getDirect(propertyName)) { + if (JSValue value = object->getDirect(propertyName)) { if (!value.isGetterSetter()) return jsUndefined(); JSObject* functionObject = asGetterSetter(value)->setter(); @@ -383,16 +386,16 @@ JSValuePtr JSObject::lookupSetter(ExecState*, const Identifier& propertyName) } } -bool JSObject::hasInstance(ExecState* exec, JSValuePtr value, JSValuePtr proto) +bool JSObject::hasInstance(ExecState* exec, JSValue value, JSValue proto) { + if (!value.isObject()) + return false; + if (!proto.isObject()) { throwError(exec, TypeError, "instanceof called on an object with an invalid prototype property."); return false; } - if (!value.isObject()) - return false; - JSObject* object = asObject(value); while ((object = object->prototype().getObject())) { if (proto == object) @@ -403,30 +406,50 @@ bool JSObject::hasInstance(ExecState* exec, JSValuePtr value, JSValuePtr proto) bool JSObject::propertyIsEnumerable(ExecState* exec, const Identifier& propertyName) const { - unsigned attributes; - if (!getPropertyAttributes(exec, propertyName, attributes)) + PropertyDescriptor descriptor; + if (!const_cast(this)->getOwnPropertyDescriptor(exec, propertyName, descriptor)) return false; - return !(attributes & DontEnum); + return descriptor.enumerable(); } -bool JSObject::getPropertyAttributes(ExecState* exec, const Identifier& propertyName, unsigned& attributes) const +bool JSObject::getPropertySpecificValue(ExecState*, const Identifier& propertyName, JSCell*& specificValue) const { - if (m_structure->get(propertyName, attributes) != WTF::notFound) - return true; - - // Look in the static hashtable of properties - const HashEntry* entry = findPropertyHashEntry(exec, propertyName); - if (entry) { - attributes = entry->attributes(); + unsigned attributes; + if (m_structure->get(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(ExecState* exec, PropertyNameArray& propertyNames) +void JSObject::getPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) +{ + getOwnPropertyNames(exec, propertyNames, mode); + + if (prototype().isNull()) + return; + + JSObject* prototype = asObject(this->prototype()); + while(1) { + if (prototype->structure()->typeInfo().overridesGetPropertyNames()) { + prototype->getPropertyNames(exec, propertyNames, mode); + break; + } + prototype->getOwnPropertyNames(exec, propertyNames, mode); + JSValue nextProto = prototype->prototype(); + if (nextProto.isNull()) + break; + prototype = asObject(nextProto); + } +} + +void JSObject::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { - m_structure->getEnumerablePropertyNames(exec, propertyNames, this); + m_structure->getPropertyNames(propertyNames, mode); + getClassPropertyNames(exec, classInfo(), propertyNames, mode); } bool JSObject::toBoolean(ExecState*) const @@ -436,7 +459,7 @@ bool JSObject::toBoolean(ExecState*) const double JSObject::toNumber(ExecState* exec) const { - JSValuePtr primitive = toPrimitive(exec, PreferNumber); + JSValue primitive = toPrimitive(exec, PreferNumber); if (exec->hadException()) // should be picked up soon in Nodes.cpp return 0.0; return primitive.toNumber(exec); @@ -444,7 +467,7 @@ double JSObject::toNumber(ExecState* exec) const UString JSObject::toString(ExecState* exec) const { - JSValuePtr primitive = toPrimitive(exec, PreferString); + JSValue primitive = toPrimitive(exec, PreferString); if (exec->hadException()) return ""; return primitive.toString(exec); @@ -468,30 +491,30 @@ JSObject* JSObject::unwrappedObject() void JSObject::removeDirect(const Identifier& propertyName) { size_t offset; - if (m_structure->isDictionary()) { + if (m_structure->isUncacheableDictionary()) { offset = m_structure->removePropertyWithoutTransition(propertyName); if (offset != WTF::notFound) - m_propertyStorage[offset] = jsUndefined(); + putDirectOffset(offset, jsUndefined()); return; } RefPtr structure = Structure::removePropertyTransition(m_structure, propertyName, offset); - if (offset != WTF::notFound) - m_propertyStorage[offset] = jsUndefined(); setStructure(structure.release()); + if (offset != WTF::notFound) + putDirectOffset(offset, jsUndefined()); } void JSObject::putDirectFunction(ExecState* exec, InternalFunction* function, unsigned attr) { - putDirect(Identifier(exec, function->name(&exec->globalData())), function, attr); + putDirectFunction(Identifier(exec, function->name(exec)), function, attr); } void JSObject::putDirectFunctionWithoutTransition(ExecState* exec, InternalFunction* function, unsigned attr) { - putDirectWithoutTransition(Identifier(exec, function->name(&exec->globalData())), function, attr); + putDirectFunctionWithoutTransition(Identifier(exec, function->name(exec)), function, attr); } -NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, JSValuePtr* location) +NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, JSValue* location) { if (JSObject* getterFunction = asGetterSetter(*location)->getter()) slot.setGetterSlot(getterFunction); @@ -510,9 +533,154 @@ void JSObject::allocatePropertyStorage(size_t oldSize, size_t newSize) allocatePropertyStorageInline(oldSize, newSize); } -JSObject* constructEmptyObject(ExecState* exec) +bool JSObject::getOwnPropertyDescriptor(ExecState*, const Identifier& propertyName, PropertyDescriptor& descriptor) +{ + unsigned attributes = 0; + JSCell* cell = 0; + size_t offset = m_structure->get(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, JSValue oldValue) { - return new (exec) JSObject(exec->lexicalGlobalObject()->emptyObjectStructure()); + if (descriptor.isGenericDescriptor() || descriptor.isDataDescriptor()) { + target->putWithAttributes(exec, propertyName, descriptor.value() ? descriptor.value() : oldValue, attributes & ~(Getter | Setter)); + return true; + } + attributes &= ~ReadOnly; + if (descriptor.getter() && descriptor.getter().isObject()) + target->defineGetter(exec, propertyName, asObject(descriptor.getter()), attributes); + if (exec->hadException()) + return false; + if (descriptor.setter() && descriptor.setter().isObject()) + target->defineSetter(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)) + return putDescriptor(exec, this, propertyName, descriptor, descriptor.attributes(), jsUndefined()); + + 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, TypeError, "Attempting to configurable attribute of unconfigurable property."); + return false; + } + if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable()) { + if (throwException) + throwError(exec, TypeError, "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)) { + deleteProperty(exec, propertyName); + putDescriptor(exec, this, propertyName, descriptor, current.attributesWithOverride(descriptor), current.value()); + } + return true; + } + + // Changing between a normal property or an accessor property + if (descriptor.isDataDescriptor() != current.isDataDescriptor()) { + if (!current.configurable()) { + if (throwException) + throwError(exec, TypeError, "Attempting to change access mechanism for an unconfigurable property."); + return false; + } + deleteProperty(exec, propertyName); + return putDescriptor(exec, this, propertyName, descriptor, current.attributesWithOverride(descriptor), current.value() ? current.value() : jsUndefined()); + } + + // Changing the value and attributes of an existing property + if (descriptor.isDataDescriptor()) { + if (!current.configurable()) { + if (!current.writable() && descriptor.writable()) { + if (throwException) + throwError(exec, TypeError, "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, TypeError, "Attempting to change value of a readonly property."); + return false; + } + } + } else if (current.attributesEqual(descriptor)) { + if (!descriptor.value()) + return true; + PutPropertySlot slot; + put(exec, propertyName, descriptor.value(), slot); + if (exec->hadException()) + return false; + return true; + } + deleteProperty(exec, propertyName); + return putDescriptor(exec, this, propertyName, descriptor, current.attributesWithOverride(descriptor), current.value()); + } + + // Changing the accessor functions of an existing accessor property + ASSERT(descriptor.isAccessorDescriptor()); + if (!current.configurable()) { + if (descriptor.setterPresent() && !(current.setter() && JSValue::strictEqual(exec, current.setter(), descriptor.setter()))) { + if (throwException) + throwError(exec, TypeError, "Attempting to change the setter of an unconfigurable property."); + return false; + } + if (descriptor.getterPresent() && !(current.getter() && JSValue::strictEqual(exec, current.getter(), descriptor.getter()))) { + if (throwException) + throwError(exec, TypeError, "Attempting to change the getter of an unconfigurable property."); + return false; + } + } + JSValue accessor = getDirect(propertyName); + if (!accessor) + return false; + GetterSetter* getterSetter = asGetterSetter(accessor); + if (current.attributesEqual(descriptor)) { + if (descriptor.setter()) + getterSetter->setSetter(asObject(descriptor.setter())); + if (descriptor.getter()) + getterSetter->setGetter(asObject(descriptor.getter())); + return true; + } + deleteProperty(exec, propertyName); + unsigned attrs = current.attributesWithOverride(descriptor); + if (descriptor.setter()) + attrs |= Setter; + if (descriptor.getter()) + attrs |= Getter; + putDirect(propertyName, getterSetter, attrs); + return true; } } // namespace JSC