1/*
2 * Copyright (C) 2016-2019 Apple Inc. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 */
19
20#pragma once
21
22#include "ArrayPrototype.h"
23#include "ButterflyInlines.h"
24#include "Error.h"
25#include "JSArray.h"
26#include "JSCellInlines.h"
27#include "Structure.h"
28
29namespace JSC {
30
31inline IndexingType JSArray::mergeIndexingTypeForCopying(IndexingType other)
32{
33 IndexingType type = indexingType();
34 if (!(type & IsArray && other & IsArray))
35 return NonArray;
36
37 if (hasAnyArrayStorage(type) || hasAnyArrayStorage(other))
38 return NonArray;
39
40 if (type == ArrayWithUndecided)
41 return other;
42
43 if (other == ArrayWithUndecided)
44 return type;
45
46 // We can memcpy an Int32 and a Contiguous into a Contiguous array since
47 // both share the same memory layout for Int32 numbers.
48 if ((type == ArrayWithInt32 || type == ArrayWithContiguous)
49 && (other == ArrayWithInt32 || other == ArrayWithContiguous)) {
50 if (other == ArrayWithContiguous)
51 return other;
52 return type;
53 }
54
55 if (type != other)
56 return NonArray;
57
58 return type;
59}
60
61inline bool JSArray::canFastCopy(VM& vm, JSArray* otherArray)
62{
63 if (otherArray == this)
64 return false;
65 if (hasAnyArrayStorage(indexingType()) || hasAnyArrayStorage(otherArray->indexingType()))
66 return false;
67 // FIXME: We should have a watchpoint for indexed properties on Array.prototype and Object.prototype
68 // instead of walking the prototype chain. https://bugs.webkit.org/show_bug.cgi?id=155592
69 if (structure(vm)->holesMustForwardToPrototype(vm, this)
70 || otherArray->structure(vm)->holesMustForwardToPrototype(vm, otherArray))
71 return false;
72 return true;
73}
74
75inline bool JSArray::canDoFastIndexedAccess(VM& vm)
76{
77 JSGlobalObject* globalObject = this->globalObject();
78 if (!globalObject->arrayPrototypeChainIsSane())
79 return false;
80
81 Structure* structure = this->structure(vm);
82 // This is the fast case. Many arrays will be an original array.
83 if (globalObject->isOriginalArrayStructure(structure))
84 return true;
85
86 if (structure->mayInterceptIndexedAccesses())
87 return false;
88
89 if (getPrototypeDirect(vm) != globalObject->arrayPrototype())
90 return false;
91
92 return true;
93}
94
95ALWAYS_INLINE double toLength(JSGlobalObject* globalObject, JSObject* obj)
96{
97 VM& vm = globalObject->vm();
98 auto scope = DECLARE_THROW_SCOPE(vm);
99 if (LIKELY(isJSArray(obj)))
100 return jsCast<JSArray*>(obj)->length();
101
102 JSValue lengthValue = obj->get(globalObject, vm.propertyNames->length);
103 RETURN_IF_EXCEPTION(scope, PNaN);
104 RELEASE_AND_RETURN(scope, lengthValue.toLength(globalObject));
105}
106
107ALWAYS_INLINE void JSArray::pushInline(JSGlobalObject* globalObject, JSValue value)
108{
109 VM& vm = globalObject->vm();
110 auto scope = DECLARE_THROW_SCOPE(vm);
111
112 ensureWritable(vm);
113
114 Butterfly* butterfly = this->butterfly();
115
116 switch (indexingMode()) {
117 case ArrayClass: {
118 createInitialUndecided(vm, 0);
119 FALLTHROUGH;
120 }
121
122 case ArrayWithUndecided: {
123 convertUndecidedForValue(vm, value);
124 scope.release();
125 push(globalObject, value);
126 return;
127 }
128
129 case ArrayWithInt32: {
130 if (!value.isInt32()) {
131 convertInt32ForValue(vm, value);
132 scope.release();
133 push(globalObject, value);
134 return;
135 }
136
137 unsigned length = butterfly->publicLength();
138 ASSERT(length <= butterfly->vectorLength());
139 if (length < butterfly->vectorLength()) {
140 butterfly->contiguousInt32().at(this, length).setWithoutWriteBarrier(value);
141 butterfly->setPublicLength(length + 1);
142 return;
143 }
144
145 if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
146 methodTable(vm)->putByIndex(this, globalObject, length, value, true);
147 if (!scope.exception())
148 throwException(globalObject, scope, createRangeError(globalObject, LengthExceededTheMaximumArrayLengthError));
149 return;
150 }
151
152 scope.release();
153 putByIndexBeyondVectorLengthWithoutAttributes<Int32Shape>(globalObject, length, value);
154 return;
155 }
156
157 case ArrayWithContiguous: {
158 unsigned length = butterfly->publicLength();
159 ASSERT(length <= butterfly->vectorLength());
160 if (length < butterfly->vectorLength()) {
161 butterfly->contiguous().at(this, length).setWithoutWriteBarrier(value);
162 butterfly->setPublicLength(length + 1);
163 vm.heap.writeBarrier(this, value);
164 return;
165 }
166
167 if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
168 methodTable(vm)->putByIndex(this, globalObject, length, value, true);
169 if (!scope.exception())
170 throwException(globalObject, scope, createRangeError(globalObject, LengthExceededTheMaximumArrayLengthError));
171 return;
172 }
173
174 scope.release();
175 putByIndexBeyondVectorLengthWithoutAttributes<ContiguousShape>(globalObject, length, value);
176 return;
177 }
178
179 case ArrayWithDouble: {
180 if (!value.isNumber()) {
181 convertDoubleToContiguous(vm);
182 scope.release();
183 push(globalObject, value);
184 return;
185 }
186 double valueAsDouble = value.asNumber();
187 if (valueAsDouble != valueAsDouble) {
188 convertDoubleToContiguous(vm);
189 scope.release();
190 push(globalObject, value);
191 return;
192 }
193
194 unsigned length = butterfly->publicLength();
195 ASSERT(length <= butterfly->vectorLength());
196 if (length < butterfly->vectorLength()) {
197 butterfly->contiguousDouble().at(this, length) = valueAsDouble;
198 butterfly->setPublicLength(length + 1);
199 return;
200 }
201
202 if (UNLIKELY(length > MAX_ARRAY_INDEX)) {
203 methodTable(vm)->putByIndex(this, globalObject, length, value, true);
204 if (!scope.exception())
205 throwException(globalObject, scope, createRangeError(globalObject, LengthExceededTheMaximumArrayLengthError));
206 return;
207 }
208
209 scope.release();
210 putByIndexBeyondVectorLengthWithoutAttributes<DoubleShape>(globalObject, length, value);
211 return;
212 }
213
214 case ArrayWithSlowPutArrayStorage: {
215 unsigned oldLength = length();
216 bool putResult = false;
217 bool result = attemptToInterceptPutByIndexOnHole(globalObject, oldLength, value, true, putResult);
218 RETURN_IF_EXCEPTION(scope, void());
219 if (result) {
220 if (oldLength < 0xFFFFFFFFu) {
221 scope.release();
222 setLength(globalObject, oldLength + 1, true);
223 }
224 return;
225 }
226 FALLTHROUGH;
227 }
228
229 case ArrayWithArrayStorage: {
230 ArrayStorage* storage = butterfly->arrayStorage();
231
232 // Fast case - push within vector, always update m_length & m_numValuesInVector.
233 unsigned length = storage->length();
234 if (length < storage->vectorLength()) {
235 storage->m_vector[length].set(vm, this, value);
236 storage->setLength(length + 1);
237 ++storage->m_numValuesInVector;
238 return;
239 }
240
241 // Pushing to an array of invalid length (2^31-1) stores the property, but throws a range error.
242 if (UNLIKELY(storage->length() > MAX_ARRAY_INDEX)) {
243 methodTable(vm)->putByIndex(this, globalObject, storage->length(), value, true);
244 // Per ES5.1 15.4.4.7 step 6 & 15.4.5.1 step 3.d.
245 if (!scope.exception())
246 throwException(globalObject, scope, createRangeError(globalObject, LengthExceededTheMaximumArrayLengthError));
247 return;
248 }
249
250 // Handled the same as putIndex.
251 scope.release();
252 putByIndexBeyondVectorLengthWithArrayStorage(globalObject, storage->length(), value, true, storage);
253 return;
254 }
255
256 default:
257 RELEASE_ASSERT_NOT_REACHED();
258 }
259}
260
261} // namespace JSC
262