[JSC] Pass VM& parameter as much as possible
[WebKit.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
31 namespace JSC {
32
33 // Section 7.3.17 of the spec.
34 template <typename AddFunction> // Add function should have a type like: (JSValue, RuntimeType) -> bool
35 void createListFromArrayLike(ExecState* exec, JSValue arrayLikeValue, RuntimeTypeMask legalTypesFilter, const String& errorMessage, AddFunction addFunction)
36 {
37     VM& vm = exec->vm();
38     auto scope = DECLARE_THROW_SCOPE(vm);
39     
40     Vector<JSValue> result;
41     JSValue lengthProperty = arrayLikeValue.get(exec, vm.propertyNames->length);
42     RETURN_IF_EXCEPTION(scope, void());
43     double lengthAsDouble = lengthProperty.toLength(exec);
44     RETURN_IF_EXCEPTION(scope, void());
45     RELEASE_ASSERT(lengthAsDouble >= 0.0 && lengthAsDouble == std::trunc(lengthAsDouble));
46     uint64_t length = static_cast<uint64_t>(lengthAsDouble);
47     for (uint64_t index = 0; index < length; index++) {
48         JSValue next = arrayLikeValue.get(exec, index);
49         RETURN_IF_EXCEPTION(scope, void());
50         
51         RuntimeType type = runtimeTypeForValue(vm, next);
52         if (!(type & legalTypesFilter)) {
53             throwTypeError(exec, scope, errorMessage);
54             return;
55         }
56         
57         bool exitEarly = addFunction(next, type);
58         if (exitEarly)
59             return;
60     }
61 }
62
63 ALWAYS_INLINE bool JSObject::canPerformFastPutInlineExcludingProto(VM& vm)
64 {
65     // Check if there are any setters or getters in the prototype chain
66     JSValue prototype;
67     JSObject* obj = this;
68     while (true) {
69         MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
70         if (obj->structure(vm)->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || obj->methodTable(vm)->getPrototype != defaultGetPrototype)
71             return false;
72
73         prototype = obj->getPrototypeDirect(vm);
74         if (prototype.isNull())
75             return true;
76
77         obj = asObject(prototype);
78     }
79
80     ASSERT_NOT_REACHED();
81 }
82
83 ALWAYS_INLINE bool JSObject::canPerformFastPutInline(VM& vm, PropertyName propertyName)
84 {
85     if (UNLIKELY(propertyName == vm.propertyNames->underscoreProto))
86         return false;
87     return canPerformFastPutInlineExcludingProto(vm);
88 }
89
90 template<typename CallbackWhenNoException>
91 ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSObject::getPropertySlot(ExecState* exec, PropertyName propertyName, CallbackWhenNoException callback) const
92 {
93     PropertySlot slot(this, PropertySlot::InternalMethodType::Get);
94     return getPropertySlot(exec, propertyName, slot, callback);
95 }
96
97 template<typename CallbackWhenNoException>
98 ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSObject::getPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot, CallbackWhenNoException callback) const
99 {
100     VM& vm = exec->vm();
101     auto scope = DECLARE_THROW_SCOPE(vm);
102     bool found = const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
103     RETURN_IF_EXCEPTION(scope, { });
104     scope.release();
105     return 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 void JSObject::putDirectWithoutTransition(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes)
177 {
178     ASSERT(!value.isGetterSetter() && !(attributes & PropertyAttribute::Accessor));
179     ASSERT(!value.isCustomGetterSetter());
180     StructureID structureID = this->structureID();
181     Structure* structure = vm.heap.structureIDTable().get(structureID);
182     PropertyOffset offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
183     bool shouldOptimize = false;
184     structure->willStoreValueForNewTransition(vm, propertyName, value, shouldOptimize);
185     putDirect(vm, offset, value);
186     if (attributes & PropertyAttribute::ReadOnly)
187         structure->setContainsReadOnlyProperties();
188 }
189
190 ALWAYS_INLINE PropertyOffset JSObject::prepareToPutDirectWithoutTransition(VM& vm, PropertyName propertyName, unsigned attributes, StructureID structureID, Structure* structure)
191 {
192     unsigned oldOutOfLineCapacity = structure->outOfLineCapacity();
193     PropertyOffset result;
194     structure->addPropertyWithoutTransition(
195         vm, propertyName, attributes,
196         [&] (const GCSafeConcurrentJSLocker&, PropertyOffset offset, PropertyOffset newLastOffset) {
197             unsigned newOutOfLineCapacity = Structure::outOfLineCapacity(newLastOffset);
198             if (newOutOfLineCapacity != oldOutOfLineCapacity) {
199                 Butterfly* butterfly = allocateMoreOutOfLineStorage(vm, oldOutOfLineCapacity, newOutOfLineCapacity);
200                 nukeStructureAndSetButterfly(vm, structureID, butterfly);
201                 structure->setLastOffset(newLastOffset);
202                 WTF::storeStoreFence();
203                 setStructureIDDirectly(structureID);
204             } else
205                 structure->setLastOffset(newLastOffset);
206             result = offset;
207         });
208     return result;
209 }
210
211 // ECMA 8.6.2.2
212 ALWAYS_INLINE bool JSObject::putInlineForJSObject(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
213 {
214     VM& vm = exec->vm();
215     auto scope = DECLARE_THROW_SCOPE(vm);
216
217     JSObject* thisObject = jsCast<JSObject*>(cell);
218     ASSERT(value);
219     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
220
221     if (UNLIKELY(isThisValueAltered(slot, thisObject))) {
222         scope.release();
223         return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
224     }
225
226     // Try indexed put first. This is required for correctness, since loads on property names that appear like
227     // valid indices will never look in the named property storage.
228     if (std::optional<uint32_t> index = parseIndex(propertyName)) {
229         scope.release();
230         return putByIndex(thisObject, exec, index.value(), value, slot.isStrictMode());
231     }
232
233     if (thisObject->canPerformFastPutInline(vm, propertyName)) {
234         ASSERT(!thisObject->prototypeChainMayInterceptStoreTo(vm, propertyName));
235         if (!thisObject->putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot))
236             return typeError(exec, scope, slot.isStrictMode(), ASCIILiteral(ReadonlyPropertyWriteError));
237         return true;
238     }
239
240     scope.release();
241     return thisObject->putInlineSlow(exec, propertyName, value, slot);
242 }
243
244 // HasOwnProperty(O, P) from section 7.3.11 in the spec.
245 // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-hasownproperty
246 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName, PropertySlot& slot) const
247 {
248     VM& vm = exec->vm();
249     ASSERT(slot.internalMethodType() == PropertySlot::InternalMethodType::GetOwnProperty);
250     if (LIKELY(const_cast<JSObject*>(this)->methodTable(vm)->getOwnPropertySlot == JSObject::getOwnPropertySlot))
251         return JSObject::getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
252     return const_cast<JSObject*>(this)->methodTable(vm)->getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
253 }
254
255 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName) const
256 {
257     PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
258     return hasOwnProperty(exec, propertyName, slot);
259 }
260
261 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, unsigned propertyName) const
262 {
263     PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
264     return const_cast<JSObject*>(this)->methodTable(exec->vm())->getOwnPropertySlotByIndex(const_cast<JSObject*>(this), exec, propertyName, slot);
265 }
266
267 template<JSObject::PutMode mode>
268 ALWAYS_INLINE bool JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes, PutPropertySlot& slot)
269 {
270     ASSERT(value);
271     ASSERT(value.isGetterSetter() == !!(attributes & PropertyAttribute::Accessor));
272     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(this));
273     ASSERT(!parseIndex(propertyName));
274
275     StructureID structureID = this->structureID();
276     Structure* structure = vm.heap.structureIDTable().get(structureID);
277     if (structure->isDictionary()) {
278         ASSERT(!isCopyOnWrite(indexingMode()));
279         ASSERT(!structure->hasInferredTypes());
280         
281         unsigned currentAttributes;
282         PropertyOffset offset = structure->get(vm, propertyName, currentAttributes);
283         if (offset != invalidOffset) {
284             if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
285                 return false;
286
287             putDirect(vm, offset, value);
288             structure->didReplaceProperty(offset);
289
290             if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessor) != (currentAttributes & PropertyAttribute::CustomAccessor)) {
291                 ASSERT(!(attributes & PropertyAttribute::ReadOnly));
292                 setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
293             } else
294                 slot.setExistingProperty(this, offset);
295
296             return true;
297         }
298
299         if ((mode == PutModePut) && !isStructureExtensible(vm))
300             return false;
301
302         offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
303         validateOffset(offset);
304         putDirect(vm, offset, value);
305         slot.setNewProperty(this, offset);
306         if (attributes & PropertyAttribute::ReadOnly)
307             this->structure(vm)->setContainsReadOnlyProperties();
308         return true;
309     }
310
311     PropertyOffset offset;
312     size_t currentCapacity = this->structure(vm)->outOfLineCapacity();
313     Structure* newStructure = Structure::addPropertyTransitionToExistingStructure(
314         structure, propertyName, attributes, offset);
315     if (newStructure) {
316         newStructure->willStoreValueForExistingTransition(
317             vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
318         
319         Butterfly* newButterfly = butterfly();
320         if (currentCapacity != newStructure->outOfLineCapacity()) {
321             ASSERT(newStructure != this->structure(vm));
322             newButterfly = allocateMoreOutOfLineStorage(vm, currentCapacity, newStructure->outOfLineCapacity());
323             nukeStructureAndSetButterfly(vm, structureID, newButterfly);
324         }
325
326         validateOffset(offset);
327         ASSERT(newStructure->isValidOffset(offset));
328         putDirect(vm, offset, value);
329         setStructure(vm, newStructure);
330         slot.setNewProperty(this, offset);
331         return true;
332     }
333
334     unsigned currentAttributes;
335     bool hasInferredType;
336     offset = structure->get(vm, propertyName, currentAttributes, hasInferredType);
337     if (offset != invalidOffset) {
338         if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
339             return false;
340
341         structure->didReplaceProperty(offset);
342         if (UNLIKELY(hasInferredType)) {
343             structure->willStoreValueForReplace(
344                 vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
345         }
346
347         putDirect(vm, offset, value);
348
349         if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessor) != (currentAttributes & PropertyAttribute::CustomAccessor)) {
350             ASSERT(!(attributes & PropertyAttribute::ReadOnly));
351             setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
352         } else
353             slot.setExistingProperty(this, offset);
354
355         return true;
356     }
357
358     if ((mode == PutModePut) && !isStructureExtensible(vm))
359         return false;
360
361     // We want the structure transition watchpoint to fire after this object has switched
362     // structure. This allows adaptive watchpoints to observe if the new structure is the one
363     // we want.
364     DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
365     
366     newStructure = Structure::addNewPropertyTransition(
367         vm, structure, propertyName, attributes, offset, slot.context(), &deferredWatchpointFire);
368     newStructure->willStoreValueForNewTransition(
369         vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
370     
371     validateOffset(offset);
372     ASSERT(newStructure->isValidOffset(offset));
373     size_t oldCapacity = structure->outOfLineCapacity();
374     size_t newCapacity = newStructure->outOfLineCapacity();
375     ASSERT(oldCapacity <= newCapacity);
376     if (oldCapacity != newCapacity) {
377         Butterfly* newButterfly = allocateMoreOutOfLineStorage(vm, oldCapacity, newCapacity);
378         nukeStructureAndSetButterfly(vm, structureID, newButterfly);
379     }
380     putDirect(vm, offset, value);
381     setStructure(vm, newStructure);
382     slot.setNewProperty(this, offset);
383     if (attributes & PropertyAttribute::ReadOnly)
384         newStructure->setContainsReadOnlyProperties();
385     return true;
386 }
387
388 } // namespace JSC