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 if (attemptToInterceptPutByIndexOnHole(exec, oldLength, value, true, putResult)) {
216 if (!scope.exception() && oldLength < 0xFFFFFFFFu) {
217 scope.release();
218 setLength(exec, oldLength + 1, true);
219 }
220 return;
221 }
222 FALLTHROUGH;
223 }
224
225 case ArrayWithArrayStorage: {
226 ArrayStorage* storage = butterfly->arrayStorage();
227
228 // Fast case - push within vector, always update m_length & m_numValuesInVector.
229 unsigned length = storage->length();
230 if (length < storage->vectorLength()) {
231 storage->m_vector[length].set(vm, this, value);
232 storage->setLength(length + 1);
233 ++storage->m_numValuesInVector;
234 return;
235 }
236
237 // Pushing to an array of invalid length (2^31-1) stores the property, but throws a range error.
238 if (UNLIKELY(storage->length() > MAX_ARRAY_INDEX)) {
239 methodTable(vm)->putByIndex(this, exec, storage->length(), value, true);
240 // Per ES5.1 15.4.4.7 step 6 & 15.4.5.1 step 3.d.
241 if (!scope.exception())
242 throwException(exec, scope, createRangeError(exec, LengthExceededTheMaximumArrayLengthError));
243 return;
244 }
245
246 // Handled the same as putIndex.
247 scope.release();
248 putByIndexBeyondVectorLengthWithArrayStorage(exec, storage->length(), value, true, storage);
249 return;
250 }
251
252 default:
253 RELEASE_ASSERT_NOT_REACHED();
254 }
255}
256
257} // namespace JSC
258