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)
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.
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.
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.
26 #include "AuxiliaryBarrierInlines.h"
30 #include "StructureInlines.h"
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)
39 auto scope = DECLARE_THROW_SCOPE(vm);
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());
52 RuntimeType type = runtimeTypeForValue(vm, next);
53 if (!(type & legalTypesFilter)) {
54 throwTypeError(exec, scope, errorMessage);
58 bool exitEarly = addFunction(next, type);
64 ALWAYS_INLINE bool JSObject::canPerformFastPutInlineExcludingProto(VM& vm)
66 // Check if there are any setters or getters in the prototype chain
70 MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
71 if (obj->structure(vm)->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || obj->methodTable(vm)->getPrototype != defaultGetPrototype)
74 prototype = obj->getPrototypeDirect(vm);
75 if (prototype.isNull())
78 obj = asObject(prototype);
84 ALWAYS_INLINE bool JSObject::canPerformFastPutInline(VM& vm, PropertyName propertyName)
86 if (UNLIKELY(propertyName == vm.propertyNames->underscoreProto))
88 return canPerformFastPutInlineExcludingProto(vm);
91 template<typename CallbackWhenNoException>
92 ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSObject::getPropertySlot(ExecState* exec, PropertyName propertyName, CallbackWhenNoException callback) const
94 PropertySlot slot(this, PropertySlot::InternalMethodType::Get);
95 return getPropertySlot(exec, propertyName, slot, callback);
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
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));
108 ALWAYS_INLINE bool JSObject::getPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot)
111 auto scope = DECLARE_THROW_SCOPE(vm);
112 auto& structureIDTable = vm.heap.structureIDTable();
113 JSObject* object = this;
114 MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
116 Structure* structure = structureIDTable.get(object->structureID());
117 bool hasSlot = structure->classInfo()->methodTable.getOwnPropertySlotByIndex(object, exec, propertyName, slot);
118 RETURN_IF_EXCEPTION(scope, false);
122 if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
123 prototype = object->getPrototypeDirect(vm);
125 prototype = object->getPrototype(vm, exec);
126 RETURN_IF_EXCEPTION(scope, false);
128 if (!prototype.isObject())
130 object = asObject(prototype);
134 ALWAYS_INLINE bool JSObject::getNonIndexPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
136 // This method only supports non-index PropertyNames.
137 ASSERT(!parseIndex(propertyName));
140 auto scope = DECLARE_THROW_SCOPE(vm);
141 auto& structureIDTable = vm.heap.structureIDTable();
142 JSObject* object = this;
143 MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype;
145 Structure* structure = structureIDTable.get(object->structureID());
146 if (LIKELY(!TypeInfo::overridesGetOwnPropertySlot(object->inlineTypeFlags()))) {
147 if (object->getOwnNonIndexPropertySlot(vm, structure, propertyName, slot))
150 bool hasSlot = structure->classInfo()->methodTable.getOwnPropertySlot(object, exec, propertyName, slot);
151 RETURN_IF_EXCEPTION(scope, false);
156 if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
157 prototype = object->getPrototypeDirect(vm);
159 prototype = object->getPrototype(vm, exec);
160 RETURN_IF_EXCEPTION(scope, false);
162 if (!prototype.isObject())
164 object = asObject(prototype);
168 inline bool JSObject::getOwnPropertySlotInline(ExecState* exec, PropertyName propertyName, PropertySlot& slot)
171 if (UNLIKELY(TypeInfo::overridesGetOwnPropertySlot(inlineTypeFlags())))
172 return methodTable(vm)->getOwnPropertySlot(this, exec, propertyName, slot);
173 return JSObject::getOwnPropertySlot(this, exec, propertyName, slot);
176 inline bool JSObject::mayInterceptIndexedAccesses(VM& vm)
178 return structure(vm)->mayInterceptIndexedAccesses();
181 inline void JSObject::putDirectWithoutTransition(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes)
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();
195 ALWAYS_INLINE PropertyOffset JSObject::prepareToPutDirectWithoutTransition(VM& vm, PropertyName propertyName, unsigned attributes, StructureID structureID, Structure* structure)
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);
210 structure->setLastOffset(newLastOffset);
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)));
221 ALWAYS_INLINE bool JSObject::putInlineForJSObject(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
224 auto scope = DECLARE_THROW_SCOPE(vm);
226 JSObject* thisObject = jsCast<JSObject*>(cell);
228 ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(thisObject));
230 if (UNLIKELY(isThisValueAltered(slot, thisObject)))
231 RELEASE_AND_RETURN(scope, ordinarySetSlow(exec, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode()));
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()));
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);
245 RELEASE_AND_RETURN(scope, thisObject->putInlineSlow(exec, propertyName, value, slot));
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
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);
259 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, PropertyName propertyName) const
261 PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
262 return hasOwnProperty(exec, propertyName, slot);
265 ALWAYS_INLINE bool JSObject::hasOwnProperty(ExecState* exec, unsigned propertyName) const
267 PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
268 return const_cast<JSObject*>(this)->methodTable(exec->vm())->getOwnPropertySlotByIndex(const_cast<JSObject*>(this), exec, propertyName, slot);
271 template<JSObject::PutMode mode>
272 ALWAYS_INLINE bool JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSValue value, unsigned attributes, PutPropertySlot& slot)
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));
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());
286 unsigned currentAttributes;
287 PropertyOffset offset = structure->get(vm, propertyName, currentAttributes);
288 if (offset != invalidOffset) {
289 if ((mode == PutModePut) && currentAttributes & PropertyAttribute::ReadOnly)
292 putDirect(vm, offset, value);
293 structure->didReplaceProperty(offset);
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));
299 slot.setExistingProperty(this, offset);
304 if ((mode == PutModePut) && !isStructureExtensible(vm))
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();
316 PropertyOffset offset;
317 size_t currentCapacity = this->structure(vm)->outOfLineCapacity();
318 Structure* newStructure = Structure::addPropertyTransitionToExistingStructure(
319 structure, propertyName, attributes, offset);
321 newStructure->willStoreValueForExistingTransition(
322 vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
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);
331 validateOffset(offset);
332 ASSERT(newStructure->isValidOffset(offset));
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);
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)
350 structure->didReplaceProperty(offset);
351 if (UNLIKELY(hasInferredType)) {
352 structure->willStoreValueForReplace(
353 vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
356 putDirect(vm, offset, value);
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));
362 slot.setExistingProperty(this, offset);
367 if ((mode == PutModePut) && !isStructureExtensible(vm))
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
373 DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
375 newStructure = Structure::addNewPropertyTransition(
376 vm, structure, propertyName, attributes, offset, slot.context(), &deferredWatchpointFire);
377 newStructure->willStoreValueForNewTransition(
378 vm, propertyName, value, slot.context() == PutPropertySlot::PutById);
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);
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();