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