A stack overflow in the parsing of a builtin (called by createExecutable) cause a...
[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(exec, structure, propertyName, slot))
144                 return true;
145             RETURN_IF_EXCEPTION(scope, false);
146         } else {
147             bool hasSlot = structure->classInfo()->methodTable.getOwnPropertySlot(object, exec, propertyName, slot);
148             RETURN_IF_EXCEPTION(scope, false);
149             if (hasSlot)
150                 return true;
151         }
152         JSValue prototype;
153         if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
154             prototype = object->getPrototypeDirect(vm);
155         else {
156             prototype = object->getPrototype(vm, exec);
157             RETURN_IF_EXCEPTION(scope, false);
158         }
159         if (!prototype.isObject())
160             return false;
161         object = asObject(prototype);
162     }
163 }
164
165 inline void JSObject::putDirectWithoutTransition(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes)
166 {
167     ASSERT(!value.isGetterSetter() && !(attributes & PropertyAttribute::Accessor));
168     ASSERT(!value.isCustomGetterSetter());
169     StructureID structureID = this->structureID();
170     Structure* structure = vm.heap.structureIDTable().get(structureID);
171     PropertyOffset offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
172     bool shouldOptimize = false;
173     structure->willStoreValueForNewTransition(vm, propertyName, value, shouldOptimize);
174     putDirect(vm, offset, value);
175     if (attributes & PropertyAttribute::ReadOnly)
176         structure->setContainsReadOnlyProperties();
177 }
178
179 ALWAYS_INLINE PropertyOffset JSObject::prepareToPutDirectWithoutTransition(VM& vm, PropertyName propertyName, unsigned attributes, StructureID structureID, Structure* structure)
180 {
181     unsigned oldOutOfLineCapacity = structure->outOfLineCapacity();
182     PropertyOffset result;
183     structure->addPropertyWithoutTransition(
184         vm, propertyName, attributes,
185         [&] (const GCSafeConcurrentJSLocker&, PropertyOffset offset, PropertyOffset newLastOffset) {
186             unsigned newOutOfLineCapacity = Structure::outOfLineCapacity(newLastOffset);
187             if (newOutOfLineCapacity != oldOutOfLineCapacity) {
188                 Butterfly* butterfly = allocateMoreOutOfLineStorage(vm, oldOutOfLineCapacity, newOutOfLineCapacity);
189                 nukeStructureAndSetButterfly(vm, structureID, butterfly, structure->indexingType());
190                 structure->setLastOffset(newLastOffset);
191                 WTF::storeStoreFence();
192                 setStructureIDDirectly(structureID);
193             } else
194                 structure->setLastOffset(newLastOffset);
195             result = offset;
196         });
197     return result;
198 }
199
200 // ECMA 8.6.2.2
201 ALWAYS_INLINE bool JSObject::putInlineForJSObject(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
202 {
203     VM& vm = exec->vm();
204     auto scope = DECLARE_THROW_SCOPE(vm);
205
206     JSObject* thisObject = jsCast<JSObject*>(cell);
207     ASSERT(value);
208     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
209
210     if (UNLIKELY(isThisValueAltered(slot, thisObject))) {
211         scope.release();
212         return ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode());
213     }
214
215     // Try indexed put first. This is required for correctness, since loads on property names that appear like
216     // valid indices will never look in the named property storage.
217     if (std::optional<uint32_t> index = parseIndex(propertyName)) {
218         scope.release();
219         return putByIndex(thisObject, exec, index.value(), value, slot.isStrictMode());
220     }
221
222     if (thisObject->canPerformFastPutInline(vm, propertyName)) {
223         ASSERT(!thisObject->prototypeChainMayInterceptStoreTo(vm, propertyName));
224         if (!thisObject->putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot))
225             return typeError(exec, scope, slot.isStrictMode(), ASCIILiteral(ReadonlyPropertyWriteError));
226         return true;
227     }
228
229     scope.release();
230     return thisObject->putInlineSlow(exec, propertyName, value, slot);
231 }
232
233 // HasOwnProperty(O, P) from section 7.3.11 in the spec.
234 // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-hasownproperty
235 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName, PropertySlot& slot) const
236 {
237     VM& vm = exec->vm();
238     ASSERT(slot.internalMethodType() == PropertySlot::InternalMethodType::GetOwnProperty);
239     if (LIKELY(const_cast<JSObject*>(this)->methodTable(vm)->getOwnPropertySlot == JSObject::getOwnPropertySlot))
240         return JSObject::getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
241     return const_cast<JSObject*>(this)->methodTable(vm)->getOwnPropertySlot(const_cast<JSObject*>(this), exec, propertyName, slot);
242 }
243
244 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName) const
245 {
246     PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
247     return hasOwnProperty(exec, propertyName, slot);
248 }
249
250 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, unsigned propertyName) const
251 {
252     PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
253     return const_cast<JSObject*>(this)->methodTable(exec->vm())->getOwnPropertySlotByIndex(const_cast<JSObject*>(this), exec, propertyName, slot);
254 }
255
256 template<JSObject::PutMode mode>
257 ALWAYS_INLINE bool JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes, PutPropertySlot& slot)
258 {
259     ASSERT(value);
260     ASSERT(value.isGetterSetter() == !!(attributes & PropertyAttribute::Accessor));
261     ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(this));
262     ASSERT(!parseIndex(propertyName));
263
264     switch (methodTable(vm)->reifyPropertyNameIfNeeded(this, globalObject(vm)->globalExec(), propertyName)) {
265     case PropertyReificationResult::Nothing: break;
266     case PropertyReificationResult::Something: break;
267     case PropertyReificationResult::TriedButFailed: RELEASE_ASSERT_NOT_REACHED();
268     }
269
270     StructureID structureID = this->structureID();
271     Structure* structure = vm.heap.structureIDTable().get(structureID);
272     if (structure->isDictionary()) {
273         ASSERT(!structure->hasInferredTypes());
274         
275         unsigned currentAttributes;
276         PropertyOffset offset = structure->get(vm, propertyName, currentAttributes);
277         if (offset != invalidOffset) {
278             if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
279                 return false;
280
281             putDirect(vm, offset, value);
282             structure->didReplaceProperty(offset);
283             slot.setExistingProperty(this, offset);
284
285             if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessor) != (currentAttributes & PropertyAttribute::CustomAccessor)) {
286                 ASSERT(!(attributes & PropertyAttribute::ReadOnly));
287                 setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
288             }
289             return true;
290         }
291
292         if ((mode == PutModePut) && !isStructureExtensible())
293             return false;
294
295         offset = prepareToPutDirectWithoutTransition(vm, propertyName, attributes, structureID, structure);
296         validateOffset(offset);
297         putDirect(vm, offset, value);
298         slot.setNewProperty(this, offset);
299         if (attributes & PropertyAttribute::ReadOnly)
300             this->structure()->setContainsReadOnlyProperties();
301         return true;
302     }
303
304     PropertyOffset offset;
305     size_t currentCapacity = this->structure()->outOfLineCapacity();
306     Structure* newStructure = Structure::addPropertyTransitionToExistingStructure(
307         structure, propertyName, attributes, offset);
308     if (newStructure) {
309         newStructure->willStoreValueForExistingTransition(
310             vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
311         
312         Butterfly* newButterfly = butterfly();
313         if (currentCapacity != newStructure->outOfLineCapacity()) {
314             ASSERT(newStructure != this->structure());
315             newButterfly = allocateMoreOutOfLineStorage(vm, currentCapacity, newStructure->outOfLineCapacity());
316             nukeStructureAndSetButterfly(vm, structureID, newButterfly, newStructure->indexingType());
317         }
318
319         validateOffset(offset);
320         ASSERT(newStructure->isValidOffset(offset));
321         putDirect(vm, offset, value);
322         setStructure(vm, newStructure);
323         slot.setNewProperty(this, offset);
324         return true;
325     }
326
327     unsigned currentAttributes;
328     bool hasInferredType;
329     offset = structure->get(vm, propertyName, currentAttributes, hasInferredType);
330     if (offset != invalidOffset) {
331         if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
332             return false;
333
334         structure->didReplaceProperty(offset);
335         if (UNLIKELY(hasInferredType)) {
336             structure->willStoreValueForReplace(
337                 vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
338         }
339
340         slot.setExistingProperty(this, offset);
341         putDirect(vm, offset, value);
342
343         if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessor) != (currentAttributes & PropertyAttribute::CustomAccessor)) {
344             ASSERT(!(attributes & PropertyAttribute::ReadOnly));
345             setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
346         }
347         return true;
348     }
349
350     if ((mode == PutModePut) && !isStructureExtensible())
351         return false;
352
353     // We want the structure transition watchpoint to fire after this object has switched
354     // structure. This allows adaptive watchpoints to observe if the new structure is the one
355     // we want.
356     DeferredStructureTransitionWatchpointFire deferredWatchpointFire;
357     
358     newStructure = Structure::addNewPropertyTransition(
359         vm, structure, propertyName, attributes, offset, slot.context(), &deferredWatchpointFire);
360     newStructure->willStoreValueForNewTransition(
361         vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
362     
363     validateOffset(offset);
364     ASSERT(newStructure->isValidOffset(offset));
365     size_t oldCapacity = structure->outOfLineCapacity();
366     size_t newCapacity = newStructure->outOfLineCapacity();
367     ASSERT(oldCapacity <= newCapacity);
368     if (oldCapacity != newCapacity) {
369         Butterfly* newButterfly = allocateMoreOutOfLineStorage(vm, oldCapacity, newCapacity);
370         nukeStructureAndSetButterfly(vm, structureID, newButterfly, newStructure->indexingType());
371     }
372     putDirect(vm, offset, value);
373     setStructure(vm, newStructure);
374     slot.setNewProperty(this, offset);
375     if (attributes & PropertyAttribute::ReadOnly)
376         newStructure->setContainsReadOnlyProperties();
377     return true;
378 }
379
380 } // namespace JSC