v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
builtins-array.cc
Go to the documentation of this file.
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"
10#include "src/debug/debug.h"
24#include "src/objects/lookup.h"
27#include "src/objects/smi.h"
28
29namespace v8 {
30namespace internal {
31
32namespace {
33
34inline bool IsJSArrayFastElementMovingAllowed(Isolate* isolate,
37}
38
39inline bool HasSimpleElements(Tagged<JSObject> current) {
40 return !IsCustomElementsReceiverMap(current->map()) &&
41 !current->GetElementsAccessor()->HasAccessors(current);
42}
43
44inline bool HasOnlySimpleReceiverElements(Isolate* isolate,
46 // Check that we have no accessors on the receiver's elements.
47 if (!HasSimpleElements(receiver)) return false;
49}
50
51inline bool HasOnlySimpleElements(Isolate* isolate,
54 PrototypeIterator iter(isolate, receiver, kStartAtReceiver);
55 for (; !iter.IsAtEnd(); iter.Advance()) {
56 if (!IsJSObject(iter.GetCurrent())) return false;
57 Tagged<JSObject> current = iter.GetCurrent<JSObject>();
58 if (!HasSimpleElements(current)) return false;
59 }
60 return true;
61}
62
63// This method may transition the elements kind of the JSArray once, to make
64// sure that all elements provided as arguments in the specified range can be
65// added without further elements kinds transitions.
66void MatchArrayElementsKindToArguments(Isolate* isolate,
67 DirectHandle<JSArray> array,
68 BuiltinArguments* args,
69 int first_arg_index, int num_arguments) {
70 int args_length = args->length();
71 if (first_arg_index >= args_length) return;
72
73 ElementsKind origin_kind = array->GetElementsKind();
74
75 // We do not need to transition for PACKED/HOLEY_ELEMENTS.
76 if (IsObjectElementsKind(origin_kind)) return;
77
78 ElementsKind target_kind = origin_kind;
79 {
81 int last_arg_index = std::min(first_arg_index + num_arguments, args_length);
82 for (int i = first_arg_index; i < last_arg_index; i++) {
83 Tagged<Object> arg = (*args)[i];
84 if (IsHeapObject(arg)) {
85 if (IsHeapNumber(arg)) {
86 target_kind = PACKED_DOUBLE_ELEMENTS;
87 } else {
88 target_kind = PACKED_ELEMENTS;
89 break;
90 }
91 }
92 }
93 }
94 if (target_kind != origin_kind) {
95 // Use a short-lived HandleScope to avoid creating several copies of the
96 // elements handle which would cause issues when left-trimming later-on.
97 HandleScope scope(isolate);
98 JSObject::TransitionElementsKind(array, target_kind);
99 }
100}
101
102// Checks if the receiver is a JSArray with fast elements that are mutable.
103// This is enough for deleting elements, but not enough for adding them (cf.
104// IsJSArrayWithAddableFastElements). Returns |false| if not applicable.
106inline bool IsJSArrayWithExtensibleFastElements(Isolate* isolate,
107 DirectHandle<Object> receiver,
108 DirectHandle<JSArray>* array) {
109 if (!TryCast<JSArray>(receiver, array)) return false;
110 ElementsKind origin_kind = (*array)->GetElementsKind();
111 if (IsFastElementsKind(origin_kind)) {
112 DCHECK(!IsDictionaryElementsKind(origin_kind));
113 DCHECK((*array)->map()->is_extensible());
114 return true;
115 }
116 return false;
117}
118
119// Checks if the receiver is a JSArray with fast elements which can add new
120// elements. Returns |false| if not applicable.
122inline bool IsJSArrayWithAddableFastElements(Isolate* isolate,
123 DirectHandle<Object> receiver,
124 DirectHandle<JSArray>* array) {
125 if (!IsJSArrayWithExtensibleFastElements(isolate, receiver, array))
126 return false;
127
128 // If there may be elements accessors in the prototype chain, the fast path
129 // cannot be used if there arguments to add to the array.
130 if (!IsJSArrayFastElementMovingAllowed(isolate, **array)) return false;
131
132 // Adding elements to the array prototype would break code that makes sure
133 // it has no elements. Handle that elsewhere.
134 if (isolate->IsInitialArrayPrototype(**array)) return false;
135
136 return true;
137}
138
139// If |index| is Undefined, returns init_if_undefined.
140// If |index| is negative, returns length + index.
141// If |index| is positive, returns index.
142// Returned value is guaranteed to be in the interval of [0, length].
143V8_WARN_UNUSED_RESULT Maybe<double> GetRelativeIndex(Isolate* isolate,
144 double length,
145 DirectHandle<Object> index,
146 double init_if_undefined) {
147 double relative_index = init_if_undefined;
148 if (!IsUndefined(*index)) {
149 MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, relative_index,
150 Object::IntegerValue(isolate, index),
152 }
153
154 if (relative_index < 0) {
155 return Just(std::max(length + relative_index, 0.0));
156 }
157
158 return Just(std::min(relative_index, length));
159}
160
161// Returns "length", has "fast-path" for JSArrays.
162V8_WARN_UNUSED_RESULT Maybe<double> GetLengthProperty(
163 Isolate* isolate, DirectHandle<JSReceiver> receiver) {
164 if (IsJSArray(*receiver)) {
165 auto array = Cast<JSArray>(receiver);
166 double length = Object::NumberValue(array->length());
167 DCHECK(0 <= length && length <= kMaxSafeInteger);
168
169 return Just(length);
170 }
171
172 DirectHandle<Object> raw_length_number;
174 isolate, raw_length_number,
176 return Just(Object::NumberValue(*raw_length_number));
177}
178
179// Set "length" property, has "fast-path" for JSArrays.
180// Returns Nothing if something went wrong.
181V8_WARN_UNUSED_RESULT MaybeDirectHandle<Object> SetLengthProperty(
182 Isolate* isolate, DirectHandle<JSReceiver> receiver, double length) {
183 if (IsJSArray(*receiver)) {
184 DirectHandle<JSArray> array = Cast<JSArray>(receiver);
185 if (!JSArray::HasReadOnlyLength(array)) {
186 DCHECK_LE(length, kMaxUInt32);
188 JSArray::SetLength(array, static_cast<uint32_t>(length)));
189 return receiver;
190 }
191 }
192
193 return Object::SetProperty(
194 isolate, receiver, isolate->factory()->length_string(),
195 isolate->factory()->NewNumber(length), StoreOrigin::kMaybeKeyed,
197}
198
200 Isolate* isolate, DirectHandle<JSReceiver> receiver,
201 DirectHandle<Object> value, double start, double end) {
202 // 7. Repeat, while k < final.
203 FOR_WITH_HANDLE_SCOPE(isolate, double, k = start, k, k < end, k++, {
204 // a. Let Pk be ! ToString(k).
205 PropertyKey key(isolate, k);
206
207 // b. Perform ? Set(O, Pk, value, true).
209 isolate,
213 ReadOnlyRoots(isolate).exception());
214
215 // c. Increase k by 1.
216 });
217
218 // 8. Return O.
219 return *receiver;
220}
221
222// Get a map which replaces the elements kind of an array. Unlike
223// `JSObject::GetElementsTransitionMap`, this is allowed to go backwards in the
224// ElementsKinds manifold, and may return a map which doesn't match the current
225// elements array. The caller must handle the elements array, potentially
226// reallocating if the FixedArray type doesn't match, and fill a valid value.
227V8_WARN_UNUSED_RESULT MaybeDirectHandle<Map> GetReplacedElementsKindsMap(
228 Isolate* isolate, DirectHandle<JSArray> array, ElementsKind origin_kind,
229 ElementsKind target_kind) {
230 Tagged<Map> map = array->map();
231
232 // Fast check for JSArrays without properties.
233 {
235 Tagged<Context> native_context = map->map()->native_context();
236 if (native_context->GetInitialJSArrayMap(origin_kind) == map) {
237 Tagged<Object> maybe_target_map =
238 native_context->get(Context::ArrayMapIndex(target_kind));
239 if (Tagged<Map> target_map; TryCast<Map>(maybe_target_map, &target_map)) {
240 map->NotifyLeafMapLayoutChange(isolate);
241 return direct_handle(target_map, isolate);
242 }
243 }
244 }
245
246 // Ideally here we would reconfigure the map to an earlier ElementsKind with:
247 // ```
248 // MapUpdater{isolate, direct_handle(map, isolate)}
249 // .ReconfigureElementsKind(target_kind);
250 // ```
251 // However, this currently treats this as an invalid ElementsKind transition,
252 // and normalizes the map.
253 // TODO(leszeks): Handle this case.
254 return {};
255}
256
257V8_WARN_UNUSED_RESULT bool TryFastArrayFill(
258 Isolate* isolate, BuiltinArguments* args, DirectHandle<JSReceiver> receiver,
259 DirectHandle<Object> value, double start_index, double end_index) {
260 // If indices are too large, use generic path since they are stored as
261 // properties, not in the element backing store.
262 if (end_index > kMaxUInt32) return false;
263 if (!IsJSObject(*receiver)) return false;
264
265 DirectHandle<JSArray> array;
266 if (!IsJSArrayWithAddableFastElements(isolate, receiver, &array)) {
267 return false;
268 }
269
270 DCHECK_LE(start_index, kMaxUInt32);
271 DCHECK_LE(end_index, kMaxUInt32);
272 DCHECK_LT(start_index, end_index);
273
274 uint32_t start, end;
277
278 // The end index should be truncated to the array length in the argument
279 // resolution part of Array.p.fill, which means it should be within the
280 // FixedArray max length since we know the array has fast elements (and that
281 // both FixedArray and FixedDoubleArray have the same max length).
282 //
283 // However, it _can_ be larger than the _current_ array length, because
284 // of side-effects in Array.p.fill argument resolution (`ToInteger` on `start`
285 // and `end`, see: https://tc39.es/ecma262/#sec-array.prototype.fill). In
286 // particular, those could have transitioned a dictionary-elements array back
287 // to fast elements, by adding enough data element properties to make it dense
288 // (an easy way to do this is another call to Array.p.fill).
290 if (end > FixedArray::kMaxLength) return false;
291
292 // Need to ensure that the fill value can be contained in the array.
293 ElementsKind origin_kind = array->GetElementsKind();
294 ElementsKind target_kind = Object::OptimalElementsKind(*value, isolate);
295
296 if (target_kind != origin_kind) {
297 // Use a short-lived HandleScope to avoid creating several copies of the
298 // elements handle which would cause issues when left-trimming later-on.
299 HandleScope scope(isolate);
300
301 bool is_replacing_all_elements =
302 (start == 0 && end == Object::NumberValue(array->length()));
303 bool did_transition_map = false;
304 if (is_replacing_all_elements) {
305 // For the case where we are replacing all elements, we can migrate the
306 // map backwards in the elements kind chain and ignore the current
307 // contents of the elements array.
308 DirectHandle<Map> new_map;
309
310 if (GetReplacedElementsKindsMap(isolate, array, origin_kind, target_kind)
311 .ToHandle(&new_map)) {
312 DirectHandle<FixedArrayBase> elements(array->elements(), isolate);
313 if (IsDoubleElementsKind(origin_kind) !=
314 IsDoubleElementsKind(target_kind)) {
315 // Reallocate the elements if doubleness doesn't match.
316 if (IsDoubleElementsKind(target_kind)) {
317 elements = isolate->factory()->NewFixedDoubleArray(end);
318 } else {
319 elements = isolate->factory()->NewFixedArrayWithZeroes(end);
320 }
321 }
322 JSObject::SetMapAndElements(array, new_map, elements);
323 if (IsMoreGeneralElementsKindTransition(origin_kind, target_kind)) {
324 // Transition through the allocation site as well if present, but
325 // only if this is a forward transition.
326 JSObject::UpdateAllocationSite(array, target_kind);
327 }
328 did_transition_map = true;
329 }
330 }
331
332 if (!did_transition_map) {
333 target_kind = GetMoreGeneralElementsKind(origin_kind, target_kind);
334 JSObject::TransitionElementsKind(array, target_kind);
335 }
336 }
337
338 ElementsAccessor* accessor = array->GetElementsAccessor();
339 accessor->Fill(array, value, start, end).Check();
340
341 // It's possible the JSArray's 'length' property was assigned to after the
342 // length was loaded due to user code during argument coercion of the start
343 // and end parameters. The spec algorithm does a Set, meaning the length would
344 // grow as needed during the fill.
345 //
346 // ElementAccessor::Fill is able to grow the backing store as needed, but we
347 // need to ensure the JSArray's length is correctly set in case the user
348 // assigned a smaller value.
349 if (Object::NumberValue(array->length()) < end) {
350 CHECK(accessor->SetLength(array, end).FromJust());
351 }
352
353 return true;
354}
355} // namespace
356
357BUILTIN(ArrayPrototypeFill) {
358 HandleScope scope(isolate);
359
360 // 1. Let O be ? ToObject(this value).
363 isolate, receiver, Object::ToObject(isolate, args.receiver()));
364
365 // 2. Let len be ? ToLength(? Get(O, "length")).
366 double length;
368 isolate, length, GetLengthProperty(isolate, receiver));
369
370 // 3. Let relativeStart be ? ToInteger(start).
371 // 4. If relativeStart < 0, let k be max((len + relativeStart), 0);
372 // else let k be min(relativeStart, len).
373 DirectHandle<Object> start = args.atOrUndefined(isolate, 2);
374
375 double start_index;
377 isolate, start_index, GetRelativeIndex(isolate, length, start, 0));
378
379 // 5. If end is undefined, let relativeEnd be len;
380 // else let relativeEnd be ? ToInteger(end).
381 // 6. If relativeEnd < 0, let final be max((len + relativeEnd), 0);
382 // else let final be min(relativeEnd, len).
383 DirectHandle<Object> end = args.atOrUndefined(isolate, 3);
384
385 double end_index;
387 isolate, end_index, GetRelativeIndex(isolate, length, end, length));
388
389 if (start_index >= end_index) return *receiver;
390
391 // Ensure indexes are within array bounds
392 DCHECK_LE(0, start_index);
393 DCHECK_LE(start_index, end_index);
394 DCHECK_LE(end_index, length);
395
396 DirectHandle<Object> value = args.atOrUndefined(isolate, 1);
397
398 if (TryFastArrayFill(isolate, &args, receiver, value, start_index,
399 end_index)) {
400 return *receiver;
401 }
402 return GenericArrayFill(isolate, receiver, value, start_index, end_index);
403}
404
405namespace {
406V8_WARN_UNUSED_RESULT Tagged<Object> GenericArrayPush(Isolate* isolate,
407 BuiltinArguments* args) {
408 // 1. Let O be ? ToObject(this value).
409 DirectHandle<JSReceiver> receiver;
411 isolate, receiver, Object::ToObject(isolate, args->receiver()));
412
413 // 2. Let len be ? ToLength(? Get(O, "length")).
414 DirectHandle<Object> raw_length_number;
416 isolate, raw_length_number,
418
419 // 3. Let args be a List whose elements are, in left to right order,
420 // the arguments that were passed to this function invocation.
421 // 4. Let arg_count be the number of elements in args.
422 int arg_count = args->length() - 1;
423
424 // 5. If len + arg_count > 2^53-1, throw a TypeError exception.
425 double length = Object::NumberValue(*raw_length_number);
426 if (arg_count > kMaxSafeInteger - length) {
428 isolate, NewTypeError(MessageTemplate::kPushPastSafeLength,
429 isolate->factory()->NewNumberFromInt(arg_count),
430 raw_length_number));
431 }
432
433 // 6. Repeat, while args is not empty.
434 for (int i = 0; i < arg_count; ++i) {
435 // a. Remove the first element from args and let E be the value of the
436 // element.
437 DirectHandle<Object> element = args->at(i + 1);
438
439 // b. Perform ? Set(O, ! ToString(len), E, true).
440 if (length <= JSObject::kMaxElementIndex) {
442 isolate, Object::SetElement(isolate, receiver, length, element,
444 } else {
445 PropertyKey key(isolate, length);
446 LookupIterator it(isolate, receiver, key);
449 ReadOnlyRoots(isolate).exception());
450 }
451
452 // c. Let len be len+1.
453 ++length;
454 }
455
456 // 7. Perform ? Set(O, "length", len, true).
457 DirectHandle<Object> final_length = isolate->factory()->NewNumber(length);
459 isolate, Object::SetProperty(isolate, receiver,
460 isolate->factory()->length_string(),
461 final_length, StoreOrigin::kMaybeKeyed,
463
464 // 8. Return len.
465 return *final_length;
466}
467} // namespace
468
469BUILTIN(ArrayPush) {
470 HandleScope scope(isolate);
473 if (!IsJSArrayWithAddableFastElements(isolate, receiver, &array)) {
474 return GenericArrayPush(isolate, &args);
475 }
476
477 bool has_read_only_length = JSArray::HasReadOnlyLength(array);
478 if (has_read_only_length) {
479 return GenericArrayPush(isolate, &args);
480 }
481
482 // Fast Elements Path
483 int to_add = args.length() - 1;
484 if (to_add == 0) {
485 uint32_t len = static_cast<uint32_t>(Object::NumberValue(array->length()));
486 return *isolate->factory()->NewNumberFromUint(len);
487 }
488
489 // Currently fixed arrays cannot grow too big, so we should never hit this.
490 DCHECK_LE(to_add, Smi::kMaxValue - Smi::ToInt(array->length()));
491
492 // Need to ensure that the values to be pushed can be contained in the array.
493 MatchArrayElementsKindToArguments(isolate, array, &args, 1,
494 args.length() - 1);
495
496 ElementsAccessor* accessor = array->GetElementsAccessor();
497 uint32_t new_length;
499 isolate, new_length, accessor->Push(array, &args, to_add));
500 return *isolate->factory()->NewNumberFromUint((new_length));
501}
502
503namespace {
504
505V8_WARN_UNUSED_RESULT Tagged<Object> GenericArrayPop(Isolate* isolate,
506 BuiltinArguments* args) {
507 // 1. Let O be ? ToObject(this value).
508 DirectHandle<JSReceiver> receiver;
510 isolate, receiver, Object::ToObject(isolate, args->receiver()));
511
512 // 2. Let len be ? ToLength(? Get(O, "length")).
513 DirectHandle<Object> raw_length_number;
515 isolate, raw_length_number,
517 double length = Object::NumberValue(*raw_length_number);
518
519 // 3. If len is zero, then.
520 if (length == 0) {
521 // a. Perform ? Set(O, "length", 0, true).
523 isolate, Object::SetProperty(isolate, receiver,
524 isolate->factory()->length_string(),
525 direct_handle(Smi::zero(), isolate),
528
529 // b. Return undefined.
530 return ReadOnlyRoots(isolate).undefined_value();
531 }
532
533 // 4. Else len > 0.
534 // a. Let new_len be len-1.
535 DirectHandle<Object> new_length = isolate->factory()->NewNumber(length - 1);
536
537 // b. Let index be ! ToString(newLen).
538 PropertyKey index(isolate, new_length);
539
540 // c. Let element be ? Get(O, index).
541 DirectHandle<Object> element;
543 isolate, element, Object::GetPropertyOrElement(isolate, receiver, index));
544
545 // d. Perform ? DeletePropertyOrThrow(O, index).
548 ReadOnlyRoots(isolate).exception());
549
550 // e. Perform ? Set(O, "length", newLen, true).
552 isolate, Object::SetProperty(isolate, receiver,
553 isolate->factory()->length_string(),
556
557 // f. Return element.
558 return *element;
559}
560
561} // namespace
562
563BUILTIN(ArrayPop) {
564 HandleScope scope(isolate);
567 if (!IsJSArrayWithExtensibleFastElements(isolate, receiver, &array)) {
568 return GenericArrayPop(isolate, &args);
569 }
570
571 uint32_t len = static_cast<uint32_t>(Object::NumberValue(array->length()));
572
573 if (JSArray::HasReadOnlyLength(array)) {
574 return GenericArrayPop(isolate, &args);
575 }
576 if (len == 0) return ReadOnlyRoots(isolate).undefined_value();
577
579 if (IsJSArrayFastElementMovingAllowed(isolate, *array)) {
580 // Fast Elements Path
582 isolate, result, array->GetElementsAccessor()->Pop(array));
583 } else {
584 // Use Slow Lookup otherwise
585 uint32_t new_length = len - 1;
587 isolate, result, JSReceiver::GetElement(isolate, array, new_length));
588
589 // The length could have become read-only during the last GetElement() call,
590 // so check again.
591 if (JSArray::HasReadOnlyLength(array)) {
593 isolate, NewTypeError(MessageTemplate::kStrictReadOnlyProperty,
594 isolate->factory()->length_string(),
595 Object::TypeOf(isolate, array), array));
596 }
597 bool set_len_ok;
599 isolate, set_len_ok, JSArray::SetLength(array, new_length));
600 }
601
602 return *result;
603}
604
605namespace {
606
607// Returns true, iff we can use ElementsAccessor for shifting.
608V8_WARN_UNUSED_RESULT bool CanUseFastArrayShift(
609 Isolate* isolate, DirectHandle<JSReceiver> receiver) {
610 if (V8_COMPRESS_POINTERS_8GB_BOOL) return false;
611
612 DirectHandle<JSArray> array;
613 if (!IsJSArrayWithExtensibleFastElements(isolate, receiver, &array)) {
614 return false;
615 }
616 if (!IsJSArrayFastElementMovingAllowed(isolate, *array)) {
617 return false;
618 }
619 return !JSArray::HasReadOnlyLength(array);
620}
621
622V8_WARN_UNUSED_RESULT Tagged<Object> GenericArrayShift(
623 Isolate* isolate, DirectHandle<JSReceiver> receiver, double length) {
624 // 4. Let first be ? Get(O, "0").
625 DirectHandle<Object> first;
627 Object::GetElement(isolate, receiver, 0));
628
629 // 5. Let k be 1.
630 // 6. Repeat, while k < len.
631 FOR_WITH_HANDLE_SCOPE(isolate, double, k = 1, k, k < length, k++, {
632 // a. Let from be ! ToString(k).
633 PropertyKey from(isolate, k);
634
635 // b. Let to be ! ToString(k-1).
636 PropertyKey to(isolate, k - 1);
637
638 // c. Let fromPresent be ? HasProperty(O, from).
639 bool from_present;
641 isolate, from_present,
643
644 // d. If fromPresent is true, then.
645 if (from_present) {
646 // i. Let fromVal be ? Get(O, from).
647 DirectHandle<Object> from_val;
649 isolate, from_val,
650 Object::GetPropertyOrElement(isolate, receiver, from));
651
652 // ii. Perform ? Set(O, to, fromVal, true).
654 isolate, receiver, to, from_val,
657 } else { // e. Else fromPresent is false,
658 // i. Perform ? DeletePropertyOrThrow(O, to).
661 ReadOnlyRoots(isolate).exception());
662 }
663
664 // f. Increase k by 1.
665 });
666
667 // 7. Perform ? DeletePropertyOrThrow(O, ! ToString(len-1)).
668 PropertyKey new_length_key(isolate, length - 1);
670 isolate, receiver, new_length_key, LanguageMode::kStrict),
671 ReadOnlyRoots(isolate).exception());
672
673 // 8. Perform ? Set(O, "length", len-1, true).
675 SetLengthProperty(isolate, receiver, length - 1));
676
677 // 9. Return first.
678 return *first;
679}
680} // namespace
681
682BUILTIN(ArrayShift) {
683 HandleScope scope(isolate);
684
685 // 1. Let O be ? ToObject(this value).
688 isolate, receiver, Object::ToObject(isolate, args.receiver()));
689
690 // 2. Let len be ? ToLength(? Get(O, "length")).
691 double length;
693 isolate, length, GetLengthProperty(isolate, receiver));
694
695 // 3. If len is zero, then.
696 if (length == 0) {
697 // a. Perform ? Set(O, "length", 0, true).
699 SetLengthProperty(isolate, receiver, length));
700
701 // b. Return undefined.
702 return ReadOnlyRoots(isolate).undefined_value();
703 }
704
705 if (CanUseFastArrayShift(isolate, receiver)) {
708 array->GetElementsAccessor()->Shift(array));
709 }
710
711 return GenericArrayShift(isolate, receiver, length);
712}
713
714BUILTIN(ArrayUnshift) {
715 HandleScope scope(isolate);
716 DCHECK(IsJSArray(*args.receiver()));
717 DirectHandle<JSArray> array = Cast<JSArray>(args.receiver());
718
719 // These are checked in the Torque builtin.
720 DCHECK(array->map()->is_extensible());
721 DCHECK(!IsDictionaryElementsKind(array->GetElementsKind()));
722 DCHECK(IsJSArrayFastElementMovingAllowed(isolate, *array));
723 DCHECK(!isolate->IsInitialArrayPrototype(*array));
724
725 MatchArrayElementsKindToArguments(isolate, array, &args, 1,
726 args.length() - 1);
727
728 int to_add = args.length() - 1;
729 if (to_add == 0) return array->length();
730
731 // Currently fixed arrays cannot grow too big, so we should never hit this.
732 DCHECK_LE(to_add, Smi::kMaxValue - Smi::ToInt(array->length()));
734
735 ElementsAccessor* accessor = array->GetElementsAccessor();
736 uint32_t new_length;
738 isolate, new_length, accessor->Unshift(array, &args, to_add));
739 return Smi::FromInt(new_length);
740}
741
742// Array Concat -------------------------------------------------------------
743
744namespace {
745
757class ArrayConcatVisitor {
758 public:
759 ArrayConcatVisitor(
760 Isolate* isolate,
762 bool fast_elements)
763 : isolate_(isolate),
764 storage_(isolate->global_handles()->Create(*storage)),
765 index_offset_(0u),
766 bit_field_(FastElementsField::encode(fast_elements) |
767 ExceedsLimitField::encode(false) |
768 IsFixedArrayField::encode(IsFixedArray(*storage, isolate)) |
769 HasSimpleElementsField::encode(
770 IsFixedArray(*storage, isolate) ||
771 // Don't take fast path for storages that might have
772 // side effects when storing to them.
773 (!IsCustomElementsReceiverMap(storage->map(isolate)) &&
774 !IsJSTypedArray(*storage, isolate)))) {
775 DCHECK_IMPLIES(this->fast_elements(), is_fixed_array());
776 }
777
778 ~ArrayConcatVisitor() { clear_storage(); }
779
780 V8_WARN_UNUSED_RESULT bool visit(uint32_t i, DirectHandle<Object> elm) {
781 uint32_t index = index_offset_ + i;
782
783 // Note we use >=kMaxArrayLength instead of the more appropriate
784 // >kMaxArrayIndex here due to overflowing arithmetic and
785 // increase_index_offset.
786 if (i >= JSArray::kMaxArrayLength - index_offset_) {
787 set_exceeds_array_limit(true);
788 // Exception hasn't been thrown at this point. Return true to
789 // break out, and caller will throw. !visit would imply that
790 // there is already an exception.
791 return true;
792 }
793
794 if (!is_fixed_array()) {
796 isolate_, direct_handle(Cast<JSReceiver>(storage_)),
797 PropertyKey(isolate_, index), elm, Just(kThrowOnError)),
798 false);
799 return true;
800 }
801
802 if (fast_elements()) {
803 if (index < static_cast<uint32_t>(storage_fixed_array()->length())) {
804 storage_fixed_array()->set(index, *elm);
805 return true;
806 }
807 // Our initial estimate of length was foiled, possibly by
808 // getters on the arrays increasing the length of later arrays
809 // during iteration.
810 // This shouldn't happen in anything but pathological cases.
811 SetDictionaryMode();
812 // Fall-through to dictionary mode.
813 }
814 DCHECK(!fast_elements());
815 DirectHandle<NumberDictionary> dict(Cast<NumberDictionary>(*storage_),
816 isolate_);
817 // The object holding this backing store has just been allocated, so
818 // it cannot yet be used as a prototype.
819 DirectHandle<JSObject> not_a_prototype_holder;
820 DirectHandle<NumberDictionary> result = NumberDictionary::Set(
821 isolate_, dict, index, elm, not_a_prototype_holder);
822 if (!result.is_identical_to(dict)) {
823 // Dictionary needed to grow.
824 clear_storage();
825 set_storage(*result);
826 }
827 return true;
828 }
829
830 uint32_t index_offset() const { return index_offset_; }
831
832 void increase_index_offset(uint32_t delta) {
833 if (JSArray::kMaxArrayLength - index_offset_ < delta) {
835 } else {
836 index_offset_ += delta;
837 }
838 // If the initial length estimate was off (see special case in visit()),
839 // but the array blowing the limit didn't contain elements beyond the
840 // provided-for index range, go to dictionary mode now.
841 if (fast_elements() &&
842 index_offset_ >
843 static_cast<uint32_t>(Cast<FixedArrayBase>(*storage_)->length())) {
844 SetDictionaryMode();
845 }
846 }
847
848 bool exceeds_array_limit() const {
849 return ExceedsLimitField::decode(bit_field_);
850 }
851
852 DirectHandle<JSArray> ToArray() {
853 DCHECK(is_fixed_array());
854 DirectHandle<JSArray> array = isolate_->factory()->NewJSArray(0);
855 DirectHandle<Number> length =
856 isolate_->factory()->NewNumber(static_cast<double>(index_offset_));
857 DirectHandle<Map> map = JSObject::GetElementsTransitionMap(
858 array, fast_elements() ? HOLEY_ELEMENTS : DICTIONARY_ELEMENTS);
859 {
861 Tagged<JSArray> raw = *array;
862 raw->set_length(*length);
863 raw->set_elements(*storage_fixed_array());
864 raw->set_map(isolate_, *map, kReleaseStore);
865 }
866 return array;
867 }
868
869 V8_WARN_UNUSED_RESULT MaybeDirectHandle<JSReceiver> ToJSReceiver() {
870 DCHECK(!is_fixed_array());
871 DirectHandle<JSReceiver> result = Cast<JSReceiver>(storage_);
872 DirectHandle<Object> length =
873 isolate_->factory()->NewNumber(static_cast<double>(index_offset_));
875 isolate_, Object::SetProperty(isolate_, result,
876 isolate_->factory()->length_string(),
879 return result;
880 }
881 bool has_simple_elements() const {
882 return HasSimpleElementsField::decode(bit_field_);
883 }
884
885 private:
886 // Convert storage to dictionary mode.
887 void SetDictionaryMode() {
888 DCHECK(fast_elements() && is_fixed_array());
889 DirectHandle<FixedArray> current_storage = storage_fixed_array();
890 DirectHandle<NumberDictionary> slow_storage(
891 NumberDictionary::New(isolate_, current_storage->length()));
892 uint32_t current_length = static_cast<uint32_t>(current_storage->length());
894 isolate_, uint32_t, i = 0, i, i < current_length, i++, {
895 DirectHandle<Object> element(current_storage->get(i), isolate_);
896 if (!IsTheHole(*element, isolate_)) {
897 // The object holding this backing store has just been allocated, so
898 // it cannot yet be used as a prototype.
899 DirectHandle<JSObject> not_a_prototype_holder;
900 DirectHandle<NumberDictionary> new_storage = NumberDictionary::Set(
901 isolate_, slow_storage, i, element, not_a_prototype_holder);
902 if (!new_storage.is_identical_to(slow_storage)) {
903 slow_storage = loop_scope.CloseAndEscape(new_storage);
904 }
905 }
906 });
907 clear_storage();
908 set_storage(*slow_storage);
909 set_fast_elements(false);
910 }
911
912 inline void clear_storage() { GlobalHandles::Destroy(storage_.location()); }
913
914 inline void set_storage(Tagged<FixedArray> storage) {
915 DCHECK(is_fixed_array());
916 DCHECK(has_simple_elements());
917 storage_ = isolate_->global_handles()->Create(storage);
918 }
919
920 using FastElementsField = base::BitField<bool, 0, 1>;
921 using ExceedsLimitField = base::BitField<bool, 1, 1>;
922 using IsFixedArrayField = base::BitField<bool, 2, 1>;
923 using HasSimpleElementsField = base::BitField<bool, 3, 1>;
924
925 bool fast_elements() const { return FastElementsField::decode(bit_field_); }
926 void set_fast_elements(bool fast) {
927 bit_field_ = FastElementsField::update(bit_field_, fast);
928 }
929 void set_exceeds_array_limit(bool exceeds) {
930 bit_field_ = ExceedsLimitField::update(bit_field_, exceeds);
931 }
932 bool is_fixed_array() const { return IsFixedArrayField::decode(bit_field_); }
933 DirectHandle<FixedArray> storage_fixed_array() {
934 DCHECK(is_fixed_array());
935 DCHECK(has_simple_elements());
936 return Cast<FixedArray>(storage_);
937 }
938
939 Isolate* isolate_;
941 storage_; // Always a global handle.
942 // Index after last seen index. Always less than or equal to
943 // JSArray::kMaxArrayLength.
945 uint32_t bit_field_;
946};
947
948uint32_t EstimateElementCount(Isolate* isolate, DirectHandle<JSArray> array) {
950 uint32_t length = static_cast<uint32_t>(Object::NumberValue(array->length()));
951 int element_count = 0;
952 switch (array->GetElementsKind()) {
955 case PACKED_ELEMENTS:
962 case HOLEY_ELEMENTS: {
963 // Fast elements can't have lengths that are not representable by
964 // a 32-bit signed integer.
965 static_assert(static_cast<int32_t>(FixedArray::kMaxLength) >= 0);
966 int fast_length = static_cast<int>(length);
967 Tagged<FixedArray> elements = Cast<FixedArray>(array->elements());
968 for (int i = 0; i < fast_length; i++) {
969 if (!IsTheHole(elements->get(i), isolate)) element_count++;
970 }
971 break;
972 }
975 // Fast elements can't have lengths that are not representable by
976 // a 32-bit signed integer.
977 static_assert(static_cast<int32_t>(FixedDoubleArray::kMaxLength) >= 0);
978 int fast_length = static_cast<int>(length);
979 if (IsFixedArray(array->elements())) {
980 DCHECK_EQ(Cast<FixedArray>(array->elements())->length(), 0);
981 break;
982 }
983 Tagged<FixedDoubleArray> elements =
984 Cast<FixedDoubleArray>(array->elements());
985 for (int i = 0; i < fast_length; i++) {
986 if (!elements->is_the_hole(i)) element_count++;
987 }
988 break;
989 }
990 case DICTIONARY_ELEMENTS: {
991 Tagged<NumberDictionary> dictionary =
992 Cast<NumberDictionary>(array->elements());
993 ReadOnlyRoots roots(isolate);
994 for (InternalIndex i : dictionary->IterateEntries()) {
995 Tagged<Object> key = dictionary->KeyAt(i);
996 if (dictionary->IsKey(roots, key)) {
997 element_count++;
998 }
999 }
1000 break;
1001 }
1002#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
1003
1006 // External arrays are always dense.
1007 return length;
1008
1009#undef TYPED_ARRAY_CASE
1010 case NO_ELEMENTS:
1011 return 0;
1018 UNREACHABLE();
1019 }
1020 // As an estimate, we assume that the prototype doesn't contain any
1021 // inherited elements.
1022 return element_count;
1023}
1024
1025void CollectElementIndices(Isolate* isolate, DirectHandle<JSObject> object,
1026 uint32_t range, std::vector<uint32_t>* indices) {
1027 ElementsKind kind = object->GetElementsKind();
1028 switch (kind) {
1030 case PACKED_ELEMENTS:
1034 case HOLEY_SMI_ELEMENTS:
1038 case HOLEY_ELEMENTS: {
1040 Tagged<FixedArray> elements = Cast<FixedArray>(object->elements());
1041 uint32_t length = static_cast<uint32_t>(elements->length());
1042 if (range < length) length = range;
1043 for (uint32_t i = 0; i < length; i++) {
1044 if (!IsTheHole(elements->get(i), isolate)) {
1045 indices->push_back(i);
1046 }
1047 }
1048 break;
1049 }
1052 if (IsFixedArray(object->elements())) {
1053 DCHECK_EQ(object->elements()->length(), 0);
1054 break;
1055 }
1056 DirectHandle<FixedDoubleArray> elements(
1057 Cast<FixedDoubleArray>(object->elements()), isolate);
1058 uint32_t length = static_cast<uint32_t>(elements->length());
1059 if (range < length) length = range;
1060 for (uint32_t i = 0; i < length; i++) {
1061 if (!elements->is_the_hole(i)) {
1062 indices->push_back(i);
1063 }
1064 }
1065 break;
1066 }
1067 case DICTIONARY_ELEMENTS: {
1070 Cast<NumberDictionary>(object->elements());
1071 uint32_t capacity = dict->Capacity();
1072 ReadOnlyRoots roots(isolate);
1073 FOR_WITH_HANDLE_SCOPE(isolate, uint32_t, j = 0, j, j < capacity, j++, {
1074 Tagged<Object> k = dict->KeyAt(InternalIndex(j));
1075 if (!dict->IsKey(roots, k)) continue;
1076 DCHECK(IsNumber(k));
1077 uint32_t index = static_cast<uint32_t>(Object::NumberValue(k));
1078 if (index < range) {
1079 indices->push_back(index);
1080 }
1081 });
1082 break;
1083 }
1084#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
1085
1087 size_t length = Cast<JSTypedArray>(object)->GetLength();
1088 if (range <= length) {
1089 length = range;
1090 // We will add all indices, so we might as well clear it first
1091 // and avoid duplicates.
1092 indices->clear();
1093 }
1094 // {range} puts a cap on {length}.
1095 DCHECK_LE(length, std::numeric_limits<uint32_t>::max());
1096 for (uint32_t i = 0; i < length; i++) {
1097 indices->push_back(i);
1098 }
1099 if (length == range) return; // All indices accounted for already.
1100 break;
1101 }
1102
1103#undef TYPED_ARRAY_CASE
1107 DisableGCMole no_gc_mole;
1108 Tagged<FixedArrayBase> elements = object->elements();
1109 Tagged<JSObject> raw_object = *object;
1110 ElementsAccessor* accessor = object->GetElementsAccessor();
1111 for (uint32_t i = 0; i < range; i++) {
1112 if (accessor->HasElement(raw_object, i, elements)) {
1113 indices->push_back(i);
1114 }
1115 }
1116 break;
1117 }
1120 DCHECK(IsJSPrimitiveWrapper(*object));
1121 auto js_value = Cast<JSPrimitiveWrapper>(object);
1122 DCHECK(IsString(js_value->value()));
1123 DirectHandle<String> string(Cast<String>(js_value->value()), isolate);
1124 uint32_t length = static_cast<uint32_t>(string->length());
1125 uint32_t i = 0;
1126 uint32_t limit = std::min(length, range);
1127 for (; i < limit; i++) {
1128 indices->push_back(i);
1129 }
1130 ElementsAccessor* accessor = object->GetElementsAccessor();
1131 for (; i < range; i++) {
1132 if (accessor->HasElement(*object, i)) {
1133 indices->push_back(i);
1134 }
1135 }
1136 break;
1137 }
1139 // TODO(ishell): implement
1140 UNIMPLEMENTED();
1141 case SHARED_ARRAY_ELEMENTS: {
1142 uint32_t length = Cast<JSSharedArray>(object)->elements()->length();
1143 if (range <= length) {
1144 length = range;
1145 indices->clear();
1146 }
1147 for (uint32_t i = 0; i < length; i++) {
1148 // JSSharedArrays are created non-resizable and do not have holes.
1149 SLOW_DCHECK(object->GetElementsAccessor()->HasElement(
1150 *object, i, object->elements()));
1151 indices->push_back(i);
1152 }
1153 if (length == range) return;
1154 break;
1155 }
1156 case NO_ELEMENTS:
1157 break;
1158 }
1159
1160 PrototypeIterator iter(isolate, object);
1161 if (!iter.IsAtEnd()) {
1162 // The prototype will usually have no inherited element indices,
1163 // but we have to check.
1164 // Casting to JSObject is safe because we ran {HasOnlySimpleElements} on
1165 // the receiver before, which checks the prototype chain.
1166 CollectElementIndices(
1167 isolate, PrototypeIterator::GetCurrent<JSObject>(iter), range, indices);
1168 }
1169}
1170
1171bool IterateElementsSlow(Isolate* isolate, DirectHandle<JSReceiver> receiver,
1172 uint32_t length, ArrayConcatVisitor* visitor) {
1173 FOR_WITH_HANDLE_SCOPE(isolate, uint32_t, i = 0, i, i < length, ++i, {
1174 Maybe<bool> maybe = JSReceiver::HasElement(isolate, receiver, i);
1175 if (maybe.IsNothing()) return false;
1176 if (maybe.FromJust()) {
1177 DirectHandle<Object> element_value;
1179 isolate, element_value, JSReceiver::GetElement(isolate, receiver, i),
1180 false);
1181 if (!visitor->visit(i, element_value)) return false;
1182 }
1183 });
1184 visitor->increase_index_offset(length);
1185 return true;
1186}
1197bool IterateElements(Isolate* isolate, DirectHandle<JSReceiver> receiver,
1198 ArrayConcatVisitor* visitor) {
1199 uint32_t length = 0;
1200
1201 if (IsJSArray(*receiver)) {
1202 auto array = Cast<JSArray>(receiver);
1203 length = static_cast<uint32_t>(Object::NumberValue(array->length()));
1204 } else {
1205 DirectHandle<Object> val;
1207 isolate, val, Object::GetLengthFromArrayLike(isolate, receiver), false);
1208 if (visitor->index_offset() + Object::NumberValue(*val) > kMaxSafeInteger) {
1209 isolate->Throw(*isolate->factory()->NewTypeError(
1210 MessageTemplate::kInvalidArrayLength));
1211 return false;
1212 }
1213 // TODO(caitp): Support larger element indexes (up to 2^53-1).
1214 if (!Object::ToUint32(*val, &length)) {
1215 length = 0;
1216 }
1217 // TODO(cbruni): handle other element kind as well
1218 return IterateElementsSlow(isolate, receiver, length, visitor);
1219 }
1220
1221 if (!visitor->has_simple_elements() ||
1222 !HasOnlySimpleElements(isolate, *receiver)) {
1223 return IterateElementsSlow(isolate, receiver, length, visitor);
1224 }
1225 DirectHandle<JSArray> array = Cast<JSArray>(receiver);
1226
1227 switch (array->GetElementsKind()) {
1229 case PACKED_ELEMENTS:
1233 case HOLEY_SMI_ELEMENTS:
1237 case HOLEY_ELEMENTS: {
1238 // Disallow execution so the cached elements won't change mid execution.
1239 DisallowJavascriptExecution no_js(isolate);
1240
1241 // Run through the elements FixedArray and use HasElement and GetElement
1242 // to check the prototype for missing elements.
1243 DirectHandle<FixedArray> elements(Cast<FixedArray>(array->elements()),
1244 isolate);
1245 int fast_length = static_cast<int>(length);
1246 DCHECK(fast_length <= elements->length());
1247 FOR_WITH_HANDLE_SCOPE(isolate, int, j = 0, j, j < fast_length, j++, {
1248 DirectHandle<Object> element_value(elements->get(j), isolate);
1249 if (!IsTheHole(*element_value, isolate)) {
1250 if (!visitor->visit(j, element_value)) return false;
1251 } else {
1252 Maybe<bool> maybe = JSReceiver::HasElement(isolate, array, j);
1253 if (maybe.IsNothing()) return false;
1254 if (maybe.FromJust()) {
1255 // Call GetElement on array, not its prototype, or getters won't
1256 // have the correct receiver.
1258 isolate, element_value,
1259 JSReceiver::GetElement(isolate, array, j), false);
1260 if (!visitor->visit(j, element_value)) return false;
1261 }
1262 }
1263 });
1264 break;
1265 }
1268 // Disallow execution so the cached elements won't change mid execution.
1269 DisallowJavascriptExecution no_js(isolate);
1270
1271 // Empty array is FixedArray but not FixedDoubleArray.
1272 if (length == 0) break;
1273 // Run through the elements FixedArray and use HasElement and GetElement
1274 // to check the prototype for missing elements.
1275 if (IsFixedArray(array->elements())) {
1276 DCHECK_EQ(array->elements()->length(), 0);
1277 break;
1278 }
1279 DirectHandle<FixedDoubleArray> elements(
1280 Cast<FixedDoubleArray>(array->elements()), isolate);
1281 int fast_length = static_cast<int>(length);
1282 DCHECK(fast_length <= elements->length());
1283 FOR_WITH_HANDLE_SCOPE(isolate, int, j = 0, j, j < fast_length, j++, {
1284 if (!elements->is_the_hole(j)) {
1285 double double_value = elements->get_scalar(j);
1286 DirectHandle<Object> element_value =
1287 isolate->factory()->NewNumber(double_value);
1288 if (!visitor->visit(j, element_value)) return false;
1289 } else {
1290 Maybe<bool> maybe = JSReceiver::HasElement(isolate, array, j);
1291 if (maybe.IsNothing()) return false;
1292 if (maybe.FromJust()) {
1293 // Call GetElement on array, not its prototype, or getters won't
1294 // have the correct receiver.
1295 DirectHandle<Object> element_value;
1297 isolate, element_value,
1298 JSReceiver::GetElement(isolate, array, j), false);
1299 if (!visitor->visit(j, element_value)) return false;
1300 }
1301 }
1302 });
1303 break;
1304 }
1305
1306 case DICTIONARY_ELEMENTS: {
1307 // Disallow execution so the cached dictionary won't change mid execution.
1308 DisallowJavascriptExecution no_js(isolate);
1309
1310 DirectHandle<NumberDictionary> dict(array->element_dictionary(), isolate);
1311 std::vector<uint32_t> indices;
1312 indices.reserve(dict->Capacity() / 2);
1313
1314 // Collect all indices in the object and the prototypes less
1315 // than length. This might introduce duplicates in the indices list.
1316 CollectElementIndices(isolate, array, length, &indices);
1317 std::sort(indices.begin(), indices.end());
1318 size_t n = indices.size();
1319 FOR_WITH_HANDLE_SCOPE(isolate, size_t, j = 0, j, j < n, (void)0, {
1320 uint32_t index = indices[j];
1321 DirectHandle<Object> element;
1323 isolate, element, JSReceiver::GetElement(isolate, array, index),
1324 false);
1325 if (!visitor->visit(index, element)) return false;
1326 // Skip to next different index (i.e., omit duplicates).
1327 do {
1328 j++;
1329 } while (j < n && indices[j] == index);
1330 });
1331 break;
1332 }
1336 isolate, uint32_t, index = 0, index, index < length, index++, {
1337 DirectHandle<Object> element;
1339 isolate, element, JSReceiver::GetElement(isolate, array, index),
1340 false);
1341 if (!visitor->visit(index, element)) return false;
1342 });
1343 break;
1344 }
1346 // TODO(ishell): implement
1347 UNIMPLEMENTED();
1348 case NO_ELEMENTS:
1349 break;
1350 // JSArrays cannot have the following elements kinds:
1351#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
1354#undef TYPED_ARRAY_CASE
1358 UNREACHABLE();
1359 }
1360 visitor->increase_index_offset(length);
1361 return true;
1362}
1363
1364static Maybe<bool> IsConcatSpreadable(Isolate* isolate,
1365 DirectHandle<Object> obj) {
1366 HandleScope handle_scope(isolate);
1367 DirectHandle<JSReceiver> receiver;
1368 if (!TryCast<JSReceiver>(obj, &receiver)) return Just(false);
1369 if (!Protectors::IsIsConcatSpreadableLookupChainIntact(isolate) ||
1370 receiver->HasProxyInPrototype(isolate)) {
1371 // Slow path if @@isConcatSpreadable has been used.
1372 DirectHandle<Symbol> key(isolate->factory()->is_concat_spreadable_symbol());
1373 DirectHandle<Object> value;
1374 MaybeDirectHandle<Object> maybeValue =
1375 i::Runtime::GetObjectProperty(isolate, receiver, key);
1376 if (!maybeValue.ToHandle(&value)) return Nothing<bool>();
1377 if (!IsUndefined(*value, isolate))
1378 return Just(Object::BooleanValue(*value, isolate));
1379 }
1380 return Object::IsArray(receiver);
1381}
1382
1383Tagged<Object> Slow_ArrayConcat(BuiltinArguments* args,
1384 DirectHandle<Object> species,
1385 Isolate* isolate) {
1386 int argument_count = args->length();
1387
1388 bool is_array_species = *species == isolate->context()->array_function();
1389
1390 // Pass 1: estimate the length and number of elements of the result.
1391 // The actual length can be larger if any of the arguments have getters
1392 // that mutate other arguments (but will otherwise be precise).
1393 // The number of elements is precise if there are no inherited elements.
1394
1396
1397 uint32_t estimate_result_length = 0;
1398 uint32_t estimate_nof = 0;
1399 FOR_WITH_HANDLE_SCOPE(isolate, int, i = 0, i, i < argument_count, i++, {
1400 DirectHandle<Object> obj = args->at(i);
1401 uint32_t length_estimate;
1402 uint32_t element_estimate;
1403 if (IsJSArray(*obj)) {
1404 auto array = Cast<JSArray>(obj);
1405 length_estimate =
1406 static_cast<uint32_t>(Object::NumberValue(array->length()));
1407 if (length_estimate != 0) {
1408 ElementsKind array_kind =
1409 GetPackedElementsKind(array->GetElementsKind());
1410 if (IsAnyNonextensibleElementsKind(array_kind)) {
1411 array_kind = PACKED_ELEMENTS;
1412 }
1413 kind = GetMoreGeneralElementsKind(kind, array_kind);
1414 }
1415 element_estimate = EstimateElementCount(isolate, array);
1416 } else {
1417 if (IsHeapObject(*obj)) {
1420 }
1421 length_estimate = 1;
1422 element_estimate = 1;
1423 }
1424 // Avoid overflows by capping at kMaxArrayLength.
1425 if (JSArray::kMaxArrayLength - estimate_result_length < length_estimate) {
1426 estimate_result_length = JSArray::kMaxArrayLength;
1427 } else {
1428 estimate_result_length += length_estimate;
1429 }
1430 if (JSArray::kMaxArrayLength - estimate_nof < element_estimate) {
1431 estimate_nof = JSArray::kMaxArrayLength;
1432 } else {
1433 estimate_nof += element_estimate;
1434 }
1435 });
1436
1437 // If estimated number of elements is more than half of length, a
1438 // fixed array (fast case) is more time and space-efficient than a
1439 // dictionary.
1440 bool fast_case = is_array_species &&
1441 (estimate_nof * 2) >= estimate_result_length &&
1442 Protectors::IsIsConcatSpreadableLookupChainIntact(isolate);
1443
1444 if (fast_case && kind == PACKED_DOUBLE_ELEMENTS) {
1445 DirectHandle<FixedArrayBase> storage =
1446 isolate->factory()->NewFixedDoubleArray(estimate_result_length);
1447 int j = 0;
1448 bool failure = false;
1449 if (estimate_result_length > 0) {
1450 auto double_storage = Cast<FixedDoubleArray>(storage);
1451 for (int i = 0; i < argument_count; i++) {
1452 DirectHandle<Object> obj = args->at(i);
1453 if (IsSmi(*obj)) {
1454 double_storage->set(j, Smi::ToInt(*obj));
1455 j++;
1456 } else if (IsNumber(*obj)) {
1457 double_storage->set(j, Object::NumberValue(*obj));
1458 j++;
1459 } else {
1461 Tagged<JSArray> array = Cast<JSArray>(*obj);
1462 uint32_t length =
1463 static_cast<uint32_t>(Object::NumberValue(array->length()));
1464 switch (array->GetElementsKind()) {
1467 // Empty array is FixedArray but not FixedDoubleArray.
1468 if (length == 0) break;
1469 Tagged<FixedDoubleArray> elements =
1470 Cast<FixedDoubleArray>(array->elements());
1471 for (uint32_t k = 0; k < length; k++) {
1472 if (elements->is_the_hole(k)) {
1473 // TODO(jkummerow/verwaest): We could be a bit more clever
1474 // here: Check if there are no elements/getters on the
1475 // prototype chain, and if so, allow creation of a holey
1476 // result array.
1477 // Same thing below (holey smi case).
1478 failure = true;
1479 break;
1480 }
1481 double double_value = elements->get_scalar(k);
1482 double_storage->set(j, double_value);
1483 j++;
1484 }
1485 break;
1486 }
1487 case HOLEY_SMI_ELEMENTS:
1488 case PACKED_SMI_ELEMENTS: {
1489 Tagged<Object> the_hole = ReadOnlyRoots(isolate).the_hole_value();
1490 Tagged<FixedArray> elements(Cast<FixedArray>(array->elements()));
1491 for (uint32_t k = 0; k < length; k++) {
1492 Tagged<Object> element = elements->get(k);
1493 if (element == the_hole) {
1494 failure = true;
1495 break;
1496 }
1497 int32_t int_value = Smi::ToInt(element);
1498 double_storage->set(j, int_value);
1499 j++;
1500 }
1501 break;
1502 }
1503 case HOLEY_ELEMENTS:
1507 case PACKED_ELEMENTS:
1512 case NO_ELEMENTS:
1513 DCHECK_EQ(0u, length);
1514 break;
1515 default:
1516 UNREACHABLE();
1517 }
1518 }
1519 if (failure) {
1520#ifdef VERIFY_HEAP
1521 // The allocated storage may contain uninitialized values which will
1522 // cause FixedDoubleArray::FixedDoubleArrayVerify to fail, when the
1523 // heap is verified (see: crbug.com/1415071). To prevent this, we
1524 // initialize the array with holes.
1525 if (v8_flags.verify_heap) {
1526 double_storage->FillWithHoles(0, estimate_result_length);
1527 }
1528#endif // VERIFY_HEAP
1529 break;
1530 }
1531 }
1532 }
1533 if (!failure) {
1534 return *isolate->factory()->NewJSArrayWithElements(storage, kind, j);
1535 }
1536 // In case of failure, fall through.
1537 }
1538
1539 DirectHandle<UnionOf<JSReceiver, FixedArray, NumberDictionary>> storage;
1540 if (fast_case) {
1541 // The backing storage array must have non-existing elements to preserve
1542 // holes across concat operations.
1543 storage =
1544 isolate->factory()->NewFixedArrayWithHoles(estimate_result_length);
1545 } else if (is_array_species) {
1546 storage = NumberDictionary::New(isolate, estimate_nof);
1547 } else {
1548 DCHECK(IsConstructor(*species));
1549 DirectHandle<Object> length(Smi::zero(), isolate);
1550 DirectHandle<JSReceiver> storage_object;
1552 isolate, storage_object,
1553 Execution::New(isolate, species, species, {&length, 1}));
1554 storage = storage_object;
1555 }
1556
1557 ArrayConcatVisitor visitor(isolate, storage, fast_case);
1558
1559 for (int i = 0; i < argument_count; i++) {
1560 DirectHandle<Object> obj = args->at(i);
1561 Maybe<bool> spreadable = IsConcatSpreadable(isolate, obj);
1562 MAYBE_RETURN(spreadable, ReadOnlyRoots(isolate).exception());
1563 if (spreadable.FromJust()) {
1564 DirectHandle<JSReceiver> object = Cast<JSReceiver>(obj);
1565 if (!IterateElements(isolate, object, &visitor)) {
1566 return ReadOnlyRoots(isolate).exception();
1567 }
1568 } else {
1569 if (!visitor.visit(0, obj)) return ReadOnlyRoots(isolate).exception();
1570 visitor.increase_index_offset(1);
1571 }
1572 }
1573
1574 if (visitor.exceeds_array_limit()) {
1576 isolate, NewRangeError(MessageTemplate::kInvalidArrayLength));
1577 }
1578
1579 if (is_array_species) {
1580 return *visitor.ToArray();
1581 } else {
1582 RETURN_RESULT_OR_FAILURE(isolate, visitor.ToJSReceiver());
1583 }
1584}
1585
1586bool IsSimpleArray(Isolate* isolate, DirectHandle<JSArray> obj) {
1588 Tagged<Map> map = obj->map();
1589 // If there is only the 'length' property we are fine.
1590 if (map->prototype() ==
1591 isolate->native_context()->initial_array_prototype() &&
1592 map->NumberOfOwnDescriptors() == 1) {
1593 return true;
1594 }
1595 // TODO(cbruni): slower lookup for array subclasses and support slow
1596 // @@IsConcatSpreadable lookup.
1597 return false;
1598}
1599
1600MaybeDirectHandle<JSArray> Fast_ArrayConcat(Isolate* isolate,
1601 BuiltinArguments* args) {
1602 if (!Protectors::IsIsConcatSpreadableLookupChainIntact(isolate)) {
1603 return {};
1604 }
1605 // We shouldn't overflow when adding another len.
1606 const int kHalfOfMaxInt = 1 << (kBitsPerInt - 2);
1607 static_assert(FixedArray::kMaxLength < kHalfOfMaxInt);
1608 static_assert(FixedDoubleArray::kMaxLength < kHalfOfMaxInt);
1609 USE(kHalfOfMaxInt);
1610
1611 int n_arguments = args->length();
1612 int result_len = 0;
1613 {
1615 // Iterate through all the arguments performing checks
1616 // and calculating total length.
1617 for (int i = 0; i < n_arguments; i++) {
1618 Tagged<Object> arg = (*args)[i];
1619 if (!IsJSArray(arg)) return {};
1620 if (!HasOnlySimpleReceiverElements(isolate, Cast<JSObject>(arg))) {
1621 return {};
1622 }
1623 // TODO(cbruni): support fast concatenation of DICTIONARY_ELEMENTS.
1624 if (!Cast<JSObject>(arg)->HasFastElements()) {
1625 return {};
1626 }
1627 DirectHandle<JSArray> array(Cast<JSArray>(arg), isolate);
1628 if (!IsSimpleArray(isolate, array)) {
1629 return {};
1630 }
1631 // The Array length is guaranteed to be <= kHalfOfMaxInt thus we won't
1632 // overflow.
1633 result_len += Smi::ToInt(array->length());
1634 DCHECK_GE(result_len, 0);
1635 // Throw an Error if we overflow the FixedArray limits
1636 if (FixedDoubleArray::kMaxLength < result_len ||
1637 FixedArray::kMaxLength < result_len) {
1639 THROW_NEW_ERROR(isolate,
1640 NewRangeError(MessageTemplate::kInvalidArrayLength));
1641 }
1642 }
1643 }
1644 return ElementsAccessor::Concat(isolate, args, n_arguments, result_len);
1645}
1646
1647} // namespace
1648
1649// ES6 22.1.3.1 Array.prototype.concat
1650BUILTIN(ArrayConcat) {
1651 HandleScope scope(isolate);
1652
1653 DirectHandle<JSAny> receiver = args.receiver();
1655 isolate, receiver,
1656 Object::ToObject(isolate, args.receiver(), "Array.prototype.concat"));
1657 BuiltinArguments::ChangeValueScope set_receiver_value_scope(
1659
1660 DirectHandle<JSArray> result_array;
1661
1662 // Avoid a real species read to avoid extra lookups to the array constructor
1663 if (V8_LIKELY(IsJSArray(*receiver) &&
1664 Cast<JSArray>(receiver)->HasArrayPrototype(isolate) &&
1665 Protectors::IsArraySpeciesLookupChainIntact(isolate))) {
1666 if (Fast_ArrayConcat(isolate, &args).ToHandle(&result_array)) {
1667 return *result_array;
1668 }
1669 if (isolate->has_exception()) return ReadOnlyRoots(isolate).exception();
1670 }
1671 // Reading @@species happens before anything else with a side effect, so
1672 // we can do it here to determine whether to take the fast path.
1673 DirectHandle<Object> species;
1675 isolate, species, Object::ArraySpeciesConstructor(isolate, receiver));
1676 if (*species == *isolate->array_function()) {
1677 if (Fast_ArrayConcat(isolate, &args).ToHandle(&result_array)) {
1678 return *result_array;
1679 }
1680 if (isolate->has_exception()) return ReadOnlyRoots(isolate).exception();
1681 }
1682 return Slow_ArrayConcat(&args, species, isolate);
1683}
1684
1685} // namespace internal
1686} // namespace v8
Isolate * isolate_
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype)
uint32_t bit_field_
uint32_t index_offset_
#define BUILTIN(name)
Builtins::Kind kind
Definition builtins.cc:40
#define SLOW_DCHECK(condition)
Definition checks.h:21
bool IsArray() const
Definition api.cc:3525
static constexpr T decode(U value)
Definition bit-field.h:66
static V8_NODISCARD constexpr U update(U previous, T value)
Definition bit-field.h:61
static constexpr int kReceiverIndex
static int ArrayMapIndex(ElementsKind elements_kind)
Definition contexts.h:676
virtual V8_WARN_UNUSED_RESULT Maybe< uint32_t > Push(DirectHandle< JSArray > receiver, BuiltinArguments *args, uint32_t push_size)=0
static DirectHandle< JSArray > Concat(Isolate *isolate, BuiltinArguments *args, uint32_t concat_size, uint32_t result_length)
Definition elements.cc:5765
virtual V8_WARN_UNUSED_RESULT Maybe< uint32_t > Unshift(DirectHandle< JSArray > receiver, BuiltinArguments *args, uint32_t unshift_size)=0
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< JSReceiver > New(Isolate *isolate, DirectHandle< Object > constructor, base::Vector< const DirectHandle< Object > > args)
Definition execution.cc:556
static constexpr int kMaxLength
static void Destroy(Address *location)
static V8_EXPORT_PRIVATE Maybe< bool > SetLength(DirectHandle< JSArray > array, uint32_t length)
Definition objects.cc:4812
static constexpr uint32_t kMaxArrayLength
Definition js-array.h:130
static bool HasReadOnlyLength(DirectHandle< JSArray > array)
Definition objects.cc:4962
static bool PrototypeHasNoElements(Isolate *isolate, Tagged< JSObject > object)
static constexpr uint32_t kMaxElementIndex
Definition js-objects.h:924
static void SetMapAndElements(DirectHandle< JSObject > object, DirectHandle< Map > map, DirectHandle< FixedArrayBase > elements)
static V8_EXPORT_PRIVATE void TransitionElementsKind(DirectHandle< JSObject > object, ElementsKind to_kind)
static DirectHandle< Map > GetElementsTransitionMap(DirectHandle< JSObject > object, ElementsKind to_kind)
static bool UpdateAllocationSite(DirectHandle< JSObject > object, ElementsKind to_kind)
V8_EXPORT_PRIVATE static V8_WARN_UNUSED_RESULT Maybe< bool > DeletePropertyOrElement(Isolate *isolate, DirectHandle< JSReceiver > object, DirectHandle< Name > name, LanguageMode language_mode=LanguageMode::kSloppy)
static V8_WARN_UNUSED_RESULT Maybe< bool > HasElement(Isolate *isolate, DirectHandle< JSReceiver > object, uint32_t index)
static V8_WARN_UNUSED_RESULT MaybeHandle< Object > GetElement(Isolate *isolate, DirectHandle< JSReceiver > receiver, uint32_t index)
static V8_WARN_UNUSED_RESULT Maybe< bool > CreateDataProperty(Isolate *isolate, DirectHandle< JSReceiver > object, DirectHandle< Name > key, DirectHandle< Object > value, Maybe< ShouldThrow > should_throw)
static V8_WARN_UNUSED_RESULT Maybe< bool > HasPropertyOrElement(Isolate *isolate, DirectHandle< JSReceiver > object, PropertyKey key)
static V8_WARN_UNUSED_RESULT HandleType< NumberDictionary > Set(Isolate *isolate, HandleType< NumberDictionary > dictionary, uint32_t key, DirectHandle< Object > value, DirectHandle< JSObject > dictionary_holder=DirectHandle< JSObject >::null(), PropertyDetails details=PropertyDetails::Empty())
V8_EXPORT_PRIVATE static V8_WARN_UNUSED_RESULT Maybe< bool > SetProperty(LookupIterator *it, DirectHandle< Object > value, StoreOrigin store_origin, Maybe< ShouldThrow > should_throw=Nothing< ShouldThrow >())
Definition objects.cc:2439
static V8_WARN_UNUSED_RESULT MaybeHandle< Object > GetPropertyOrElement(Isolate *isolate, DirectHandle< JSAny > object, DirectHandle< Name > name)
static Handle< String > TypeOf(Isolate *isolate, DirectHandle< Object > object)
Definition objects.cc:1001
static V8_EXPORT_PRIVATE bool BooleanValue(Tagged< Object > obj, IsolateT *isolate)
static ElementsKind OptimalElementsKind(Tagged< Object > obj, PtrComprCageBase cage_base)
static V8_WARN_UNUSED_RESULT HandleType< JSReceiver >::MaybeType ToObject(Isolate *isolate, HandleType< T > object, const char *method_name=nullptr)
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< Object > SetElement(Isolate *isolate, DirectHandle< JSAny > object, uint32_t index, DirectHandle< Object > value, ShouldThrow should_throw)
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< Object > SetPropertyOrElement(Isolate *isolate, DirectHandle< JSAny > object, DirectHandle< Name > name, DirectHandle< Object > value, Maybe< ShouldThrow > should_throw=Nothing< ShouldThrow >(), StoreOrigin store_origin=StoreOrigin::kMaybeKeyed)
static double NumberValue(Tagged< Number > obj)
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< Object > GetLengthFromArrayLike(Isolate *isolate, DirectHandle< JSReceiver > object)
Definition objects.cc:1238
static V8_WARN_UNUSED_RESULT Maybe< double > IntegerValue(Isolate *isolate, HandleType< T > input)
static bool ToUint32(Tagged< Object > obj, uint32_t *value)
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< Object > ArraySpeciesConstructor(Isolate *isolate, DirectHandle< JSAny > original_array)
Definition objects.cc:1742
static V8_WARN_UNUSED_RESULT MaybeHandle< Object > GetElement(Isolate *isolate, DirectHandle< JSAny > object, uint32_t index)
Tagged< T > GetCurrent() const
Definition prototype.h:52
static constexpr int ToInt(const Tagged< Object > object)
Definition smi.h:33
static constexpr Tagged< Smi > FromInt(int value)
Definition smi.h:38
static constexpr Tagged< Smi > zero()
Definition smi.h:99
static constexpr int kMaxValue
Definition smi.h:101
#define V8_COMPRESS_POINTERS_8GB_BOOL
Definition globals.h:608
int start
int end
#define RAB_GSAB_TYPED_ARRAYS(V)
#define TYPED_ARRAYS(V)
#define RETURN_ON_EXCEPTION(isolate, call)
Definition isolate.h:395
#define ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, dst, call)
Definition isolate.h:284
#define THROW_NEW_ERROR(isolate, call)
Definition isolate.h:307
#define ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, dst, call, value)
Definition isolate.h:276
#define THROW_NEW_ERROR_RETURN_FAILURE(isolate, call)
Definition isolate.h:294
#define RETURN_FAILURE_ON_EXCEPTION(isolate, call)
Definition isolate.h:368
#define FOR_WITH_HANDLE_SCOPE(isolate, loop_var_type, init, loop_var, limit_check, increment, body)
Definition isolate.h:457
#define MAYBE_RETURN_NULL(call)
Definition isolate.h:413
#define MAYBE_RETURN(call, value)
Definition isolate.h:408
#define RETURN_ON_EXCEPTION_VALUE(isolate, call, value)
Definition isolate.h:340
#define RETURN_RESULT_OR_FAILURE(isolate, call)
Definition isolate.h:264
#define MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, dst, call)
Definition isolate.h:448
#define MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, dst, call, value)
Definition isolate.h:440
base::Vector< const DirectHandle< Object > > args
Definition execution.cc:74
TNode< Object > receiver
ZoneVector< RpoNumber > & result
Point from
Point to
digit_t * storage_
Definition mul-fft.cc:475
int int32_t
Definition unicode.cc:40
bool TryCast(Tagged< From > value, Tagged< To > *out)
Definition casting.h:77
constexpr double kMaxSafeInteger
Definition globals.h:1985
PerThreadAssertScopeDebugOnly< false, SAFEPOINTS_ASSERT, HEAP_ALLOCATION_ASSERT > DisallowGarbageCollection
bool DoubleToUint32IfEqualToSelf(double value, uint32_t *uint32_value)
bool IsMoreGeneralElementsKindTransition(ElementsKind from_kind, ElementsKind to_kind)
bool IsNumber(Tagged< Object > obj)
bool IsCustomElementsReceiverMap(Tagged< Map > map)
Tagged(T object) -> Tagged< T >
ElementsKind GetPackedElementsKind(ElementsKind holey_kind)
V8_INLINE constexpr bool IsSmi(TaggedImpl< kRefType, StorageType > obj)
Definition objects.h:665
bool IsAnyNonextensibleElementsKind(ElementsKind kind)
constexpr bool IsObjectElementsKind(ElementsKind kind)
kStaticElementsTemplateOffset kInstancePropertiesTemplateOffset Tagged< FixedArray >
@ HOLEY_NONEXTENSIBLE_ELEMENTS
@ SLOW_STRING_WRAPPER_ELEMENTS
@ PACKED_NONEXTENSIBLE_ELEMENTS
@ SLOW_SLOPPY_ARGUMENTS_ELEMENTS
@ FAST_SLOPPY_ARGUMENTS_ELEMENTS
@ FAST_STRING_WRAPPER_ELEMENTS
V8_INLINE DirectHandle< T > direct_handle(Tagged< T > object, Isolate *isolate)
return Cast< NumberDictionary >(elements(cage_base))
PerThreadAssertScopeDebugOnly< false, GC_MOLE > DisableGCMole
PerThreadAssertScopeDebugOnly< true, SAFEPOINTS_ASSERT, HEAP_ALLOCATION_ASSERT > AllowGarbageCollection
Handle< T > IndirectHandle
Definition globals.h:1086
DONT_OVERRIDE DISABLE_ALLOCATION_SITES HOLEY_ELEMENTS
constexpr int kBitsPerInt
Definition globals.h:687
typename detail::FlattenUnionHelper< Union<>, Ts... >::type UnionOf
Definition union.h:123
bool IsFastElementsKind(ElementsKind kind)
DONT_OVERRIDE DISABLE_ALLOCATION_SITES DISABLE_ALLOCATION_SITES HOLEY_DOUBLE_ELEMENTS
bool IsDictionaryElementsKind(ElementsKind kind)
V8_INLINE constexpr bool IsHeapObject(TaggedImpl< kRefType, StorageType > obj)
Definition objects.h:669
V8_EXPORT_PRIVATE FlagValues v8_flags
return value
Definition map-inl.h:893
constexpr bool IsDoubleElementsKind(ElementsKind kind)
ElementsKind GetMoreGeneralElementsKind(ElementsKind from_kind, ElementsKind to_kind)
constexpr uint32_t kMaxUInt32
Definition globals.h:387
template const char * string
!IsContextMap !IsContextMap native_context
Definition map-inl.h:877
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
static constexpr ReleaseStoreTag kReleaseStore
Definition globals.h:2910
Maybe< T > Nothing()
Definition v8-maybe.h:112
Maybe< T > Just(const T &t)
Definition v8-maybe.h:117
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_IMPLIES(v1, v2)
Definition logging.h:493
#define DCHECK_GE(v1, v2)
Definition logging.h:488
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define USE(...)
Definition macros.h:293
#define V8_LIKELY(condition)
Definition v8config.h:661
#define V8_WARN_UNUSED_RESULT
Definition v8config.h:671