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