Coverage Report

Created: 2026-09-26 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibJS/Runtime/Array.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <AK/Function.h>
9
#include <LibJS/Runtime/AbstractOperations.h>
10
#include <LibJS/Runtime/Array.h>
11
#include <LibJS/Runtime/ArrayPrototype.h>
12
#include <LibJS/Runtime/Completion.h>
13
#include <LibJS/Runtime/Error.h>
14
#include <LibJS/Runtime/GlobalObject.h>
15
#include <LibJS/Runtime/NativeFunction.h>
16
#include <LibJS/Runtime/ValueInlines.h>
17
18
namespace JS {
19
20
JS_DEFINE_ALLOCATOR(Array);
21
22
// 10.4.2.2 ArrayCreate ( length [ , proto ] ), https://tc39.es/ecma262/#sec-arraycreate
23
ThrowCompletionOr<NonnullGCPtr<Array>> Array::create(Realm& realm, u64 length, Object* prototype)
24
5
{
25
5
    auto& vm = realm.vm();
26
27
    // 1. If length > 2^32 - 1, throw a RangeError exception.
28
5
    if (length > NumericLimits<u32>::max())
29
0
        return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "array");
30
31
    // 2. If proto is not present, set proto to %Array.prototype%.
32
5
    if (!prototype)
33
5
        prototype = realm.intrinsics().array_prototype();
34
35
    // 3. Let A be MakeBasicObject(ยซ [[Prototype]], [[Extensible]] ยป).
36
    // 4. Set A.[[Prototype]] to proto.
37
    // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1.
38
5
    auto array = realm.heap().allocate<Array>(realm, *prototype);
39
40
    // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: ๐”ฝ(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
41
5
    MUST(array->internal_define_own_property(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = false }));
42
43
    // 7. Return A.
44
5
    return array;
45
5
}
46
47
// 7.3.18 CreateArrayFromList ( elements ), https://tc39.es/ecma262/#sec-createarrayfromlist
48
NonnullGCPtr<Array> Array::create_from(Realm& realm, Vector<Value> const& elements)
49
0
{
50
    // 1. Let array be ! ArrayCreate(0).
51
0
    auto array = MUST(Array::create(realm, 0));
52
53
    // 2. Let n be 0.
54
    // 3. For each element e of elements, do
55
0
    for (u32 n = 0; n < elements.size(); ++n) {
56
        // a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(๐”ฝ(n)), e).
57
0
        MUST(array->create_data_property_or_throw(n, elements[n]));
58
59
        // b. Set n to n + 1.
60
0
    }
61
62
    // 4. Return array.
63
0
    return array;
64
0
}
65
66
NonnullGCPtr<Array> Array::create_from(Realm& realm, ReadonlySpan<Value> const& elements)
67
0
{
68
    // 1. Let array be ! ArrayCreate(0).
69
0
    auto array = MUST(Array::create(realm, 0));
70
71
    // 2. Let n be 0.
72
    // 3. For each element e of elements, do
73
0
    for (u32 n = 0; n < elements.size(); ++n) {
74
        // a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(๐”ฝ(n)), e).
75
0
        MUST(array->create_data_property_or_throw(n, elements[n]));
76
77
        // b. Set n to n + 1.
78
0
    }
79
80
    // 4. Return array.
81
0
    return array;
82
0
}
83
84
Array::Array(Object& prototype)
85
53
    : Object(ConstructWithPrototypeTag::Tag, prototype)
86
53
{
87
53
    m_has_magical_length_property = true;
88
53
}
89
90
// 10.4.2.4 ArraySetLength ( A, Desc ), https://tc39.es/ecma262/#sec-arraysetlength
91
ThrowCompletionOr<bool> Array::set_length(PropertyDescriptor const& property_descriptor)
92
5
{
93
5
    auto& vm = this->vm();
94
95
    // 1. If Desc does not have a [[Value]] field, then
96
    // a. Return ! OrdinaryDefineOwnProperty(A, "length", Desc).
97
    // 2. Let newLenDesc be a copy of Desc.
98
    // NOTE: Handled by step 16
99
100
5
    size_t new_length = indexed_properties().array_like_size();
101
5
    if (property_descriptor.value.has_value()) {
102
        // 3. Let newLen be ? ToUint32(Desc.[[Value]]).
103
5
        new_length = TRY(property_descriptor.value->to_u32(vm));
104
        // 4. Let numberLen be ? ToNumber(Desc.[[Value]]).
105
5
        auto number_length = TRY(property_descriptor.value->to_number(vm));
106
        // 5. If newLen is not the same value as numberLen, throw a RangeError exception.
107
5
        if (new_length != number_length.as_double())
108
0
            return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "array");
109
5
    }
110
111
    // 6. Set newLenDesc.[[Value]] to newLen.
112
    // 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
113
    // 8. Assert: IsDataDescriptor(oldLenDesc) is true.
114
    // 9. Assert: oldLenDesc.[[Configurable]] is false.
115
    // 10. Let oldLen be oldLenDesc.[[Value]].
116
    // 11. If newLen โ‰ฅ oldLen, then
117
    // a. Return ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
118
    // 12. If oldLenDesc.[[Writable]] is false, return false.
119
    // NOTE: Handled by step 16
120
121
    // 13. If newLenDesc does not have a [[Writable]] field or newLenDesc.[[Writable]] true, let newWritable be true.
122
    // 14. Else,
123
    // a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any elements cannot be deleted.
124
    // b. Let newWritable be false.
125
5
    auto new_writable = property_descriptor.writable.value_or(true);
126
127
    // c. Set newLenDesc.[[Writable]] to true.
128
    // 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
129
    // 16. If succeeded is false, return false.
130
    // NOTE: Because the length property does not actually exist calling OrdinaryDefineOwnProperty
131
    // will result in unintended behavior, so instead we only implement here the small subset of
132
    // checks performed inside of it that would have mattered to us:
133
134
    // 10.1.6.3 ValidateAndApplyPropertyDescriptor ( O, P, extensible, Desc, current ), https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
135
    // 5. If current.[[Configurable]] is false, then
136
    // a. If Desc has a [[Configurable]] field and Desc.[[Configurable]] is true, return false.
137
5
    if (property_descriptor.configurable.has_value() && *property_descriptor.configurable)
138
0
        return false;
139
    // b. If Desc has an [[Enumerable]] field and SameValue(Desc.[[Enumerable]], current.[[Enumerable]]) is false, return false.
140
5
    if (property_descriptor.enumerable.has_value() && *property_descriptor.enumerable)
141
0
        return false;
142
    // c. If IsGenericDescriptor(Desc) is false and SameValue(IsAccessorDescriptor(Desc), IsAccessorDescriptor(current)) is false, return false.
143
5
    if (!property_descriptor.is_generic_descriptor() && property_descriptor.is_accessor_descriptor())
144
0
        return false;
145
    // NOTE: Step d. doesn't apply here.
146
    // e. Else if current.[[Writable]] is false, then
147
5
    if (!m_length_writable) {
148
        // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is true, return false.
149
0
        if (property_descriptor.writable.has_value() && *property_descriptor.writable)
150
0
            return false;
151
        // ii. If Desc has a [[Value]] field and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false.
152
0
        if (new_length != indexed_properties().array_like_size())
153
0
            return false;
154
0
    }
155
156
    // 17. For each own property key P of A that is an array index, whose numeric value is greater than or equal to newLen, in descending numeric index order, do
157
    // a. Let deleteSucceeded be ! A.[[Delete]](P).
158
    // b. If deleteSucceeded is false, then
159
    // i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1๐”ฝ.
160
5
    bool success = indexed_properties().set_array_like_size(new_length);
161
162
    // ii. If newWritable is false, set newLenDesc.[[Writable]] to false.
163
    // iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
164
    // NOTE: Handled by step 18
165
166
    // 18. If newWritable is false, then
167
    // a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Writable]]: false }).
168
    // b. Assert: succeeded is true.
169
5
    if (!new_writable)
170
0
        m_length_writable = false;
171
172
    // NOTE: Continuation of step #17
173
    // iv. Return false.
174
5
    if (!success)
175
0
        return false;
176
177
    // 19. Return true.
178
5
    return true;
179
5
}
180
181
// 23.1.3.30.1 SortIndexedProperties ( obj, len, SortCompare, holes ), https://tc39.es/ecma262/#sec-sortindexedproperties
182
ThrowCompletionOr<MarkedVector<Value>> sort_indexed_properties(VM& vm, Object const& object, size_t length, Function<ThrowCompletionOr<double>(Value, Value)> const& sort_compare, Holes holes)
183
0
{
184
    // 1. Let items be a new empty List.
185
0
    auto items = MarkedVector<Value> { vm.heap() };
186
187
    // 2. Let k be 0.
188
    // 3. Repeat, while k < len,
189
0
    for (size_t k = 0; k < length; ++k) {
190
        // a. Let Pk be ! ToString(๐”ฝ(k)).
191
0
        auto property_key = PropertyKey { k };
192
193
0
        bool k_read;
194
195
        // b. If holes is skip-holes, then
196
0
        if (holes == Holes::SkipHoles) {
197
            // i. Let kRead be ? HasProperty(obj, Pk).
198
0
            k_read = TRY(object.has_property(property_key));
199
0
        }
200
        // c. Else,
201
0
        else {
202
            // i. Assert: holes is read-through-holes.
203
0
            VERIFY(holes == Holes::ReadThroughHoles);
204
205
            // ii. Let kRead be true.
206
0
            k_read = true;
207
0
        }
208
209
        // d. If kRead is true, then
210
0
        if (k_read) {
211
            // i. Let kValue be ? Get(obj, Pk).
212
0
            auto k_value = TRY(object.get(property_key));
213
214
            // ii. Append kValue to items.
215
0
            items.append(k_value);
216
0
        }
217
218
        // e. Set k to k + 1.
219
0
    }
220
221
    // 4. Sort items using an implementation-defined sequence of calls to SortCompare. If any such call returns an abrupt completion, stop before performing any further calls to SortCompare or steps in this algorithm and return that Completion Record.
222
223
    // Perform sorting by merge sort. This isn't as efficient compared to quick sort, but
224
    // quicksort can't be used in all cases because the spec requires Array.prototype.sort()
225
    // to be stable. FIXME: when initially scanning through the array, maintain a flag
226
    // for if an unstable sort would be indistinguishable from a stable sort (such as just
227
    // just strings or numbers), and in that case use quick sort instead for better performance.
228
0
    TRY(array_merge_sort(vm, sort_compare, items));
229
230
    // 5. Return items.
231
0
    return items;
232
0
}
233
234
// 23.1.3.30.2 CompareArrayElements ( x, y, comparefn ), https://tc39.es/ecma262/#sec-comparearrayelements
235
ThrowCompletionOr<double> compare_array_elements(VM& vm, Value x, Value y, FunctionObject* comparefn)
236
0
{
237
    // 1. If x and y are both undefined, return +0๐”ฝ.
238
0
    if (x.is_undefined() && y.is_undefined())
239
0
        return 0;
240
241
    // 2. If x is undefined, return 1๐”ฝ.
242
0
    if (x.is_undefined())
243
0
        return 1;
244
245
    // 3. If y is undefined, return -1๐”ฝ.
246
0
    if (y.is_undefined())
247
0
        return -1;
248
249
    // 4. If comparefn is not undefined, then
250
0
    if (comparefn != nullptr) {
251
        // a. Let v be ? ToNumber(? Call(comparefn, undefined, ยซ x, y ยป)).
252
0
        auto value = TRY(call(vm, comparefn, js_undefined(), x, y));
253
0
        auto value_number = TRY(value.to_number(vm));
254
255
        // b. If v is NaN, return +0๐”ฝ.
256
0
        if (value_number.is_nan())
257
0
            return 0;
258
259
        // c. Return v.
260
0
        return value_number.as_double();
261
0
    }
262
263
    // 5. Let xString be ? ToString(x).
264
0
    auto x_string = PrimitiveString::create(vm, TRY(x.to_byte_string(vm)));
265
266
    // 6. Let yString be ? ToString(y).
267
0
    auto y_string = PrimitiveString::create(vm, TRY(y.to_byte_string(vm)));
268
269
    // 7. Let xSmaller be ! IsLessThan(xString, yString, true).
270
0
    auto x_smaller = MUST(is_less_than(vm, x_string, y_string, true));
271
272
    // 8. If xSmaller is true, return -1๐”ฝ.
273
0
    if (x_smaller == TriState::True)
274
0
        return -1;
275
276
    // 9. Let ySmaller be ! IsLessThan(yString, xString, true).
277
0
    auto y_smaller = MUST(is_less_than(vm, y_string, x_string, true));
278
279
    // 10. If ySmaller is true, return 1๐”ฝ.
280
0
    if (y_smaller == TriState::True)
281
0
        return 1;
282
283
    // 11. Return +0๐”ฝ.
284
0
    return 0;
285
0
}
286
287
// NON-STANDARD: Used to return the value of the ephemeral length property
288
ThrowCompletionOr<Optional<PropertyDescriptor>> Array::internal_get_own_property(PropertyKey const& property_key) const
289
0
{
290
0
    auto& vm = this->vm();
291
0
    if (property_key.is_string() && property_key.as_string() == vm.names.length.as_string())
292
0
        return PropertyDescriptor { .value = Value(indexed_properties().array_like_size()), .writable = m_length_writable, .enumerable = false, .configurable = false };
293
294
0
    return Object::internal_get_own_property(property_key);
295
0
}
296
297
// 10.4.2.1 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
298
ThrowCompletionOr<bool> Array::internal_define_own_property(PropertyKey const& property_key, PropertyDescriptor const& property_descriptor, Optional<PropertyDescriptor>* precomputed_get_own_property)
299
5
{
300
5
    auto& vm = this->vm();
301
302
5
    VERIFY(property_key.is_valid());
303
304
    // 1. If P is "length", then
305
5
    if (property_key.is_string() && property_key.as_string() == vm.names.length.as_string()) {
306
        // a. Return ? ArraySetLength(A, Desc).
307
5
        return set_length(property_descriptor);
308
5
    }
309
310
    // 2. Else if P is an array index, then
311
0
    if (property_key.is_number()) {
312
        // a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
313
        // b. Assert: IsDataDescriptor(oldLenDesc) is true.
314
        // c. Assert: oldLenDesc.[[Configurable]] is false.
315
        // d. Let oldLen be oldLenDesc.[[Value]].
316
        // e. Assert: oldLen is a non-negative integral Number.
317
        // f. Let index be ! ToUint32(P).
318
319
        // g. If index โ‰ฅ oldLen and oldLenDesc.[[Writable]] is false, return false.
320
0
        if (property_key.as_number() >= indexed_properties().array_like_size() && !m_length_writable)
321
0
            return false;
322
323
        // h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc).
324
0
        auto succeeded = MUST(Object::internal_define_own_property(property_key, property_descriptor, precomputed_get_own_property));
325
326
        // i. If succeeded is false, return false.
327
0
        if (!succeeded)
328
0
            return false;
329
330
        // j. If index โ‰ฅ oldLen, then
331
        // i. Set oldLenDesc.[[Value]] to index + 1๐”ฝ.
332
        // ii. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length", oldLenDesc).
333
        // iii. Assert: succeeded is true.
334
335
        // k. Return true.
336
0
        return true;
337
0
    }
338
339
    // 3. Return ? OrdinaryDefineOwnProperty(A, P, Desc).
340
0
    return Object::internal_define_own_property(property_key, property_descriptor, precomputed_get_own_property);
341
0
}
342
343
// NON-STANDARD: Used to reject deletes to ephemeral (non-configurable) length property
344
ThrowCompletionOr<bool> Array::internal_delete(PropertyKey const& property_key)
345
0
{
346
0
    auto& vm = this->vm();
347
0
    if (property_key.is_string() && property_key.as_string() == vm.names.length.as_string())
348
0
        return false;
349
0
    return Object::internal_delete(property_key);
350
0
}
351
352
// NON-STANDARD: Used to inject the ephemeral length property's key
353
ThrowCompletionOr<MarkedVector<Value>> Array::internal_own_property_keys() const
354
0
{
355
0
    auto& vm = this->vm();
356
0
    auto keys = TRY(Object::internal_own_property_keys());
357
    // FIXME: This is pretty expensive, find a better way to do this
358
0
    keys.insert(indexed_properties().real_size(), PrimitiveString::create(vm, vm.names.length.as_string()));
359
0
    return { move(keys) };
360
0
}
361
362
}