PropertyAttribute needs a CustomValue bit.
[WebKit-https.git] / Source / JavaScriptCore / runtime / JSObjectInlines.h
1 /*
2  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
4  *  Copyright (C) 2003-2017 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 #pragma once
25
26 #include "AuxiliaryBarrierInlines.h"
27 #include "Error.h"
28 #include "JSObject.h"
29 #include "Lookup.h"
30 #include "StructureInlines.h"
31
32 namespace JSC {
33
34 // Section 7.3.17 of the spec.
35 template <typename AddFunction> // Add function should have a type like: (JSValue, RuntimeType) -> bool
36 void createListFromArrayLike(ExecState* exec, JSValue arrayLikeValue, RuntimeTypeMask legalTypesFilter, const String& errorMessage, AddFunction addFunction)
37 {
38     VM& vm = exec->vm();
39     auto scope = DECLARE_THROW_SCOPE(vm);
40     
41     Vector<JSValue> result;
42     JSValue lengthProperty = arrayLikeValue.get(exec, vm.propertyNames->length);
43     RETURN_IF_EXCEPTION(scope, void());
44     double lengthAsDouble = lengthProperty.toLength(exec);
45     RETURN_IF_EXCEPTION(scope, void());
46     RELEASE_ASSERT(lengthAsDouble >= 0.0 && lengthAsDouble == std::trunc(lengthAsDouble));
47     uint64_t length = static_cast<uint64_t>(lengthAsDouble);
48     for (uint64_t index = 0; index < length; index++) {
49         JSValue next = arrayLikeValue.get(exec, index);
50         RETURN_IF_EXCEPTION(scope, void());
51         
52         RuntimeType type = runtimeTypeForValue(vm, next);
53         if (!(type & legalTypesFilter)) {
54             throwTypeError(exec, scope, errorMessage);
55             return;
56         }
57         
58         bool exitEarly = addFunction(next, type);
59         if (exitEarly)
60             return;
61     }
62 }
63
64 ALWAYS_INLINE bool JSObject::canPerformFastPutInlineExcludingProto(VM& vm)
65 {
66     // Check if there are any setters or getters in the prototype chain
67     JSValue prototype;
68     JSObject* obj = this;
69     while (true) {
70         MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
71         if (obj->structure(vm)->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || obj->methodTable(vm)->getPrototype != defaultGetPrototype)
72             return false;
73
74         prototype = obj->getPrototypeDirect(vm);
75         if (prototype.isNull())
76             return true;
77
78         obj = asObject(prototype);
79     }
80
81     ASSERT_NOT_REACHED();
82 }
83
84 ALWAYS_INLINE bool JSObject::canPerformFastPutInline(VM& vm, PropertyName propertyName)
85 {
86     if (UNLIKELY(propertyName == vm.propertyNames->underscoreProto))
87         return false;
88     return canPerformFastPutInlineExcludingProto(vm);
89 }
90
91 template<typename CallbackWhenNoException>
92 ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSObject::getPropertySlot(ExecState* exec, PropertyName propertyName, CallbackWhenNoException callback) const
93 {
94     PropertySlot slot(this, PropertySlot::InternalMethodType::Get);
95     return getPropertySlot(exec, propertyName, slot, callback);
96 }
97
98 template<typename CallbackWhenNoException>
99 ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSObject::getPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot, CallbackWhenNoException callback) const
100 {
101     VM& vm = exec->vm();
102     auto scope = DECLARE_THROW_SCOPE(vm);
103     bool found = const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
104     RETURN_IF_EXCEPTION(scope, { });
105     RELEASE_AND_RETURN(scope, callback(found, slot));
106 }
107
108 ALWAYS_INLINE bool JSObject::getPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot)
109 {
110     VM& vm = exec->vm();
111     auto scope = DECLARE_THROW_SCOPE(vm);
112     auto& structureIDTable = vm.heap.structureIDTable();
113     JSObject* object = this;
114     MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
115     while (true) {
116         Structure* structure = structureIDTable.get(object->structureID());
117         bool hasSlot = structure->classInfo()->methodTable.getOwnPropertySlotByIndex(object, exec, propertyName, slot);
118         RETURN_IF_EXCEPTION(scope, false);
119         if (hasSlot)
120             return true;
121         JSValue prototype;
122         if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
123             prototype = object->getPrototypeDirect(vm);
124         else {
125             prototype = object->getPrototype(vm, exec);
126             RETURN_IF_EXCEPTION(scope, false);
127         }
128         if (!prototype.isObject())
129             return false;
130         object = asObject(prototype);
131     }
132 }
133
134 ALWAYS_INLINE bool JSObject::getNonIndexPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
135 {
136     // This method only supports non-index PropertyNames.
137     ASSERT(!parseIndex(propertyName));
138
139     VM& vm = exec->vm();
140     auto scope = DECLARE_THROW_SCOPE(vm);
141     auto& structureIDTable = vm.heap.structureIDTable();
142     JSObject* object = this;
143     MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
144     while (true) {
145         Structure* structure = structureIDTable.get(object->structureID());
146         if (LIKELY(!TypeInfo::overridesGetOwnPropertySlot(object->inlineTypeFlags()))) {
147             if (object->getOwnNonIndexPropertySlot(vm, structure, propertyName, slot))
148                 return true;
149         } else {
150             bool hasSlot = structure->classInfo()->methodTable.getOwnPropertySlot(object, exec, propertyName, slot);
151             RETURN_IF_EXCEPTION(scope, false);
152             if (hasSlot)
153                 return true;
154         }
155         JSValue prototype;
156         if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
157             prototype = object->getPrototypeDirect(vm);
158         else {
159             prototype = object->getPrototype(vm, exec);
160             RETURN_IF_EXCEPTION(scope, false);
161         }
162         if (!prototype.isObject())
163             return false;
164         object = asObject(prototype);
165     }
166 }
167
168 inline bool JSObject::getOwnPropertySlotInline(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
169 {
170     VM& vm = exec->vm();
171     if (UNLIKELY(TypeInfo::overridesGetOwnPropertySlot(inlineTypeFlags())))
172         return methodTable(vm)->getOwnPropertySlot(this, exec, propertyName, slot);
173     return JSObject::getOwnPropertySlot(this, exec, propertyName, slot);
174 }
175
176 inline bool JSObject::mayInterceptIndexedAccesses(VM& vm)
177 {
178     return structure(vm)->mayInterceptIndexedAccesses();
179 }
180
181 inline void JSObject::putDirectWithoutTransition(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes)
182 {
183     ASSERT(!value.isGetterSetter() && !(attributes & PropertyAttribute::Accessor));
184     ASSERT(!value.isCustomGetterSetter());
185     StructureID structureID = this->structureID();
186     Structure* structure = vm.heap.structureIDTable().get(structureID);
187     PropertyOffset offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
188     bool shouldOptimize = false;
189     structure->willStoreValueForNewTransition(vm, propertyName, value, shouldOptimize);
190     putDirect(vm, offset, value);
191     if (attributes & PropertyAttribute::ReadOnly)
192         structure->setContainsReadOnlyProperties();
193 }
194
195 ALWAYS_INLINE PropertyOffset JSObject::prepareToPutDirectWithoutTransition(VM& vm, PropertyName propertyName, unsigned attributes, StructureID structureID, Structure* structure)
196 {
197     unsigned oldOutOfLineCapacity = structure->outOfLineCapacity();
198     PropertyOffset result;
199     structure->addPropertyWithoutTransition(
200         vm, propertyName, attributes,
201         [&] (const GCSafeConcurrentJSLocker&, PropertyOffset offset, PropertyOffset newLastOffset) {
202             unsigned newOutOfLineCapacity = Structure::outOfLineCapacity(newLastOffset);
203             if (newOutOfLineCapacity != oldOutOfLineCapacity) {
204                 Butterfly* butterfly = allocateMoreOutOfLineStorage(vm, oldOutOfLineCapacity, newOutOfLineCapacity);
205                 nukeStructureAndSetButterfly(vm, structureID, butterfly);
206                 structure->setLastOffset(newLastOffset);
207                 WTF::storeStoreFence();
208                 setStructureIDDirectly(structureID);
209             } else
210                 structure->setLastOffset(newLastOffset);
211
212             // This assertion verifies that the concurrent GC won't read garbage if the concurrentGC
213             // is running at the same time we put without transitioning.
214             ASSERT(!getDirect(offset) || !JSValue::encode(getDirect(offset)));
215             result = offset;
216         });
217     return result;
218 }
219
220 // ECMA 8.6.2.2
221 ALWAYS_INLINE bool JSObject::putInlineForJSObject(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
222 {
223     VM& vm = exec->vm();
224     auto scope = DECLARE_THROW_SCOPE(vm);
225
226     JSObject* thisObject = jsCast<JSObject*>(cell);
227     ASSERT(value);
228     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
229
230     if (UNLIKELY(isThisValueAltered(slot, thisObject)))
231         RELEASE_AND_RETURN(scope, ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode()));
232
233     // Try indexed put first. This is required for correctness, since loads on property names that appear like
234     // valid indices will never look in the named property storage.
235     if (std::optional<uint32_t> index = parseIndex(propertyName))
236         RELEASE_AND_RETURN(scope, putByIndex(thisObject, exec, index.value(), value, slot.isStrictMode()));
237
238     if (thisObject->canPerformFastPutInline(vm, propertyName)) {
239         ASSERT(!thisObject->prototypeChainMayInterceptStoreTo(vm, propertyName));
240         if (!thisObject->putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot))
241             return typeError(exec, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
242         return true;
243     }
244
245     RELEASE_AND_RETURN(scope, thisObject->putInlineSlow(exec, propertyName, value, slot));
246 }
247
248 // HasOwnProperty(O, P) from section 7.3.11 in the spec.
249 // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-hasownproperty
250 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName, PropertySlot& slot) const
251 {
252     VM& vm = exec->vm();
253     ASSERT(slot.internalMethodType() == PropertySlot::InternalMethodType::GetOwnProperty);
254     if (LIKELY(const_cast<JSObject*>(this)->methodTable(vm)->getOwnPropertySlot == JSObject::getOwnPropertySlot))
255         return JSObject::getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
256     return const_cast<JSObject*>(this)->methodTable(vm)->getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
257 }
258
259 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName) const
260 {
261     PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
262     return hasOwnProperty(exec, propertyName, slot);
263 }
264
265 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, unsigned propertyName) const
266 {
267     PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
268     return const_cast<JSObject*>(this)->methodTable(exec->vm())->getOwnPropertySlotByIndex(const_cast<JSObject*>(this), exec, propertyName, slot);
269 }
270
271 template<JSObject::PutMode mode>
272 ALWAYS_INLINE bool JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes, PutPropertySlot& slot)
273 {
274     ASSERT(value);
275     ASSERT(value.isGetterSetter() == !!(attributes & PropertyAttribute::Accessor));
276     ASSERT(value.isCustomGetterSetter() == !!(attributes & PropertyAttribute::CustomAccessorOrValue));
277     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(this));
278     ASSERT(!parseIndex(propertyName));
279
280     StructureID structureID = this->structureID();
281     Structure* structure = vm.heap.structureIDTable().get(structureID);
282     if (structure->isDictionary()) {
283         ASSERT(!isCopyOnWrite(indexingMode()));
284         ASSERT(!structure->hasInferredTypes());
285         
286         unsigned currentAttributes;
287         PropertyOffset offset = structure->get(vm, propertyName, currentAttributes);
288         if (offset != invalidOffset) {
289             if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
290                 return false;
291
292             putDirect(vm, offset, value);
293             structure->didReplaceProperty(offset);
294
295             if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessorOrValue) != (currentAttributes & PropertyAttribute::CustomAccessorOrValue)) {
296                 ASSERT(!(attributes & PropertyAttribute::ReadOnly));
297                 setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
298             } else
299                 slot.setExistingProperty(this, offset);
300
301             return true;
302         }
303
304         if ((mode == PutModePut) && !isStructureExtensible(vm))
305             return false;
306
307         offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
308         validateOffset(offset);
309         putDirect(vm, offset, value);
310         slot.setNewProperty(this, offset);
311         if (attributes & PropertyAttribute::ReadOnly)
312             this->structure(vm)->setContainsReadOnlyProperties();
313         return true;
314     }
315
316     PropertyOffset offset;
317     size_t currentCapacity = this->structure(vm)->outOfLineCapacity();
318     Structure* newStructure = Structure::addPropertyTransitionToExistingStructure(
319         structure, propertyName, attributes, offset);
320     if (newStructure) {
321         newStructure->willStoreValueForExistingTransition(
322             vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
323         
324         Butterfly* newButterfly = butterfly();
325         if (currentCapacity != newStructure->outOfLineCapacity()) {
326             ASSERT(newStructure != this->structure(vm));
327             newButterfly = allocateMoreOutOfLineStorage(vm, currentCapacity, newStructure->outOfLineCapacity());
328             nukeStructureAndSetButterfly(vm, structureID, newButterfly);
329         }
330
331         validateOffset(offset);
332         ASSERT(newStructure->isValidOffset(offset));
333
334         // This assertion verifies that the concurrent GC won't read garbage if the concurrentGC
335         // is running at the same time we put without transitioning.
336         ASSERT(!getDirect(offset) || !JSValue::encode(getDirect(offset)));
337         putDirect(vm, offset, value);
338         setStructure(vm, newStructure);
339         slot.setNewProperty(this, offset);
340         return true;
341     }
342
343     unsigned currentAttributes;
344     bool hasInferredType;
345     offset = structure->get(vm, propertyName, currentAttributes, hasInferredType);
346     if (offset != invalidOffset) {
347         if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
348             return false;
349
350         structure->didReplaceProperty(offset);
351         if (UNLIKELY(hasInferredType)) {
352             structure->willStoreValueForReplace(
353                 vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
354         }
355
356         putDirect(vm, offset, value);
357
358         if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessorOrValue) != (currentAttributes & PropertyAttribute::CustomAccessorOrValue)) {
359             ASSERT(!(attributes & PropertyAttribute::ReadOnly));
360             setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
361         } else
362             slot.setExistingProperty(this, offset);
363
364         return true;
365     }
366
367     if ((mode == PutModePut) && !isStructureExtensible(vm))
368         return false;
369
370     // We want the structure transition watchpoint to fire after this object has switched
371     // structure. This allows adaptive watchpoints to observe if the new structure is the one
372     // we want.
373     DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
374     
375     newStructure = Structure::addNewPropertyTransition(
376         vm, structure, propertyName, attributes, offset, slot.context(), &deferredWatchpointFire);
377     newStructure->willStoreValueForNewTransition(
378         vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
379     
380     validateOffset(offset);
381     ASSERT(newStructure->isValidOffset(offset));
382     size_t oldCapacity = structure->outOfLineCapacity();
383     size_t newCapacity = newStructure->outOfLineCapacity();
384     ASSERT(oldCapacity <= newCapacity);
385     if (oldCapacity != newCapacity) {
386         Butterfly* newButterfly = allocateMoreOutOfLineStorage(vm, oldCapacity, newCapacity);
387         nukeStructureAndSetButterfly(vm, structureID, newButterfly);
388     }
389
390     // This assertion verifies that the concurrent GC won't read garbage if the concurrentGC
391     // is running at the same time we put without transitioning.
392     ASSERT(!getDirect(offset) || !JSValue::encode(getDirect(offset)));
393     putDirect(vm, offset, value);
394     setStructure(vm, newStructure);
395     slot.setNewProperty(this, offset);
396     if (attributes & PropertyAttribute::ReadOnly)
397         newStructure->setContainsReadOnlyProperties();
398     return true;
399 }
400
401 } // namespace JSC