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 "PropertyDescriptor.h" |
34 | #include "PropertyNameArray.h" |
35 | #include "Lookup.h" |
36 | #include "Nodes.h" |
37 | #include "Operations.h" |
38 | #include <math.h> |
39 | #include <wtf/Assertions.h> |
40 | |
41 | namespace JSC { |
42 | |
43 | ASSERT_CLASS_FITS_IN_CELL(JSObject); |
44 | |
45 | static inline void getClassPropertyNames(ExecState* exec, const ClassInfo* classInfo, PropertyNameArray& propertyNames, EnumerationMode mode) |
46 | { |
47 | // Add properties from the static hashtables of properties |
48 | for (; classInfo; classInfo = classInfo->parentClass) { |
49 | const HashTable* table = classInfo->propHashTable(exec); |
50 | if (!table) |
51 | continue; |
52 | table->initializeIfNeeded(exec); |
53 | ASSERT(table->table); |
54 | |
55 | int hashSizeMask = table->compactSize - 1; |
56 | const HashEntry* entry = table->table; |
57 | for (int i = 0; i <= hashSizeMask; ++i, ++entry) { |
58 | if (entry->key() && (!(entry->attributes() & DontEnum) || (mode == IncludeDontEnumProperties))) |
59 | propertyNames.add(entry->key()); |
60 | } |
61 | } |
62 | } |
63 | |
64 | void JSObject::markChildren(MarkStack& markStack) |
65 | { |
66 | #ifndef NDEBUG |
67 | bool wasCheckingForDefaultMarkViolation = markStack.m_isCheckingForDefaultMarkViolation; |
68 | markStack.m_isCheckingForDefaultMarkViolation = false; |
69 | #endif |
70 | |
71 | markChildrenDirect(markStack); |
72 | |
73 | #ifndef NDEBUG |
74 | markStack.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation; |
75 | #endif |
76 | } |
77 | |
78 | UString JSObject::className() const |
79 | { |
80 | const ClassInfo* info = classInfo(); |
81 | if (info) |
82 | return info->className; |
83 | return "Object" ; |
84 | } |
85 | |
86 | bool JSObject::getOwnPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot) |
87 | { |
88 | return getOwnPropertySlot(exec, propertyName: Identifier::from(exec, y: propertyName), slot); |
89 | } |
90 | |
91 | static void throwSetterError(ExecState* exec) |
92 | { |
93 | throwError(exec, TypeError, message: "setting a property that has only a getter" ); |
94 | } |
95 | |
96 | // ECMA 8.6.2.2 |
97 | void JSObject::put(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot) |
98 | { |
99 | ASSERT(value); |
100 | ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(this)); |
101 | |
102 | if (propertyName == exec->propertyNames().underscoreProto) { |
103 | // Setting __proto__ to a non-object, non-null value is silently ignored to match Mozilla. |
104 | if (!value.isObject() && !value.isNull()) |
105 | return; |
106 | |
107 | JSValue nextPrototypeValue = value; |
108 | while (nextPrototypeValue && nextPrototypeValue.isObject()) { |
109 | JSObject* nextPrototype = asObject(value: nextPrototypeValue)->unwrappedObject(); |
110 | if (nextPrototype == this) { |
111 | throwError(exec, GeneralError, message: "cyclic __proto__ value" ); |
112 | return; |
113 | } |
114 | nextPrototypeValue = nextPrototype->prototype(); |
115 | } |
116 | |
117 | setPrototype(value); |
118 | return; |
119 | } |
120 | |
121 | // Check if there are any setters or getters in the prototype chain |
122 | JSValue prototype; |
123 | for (JSObject* obj = this; !obj->structure()->hasGetterSetterProperties(); obj = asObject(value: prototype)) { |
124 | prototype = obj->prototype(); |
125 | if (prototype.isNull()) { |
126 | putDirectInternal(globalData&: exec->globalData(), propertyName, value, attributes: 0, checkReadOnly: true, slot); |
127 | return; |
128 | } |
129 | } |
130 | |
131 | unsigned attributes; |
132 | JSCell* specificValue; |
133 | if ((m_structure->get(propertyName, attributes, specificValue) != WTF::notFound) && attributes & ReadOnly) |
134 | return; |
135 | |
136 | for (JSObject* obj = this; ; obj = asObject(value: prototype)) { |
137 | #ifdef QT_BUILD_SCRIPT_LIB |
138 | PropertyDescriptor descriptor; |
139 | if (obj->getPropertyDescriptor(exec, propertyName, descriptor)) { |
140 | JSObject* setterFunc; |
141 | if ((descriptor.isAccessorDescriptor() && !!descriptor.setter() && ((setterFunc = asObject(value: descriptor.setter())), true)) |
142 | || (!!descriptor.value() && descriptor.value().isGetterSetter() && ((setterFunc = asGetterSetter(value: descriptor.value())->setter()), true))) { |
143 | #else |
144 | if (JSValue gs = obj->getDirect(propertyName)) { |
145 | if (gs.isGetterSetter()) { |
146 | JSObject* setterFunc = asGetterSetter(gs)->setter(); |
147 | #endif |
148 | if (!setterFunc) { |
149 | throwSetterError(exec); |
150 | return; |
151 | } |
152 | |
153 | CallData callData; |
154 | CallType callType = setterFunc->getCallData(callData); |
155 | MarkedArgumentBuffer args; |
156 | args.append(v: value); |
157 | call(exec, functionObject: setterFunc, callType, callData, thisValue: this, args); |
158 | return; |
159 | } |
160 | |
161 | // If there's an existing property on the object or one of its |
162 | // prototypes it should be replaced, so break here. |
163 | break; |
164 | } |
165 | |
166 | prototype = obj->prototype(); |
167 | if (prototype.isNull()) |
168 | break; |
169 | } |
170 | |
171 | putDirectInternal(globalData&: exec->globalData(), propertyName, value, attributes: 0, checkReadOnly: true, slot); |
172 | return; |
173 | } |
174 | |
175 | void JSObject::put(ExecState* exec, unsigned propertyName, JSValue value) |
176 | { |
177 | PutPropertySlot slot; |
178 | put(exec, propertyName: Identifier::from(exec, y: propertyName), value, slot); |
179 | } |
180 | |
181 | void JSObject::putWithAttributes(ExecState* exec, const Identifier& propertyName, JSValue value, unsigned attributes, bool checkReadOnly, PutPropertySlot& slot) |
182 | { |
183 | putDirectInternal(globalData&: exec->globalData(), propertyName, value, attributes, checkReadOnly, slot); |
184 | } |
185 | |
186 | void JSObject::putWithAttributes(ExecState* exec, const Identifier& propertyName, JSValue value, unsigned attributes) |
187 | { |
188 | putDirectInternal(globalData&: exec->globalData(), propertyName, value, attributes); |
189 | } |
190 | |
191 | void JSObject::putWithAttributes(ExecState* exec, unsigned propertyName, JSValue value, unsigned attributes) |
192 | { |
193 | putWithAttributes(exec, propertyName: Identifier::from(exec, y: propertyName), value, attributes); |
194 | } |
195 | |
196 | bool JSObject::hasProperty(ExecState* exec, const Identifier& propertyName) const |
197 | { |
198 | PropertySlot slot; |
199 | return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot); |
200 | } |
201 | |
202 | bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const |
203 | { |
204 | PropertySlot slot; |
205 | return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot); |
206 | } |
207 | |
208 | // ECMA 8.6.2.5 |
209 | bool JSObject::deleteProperty(ExecState* exec, const Identifier& propertyName) |
210 | { |
211 | unsigned attributes; |
212 | JSCell* specificValue; |
213 | if (m_structure->get(propertyName, attributes, specificValue) != WTF::notFound) { |
214 | if ((attributes & DontDelete)) |
215 | return false; |
216 | removeDirect(propertyName); |
217 | return true; |
218 | } |
219 | |
220 | // Look in the static hashtable of properties |
221 | const HashEntry* entry = findPropertyHashEntry(exec, propertyName); |
222 | if (entry && entry->attributes() & DontDelete) |
223 | return false; // this builtin property can't be deleted |
224 | |
225 | // FIXME: Should the code here actually do some deletion? |
226 | return true; |
227 | } |
228 | |
229 | bool JSObject::hasOwnProperty(ExecState* exec, const Identifier& propertyName) const |
230 | { |
231 | PropertySlot slot; |
232 | return const_cast<JSObject*>(this)->getOwnPropertySlot(exec, propertyName, slot); |
233 | } |
234 | |
235 | bool JSObject::deleteProperty(ExecState* exec, unsigned propertyName) |
236 | { |
237 | return deleteProperty(exec, propertyName: Identifier::from(exec, y: propertyName)); |
238 | } |
239 | |
240 | static ALWAYS_INLINE JSValue callDefaultValueFunction(ExecState* exec, const JSObject* object, const Identifier& propertyName) |
241 | { |
242 | JSValue function = object->get(exec, propertyName); |
243 | CallData callData; |
244 | CallType callType = function.getCallData(callData); |
245 | if (callType == CallTypeNone) |
246 | return exec->exception(); |
247 | |
248 | // Prevent "toString" and "valueOf" from observing execution if an exception |
249 | // is pending. |
250 | if (exec->hadException()) |
251 | return exec->exception(); |
252 | |
253 | JSValue result = call(exec, functionObject: function, callType, callData, thisValue: const_cast<JSObject*>(object), exec->emptyList()); |
254 | ASSERT(!result.isGetterSetter()); |
255 | if (exec->hadException()) |
256 | return exec->exception(); |
257 | if (result.isObject()) |
258 | return JSValue(); |
259 | return result; |
260 | } |
261 | |
262 | bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) |
263 | { |
264 | result = defaultValue(exec, PreferNumber); |
265 | number = result.toNumber(exec); |
266 | return !result.isString(); |
267 | } |
268 | |
269 | // ECMA 8.6.2.6 |
270 | JSValue JSObject::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const |
271 | { |
272 | // Must call toString first for Date objects. |
273 | if ((hint == PreferString) || (hint != PreferNumber && prototype() == exec->lexicalGlobalObject()->datePrototype())) { |
274 | JSValue value = callDefaultValueFunction(exec, object: this, propertyName: exec->propertyNames().toString); |
275 | if (value) |
276 | return value; |
277 | value = callDefaultValueFunction(exec, object: this, propertyName: exec->propertyNames().valueOf); |
278 | if (value) |
279 | return value; |
280 | } else { |
281 | JSValue value = callDefaultValueFunction(exec, object: this, propertyName: exec->propertyNames().valueOf); |
282 | if (value) |
283 | return value; |
284 | value = callDefaultValueFunction(exec, object: this, propertyName: exec->propertyNames().toString); |
285 | if (value) |
286 | return value; |
287 | } |
288 | |
289 | ASSERT(!exec->hadException()); |
290 | |
291 | return throwError(exec, TypeError, message: "No default value" ); |
292 | } |
293 | |
294 | const HashEntry* JSObject::findPropertyHashEntry(ExecState* exec, const Identifier& propertyName) const |
295 | { |
296 | for (const ClassInfo* info = classInfo(); info; info = info->parentClass) { |
297 | if (const HashTable* propHashTable = info->propHashTable(exec)) { |
298 | if (const HashEntry* entry = propHashTable->entry(exec, identifier: propertyName)) |
299 | return entry; |
300 | } |
301 | } |
302 | return 0; |
303 | } |
304 | |
305 | void JSObject::defineGetter(ExecState* exec, const Identifier& propertyName, JSObject* getterFunction, unsigned attributes) |
306 | { |
307 | JSValue object = getDirect(propertyName); |
308 | if (object && object.isGetterSetter()) { |
309 | ASSERT(m_structure->hasGetterSetterProperties()); |
310 | asGetterSetter(value: object)->setGetter(getterFunction); |
311 | return; |
312 | } |
313 | |
314 | PutPropertySlot slot; |
315 | GetterSetter* getterSetter = new (exec) GetterSetter(exec); |
316 | putDirectInternal(globalData&: exec->globalData(), propertyName, value: getterSetter, attributes: attributes | Getter, checkReadOnly: true, slot); |
317 | |
318 | // putDirect will change our Structure if we add a new property. For |
319 | // getters and setters, though, we also need to change our Structure |
320 | // if we override an existing non-getter or non-setter. |
321 | if (slot.type() != PutPropertySlot::NewProperty) { |
322 | if (!m_structure->isDictionary()) { |
323 | RefPtr<Structure> structure = Structure::getterSetterTransition(m_structure); |
324 | setStructure(structure.release()); |
325 | } |
326 | } |
327 | |
328 | m_structure->setHasGetterSetterProperties(true); |
329 | getterSetter->setGetter(getterFunction); |
330 | } |
331 | |
332 | void JSObject::defineSetter(ExecState* exec, const Identifier& propertyName, JSObject* setterFunction, unsigned attributes) |
333 | { |
334 | JSValue object = getDirect(propertyName); |
335 | if (object && object.isGetterSetter()) { |
336 | ASSERT(m_structure->hasGetterSetterProperties()); |
337 | asGetterSetter(value: object)->setSetter(setterFunction); |
338 | return; |
339 | } |
340 | |
341 | PutPropertySlot slot; |
342 | GetterSetter* getterSetter = new (exec) GetterSetter(exec); |
343 | putDirectInternal(globalData&: exec->globalData(), propertyName, value: getterSetter, attributes: attributes | Setter, checkReadOnly: true, slot); |
344 | |
345 | // putDirect will change our Structure if we add a new property. For |
346 | // getters and setters, though, we also need to change our Structure |
347 | // if we override an existing non-getter or non-setter. |
348 | if (slot.type() != PutPropertySlot::NewProperty) { |
349 | if (!m_structure->isDictionary()) { |
350 | RefPtr<Structure> structure = Structure::getterSetterTransition(m_structure); |
351 | setStructure(structure.release()); |
352 | } |
353 | } |
354 | |
355 | m_structure->setHasGetterSetterProperties(true); |
356 | getterSetter->setSetter(setterFunction); |
357 | } |
358 | |
359 | JSValue JSObject::lookupGetter(ExecState*, const Identifier& propertyName) |
360 | { |
361 | JSObject* object = this; |
362 | while (true) { |
363 | if (JSValue value = object->getDirect(propertyName)) { |
364 | if (!value.isGetterSetter()) |
365 | return jsUndefined(); |
366 | JSObject* functionObject = asGetterSetter(value)->getter(); |
367 | if (!functionObject) |
368 | return jsUndefined(); |
369 | return functionObject; |
370 | } |
371 | |
372 | if (!object->prototype() || !object->prototype().isObject()) |
373 | return jsUndefined(); |
374 | object = asObject(value: object->prototype()); |
375 | } |
376 | } |
377 | |
378 | JSValue JSObject::lookupSetter(ExecState*, const Identifier& propertyName) |
379 | { |
380 | JSObject* object = this; |
381 | while (true) { |
382 | if (JSValue value = object->getDirect(propertyName)) { |
383 | if (!value.isGetterSetter()) |
384 | return jsUndefined(); |
385 | JSObject* functionObject = asGetterSetter(value)->setter(); |
386 | if (!functionObject) |
387 | return jsUndefined(); |
388 | return functionObject; |
389 | } |
390 | |
391 | if (!object->prototype() || !object->prototype().isObject()) |
392 | return jsUndefined(); |
393 | object = asObject(value: object->prototype()); |
394 | } |
395 | } |
396 | |
397 | bool JSObject::hasInstance(ExecState* exec, JSValue value, JSValue proto) |
398 | { |
399 | if (!value.isObject()) |
400 | return false; |
401 | |
402 | if (!proto.isObject()) { |
403 | throwError(exec, TypeError, message: "instanceof called on an object with an invalid prototype property." ); |
404 | return false; |
405 | } |
406 | |
407 | JSObject* object = asObject(value); |
408 | while ((object = object->prototype().getObject())) { |
409 | if (proto == object) |
410 | return true; |
411 | } |
412 | return false; |
413 | } |
414 | |
415 | bool JSObject::propertyIsEnumerable(ExecState* exec, const Identifier& propertyName) const |
416 | { |
417 | PropertyDescriptor descriptor; |
418 | if (!const_cast<JSObject*>(this)->getOwnPropertyDescriptor(exec, propertyName, descriptor)) |
419 | return false; |
420 | return descriptor.enumerable(); |
421 | } |
422 | |
423 | bool JSObject::getPropertySpecificValue(ExecState*, const Identifier& propertyName, JSCell*& specificValue) const |
424 | { |
425 | unsigned attributes; |
426 | if (m_structure->get(propertyName, attributes, specificValue) != WTF::notFound) |
427 | return true; |
428 | |
429 | // This could be a function within the static table? - should probably |
430 | // also look in the hash? This currently should not be a problem, since |
431 | // we've currently always call 'get' first, which should have populated |
432 | // the normal storage. |
433 | return false; |
434 | } |
435 | |
436 | void JSObject::getPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) |
437 | { |
438 | getOwnPropertyNames(exec, propertyNames, mode); |
439 | |
440 | if (prototype().isNull()) |
441 | return; |
442 | |
443 | JSObject* prototype = asObject(value: this->prototype()); |
444 | while(1) { |
445 | if (prototype->structure()->typeInfo().overridesGetPropertyNames()) { |
446 | prototype->getPropertyNames(exec, propertyNames, mode); |
447 | break; |
448 | } |
449 | prototype->getOwnPropertyNames(exec, propertyNames, mode); |
450 | JSValue nextProto = prototype->prototype(); |
451 | if (nextProto.isNull()) |
452 | break; |
453 | prototype = asObject(value: nextProto); |
454 | } |
455 | } |
456 | |
457 | void JSObject::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) |
458 | { |
459 | m_structure->getPropertyNames(propertyNames, mode); |
460 | getClassPropertyNames(exec, classInfo: classInfo(), propertyNames, mode); |
461 | } |
462 | |
463 | bool JSObject::toBoolean(ExecState*) const |
464 | { |
465 | return true; |
466 | } |
467 | |
468 | double JSObject::toNumber(ExecState* exec) const |
469 | { |
470 | JSValue primitive = toPrimitive(exec, preferredType: PreferNumber); |
471 | if (exec->hadException()) // should be picked up soon in Nodes.cpp |
472 | return 0.0; |
473 | return primitive.toNumber(exec); |
474 | } |
475 | |
476 | UString JSObject::toString(ExecState* exec) const |
477 | { |
478 | JSValue primitive = toPrimitive(exec, preferredType: PreferString); |
479 | if (exec->hadException()) |
480 | return "" ; |
481 | return primitive.toString(exec); |
482 | } |
483 | |
484 | JSObject* JSObject::toObject(ExecState*) const |
485 | { |
486 | return const_cast<JSObject*>(this); |
487 | } |
488 | |
489 | JSObject* JSObject::toThisObject(ExecState*) const |
490 | { |
491 | return const_cast<JSObject*>(this); |
492 | } |
493 | |
494 | JSObject* JSObject::unwrappedObject() |
495 | { |
496 | return this; |
497 | } |
498 | |
499 | void JSObject::removeDirect(const Identifier& propertyName) |
500 | { |
501 | size_t offset; |
502 | if (m_structure->isUncacheableDictionary()) { |
503 | offset = m_structure->removePropertyWithoutTransition(propertyName); |
504 | if (offset != WTF::notFound) |
505 | putDirectOffset(offset, value: jsUndefined()); |
506 | return; |
507 | } |
508 | |
509 | RefPtr<Structure> structure = Structure::removePropertyTransition(m_structure, propertyName, offset); |
510 | setStructure(structure.release()); |
511 | if (offset != WTF::notFound) |
512 | putDirectOffset(offset, value: jsUndefined()); |
513 | } |
514 | |
515 | void JSObject::putDirectFunction(ExecState* exec, InternalFunction* function, unsigned attr) |
516 | { |
517 | putDirectFunction(propertyName: Identifier(exec, function->name(exec)), value: function, attr); |
518 | } |
519 | |
520 | void JSObject::putDirectFunctionWithoutTransition(ExecState* exec, InternalFunction* function, unsigned attr) |
521 | { |
522 | putDirectFunctionWithoutTransition(propertyName: Identifier(exec, function->name(exec)), value: function, attributes: attr); |
523 | } |
524 | |
525 | NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, JSValue* location) |
526 | { |
527 | if (JSObject* getterFunction = asGetterSetter(value: *location)->getter()) |
528 | slot.setGetterSlot(getterFunction); |
529 | else |
530 | slot.setUndefined(); |
531 | } |
532 | |
533 | Structure* JSObject::createInheritorID() |
534 | { |
535 | #ifdef QT_BUILD_SCRIPT_LIB |
536 | // ### Qt Script needs the hasOwnProperty() calls etc. for QScriptObject |
537 | m_inheritorID = Structure::create(prototype: this, typeInfo: TypeInfo(ObjectType, ImplementsHasInstance | JSC::OverridesHasInstance | JSC::OverridesGetOwnPropertySlot | JSC::OverridesMarkChildren | JSC::OverridesGetPropertyNames)); |
538 | #else |
539 | m_inheritorID = JSObject::createStructure(this); |
540 | #endif |
541 | return m_inheritorID.get(); |
542 | } |
543 | |
544 | void JSObject::allocatePropertyStorage(size_t oldSize, size_t newSize) |
545 | { |
546 | allocatePropertyStorageInline(oldSize, newSize); |
547 | } |
548 | |
549 | bool JSObject::getOwnPropertyDescriptor(ExecState*, const Identifier& propertyName, PropertyDescriptor& descriptor) |
550 | { |
551 | unsigned attributes = 0; |
552 | JSCell* cell = 0; |
553 | size_t offset = m_structure->get(propertyName, attributes, specificValue&: cell); |
554 | if (offset == WTF::notFound) |
555 | return false; |
556 | descriptor.setDescriptor(value: getDirectOffset(offset), attributes); |
557 | return true; |
558 | } |
559 | |
560 | bool JSObject::getPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor) |
561 | { |
562 | JSObject* object = this; |
563 | while (true) { |
564 | if (object->getOwnPropertyDescriptor(exec, propertyName, descriptor)) |
565 | return true; |
566 | JSValue prototype = object->prototype(); |
567 | if (!prototype.isObject()) |
568 | return false; |
569 | object = asObject(value: prototype); |
570 | } |
571 | } |
572 | |
573 | static bool putDescriptor(ExecState* exec, JSObject* target, const Identifier& propertyName, PropertyDescriptor& descriptor, unsigned attributes, JSValue oldValue) |
574 | { |
575 | if (descriptor.isGenericDescriptor() || descriptor.isDataDescriptor()) { |
576 | target->putWithAttributes(exec, propertyName, value: descriptor.value() ? descriptor.value() : oldValue, attributes: attributes & ~(Getter | Setter)); |
577 | return true; |
578 | } |
579 | attributes &= ~ReadOnly; |
580 | if (descriptor.getter() && descriptor.getter().isObject()) |
581 | target->defineGetter(exec, propertyName, getterFunction: asObject(value: descriptor.getter()), attributes); |
582 | if (exec->hadException()) |
583 | return false; |
584 | if (descriptor.setter() && descriptor.setter().isObject()) |
585 | target->defineSetter(exec, propertyName, setterFunction: asObject(value: descriptor.setter()), attributes); |
586 | return !exec->hadException(); |
587 | } |
588 | |
589 | bool JSObject::defineOwnProperty(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor, bool throwException) |
590 | { |
591 | // If we have a new property we can just put it on normally |
592 | PropertyDescriptor current; |
593 | if (!getOwnPropertyDescriptor(exec, propertyName, descriptor&: current)) |
594 | return putDescriptor(exec, target: this, propertyName, descriptor, attributes: descriptor.attributes(), oldValue: jsUndefined()); |
595 | |
596 | if (descriptor.isEmpty()) |
597 | return true; |
598 | |
599 | if (current.equalTo(exec, other: descriptor)) |
600 | return true; |
601 | |
602 | // Filter out invalid changes |
603 | if (!current.configurable()) { |
604 | if (descriptor.configurable()) { |
605 | if (throwException) |
606 | throwError(exec, TypeError, message: "Attempting to configurable attribute of unconfigurable property." ); |
607 | return false; |
608 | } |
609 | if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable()) { |
610 | if (throwException) |
611 | throwError(exec, TypeError, message: "Attempting to change enumerable attribute of unconfigurable property." ); |
612 | return false; |
613 | } |
614 | } |
615 | |
616 | // A generic descriptor is simply changing the attributes of an existing property |
617 | if (descriptor.isGenericDescriptor()) { |
618 | if (!current.attributesEqual(other: descriptor)) { |
619 | deleteProperty(exec, propertyName); |
620 | putDescriptor(exec, target: this, propertyName, descriptor, attributes: current.attributesWithOverride(other: descriptor), oldValue: current.value()); |
621 | } |
622 | return true; |
623 | } |
624 | |
625 | // Changing between a normal property or an accessor property |
626 | if (descriptor.isDataDescriptor() != current.isDataDescriptor()) { |
627 | if (!current.configurable()) { |
628 | if (throwException) |
629 | throwError(exec, TypeError, message: "Attempting to change access mechanism for an unconfigurable property." ); |
630 | return false; |
631 | } |
632 | deleteProperty(exec, propertyName); |
633 | return putDescriptor(exec, target: this, propertyName, descriptor, attributes: current.attributesWithOverride(other: descriptor), oldValue: current.value() ? current.value() : jsUndefined()); |
634 | } |
635 | |
636 | // Changing the value and attributes of an existing property |
637 | if (descriptor.isDataDescriptor()) { |
638 | if (!current.configurable()) { |
639 | if (!current.writable() && descriptor.writable()) { |
640 | if (throwException) |
641 | throwError(exec, TypeError, message: "Attempting to change writable attribute of unconfigurable property." ); |
642 | return false; |
643 | } |
644 | if (!current.writable()) { |
645 | if (descriptor.value() || !JSValue::strictEqual(exec, v1: current.value(), v2: descriptor.value())) { |
646 | if (throwException) |
647 | throwError(exec, TypeError, message: "Attempting to change value of a readonly property." ); |
648 | return false; |
649 | } |
650 | } |
651 | } else if (current.attributesEqual(other: descriptor)) { |
652 | if (!descriptor.value()) |
653 | return true; |
654 | PutPropertySlot slot; |
655 | put(exec, propertyName, value: descriptor.value(), slot); |
656 | if (exec->hadException()) |
657 | return false; |
658 | return true; |
659 | } |
660 | deleteProperty(exec, propertyName); |
661 | return putDescriptor(exec, target: this, propertyName, descriptor, attributes: current.attributesWithOverride(other: descriptor), oldValue: current.value()); |
662 | } |
663 | |
664 | // Changing the accessor functions of an existing accessor property |
665 | ASSERT(descriptor.isAccessorDescriptor()); |
666 | if (!current.configurable()) { |
667 | if (descriptor.setterPresent() && !(current.setter() && JSValue::strictEqual(exec, v1: current.setter(), v2: descriptor.setter()))) { |
668 | if (throwException) |
669 | throwError(exec, TypeError, message: "Attempting to change the setter of an unconfigurable property." ); |
670 | return false; |
671 | } |
672 | if (descriptor.getterPresent() && !(current.getter() && JSValue::strictEqual(exec, v1: current.getter(), v2: descriptor.getter()))) { |
673 | if (throwException) |
674 | throwError(exec, TypeError, message: "Attempting to change the getter of an unconfigurable property." ); |
675 | return false; |
676 | } |
677 | } |
678 | JSValue accessor = getDirect(propertyName); |
679 | if (!accessor) |
680 | return false; |
681 | GetterSetter* getterSetter = asGetterSetter(value: accessor); |
682 | if (current.attributesEqual(other: descriptor)) { |
683 | if (descriptor.setter()) |
684 | getterSetter->setSetter(asObject(value: descriptor.setter())); |
685 | if (descriptor.getter()) |
686 | getterSetter->setGetter(asObject(value: descriptor.getter())); |
687 | return true; |
688 | } |
689 | deleteProperty(exec, propertyName); |
690 | unsigned attrs = current.attributesWithOverride(other: descriptor); |
691 | if (descriptor.setter()) |
692 | attrs |= Setter; |
693 | if (descriptor.getter()) |
694 | attrs |= Getter; |
695 | putDirect(propertyName, value: getterSetter, attributes: attrs); |
696 | return true; |
697 | } |
698 | |
699 | } // namespace JSC |
700 | |