Fix array concat to follow the specification in the presence of element getters.
Also fix issue 1175 and 1177. BUG=v8:1175 Review URL: http://codereview.chromium.org/6568007 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@6934 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
ef0f8985ed
commit
68f1c73a06
@ -418,7 +418,6 @@ function ArrayPush() {
|
|||||||
|
|
||||||
|
|
||||||
function ArrayConcat(arg1) { // length == 1
|
function ArrayConcat(arg1) { // length == 1
|
||||||
// TODO: can we just use arguments?
|
|
||||||
var arg_count = %_ArgumentsLength();
|
var arg_count = %_ArgumentsLength();
|
||||||
var arrays = new $Array(1 + arg_count);
|
var arrays = new $Array(1 + arg_count);
|
||||||
arrays[0] = this;
|
arrays[0] = this;
|
||||||
|
@ -36,14 +36,14 @@
|
|||||||
namespace v8 {
|
namespace v8 {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
template<class T>
|
template<typename T>
|
||||||
Handle<T>::Handle(T* obj) {
|
Handle<T>::Handle(T* obj) {
|
||||||
ASSERT(!obj->IsFailure());
|
ASSERT(!obj->IsFailure());
|
||||||
location_ = HandleScope::CreateHandle(obj);
|
location_ = HandleScope::CreateHandle(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <class T>
|
template <typename T>
|
||||||
inline T* Handle<T>::operator*() const {
|
inline T* Handle<T>::operator*() const {
|
||||||
ASSERT(location_ != NULL);
|
ASSERT(location_ != NULL);
|
||||||
ASSERT(reinterpret_cast<Address>(*location_) != kHandleZapValue);
|
ASSERT(reinterpret_cast<Address>(*location_) != kHandleZapValue);
|
||||||
@ -51,6 +51,16 @@ inline T* Handle<T>::operator*() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
HandleCell<T>::HandleCell(T* value)
|
||||||
|
: location_(HandleScope::CreateHandle(value)) { }
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
HandleCell<T>::HandleCell(Handle<T> value)
|
||||||
|
: location_(HandleScope::CreateHandle(*value)) { }
|
||||||
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
inline NoHandleAllocation::NoHandleAllocation() {
|
inline NoHandleAllocation::NoHandleAllocation() {
|
||||||
v8::ImplementationUtilities::HandleScopeData* current =
|
v8::ImplementationUtilities::HandleScopeData* current =
|
||||||
|
@ -39,7 +39,7 @@ namespace internal {
|
|||||||
// Handles are only valid within a HandleScope.
|
// Handles are only valid within a HandleScope.
|
||||||
// When a handle is created for an object a cell is allocated in the heap.
|
// When a handle is created for an object a cell is allocated in the heap.
|
||||||
|
|
||||||
template<class T>
|
template<typename T>
|
||||||
class Handle {
|
class Handle {
|
||||||
public:
|
public:
|
||||||
INLINE(explicit Handle(T** location)) { location_ = location; }
|
INLINE(explicit Handle(T** location)) { location_ = location; }
|
||||||
@ -93,6 +93,55 @@ class Handle {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// A handle-scope based variable. The value stored in the variable can change
|
||||||
|
// over time. The value stored in the variable at any time is a root
|
||||||
|
// for garbage collection.
|
||||||
|
// The variable is backed by the current HandleScope.
|
||||||
|
template <typename T>
|
||||||
|
class HandleCell {
|
||||||
|
public:
|
||||||
|
// Create a new HandleCell holding the given value.
|
||||||
|
explicit HandleCell(Handle<T> value);
|
||||||
|
explicit HandleCell(T* value);
|
||||||
|
|
||||||
|
// Create an alias of an existing HandleCell.
|
||||||
|
explicit HandleCell(const HandleCell<T>& value)
|
||||||
|
: location_(value.location_) { }
|
||||||
|
|
||||||
|
INLINE(T* operator->() const) { return operator*(); }
|
||||||
|
INLINE(T* operator*() const) {
|
||||||
|
return *location_;
|
||||||
|
}
|
||||||
|
INLINE(void operator=(T* value)) {
|
||||||
|
*location_ = value;
|
||||||
|
}
|
||||||
|
INLINE(void operator=(Handle<T> value)) {
|
||||||
|
*location_ = *value;
|
||||||
|
}
|
||||||
|
INLINE(void operator=(const HandleCell<T>& value)) {
|
||||||
|
*location_ = *value.location_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the value of the variable and cast it to a give type.
|
||||||
|
// This is typically used for calling methods on a more specialized type.
|
||||||
|
template <typename S>
|
||||||
|
inline S* cast() {
|
||||||
|
S::cast(*location_);
|
||||||
|
return *reinterpret_cast<S**>(location_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<T> ToHandle() const {
|
||||||
|
return Handle<T>(*location_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Prevent implicit constructor from being created.
|
||||||
|
HandleCell();
|
||||||
|
|
||||||
|
T** location_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// A stack-allocated class that governs a number of local handles.
|
// A stack-allocated class that governs a number of local handles.
|
||||||
// After a handle scope has been created, all local handles will be
|
// After a handle scope has been created, all local handles will be
|
||||||
// allocated within that handle scope until either the handle scope is
|
// allocated within that handle scope until either the handle scope is
|
||||||
|
@ -769,6 +769,10 @@ bool Object::HasSpecificClassOf(String* name) {
|
|||||||
|
|
||||||
|
|
||||||
MaybeObject* Object::GetElement(uint32_t index) {
|
MaybeObject* Object::GetElement(uint32_t index) {
|
||||||
|
// GetElement can trigger a getter which can cause allocation.
|
||||||
|
// This was not always the case. This ASSERT is here to catch
|
||||||
|
// leftover incorrect uses.
|
||||||
|
ASSERT(Heap::IsAllocationAllowed());
|
||||||
return GetElementWithReceiver(this, index);
|
return GetElementWithReceiver(this, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
702
src/runtime.cc
702
src/runtime.cc
@ -8028,377 +8028,448 @@ static MaybeObject* Runtime_PushIfAbsent(Arguments args) {
|
|||||||
class ArrayConcatVisitor {
|
class ArrayConcatVisitor {
|
||||||
public:
|
public:
|
||||||
ArrayConcatVisitor(Handle<FixedArray> storage,
|
ArrayConcatVisitor(Handle<FixedArray> storage,
|
||||||
uint32_t index_limit,
|
|
||||||
bool fast_elements) :
|
bool fast_elements) :
|
||||||
storage_(storage), index_limit_(index_limit),
|
storage_(storage),
|
||||||
index_offset_(0), fast_elements_(fast_elements) { }
|
index_offset_(0u),
|
||||||
|
fast_elements_(fast_elements) { }
|
||||||
|
|
||||||
void visit(uint32_t i, Handle<Object> elm) {
|
void visit(uint32_t i, Handle<Object> elm) {
|
||||||
if (i >= index_limit_ - index_offset_) return;
|
if (i >= JSObject::kMaxElementCount - index_offset_) return;
|
||||||
uint32_t index = index_offset_ + i;
|
uint32_t index = index_offset_ + i;
|
||||||
|
|
||||||
if (fast_elements_) {
|
if (fast_elements_) {
|
||||||
ASSERT(index < static_cast<uint32_t>(storage_->length()));
|
if (index < static_cast<uint32_t>(storage_->length())) {
|
||||||
storage_->set(index, *elm);
|
storage_->set(index, *elm);
|
||||||
|
return;
|
||||||
} else {
|
}
|
||||||
Handle<NumberDictionary> dict = Handle<NumberDictionary>::cast(storage_);
|
// Our initial estimate of length was foiled, possibly by
|
||||||
Handle<NumberDictionary> result =
|
// getters on the arrays increasing the length of later arrays
|
||||||
Factory::DictionaryAtNumberPut(dict, index, elm);
|
// during iteration.
|
||||||
if (!result.is_identical_to(dict))
|
// This shouldn't happen in anything but pathological cases.
|
||||||
storage_ = result;
|
SetDictionaryMode(index);
|
||||||
|
// Fall-through to dictionary mode.
|
||||||
}
|
}
|
||||||
}
|
ASSERT(!fast_elements_);
|
||||||
|
Handle<NumberDictionary> dict(storage_.cast<NumberDictionary>());
|
||||||
|
Handle<NumberDictionary> result =
|
||||||
|
Factory::DictionaryAtNumberPut(dict, index, elm);
|
||||||
|
if (!result.is_identical_to(dict)) {
|
||||||
|
storage_ = Handle<FixedArray>::cast(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void increase_index_offset(uint32_t delta) {
|
void increase_index_offset(uint32_t delta) {
|
||||||
if (index_limit_ - index_offset_ < delta) {
|
if (JSObject::kMaxElementCount - index_offset_ < delta) {
|
||||||
index_offset_ = index_limit_;
|
index_offset_ = JSObject::kMaxElementCount;
|
||||||
} else {
|
} else {
|
||||||
index_offset_ += delta;
|
index_offset_ += delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<FixedArray> storage() { return storage_; }
|
Handle<JSArray> ToArray() {
|
||||||
|
Handle<JSArray> array = Factory::NewJSArray(0);
|
||||||
|
Handle<Object> length =
|
||||||
|
Factory::NewNumber(static_cast<double>(index_offset_));
|
||||||
|
Handle<Map> map;
|
||||||
|
if (fast_elements_) {
|
||||||
|
map = Factory::GetFastElementsMap(Handle<Map>(array->map()));
|
||||||
|
} else {
|
||||||
|
map = Factory::GetSlowElementsMap(Handle<Map>(array->map()));
|
||||||
|
}
|
||||||
|
array->set_map(*map);
|
||||||
|
array->set_length(*length);
|
||||||
|
array->set_elements(*storage_);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Handle<FixedArray> storage_;
|
// Convert storage to dictionary mode.
|
||||||
// Limit on the accepted indices. Elements with indices larger than the
|
void SetDictionaryMode(uint32_t index) {
|
||||||
// limit are ignored by the visitor.
|
ASSERT(fast_elements_);
|
||||||
uint32_t index_limit_;
|
Handle<FixedArray> current_storage(storage_.ToHandle());
|
||||||
// Index after last seen index. Always less than or equal to index_limit_.
|
HandleCell<NumberDictionary> slow_storage(
|
||||||
|
Factory::NewNumberDictionary(current_storage->length()));
|
||||||
|
uint32_t current_length = static_cast<uint32_t>(current_storage->length());
|
||||||
|
for (uint32_t i = 0; i < current_length; i++) {
|
||||||
|
HandleScope loop_scope;
|
||||||
|
Handle<Object> element(current_storage->get(i));
|
||||||
|
if (!element->IsTheHole()) {
|
||||||
|
slow_storage =
|
||||||
|
Factory::DictionaryAtNumberPut(slow_storage.ToHandle(), i, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storage_ = slow_storage.cast<FixedArray>();
|
||||||
|
fast_elements_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleCell<FixedArray> storage_;
|
||||||
|
// Index after last seen index. Always less than or equal to
|
||||||
|
// JSObject::kMaxElementCount.
|
||||||
uint32_t index_offset_;
|
uint32_t index_offset_;
|
||||||
const bool fast_elements_;
|
bool fast_elements_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template<class ExternalArrayClass, class ElementType>
|
static uint32_t EstimateElementCount(Handle<JSArray> array) {
|
||||||
static uint32_t IterateExternalArrayElements(Handle<JSObject> receiver,
|
uint32_t length = static_cast<uint32_t>(array->length()->Number());
|
||||||
bool elements_are_ints,
|
int element_count = 0;
|
||||||
bool elements_are_guaranteed_smis,
|
switch (array->GetElementsKind()) {
|
||||||
uint32_t range,
|
|
||||||
ArrayConcatVisitor* visitor) {
|
|
||||||
Handle<ExternalArrayClass> array(
|
|
||||||
ExternalArrayClass::cast(receiver->elements()));
|
|
||||||
uint32_t len = Min(static_cast<uint32_t>(array->length()), range);
|
|
||||||
|
|
||||||
if (visitor != NULL) {
|
|
||||||
if (elements_are_ints) {
|
|
||||||
if (elements_are_guaranteed_smis) {
|
|
||||||
for (uint32_t j = 0; j < len; j++) {
|
|
||||||
Handle<Smi> e(Smi::FromInt(static_cast<int>(array->get(j))));
|
|
||||||
visitor->visit(j, e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (uint32_t j = 0; j < len; j++) {
|
|
||||||
int64_t val = static_cast<int64_t>(array->get(j));
|
|
||||||
if (Smi::IsValid(static_cast<intptr_t>(val))) {
|
|
||||||
Handle<Smi> e(Smi::FromInt(static_cast<int>(val)));
|
|
||||||
visitor->visit(j, e);
|
|
||||||
} else {
|
|
||||||
Handle<Object> e =
|
|
||||||
Factory::NewNumber(static_cast<ElementType>(val));
|
|
||||||
visitor->visit(j, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (uint32_t j = 0; j < len; j++) {
|
|
||||||
Handle<Object> e = Factory::NewNumber(array->get(j));
|
|
||||||
visitor->visit(j, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function that visits elements of a JSObject. Only elements
|
|
||||||
* whose index between 0 and range (exclusive) are visited.
|
|
||||||
*
|
|
||||||
* If the third parameter, visitor, is not NULL, the visitor is called
|
|
||||||
* with parameters, 'visitor_index_offset + element index' and the element.
|
|
||||||
*
|
|
||||||
* It returns the number of visisted elements.
|
|
||||||
*/
|
|
||||||
static uint32_t IterateElements(Handle<JSObject> receiver,
|
|
||||||
uint32_t range,
|
|
||||||
ArrayConcatVisitor* visitor) {
|
|
||||||
uint32_t num_of_elements = 0;
|
|
||||||
|
|
||||||
switch (receiver->GetElementsKind()) {
|
|
||||||
case JSObject::FAST_ELEMENTS: {
|
case JSObject::FAST_ELEMENTS: {
|
||||||
Handle<FixedArray> elements(FixedArray::cast(receiver->elements()));
|
// Fast elements can't have lengths that are not representable by
|
||||||
uint32_t len = elements->length();
|
// a 32-bit signed integer.
|
||||||
if (range < len) {
|
ASSERT(static_cast<int32_t>(FixedArray::kMaxLength) >= 0);
|
||||||
len = range;
|
int fast_length = static_cast<int>(length);
|
||||||
|
Handle<FixedArray> elements(FixedArray::cast(array->elements()));
|
||||||
|
for (int i = 0; i < fast_length; i++) {
|
||||||
|
if (!elements->get(i)->IsTheHole()) element_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint32_t j = 0; j < len; j++) {
|
|
||||||
Handle<Object> e(elements->get(j));
|
|
||||||
if (!e->IsTheHole()) {
|
|
||||||
num_of_elements++;
|
|
||||||
if (visitor) {
|
|
||||||
visitor->visit(j, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSObject::PIXEL_ELEMENTS: {
|
|
||||||
Handle<PixelArray> pixels(PixelArray::cast(receiver->elements()));
|
|
||||||
uint32_t len = pixels->length();
|
|
||||||
if (range < len) {
|
|
||||||
len = range;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t j = 0; j < len; j++) {
|
|
||||||
num_of_elements++;
|
|
||||||
if (visitor != NULL) {
|
|
||||||
Handle<Smi> e(Smi::FromInt(pixels->get(j)));
|
|
||||||
visitor->visit(j, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSObject::EXTERNAL_BYTE_ELEMENTS: {
|
|
||||||
num_of_elements =
|
|
||||||
IterateExternalArrayElements<ExternalByteArray, int8_t>(
|
|
||||||
receiver, true, true, range, visitor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSObject::EXTERNAL_UNSIGNED_BYTE_ELEMENTS: {
|
|
||||||
num_of_elements =
|
|
||||||
IterateExternalArrayElements<ExternalUnsignedByteArray, uint8_t>(
|
|
||||||
receiver, true, true, range, visitor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSObject::EXTERNAL_SHORT_ELEMENTS: {
|
|
||||||
num_of_elements =
|
|
||||||
IterateExternalArrayElements<ExternalShortArray, int16_t>(
|
|
||||||
receiver, true, true, range, visitor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSObject::EXTERNAL_UNSIGNED_SHORT_ELEMENTS: {
|
|
||||||
num_of_elements =
|
|
||||||
IterateExternalArrayElements<ExternalUnsignedShortArray, uint16_t>(
|
|
||||||
receiver, true, true, range, visitor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSObject::EXTERNAL_INT_ELEMENTS: {
|
|
||||||
num_of_elements =
|
|
||||||
IterateExternalArrayElements<ExternalIntArray, int32_t>(
|
|
||||||
receiver, true, false, range, visitor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSObject::EXTERNAL_UNSIGNED_INT_ELEMENTS: {
|
|
||||||
num_of_elements =
|
|
||||||
IterateExternalArrayElements<ExternalUnsignedIntArray, uint32_t>(
|
|
||||||
receiver, true, false, range, visitor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSObject::EXTERNAL_FLOAT_ELEMENTS: {
|
|
||||||
num_of_elements =
|
|
||||||
IterateExternalArrayElements<ExternalFloatArray, float>(
|
|
||||||
receiver, false, false, range, visitor);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case JSObject::DICTIONARY_ELEMENTS: {
|
case JSObject::DICTIONARY_ELEMENTS: {
|
||||||
Handle<NumberDictionary> dict(receiver->element_dictionary());
|
Handle<NumberDictionary> dictionary(
|
||||||
|
NumberDictionary::cast(array->elements()));
|
||||||
|
int capacity = dictionary->Capacity();
|
||||||
|
for (int i = 0; i < capacity; i++) {
|
||||||
|
Handle<Object> key(dictionary->KeyAt(i));
|
||||||
|
if (dictionary->IsKey(*key)) {
|
||||||
|
element_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// External arrays are always dense.
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
// As an estimate, we assume that the prototype doesn't contain any
|
||||||
|
// inherited elements.
|
||||||
|
return element_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<class ExternalArrayClass, class ElementType>
|
||||||
|
static void IterateExternalArrayElements(Handle<JSObject> receiver,
|
||||||
|
bool elements_are_ints,
|
||||||
|
bool elements_are_guaranteed_smis,
|
||||||
|
ArrayConcatVisitor* visitor) {
|
||||||
|
Handle<ExternalArrayClass> array(
|
||||||
|
ExternalArrayClass::cast(receiver->elements()));
|
||||||
|
uint32_t len = static_cast<uint32_t>(array->length());
|
||||||
|
|
||||||
|
ASSERT(visitor != NULL);
|
||||||
|
if (elements_are_ints) {
|
||||||
|
if (elements_are_guaranteed_smis) {
|
||||||
|
for (uint32_t j = 0; j < len; j++) {
|
||||||
|
HandleScope loop_scope;
|
||||||
|
Handle<Smi> e(Smi::FromInt(static_cast<int>(array->get(j))));
|
||||||
|
visitor->visit(j, e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (uint32_t j = 0; j < len; j++) {
|
||||||
|
HandleScope loop_scope;
|
||||||
|
int64_t val = static_cast<int64_t>(array->get(j));
|
||||||
|
if (Smi::IsValid(static_cast<intptr_t>(val))) {
|
||||||
|
Handle<Smi> e(Smi::FromInt(static_cast<int>(val)));
|
||||||
|
visitor->visit(j, e);
|
||||||
|
} else {
|
||||||
|
Handle<Object> e =
|
||||||
|
Factory::NewNumber(static_cast<ElementType>(val));
|
||||||
|
visitor->visit(j, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (uint32_t j = 0; j < len; j++) {
|
||||||
|
HandleScope loop_scope;
|
||||||
|
Handle<Object> e = Factory::NewNumber(array->get(j));
|
||||||
|
visitor->visit(j, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Used for sorting indices in a List<uint32_t>.
|
||||||
|
static int compareUInt32(const uint32_t* ap, const uint32_t* bp) {
|
||||||
|
uint32_t a = *ap;
|
||||||
|
uint32_t b = *bp;
|
||||||
|
return (a == b) ? 0 : (a < b) ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void CollectElementIndices(Handle<JSObject> object,
|
||||||
|
uint32_t range,
|
||||||
|
List<uint32_t>* indices) {
|
||||||
|
JSObject::ElementsKind kind = object->GetElementsKind();
|
||||||
|
switch (kind) {
|
||||||
|
case JSObject::FAST_ELEMENTS: {
|
||||||
|
Handle<FixedArray> elements(FixedArray::cast(object->elements()));
|
||||||
|
uint32_t length = static_cast<uint32_t>(elements->length());
|
||||||
|
if (range < length) length = range;
|
||||||
|
for (uint32_t i = 0; i < length; i++) {
|
||||||
|
if (!elements->get(i)->IsTheHole()) {
|
||||||
|
indices->Add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::DICTIONARY_ELEMENTS: {
|
||||||
|
Handle<NumberDictionary> dict(NumberDictionary::cast(object->elements()));
|
||||||
uint32_t capacity = dict->Capacity();
|
uint32_t capacity = dict->Capacity();
|
||||||
for (uint32_t j = 0; j < capacity; j++) {
|
for (uint32_t j = 0; j < capacity; j++) {
|
||||||
|
HandleScope loop_scope;
|
||||||
Handle<Object> k(dict->KeyAt(j));
|
Handle<Object> k(dict->KeyAt(j));
|
||||||
if (dict->IsKey(*k)) {
|
if (dict->IsKey(*k)) {
|
||||||
ASSERT(k->IsNumber());
|
ASSERT(k->IsNumber());
|
||||||
uint32_t index = static_cast<uint32_t>(k->Number());
|
uint32_t index = static_cast<uint32_t>(k->Number());
|
||||||
if (index < range) {
|
if (index < range) {
|
||||||
num_of_elements++;
|
indices->Add(index);
|
||||||
if (visitor) {
|
|
||||||
visitor->visit(index, Handle<Object>(dict->ValueAt(j)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default: {
|
||||||
|
int dense_elements_length;
|
||||||
|
switch (kind) {
|
||||||
|
case JSObject::PIXEL_ELEMENTS: {
|
||||||
|
dense_elements_length =
|
||||||
|
PixelArray::cast(object->elements())->length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_BYTE_ELEMENTS: {
|
||||||
|
dense_elements_length =
|
||||||
|
ExternalByteArray::cast(object->elements())->length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_UNSIGNED_BYTE_ELEMENTS: {
|
||||||
|
dense_elements_length =
|
||||||
|
ExternalUnsignedByteArray::cast(object->elements())->length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_SHORT_ELEMENTS: {
|
||||||
|
dense_elements_length =
|
||||||
|
ExternalShortArray::cast(object->elements())->length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_UNSIGNED_SHORT_ELEMENTS: {
|
||||||
|
dense_elements_length =
|
||||||
|
ExternalUnsignedShortArray::cast(object->elements())->length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_INT_ELEMENTS: {
|
||||||
|
dense_elements_length =
|
||||||
|
ExternalIntArray::cast(object->elements())->length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_UNSIGNED_INT_ELEMENTS: {
|
||||||
|
dense_elements_length =
|
||||||
|
ExternalUnsignedIntArray::cast(object->elements())->length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_FLOAT_ELEMENTS: {
|
||||||
|
dense_elements_length =
|
||||||
|
ExternalFloatArray::cast(object->elements())->length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint32_t length = static_cast<uint32_t>(dense_elements_length);
|
||||||
|
if (range <= length) {
|
||||||
|
length = range;
|
||||||
|
// We will add all indices, so we might as well clear it first
|
||||||
|
// and avoid duplicates.
|
||||||
|
indices->Clear();
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < length; i++) {
|
||||||
|
indices->Add(i);
|
||||||
|
}
|
||||||
|
if (length == range) return; // All indices accounted for already.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Object> prototype(object->GetPrototype());
|
||||||
|
if (prototype->IsJSObject()) {
|
||||||
|
// The prototype will usually have no inherited element indices,
|
||||||
|
// but we have to check.
|
||||||
|
CollectElementIndices(Handle<JSObject>::cast(prototype), range, indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper function that visits elements of a JSArray in numerical
|
||||||
|
* order.
|
||||||
|
*
|
||||||
|
* The visitor argument called for each existing element in the array
|
||||||
|
* with the element index and the element's value.
|
||||||
|
* Afterwards it increments the base-index of the visitor by the array
|
||||||
|
* length.
|
||||||
|
*/
|
||||||
|
static void IterateElements(Handle<JSArray> receiver,
|
||||||
|
ArrayConcatVisitor* visitor) {
|
||||||
|
uint32_t length = static_cast<uint32_t>(receiver->length()->Number());
|
||||||
|
switch (receiver->GetElementsKind()) {
|
||||||
|
case JSObject::FAST_ELEMENTS: {
|
||||||
|
// Run through the elements FixedArray and use HasElement and GetElement
|
||||||
|
// to check the prototype for missing elements.
|
||||||
|
Handle<FixedArray> elements(FixedArray::cast(receiver->elements()));
|
||||||
|
int fast_length = static_cast<int>(length);
|
||||||
|
ASSERT(fast_length <= elements->length());
|
||||||
|
for (int j = 0; j < fast_length; j++) {
|
||||||
|
HandleScope loop_scope;
|
||||||
|
Handle<Object> element_value(elements->get(j));
|
||||||
|
if (!element_value->IsTheHole()) {
|
||||||
|
visitor->visit(j, element_value);
|
||||||
|
} else if (receiver->HasElement(j)) {
|
||||||
|
// Call GetElement on receiver, not its prototype, or getters won't
|
||||||
|
// have the correct receiver.
|
||||||
|
element_value = GetElement(receiver, j);
|
||||||
|
visitor->visit(j, element_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::DICTIONARY_ELEMENTS: {
|
||||||
|
Handle<NumberDictionary> dict(receiver->element_dictionary());
|
||||||
|
List<uint32_t> indices(dict->Capacity() / 2);
|
||||||
|
// Collect all indices in the object and the prototypes less
|
||||||
|
// than length. This might introduce duplicates in the indices list.
|
||||||
|
CollectElementIndices(receiver, length, &indices);
|
||||||
|
indices.Sort(&compareUInt32);
|
||||||
|
int j = 0;
|
||||||
|
int n = indices.length();
|
||||||
|
while (j < n) {
|
||||||
|
HandleScope loop_scope;
|
||||||
|
uint32_t index = indices[j];
|
||||||
|
Handle<Object> element = GetElement(receiver, index);
|
||||||
|
visitor->visit(index, element);
|
||||||
|
// Skip to next different index (i.e., omit duplicates).
|
||||||
|
do {
|
||||||
|
j++;
|
||||||
|
} while (j < n && indices[j] == index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::PIXEL_ELEMENTS: {
|
||||||
|
Handle<PixelArray> pixels(PixelArray::cast(receiver->elements()));
|
||||||
|
for (uint32_t j = 0; j < length; j++) {
|
||||||
|
Handle<Smi> e(Smi::FromInt(pixels->get(j)));
|
||||||
|
visitor->visit(j, e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_BYTE_ELEMENTS: {
|
||||||
|
IterateExternalArrayElements<ExternalByteArray, int8_t>(
|
||||||
|
receiver, true, true, visitor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_UNSIGNED_BYTE_ELEMENTS: {
|
||||||
|
IterateExternalArrayElements<ExternalUnsignedByteArray, uint8_t>(
|
||||||
|
receiver, true, true, visitor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_SHORT_ELEMENTS: {
|
||||||
|
IterateExternalArrayElements<ExternalShortArray, int16_t>(
|
||||||
|
receiver, true, true, visitor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_UNSIGNED_SHORT_ELEMENTS: {
|
||||||
|
IterateExternalArrayElements<ExternalUnsignedShortArray, uint16_t>(
|
||||||
|
receiver, true, true, visitor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_INT_ELEMENTS: {
|
||||||
|
IterateExternalArrayElements<ExternalIntArray, int32_t>(
|
||||||
|
receiver, true, false, visitor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_UNSIGNED_INT_ELEMENTS: {
|
||||||
|
IterateExternalArrayElements<ExternalUnsignedIntArray, uint32_t>(
|
||||||
|
receiver, true, false, visitor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case JSObject::EXTERNAL_FLOAT_ELEMENTS: {
|
||||||
|
IterateExternalArrayElements<ExternalFloatArray, float>(
|
||||||
|
receiver, false, false, visitor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
visitor->increase_index_offset(length);
|
||||||
return num_of_elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function that visits elements of an Array object, and elements
|
|
||||||
* on its prototypes.
|
|
||||||
*
|
|
||||||
* Elements on prototypes are visited first, and only elements whose indices
|
|
||||||
* less than Array length are visited.
|
|
||||||
*
|
|
||||||
* If a ArrayConcatVisitor object is given, the visitor is called with
|
|
||||||
* parameters, element's index + visitor_index_offset and the element.
|
|
||||||
*
|
|
||||||
* The returned number of elements is an upper bound on the actual number
|
|
||||||
* of elements added. If the same element occurs in more than one object
|
|
||||||
* in the array's prototype chain, it will be counted more than once, but
|
|
||||||
* will only occur once in the result.
|
|
||||||
*/
|
|
||||||
static uint32_t IterateArrayAndPrototypeElements(Handle<JSArray> array,
|
|
||||||
ArrayConcatVisitor* visitor) {
|
|
||||||
uint32_t range = static_cast<uint32_t>(array->length()->Number());
|
|
||||||
Handle<Object> obj = array;
|
|
||||||
|
|
||||||
static const int kEstimatedPrototypes = 3;
|
|
||||||
List< Handle<JSObject> > objects(kEstimatedPrototypes);
|
|
||||||
|
|
||||||
// Visit prototype first. If an element on the prototype is shadowed by
|
|
||||||
// the inheritor using the same index, the ArrayConcatVisitor visits
|
|
||||||
// the prototype element before the shadowing element.
|
|
||||||
// The visitor can simply overwrite the old value by new value using
|
|
||||||
// the same index. This follows Array::concat semantics.
|
|
||||||
while (!obj->IsNull()) {
|
|
||||||
objects.Add(Handle<JSObject>::cast(obj));
|
|
||||||
obj = Handle<Object>(obj->GetPrototype());
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t nof_elements = 0;
|
|
||||||
for (int i = objects.length() - 1; i >= 0; i--) {
|
|
||||||
Handle<JSObject> obj = objects[i];
|
|
||||||
uint32_t encountered_elements =
|
|
||||||
IterateElements(Handle<JSObject>::cast(obj), range, visitor);
|
|
||||||
|
|
||||||
if (encountered_elements > JSObject::kMaxElementCount - nof_elements) {
|
|
||||||
nof_elements = JSObject::kMaxElementCount;
|
|
||||||
} else {
|
|
||||||
nof_elements += encountered_elements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nof_elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper function of Runtime_ArrayConcat.
|
|
||||||
*
|
|
||||||
* The first argument is an Array of arrays and objects. It is the
|
|
||||||
* same as the arguments array of Array::concat JS function.
|
|
||||||
*
|
|
||||||
* If an argument is an Array object, the function visits array
|
|
||||||
* elements. If an argument is not an Array object, the function
|
|
||||||
* visits the object as if it is an one-element array.
|
|
||||||
*
|
|
||||||
* If the result array index overflows 32-bit unsigned integer, the rounded
|
|
||||||
* non-negative number is used as new length. For example, if one
|
|
||||||
* array length is 2^32 - 1, second array length is 1, the
|
|
||||||
* concatenated array length is 0.
|
|
||||||
* TODO(lrn) Change length behavior to ECMAScript 5 specification (length
|
|
||||||
* is one more than the last array index to get a value assigned).
|
|
||||||
*/
|
|
||||||
static uint32_t IterateArguments(Handle<JSArray> arguments,
|
|
||||||
ArrayConcatVisitor* visitor) {
|
|
||||||
uint32_t visited_elements = 0;
|
|
||||||
uint32_t num_of_args = static_cast<uint32_t>(arguments->length()->Number());
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < num_of_args; i++) {
|
|
||||||
Object *element;
|
|
||||||
MaybeObject* maybe_element = arguments->GetElement(i);
|
|
||||||
// This if() is not expected to fail, but we have the check in the
|
|
||||||
// interest of hardening the runtime calls.
|
|
||||||
if (maybe_element->ToObject(&element)) {
|
|
||||||
Handle<Object> obj(element);
|
|
||||||
if (obj->IsJSArray()) {
|
|
||||||
Handle<JSArray> array = Handle<JSArray>::cast(obj);
|
|
||||||
uint32_t len = static_cast<uint32_t>(array->length()->Number());
|
|
||||||
uint32_t nof_elements =
|
|
||||||
IterateArrayAndPrototypeElements(array, visitor);
|
|
||||||
// Total elements of array and its prototype chain can be more than
|
|
||||||
// the array length, but ArrayConcat can only concatenate at most
|
|
||||||
// the array length number of elements. We use the length as an estimate
|
|
||||||
// for the actual number of elements added.
|
|
||||||
uint32_t added_elements = (nof_elements > len) ? len : nof_elements;
|
|
||||||
if (JSArray::kMaxElementCount - visited_elements < added_elements) {
|
|
||||||
visited_elements = JSArray::kMaxElementCount;
|
|
||||||
} else {
|
|
||||||
visited_elements += added_elements;
|
|
||||||
}
|
|
||||||
if (visitor) visitor->increase_index_offset(len);
|
|
||||||
} else {
|
|
||||||
if (visitor) {
|
|
||||||
visitor->visit(0, obj);
|
|
||||||
visitor->increase_index_offset(1);
|
|
||||||
}
|
|
||||||
if (visited_elements < JSArray::kMaxElementCount) {
|
|
||||||
visited_elements++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return visited_elements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array::concat implementation.
|
* Array::concat implementation.
|
||||||
* See ECMAScript 262, 15.4.4.4.
|
* See ECMAScript 262, 15.4.4.4.
|
||||||
* TODO(lrn): Fix non-compliance for very large concatenations and update to
|
* TODO(581): Fix non-compliance for very large concatenations and update to
|
||||||
* following the ECMAScript 5 specification.
|
* following the ECMAScript 5 specification.
|
||||||
*/
|
*/
|
||||||
static MaybeObject* Runtime_ArrayConcat(Arguments args) {
|
static MaybeObject* Runtime_ArrayConcat(Arguments args) {
|
||||||
ASSERT(args.length() == 1);
|
ASSERT(args.length() == 1);
|
||||||
HandleScope handle_scope;
|
HandleScope handle_scope;
|
||||||
|
|
||||||
CONVERT_CHECKED(JSArray, arg_arrays, args[0]);
|
CONVERT_ARG_CHECKED(JSArray, arguments, 0);
|
||||||
Handle<JSArray> arguments(arg_arrays);
|
int argument_count = static_cast<int>(arguments->length()->Number());
|
||||||
|
RUNTIME_ASSERT(arguments->HasFastElements());
|
||||||
|
Handle<FixedArray> elements(FixedArray::cast(arguments->elements()));
|
||||||
|
|
||||||
// Pass 1: estimate the number of elements of the result
|
// Pass 1: estimate the length and number of elements of the result.
|
||||||
// (it could be more than real numbers if prototype has elements).
|
// The actual length can be larger if any of the arguments have getters
|
||||||
uint32_t result_length = 0;
|
// that mutate other arguments (but will otherwise be precise).
|
||||||
uint32_t num_of_args = static_cast<uint32_t>(arguments->length()->Number());
|
// The number of elements is precise if there are no inherited elements.
|
||||||
|
|
||||||
{ AssertNoAllocation nogc;
|
uint32_t estimate_result_length = 0;
|
||||||
for (uint32_t i = 0; i < num_of_args; i++) {
|
uint32_t estimate_nof_elements = 0;
|
||||||
Object* obj;
|
{
|
||||||
MaybeObject* maybe_object = arguments->GetElement(i);
|
for (int i = 0; i < argument_count; i++) {
|
||||||
// This if() is not expected to fail, but we have the check in the
|
HandleScope loop_scope;
|
||||||
// interest of hardening the runtime calls.
|
Handle<Object> obj(elements->get(i));
|
||||||
if (maybe_object->ToObject(&obj)) {
|
uint32_t length_estimate;
|
||||||
uint32_t length_estimate;
|
uint32_t element_estimate;
|
||||||
if (obj->IsJSArray()) {
|
if (obj->IsJSArray()) {
|
||||||
length_estimate =
|
Handle<JSArray> array(Handle<JSArray>::cast(obj));
|
||||||
static_cast<uint32_t>(JSArray::cast(obj)->length()->Number());
|
length_estimate =
|
||||||
} else {
|
static_cast<uint32_t>(array->length()->Number());
|
||||||
length_estimate = 1;
|
element_estimate =
|
||||||
}
|
EstimateElementCount(array);
|
||||||
if (JSObject::kMaxElementCount - result_length < length_estimate) {
|
} else {
|
||||||
result_length = JSObject::kMaxElementCount;
|
length_estimate = 1;
|
||||||
break;
|
element_estimate = 1;
|
||||||
}
|
}
|
||||||
result_length += length_estimate;
|
// Avoid overflows by capping at kMaxElementCount.
|
||||||
|
if (JSObject::kMaxElementCount - estimate_result_length <
|
||||||
|
length_estimate) {
|
||||||
|
estimate_result_length = JSObject::kMaxElementCount;
|
||||||
|
} else {
|
||||||
|
estimate_result_length += length_estimate;
|
||||||
|
}
|
||||||
|
if (JSObject::kMaxElementCount - estimate_nof_elements <
|
||||||
|
element_estimate) {
|
||||||
|
estimate_nof_elements = JSObject::kMaxElementCount;
|
||||||
|
} else {
|
||||||
|
estimate_nof_elements += element_estimate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate an empty array, will set map, length, and content later.
|
|
||||||
Handle<JSArray> result = Factory::NewJSArray(0);
|
|
||||||
|
|
||||||
uint32_t estimate_nof_elements = IterateArguments(arguments, NULL);
|
|
||||||
// If estimated number of elements is more than half of length, a
|
// If estimated number of elements is more than half of length, a
|
||||||
// fixed array (fast case) is more time and space-efficient than a
|
// fixed array (fast case) is more time and space-efficient than a
|
||||||
// dictionary.
|
// dictionary.
|
||||||
bool fast_case = (estimate_nof_elements * 2) >= result_length;
|
bool fast_case = (estimate_nof_elements * 2) >= estimate_result_length;
|
||||||
|
|
||||||
Handle<Map> map;
|
|
||||||
Handle<FixedArray> storage;
|
Handle<FixedArray> storage;
|
||||||
if (fast_case) {
|
if (fast_case) {
|
||||||
// The backing storage array must have non-existing elements to
|
// The backing storage array must have non-existing elements to
|
||||||
// preserve holes across concat operations.
|
// preserve holes across concat operations.
|
||||||
map = Factory::GetFastElementsMap(Handle<Map>(result->map()));
|
storage = Factory::NewFixedArrayWithHoles(estimate_result_length);
|
||||||
storage = Factory::NewFixedArrayWithHoles(result_length);
|
|
||||||
} else {
|
} else {
|
||||||
map = Factory::GetSlowElementsMap(Handle<Map>(result->map()));
|
|
||||||
// TODO(126): move 25% pre-allocation logic into Dictionary::Allocate
|
// TODO(126): move 25% pre-allocation logic into Dictionary::Allocate
|
||||||
uint32_t at_least_space_for = estimate_nof_elements +
|
uint32_t at_least_space_for = estimate_nof_elements +
|
||||||
(estimate_nof_elements >> 2);
|
(estimate_nof_elements >> 2);
|
||||||
@ -8406,21 +8477,20 @@ static MaybeObject* Runtime_ArrayConcat(Arguments args) {
|
|||||||
Factory::NewNumberDictionary(at_least_space_for));
|
Factory::NewNumberDictionary(at_least_space_for));
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Object> len = Factory::NewNumber(static_cast<double>(result_length));
|
ArrayConcatVisitor visitor(storage, fast_case);
|
||||||
|
|
||||||
ArrayConcatVisitor visitor(storage, result_length, fast_case);
|
for (int i = 0; i < argument_count; i++) {
|
||||||
|
Handle<Object> obj(elements->get(i));
|
||||||
|
if (obj->IsJSArray()) {
|
||||||
|
Handle<JSArray> array = Handle<JSArray>::cast(obj);
|
||||||
|
IterateElements(array, &visitor);
|
||||||
|
} else {
|
||||||
|
visitor.visit(0, obj);
|
||||||
|
visitor.increase_index_offset(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IterateArguments(arguments, &visitor);
|
return *visitor.ToArray();
|
||||||
|
|
||||||
// Please note:
|
|
||||||
// - the storage might have been changed in the visitor;
|
|
||||||
// - the map and the storage must be set together to avoid breaking
|
|
||||||
// the invariant that the map describes the array's elements.
|
|
||||||
result->set_map(*map);
|
|
||||||
result->set_length(*len);
|
|
||||||
result->set_elements(*visitor.storage());
|
|
||||||
|
|
||||||
return *result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,7 +101,6 @@ while (pos = poses.shift()) {
|
|||||||
assertEquals("undefined", typeof(c[-1]));
|
assertEquals("undefined", typeof(c[-1]));
|
||||||
assertEquals("undefined", typeof(c[0xffffffff]));
|
assertEquals("undefined", typeof(c[0xffffffff]));
|
||||||
assertEquals(c.length, a.length + 1);
|
assertEquals(c.length, a.length + 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
poses = [140, 4000000000];
|
poses = [140, 4000000000];
|
||||||
@ -193,3 +192,46 @@ for (var i = 0; i < holey.length; i++) {
|
|||||||
assertTrue(i in holey);
|
assertTrue(i in holey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Polluted prototype from prior tests.
|
||||||
|
delete Array.prototype[123];
|
||||||
|
|
||||||
|
// Check that concat reads getters in the correct order.
|
||||||
|
var arr1 = [,2];
|
||||||
|
var arr2 = [1,3];
|
||||||
|
var r1 = [].concat(arr1, arr2); // [,2,1,3]
|
||||||
|
assertEquals([,2,1,3], r1);
|
||||||
|
|
||||||
|
// Make first array change length of second array.
|
||||||
|
Object.defineProperty(arr1, 0, {get: function() {
|
||||||
|
arr2.push("X");
|
||||||
|
return undefined;
|
||||||
|
}, configurable: true})
|
||||||
|
var r2 = [].concat(arr1, arr2); // [undefined,2,1,3,"X"]
|
||||||
|
assertEquals([undefined,2,1,3,"X"], r2);
|
||||||
|
|
||||||
|
// Make first array change length of second array massively.
|
||||||
|
arr2.length = 2;
|
||||||
|
Object.defineProperty(arr1, 0, {get: function() {
|
||||||
|
arr2[500000] = "X";
|
||||||
|
return undefined;
|
||||||
|
}, configurable: true})
|
||||||
|
var r3 = [].concat(arr1, arr2); // [undefined,2,1,3,"X"]
|
||||||
|
var expected = [undefined,2,1,3];
|
||||||
|
expected[500000 + 2] = "X";
|
||||||
|
|
||||||
|
assertEquals(expected, r3);
|
||||||
|
|
||||||
|
var arr3 = [];
|
||||||
|
var trace = [];
|
||||||
|
var expectedTrace = []
|
||||||
|
function mkGetter(i) { return function() { trace.push(i); }; }
|
||||||
|
arr3.length = 10000;
|
||||||
|
for (var i = 0; i < 100; i++) {
|
||||||
|
Object.defineProperty(arr3, i * i, {get: mkGetter(i)});
|
||||||
|
expectedTrace[i] = i;
|
||||||
|
expectedTrace[100 + i] = i;
|
||||||
|
}
|
||||||
|
var r4 = [0].concat(arr3, arr3);
|
||||||
|
assertEquals(1 + arr3.length * 2, r4.length);
|
||||||
|
assertEquals(expectedTrace, trace);
|
||||||
|
Loading…
Reference in New Issue
Block a user