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