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