Indexing should only be computed when the new structure has an indexing header.
[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
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(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::canPerformFastPutInline(VM& vm, PropertyName propertyName)
64 {
65     if (UNLIKELY(propertyName == vm.propertyNames->underscoreProto))
66         return false;
67
68     // Check if there are any setters or getters in the prototype chain
69     JSValue prototype;
70     JSObject* obj = this;
71     while (true) {
72         MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
73         if (obj->structure(vm)->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || obj->methodTable(vm)->getPrototype != defaultGetPrototype)
74             return false;
75
76         prototype = obj->getPrototypeDirect(vm);
77         if (prototype.isNull())
78             return true;
79
80         obj = asObject(prototype);
81     }
82
83     ASSERT_NOT_REACHED();
84 }
85
86 template<typename CallbackWhenNoException>
87 ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSObject::getPropertySlot(ExecState* exec, PropertyName propertyName, CallbackWhenNoException callback) const
88 {
89     PropertySlot slot(this, PropertySlot::InternalMethodType::Get);
90     return getPropertySlot(exec, propertyName, slot, callback);
91 }
92
93 template<typename CallbackWhenNoException>
94 ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSObject::getPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot, CallbackWhenNoException callback) const
95 {
96     VM& vm = exec->vm();
97     auto scope = DECLARE_THROW_SCOPE(vm);
98     bool found = const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot);
99     RETURN_IF_EXCEPTION(scope, { });
100     scope.release();
101     return callback(found, slot);
102 }
103
104 ALWAYS_INLINE bool JSObject::getPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot)
105 {
106     VM& vm = exec->vm();
107     auto scope = DECLARE_THROW_SCOPE(vm);
108     auto& structureIDTable = vm.heap.structureIDTable();
109     JSObject* object = this;
110     MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
111     while (true) {
112         Structure* structure = structureIDTable.get(object->structureID());
113         bool hasSlot = structure->classInfo()->methodTable.getOwnPropertySlotByIndex(object, exec, propertyName, slot);
114         RETURN_IF_EXCEPTION(scope, false);
115         if (hasSlot)
116             return true;
117         JSValue prototype;
118         if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
119             prototype = object->getPrototypeDirect(vm);
120         else {
121             prototype = object->getPrototype(vm, exec);
122             RETURN_IF_EXCEPTION(scope, false);
123         }
124         if (!prototype.isObject())
125             return false;
126         object = asObject(prototype);
127     }
128 }
129
130 ALWAYS_INLINE bool JSObject::getNonIndexPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
131 {
132     // This method only supports non-index PropertyNames.
133     ASSERT(!parseIndex(propertyName));
134
135     VM& vm = exec->vm();
136     auto scope = DECLARE_THROW_SCOPE(vm);
137     auto& structureIDTable = vm.heap.structureIDTable();
138     JSObject* object = this;
139     MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
140     while (true) {
141         Structure* structure = structureIDTable.get(object->structureID());
142         if (LIKELY(!TypeInfo::overridesGetOwnPropertySlot(object->inlineTypeFlags()))) {
143             if (object->getOwnNonIndexPropertySlot(vm, structure, propertyName, slot))
144                 return true;
145         } else {
146             bool hasSlot = structure->classInfo()->methodTable.getOwnPropertySlot(object, exec, propertyName, slot);
147             RETURN_IF_EXCEPTION(scope, false);
148             if (hasSlot)
149                 return true;
150         }
151         JSValue prototype;
152         if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
153             prototype = object->getPrototypeDirect(vm);
154         else {
155             prototype = object->getPrototype(vm, exec);
156             RETURN_IF_EXCEPTION(scope, false);
157         }
158         if (!prototype.isObject())
159             return false;
160         object = asObject(prototype);
161     }
162 }
163
164 inline void JSObject::putDirectWithoutTransition(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes)
165 {
166     ASSERT(!value.isGetterSetter() && !(attributes & PropertyAttribute::Accessor));
167     ASSERT(!value.isCustomGetterSetter());
168     StructureID structureID = this->structureID();
169     Structure* structure = vm.heap.structureIDTable().get(structureID);
170     PropertyOffset offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
171     bool shouldOptimize = false;
172     structure->willStoreValueForNewTransition(vm, propertyName, value, shouldOptimize);
173     putDirect(vm, offset, value);
174     if (attributes & PropertyAttribute::ReadOnly)
175         structure->setContainsReadOnlyProperties();
176 }
177
178 ALWAYS_INLINE PropertyOffset JSObject::prepareToPutDirectWithoutTransition(VM& vm, PropertyName propertyName, unsigned attributes, StructureID structureID, Structure* structure)
179 {
180     unsigned oldOutOfLineCapacity = structure->outOfLineCapacity();
181     PropertyOffset result;
182     structure->addPropertyWithoutTransition(
183         vm, propertyName, attributes,
184         [&] (const GCSafeConcurrentJSLocker&, PropertyOffset offset, PropertyOffset newLastOffset) {
185             unsigned newOutOfLineCapacity = Structure::outOfLineCapacity(newLastOffset);
186             if (newOutOfLineCapacity != oldOutOfLineCapacity) {
187                 Butterfly* butterfly = allocateMoreOutOfLineStorage(vm, oldOutOfLineCapacity, newOutOfLineCapacity);
188                 nukeStructureAndSetButterfly(vm, structureID, butterfly, structure->indexingType());
189                 structure->setLastOffset(newLastOffset);
190                 WTF::storeStoreFence();
191                 setStructureIDDirectly(structureID);
192             } else
193                 structure->setLastOffset(newLastOffset);
194             result = offset;
195         });
196     return result;
197 }
198
199 // ECMA 8.6.2.2
200 ALWAYS_INLINE bool JSObject::putInlineForJSObject(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
201 {
202     VM& vm = exec->vm();
203     auto scope = DECLARE_THROW_SCOPE(vm);
204
205     JSObject* thisObject = jsCast<JSObject*>(cell);
206     ASSERT(value);
207     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
208
209     if (UNLIKELY(isThisValueAltered(slot, thisObject))) {
210         scope.release();
211         return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
212     }
213
214     // Try indexed put first. This is required for correctness, since loads on property names that appear like
215     // valid indices will never look in the named property storage.
216     if (std::optional<uint32_t> index = parseIndex(propertyName)) {
217         scope.release();
218         return putByIndex(thisObject, exec, index.value(), value, slot.isStrictMode());
219     }
220
221     if (thisObject->canPerformFastPutInline(vm, propertyName)) {
222         ASSERT(!thisObject->prototypeChainMayInterceptStoreTo(vm, propertyName));
223         if (!thisObject->putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot))
224             return typeError(exec, scope, slot.isStrictMode(), ASCIILiteral(ReadonlyPropertyWriteError));
225         return true;
226     }
227
228     scope.release();
229     return thisObject->putInlineSlow(exec, propertyName, value, slot);
230 }
231
232 // HasOwnProperty(O, P) from section 7.3.11 in the spec.
233 // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-hasownproperty
234 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName, PropertySlot& slot) const
235 {
236     VM& vm = exec->vm();
237     ASSERT(slot.internalMethodType() == PropertySlot::InternalMethodType::GetOwnProperty);
238     if (LIKELY(const_cast<JSObject*>(this)->methodTable(vm)->getOwnPropertySlot == JSObject::getOwnPropertySlot))
239         return JSObject::getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
240     return const_cast<JSObject*>(this)->methodTable(vm)->getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
241 }
242
243 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName) const
244 {
245     PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
246     return hasOwnProperty(exec, propertyName, slot);
247 }
248
249 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, unsigned propertyName) const
250 {
251     PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
252     return const_cast<JSObject*>(this)->methodTable(exec->vm())->getOwnPropertySlotByIndex(const_cast<JSObject*>(this), exec, propertyName, slot);
253 }
254
255 template<JSObject::PutMode mode>
256 ALWAYS_INLINE bool JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes, PutPropertySlot& slot)
257 {
258     ASSERT(value);
259     ASSERT(value.isGetterSetter() == !!(attributes & PropertyAttribute::Accessor));
260     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(this));
261     ASSERT(!parseIndex(propertyName));
262
263     switch (methodTable(vm)->reifyPropertyNameIfNeeded(this, globalObject(vm)->globalExec(), propertyName)) {
264     case PropertyReificationResult::Nothing: break;
265     case PropertyReificationResult::Something: break;
266     case PropertyReificationResult::TriedButFailed: RELEASE_ASSERT_NOT_REACHED();
267     }
268
269     StructureID structureID = this->structureID();
270     Structure* structure = vm.heap.structureIDTable().get(structureID);
271     if (structure->isDictionary()) {
272         ASSERT(!structure->hasInferredTypes());
273         
274         unsigned currentAttributes;
275         PropertyOffset offset = structure->get(vm, propertyName, currentAttributes);
276         if (offset != invalidOffset) {
277             if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
278                 return false;
279
280             putDirect(vm, offset, value);
281             structure->didReplaceProperty(offset);
282             slot.setExistingProperty(this, offset);
283
284             if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessor) != (currentAttributes & PropertyAttribute::CustomAccessor)) {
285                 ASSERT(!(attributes & PropertyAttribute::ReadOnly));
286                 setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
287             }
288             return true;
289         }
290
291         if ((mode == PutModePut) && !isStructureExtensible())
292             return false;
293
294         offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
295         validateOffset(offset);
296         putDirect(vm, offset, value);
297         slot.setNewProperty(this, offset);
298         if (attributes & PropertyAttribute::ReadOnly)
299             this->structure()->setContainsReadOnlyProperties();
300         return true;
301     }
302
303     PropertyOffset offset;
304     size_t currentCapacity = this->structure()->outOfLineCapacity();
305     Structure* newStructure = Structure::addPropertyTransitionToExistingStructure(
306         structure, propertyName, attributes, offset);
307     if (newStructure) {
308         newStructure->willStoreValueForExistingTransition(
309             vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
310         
311         Butterfly* newButterfly = butterfly();
312         if (currentCapacity != newStructure->outOfLineCapacity()) {
313             ASSERT(newStructure != this->structure());
314             newButterfly = allocateMoreOutOfLineStorage(vm, currentCapacity, newStructure->outOfLineCapacity());
315             nukeStructureAndSetButterfly(vm, structureID, newButterfly, newStructure->indexingType());
316         }
317
318         validateOffset(offset);
319         ASSERT(newStructure->isValidOffset(offset));
320         putDirect(vm, offset, value);
321         setStructure(vm, newStructure);
322         slot.setNewProperty(this, offset);
323         return true;
324     }
325
326     unsigned currentAttributes;
327     bool hasInferredType;
328     offset = structure->get(vm, propertyName, currentAttributes, hasInferredType);
329     if (offset != invalidOffset) {
330         if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
331             return false;
332
333         structure->didReplaceProperty(offset);
334         if (UNLIKELY(hasInferredType)) {
335             structure->willStoreValueForReplace(
336                 vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
337         }
338
339         slot.setExistingProperty(this, offset);
340         putDirect(vm, offset, value);
341
342         if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessor) != (currentAttributes & PropertyAttribute::CustomAccessor)) {
343             ASSERT(!(attributes & PropertyAttribute::ReadOnly));
344             setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
345         }
346         return true;
347     }
348
349     if ((mode == PutModePut) && !isStructureExtensible())
350         return false;
351
352     // We want the structure transition watchpoint to fire after this object has switched
353     // structure. This allows adaptive watchpoints to observe if the new structure is the one
354     // we want.
355     DeferredStructureTransitionWatchpointFire deferredWatchpointFire;
356     
357     newStructure = Structure::addNewPropertyTransition(
358         vm, structure, propertyName, attributes, offset, slot.context(), &deferredWatchpointFire);
359     newStructure->willStoreValueForNewTransition(
360         vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
361     
362     validateOffset(offset);
363     ASSERT(newStructure->isValidOffset(offset));
364     size_t oldCapacity = structure->outOfLineCapacity();
365     size_t newCapacity = newStructure->outOfLineCapacity();
366     ASSERT(oldCapacity <= newCapacity);
367     if (oldCapacity != newCapacity) {
368         Butterfly* newButterfly = allocateMoreOutOfLineStorage(vm, oldCapacity, newCapacity);
369         nukeStructureAndSetButterfly(vm, structureID, newButterfly, newStructure->indexingType());
370     }
371     putDirect(vm, offset, value);
372     setStructure(vm, newStructure);
373     slot.setNewProperty(this, offset);
374     if (attributes & PropertyAttribute::ReadOnly)
375         newStructure->setContainsReadOnlyProperties();
376     return true;
377 }
378
379 } // namespace JSC