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