1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/base/logging.h"
6 #include "src/builtins/builtins-utils-inl.h"
7 #include "src/builtins/builtins.h"
8 #include "src/codegen/code-factory.h"
9 #include "src/common/assert-scope.h"
10 #include "src/debug/debug.h"
11 #include "src/execution/isolate.h"
12 #include "src/execution/protectors-inl.h"
13 #include "src/handles/global-handles-inl.h"
14 #include "src/logging/counters.h"
15 #include "src/objects/contexts.h"
16 #include "src/objects/elements-inl.h"
17 #include "src/objects/hash-table-inl.h"
18 #include "src/objects/js-array-buffer-inl.h"
19 #include "src/objects/js-array-inl.h"
20 #include "src/objects/js-collection-inl.h"
21 #include "src/objects/lookup.h"
22 #include "src/objects/objects-inl.h"
23 #include "src/objects/prototype.h"
24 #include "src/objects/smi.h"
25
26 namespace v8 {
27 namespace internal {
28
29 namespace {
30
IsJSArrayFastElementMovingAllowed(Isolate * isolate,JSArray receiver)31 inline bool IsJSArrayFastElementMovingAllowed(Isolate* isolate,
32 JSArray receiver) {
33 return JSObject::PrototypeHasNoElements(isolate, receiver);
34 }
35
HasSimpleElements(JSObject current)36 inline bool HasSimpleElements(JSObject current) {
37 return !current.map().IsCustomElementsReceiverMap() &&
38 !current.GetElementsAccessor()->HasAccessors(current);
39 }
40
HasOnlySimpleReceiverElements(Isolate * isolate,JSObject receiver)41 inline bool HasOnlySimpleReceiverElements(Isolate* isolate, JSObject receiver) {
42 // Check that we have no accessors on the receiver's elements.
43 if (!HasSimpleElements(receiver)) return false;
44 return JSObject::PrototypeHasNoElements(isolate, receiver);
45 }
46
HasOnlySimpleElements(Isolate * isolate,JSReceiver receiver)47 inline bool HasOnlySimpleElements(Isolate* isolate, JSReceiver receiver) {
48 DisallowGarbageCollection no_gc;
49 PrototypeIterator iter(isolate, receiver, kStartAtReceiver);
50 for (; !iter.IsAtEnd(); iter.Advance()) {
51 if (iter.GetCurrent().IsJSProxy()) return false;
52 JSObject current = iter.GetCurrent<JSObject>();
53 if (!HasSimpleElements(current)) return false;
54 }
55 return true;
56 }
57
58 // This method may transition the elements kind of the JSArray once, to make
59 // sure that all elements provided as arguments in the specified range can be
60 // added without further elements kinds transitions.
MatchArrayElementsKindToArguments(Isolate * isolate,Handle<JSArray> array,BuiltinArguments * args,int first_arg_index,int num_arguments)61 void MatchArrayElementsKindToArguments(Isolate* isolate, Handle<JSArray> array,
62 BuiltinArguments* args,
63 int first_arg_index, int num_arguments) {
64 int args_length = args->length();
65 if (first_arg_index >= args_length) return;
66
67 ElementsKind origin_kind = array->GetElementsKind();
68
69 // We do not need to transition for PACKED/HOLEY_ELEMENTS.
70 if (IsObjectElementsKind(origin_kind)) return;
71
72 ElementsKind target_kind = origin_kind;
73 {
74 DisallowGarbageCollection no_gc;
75 int last_arg_index = std::min(first_arg_index + num_arguments, args_length);
76 for (int i = first_arg_index; i < last_arg_index; i++) {
77 Object arg = (*args)[i];
78 if (arg.IsHeapObject()) {
79 if (arg.IsHeapNumber()) {
80 target_kind = PACKED_DOUBLE_ELEMENTS;
81 } else {
82 target_kind = PACKED_ELEMENTS;
83 break;
84 }
85 }
86 }
87 }
88 if (target_kind != origin_kind) {
89 // Use a short-lived HandleScope to avoid creating several copies of the
90 // elements handle which would cause issues when left-trimming later-on.
91 HandleScope scope(isolate);
92 JSObject::TransitionElementsKind(array, target_kind);
93 }
94 }
95
96 // Returns |false| if not applicable.
97 // TODO(szuend): Refactor this function because it is getting hard to
98 // understand what each call-site actually checks.
99 V8_WARN_UNUSED_RESULT
EnsureJSArrayWithWritableFastElements(Isolate * isolate,Handle<Object> receiver,BuiltinArguments * args,int first_arg_index,int num_arguments)100 inline bool EnsureJSArrayWithWritableFastElements(Isolate* isolate,
101 Handle<Object> receiver,
102 BuiltinArguments* args,
103 int first_arg_index,
104 int num_arguments) {
105 if (!receiver->IsJSArray()) return false;
106 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
107 ElementsKind origin_kind = array->GetElementsKind();
108 if (IsDictionaryElementsKind(origin_kind)) return false;
109 if (!array->map().is_extensible()) return false;
110 if (args == nullptr) return true;
111
112 // If there may be elements accessors in the prototype chain, the fast path
113 // cannot be used if there arguments to add to the array.
114 if (!IsJSArrayFastElementMovingAllowed(isolate, *array)) return false;
115
116 // Adding elements to the array prototype would break code that makes sure
117 // it has no elements. Handle that elsewhere.
118 if (isolate->IsAnyInitialArrayPrototype(*array)) return false;
119
120 // Need to ensure that the arguments passed in args can be contained in
121 // the array.
122 MatchArrayElementsKindToArguments(isolate, array, args, first_arg_index,
123 num_arguments);
124 return true;
125 }
126
127 // If |index| is Undefined, returns init_if_undefined.
128 // If |index| is negative, returns length + index.
129 // If |index| is positive, returns index.
130 // Returned value is guaranteed to be in the interval of [0, length].
GetRelativeIndex(Isolate * isolate,double length,Handle<Object> index,double init_if_undefined)131 V8_WARN_UNUSED_RESULT Maybe<double> GetRelativeIndex(Isolate* isolate,
132 double length,
133 Handle<Object> index,
134 double init_if_undefined) {
135 double relative_index = init_if_undefined;
136 if (!index->IsUndefined()) {
137 Handle<Object> relative_index_obj;
138 ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, relative_index_obj,
139 Object::ToInteger(isolate, index),
140 Nothing<double>());
141 relative_index = relative_index_obj->Number();
142 }
143
144 if (relative_index < 0) {
145 return Just(std::max(length + relative_index, 0.0));
146 }
147
148 return Just(std::min(relative_index, length));
149 }
150
151 // Returns "length", has "fast-path" for JSArrays.
GetLengthProperty(Isolate * isolate,Handle<JSReceiver> receiver)152 V8_WARN_UNUSED_RESULT Maybe<double> GetLengthProperty(
153 Isolate* isolate, Handle<JSReceiver> receiver) {
154 if (receiver->IsJSArray()) {
155 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
156 double length = array->length().Number();
157 DCHECK(0 <= length && length <= kMaxSafeInteger);
158
159 return Just(length);
160 }
161
162 Handle<Object> raw_length_number;
163 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
164 isolate, raw_length_number,
165 Object::GetLengthFromArrayLike(isolate, receiver), Nothing<double>());
166 return Just(raw_length_number->Number());
167 }
168
169 // Set "length" property, has "fast-path" for JSArrays.
170 // Returns Nothing if something went wrong.
SetLengthProperty(Isolate * isolate,Handle<JSReceiver> receiver,double length)171 V8_WARN_UNUSED_RESULT MaybeHandle<Object> SetLengthProperty(
172 Isolate* isolate, Handle<JSReceiver> receiver, double length) {
173 if (receiver->IsJSArray()) {
174 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
175 if (!JSArray::HasReadOnlyLength(array)) {
176 DCHECK_LE(length, kMaxUInt32);
177 MAYBE_RETURN_NULL(
178 JSArray::SetLength(array, static_cast<uint32_t>(length)));
179 return receiver;
180 }
181 }
182
183 return Object::SetProperty(
184 isolate, receiver, isolate->factory()->length_string(),
185 isolate->factory()->NewNumber(length), StoreOrigin::kMaybeKeyed,
186 Just(ShouldThrow::kThrowOnError));
187 }
188
GenericArrayFill(Isolate * isolate,Handle<JSReceiver> receiver,Handle<Object> value,double start,double end)189 V8_WARN_UNUSED_RESULT Object GenericArrayFill(Isolate* isolate,
190 Handle<JSReceiver> receiver,
191 Handle<Object> value,
192 double start, double end) {
193 // 7. Repeat, while k < final.
194 while (start < end) {
195 // a. Let Pk be ! ToString(k).
196 Handle<String> index = isolate->factory()->NumberToString(
197 isolate->factory()->NewNumber(start));
198
199 // b. Perform ? Set(O, Pk, value, true).
200 RETURN_FAILURE_ON_EXCEPTION(isolate, Object::SetPropertyOrElement(
201 isolate, receiver, index, value,
202 Just(ShouldThrow::kThrowOnError)));
203
204 // c. Increase k by 1.
205 ++start;
206 }
207
208 // 8. Return O.
209 return *receiver;
210 }
211
TryFastArrayFill(Isolate * isolate,BuiltinArguments * args,Handle<JSReceiver> receiver,Handle<Object> value,double start_index,double end_index)212 V8_WARN_UNUSED_RESULT Maybe<bool> TryFastArrayFill(
213 Isolate* isolate, BuiltinArguments* args, Handle<JSReceiver> receiver,
214 Handle<Object> value, double start_index, double end_index) {
215 // If indices are too large, use generic path since they are stored as
216 // properties, not in the element backing store.
217 if (end_index > kMaxUInt32) return Just(false);
218 if (!receiver->IsJSObject()) return Just(false);
219
220 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, args, 1, 1)) {
221 return Just(false);
222 }
223
224 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
225
226 // If no argument was provided, we fill the array with 'undefined'.
227 // EnsureJSArrayWith... does not handle that case so we do it here.
228 // TODO(szuend): Pass target elements kind to EnsureJSArrayWith... when
229 // it gets refactored.
230 if (args->length() == 1 && array->GetElementsKind() != PACKED_ELEMENTS) {
231 // Use a short-lived HandleScope to avoid creating several copies of the
232 // elements handle which would cause issues when left-trimming later-on.
233 HandleScope scope(isolate);
234 JSObject::TransitionElementsKind(array, PACKED_ELEMENTS);
235 }
236
237 DCHECK_LE(start_index, kMaxUInt32);
238 DCHECK_LE(end_index, kMaxUInt32);
239
240 uint32_t start, end;
241 CHECK(DoubleToUint32IfEqualToSelf(start_index, &start));
242 CHECK(DoubleToUint32IfEqualToSelf(end_index, &end));
243
244 ElementsAccessor* accessor = array->GetElementsAccessor();
245 RETURN_ON_EXCEPTION_VALUE(isolate, accessor->Fill(array, value, start, end),
246 Nothing<bool>());
247 return Just(true);
248 }
249 } // namespace
250
BUILTIN(ArrayPrototypeFill)251 BUILTIN(ArrayPrototypeFill) {
252 HandleScope scope(isolate);
253 if (isolate->debug_execution_mode() == DebugInfo::kSideEffects) {
254 if (!isolate->debug()->PerformSideEffectCheckForObject(args.receiver())) {
255 return ReadOnlyRoots(isolate).exception();
256 }
257 }
258
259 // 1. Let O be ? ToObject(this value).
260 Handle<JSReceiver> receiver;
261 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
262 isolate, receiver, Object::ToObject(isolate, args.receiver()));
263
264 // 2. Let len be ? ToLength(? Get(O, "length")).
265 double length;
266 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
267 isolate, length, GetLengthProperty(isolate, receiver));
268
269 // 3. Let relativeStart be ? ToInteger(start).
270 // 4. If relativeStart < 0, let k be max((len + relativeStart), 0);
271 // else let k be min(relativeStart, len).
272 Handle<Object> start = args.atOrUndefined(isolate, 2);
273
274 double start_index;
275 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
276 isolate, start_index, GetRelativeIndex(isolate, length, start, 0));
277
278 // 5. If end is undefined, let relativeEnd be len;
279 // else let relativeEnd be ? ToInteger(end).
280 // 6. If relativeEnd < 0, let final be max((len + relativeEnd), 0);
281 // else let final be min(relativeEnd, len).
282 Handle<Object> end = args.atOrUndefined(isolate, 3);
283
284 double end_index;
285 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
286 isolate, end_index, GetRelativeIndex(isolate, length, end, length));
287
288 if (start_index >= end_index) return *receiver;
289
290 // Ensure indexes are within array bounds
291 DCHECK_LE(0, start_index);
292 DCHECK_LE(start_index, end_index);
293 DCHECK_LE(end_index, length);
294
295 Handle<Object> value = args.atOrUndefined(isolate, 1);
296
297 bool success;
298 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
299 isolate, success,
300 TryFastArrayFill(isolate, &args, receiver, value, start_index,
301 end_index));
302 if (success) return *receiver;
303 return GenericArrayFill(isolate, receiver, value, start_index, end_index);
304 }
305
306 namespace {
GenericArrayPush(Isolate * isolate,BuiltinArguments * args)307 V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
308 BuiltinArguments* args) {
309 // 1. Let O be ? ToObject(this value).
310 Handle<JSReceiver> receiver;
311 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
312 isolate, receiver, Object::ToObject(isolate, args->receiver()));
313
314 // 2. Let len be ? ToLength(? Get(O, "length")).
315 Handle<Object> raw_length_number;
316 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
317 isolate, raw_length_number,
318 Object::GetLengthFromArrayLike(isolate, receiver));
319
320 // 3. Let args be a List whose elements are, in left to right order,
321 // the arguments that were passed to this function invocation.
322 // 4. Let arg_count be the number of elements in args.
323 int arg_count = args->length() - 1;
324
325 // 5. If len + arg_count > 2^53-1, throw a TypeError exception.
326 double length = raw_length_number->Number();
327 if (arg_count > kMaxSafeInteger - length) {
328 THROW_NEW_ERROR_RETURN_FAILURE(
329 isolate, NewTypeError(MessageTemplate::kPushPastSafeLength,
330 isolate->factory()->NewNumberFromInt(arg_count),
331 raw_length_number));
332 }
333
334 // 6. Repeat, while args is not empty.
335 for (int i = 0; i < arg_count; ++i) {
336 // a. Remove the first element from args and let E be the value of the
337 // element.
338 Handle<Object> element = args->at(i + 1);
339
340 // b. Perform ? Set(O, ! ToString(len), E, true).
341 if (length <= JSObject::kMaxElementIndex) {
342 RETURN_FAILURE_ON_EXCEPTION(
343 isolate, Object::SetElement(isolate, receiver, length, element,
344 ShouldThrow::kThrowOnError));
345 } else {
346 PropertyKey key(isolate, length);
347 LookupIterator it(isolate, receiver, key);
348 MAYBE_RETURN(Object::SetProperty(&it, element, StoreOrigin::kMaybeKeyed,
349 Just(ShouldThrow::kThrowOnError)),
350 ReadOnlyRoots(isolate).exception());
351 }
352
353 // c. Let len be len+1.
354 ++length;
355 }
356
357 // 7. Perform ? Set(O, "length", len, true).
358 Handle<Object> final_length = isolate->factory()->NewNumber(length);
359 RETURN_FAILURE_ON_EXCEPTION(
360 isolate, Object::SetProperty(isolate, receiver,
361 isolate->factory()->length_string(),
362 final_length, StoreOrigin::kMaybeKeyed,
363 Just(ShouldThrow::kThrowOnError)));
364
365 // 8. Return len.
366 return *final_length;
367 }
368 } // namespace
369
BUILTIN(ArrayPush)370 BUILTIN(ArrayPush) {
371 HandleScope scope(isolate);
372 Handle<Object> receiver = args.receiver();
373 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 1,
374 args.length() - 1)) {
375 return GenericArrayPush(isolate, &args);
376 }
377
378 // Fast Elements Path
379 int to_add = args.length() - 1;
380 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
381 uint32_t len = static_cast<uint32_t>(array->length().Number());
382 if (to_add == 0) return *isolate->factory()->NewNumberFromUint(len);
383
384 // Currently fixed arrays cannot grow too big, so we should never hit this.
385 DCHECK_LE(to_add, Smi::kMaxValue - Smi::ToInt(array->length()));
386
387 if (JSArray::HasReadOnlyLength(array)) {
388 return GenericArrayPush(isolate, &args);
389 }
390
391 ElementsAccessor* accessor = array->GetElementsAccessor();
392 uint32_t new_length;
393 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
394 isolate, new_length, accessor->Push(array, &args, to_add));
395 return *isolate->factory()->NewNumberFromUint((new_length));
396 }
397
398 namespace {
399
GenericArrayPop(Isolate * isolate,BuiltinArguments * args)400 V8_WARN_UNUSED_RESULT Object GenericArrayPop(Isolate* isolate,
401 BuiltinArguments* args) {
402 // 1. Let O be ? ToObject(this value).
403 Handle<JSReceiver> receiver;
404 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
405 isolate, receiver, Object::ToObject(isolate, args->receiver()));
406
407 // 2. Let len be ? ToLength(? Get(O, "length")).
408 Handle<Object> raw_length_number;
409 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
410 isolate, raw_length_number,
411 Object::GetLengthFromArrayLike(isolate, receiver));
412 double length = raw_length_number->Number();
413
414 // 3. If len is zero, then.
415 if (length == 0) {
416 // a. Perform ? Set(O, "length", 0, true).
417 RETURN_FAILURE_ON_EXCEPTION(
418 isolate, Object::SetProperty(isolate, receiver,
419 isolate->factory()->length_string(),
420 Handle<Smi>(Smi::zero(), isolate),
421 StoreOrigin::kMaybeKeyed,
422 Just(ShouldThrow::kThrowOnError)));
423
424 // b. Return undefined.
425 return ReadOnlyRoots(isolate).undefined_value();
426 }
427
428 // 4. Else len > 0.
429 // a. Let new_len be len-1.
430 Handle<Object> new_length = isolate->factory()->NewNumber(length - 1);
431
432 // b. Let index be ! ToString(newLen).
433 Handle<String> index = isolate->factory()->NumberToString(new_length);
434
435 // c. Let element be ? Get(O, index).
436 Handle<Object> element;
437 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
438 isolate, element, Object::GetPropertyOrElement(isolate, receiver, index));
439
440 // d. Perform ? DeletePropertyOrThrow(O, index).
441 MAYBE_RETURN(JSReceiver::DeletePropertyOrElement(receiver, index,
442 LanguageMode::kStrict),
443 ReadOnlyRoots(isolate).exception());
444
445 // e. Perform ? Set(O, "length", newLen, true).
446 RETURN_FAILURE_ON_EXCEPTION(
447 isolate, Object::SetProperty(isolate, receiver,
448 isolate->factory()->length_string(),
449 new_length, StoreOrigin::kMaybeKeyed,
450 Just(ShouldThrow::kThrowOnError)));
451
452 // f. Return element.
453 return *element;
454 }
455
456 } // namespace
457
BUILTIN(ArrayPop)458 BUILTIN(ArrayPop) {
459 HandleScope scope(isolate);
460 Handle<Object> receiver = args.receiver();
461 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, nullptr, 0,
462 0)) {
463 return GenericArrayPop(isolate, &args);
464 }
465 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
466
467 uint32_t len = static_cast<uint32_t>(array->length().Number());
468
469 if (JSArray::HasReadOnlyLength(array)) {
470 return GenericArrayPop(isolate, &args);
471 }
472 if (len == 0) return ReadOnlyRoots(isolate).undefined_value();
473
474 Handle<Object> result;
475 if (IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) {
476 // Fast Elements Path
477 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
478 isolate, result, array->GetElementsAccessor()->Pop(array));
479 } else {
480 // Use Slow Lookup otherwise
481 uint32_t new_length = len - 1;
482 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
483 isolate, result, JSReceiver::GetElement(isolate, array, new_length));
484
485 // The length could have become read-only during the last GetElement() call,
486 // so check again.
487 if (JSArray::HasReadOnlyLength(array)) {
488 THROW_NEW_ERROR_RETURN_FAILURE(
489 isolate, NewTypeError(MessageTemplate::kStrictReadOnlyProperty,
490 isolate->factory()->length_string(),
491 Object::TypeOf(isolate, array), array));
492 }
493 bool set_len_ok;
494 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
495 isolate, set_len_ok, JSArray::SetLength(array, new_length));
496 }
497
498 return *result;
499 }
500
501 namespace {
502
503 // Returns true, iff we can use ElementsAccessor for shifting.
CanUseFastArrayShift(Isolate * isolate,Handle<JSReceiver> receiver)504 V8_WARN_UNUSED_RESULT bool CanUseFastArrayShift(Isolate* isolate,
505 Handle<JSReceiver> receiver) {
506 if (!EnsureJSArrayWithWritableFastElements(isolate, receiver, nullptr, 0,
507 0) ||
508 !IsJSArrayFastElementMovingAllowed(isolate, JSArray::cast(*receiver))) {
509 return false;
510 }
511
512 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
513 return !JSArray::HasReadOnlyLength(array);
514 }
515
GenericArrayShift(Isolate * isolate,Handle<JSReceiver> receiver,double length)516 V8_WARN_UNUSED_RESULT Object GenericArrayShift(Isolate* isolate,
517 Handle<JSReceiver> receiver,
518 double length) {
519 // 4. Let first be ? Get(O, "0").
520 Handle<Object> first;
521 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, first,
522 Object::GetElement(isolate, receiver, 0));
523
524 // 5. Let k be 1.
525 double k = 1;
526
527 // 6. Repeat, while k < len.
528 while (k < length) {
529 // a. Let from be ! ToString(k).
530 Handle<String> from =
531 isolate->factory()->NumberToString(isolate->factory()->NewNumber(k));
532
533 // b. Let to be ! ToString(k-1).
534 Handle<String> to = isolate->factory()->NumberToString(
535 isolate->factory()->NewNumber(k - 1));
536
537 // c. Let fromPresent be ? HasProperty(O, from).
538 bool from_present;
539 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
540 isolate, from_present,
541 JSReceiver::HasProperty(isolate, receiver, from));
542
543 // d. If fromPresent is true, then.
544 if (from_present) {
545 // i. Let fromVal be ? Get(O, from).
546 Handle<Object> from_val;
547 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
548 isolate, from_val,
549 Object::GetPropertyOrElement(isolate, receiver, from));
550
551 // ii. Perform ? Set(O, to, fromVal, true).
552 RETURN_FAILURE_ON_EXCEPTION(
553 isolate,
554 Object::SetPropertyOrElement(isolate, receiver, to, from_val,
555 Just(ShouldThrow::kThrowOnError)));
556 } else { // e. Else fromPresent is false,
557 // i. Perform ? DeletePropertyOrThrow(O, to).
558 MAYBE_RETURN(JSReceiver::DeletePropertyOrElement(receiver, to,
559 LanguageMode::kStrict),
560 ReadOnlyRoots(isolate).exception());
561 }
562
563 // f. Increase k by 1.
564 ++k;
565 }
566
567 // 7. Perform ? DeletePropertyOrThrow(O, ! ToString(len-1)).
568 Handle<String> new_length = isolate->factory()->NumberToString(
569 isolate->factory()->NewNumber(length - 1));
570 MAYBE_RETURN(JSReceiver::DeletePropertyOrElement(receiver, new_length,
571 LanguageMode::kStrict),
572 ReadOnlyRoots(isolate).exception());
573
574 // 8. Perform ? Set(O, "length", len-1, true).
575 RETURN_FAILURE_ON_EXCEPTION(isolate,
576 SetLengthProperty(isolate, receiver, length - 1));
577
578 // 9. Return first.
579 return *first;
580 }
581 } // namespace
582
BUILTIN(ArrayShift)583 BUILTIN(ArrayShift) {
584 HandleScope scope(isolate);
585
586 // 1. Let O be ? ToObject(this value).
587 Handle<JSReceiver> receiver;
588 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
589 isolate, receiver, Object::ToObject(isolate, args.receiver()));
590
591 // 2. Let len be ? ToLength(? Get(O, "length")).
592 double length;
593 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
594 isolate, length, GetLengthProperty(isolate, receiver));
595
596 // 3. If len is zero, then.
597 if (length == 0) {
598 // a. Perform ? Set(O, "length", 0, true).
599 RETURN_FAILURE_ON_EXCEPTION(isolate,
600 SetLengthProperty(isolate, receiver, length));
601
602 // b. Return undefined.
603 return ReadOnlyRoots(isolate).undefined_value();
604 }
605
606 if (CanUseFastArrayShift(isolate, receiver)) {
607 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
608 RETURN_RESULT_OR_FAILURE(isolate,
609 array->GetElementsAccessor()->Shift(array));
610 }
611
612 return GenericArrayShift(isolate, receiver, length);
613 }
614
BUILTIN(ArrayUnshift)615 BUILTIN(ArrayUnshift) {
616 HandleScope scope(isolate);
617 DCHECK(args.receiver()->IsJSArray());
618 Handle<JSArray> array = Handle<JSArray>::cast(args.receiver());
619
620 // These are checked in the Torque builtin.
621 DCHECK(array->map().is_extensible());
622 DCHECK(!IsDictionaryElementsKind(array->GetElementsKind()));
623 DCHECK(IsJSArrayFastElementMovingAllowed(isolate, *array));
624 DCHECK(!isolate->IsAnyInitialArrayPrototype(*array));
625
626 MatchArrayElementsKindToArguments(isolate, array, &args, 1,
627 args.length() - 1);
628
629 int to_add = args.length() - 1;
630 if (to_add == 0) return array->length();
631
632 // Currently fixed arrays cannot grow too big, so we should never hit this.
633 DCHECK_LE(to_add, Smi::kMaxValue - Smi::ToInt(array->length()));
634 DCHECK(!JSArray::HasReadOnlyLength(array));
635
636 ElementsAccessor* accessor = array->GetElementsAccessor();
637 uint32_t new_length;
638 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
639 isolate, new_length, accessor->Unshift(array, &args, to_add));
640 return Smi::FromInt(new_length);
641 }
642
643 // Array Concat -------------------------------------------------------------
644
645 namespace {
646
647 /**
648 * A simple visitor visits every element of Array's.
649 * The backend storage can be a fixed array for fast elements case,
650 * or a dictionary for sparse array. Since Dictionary is a subtype
651 * of FixedArray, the class can be used by both fast and slow cases.
652 * The second parameter of the constructor, fast_elements, specifies
653 * whether the storage is a FixedArray or Dictionary.
654 *
655 * An index limit is used to deal with the situation that a result array
656 * length overflows 32-bit non-negative integer.
657 */
658 class ArrayConcatVisitor {
659 public:
ArrayConcatVisitor(Isolate * isolate,Handle<HeapObject> storage,bool fast_elements)660 ArrayConcatVisitor(Isolate* isolate, Handle<HeapObject> storage,
661 bool fast_elements)
662 : isolate_(isolate),
663 storage_(isolate->global_handles()->Create(*storage)),
664 index_offset_(0u),
665 bit_field_(FastElementsField::encode(fast_elements) |
666 ExceedsLimitField::encode(false) |
667 IsFixedArrayField::encode(storage->IsFixedArray(isolate)) |
668 HasSimpleElementsField::encode(
669 storage->IsFixedArray(isolate) ||
670 // Don't take fast path for storages that might have
671 // side effects when storing to them.
672 (!storage->map(isolate).IsCustomElementsReceiverMap() &&
673 !storage->IsJSTypedArray(isolate)))) {
674 DCHECK_IMPLIES(this->fast_elements(), is_fixed_array());
675 }
676
~ArrayConcatVisitor()677 ~ArrayConcatVisitor() { clear_storage(); }
678
visit(uint32_t i,Handle<Object> elm)679 V8_WARN_UNUSED_RESULT bool visit(uint32_t i, Handle<Object> elm) {
680 uint32_t index = index_offset_ + i;
681
682 // Note we use >=kMaxArrayLength instead of the more appropriate
683 // >kMaxArrayIndex here due to overflowing arithmetic and
684 // increase_index_offset.
685 if (i >= JSArray::kMaxArrayLength - index_offset_) {
686 set_exceeds_array_limit(true);
687 // Exception hasn't been thrown at this point. Return true to
688 // break out, and caller will throw. !visit would imply that
689 // there is already a pending exception.
690 return true;
691 }
692
693 if (!is_fixed_array()) {
694 LookupIterator it(isolate_, storage_, index, LookupIterator::OWN);
695 MAYBE_RETURN(
696 JSReceiver::CreateDataProperty(&it, elm, Just(kThrowOnError)), false);
697 return true;
698 }
699
700 if (fast_elements()) {
701 if (index < static_cast<uint32_t>(storage_fixed_array()->length())) {
702 storage_fixed_array()->set(index, *elm);
703 return true;
704 }
705 // Our initial estimate of length was foiled, possibly by
706 // getters on the arrays increasing the length of later arrays
707 // during iteration.
708 // This shouldn't happen in anything but pathological cases.
709 SetDictionaryMode();
710 // Fall-through to dictionary mode.
711 }
712 DCHECK(!fast_elements());
713 Handle<NumberDictionary> dict(NumberDictionary::cast(*storage_), isolate_);
714 // The object holding this backing store has just been allocated, so
715 // it cannot yet be used as a prototype.
716 Handle<JSObject> not_a_prototype_holder;
717 Handle<NumberDictionary> result = NumberDictionary::Set(
718 isolate_, dict, index, elm, not_a_prototype_holder);
719 if (!result.is_identical_to(dict)) {
720 // Dictionary needed to grow.
721 clear_storage();
722 set_storage(*result);
723 }
724 return true;
725 }
726
index_offset() const727 uint32_t index_offset() const { return index_offset_; }
728
increase_index_offset(uint32_t delta)729 void increase_index_offset(uint32_t delta) {
730 if (JSArray::kMaxArrayLength - index_offset_ < delta) {
731 index_offset_ = JSArray::kMaxArrayLength;
732 } else {
733 index_offset_ += delta;
734 }
735 // If the initial length estimate was off (see special case in visit()),
736 // but the array blowing the limit didn't contain elements beyond the
737 // provided-for index range, go to dictionary mode now.
738 if (fast_elements() &&
739 index_offset_ >
740 static_cast<uint32_t>(FixedArrayBase::cast(*storage_).length())) {
741 SetDictionaryMode();
742 }
743 }
744
exceeds_array_limit() const745 bool exceeds_array_limit() const {
746 return ExceedsLimitField::decode(bit_field_);
747 }
748
ToArray()749 Handle<JSArray> ToArray() {
750 DCHECK(is_fixed_array());
751 Handle<JSArray> array = isolate_->factory()->NewJSArray(0);
752 Handle<Object> length =
753 isolate_->factory()->NewNumber(static_cast<double>(index_offset_));
754 Handle<Map> map = JSObject::GetElementsTransitionMap(
755 array, fast_elements() ? HOLEY_ELEMENTS : DICTIONARY_ELEMENTS);
756 array->set_length(*length);
757 array->set_elements(*storage_fixed_array());
758 array->set_map(*map, kReleaseStore);
759 return array;
760 }
761
ToJSReceiver()762 V8_WARN_UNUSED_RESULT MaybeHandle<JSReceiver> ToJSReceiver() {
763 DCHECK(!is_fixed_array());
764 Handle<JSReceiver> result = Handle<JSReceiver>::cast(storage_);
765 Handle<Object> length =
766 isolate_->factory()->NewNumber(static_cast<double>(index_offset_));
767 RETURN_ON_EXCEPTION(
768 isolate_,
769 Object::SetProperty(
770 isolate_, result, isolate_->factory()->length_string(), length,
771 StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError)),
772 JSReceiver);
773 return result;
774 }
has_simple_elements() const775 bool has_simple_elements() const {
776 return HasSimpleElementsField::decode(bit_field_);
777 }
778
779 private:
780 // Convert storage to dictionary mode.
SetDictionaryMode()781 void SetDictionaryMode() {
782 DCHECK(fast_elements() && is_fixed_array());
783 Handle<FixedArray> current_storage = storage_fixed_array();
784 Handle<NumberDictionary> slow_storage(
785 NumberDictionary::New(isolate_, current_storage->length()));
786 uint32_t current_length = static_cast<uint32_t>(current_storage->length());
787 FOR_WITH_HANDLE_SCOPE(
788 isolate_, uint32_t, i = 0, i, i < current_length, i++, {
789 Handle<Object> element(current_storage->get(i), isolate_);
790 if (!element->IsTheHole(isolate_)) {
791 // The object holding this backing store has just been allocated, so
792 // it cannot yet be used as a prototype.
793 Handle<JSObject> not_a_prototype_holder;
794 Handle<NumberDictionary> new_storage = NumberDictionary::Set(
795 isolate_, slow_storage, i, element, not_a_prototype_holder);
796 if (!new_storage.is_identical_to(slow_storage)) {
797 slow_storage = loop_scope.CloseAndEscape(new_storage);
798 }
799 }
800 });
801 clear_storage();
802 set_storage(*slow_storage);
803 set_fast_elements(false);
804 }
805
clear_storage()806 inline void clear_storage() { GlobalHandles::Destroy(storage_.location()); }
807
set_storage(FixedArray storage)808 inline void set_storage(FixedArray storage) {
809 DCHECK(is_fixed_array());
810 DCHECK(has_simple_elements());
811 storage_ = isolate_->global_handles()->Create(storage);
812 }
813
814 using FastElementsField = base::BitField<bool, 0, 1>;
815 using ExceedsLimitField = base::BitField<bool, 1, 1>;
816 using IsFixedArrayField = base::BitField<bool, 2, 1>;
817 using HasSimpleElementsField = base::BitField<bool, 3, 1>;
818
fast_elements() const819 bool fast_elements() const { return FastElementsField::decode(bit_field_); }
set_fast_elements(bool fast)820 void set_fast_elements(bool fast) {
821 bit_field_ = FastElementsField::update(bit_field_, fast);
822 }
set_exceeds_array_limit(bool exceeds)823 void set_exceeds_array_limit(bool exceeds) {
824 bit_field_ = ExceedsLimitField::update(bit_field_, exceeds);
825 }
is_fixed_array() const826 bool is_fixed_array() const { return IsFixedArrayField::decode(bit_field_); }
storage_fixed_array()827 Handle<FixedArray> storage_fixed_array() {
828 DCHECK(is_fixed_array());
829 DCHECK(has_simple_elements());
830 return Handle<FixedArray>::cast(storage_);
831 }
832
833 Isolate* isolate_;
834 Handle<Object> storage_; // Always a global handle.
835 // Index after last seen index. Always less than or equal to
836 // JSArray::kMaxArrayLength.
837 uint32_t index_offset_;
838 uint32_t bit_field_;
839 };
840
EstimateElementCount(Isolate * isolate,Handle<JSArray> array)841 uint32_t EstimateElementCount(Isolate* isolate, Handle<JSArray> array) {
842 DisallowGarbageCollection no_gc;
843 uint32_t length = static_cast<uint32_t>(array->length().Number());
844 int element_count = 0;
845 switch (array->GetElementsKind()) {
846 case PACKED_SMI_ELEMENTS:
847 case HOLEY_SMI_ELEMENTS:
848 case PACKED_ELEMENTS:
849 case PACKED_FROZEN_ELEMENTS:
850 case PACKED_SEALED_ELEMENTS:
851 case PACKED_NONEXTENSIBLE_ELEMENTS:
852 case HOLEY_FROZEN_ELEMENTS:
853 case HOLEY_SEALED_ELEMENTS:
854 case HOLEY_NONEXTENSIBLE_ELEMENTS:
855 case HOLEY_ELEMENTS: {
856 // Fast elements can't have lengths that are not representable by
857 // a 32-bit signed integer.
858 DCHECK_GE(static_cast<int32_t>(FixedArray::kMaxLength), 0);
859 int fast_length = static_cast<int>(length);
860 FixedArray elements = FixedArray::cast(array->elements());
861 for (int i = 0; i < fast_length; i++) {
862 if (!elements.get(i).IsTheHole(isolate)) element_count++;
863 }
864 break;
865 }
866 case PACKED_DOUBLE_ELEMENTS:
867 case HOLEY_DOUBLE_ELEMENTS: {
868 // Fast elements can't have lengths that are not representable by
869 // a 32-bit signed integer.
870 DCHECK_GE(static_cast<int32_t>(FixedDoubleArray::kMaxLength), 0);
871 int fast_length = static_cast<int>(length);
872 if (array->elements().IsFixedArray()) {
873 DCHECK_EQ(FixedArray::cast(array->elements()).length(), 0);
874 break;
875 }
876 FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
877 for (int i = 0; i < fast_length; i++) {
878 if (!elements.is_the_hole(i)) element_count++;
879 }
880 break;
881 }
882 case DICTIONARY_ELEMENTS: {
883 NumberDictionary dictionary = NumberDictionary::cast(array->elements());
884 ReadOnlyRoots roots(isolate);
885 for (InternalIndex i : dictionary.IterateEntries()) {
886 Object key = dictionary.KeyAt(i);
887 if (dictionary.IsKey(roots, key)) {
888 element_count++;
889 }
890 }
891 break;
892 }
893 #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
894
895 TYPED_ARRAYS(TYPED_ARRAY_CASE)
896 RAB_GSAB_TYPED_ARRAYS(TYPED_ARRAY_CASE)
897 // External arrays are always dense.
898 return length;
899
900 #undef TYPED_ARRAY_CASE
901 case NO_ELEMENTS:
902 return 0;
903 case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
904 case SLOW_SLOPPY_ARGUMENTS_ELEMENTS:
905 case FAST_STRING_WRAPPER_ELEMENTS:
906 case SLOW_STRING_WRAPPER_ELEMENTS:
907 case WASM_ARRAY_ELEMENTS:
908 UNREACHABLE();
909 }
910 // As an estimate, we assume that the prototype doesn't contain any
911 // inherited elements.
912 return element_count;
913 }
914
CollectElementIndices(Isolate * isolate,Handle<JSObject> object,uint32_t range,std::vector<uint32_t> * indices)915 void CollectElementIndices(Isolate* isolate, Handle<JSObject> object,
916 uint32_t range, std::vector<uint32_t>* indices) {
917 ElementsKind kind = object->GetElementsKind();
918 switch (kind) {
919 case PACKED_SMI_ELEMENTS:
920 case PACKED_ELEMENTS:
921 case PACKED_FROZEN_ELEMENTS:
922 case PACKED_SEALED_ELEMENTS:
923 case PACKED_NONEXTENSIBLE_ELEMENTS:
924 case HOLEY_SMI_ELEMENTS:
925 case HOLEY_FROZEN_ELEMENTS:
926 case HOLEY_SEALED_ELEMENTS:
927 case HOLEY_NONEXTENSIBLE_ELEMENTS:
928 case HOLEY_ELEMENTS: {
929 DisallowGarbageCollection no_gc;
930 FixedArray elements = FixedArray::cast(object->elements());
931 uint32_t length = static_cast<uint32_t>(elements.length());
932 if (range < length) length = range;
933 for (uint32_t i = 0; i < length; i++) {
934 if (!elements.get(i).IsTheHole(isolate)) {
935 indices->push_back(i);
936 }
937 }
938 break;
939 }
940 case HOLEY_DOUBLE_ELEMENTS:
941 case PACKED_DOUBLE_ELEMENTS: {
942 if (object->elements().IsFixedArray()) {
943 DCHECK_EQ(object->elements().length(), 0);
944 break;
945 }
946 Handle<FixedDoubleArray> elements(
947 FixedDoubleArray::cast(object->elements()), isolate);
948 uint32_t length = static_cast<uint32_t>(elements->length());
949 if (range < length) length = range;
950 for (uint32_t i = 0; i < length; i++) {
951 if (!elements->is_the_hole(i)) {
952 indices->push_back(i);
953 }
954 }
955 break;
956 }
957 case DICTIONARY_ELEMENTS: {
958 DisallowGarbageCollection no_gc;
959 NumberDictionary dict = NumberDictionary::cast(object->elements());
960 uint32_t capacity = dict.Capacity();
961 ReadOnlyRoots roots(isolate);
962 FOR_WITH_HANDLE_SCOPE(isolate, uint32_t, j = 0, j, j < capacity, j++, {
963 Object k = dict.KeyAt(InternalIndex(j));
964 if (!dict.IsKey(roots, k)) continue;
965 DCHECK(k.IsNumber());
966 uint32_t index = static_cast<uint32_t>(k.Number());
967 if (index < range) {
968 indices->push_back(index);
969 }
970 });
971 break;
972 }
973 #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
974
975 TYPED_ARRAYS(TYPED_ARRAY_CASE) {
976 size_t length = Handle<JSTypedArray>::cast(object)->length();
977 if (range <= length) {
978 length = range;
979 // We will add all indices, so we might as well clear it first
980 // and avoid duplicates.
981 indices->clear();
982 }
983 // {range} puts a cap on {length}.
984 DCHECK_LE(length, std::numeric_limits<uint32_t>::max());
985 for (uint32_t i = 0; i < length; i++) {
986 indices->push_back(i);
987 }
988 if (length == range) return; // All indices accounted for already.
989 break;
990 }
991 RAB_GSAB_TYPED_ARRAYS(TYPED_ARRAY_CASE)
992 // TODO(v8:11111): Support RAB / GSAB.
993 UNREACHABLE();
994
995 #undef TYPED_ARRAY_CASE
996 case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
997 case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: {
998 DisallowGarbageCollection no_gc;
999 DisableGCMole no_gc_mole;
1000 FixedArrayBase elements = object->elements();
1001 JSObject raw_object = *object;
1002 ElementsAccessor* accessor = object->GetElementsAccessor();
1003 for (uint32_t i = 0; i < range; i++) {
1004 if (accessor->HasElement(raw_object, i, elements)) {
1005 indices->push_back(i);
1006 }
1007 }
1008 break;
1009 }
1010 case FAST_STRING_WRAPPER_ELEMENTS:
1011 case SLOW_STRING_WRAPPER_ELEMENTS: {
1012 DCHECK(object->IsJSPrimitiveWrapper());
1013 Handle<JSPrimitiveWrapper> js_value =
1014 Handle<JSPrimitiveWrapper>::cast(object);
1015 DCHECK(js_value->value().IsString());
1016 Handle<String> string(String::cast(js_value->value()), isolate);
1017 uint32_t length = static_cast<uint32_t>(string->length());
1018 uint32_t i = 0;
1019 uint32_t limit = std::min(length, range);
1020 for (; i < limit; i++) {
1021 indices->push_back(i);
1022 }
1023 ElementsAccessor* accessor = object->GetElementsAccessor();
1024 for (; i < range; i++) {
1025 if (accessor->HasElement(*object, i)) {
1026 indices->push_back(i);
1027 }
1028 }
1029 break;
1030 }
1031 case WASM_ARRAY_ELEMENTS:
1032 // TODO(ishell): implement
1033 UNIMPLEMENTED();
1034 case NO_ELEMENTS:
1035 break;
1036 }
1037
1038 PrototypeIterator iter(isolate, object);
1039 if (!iter.IsAtEnd()) {
1040 // The prototype will usually have no inherited element indices,
1041 // but we have to check.
1042 CollectElementIndices(
1043 isolate, PrototypeIterator::GetCurrent<JSObject>(iter), range, indices);
1044 }
1045 }
1046
IterateElementsSlow(Isolate * isolate,Handle<JSReceiver> receiver,uint32_t length,ArrayConcatVisitor * visitor)1047 bool IterateElementsSlow(Isolate* isolate, Handle<JSReceiver> receiver,
1048 uint32_t length, ArrayConcatVisitor* visitor) {
1049 FOR_WITH_HANDLE_SCOPE(isolate, uint32_t, i = 0, i, i < length, ++i, {
1050 Maybe<bool> maybe = JSReceiver::HasElement(isolate, receiver, i);
1051 if (maybe.IsNothing()) return false;
1052 if (maybe.FromJust()) {
1053 Handle<Object> element_value;
1054 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1055 isolate, element_value, JSReceiver::GetElement(isolate, receiver, i),
1056 false);
1057 if (!visitor->visit(i, element_value)) return false;
1058 }
1059 });
1060 visitor->increase_index_offset(length);
1061 return true;
1062 }
1063 /**
1064 * A helper function that visits "array" elements of a JSReceiver in numerical
1065 * order.
1066 *
1067 * The visitor argument called for each existing element in the array
1068 * with the element index and the element's value.
1069 * Afterwards it increments the base-index of the visitor by the array
1070 * length.
1071 * Returns false if any access threw an exception, otherwise true.
1072 */
IterateElements(Isolate * isolate,Handle<JSReceiver> receiver,ArrayConcatVisitor * visitor)1073 bool IterateElements(Isolate* isolate, Handle<JSReceiver> receiver,
1074 ArrayConcatVisitor* visitor) {
1075 uint32_t length = 0;
1076
1077 if (receiver->IsJSArray()) {
1078 Handle<JSArray> array = Handle<JSArray>::cast(receiver);
1079 length = static_cast<uint32_t>(array->length().Number());
1080 } else {
1081 Handle<Object> val;
1082 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1083 isolate, val, Object::GetLengthFromArrayLike(isolate, receiver), false);
1084 if (visitor->index_offset() + val->Number() > kMaxSafeInteger) {
1085 isolate->Throw(*isolate->factory()->NewTypeError(
1086 MessageTemplate::kInvalidArrayLength));
1087 return false;
1088 }
1089 // TODO(caitp): Support larger element indexes (up to 2^53-1).
1090 if (!val->ToUint32(&length)) {
1091 length = 0;
1092 }
1093 // TODO(cbruni): handle other element kind as well
1094 return IterateElementsSlow(isolate, receiver, length, visitor);
1095 }
1096
1097 if (!visitor->has_simple_elements() ||
1098 !HasOnlySimpleElements(isolate, *receiver)) {
1099 return IterateElementsSlow(isolate, receiver, length, visitor);
1100 }
1101 Handle<JSObject> array = Handle<JSObject>::cast(receiver);
1102
1103 switch (array->GetElementsKind()) {
1104 case PACKED_SMI_ELEMENTS:
1105 case PACKED_ELEMENTS:
1106 case PACKED_FROZEN_ELEMENTS:
1107 case PACKED_SEALED_ELEMENTS:
1108 case PACKED_NONEXTENSIBLE_ELEMENTS:
1109 case HOLEY_SMI_ELEMENTS:
1110 case HOLEY_FROZEN_ELEMENTS:
1111 case HOLEY_SEALED_ELEMENTS:
1112 case HOLEY_NONEXTENSIBLE_ELEMENTS:
1113 case HOLEY_ELEMENTS: {
1114 // Disallow execution so the cached elements won't change mid execution.
1115 DisallowJavascriptExecution no_js(isolate);
1116
1117 // Run through the elements FixedArray and use HasElement and GetElement
1118 // to check the prototype for missing elements.
1119 Handle<FixedArray> elements(FixedArray::cast(array->elements()), isolate);
1120 int fast_length = static_cast<int>(length);
1121 DCHECK(fast_length <= elements->length());
1122 FOR_WITH_HANDLE_SCOPE(isolate, int, j = 0, j, j < fast_length, j++, {
1123 Handle<Object> element_value(elements->get(j), isolate);
1124 if (!element_value->IsTheHole(isolate)) {
1125 if (!visitor->visit(j, element_value)) return false;
1126 } else {
1127 Maybe<bool> maybe = JSReceiver::HasElement(isolate, array, j);
1128 if (maybe.IsNothing()) return false;
1129 if (maybe.FromJust()) {
1130 // Call GetElement on array, not its prototype, or getters won't
1131 // have the correct receiver.
1132 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1133 isolate, element_value,
1134 JSReceiver::GetElement(isolate, array, j), false);
1135 if (!visitor->visit(j, element_value)) return false;
1136 }
1137 }
1138 });
1139 break;
1140 }
1141 case HOLEY_DOUBLE_ELEMENTS:
1142 case PACKED_DOUBLE_ELEMENTS: {
1143 // Disallow execution so the cached elements won't change mid execution.
1144 DisallowJavascriptExecution no_js(isolate);
1145
1146 // Empty array is FixedArray but not FixedDoubleArray.
1147 if (length == 0) break;
1148 // Run through the elements FixedArray and use HasElement and GetElement
1149 // to check the prototype for missing elements.
1150 if (array->elements().IsFixedArray()) {
1151 DCHECK_EQ(array->elements().length(), 0);
1152 break;
1153 }
1154 Handle<FixedDoubleArray> elements(
1155 FixedDoubleArray::cast(array->elements()), isolate);
1156 int fast_length = static_cast<int>(length);
1157 DCHECK(fast_length <= elements->length());
1158 FOR_WITH_HANDLE_SCOPE(isolate, int, j = 0, j, j < fast_length, j++, {
1159 if (!elements->is_the_hole(j)) {
1160 double double_value = elements->get_scalar(j);
1161 Handle<Object> element_value =
1162 isolate->factory()->NewNumber(double_value);
1163 if (!visitor->visit(j, element_value)) return false;
1164 } else {
1165 Maybe<bool> maybe = JSReceiver::HasElement(isolate, array, j);
1166 if (maybe.IsNothing()) return false;
1167 if (maybe.FromJust()) {
1168 // Call GetElement on array, not its prototype, or getters won't
1169 // have the correct receiver.
1170 Handle<Object> element_value;
1171 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1172 isolate, element_value,
1173 JSReceiver::GetElement(isolate, array, j), false);
1174 if (!visitor->visit(j, element_value)) return false;
1175 }
1176 }
1177 });
1178 break;
1179 }
1180
1181 case DICTIONARY_ELEMENTS: {
1182 // Disallow execution so the cached dictionary won't change mid execution.
1183 DisallowJavascriptExecution no_js(isolate);
1184
1185 Handle<NumberDictionary> dict(array->element_dictionary(), isolate);
1186 std::vector<uint32_t> indices;
1187 indices.reserve(dict->Capacity() / 2);
1188
1189 // Collect all indices in the object and the prototypes less
1190 // than length. This might introduce duplicates in the indices list.
1191 CollectElementIndices(isolate, array, length, &indices);
1192 std::sort(indices.begin(), indices.end());
1193 size_t n = indices.size();
1194 FOR_WITH_HANDLE_SCOPE(isolate, size_t, j = 0, j, j < n, (void)0, {
1195 uint32_t index = indices[j];
1196 Handle<Object> element;
1197 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1198 isolate, element, JSReceiver::GetElement(isolate, array, index),
1199 false);
1200 if (!visitor->visit(index, element)) return false;
1201 // Skip to next different index (i.e., omit duplicates).
1202 do {
1203 j++;
1204 } while (j < n && indices[j] == index);
1205 });
1206 break;
1207 }
1208 case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
1209 case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: {
1210 FOR_WITH_HANDLE_SCOPE(
1211 isolate, uint32_t, index = 0, index, index < length, index++, {
1212 Handle<Object> element;
1213 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1214 isolate, element, JSReceiver::GetElement(isolate, array, index),
1215 false);
1216 if (!visitor->visit(index, element)) return false;
1217 });
1218 break;
1219 }
1220 case WASM_ARRAY_ELEMENTS:
1221 // TODO(ishell): implement
1222 UNIMPLEMENTED();
1223 case NO_ELEMENTS:
1224 break;
1225 #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
1226 TYPED_ARRAYS(TYPED_ARRAY_CASE)
1227 return IterateElementsSlow(isolate, receiver, length, visitor);
1228 RAB_GSAB_TYPED_ARRAYS(TYPED_ARRAY_CASE)
1229 // TODO(v8:11111): Support RAB / GSAB.
1230 UNREACHABLE();
1231 #undef TYPED_ARRAY_CASE
1232 case FAST_STRING_WRAPPER_ELEMENTS:
1233 case SLOW_STRING_WRAPPER_ELEMENTS:
1234 // |array| is guaranteed to be an array or typed array.
1235 UNREACHABLE();
1236 }
1237 visitor->increase_index_offset(length);
1238 return true;
1239 }
1240
IsConcatSpreadable(Isolate * isolate,Handle<Object> obj)1241 static Maybe<bool> IsConcatSpreadable(Isolate* isolate, Handle<Object> obj) {
1242 HandleScope handle_scope(isolate);
1243 if (!obj->IsJSReceiver()) return Just(false);
1244 if (!Protectors::IsIsConcatSpreadableLookupChainIntact(isolate) ||
1245 JSReceiver::cast(*obj).HasProxyInPrototype(isolate)) {
1246 // Slow path if @@isConcatSpreadable has been used.
1247 Handle<Symbol> key(isolate->factory()->is_concat_spreadable_symbol());
1248 Handle<Object> value;
1249 MaybeHandle<Object> maybeValue =
1250 i::Runtime::GetObjectProperty(isolate, obj, key);
1251 if (!maybeValue.ToHandle(&value)) return Nothing<bool>();
1252 if (!value->IsUndefined(isolate)) return Just(value->BooleanValue(isolate));
1253 }
1254 return Object::IsArray(obj);
1255 }
1256
Slow_ArrayConcat(BuiltinArguments * args,Handle<Object> species,Isolate * isolate)1257 Object Slow_ArrayConcat(BuiltinArguments* args, Handle<Object> species,
1258 Isolate* isolate) {
1259 int argument_count = args->length();
1260
1261 bool is_array_species = *species == isolate->context().array_function();
1262
1263 // Pass 1: estimate the length and number of elements of the result.
1264 // The actual length can be larger if any of the arguments have getters
1265 // that mutate other arguments (but will otherwise be precise).
1266 // The number of elements is precise if there are no inherited elements.
1267
1268 ElementsKind kind = PACKED_SMI_ELEMENTS;
1269
1270 uint32_t estimate_result_length = 0;
1271 uint32_t estimate_nof = 0;
1272 FOR_WITH_HANDLE_SCOPE(isolate, int, i = 0, i, i < argument_count, i++, {
1273 Handle<Object> obj = args->at(i);
1274 uint32_t length_estimate;
1275 uint32_t element_estimate;
1276 if (obj->IsJSArray()) {
1277 Handle<JSArray> array(Handle<JSArray>::cast(obj));
1278 length_estimate = static_cast<uint32_t>(array->length().Number());
1279 if (length_estimate != 0) {
1280 ElementsKind array_kind =
1281 GetPackedElementsKind(array->GetElementsKind());
1282 if (IsAnyNonextensibleElementsKind(array_kind)) {
1283 array_kind = PACKED_ELEMENTS;
1284 }
1285 kind = GetMoreGeneralElementsKind(kind, array_kind);
1286 }
1287 element_estimate = EstimateElementCount(isolate, array);
1288 } else {
1289 if (obj->IsHeapObject()) {
1290 kind = GetMoreGeneralElementsKind(
1291 kind, obj->IsNumber() ? PACKED_DOUBLE_ELEMENTS : PACKED_ELEMENTS);
1292 }
1293 length_estimate = 1;
1294 element_estimate = 1;
1295 }
1296 // Avoid overflows by capping at kMaxArrayLength.
1297 if (JSArray::kMaxArrayLength - estimate_result_length < length_estimate) {
1298 estimate_result_length = JSArray::kMaxArrayLength;
1299 } else {
1300 estimate_result_length += length_estimate;
1301 }
1302 if (JSArray::kMaxArrayLength - estimate_nof < element_estimate) {
1303 estimate_nof = JSArray::kMaxArrayLength;
1304 } else {
1305 estimate_nof += element_estimate;
1306 }
1307 });
1308
1309 // If estimated number of elements is more than half of length, a
1310 // fixed array (fast case) is more time and space-efficient than a
1311 // dictionary.
1312 bool fast_case = is_array_species &&
1313 (estimate_nof * 2) >= estimate_result_length &&
1314 Protectors::IsIsConcatSpreadableLookupChainIntact(isolate);
1315
1316 if (fast_case && kind == PACKED_DOUBLE_ELEMENTS) {
1317 Handle<FixedArrayBase> storage =
1318 isolate->factory()->NewFixedDoubleArray(estimate_result_length);
1319 int j = 0;
1320 bool failure = false;
1321 if (estimate_result_length > 0) {
1322 Handle<FixedDoubleArray> double_storage =
1323 Handle<FixedDoubleArray>::cast(storage);
1324 for (int i = 0; i < argument_count; i++) {
1325 Handle<Object> obj = args->at(i);
1326 if (obj->IsSmi()) {
1327 double_storage->set(j, Smi::ToInt(*obj));
1328 j++;
1329 } else if (obj->IsNumber()) {
1330 double_storage->set(j, obj->Number());
1331 j++;
1332 } else {
1333 DisallowGarbageCollection no_gc;
1334 JSArray array = JSArray::cast(*obj);
1335 uint32_t length = static_cast<uint32_t>(array.length().Number());
1336 switch (array.GetElementsKind()) {
1337 case HOLEY_DOUBLE_ELEMENTS:
1338 case PACKED_DOUBLE_ELEMENTS: {
1339 // Empty array is FixedArray but not FixedDoubleArray.
1340 if (length == 0) break;
1341 FixedDoubleArray elements =
1342 FixedDoubleArray::cast(array.elements());
1343 for (uint32_t k = 0; k < length; k++) {
1344 if (elements.is_the_hole(k)) {
1345 // TODO(jkummerow/verwaest): We could be a bit more clever
1346 // here: Check if there are no elements/getters on the
1347 // prototype chain, and if so, allow creation of a holey
1348 // result array.
1349 // Same thing below (holey smi case).
1350 failure = true;
1351 break;
1352 }
1353 double double_value = elements.get_scalar(k);
1354 double_storage->set(j, double_value);
1355 j++;
1356 }
1357 break;
1358 }
1359 case HOLEY_SMI_ELEMENTS:
1360 case PACKED_SMI_ELEMENTS: {
1361 Object the_hole = ReadOnlyRoots(isolate).the_hole_value();
1362 FixedArray elements(FixedArray::cast(array.elements()));
1363 for (uint32_t k = 0; k < length; k++) {
1364 Object element = elements.get(k);
1365 if (element == the_hole) {
1366 failure = true;
1367 break;
1368 }
1369 int32_t int_value = Smi::ToInt(element);
1370 double_storage->set(j, int_value);
1371 j++;
1372 }
1373 break;
1374 }
1375 case HOLEY_ELEMENTS:
1376 case HOLEY_FROZEN_ELEMENTS:
1377 case HOLEY_SEALED_ELEMENTS:
1378 case HOLEY_NONEXTENSIBLE_ELEMENTS:
1379 case PACKED_ELEMENTS:
1380 case PACKED_FROZEN_ELEMENTS:
1381 case PACKED_SEALED_ELEMENTS:
1382 case PACKED_NONEXTENSIBLE_ELEMENTS:
1383 case DICTIONARY_ELEMENTS:
1384 case NO_ELEMENTS:
1385 DCHECK_EQ(0u, length);
1386 break;
1387 default:
1388 UNREACHABLE();
1389 }
1390 }
1391 if (failure) break;
1392 }
1393 }
1394 if (!failure) {
1395 return *isolate->factory()->NewJSArrayWithElements(storage, kind, j);
1396 }
1397 // In case of failure, fall through.
1398 }
1399
1400 Handle<HeapObject> storage;
1401 if (fast_case) {
1402 // The backing storage array must have non-existing elements to preserve
1403 // holes across concat operations.
1404 storage =
1405 isolate->factory()->NewFixedArrayWithHoles(estimate_result_length);
1406 } else if (is_array_species) {
1407 storage = NumberDictionary::New(isolate, estimate_nof);
1408 } else {
1409 DCHECK(species->IsConstructor());
1410 Handle<Object> length(Smi::zero(), isolate);
1411 Handle<Object> storage_object;
1412 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1413 isolate, storage_object,
1414 Execution::New(isolate, species, species, 1, &length));
1415 storage = Handle<HeapObject>::cast(storage_object);
1416 }
1417
1418 ArrayConcatVisitor visitor(isolate, storage, fast_case);
1419
1420 for (int i = 0; i < argument_count; i++) {
1421 Handle<Object> obj = args->at(i);
1422 Maybe<bool> spreadable = IsConcatSpreadable(isolate, obj);
1423 MAYBE_RETURN(spreadable, ReadOnlyRoots(isolate).exception());
1424 if (spreadable.FromJust()) {
1425 Handle<JSReceiver> object = Handle<JSReceiver>::cast(obj);
1426 if (!IterateElements(isolate, object, &visitor)) {
1427 return ReadOnlyRoots(isolate).exception();
1428 }
1429 } else {
1430 if (!visitor.visit(0, obj)) return ReadOnlyRoots(isolate).exception();
1431 visitor.increase_index_offset(1);
1432 }
1433 }
1434
1435 if (visitor.exceeds_array_limit()) {
1436 THROW_NEW_ERROR_RETURN_FAILURE(
1437 isolate, NewRangeError(MessageTemplate::kInvalidArrayLength));
1438 }
1439
1440 if (is_array_species) {
1441 return *visitor.ToArray();
1442 } else {
1443 RETURN_RESULT_OR_FAILURE(isolate, visitor.ToJSReceiver());
1444 }
1445 }
1446
IsSimpleArray(Isolate * isolate,Handle<JSArray> obj)1447 bool IsSimpleArray(Isolate* isolate, Handle<JSArray> obj) {
1448 DisallowGarbageCollection no_gc;
1449 Map map = obj->map();
1450 // If there is only the 'length' property we are fine.
1451 if (map.prototype() == isolate->native_context()->initial_array_prototype() &&
1452 map.NumberOfOwnDescriptors() == 1) {
1453 return true;
1454 }
1455 // TODO(cbruni): slower lookup for array subclasses and support slow
1456 // @@IsConcatSpreadable lookup.
1457 return false;
1458 }
1459
Fast_ArrayConcat(Isolate * isolate,BuiltinArguments * args)1460 MaybeHandle<JSArray> Fast_ArrayConcat(Isolate* isolate,
1461 BuiltinArguments* args) {
1462 if (!Protectors::IsIsConcatSpreadableLookupChainIntact(isolate)) {
1463 return MaybeHandle<JSArray>();
1464 }
1465 // We shouldn't overflow when adding another len.
1466 const int kHalfOfMaxInt = 1 << (kBitsPerInt - 2);
1467 STATIC_ASSERT(FixedArray::kMaxLength < kHalfOfMaxInt);
1468 STATIC_ASSERT(FixedDoubleArray::kMaxLength < kHalfOfMaxInt);
1469 USE(kHalfOfMaxInt);
1470
1471 int n_arguments = args->length();
1472 int result_len = 0;
1473 {
1474 DisallowGarbageCollection no_gc;
1475 // Iterate through all the arguments performing checks
1476 // and calculating total length.
1477 for (int i = 0; i < n_arguments; i++) {
1478 Object arg = (*args)[i];
1479 if (!arg.IsJSArray()) return MaybeHandle<JSArray>();
1480 if (!HasOnlySimpleReceiverElements(isolate, JSObject::cast(arg))) {
1481 return MaybeHandle<JSArray>();
1482 }
1483 // TODO(cbruni): support fast concatenation of DICTIONARY_ELEMENTS.
1484 if (!JSObject::cast(arg).HasFastElements()) {
1485 return MaybeHandle<JSArray>();
1486 }
1487 Handle<JSArray> array(JSArray::cast(arg), isolate);
1488 if (!IsSimpleArray(isolate, array)) {
1489 return MaybeHandle<JSArray>();
1490 }
1491 // The Array length is guaranted to be <= kHalfOfMaxInt thus we won't
1492 // overflow.
1493 result_len += Smi::ToInt(array->length());
1494 DCHECK_GE(result_len, 0);
1495 // Throw an Error if we overflow the FixedArray limits
1496 if (FixedDoubleArray::kMaxLength < result_len ||
1497 FixedArray::kMaxLength < result_len) {
1498 AllowGarbageCollection gc;
1499 THROW_NEW_ERROR(isolate,
1500 NewRangeError(MessageTemplate::kInvalidArrayLength),
1501 JSArray);
1502 }
1503 }
1504 }
1505 return ElementsAccessor::Concat(isolate, args, n_arguments, result_len);
1506 }
1507
1508 } // namespace
1509
1510 // ES6 22.1.3.1 Array.prototype.concat
BUILTIN(ArrayConcat)1511 BUILTIN(ArrayConcat) {
1512 HandleScope scope(isolate);
1513
1514 Handle<Object> receiver = args.receiver();
1515 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1516 isolate, receiver,
1517 Object::ToObject(isolate, args.receiver(), "Array.prototype.concat"));
1518 args.set_at(0, *receiver);
1519
1520 Handle<JSArray> result_array;
1521
1522 // Avoid a real species read to avoid extra lookups to the array constructor
1523 if (V8_LIKELY(receiver->IsJSArray() &&
1524 Handle<JSArray>::cast(receiver)->HasArrayPrototype(isolate) &&
1525 Protectors::IsArraySpeciesLookupChainIntact(isolate))) {
1526 if (Fast_ArrayConcat(isolate, &args).ToHandle(&result_array)) {
1527 return *result_array;
1528 }
1529 if (isolate->has_pending_exception())
1530 return ReadOnlyRoots(isolate).exception();
1531 }
1532 // Reading @@species happens before anything else with a side effect, so
1533 // we can do it here to determine whether to take the fast path.
1534 Handle<Object> species;
1535 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1536 isolate, species, Object::ArraySpeciesConstructor(isolate, receiver));
1537 if (*species == *isolate->array_function()) {
1538 if (Fast_ArrayConcat(isolate, &args).ToHandle(&result_array)) {
1539 return *result_array;
1540 }
1541 if (isolate->has_pending_exception())
1542 return ReadOnlyRoots(isolate).exception();
1543 }
1544 return Slow_ArrayConcat(&args, species, isolate);
1545 }
1546
1547 namespace {
1548
1549 // https://tc39.es/proposal-array-grouping/#sec-add-value-to-keyed-group
1550 // Each keyed group is an array list.
AddValueToKeyedGroup(Isolate * isolate,Handle<OrderedHashMap> groups,Handle<Object> key,Handle<Object> value)1551 inline Handle<OrderedHashMap> AddValueToKeyedGroup(
1552 Isolate* isolate, Handle<OrderedHashMap> groups, Handle<Object> key,
1553 Handle<Object> value) {
1554 InternalIndex entry = groups->FindEntry(isolate, *key);
1555 if (!entry.is_found()) {
1556 Handle<ArrayList> array = ArrayList::New(isolate, 1);
1557 array = ArrayList::Add(isolate, array, value);
1558 return OrderedHashMap::Add(isolate, groups, key, array).ToHandleChecked();
1559 }
1560 Handle<ArrayList> array =
1561 Handle<ArrayList>(ArrayList::cast(groups->ValueAt(entry)), isolate);
1562 array = ArrayList::Add(isolate, array, value);
1563 groups->SetEntry(entry, *key, *array);
1564 return groups;
1565 }
1566
IsFastArray(Handle<JSReceiver> object)1567 inline bool IsFastArray(Handle<JSReceiver> object) {
1568 Isolate* isolate = object->GetIsolate();
1569 if (isolate->force_slow_path()) return false;
1570 if (!object->IsJSArray()) return false;
1571 Handle<JSArray> array = Handle<JSArray>::cast(object);
1572 if (!array->HasFastElements(isolate)) return false;
1573
1574 Context context = isolate->context();
1575 if (array->map().prototype() !=
1576 context.get(Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) {
1577 return false;
1578 }
1579
1580 return Protectors::IsNoElementsIntact(isolate);
1581 }
1582
CheckArrayMapNotModified(Handle<JSArray> array,Handle<Map> original_map)1583 inline bool CheckArrayMapNotModified(Handle<JSArray> array,
1584 Handle<Map> original_map) {
1585 if (array->map() != *original_map) {
1586 return false;
1587 }
1588 return Protectors::IsNoElementsIntact(array->GetIsolate());
1589 }
1590
1591 enum class GroupByMode { kToObject, kToMap };
1592
1593 template <GroupByMode mode>
GenericArrayGroupBy(Isolate * isolate,Handle<JSReceiver> O,Handle<Object> callbackfn,Handle<OrderedHashMap> groups,double initialK,double len)1594 inline MaybeHandle<OrderedHashMap> GenericArrayGroupBy(
1595 Isolate* isolate, Handle<JSReceiver> O, Handle<Object> callbackfn,
1596 Handle<OrderedHashMap> groups, double initialK, double len) {
1597 // 6. Repeat, while k < len
1598 for (double k = initialK; k < len; ++k) {
1599 // 6a. Let Pk be ! ToString((k)).
1600 Handle<Name> Pk;
1601 ASSIGN_RETURN_ON_EXCEPTION(
1602 isolate, Pk, Object::ToName(isolate, isolate->factory()->NewNumber(k)),
1603 OrderedHashMap);
1604 // 6b. Let kValue be ? Get(O, Pk).
1605 Handle<Object> kValue;
1606 ASSIGN_RETURN_ON_EXCEPTION(isolate, kValue,
1607 Object::GetPropertyOrElement(isolate, O, Pk),
1608 OrderedHashMap);
1609
1610 // Common steps for ArrayPrototypeGroupBy and ArrayPrototypeGroupByToMap
1611 // 6c. Let key be ? Call(callbackfn, thisArg, « kValue, (k), O »).
1612 Handle<Object> propertyKey;
1613 Handle<Object> argv[] = {kValue, isolate->factory()->NewNumber(k), O};
1614 ASSIGN_RETURN_ON_EXCEPTION(isolate, propertyKey,
1615 Execution::Call(isolate, callbackfn, O, 3, argv),
1616 OrderedHashMap);
1617
1618 if (mode == GroupByMode::kToMap) {
1619 // 6d. If key is -0, set key to +0.
1620 if (propertyKey->IsMinusZero()) {
1621 propertyKey = Handle<Smi>(Smi::FromInt(0), isolate);
1622 }
1623 } else {
1624 // 6c. Let propertyKey be ? ToPropertyKey(? Call(callbackfn, thisArg, «
1625 // kValue, (k), O »)).
1626 Handle<Name> propertyKeyName;
1627 ASSIGN_RETURN_ON_EXCEPTION(isolate, propertyKeyName,
1628 Object::ToName(isolate, propertyKey),
1629 OrderedHashMap);
1630 propertyKey = isolate->factory()->InternalizeName(propertyKeyName);
1631 }
1632
1633 // 6e. Perform ! AddValueToKeyedGroup(groups, propertyKey, kValue).
1634 groups = AddValueToKeyedGroup(isolate, groups, propertyKey, kValue);
1635
1636 // 6f. Set k to k + 1.
1637 // done by the loop.
1638 }
1639
1640 return groups;
1641 }
1642
1643 template <GroupByMode mode>
FastArrayGroupBy(Isolate * isolate,Handle<JSArray> array,Handle<Object> callbackfn,Handle<OrderedHashMap> groups,double len,ElementsKind * result_elements_kind)1644 inline MaybeHandle<OrderedHashMap> FastArrayGroupBy(
1645 Isolate* isolate, Handle<JSArray> array, Handle<Object> callbackfn,
1646 Handle<OrderedHashMap> groups, double len,
1647 ElementsKind* result_elements_kind) {
1648 DCHECK_NOT_NULL(result_elements_kind);
1649
1650 Handle<Map> original_map = Handle<Map>(array->map(), isolate);
1651 uint32_t uint_len = static_cast<uint32_t>(len);
1652 ElementsAccessor* accessor = array->GetElementsAccessor();
1653
1654 // 4. Let k be 0.
1655 // 6. Repeat, while k < len
1656 for (InternalIndex k : InternalIndex::Range(uint_len)) {
1657 if (!CheckArrayMapNotModified(array, original_map) ||
1658 k.as_uint32() >= static_cast<uint32_t>(array->length().Number())) {
1659 return GenericArrayGroupBy<mode>(isolate, array, callbackfn, groups,
1660 k.as_uint32(), len);
1661 }
1662 // 6a. Let Pk be ! ToString((k)).
1663 // 6b. Let kValue be ? Get(O, Pk).
1664 Handle<Object> kValue = accessor->Get(array, k);
1665 if (kValue->IsTheHole()) {
1666 kValue = isolate->factory()->undefined_value();
1667 }
1668
1669 // Common steps for ArrayPrototypeGroupBy and ArrayPrototypeGroupByToMap
1670 // 6c. Let key be ? Call(callbackfn, thisArg, « kValue, (k), O »).
1671 Handle<Object> propertyKey;
1672 Handle<Object> argv[] = {
1673 kValue, isolate->factory()->NewNumber(k.as_uint32()), array};
1674 ASSIGN_RETURN_ON_EXCEPTION(
1675 isolate, propertyKey,
1676 Execution::Call(isolate, callbackfn, array, 3, argv), OrderedHashMap);
1677
1678 if (mode == GroupByMode::kToMap) {
1679 // 6d. If key is -0, set key to +0.
1680 if (propertyKey->IsMinusZero()) {
1681 propertyKey = Handle<Smi>(Smi::FromInt(0), isolate);
1682 }
1683 } else {
1684 // 6c. Let propertyKey be ? ToPropertyKey(? Call(callbackfn, thisArg, «
1685 // kValue, (k), O »)).
1686 Handle<Name> propertyKeyName;
1687 ASSIGN_RETURN_ON_EXCEPTION(isolate, propertyKeyName,
1688 Object::ToName(isolate, propertyKey),
1689 OrderedHashMap);
1690 propertyKey = isolate->factory()->InternalizeName(propertyKeyName);
1691 }
1692
1693 // 6e. Perform ! AddValueToKeyedGroup(groups, propertyKey, kValue).
1694 groups = AddValueToKeyedGroup(isolate, groups, propertyKey, kValue);
1695
1696 // 6f. Set k to k + 1.
1697 // done by the loop.
1698 }
1699
1700 // When staying on the fast path, we can deduce a more specific results
1701 // ElementsKind for the keyed groups based on the input ElementsKind.
1702 //
1703 // Double elements are stored as HeapNumbers in the keyed group elements
1704 // so that we don't need to cast all the keyed groups when switching from
1705 // fast path to the generic path.
1706 // TODO(v8:12499) add unboxed double elements support
1707 if (array->GetElementsKind() == ElementsKind::PACKED_SMI_ELEMENTS) {
1708 *result_elements_kind = ElementsKind::PACKED_SMI_ELEMENTS;
1709 }
1710
1711 return groups;
1712 }
1713
1714 } // namespace
1715
1716 // https://tc39.es/proposal-array-grouping/#sec-array.prototype.groupby
BUILTIN(ArrayPrototypeGroupBy)1717 BUILTIN(ArrayPrototypeGroupBy) {
1718 const char* const kMethodName = "Array.prototype.groupBy";
1719 HandleScope scope(isolate);
1720
1721 Handle<JSReceiver> O;
1722 // 1. Let O be ? ToObject(this value).
1723 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1724 isolate, O, Object::ToObject(isolate, args.receiver(), kMethodName));
1725
1726 // 2. Let len be ? LengthOfArrayLike(O).
1727 double len;
1728 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len,
1729 GetLengthProperty(isolate, O));
1730
1731 // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
1732 Handle<Object> callbackfn = args.atOrUndefined(isolate, 1);
1733 if (!callbackfn->IsCallable()) {
1734 THROW_NEW_ERROR_RETURN_FAILURE(
1735 isolate, NewTypeError(MessageTemplate::kCalledNonCallable, callbackfn));
1736 }
1737
1738 // 5. Let groups be a new empty List.
1739 Handle<OrderedHashMap> groups = isolate->factory()->NewOrderedHashMap();
1740 ElementsKind result_elements_kind = ElementsKind::PACKED_ELEMENTS;
1741 if (IsFastArray(O)) {
1742 Handle<JSArray> array = Handle<JSArray>::cast(O);
1743 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1744 isolate, groups,
1745 FastArrayGroupBy<GroupByMode::kToObject>(
1746 isolate, array, callbackfn, groups, len, &result_elements_kind));
1747 } else {
1748 // 4. Let k be 0.
1749 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1750 isolate, groups,
1751 GenericArrayGroupBy<GroupByMode::kToObject>(isolate, O, callbackfn,
1752 groups, 0, len));
1753 }
1754
1755 // 7. Let obj be ! OrdinaryObjectCreate(null).
1756 Handle<JSObject> obj = isolate->factory()->NewJSObjectWithNullProto();
1757
1758 // 8. For each Record { [[Key]], [[Elements]] } g of groups, do
1759 for (InternalIndex entry : groups->IterateEntries()) {
1760 Handle<Name> key = Handle<Name>(Name::cast(groups->KeyAt(entry)), isolate);
1761 // 8a. Let elements be ! CreateArrayFromList(g.[[Elements]]).
1762 Handle<ArrayList> array_list =
1763 Handle<ArrayList>(ArrayList::cast(groups->ValueAt(entry)), isolate);
1764 Handle<FixedArray> elements = ArrayList::Elements(isolate, array_list);
1765 Handle<JSArray> array = isolate->factory()->NewJSArrayWithElements(
1766 elements, result_elements_kind, array_list->Length());
1767
1768 // 8b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
1769 JSReceiver::CreateDataProperty(isolate, obj, key, array,
1770 Just(kThrowOnError))
1771 .Check();
1772 }
1773
1774 // 9. Return obj.
1775 return *obj;
1776 }
1777
1778 // https://tc39.es/proposal-array-grouping/#sec-array.prototype.groupbymap
BUILTIN(ArrayPrototypeGroupByToMap)1779 BUILTIN(ArrayPrototypeGroupByToMap) {
1780 const char* const kMethodName = "Array.prototype.groupByToMap";
1781 HandleScope scope(isolate);
1782
1783 Handle<JSReceiver> O;
1784 // 1. Let O be ? ToObject(this value).
1785 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1786 isolate, O, Object::ToObject(isolate, args.receiver(), kMethodName));
1787
1788 // 2. Let len be ? LengthOfArrayLike(O).
1789 double len;
1790 MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len,
1791 GetLengthProperty(isolate, O));
1792
1793 // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
1794 Handle<Object> callbackfn = args.atOrUndefined(isolate, 1);
1795 if (!callbackfn->IsCallable()) {
1796 THROW_NEW_ERROR_RETURN_FAILURE(
1797 isolate, NewTypeError(MessageTemplate::kCalledNonCallable, callbackfn));
1798 }
1799
1800 // 5. Let groups be a new empty List.
1801 Handle<OrderedHashMap> groups = isolate->factory()->NewOrderedHashMap();
1802 ElementsKind result_elements_kind = ElementsKind::PACKED_ELEMENTS;
1803 if (IsFastArray(O)) {
1804 Handle<JSArray> array = Handle<JSArray>::cast(O);
1805 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1806 isolate, groups,
1807 FastArrayGroupBy<GroupByMode::kToMap>(
1808 isolate, array, callbackfn, groups, len, &result_elements_kind));
1809 } else {
1810 // 4. Let k be 0.
1811 ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
1812 isolate, groups,
1813 GenericArrayGroupBy<GroupByMode::kToMap>(isolate, O, callbackfn, groups,
1814 0, len));
1815 }
1816
1817 // 7. Let map be ! Construct(%Map%).
1818 Handle<JSMap> map = isolate->factory()->NewJSMap();
1819 Handle<OrderedHashMap> map_table = isolate->factory()->NewOrderedHashMap();
1820 // 8. For each Record { [[Key]], [[Elements]] } g of groups, do
1821 for (InternalIndex entry : groups->IterateEntries()) {
1822 Handle<Object> key = Handle<Object>(groups->KeyAt(entry), isolate);
1823 // 8a. Let elements be ! CreateArrayFromList(g.[[Elements]]).
1824 Handle<ArrayList> array_list =
1825 Handle<ArrayList>(ArrayList::cast(groups->ValueAt(entry)), isolate);
1826 Handle<FixedArray> elements = ArrayList::Elements(isolate, array_list);
1827 Handle<JSArray> array = isolate->factory()->NewJSArrayWithElements(
1828 elements, result_elements_kind, array_list->Length());
1829
1830 // 8b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }.
1831 // 8c. Append entry as the last element of map.[[MapData]].
1832 map_table =
1833 OrderedHashMap::Add(isolate, map_table, key, array).ToHandleChecked();
1834 }
1835 map->set_table(*map_table);
1836
1837 // 9. Return map.
1838 return *map;
1839 }
1840
1841 } // namespace internal
1842 } // namespace v8
1843