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