JSObject.cpp revision 8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2
1/* 2 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) 3 * Copyright (C) 2001 Peter Kelly (pmk@post.com) 4 * Copyright (C) 2003, 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. 5 * Copyright (C) 2007 Eric Seidel (eric@webkit.org) 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Library General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Library General Public License for more details. 16 * 17 * You should have received a copy of the GNU Library General Public License 18 * along with this library; see the file COPYING.LIB. If not, write to 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 * 22 */ 23 24#include "config.h" 25#include "JSObject.h" 26 27#include "DatePrototype.h" 28#include "ErrorConstructor.h" 29#include "GetterSetter.h" 30#include "JSGlobalObject.h" 31#include "NativeErrorConstructor.h" 32#include "ObjectPrototype.h" 33#include "PropertyNameArray.h" 34#include "lookup.h" 35#include "nodes.h" 36#include "operations.h" 37#include <math.h> 38#include <wtf/Assertions.h> 39 40#define JSOBJECT_MARK_TRACING 0 41 42#if JSOBJECT_MARK_TRACING 43 44#define JSOBJECT_MARK_BEGIN() \ 45 static int markStackDepth = 0; \ 46 for (int i = 0; i < markStackDepth; i++) \ 47 putchar('-'); \ 48 printf("%s (%p)\n", className().UTF8String().c_str(), this); \ 49 markStackDepth++; \ 50 51#define JSOBJECT_MARK_END() \ 52 markStackDepth--; 53 54#else // JSOBJECT_MARK_TRACING 55 56#define JSOBJECT_MARK_BEGIN() 57#define JSOBJECT_MARK_END() 58 59#endif // JSOBJECT_MARK_TRACING 60 61namespace JSC { 62 63ASSERT_CLASS_FITS_IN_CELL(JSObject); 64 65void JSObject::mark() 66{ 67 JSOBJECT_MARK_BEGIN(); 68 69 JSCell::mark(); 70 m_structureID->mark(); 71 72 size_t storageSize = m_structureID->propertyStorageSize(); 73 for (size_t i = 0; i < storageSize; ++i) { 74 JSValue* v = m_propertyStorage[i]; 75 if (!v->marked()) 76 v->mark(); 77 } 78 79 JSOBJECT_MARK_END(); 80} 81 82UString JSObject::className() const 83{ 84 const ClassInfo* info = classInfo(); 85 if (info) 86 return info->className; 87 return "Object"; 88} 89 90bool JSObject::getOwnPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot) 91{ 92 return getOwnPropertySlot(exec, Identifier::from(exec, propertyName), slot); 93} 94 95static void throwSetterError(ExecState* exec) 96{ 97 throwError(exec, TypeError, "setting a property that has only a getter"); 98} 99 100// ECMA 8.6.2.2 101void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValue* value, PutPropertySlot& slot) 102{ 103 ASSERT(value); 104 ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(this)); 105 106 if (propertyName == exec->propertyNames().underscoreProto) { 107 JSObject* proto = value->getObject(); 108 109 // Setting __proto__ to a non-object, non-null value is silently ignored to match Mozilla. 110 if (!proto && !value->isNull()) 111 return; 112 113 while (proto) { 114 if (proto == this) { 115 throwError(exec, GeneralError, "cyclic __proto__ value"); 116 return; 117 } 118 proto = proto->prototype() ? proto->prototype()->getObject() : 0; 119 } 120 121 setPrototype(value); 122 return; 123 } 124 125 // Check if there are any setters or getters in the prototype chain 126 JSValue* prototype; 127 for (JSObject* obj = this; !obj->structureID()->hasGetterSetterProperties(); obj = asObject(prototype)) { 128 prototype = obj->prototype(); 129 if (prototype->isNull()) { 130 putDirect(propertyName, value, 0, true, slot); 131 return; 132 } 133 } 134 135 unsigned attributes; 136 if ((m_structureID->get(propertyName, attributes) != WTF::notFound) && attributes & ReadOnly) 137 return; 138 139 for (JSObject* obj = this; ; obj = asObject(prototype)) { 140 if (JSValue* gs = obj->getDirect(propertyName)) { 141 if (gs->isGetterSetter()) { 142 JSObject* setterFunc = asGetterSetter(gs)->setter(); 143 if (!setterFunc) { 144 throwSetterError(exec); 145 return; 146 } 147 148 CallData callData; 149 CallType callType = setterFunc->getCallData(callData); 150 ArgList args; 151 args.append(value); 152 call(exec, setterFunc, callType, callData, this, args); 153 return; 154 } 155 156 // If there's an existing property on the object or one of its 157 // prototypes it should be replaced, so break here. 158 break; 159 } 160 161 prototype = obj->prototype(); 162 if (prototype->isNull()) 163 break; 164 } 165 166 putDirect(propertyName, value, 0, true, slot); 167 return; 168} 169 170void JSObject::put(ExecState* exec, unsigned propertyName, JSValue* value) 171{ 172 PutPropertySlot slot; 173 put(exec, Identifier::from(exec, propertyName), value, slot); 174} 175 176void JSObject::putWithAttributes(ExecState*, const Identifier& propertyName, JSValue* value, unsigned attributes) 177{ 178 putDirect(propertyName, value, attributes); 179} 180 181void JSObject::putWithAttributes(ExecState* exec, unsigned propertyName, JSValue* value, unsigned attributes) 182{ 183 putWithAttributes(exec, Identifier::from(exec, propertyName), value, attributes); 184} 185 186bool JSObject::hasProperty(ExecState* exec, const Identifier& propertyName) const 187{ 188 PropertySlot slot; 189 return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot); 190} 191 192bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const 193{ 194 PropertySlot slot; 195 return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot); 196} 197 198// ECMA 8.6.2.5 199bool JSObject::deleteProperty(ExecState* exec, const Identifier& propertyName) 200{ 201 unsigned attributes; 202 if (m_structureID->get(propertyName, attributes) != WTF::notFound) { 203 if ((attributes & DontDelete)) 204 return false; 205 removeDirect(propertyName); 206 return true; 207 } 208 209 // Look in the static hashtable of properties 210 const HashEntry* entry = findPropertyHashEntry(exec, propertyName); 211 if (entry && entry->attributes() & DontDelete) 212 return false; // this builtin property can't be deleted 213 214 // FIXME: Should the code here actually do some deletion? 215 return true; 216} 217 218bool JSObject::hasOwnProperty(ExecState* exec, const Identifier& propertyName) const 219{ 220 PropertySlot slot; 221 return const_cast<JSObject*>(this)->getOwnPropertySlot(exec, propertyName, slot); 222} 223 224bool JSObject::deleteProperty(ExecState* exec, unsigned propertyName) 225{ 226 return deleteProperty(exec, Identifier::from(exec, propertyName)); 227} 228 229static ALWAYS_INLINE JSValue* callDefaultValueFunction(ExecState* exec, const JSObject* object, const Identifier& propertyName) 230{ 231 JSValue* function = object->get(exec, propertyName); 232 CallData callData; 233 CallType callType = function->getCallData(callData); 234 if (callType == CallTypeNone) 235 return exec->exception(); 236 237 // Prevent "toString" and "valueOf" from observing execution if an exception 238 // is pending. 239 if (exec->hadException()) 240 return exec->exception(); 241 242 JSValue* result = call(exec, function, callType, callData, const_cast<JSObject*>(object), exec->emptyList()); 243 ASSERT(!result->isGetterSetter()); 244 if (exec->hadException()) 245 return exec->exception(); 246 if (result->isObject()) 247 return noValue(); 248 return result; 249} 250 251bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue*& result) 252{ 253 result = defaultValue(exec, PreferNumber); 254 number = result->toNumber(exec); 255 return !result->isString(); 256} 257 258// ECMA 8.6.2.6 259JSValue* JSObject::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const 260{ 261 // Must call toString first for Date objects. 262 if ((hint == PreferString) || (hint != PreferNumber && prototype() == exec->lexicalGlobalObject()->datePrototype())) { 263 if (JSValue* value = callDefaultValueFunction(exec, this, exec->propertyNames().toString)) 264 return value; 265 if (JSValue* value = callDefaultValueFunction(exec, this, exec->propertyNames().valueOf)) 266 return value; 267 } else { 268 if (JSValue* value = callDefaultValueFunction(exec, this, exec->propertyNames().valueOf)) 269 return value; 270 if (JSValue* value = callDefaultValueFunction(exec, this, exec->propertyNames().toString)) 271 return value; 272 } 273 274 ASSERT(!exec->hadException()); 275 276 return throwError(exec, TypeError, "No default value"); 277} 278 279const HashEntry* JSObject::findPropertyHashEntry(ExecState* exec, const Identifier& propertyName) const 280{ 281 for (const ClassInfo* info = classInfo(); info; info = info->parentClass) { 282 if (const HashTable* propHashTable = info->propHashTable(exec)) { 283 if (const HashEntry* entry = propHashTable->entry(exec, propertyName)) 284 return entry; 285 } 286 } 287 return 0; 288} 289 290void JSObject::defineGetter(ExecState* exec, const Identifier& propertyName, JSObject* getterFunction) 291{ 292 JSValue* object = getDirect(propertyName); 293 if (object && object->isGetterSetter()) { 294 ASSERT(m_structureID->hasGetterSetterProperties()); 295 asGetterSetter(object)->setGetter(getterFunction); 296 return; 297 } 298 299 PutPropertySlot slot; 300 GetterSetter* getterSetter = new (exec) GetterSetter; 301 putDirect(propertyName, getterSetter, None, true, slot); 302 303 // putDirect will change our StructureID if we add a new property. For 304 // getters and setters, though, we also need to change our StructureID 305 // if we override an existing non-getter or non-setter. 306 if (slot.type() != PutPropertySlot::NewProperty) { 307 if (!m_structureID->isDictionary()) { 308 RefPtr<StructureID> structureID = StructureID::getterSetterTransition(m_structureID); 309 setStructureID(structureID.release()); 310 } 311 } 312 313 m_structureID->setHasGetterSetterProperties(true); 314 getterSetter->setGetter(getterFunction); 315} 316 317void JSObject::defineSetter(ExecState* exec, const Identifier& propertyName, JSObject* setterFunction) 318{ 319 JSValue* object = getDirect(propertyName); 320 if (object && object->isGetterSetter()) { 321 ASSERT(m_structureID->hasGetterSetterProperties()); 322 asGetterSetter(object)->setSetter(setterFunction); 323 return; 324 } 325 326 PutPropertySlot slot; 327 GetterSetter* getterSetter = new (exec) GetterSetter; 328 putDirect(propertyName, getterSetter, None, true, slot); 329 330 // putDirect will change our StructureID if we add a new property. For 331 // getters and setters, though, we also need to change our StructureID 332 // if we override an existing non-getter or non-setter. 333 if (slot.type() != PutPropertySlot::NewProperty) { 334 if (!m_structureID->isDictionary()) { 335 RefPtr<StructureID> structureID = StructureID::getterSetterTransition(m_structureID); 336 setStructureID(structureID.release()); 337 } 338 } 339 340 m_structureID->setHasGetterSetterProperties(true); 341 getterSetter->setSetter(setterFunction); 342} 343 344JSValue* JSObject::lookupGetter(ExecState*, const Identifier& propertyName) 345{ 346 JSObject* object = this; 347 while (true) { 348 JSValue* value = object->getDirect(propertyName); 349 if (value) { 350 if (!value->isGetterSetter()) 351 return jsUndefined(); 352 JSObject* functionObject = asGetterSetter(value)->getter(); 353 if (!functionObject) 354 return jsUndefined(); 355 return functionObject; 356 } 357 358 if (!object->prototype() || !object->prototype()->isObject()) 359 return jsUndefined(); 360 object = asObject(object->prototype()); 361 } 362} 363 364JSValue* JSObject::lookupSetter(ExecState*, const Identifier& propertyName) 365{ 366 JSObject* object = this; 367 while (true) { 368 JSValue* value = object->getDirect(propertyName); 369 if (value) { 370 if (!value->isGetterSetter()) 371 return jsUndefined(); 372 JSObject* functionObject = asGetterSetter(value)->setter(); 373 if (!functionObject) 374 return jsUndefined(); 375 return functionObject; 376 } 377 378 if (!object->prototype() || !object->prototype()->isObject()) 379 return jsUndefined(); 380 object = asObject(object->prototype()); 381 } 382} 383 384bool JSObject::hasInstance(ExecState* exec, JSValue* value, JSValue* proto) 385{ 386 if (!proto->isObject()) { 387 throwError(exec, TypeError, "instanceof called on an object with an invalid prototype property."); 388 return false; 389 } 390 391 if (!value->isObject()) 392 return false; 393 394 JSObject* object = asObject(value); 395 while ((object = object->prototype()->getObject())) { 396 if (object == proto) 397 return true; 398 } 399 return false; 400} 401 402bool JSObject::propertyIsEnumerable(ExecState* exec, const Identifier& propertyName) const 403{ 404 unsigned attributes; 405 if (!getPropertyAttributes(exec, propertyName, attributes)) 406 return false; 407 return !(attributes & DontEnum); 408} 409 410bool JSObject::getPropertyAttributes(ExecState* exec, const Identifier& propertyName, unsigned& attributes) const 411{ 412 if (m_structureID->get(propertyName, attributes) != WTF::notFound) 413 return true; 414 415 // Look in the static hashtable of properties 416 const HashEntry* entry = findPropertyHashEntry(exec, propertyName); 417 if (entry) { 418 attributes = entry->attributes(); 419 return true; 420 } 421 422 return false; 423} 424 425void JSObject::getPropertyNames(ExecState* exec, PropertyNameArray& propertyNames) 426{ 427 m_structureID->getEnumerablePropertyNames(exec, propertyNames, this); 428} 429 430bool JSObject::toBoolean(ExecState*) const 431{ 432 return true; 433} 434 435double JSObject::toNumber(ExecState* exec) const 436{ 437 JSValue* primitive = toPrimitive(exec, PreferNumber); 438 if (exec->hadException()) // should be picked up soon in nodes.cpp 439 return 0.0; 440 return primitive->toNumber(exec); 441} 442 443UString JSObject::toString(ExecState* exec) const 444{ 445 JSValue* primitive = toPrimitive(exec, PreferString); 446 if (exec->hadException()) 447 return ""; 448 return primitive->toString(exec); 449} 450 451JSObject* JSObject::toObject(ExecState*) const 452{ 453 return const_cast<JSObject*>(this); 454} 455 456JSObject* JSObject::toThisObject(ExecState*) const 457{ 458 return const_cast<JSObject*>(this); 459} 460 461JSGlobalObject* JSObject::toGlobalObject(ExecState*) const 462{ 463 return 0; 464} 465 466void JSObject::removeDirect(const Identifier& propertyName) 467{ 468 size_t offset; 469 if (m_structureID->isDictionary()) { 470 offset = m_structureID->removePropertyWithoutTransition(propertyName); 471 if (offset != WTF::notFound) 472 m_propertyStorage[offset] = jsUndefined(); 473 return; 474 } 475 476 RefPtr<StructureID> structureID = StructureID::removePropertyTransition(m_structureID, propertyName, offset); 477 if (offset != WTF::notFound) 478 m_propertyStorage[offset] = jsUndefined(); 479 setStructureID(structureID.release()); 480} 481 482void JSObject::putDirectFunction(ExecState* exec, InternalFunction* function, unsigned attr) 483{ 484 putDirect(Identifier(exec, function->name(&exec->globalData())), function, attr); 485} 486 487void JSObject::putDirectFunctionWithoutTransition(ExecState* exec, InternalFunction* function, unsigned attr) 488{ 489 putDirectWithoutTransition(Identifier(exec, function->name(&exec->globalData())), function, attr); 490} 491 492NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, JSValue** location) 493{ 494 if (JSObject* getterFunction = asGetterSetter(*location)->getter()) 495 slot.setGetterSlot(getterFunction); 496 else 497 slot.setUndefined(); 498} 499 500StructureID* JSObject::createInheritorID() 501{ 502 m_inheritorID = JSObject::createStructureID(this); 503 return m_inheritorID.get(); 504} 505 506void JSObject::allocatePropertyStorage(size_t oldSize, size_t newSize) 507{ 508 allocatePropertyStorageInline(oldSize, newSize); 509} 510 511JSObject* constructEmptyObject(ExecState* exec) 512{ 513 return new (exec) JSObject(exec->lexicalGlobalObject()->emptyObjectStructure()); 514} 515 516} // namespace JSC 517