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