// Copyright 2014 the V8 project authors. All rights reserved.
|
// Use of this source code is governed by a BSD-style license that can be
|
// found in the LICENSE file.
|
|
#include "src/arguments-inl.h"
|
#include "src/code-stubs.h"
|
#include "src/conversions-inl.h"
|
#include "src/debug/debug.h"
|
#include "src/elements.h"
|
#include "src/heap/factory.h"
|
#include "src/isolate-inl.h"
|
#include "src/keys.h"
|
#include "src/messages.h"
|
#include "src/objects/arguments-inl.h"
|
#include "src/objects/hash-table-inl.h"
|
#include "src/objects/js-array-inl.h"
|
#include "src/prototype.h"
|
#include "src/runtime/runtime-utils.h"
|
|
namespace v8 {
|
namespace internal {
|
|
RUNTIME_FUNCTION(Runtime_TransitionElementsKind) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(2, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
|
CONVERT_ARG_HANDLE_CHECKED(Map, to_map, 1);
|
ElementsKind to_kind = to_map->elements_kind();
|
ElementsAccessor::ForKind(to_kind)->TransitionElementsKind(object, to_map);
|
return *object;
|
}
|
|
namespace {
|
// Find the next free position. undefined and holes are both considered
|
// free spots. Returns "Nothing" if an exception occurred.
|
V8_WARN_UNUSED_RESULT
|
Maybe<uint32_t> FindNextFreePosition(Isolate* isolate,
|
Handle<JSReceiver> receiver,
|
uint32_t current_pos) {
|
for (uint32_t position = current_pos;; ++position) {
|
Maybe<bool> has_element = JSReceiver::HasElement(receiver, position);
|
MAYBE_RETURN(has_element, Nothing<uint32_t>());
|
if (!has_element.FromJust()) return Just(position);
|
|
Handle<Object> element;
|
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
isolate, element, JSReceiver::GetElement(isolate, receiver, position),
|
Nothing<uint32_t>());
|
if (element->IsUndefined(isolate)) return Just(position);
|
}
|
}
|
|
// As RemoveArrayHoles, but also handles Dictionary elements that stay
|
// Dictionary (requires_slow_elements() is true), proxies and objects that
|
// might have accessors.
|
V8_WARN_UNUSED_RESULT
|
Object* RemoveArrayHolesGeneric(Isolate* isolate, Handle<JSReceiver> receiver,
|
uint32_t limit) {
|
HandleScope scope(isolate);
|
|
// For proxies, we do not collect the keys, instead we use all indices in
|
// the full range of [0, limit).
|
Handle<FixedArray> keys;
|
if (!receiver->IsJSProxy()) {
|
keys = JSReceiver::GetOwnElementIndices(isolate, receiver,
|
Handle<JSObject>::cast(receiver));
|
}
|
|
uint32_t num_undefined = 0;
|
uint32_t current_pos = 0;
|
int num_indices = keys.is_null() ? limit : keys->length();
|
|
// Compact keys with undefined values and moves non-undefined
|
// values to the front.
|
// The loop does two things simultaneously:
|
// (1) Count the number of 'undefined', i.e.
|
// i.e.: HasProperty(receiver, key) && Get(receiver, key) == undefined
|
// (2) Move all non-undefined values to the front. The variable current_pos
|
// is used to track free spots in the array starting at the beginning.
|
// Holes and 'undefined' are considered free spots.
|
// A hole is when HasElement(receiver, key) is false.
|
for (int i = 0; i < num_indices; ++i) {
|
uint32_t key = keys.is_null() ? i : NumberToUint32(keys->get(i));
|
|
// We only care about array indices that are smaller than the limit.
|
// The keys are sorted, so we can break as soon as we encounter the first.
|
if (key >= limit) break;
|
|
Maybe<bool> has_element = JSReceiver::HasElement(receiver, key);
|
MAYBE_RETURN(has_element, ReadOnlyRoots(isolate).exception());
|
if (!has_element.FromJust()) {
|
continue;
|
}
|
|
Handle<Object> element;
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
isolate, element, JSReceiver::GetElement(isolate, receiver, key));
|
|
if (element->IsUndefined(isolate)) {
|
++num_undefined;
|
} else {
|
// Find next free position to move elements to.
|
Maybe<uint32_t> free_position =
|
FindNextFreePosition(isolate, receiver, current_pos);
|
MAYBE_RETURN(free_position, ReadOnlyRoots(isolate).exception());
|
current_pos = free_position.FromJust();
|
|
// Do not move elements that are already in the "packed" area.
|
if (key <= current_pos) continue;
|
|
// array[current_pos] = array[key].
|
// Deleting array[key] is done later. This is to preserve the same
|
// semantics as the old JS implementation when working with non-extensible
|
// objects:
|
// If the array contains undefineds, the position at 'key' might later
|
// bet set to 'undefined'. If we delete the element now and later set it
|
// to undefined, the set operation would throw an exception.
|
RETURN_FAILURE_ON_EXCEPTION(
|
isolate, JSReceiver::SetElement(isolate, receiver, current_pos,
|
element, LanguageMode::kStrict));
|
++current_pos;
|
}
|
}
|
|
// Set [current_pos, current_pos + num_undefined) to undefined.
|
uint32_t result = current_pos;
|
for (uint32_t i = 0; i < num_undefined; ++i) {
|
RETURN_FAILURE_ON_EXCEPTION(
|
isolate, JSReceiver::SetElement(isolate, receiver, current_pos++,
|
isolate->factory()->undefined_value(),
|
LanguageMode::kStrict));
|
}
|
// TODO(szuend): Re-enable when we also copy from the prototype chain for
|
// JSArrays. Then we can use HasOwnProperty instead of
|
// HasElement and this condition will hold.
|
// DCHECK_LE(current_pos, num_indices);
|
|
// Deleting everything after the undefineds up unto the limit.
|
for (int i = num_indices - 1; i >= 0; --i) {
|
uint32_t key = keys.is_null() ? i : NumberToUint32(keys->get(i));
|
if (key < current_pos) break;
|
if (key >= limit) continue;
|
|
Maybe<bool> delete_result = JSReceiver::DeleteElement(receiver, key);
|
MAYBE_RETURN(delete_result, ReadOnlyRoots(isolate).exception());
|
}
|
|
// TODO(jgruber, szuend, chromium:897512): This is a workaround to prevent
|
// returning a number greater than array.length to Array.p.sort, which could
|
// trigger OOB accesses. There is still a correctness bug here though in
|
// how we shift around undefineds and delete elements in the two blocks above.
|
// This needs to be fixed soon.
|
const uint32_t number_of_non_undefined_elements = std::min(limit, result);
|
|
return *isolate->factory()->NewNumberFromUint(
|
number_of_non_undefined_elements);
|
}
|
|
// Collects all defined (non-hole) and non-undefined (array) elements at the
|
// start of the elements array. If the object is in dictionary mode, it is
|
// converted to fast elements mode. Undefined values are placed after
|
// non-undefined values. Returns the number of non-undefined values.
|
V8_WARN_UNUSED_RESULT
|
Object* RemoveArrayHoles(Isolate* isolate, Handle<JSReceiver> receiver,
|
uint32_t limit) {
|
if (receiver->IsJSProxy()) {
|
return RemoveArrayHolesGeneric(isolate, receiver, limit);
|
}
|
|
Handle<JSObject> object = Handle<JSObject>::cast(receiver);
|
if (object->HasStringWrapperElements()) {
|
int len = String::cast(Handle<JSValue>::cast(object)->value())->length();
|
DCHECK_LE(len, limit);
|
return Smi::FromInt(len);
|
}
|
|
if (object->HasSloppyArgumentsElements() || !object->map()->is_extensible()) {
|
return RemoveArrayHolesGeneric(isolate, receiver, limit);
|
}
|
|
JSObject::ValidateElements(*object);
|
if (object->HasDictionaryElements()) {
|
// Convert to fast elements containing only the existing properties.
|
// Ordering is irrelevant, since we are going to sort anyway.
|
Handle<NumberDictionary> dict(object->element_dictionary(), isolate);
|
if (object->IsJSArray() || dict->requires_slow_elements() ||
|
dict->max_number_key() >= limit) {
|
return RemoveArrayHolesGeneric(isolate, receiver, limit);
|
}
|
// Convert to fast elements.
|
Handle<Map> new_map =
|
JSObject::GetElementsTransitionMap(object, HOLEY_ELEMENTS);
|
|
PretenureFlag tenure = Heap::InNewSpace(*object) ? NOT_TENURED : TENURED;
|
Handle<FixedArray> fast_elements =
|
isolate->factory()->NewFixedArray(dict->NumberOfElements(), tenure);
|
dict->CopyValuesTo(*fast_elements);
|
|
JSObject::SetMapAndElements(object, new_map, fast_elements);
|
JSObject::ValidateElements(*object);
|
} else if (object->HasFixedTypedArrayElements()) {
|
// Typed arrays cannot have holes or undefined elements.
|
int array_length = FixedArrayBase::cast(object->elements())->length();
|
return Smi::FromInt(Min(limit, static_cast<uint32_t>(array_length)));
|
} else if (!object->HasDoubleElements()) {
|
JSObject::EnsureWritableFastElements(object);
|
}
|
DCHECK(object->HasSmiOrObjectElements() || object->HasDoubleElements());
|
|
// Collect holes at the end, undefined before that and the rest at the
|
// start, and return the number of non-hole, non-undefined values.
|
|
Handle<FixedArrayBase> elements_base(object->elements(), isolate);
|
uint32_t elements_length = static_cast<uint32_t>(elements_base->length());
|
if (limit > elements_length) {
|
limit = elements_length;
|
}
|
if (limit == 0) {
|
return Smi::kZero;
|
}
|
|
uint32_t result = 0;
|
if (elements_base->map() == ReadOnlyRoots(isolate).fixed_double_array_map()) {
|
FixedDoubleArray* elements = FixedDoubleArray::cast(*elements_base);
|
// Split elements into defined and the_hole, in that order.
|
unsigned int holes = limit;
|
// Assume most arrays contain no holes and undefined values, so minimize the
|
// number of stores of non-undefined, non-the-hole values.
|
for (unsigned int i = 0; i < holes; i++) {
|
if (elements->is_the_hole(i)) {
|
holes--;
|
} else {
|
continue;
|
}
|
// Position i needs to be filled.
|
while (holes > i) {
|
if (elements->is_the_hole(holes)) {
|
holes--;
|
} else {
|
elements->set(i, elements->get_scalar(holes));
|
break;
|
}
|
}
|
}
|
result = holes;
|
while (holes < limit) {
|
elements->set_the_hole(holes);
|
holes++;
|
}
|
} else {
|
FixedArray* elements = FixedArray::cast(*elements_base);
|
DisallowHeapAllocation no_gc;
|
|
// Split elements into defined, undefined and the_hole, in that order. Only
|
// count locations for undefined and the hole, and fill them afterwards.
|
WriteBarrierMode write_barrier = elements->GetWriteBarrierMode(no_gc);
|
unsigned int undefs = limit;
|
unsigned int holes = limit;
|
// Assume most arrays contain no holes and undefined values, so minimize the
|
// number of stores of non-undefined, non-the-hole values.
|
for (unsigned int i = 0; i < undefs; i++) {
|
Object* current = elements->get(i);
|
if (current->IsTheHole(isolate)) {
|
holes--;
|
undefs--;
|
} else if (current->IsUndefined(isolate)) {
|
undefs--;
|
} else {
|
continue;
|
}
|
// Position i needs to be filled.
|
while (undefs > i) {
|
current = elements->get(undefs);
|
if (current->IsTheHole(isolate)) {
|
holes--;
|
undefs--;
|
} else if (current->IsUndefined(isolate)) {
|
undefs--;
|
} else {
|
elements->set(i, current, write_barrier);
|
break;
|
}
|
}
|
}
|
result = undefs;
|
while (undefs < holes) {
|
elements->set_undefined(isolate, undefs);
|
undefs++;
|
}
|
while (holes < limit) {
|
elements->set_the_hole(isolate, holes);
|
holes++;
|
}
|
}
|
|
DCHECK_LE(result, limit);
|
return *isolate->factory()->NewNumberFromUint(result);
|
}
|
|
// Copy element at index from source to target only if target does not have the
|
// element on its own. Returns true if a copy occurred, false if not
|
// and Nothing if an exception occurred.
|
V8_WARN_UNUSED_RESULT
|
Maybe<bool> ConditionalCopy(Isolate* isolate, Handle<JSReceiver> source,
|
Handle<JSReceiver> target, uint32_t index) {
|
Maybe<bool> source_has_prop = JSReceiver::HasOwnProperty(source, index);
|
MAYBE_RETURN(source_has_prop, Nothing<bool>());
|
if (!source_has_prop.FromJust()) return Just(false);
|
|
Maybe<bool> target_has_prop = JSReceiver::HasOwnProperty(target, index);
|
MAYBE_RETURN(target_has_prop, Nothing<bool>());
|
if (target_has_prop.FromJust()) return Just(false);
|
|
Handle<Object> source_element;
|
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
isolate, source_element, JSReceiver::GetElement(isolate, source, index),
|
Nothing<bool>());
|
|
Handle<Object> set_result;
|
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
isolate, set_result,
|
JSReceiver::SetElement(isolate, target, index, source_element,
|
LanguageMode::kStrict),
|
Nothing<bool>());
|
|
return Just(true);
|
}
|
|
// Copy elements in the range 0..length from objects prototype chain
|
// to object itself, if object has holes. Returns null on error and undefined on
|
// success.
|
V8_WARN_UNUSED_RESULT
|
MaybeHandle<Object> CopyFromPrototype(Isolate* isolate,
|
Handle<JSReceiver> object,
|
uint32_t length) {
|
for (PrototypeIterator iter(isolate, object, kStartAtPrototype);
|
!iter.IsAtEnd(); iter.Advance()) {
|
Handle<JSReceiver> current(PrototypeIterator::GetCurrent<JSReceiver>(iter));
|
|
if (current->IsJSProxy()) {
|
for (uint32_t i = 0; i < length; ++i) {
|
MAYBE_RETURN_NULL(ConditionalCopy(isolate, current, object, i));
|
}
|
} else {
|
Handle<FixedArray> keys = JSReceiver::GetOwnElementIndices(
|
isolate, object, Handle<JSObject>::cast(current));
|
|
uint32_t num_indices = keys->length();
|
for (uint32_t i = 0; i < num_indices; ++i) {
|
uint32_t idx = NumberToUint32(keys->get(i));
|
|
// Prototype might have indices that go past length, but we are only
|
// interested in the range [0, length).
|
if (idx >= length) break;
|
|
MAYBE_RETURN_NULL(ConditionalCopy(isolate, current, object, idx));
|
}
|
}
|
}
|
return isolate->factory()->undefined_value();
|
}
|
|
} // namespace
|
|
RUNTIME_FUNCTION(Runtime_PrepareElementsForSort) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(2, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, object, 0);
|
CONVERT_NUMBER_CHECKED(uint32_t, length, Uint32, args[1]);
|
|
if (isolate->debug_execution_mode() == DebugInfo::kSideEffects) {
|
if (!isolate->debug()->PerformSideEffectCheckForObject(object)) {
|
return ReadOnlyRoots(isolate).exception();
|
}
|
}
|
|
// Counter for sorting arrays that have non-packed elements and where either
|
// the ElementsProtector is invalid or the prototype does not match
|
// Array.prototype.
|
if (object->IsJSArray() &&
|
!Handle<JSArray>::cast(object)->HasFastPackedElements()) {
|
JSObject* initial_array_proto = JSObject::cast(
|
isolate->native_context()->get(Context::INITIAL_ARRAY_PROTOTYPE_INDEX));
|
if (!isolate->IsNoElementsProtectorIntact() ||
|
object->map()->prototype() != initial_array_proto) {
|
isolate->CountUsage(
|
v8::Isolate::kArrayPrototypeSortJSArrayModifiedPrototype);
|
}
|
}
|
|
if (!object->IsJSArray()) {
|
RETURN_FAILURE_ON_EXCEPTION(isolate,
|
CopyFromPrototype(isolate, object, length));
|
}
|
return RemoveArrayHoles(isolate, object, length);
|
}
|
|
// Move contents of argument 0 (an array) to argument 1 (an array)
|
RUNTIME_FUNCTION(Runtime_MoveArrayContents) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(2, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(JSArray, from, 0);
|
CONVERT_ARG_HANDLE_CHECKED(JSArray, to, 1);
|
JSObject::ValidateElements(*from);
|
JSObject::ValidateElements(*to);
|
|
Handle<FixedArrayBase> new_elements(from->elements(), isolate);
|
ElementsKind from_kind = from->GetElementsKind();
|
Handle<Map> new_map = JSObject::GetElementsTransitionMap(to, from_kind);
|
JSObject::SetMapAndElements(to, new_map, new_elements);
|
to->set_length(from->length());
|
|
from->initialize_elements();
|
from->set_length(Smi::kZero);
|
|
JSObject::ValidateElements(*to);
|
return *to;
|
}
|
|
|
// How many elements does this object/array have?
|
RUNTIME_FUNCTION(Runtime_EstimateNumberOfElements) {
|
DisallowHeapAllocation no_gc;
|
HandleScope scope(isolate);
|
DCHECK_EQ(1, args.length());
|
CONVERT_ARG_CHECKED(JSArray, array, 0);
|
FixedArrayBase* elements = array->elements();
|
SealHandleScope shs(isolate);
|
if (elements->IsNumberDictionary()) {
|
int result = NumberDictionary::cast(elements)->NumberOfElements();
|
return Smi::FromInt(result);
|
} else {
|
DCHECK(array->length()->IsSmi());
|
// For packed elements, we know the exact number of elements
|
int length = elements->length();
|
ElementsKind kind = array->GetElementsKind();
|
if (IsFastPackedElementsKind(kind)) {
|
return Smi::FromInt(length);
|
}
|
// For holey elements, take samples from the buffer checking for holes
|
// to generate the estimate.
|
const int kNumberOfHoleCheckSamples = 97;
|
int increment = (length < kNumberOfHoleCheckSamples)
|
? 1
|
: static_cast<int>(length / kNumberOfHoleCheckSamples);
|
ElementsAccessor* accessor = array->GetElementsAccessor();
|
int holes = 0;
|
for (int i = 0; i < length; i += increment) {
|
if (!accessor->HasElement(array, i, elements)) {
|
++holes;
|
}
|
}
|
int estimate = static_cast<int>((kNumberOfHoleCheckSamples - holes) /
|
kNumberOfHoleCheckSamples * length);
|
return Smi::FromInt(estimate);
|
}
|
}
|
|
|
// Returns an array that tells you where in the [0, length) interval an array
|
// might have elements. Can either return an array of keys (positive integers
|
// or undefined) or a number representing the positive length of an interval
|
// starting at index 0.
|
// Intervals can span over some keys that are not in the object.
|
RUNTIME_FUNCTION(Runtime_GetArrayKeys) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(2, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
|
CONVERT_NUMBER_CHECKED(uint32_t, length, Uint32, args[1]);
|
ElementsKind kind = array->GetElementsKind();
|
|
if (IsFastElementsKind(kind) || IsFixedTypedArrayElementsKind(kind)) {
|
uint32_t actual_length = static_cast<uint32_t>(array->elements()->length());
|
return *isolate->factory()->NewNumberFromUint(Min(actual_length, length));
|
}
|
|
if (kind == FAST_STRING_WRAPPER_ELEMENTS) {
|
int string_length =
|
String::cast(Handle<JSValue>::cast(array)->value())->length();
|
int backing_store_length = array->elements()->length();
|
return *isolate->factory()->NewNumberFromUint(
|
Min(length,
|
static_cast<uint32_t>(Max(string_length, backing_store_length))));
|
}
|
|
KeyAccumulator accumulator(isolate, KeyCollectionMode::kOwnOnly,
|
ALL_PROPERTIES);
|
for (PrototypeIterator iter(isolate, array, kStartAtReceiver);
|
!iter.IsAtEnd(); iter.Advance()) {
|
Handle<JSReceiver> current(PrototypeIterator::GetCurrent<JSReceiver>(iter));
|
if (current->HasComplexElements()) {
|
return *isolate->factory()->NewNumberFromUint(length);
|
}
|
accumulator.CollectOwnElementIndices(array,
|
Handle<JSObject>::cast(current));
|
}
|
// Erase any keys >= length.
|
Handle<FixedArray> keys =
|
accumulator.GetKeys(GetKeysConversion::kKeepNumbers);
|
int j = 0;
|
for (int i = 0; i < keys->length(); i++) {
|
if (NumberToUint32(keys->get(i)) >= length) continue;
|
if (i != j) keys->set(j, keys->get(i));
|
j++;
|
}
|
|
keys = FixedArray::ShrinkOrEmpty(isolate, keys, j);
|
return *isolate->factory()->NewJSArrayWithElements(keys);
|
}
|
|
RUNTIME_FUNCTION(Runtime_TrySliceSimpleNonFastElements) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(3, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
|
CONVERT_SMI_ARG_CHECKED(first, 1);
|
CONVERT_SMI_ARG_CHECKED(count, 2);
|
uint32_t length = first + count;
|
|
// Only handle elements kinds that have a ElementsAccessor Slice
|
// implementation.
|
if (receiver->IsJSArray()) {
|
// This "fastish" path must make sure the destination array is a JSArray.
|
if (!isolate->IsArraySpeciesLookupChainIntact() ||
|
!JSArray::cast(*receiver)->HasArrayPrototype(isolate)) {
|
return Smi::FromInt(0);
|
}
|
} else {
|
int len;
|
if (!receiver->IsJSObject() ||
|
!JSSloppyArgumentsObject::GetSloppyArgumentsLength(
|
isolate, Handle<JSObject>::cast(receiver), &len) ||
|
(length > static_cast<uint32_t>(len))) {
|
return Smi::FromInt(0);
|
}
|
}
|
|
// This "fastish" path must also ensure that elements are simple (no
|
// geters/setters), no elements on prototype chain.
|
Handle<JSObject> object(Handle<JSObject>::cast(receiver));
|
if (!JSObject::PrototypeHasNoElements(isolate, *object) ||
|
object->HasComplexElements()) {
|
return Smi::FromInt(0);
|
}
|
|
ElementsAccessor* accessor = object->GetElementsAccessor();
|
return *accessor->Slice(object, first, length);
|
}
|
|
RUNTIME_FUNCTION(Runtime_NewArray) {
|
HandleScope scope(isolate);
|
DCHECK_LE(3, args.length());
|
int const argc = args.length() - 3;
|
// TODO(bmeurer): Remove this Arguments nonsense.
|
Arguments argv(argc, args.arguments() - 1);
|
CONVERT_ARG_HANDLE_CHECKED(JSFunction, constructor, 0);
|
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, new_target, argc + 1);
|
CONVERT_ARG_HANDLE_CHECKED(HeapObject, type_info, argc + 2);
|
// TODO(bmeurer): Use MaybeHandle to pass around the AllocationSite.
|
Handle<AllocationSite> site = type_info->IsAllocationSite()
|
? Handle<AllocationSite>::cast(type_info)
|
: Handle<AllocationSite>::null();
|
|
Factory* factory = isolate->factory();
|
|
// If called through new, new.target can be:
|
// - a subclass of constructor,
|
// - a proxy wrapper around constructor, or
|
// - the constructor itself.
|
// If called through Reflect.construct, it's guaranteed to be a constructor by
|
// REFLECT_CONSTRUCT_PREPARE.
|
DCHECK(new_target->IsConstructor());
|
|
bool holey = false;
|
bool can_use_type_feedback = !site.is_null();
|
bool can_inline_array_constructor = true;
|
if (argv.length() == 1) {
|
Handle<Object> argument_one = argv.at<Object>(0);
|
if (argument_one->IsSmi()) {
|
int value = Handle<Smi>::cast(argument_one)->value();
|
if (value < 0 ||
|
JSArray::SetLengthWouldNormalize(isolate->heap(), value)) {
|
// the array is a dictionary in this case.
|
can_use_type_feedback = false;
|
} else if (value != 0) {
|
holey = true;
|
if (value >= JSArray::kInitialMaxFastElementArray) {
|
can_inline_array_constructor = false;
|
}
|
}
|
} else {
|
// Non-smi length argument produces a dictionary
|
can_use_type_feedback = false;
|
}
|
}
|
|
Handle<Map> initial_map;
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
isolate, initial_map,
|
JSFunction::GetDerivedMap(isolate, constructor, new_target));
|
|
ElementsKind to_kind = can_use_type_feedback ? site->GetElementsKind()
|
: initial_map->elements_kind();
|
if (holey && !IsHoleyElementsKind(to_kind)) {
|
to_kind = GetHoleyElementsKind(to_kind);
|
// Update the allocation site info to reflect the advice alteration.
|
if (!site.is_null()) site->SetElementsKind(to_kind);
|
}
|
|
// We should allocate with an initial map that reflects the allocation site
|
// advice. Therefore we use AllocateJSObjectFromMap instead of passing
|
// the constructor.
|
initial_map = Map::AsElementsKind(isolate, initial_map, to_kind);
|
|
// If we don't care to track arrays of to_kind ElementsKind, then
|
// don't emit a memento for them.
|
Handle<AllocationSite> allocation_site;
|
if (AllocationSite::ShouldTrack(to_kind)) {
|
allocation_site = site;
|
}
|
|
Handle<JSArray> array = Handle<JSArray>::cast(
|
factory->NewJSObjectFromMap(initial_map, NOT_TENURED, allocation_site));
|
|
factory->NewJSArrayStorage(array, 0, 0, DONT_INITIALIZE_ARRAY_ELEMENTS);
|
|
ElementsKind old_kind = array->GetElementsKind();
|
RETURN_FAILURE_ON_EXCEPTION(isolate,
|
ArrayConstructInitializeElements(array, &argv));
|
if (!site.is_null()) {
|
if ((old_kind != array->GetElementsKind() || !can_use_type_feedback ||
|
!can_inline_array_constructor)) {
|
// The arguments passed in caused a transition. This kind of complexity
|
// can't be dealt with in the inlined optimized array constructor case.
|
// We must mark the allocationsite as un-inlinable.
|
site->SetDoNotInlineCall();
|
}
|
} else {
|
if (old_kind != array->GetElementsKind() || !can_inline_array_constructor) {
|
// We don't have an AllocationSite for this Array constructor invocation,
|
// i.e. it might a call from Array#map or from an Array subclass, so we
|
// just flip the bit on the global protector cell instead.
|
// TODO(bmeurer): Find a better way to mark this. Global protectors
|
// tend to back-fire over time...
|
if (isolate->IsArrayConstructorIntact()) {
|
isolate->InvalidateArrayConstructorProtector();
|
}
|
}
|
}
|
|
return *array;
|
}
|
|
RUNTIME_FUNCTION(Runtime_NormalizeElements) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(1, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
|
CHECK(!array->HasFixedTypedArrayElements());
|
CHECK(!array->IsJSGlobalProxy());
|
JSObject::NormalizeElements(array);
|
return *array;
|
}
|
|
// GrowArrayElements returns a sentinel Smi if the object was normalized or if
|
// the key is negative.
|
RUNTIME_FUNCTION(Runtime_GrowArrayElements) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(2, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
|
CONVERT_NUMBER_CHECKED(int, key, Int32, args[1]);
|
|
if (key < 0) return Smi::kZero;
|
|
uint32_t capacity = static_cast<uint32_t>(object->elements()->length());
|
uint32_t index = static_cast<uint32_t>(key);
|
|
if (index >= capacity) {
|
if (!object->GetElementsAccessor()->GrowCapacity(object, index)) {
|
return Smi::kZero;
|
}
|
}
|
|
return object->elements();
|
}
|
|
|
RUNTIME_FUNCTION(Runtime_HasComplexElements) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(1, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(JSObject, array, 0);
|
for (PrototypeIterator iter(isolate, array, kStartAtReceiver);
|
!iter.IsAtEnd(); iter.Advance()) {
|
if (PrototypeIterator::GetCurrent<JSReceiver>(iter)->HasComplexElements()) {
|
return ReadOnlyRoots(isolate).true_value();
|
}
|
}
|
return ReadOnlyRoots(isolate).false_value();
|
}
|
|
// ES6 22.1.2.2 Array.isArray
|
RUNTIME_FUNCTION(Runtime_ArrayIsArray) {
|
HandleScope shs(isolate);
|
DCHECK_EQ(1, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(Object, object, 0);
|
Maybe<bool> result = Object::IsArray(object);
|
MAYBE_RETURN(result, ReadOnlyRoots(isolate).exception());
|
return isolate->heap()->ToBoolean(result.FromJust());
|
}
|
|
RUNTIME_FUNCTION(Runtime_IsArray) {
|
SealHandleScope shs(isolate);
|
DCHECK_EQ(1, args.length());
|
CONVERT_ARG_CHECKED(Object, obj, 0);
|
return isolate->heap()->ToBoolean(obj->IsJSArray());
|
}
|
|
RUNTIME_FUNCTION(Runtime_ArraySpeciesConstructor) {
|
HandleScope scope(isolate);
|
DCHECK_EQ(1, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(Object, original_array, 0);
|
RETURN_RESULT_OR_FAILURE(
|
isolate, Object::ArraySpeciesConstructor(isolate, original_array));
|
}
|
|
// ES7 22.1.3.11 Array.prototype.includes
|
RUNTIME_FUNCTION(Runtime_ArrayIncludes_Slow) {
|
HandleScope shs(isolate);
|
DCHECK_EQ(3, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(Object, search_element, 1);
|
CONVERT_ARG_HANDLE_CHECKED(Object, from_index, 2);
|
|
// Let O be ? ToObject(this value).
|
Handle<JSReceiver> object;
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
isolate, object, Object::ToObject(isolate, handle(args[0], isolate)));
|
|
// Let len be ? ToLength(? Get(O, "length")).
|
int64_t len;
|
{
|
if (object->map()->instance_type() == JS_ARRAY_TYPE) {
|
uint32_t len32 = 0;
|
bool success = JSArray::cast(*object)->length()->ToArrayLength(&len32);
|
DCHECK(success);
|
USE(success);
|
len = len32;
|
} else {
|
Handle<Object> len_;
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
isolate, len_,
|
Object::GetProperty(isolate, object,
|
isolate->factory()->length_string()));
|
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len_,
|
Object::ToLength(isolate, len_));
|
len = static_cast<int64_t>(len_->Number());
|
DCHECK_EQ(len, len_->Number());
|
}
|
}
|
|
if (len == 0) return ReadOnlyRoots(isolate).false_value();
|
|
// Let n be ? ToInteger(fromIndex). (If fromIndex is undefined, this step
|
// produces the value 0.)
|
int64_t index = 0;
|
if (!from_index->IsUndefined(isolate)) {
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, from_index,
|
Object::ToInteger(isolate, from_index));
|
|
if (V8_LIKELY(from_index->IsSmi())) {
|
int start_from = Smi::ToInt(*from_index);
|
if (start_from < 0) {
|
index = std::max<int64_t>(len + start_from, 0);
|
} else {
|
index = start_from;
|
}
|
} else {
|
DCHECK(from_index->IsHeapNumber());
|
double start_from = from_index->Number();
|
if (start_from >= len) return ReadOnlyRoots(isolate).false_value();
|
if (V8_LIKELY(std::isfinite(start_from))) {
|
if (start_from < 0) {
|
index = static_cast<int64_t>(std::max<double>(start_from + len, 0));
|
} else {
|
index = start_from;
|
}
|
}
|
}
|
|
DCHECK_GE(index, 0);
|
}
|
|
// If the receiver is not a special receiver type, and the length is a valid
|
// element index, perform fast operation tailored to specific ElementsKinds.
|
if (!object->map()->IsSpecialReceiverMap() && len < kMaxUInt32 &&
|
JSObject::PrototypeHasNoElements(isolate, JSObject::cast(*object))) {
|
Handle<JSObject> obj = Handle<JSObject>::cast(object);
|
ElementsAccessor* elements = obj->GetElementsAccessor();
|
Maybe<bool> result = elements->IncludesValue(isolate, obj, search_element,
|
static_cast<uint32_t>(index),
|
static_cast<uint32_t>(len));
|
MAYBE_RETURN(result, ReadOnlyRoots(isolate).exception());
|
return *isolate->factory()->ToBoolean(result.FromJust());
|
}
|
|
// Otherwise, perform slow lookups for special receiver types
|
for (; index < len; ++index) {
|
// Let elementK be the result of ? Get(O, ! ToString(k)).
|
Handle<Object> element_k;
|
{
|
Handle<Object> index_obj = isolate->factory()->NewNumberFromInt64(index);
|
bool success;
|
LookupIterator it = LookupIterator::PropertyOrElement(
|
isolate, object, index_obj, &success);
|
DCHECK(success);
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, element_k,
|
Object::GetProperty(&it));
|
}
|
|
// If SameValueZero(searchElement, elementK) is true, return true.
|
if (search_element->SameValueZero(*element_k)) {
|
return ReadOnlyRoots(isolate).true_value();
|
}
|
}
|
return ReadOnlyRoots(isolate).false_value();
|
}
|
|
RUNTIME_FUNCTION(Runtime_ArrayIndexOf) {
|
HandleScope shs(isolate);
|
DCHECK_EQ(3, args.length());
|
CONVERT_ARG_HANDLE_CHECKED(Object, search_element, 1);
|
CONVERT_ARG_HANDLE_CHECKED(Object, from_index, 2);
|
|
// Let O be ? ToObject(this value).
|
Handle<JSReceiver> object;
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
isolate, object,
|
Object::ToObject(isolate, args.at(0), "Array.prototype.indexOf"));
|
|
// Let len be ? ToLength(? Get(O, "length")).
|
int64_t len;
|
{
|
if (object->IsJSArray()) {
|
uint32_t len32 = 0;
|
bool success = JSArray::cast(*object)->length()->ToArrayLength(&len32);
|
DCHECK(success);
|
USE(success);
|
len = len32;
|
} else {
|
Handle<Object> len_;
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
isolate, len_,
|
Object::GetProperty(isolate, object,
|
isolate->factory()->length_string()));
|
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len_,
|
Object::ToLength(isolate, len_));
|
len = static_cast<int64_t>(len_->Number());
|
DCHECK_EQ(len, len_->Number());
|
}
|
}
|
|
if (len == 0) return Smi::FromInt(-1);
|
|
// Let n be ? ToInteger(fromIndex). (If fromIndex is undefined, this step
|
// produces the value 0.)
|
int64_t start_from;
|
{
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, from_index,
|
Object::ToInteger(isolate, from_index));
|
double fp = from_index->Number();
|
if (fp > len) return Smi::FromInt(-1);
|
if (V8_LIKELY(fp >=
|
static_cast<double>(std::numeric_limits<int64_t>::min()))) {
|
DCHECK(fp < std::numeric_limits<int64_t>::max());
|
start_from = static_cast<int64_t>(fp);
|
} else {
|
start_from = std::numeric_limits<int64_t>::min();
|
}
|
}
|
|
int64_t index;
|
if (start_from >= 0) {
|
index = start_from;
|
} else {
|
index = len + start_from;
|
if (index < 0) {
|
index = 0;
|
}
|
}
|
|
// If the receiver is not a special receiver type, and the length is a valid
|
// element index, perform fast operation tailored to specific ElementsKinds.
|
if (!object->map()->IsSpecialReceiverMap() && len < kMaxUInt32 &&
|
JSObject::PrototypeHasNoElements(isolate, JSObject::cast(*object))) {
|
Handle<JSObject> obj = Handle<JSObject>::cast(object);
|
ElementsAccessor* elements = obj->GetElementsAccessor();
|
Maybe<int64_t> result = elements->IndexOfValue(isolate, obj, search_element,
|
static_cast<uint32_t>(index),
|
static_cast<uint32_t>(len));
|
MAYBE_RETURN(result, ReadOnlyRoots(isolate).exception());
|
return *isolate->factory()->NewNumberFromInt64(result.FromJust());
|
}
|
|
// Otherwise, perform slow lookups for special receiver types
|
for (; index < len; ++index) {
|
// Let elementK be the result of ? Get(O, ! ToString(k)).
|
Handle<Object> element_k;
|
{
|
Handle<Object> index_obj = isolate->factory()->NewNumberFromInt64(index);
|
bool success;
|
LookupIterator it = LookupIterator::PropertyOrElement(
|
isolate, object, index_obj, &success);
|
DCHECK(success);
|
Maybe<bool> present = JSReceiver::HasProperty(&it);
|
MAYBE_RETURN(present, ReadOnlyRoots(isolate).exception());
|
if (!present.FromJust()) continue;
|
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, element_k,
|
Object::GetProperty(&it));
|
if (search_element->StrictEquals(*element_k)) {
|
return *index_obj;
|
}
|
}
|
}
|
return Smi::FromInt(-1);
|
}
|
|
} // namespace internal
|
} // namespace v8
|