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