]>
Commit | Line | Data |
---|---|---|
9dae56ea A |
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 | ||
ba379fdc A |
72 | PropertyStorage storage = propertyStorage(); |
73 | ||
9dae56ea A |
74 | size_t storageSize = m_structure->propertyStorageSize(); |
75 | for (size_t i = 0; i < storageSize; ++i) { | |
ba379fdc | 76 | JSValue v = JSValue::decode(storage[i]); |
9dae56ea A |
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 | |
ba379fdc | 103 | void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot) |
9dae56ea A |
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 | ||
ba379fdc | 113 | JSValue nextPrototypeValue = value; |
9dae56ea A |
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 | |
ba379fdc | 128 | JSValue prototype; |
9dae56ea A |
129 | for (JSObject* obj = this; !obj->structure()->hasGetterSetterProperties(); obj = asObject(prototype)) { |
130 | prototype = obj->prototype(); | |
131 | if (prototype.isNull()) { | |
ba379fdc | 132 | putDirectInternal(exec->globalData(), propertyName, value, 0, true, slot); |
9dae56ea A |
133 | return; |
134 | } | |
135 | } | |
136 | ||
137 | unsigned attributes; | |
ba379fdc A |
138 | JSCell* specificValue; |
139 | if ((m_structure->get(propertyName, attributes, specificValue) != WTF::notFound) && attributes & ReadOnly) | |
9dae56ea A |
140 | return; |
141 | ||
142 | for (JSObject* obj = this; ; obj = asObject(prototype)) { | |
ba379fdc | 143 | if (JSValue gs = obj->getDirect(propertyName)) { |
9dae56ea A |
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); | |
ba379fdc | 153 | MarkedArgumentBuffer args; |
9dae56ea A |
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 | ||
ba379fdc | 169 | putDirectInternal(exec->globalData(), propertyName, value, 0, true, slot); |
9dae56ea A |
170 | return; |
171 | } | |
172 | ||
ba379fdc | 173 | void JSObject::put(ExecState* exec, unsigned propertyName, JSValue value) |
9dae56ea A |
174 | { |
175 | PutPropertySlot slot; | |
176 | put(exec, Identifier::from(exec, propertyName), value, slot); | |
177 | } | |
178 | ||
ba379fdc | 179 | void JSObject::putWithAttributes(ExecState* exec, const Identifier& propertyName, JSValue value, unsigned attributes, bool checkReadOnly, PutPropertySlot& slot) |
9dae56ea | 180 | { |
ba379fdc | 181 | putDirectInternal(exec->globalData(), propertyName, value, attributes, checkReadOnly, slot); |
9dae56ea A |
182 | } |
183 | ||
ba379fdc A |
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) | |
9dae56ea A |
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; | |
ba379fdc A |
210 | JSCell* specificValue; |
211 | if (m_structure->get(propertyName, attributes, specificValue) != WTF::notFound) { | |
9dae56ea A |
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 | ||
ba379fdc | 238 | static ALWAYS_INLINE JSValue callDefaultValueFunction(ExecState* exec, const JSObject* object, const Identifier& propertyName) |
9dae56ea | 239 | { |
ba379fdc | 240 | JSValue function = object->get(exec, propertyName); |
9dae56ea A |
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 | ||
ba379fdc | 251 | JSValue result = call(exec, function, callType, callData, const_cast<JSObject*>(object), exec->emptyList()); |
9dae56ea A |
252 | ASSERT(!result.isGetterSetter()); |
253 | if (exec->hadException()) | |
254 | return exec->exception(); | |
255 | if (result.isObject()) | |
ba379fdc | 256 | return JSValue(); |
9dae56ea A |
257 | return result; |
258 | } | |
259 | ||
ba379fdc | 260 | bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) |
9dae56ea A |
261 | { |
262 | result = defaultValue(exec, PreferNumber); | |
263 | number = result.toNumber(exec); | |
264 | return !result.isString(); | |
265 | } | |
266 | ||
267 | // ECMA 8.6.2.6 | |
ba379fdc | 268 | JSValue JSObject::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const |
9dae56ea A |
269 | { |
270 | // Must call toString first for Date objects. | |
271 | if ((hint == PreferString) || (hint != PreferNumber && prototype() == exec->lexicalGlobalObject()->datePrototype())) { | |
ba379fdc | 272 | JSValue value = callDefaultValueFunction(exec, this, exec->propertyNames().toString); |
9dae56ea A |
273 | if (value) |
274 | return value; | |
275 | value = callDefaultValueFunction(exec, this, exec->propertyNames().valueOf); | |
276 | if (value) | |
277 | return value; | |
278 | } else { | |
ba379fdc | 279 | JSValue value = callDefaultValueFunction(exec, this, exec->propertyNames().valueOf); |
9dae56ea A |
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 | { | |
ba379fdc | 305 | JSValue object = getDirect(propertyName); |
9dae56ea A |
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; | |
ba379fdc | 314 | putDirectInternal(exec->globalData(), propertyName, getterSetter, Getter, true, slot); |
9dae56ea A |
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 | { | |
ba379fdc | 332 | JSValue object = getDirect(propertyName); |
9dae56ea A |
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; | |
ba379fdc | 341 | putDirectInternal(exec->globalData(), propertyName, getterSetter, Setter, true, slot); |
9dae56ea A |
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 | ||
ba379fdc | 357 | JSValue JSObject::lookupGetter(ExecState*, const Identifier& propertyName) |
9dae56ea A |
358 | { |
359 | JSObject* object = this; | |
360 | while (true) { | |
ba379fdc | 361 | if (JSValue value = object->getDirect(propertyName)) { |
9dae56ea A |
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 | ||
ba379fdc | 376 | JSValue JSObject::lookupSetter(ExecState*, const Identifier& propertyName) |
9dae56ea A |
377 | { |
378 | JSObject* object = this; | |
379 | while (true) { | |
ba379fdc | 380 | if (JSValue value = object->getDirect(propertyName)) { |
9dae56ea A |
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 | ||
ba379fdc | 395 | bool JSObject::hasInstance(ExecState* exec, JSValue value, JSValue proto) |
9dae56ea | 396 | { |
ba379fdc A |
397 | if (!value.isObject()) |
398 | return false; | |
399 | ||
9dae56ea A |
400 | if (!proto.isObject()) { |
401 | throwError(exec, TypeError, "instanceof called on an object with an invalid prototype property."); | |
402 | return false; | |
403 | } | |
404 | ||
9dae56ea A |
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 | { | |
ba379fdc A |
423 | JSCell* specificValue; |
424 | if (m_structure->get(propertyName, attributes, specificValue) != WTF::notFound) | |
9dae56ea A |
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 | ||
ba379fdc A |
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 | ||
9dae56ea A |
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 | { | |
ba379fdc | 462 | JSValue primitive = toPrimitive(exec, PreferNumber); |
9dae56ea A |
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 | { | |
ba379fdc | 470 | JSValue primitive = toPrimitive(exec, PreferString); |
9dae56ea A |
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; | |
ba379fdc | 494 | if (m_structure->isUncacheableDictionary()) { |
9dae56ea A |
495 | offset = m_structure->removePropertyWithoutTransition(propertyName); |
496 | if (offset != WTF::notFound) | |
ba379fdc | 497 | putDirectOffset(offset, jsUndefined()); |
9dae56ea A |
498 | return; |
499 | } | |
500 | ||
501 | RefPtr<Structure> structure = Structure::removePropertyTransition(m_structure, propertyName, offset); | |
9dae56ea | 502 | setStructure(structure.release()); |
ba379fdc A |
503 | if (offset != WTF::notFound) |
504 | putDirectOffset(offset, jsUndefined()); | |
9dae56ea A |
505 | } |
506 | ||
507 | void JSObject::putDirectFunction(ExecState* exec, InternalFunction* function, unsigned attr) | |
508 | { | |
ba379fdc | 509 | putDirectFunction(Identifier(exec, function->name(&exec->globalData())), function, attr); |
9dae56ea A |
510 | } |
511 | ||
512 | void JSObject::putDirectFunctionWithoutTransition(ExecState* exec, InternalFunction* function, unsigned attr) | |
513 | { | |
ba379fdc | 514 | putDirectFunctionWithoutTransition(Identifier(exec, function->name(&exec->globalData())), function, attr); |
9dae56ea A |
515 | } |
516 | ||
ba379fdc | 517 | NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, JSValue* location) |
9dae56ea A |
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 |