Add support for private names
[WebKit-https.git] / Source / JavaScriptCore / 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, 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 "CopiedSpaceInlineMethods.h"
28 #include "DatePrototype.h"
29 #include "ErrorConstructor.h"
30 #include "GetterSetter.h"
31 #include "JSFunction.h"
32 #include "JSGlobalObject.h"
33 #include "JSGlobalThis.h"
34 #include "Lookup.h"
35 #include "NativeErrorConstructor.h"
36 #include "Nodes.h"
37 #include "ObjectPrototype.h"
38 #include "Operations.h"
39 #include "PropertyDescriptor.h"
40 #include "PropertyNameArray.h"
41 #include <math.h>
42 #include <wtf/Assertions.h>
43
44 namespace JSC {
45
46 ASSERT_CLASS_FITS_IN_CELL(JSObject);
47 ASSERT_CLASS_FITS_IN_CELL(JSNonFinalObject);
48 ASSERT_CLASS_FITS_IN_CELL(JSFinalObject);
49
50 ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSObject);
51 ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSFinalObject);
52
53 const char* StrictModeReadonlyPropertyWriteError = "Attempted to assign to readonly property.";
54
55 const ClassInfo JSObject::s_info = { "Object", 0, 0, 0, CREATE_METHOD_TABLE(JSObject) };
56
57 const ClassInfo JSFinalObject::s_info = { "Object", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(JSFinalObject) };
58
59 static inline void getClassPropertyNames(ExecState* exec, const ClassInfo* classInfo, PropertyNameArray& propertyNames, EnumerationMode mode)
60 {
61     // Add properties from the static hashtables of properties
62     for (; classInfo; classInfo = classInfo->parentClass) {
63         const HashTable* table = classInfo->propHashTable(exec);
64         if (!table)
65             continue;
66         table->initializeIfNeeded(exec);
67         ASSERT(table->table);
68
69         int hashSizeMask = table->compactSize - 1;
70         const HashEntry* entry = table->table;
71         for (int i = 0; i <= hashSizeMask; ++i, ++entry) {
72             if (entry->key() && (!(entry->attributes() & DontEnum) || (mode == IncludeDontEnumProperties)))
73                 propertyNames.add(entry->key());
74         }
75     }
76 }
77
78 void JSObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
79 {
80     JSObject* thisObject = jsCast<JSObject*>(cell);
81     ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info);
82 #if !ASSERT_DISABLED
83     bool wasCheckingForDefaultMarkViolation = visitor.m_isCheckingForDefaultMarkViolation;
84     visitor.m_isCheckingForDefaultMarkViolation = false;
85 #endif
86
87     JSCell::visitChildren(thisObject, visitor);
88
89     PropertyStorage storage = thisObject->propertyStorage();
90     size_t storageSize = thisObject->structure()->propertyStorageSize();
91     if (thisObject->isUsingInlineStorage())
92         visitor.appendValues(storage, storageSize);
93     else {
94         // We have this extra temp here to slake GCC's thirst for the blood of those who dereference type-punned pointers.
95         void* temp = storage;
96         visitor.copyAndAppend(&temp, thisObject->structure()->propertyStorageCapacity() * sizeof(WriteBarrierBase<Unknown>), storage->slot(), storageSize);
97         storage = static_cast<PropertyStorage>(temp);
98         thisObject->m_propertyStorage.set(storage, StorageBarrier::Unchecked);
99     }
100
101     if (thisObject->m_inheritorID)
102         visitor.append(&thisObject->m_inheritorID);
103
104 #if !ASSERT_DISABLED
105     visitor.m_isCheckingForDefaultMarkViolation = wasCheckingForDefaultMarkViolation;
106 #endif
107 }
108
109 UString JSObject::className(const JSObject* object)
110 {
111     const ClassInfo* info = object->classInfo();
112     ASSERT(info);
113     return info->className;
114 }
115
116 bool JSObject::getOwnPropertySlotByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, PropertySlot& slot)
117 {
118     JSObject* thisObject = jsCast<JSObject*>(cell);
119     return thisObject->methodTable()->getOwnPropertySlot(thisObject, exec, Identifier::from(exec, propertyName), slot);
120 }
121
122 // ECMA 8.6.2.2
123 void JSObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
124 {
125     JSObject* thisObject = jsCast<JSObject*>(cell);
126     ASSERT(value);
127     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
128     JSGlobalData& globalData = exec->globalData();
129
130     // Check if there are any setters or getters in the prototype chain
131     JSValue prototype;
132     if (propertyName != exec->propertyNames().underscoreProto) {
133         for (JSObject* obj = thisObject; !obj->structure()->hasReadOnlyOrGetterSetterPropertiesExcludingProto(); obj = asObject(prototype)) {
134             prototype = obj->prototype();
135             if (prototype.isNull()) {
136                 if (!thisObject->putDirectInternal<PutModePut>(globalData, propertyName, value, 0, slot, getJSFunction(value)) && slot.isStrictMode())
137                     throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
138                 return;
139             }
140         }
141     }
142
143     for (JSObject* obj = thisObject; ; obj = asObject(prototype)) {
144         unsigned attributes;
145         JSCell* specificValue;
146         size_t offset = obj->structure()->get(globalData, propertyName, attributes, specificValue);
147         if (offset != WTF::notFound) {
148             if (attributes & ReadOnly) {
149                 if (slot.isStrictMode())
150                     throwError(exec, createTypeError(exec, StrictModeReadonlyPropertyWriteError));
151                 return;
152             }
153
154             JSValue gs = obj->getDirectOffset(offset);
155             if (gs.isGetterSetter()) {
156                 JSObject* setterFunc = asGetterSetter(gs)->setter();        
157                 if (!setterFunc) {
158                     if (slot.isStrictMode())
159                         throwError(exec, createTypeError(exec, "setting a property that has only a getter"));
160                     return;
161                 }
162                 
163                 CallData callData;
164                 CallType callType = setterFunc->methodTable()->getCallData(setterFunc, callData);
165                 MarkedArgumentBuffer args;
166                 args.append(value);
167
168                 // If this is WebCore's global object then we need to substitute the shell.
169                 call(exec, setterFunc, callType, callData, thisObject->methodTable()->toThisObject(thisObject, exec), args);
170                 return;
171             }
172
173             // If there's an existing property on the object or one of its 
174             // prototypes it should be replaced, so break here.
175             break;
176         }
177
178         prototype = obj->prototype();
179         if (prototype.isNull())
180             break;
181     }
182     
183     if (!thisObject->putDirectInternal<PutModePut>(globalData, propertyName, value, 0, slot, getJSFunction(value)) && slot.isStrictMode())
184         throwTypeError(exec, StrictModeReadonlyPropertyWriteError);
185     return;
186 }
187
188 void JSObject::putByIndex(JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow)
189 {
190     PutPropertySlot slot(shouldThrow);
191     JSObject* thisObject = jsCast<JSObject*>(cell);
192     thisObject->methodTable()->put(thisObject, exec, Identifier::from(exec, propertyName), value, slot);
193 }
194
195 void JSObject::putDirectVirtual(JSObject* object, ExecState* exec, PropertyName propertyName, JSValue value, unsigned attributes)
196 {
197     ASSERT(!value.isGetterSetter() && !(attributes & Accessor));
198     PutPropertySlot slot;
199     object->putDirectInternal<PutModeDefineOwnProperty>(exec->globalData(), propertyName, value, attributes, slot, getJSFunction(value));
200 }
201
202 bool JSObject::setPrototypeWithCycleCheck(JSGlobalData& globalData, JSValue prototype)
203 {
204     JSValue checkFor = this;
205     if (this->isGlobalObject())
206         checkFor = jsCast<JSGlobalObject*>(this)->globalExec()->thisValue();
207
208     JSValue nextPrototype = prototype;
209     while (nextPrototype && nextPrototype.isObject()) {
210         if (nextPrototype == checkFor)
211             return false;
212         nextPrototype = asObject(nextPrototype)->prototype();
213     }
214     setPrototype(globalData, prototype);
215     return true;
216 }
217
218 bool JSObject::allowsAccessFrom(ExecState* exec)
219 {
220     JSGlobalObject* globalObject = isGlobalThis() ? jsCast<JSGlobalThis*>(this)->unwrappedObject() : this->globalObject();
221     return globalObject->globalObjectMethodTable()->allowsAccessFrom(globalObject, exec);
222 }
223
224 void JSObject::putDirectAccessor(JSGlobalData& globalData, PropertyName propertyName, JSValue value, unsigned attributes)
225 {
226     ASSERT(value.isGetterSetter() && (attributes & Accessor));
227
228     PutPropertySlot slot;
229     putDirectInternal<PutModeDefineOwnProperty>(globalData, propertyName, value, attributes, slot, getJSFunction(value));
230
231     // putDirect will change our Structure if we add a new property. For
232     // getters and setters, though, we also need to change our Structure
233     // if we override an existing non-getter or non-setter.
234     if (slot.type() != PutPropertySlot::NewProperty)
235         setStructure(globalData, Structure::attributeChangeTransition(globalData, structure(), propertyName, attributes));
236
237     if (attributes & ReadOnly)
238         structure()->setContainsReadOnlyProperties();
239
240     structure()->setHasGetterSetterProperties(propertyName == globalData.propertyNames->underscoreProto);
241 }
242
243 bool JSObject::hasProperty(ExecState* exec, PropertyName propertyName) const
244 {
245     PropertySlot slot;
246     return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
247 }
248
249 bool JSObject::hasProperty(ExecState* exec, unsigned propertyName) const
250 {
251     PropertySlot slot;
252     return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
253 }
254
255 // ECMA 8.6.2.5
256 bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
257 {
258     JSObject* thisObject = jsCast<JSObject*>(cell);
259
260     if (!thisObject->staticFunctionsReified())
261         thisObject->reifyStaticFunctionsForDelete(exec);
262
263     unsigned attributes;
264     JSCell* specificValue;
265     if (thisObject->structure()->get(exec->globalData(), propertyName, attributes, specificValue) != WTF::notFound) {
266         if (attributes & DontDelete && !exec->globalData().isInDefineOwnProperty())
267             return false;
268         thisObject->removeDirect(exec->globalData(), propertyName);
269         return true;
270     }
271
272     // Look in the static hashtable of properties
273     const HashEntry* entry = thisObject->findPropertyHashEntry(exec, propertyName);
274     if (entry && entry->attributes() & DontDelete && !exec->globalData().isInDefineOwnProperty())
275         return false; // this builtin property can't be deleted
276
277     // FIXME: Should the code here actually do some deletion?
278     return true;
279 }
280
281 bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName) const
282 {
283     PropertySlot slot;
284     return const_cast<JSObject*>(this)->methodTable()->getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
285 }
286
287 bool JSObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
288 {
289     JSObject* thisObject = jsCast<JSObject*>(cell);
290     return thisObject->methodTable()->deleteProperty(thisObject, exec, Identifier::from(exec, propertyName));
291 }
292
293 static ALWAYS_INLINE JSValue callDefaultValueFunction(ExecState* exec, const JSObject* object, PropertyName propertyName)
294 {
295     JSValue function = object->get(exec, propertyName);
296     CallData callData;
297     CallType callType = getCallData(function, callData);
298     if (callType == CallTypeNone)
299         return exec->exception();
300
301     // Prevent "toString" and "valueOf" from observing execution if an exception
302     // is pending.
303     if (exec->hadException())
304         return exec->exception();
305
306     JSValue result = call(exec, function, callType, callData, const_cast<JSObject*>(object), exec->emptyList());
307     ASSERT(!result.isGetterSetter());
308     if (exec->hadException())
309         return exec->exception();
310     if (result.isObject())
311         return JSValue();
312     return result;
313 }
314
315 bool JSObject::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) const
316 {
317     result = methodTable()->defaultValue(this, exec, PreferNumber);
318     number = result.toNumber(exec);
319     return !result.isString();
320 }
321
322 // ECMA 8.6.2.6
323 JSValue JSObject::defaultValue(const JSObject* object, ExecState* exec, PreferredPrimitiveType hint)
324 {
325     // Must call toString first for Date objects.
326     if ((hint == PreferString) || (hint != PreferNumber && object->prototype() == exec->lexicalGlobalObject()->datePrototype())) {
327         JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().toString);
328         if (value)
329             return value;
330         value = callDefaultValueFunction(exec, object, exec->propertyNames().valueOf);
331         if (value)
332             return value;
333     } else {
334         JSValue value = callDefaultValueFunction(exec, object, exec->propertyNames().valueOf);
335         if (value)
336             return value;
337         value = callDefaultValueFunction(exec, object, exec->propertyNames().toString);
338         if (value)
339             return value;
340     }
341
342     ASSERT(!exec->hadException());
343
344     return throwError(exec, createTypeError(exec, "No default value"));
345 }
346
347 const HashEntry* JSObject::findPropertyHashEntry(ExecState* exec, PropertyName propertyName) const
348 {
349     for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
350         if (const HashTable* propHashTable = info->propHashTable(exec)) {
351             if (const HashEntry* entry = propHashTable->entry(exec, propertyName))
352                 return entry;
353         }
354     }
355     return 0;
356 }
357
358 bool JSObject::hasInstance(JSObject*, ExecState* exec, JSValue value, JSValue proto)
359 {
360     if (!value.isObject())
361         return false;
362
363     if (!proto.isObject()) {
364         throwError(exec, createTypeError(exec, "instanceof called on an object with an invalid prototype property."));
365         return false;
366     }
367
368     JSObject* object = asObject(value);
369     while ((object = object->prototype().getObject())) {
370         if (proto == object)
371             return true;
372     }
373     return false;
374 }
375
376 bool JSObject::propertyIsEnumerable(ExecState* exec, const Identifier& propertyName) const
377 {
378     PropertyDescriptor descriptor;
379     if (!const_cast<JSObject*>(this)->methodTable()->getOwnPropertyDescriptor(const_cast<JSObject*>(this), exec, propertyName, descriptor))
380         return false;
381     return descriptor.enumerable();
382 }
383
384 bool JSObject::getPropertySpecificValue(ExecState* exec, PropertyName propertyName, JSCell*& specificValue) const
385 {
386     unsigned attributes;
387     if (structure()->get(exec->globalData(), propertyName, attributes, specificValue) != WTF::notFound)
388         return true;
389
390     // This could be a function within the static table? - should probably
391     // also look in the hash?  This currently should not be a problem, since
392     // we've currently always call 'get' first, which should have populated
393     // the normal storage.
394     return false;
395 }
396
397 void JSObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
398 {
399     object->methodTable()->getOwnPropertyNames(object, exec, propertyNames, mode);
400
401     if (object->prototype().isNull())
402         return;
403
404     JSObject* prototype = asObject(object->prototype());
405     while(1) {
406         if (prototype->structure()->typeInfo().overridesGetPropertyNames()) {
407             prototype->methodTable()->getPropertyNames(prototype, exec, propertyNames, mode);
408             break;
409         }
410         prototype->methodTable()->getOwnPropertyNames(prototype, exec, propertyNames, mode);
411         JSValue nextProto = prototype->prototype();
412         if (nextProto.isNull())
413             break;
414         prototype = asObject(nextProto);
415     }
416 }
417
418 void JSObject::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
419 {
420     object->structure()->getPropertyNamesFromStructure(exec->globalData(), propertyNames, mode);
421     if (!object->staticFunctionsReified())
422         getClassPropertyNames(exec, object->classInfo(), propertyNames, mode);
423 }
424
425 double JSObject::toNumber(ExecState* exec) const
426 {
427     JSValue primitive = toPrimitive(exec, PreferNumber);
428     if (exec->hadException()) // should be picked up soon in Nodes.cpp
429         return 0.0;
430     return primitive.toNumber(exec);
431 }
432
433 JSString* JSObject::toString(ExecState* exec) const
434 {
435     JSValue primitive = toPrimitive(exec, PreferString);
436     if (exec->hadException())
437         return jsEmptyString(exec);
438     return primitive.toString(exec);
439 }
440
441 JSObject* JSObject::toThisObject(JSCell* cell, ExecState*)
442 {
443     return jsCast<JSObject*>(cell);
444 }
445
446 JSObject* JSObject::unwrappedObject()
447 {
448     if (isGlobalThis())
449         return jsCast<JSGlobalThis*>(this)->unwrappedObject();
450     return this;
451 }
452
453 void JSObject::seal(JSGlobalData& globalData)
454 {
455     if (isSealed(globalData))
456         return;
457     preventExtensions(globalData);
458     setStructure(globalData, Structure::sealTransition(globalData, structure()));
459 }
460
461 void JSObject::freeze(JSGlobalData& globalData)
462 {
463     if (isFrozen(globalData))
464         return;
465     preventExtensions(globalData);
466     setStructure(globalData, Structure::freezeTransition(globalData, structure()));
467 }
468
469 void JSObject::preventExtensions(JSGlobalData& globalData)
470 {
471     if (isJSArray(this))
472         asArray(this)->enterDictionaryMode(globalData);
473     if (isExtensible())
474         setStructure(globalData, Structure::preventExtensionsTransition(globalData, structure()));
475 }
476
477 // This presently will flatten to an uncachable dictionary; this is suitable
478 // for use in delete, we may want to do something different elsewhere.
479 void JSObject::reifyStaticFunctionsForDelete(ExecState* exec)
480 {
481     ASSERT(!staticFunctionsReified());
482     JSGlobalData& globalData = exec->globalData();
483
484     // If this object's ClassInfo has no static properties, then nothing to reify!
485     // We can safely set the flag to avoid the expensive check again in the future.
486     if (!classInfo()->hasStaticProperties()) {
487         structure()->setStaticFunctionsReified();
488         return;
489     }
490
491     if (!structure()->isUncacheableDictionary())
492         setStructure(globalData, Structure::toUncacheableDictionaryTransition(globalData, structure()));
493
494     for (const ClassInfo* info = classInfo(); info; info = info->parentClass) {
495         const HashTable* hashTable = info->propHashTable(globalObject()->globalExec());
496         if (!hashTable)
497             continue;
498         PropertySlot slot;
499         for (HashTable::ConstIterator iter = hashTable->begin(globalData); iter != hashTable->end(globalData); ++iter) {
500             if (iter->attributes() & Function)
501                 setUpStaticFunctionSlot(globalObject()->globalExec(), *iter, this, Identifier(&globalData, iter->key()), slot);
502         }
503     }
504
505     structure()->setStaticFunctionsReified();
506 }
507
508 void JSObject::removeDirect(JSGlobalData& globalData, PropertyName propertyName)
509 {
510     if (structure()->get(globalData, propertyName) == WTF::notFound)
511         return;
512
513     size_t offset;
514     if (structure()->isUncacheableDictionary()) {
515         offset = structure()->removePropertyWithoutTransition(globalData, propertyName);
516         if (offset != WTF::notFound)
517             putUndefinedAtDirectOffset(offset);
518         return;
519     }
520
521     setStructure(globalData, Structure::removePropertyTransition(globalData, structure(), propertyName, offset));
522     if (offset != WTF::notFound)
523         putUndefinedAtDirectOffset(offset);
524 }
525
526 NEVER_INLINE void JSObject::fillGetterPropertySlot(PropertySlot& slot, WriteBarrierBase<Unknown>* location)
527 {
528     if (JSObject* getterFunction = asGetterSetter(location->get())->getter()) {
529         if (!structure()->isDictionary())
530             slot.setCacheableGetterSlot(this, getterFunction, offsetForLocation(location));
531         else
532             slot.setGetterSlot(getterFunction);
533     } else
534         slot.setUndefined();
535 }
536
537 Structure* JSObject::createInheritorID(JSGlobalData& globalData)
538 {
539     JSGlobalObject* globalObject;
540     if (isGlobalThis())
541         globalObject = static_cast<JSGlobalThis*>(this)->unwrappedObject();
542     else
543         globalObject = structure()->globalObject();
544     ASSERT(globalObject);
545     m_inheritorID.set(globalData, this, createEmptyObjectStructure(globalData, globalObject, this));
546     ASSERT(m_inheritorID->isEmpty());
547     return m_inheritorID.get();
548 }
549
550 PropertyStorage JSObject::growPropertyStorage(JSGlobalData& globalData, size_t oldSize, size_t newSize)
551 {
552     ASSERT(newSize > oldSize);
553
554     // It's important that this function not rely on structure(), since
555     // we might be in the middle of a transition.
556
557     PropertyStorage oldPropertyStorage = m_propertyStorage.get();
558     PropertyStorage newPropertyStorage = 0;
559
560     if (isUsingInlineStorage()) {
561         // We have this extra temp here to slake GCC's thirst for the blood of those who dereference type-punned pointers.
562         void* temp = newPropertyStorage;
563         if (!globalData.heap.tryAllocateStorage(sizeof(WriteBarrierBase<Unknown>) * newSize, &temp))
564             CRASH();
565         newPropertyStorage = static_cast<PropertyStorage>(temp);
566
567         for (unsigned i = 0; i < oldSize; ++i)
568             newPropertyStorage[i] = oldPropertyStorage[i];
569     } else {
570         // We have this extra temp here to slake GCC's thirst for the blood of those who dereference type-punned pointers.
571         void* temp = oldPropertyStorage;
572         if (!globalData.heap.tryReallocateStorage(&temp, sizeof(WriteBarrierBase<Unknown>) * oldSize, sizeof(WriteBarrierBase<Unknown>) * newSize))
573             CRASH();
574         newPropertyStorage = static_cast<PropertyStorage>(temp);
575     }
576
577     ASSERT(newPropertyStorage);
578     return newPropertyStorage;
579 }
580
581 bool JSObject::getOwnPropertyDescriptor(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor)
582 {
583     unsigned attributes = 0;
584     JSCell* cell = 0;
585     size_t offset = object->structure()->get(exec->globalData(), propertyName, attributes, cell);
586     if (offset == WTF::notFound)
587         return false;
588     descriptor.setDescriptor(object->getDirectOffset(offset), attributes);
589     return true;
590 }
591
592 bool JSObject::getPropertyDescriptor(ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor)
593 {
594     JSObject* object = this;
595     while (true) {
596         if (object->methodTable()->getOwnPropertyDescriptor(object, exec, propertyName, descriptor))
597             return true;
598         JSValue prototype = object->prototype();
599         if (!prototype.isObject())
600             return false;
601         object = asObject(prototype);
602     }
603 }
604
605 static bool putDescriptor(ExecState* exec, JSObject* target, PropertyName propertyName, PropertyDescriptor& descriptor, unsigned attributes, const PropertyDescriptor& oldDescriptor)
606 {
607     if (descriptor.isGenericDescriptor() || descriptor.isDataDescriptor()) {
608         if (descriptor.isGenericDescriptor() && oldDescriptor.isAccessorDescriptor()) {
609             GetterSetter* accessor = GetterSetter::create(exec);
610             if (oldDescriptor.getterPresent())
611                 accessor->setGetter(exec->globalData(), oldDescriptor.getterObject());
612             if (oldDescriptor.setterPresent())
613                 accessor->setSetter(exec->globalData(), oldDescriptor.setterObject());
614             target->putDirectAccessor(exec->globalData(), propertyName, accessor, attributes | Accessor);
615             return true;
616         }
617         JSValue newValue = jsUndefined();
618         if (descriptor.value())
619             newValue = descriptor.value();
620         else if (oldDescriptor.value())
621             newValue = oldDescriptor.value();
622         target->putDirect(exec->globalData(), propertyName, newValue, attributes & ~Accessor);
623         if (attributes & ReadOnly)
624             target->structure()->setContainsReadOnlyProperties();
625         return true;
626     }
627     attributes &= ~ReadOnly;
628     GetterSetter* accessor = GetterSetter::create(exec);
629
630     if (descriptor.getterPresent())
631         accessor->setGetter(exec->globalData(), descriptor.getterObject());
632     else if (oldDescriptor.getterPresent())
633         accessor->setGetter(exec->globalData(), oldDescriptor.getterObject());
634     if (descriptor.setterPresent())
635         accessor->setSetter(exec->globalData(), descriptor.setterObject());
636     else if (oldDescriptor.setterPresent())
637         accessor->setSetter(exec->globalData(), oldDescriptor.setterObject());
638
639     target->putDirectAccessor(exec->globalData(), propertyName, accessor, attributes | Accessor);
640     return true;
641 }
642
643 class DefineOwnPropertyScope {
644 public:
645     DefineOwnPropertyScope(ExecState* exec)
646         : m_globalData(exec->globalData())
647     {
648         m_globalData.setInDefineOwnProperty(true);
649     }
650
651     ~DefineOwnPropertyScope()
652     {
653         m_globalData.setInDefineOwnProperty(false);
654     }
655
656 private:
657     JSGlobalData& m_globalData;
658 };
659
660 bool JSObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, PropertyDescriptor& descriptor, bool throwException)
661 {
662     // Track on the globaldata that we're in define property.
663     // Currently DefineOwnProperty uses delete to remove properties when they are being replaced
664     // (particularly when changing attributes), however delete won't allow non-configurable (i.e.
665     // DontDelete) properties to be deleted. For now, we can use this flag to make this work.
666     DefineOwnPropertyScope scope(exec);
667
668     // If we have a new property we can just put it on normally
669     PropertyDescriptor current;
670     if (!object->methodTable()->getOwnPropertyDescriptor(object, exec, propertyName, current)) {
671         // unless extensions are prevented!
672         if (!object->isExtensible()) {
673             if (throwException)
674                 throwError(exec, createTypeError(exec, "Attempting to define property on object that is not extensible."));
675             return false;
676         }
677         PropertyDescriptor oldDescriptor;
678         oldDescriptor.setValue(jsUndefined());
679         return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributes(), oldDescriptor);
680     }
681
682     if (descriptor.isEmpty())
683         return true;
684
685     if (current.equalTo(exec, descriptor))
686         return true;
687
688     // Filter out invalid changes
689     if (!current.configurable()) {
690         if (descriptor.configurable()) {
691             if (throwException)
692                 throwError(exec, createTypeError(exec, "Attempting to configurable attribute of unconfigurable property."));
693             return false;
694         }
695         if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable()) {
696             if (throwException)
697                 throwError(exec, createTypeError(exec, "Attempting to change enumerable attribute of unconfigurable property."));
698             return false;
699         }
700     }
701
702     // A generic descriptor is simply changing the attributes of an existing property
703     if (descriptor.isGenericDescriptor()) {
704         if (!current.attributesEqual(descriptor)) {
705             object->methodTable()->deleteProperty(object, exec, propertyName);
706             return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
707         }
708         return true;
709     }
710
711     // Changing between a normal property or an accessor property
712     if (descriptor.isDataDescriptor() != current.isDataDescriptor()) {
713         if (!current.configurable()) {
714             if (throwException)
715                 throwError(exec, createTypeError(exec, "Attempting to change access mechanism for an unconfigurable property."));
716             return false;
717         }
718         object->methodTable()->deleteProperty(object, exec, propertyName);
719         return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
720     }
721
722     // Changing the value and attributes of an existing property
723     if (descriptor.isDataDescriptor()) {
724         if (!current.configurable()) {
725             if (!current.writable() && descriptor.writable()) {
726                 if (throwException)
727                     throwError(exec, createTypeError(exec, "Attempting to change writable attribute of unconfigurable property."));
728                 return false;
729             }
730             if (!current.writable()) {
731                 if (descriptor.value() && !sameValue(exec, current.value(), descriptor.value())) {
732                     if (throwException)
733                         throwError(exec, createTypeError(exec, "Attempting to change value of a readonly property."));
734                     return false;
735                 }
736             }
737         }
738         if (current.attributesEqual(descriptor) && !descriptor.value())
739             return true;
740         object->methodTable()->deleteProperty(object, exec, propertyName);
741         return putDescriptor(exec, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
742     }
743
744     // Changing the accessor functions of an existing accessor property
745     ASSERT(descriptor.isAccessorDescriptor());
746     if (!current.configurable()) {
747         if (descriptor.setterPresent() && !(current.setterPresent() && JSValue::strictEqual(exec, current.setter(), descriptor.setter()))) {
748             if (throwException)
749                 throwError(exec, createTypeError(exec, "Attempting to change the setter of an unconfigurable property."));
750             return false;
751         }
752         if (descriptor.getterPresent() && !(current.getterPresent() && JSValue::strictEqual(exec, current.getter(), descriptor.getter()))) {
753             if (throwException)
754                 throwError(exec, createTypeError(exec, "Attempting to change the getter of an unconfigurable property."));
755             return false;
756         }
757     }
758     JSValue accessor = object->getDirect(exec->globalData(), propertyName);
759     if (!accessor)
760         return false;
761     GetterSetter* getterSetter = asGetterSetter(accessor);
762     if (descriptor.setterPresent())
763         getterSetter->setSetter(exec->globalData(), descriptor.setterObject());
764     if (descriptor.getterPresent())
765         getterSetter->setGetter(exec->globalData(), descriptor.getterObject());
766     if (current.attributesEqual(descriptor))
767         return true;
768     object->methodTable()->deleteProperty(object, exec, propertyName);
769     unsigned attrs = descriptor.attributesOverridingCurrent(current);
770     object->putDirectAccessor(exec->globalData(), propertyName, getterSetter, attrs | Accessor);
771     return true;
772 }
773
774 JSObject* throwTypeError(ExecState* exec, const UString& message)
775 {
776     return throwError(exec, createTypeError(exec, message));
777 }
778
779 } // namespace JSC