diff --git a/src/builtins.cc b/src/builtins.cc index 4bc61b8f6b..6fe2d2413f 100644 --- a/src/builtins.cc +++ b/src/builtins.cc @@ -176,6 +176,27 @@ BUILTIN(EmptyFunction) { } +// TODO(cbruni): check if this is a suitable method on Object +bool ClampedToInteger(Object* object, int* out) { + // This is an extended version of ECMA-262 9.4, but additionally + // clamps values to [kMinInt, kMaxInt] + if (object->IsSmi()) { + *out = Smi::cast(object)->value(); + return true; + } else if (object->IsHeapNumber()) { + *out = FastD2IChecked(HeapNumber::cast(object)->value()); + return true; + } else if (object->IsUndefined()) { + *out = 0; + return true; + } else if (object->IsBoolean()) { + *out = (Oddball::cast(object)->kind() == Oddball::kTrue) ? 1 : 0; + return true; + } + return false; +} + + static void MoveDoubleElements(FixedDoubleArray* dst, int dst_index, FixedDoubleArray* src, int src_index, int len) { if (len == 0) return; @@ -621,7 +642,6 @@ BUILTIN(ArraySlice) { BUILTIN(ArraySplice) { HandleScope scope(isolate); - Heap* heap = isolate->heap(); Handle receiver = args.receiver(); MaybeHandle maybe_elms_obj = EnsureJSArrayWithWritableFastElements(isolate, receiver, &args, 3); @@ -632,209 +652,51 @@ BUILTIN(ArraySplice) { Handle array = Handle::cast(receiver); DCHECK(!array->map()->is_observed()); - int len = Smi::cast(array->length())->value(); - - int n_arguments = args.length() - 1; - + int argument_count = args.length() - 1; int relative_start = 0; - if (n_arguments > 0) { + if (argument_count > 0) { DisallowHeapAllocation no_gc; - Object* arg1 = args[1]; - if (arg1->IsSmi()) { - relative_start = Smi::cast(arg1)->value(); - } else if (arg1->IsHeapNumber()) { - double start = HeapNumber::cast(arg1)->value(); - if (start < kMinInt || start > kMaxInt) { - AllowHeapAllocation allow_allocation; - return CallJsBuiltin(isolate, "$arraySplice", args); - } - relative_start = std::isnan(start) ? 0 : static_cast(start); - } else if (!arg1->IsUndefined()) { + if (!ClampedToInteger(args[1], &relative_start)) { AllowHeapAllocation allow_allocation; return CallJsBuiltin(isolate, "$arraySplice", args); } } + int len = Smi::cast(array->length())->value(); + // clip relative start to [0, len] int actual_start = (relative_start < 0) ? Max(len + relative_start, 0) : Min(relative_start, len); - // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is - // given as a request to delete all the elements from the start. - // And it differs from the case of undefined delete count. - // This does not follow ECMA-262, but we do the same for - // compatibility. int actual_delete_count; - if (n_arguments == 1) { + if (argument_count == 1) { + // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is + // given as a request to delete all the elements from the start. + // And it differs from the case of undefined delete count. + // This does not follow ECMA-262, but we do the same for compatibility. DCHECK(len - actual_start >= 0); actual_delete_count = len - actual_start; } else { - int value = 0; // ToInteger(undefined) == 0 - if (n_arguments > 1) { - DisallowHeapAllocation no_gc; - Object* arg2 = args[2]; - if (arg2->IsSmi()) { - value = Smi::cast(arg2)->value(); - } else { + int delete_count = 0; + DisallowHeapAllocation no_gc; + if (argument_count > 1) { + if (!ClampedToInteger(args[2], &delete_count)) { AllowHeapAllocation allow_allocation; return CallJsBuiltin(isolate, "$arraySplice", args); } } - actual_delete_count = Min(Max(value, 0), len - actual_start); + actual_delete_count = Min(Max(delete_count, 0), len - actual_start); } - ElementsKind elements_kind = array->GetElementsKind(); - - int item_count = (n_arguments > 1) ? (n_arguments - 2) : 0; - int new_length = len - actual_delete_count + item_count; - - // For double mode we do not support changing the length. - if (new_length > len && IsFastDoubleElementsKind(elements_kind)) { - return CallJsBuiltin(isolate, "$arraySplice", args); - } + int add_count = (argument_count > 1) ? (argument_count - 2) : 0; + int new_length = len - actual_delete_count + add_count; if (new_length != len && JSArray::HasReadOnlyLength(array)) { AllowHeapAllocation allow_allocation; return CallJsBuiltin(isolate, "$arraySplice", args); } - - if (new_length == 0) { - Handle result = isolate->factory()->NewJSArrayWithElements( - elms_obj, elements_kind, actual_delete_count); - array->set_elements(heap->empty_fixed_array()); - array->set_length(Smi::FromInt(0)); - return *result; - } - - Handle result_array = - isolate->factory()->NewJSArray(elements_kind, - actual_delete_count, - actual_delete_count); - - if (actual_delete_count > 0) { - DisallowHeapAllocation no_gc; - ElementsAccessor* accessor = array->GetElementsAccessor(); - accessor->CopyElements( - elms_obj, actual_start, elements_kind, - handle(result_array->elements(), isolate), 0, actual_delete_count); - } - - bool elms_changed = false; - if (item_count < actual_delete_count) { - // Shrink the array. - const bool trim_array = !heap->lo_space()->Contains(*elms_obj) && - ((actual_start + item_count) < - (len - actual_delete_count - actual_start)); - if (trim_array) { - const int delta = actual_delete_count - item_count; - - if (elms_obj->IsFixedDoubleArray()) { - Handle elms = - Handle::cast(elms_obj); - MoveDoubleElements(*elms, delta, *elms, 0, actual_start); - } else { - Handle elms = Handle::cast(elms_obj); - DisallowHeapAllocation no_gc; - heap->MoveElements(*elms, delta, 0, actual_start); - } - - if (heap->CanMoveObjectStart(*elms_obj)) { - // On the fast path we move the start of the object in memory. - elms_obj = handle(heap->LeftTrimFixedArray(*elms_obj, delta)); - } else { - // This is the slow path. We are going to move the elements to the left - // by copying them. For trimmed values we store the hole. - if (elms_obj->IsFixedDoubleArray()) { - Handle elms = - Handle::cast(elms_obj); - MoveDoubleElements(*elms, 0, *elms, delta, len - delta); - elms->FillWithHoles(len - delta, len); - } else { - Handle elms = Handle::cast(elms_obj); - DisallowHeapAllocation no_gc; - heap->MoveElements(*elms, 0, delta, len - delta); - elms->FillWithHoles(len - delta, len); - } - } - elms_changed = true; - } else { - if (elms_obj->IsFixedDoubleArray()) { - Handle elms = - Handle::cast(elms_obj); - MoveDoubleElements(*elms, actual_start + item_count, - *elms, actual_start + actual_delete_count, - (len - actual_delete_count - actual_start)); - elms->FillWithHoles(new_length, len); - } else { - Handle elms = Handle::cast(elms_obj); - DisallowHeapAllocation no_gc; - heap->MoveElements(*elms, actual_start + item_count, - actual_start + actual_delete_count, - (len - actual_delete_count - actual_start)); - elms->FillWithHoles(new_length, len); - } - } - } else if (item_count > actual_delete_count) { - Handle elms = Handle::cast(elms_obj); - // Currently fixed arrays cannot grow too big, so - // we should never hit this case. - DCHECK((item_count - actual_delete_count) <= (Smi::kMaxValue - len)); - - // Check if array need to grow. - if (new_length > elms->length()) { - // New backing storage is needed. - int capacity = new_length + (new_length >> 1) + 16; - Handle new_elms = - isolate->factory()->NewUninitializedFixedArray(capacity); - - DisallowHeapAllocation no_gc; - - ElementsKind kind = array->GetElementsKind(); - ElementsAccessor* accessor = array->GetElementsAccessor(); - if (actual_start > 0) { - // Copy the part before actual_start as is. - accessor->CopyElements( - elms, 0, kind, new_elms, 0, actual_start); - } - accessor->CopyElements( - elms, actual_start + actual_delete_count, kind, - new_elms, actual_start + item_count, - ElementsAccessor::kCopyToEndAndInitializeToHole); - - elms_obj = new_elms; - elms_changed = true; - } else { - DisallowHeapAllocation no_gc; - heap->MoveElements(*elms, actual_start + item_count, - actual_start + actual_delete_count, - (len - actual_delete_count - actual_start)); - } - } - - if (IsFastDoubleElementsKind(elements_kind)) { - Handle elms = Handle::cast(elms_obj); - for (int k = actual_start; k < actual_start + item_count; k++) { - Object* arg = args[3 + k - actual_start]; - if (arg->IsSmi()) { - elms->set(k, Smi::cast(arg)->value()); - } else { - elms->set(k, HeapNumber::cast(arg)->value()); - } - } - } else { - Handle elms = Handle::cast(elms_obj); - DisallowHeapAllocation no_gc; - WriteBarrierMode mode = elms->GetWriteBarrierMode(no_gc); - for (int k = actual_start; k < actual_start + item_count; k++) { - elms->set(k, args[3 + k - actual_start], mode); - } - } - - if (elms_changed) { - array->set_elements(*elms_obj); - } - // Set the length. - array->set_length(Smi::FromInt(new_length)); - - return *result_array; + ElementsAccessor* accessor = array->GetElementsAccessor(); + Handle result = accessor->Splice( + array, elms_obj, actual_start, actual_delete_count, args, add_count); + return *result; } diff --git a/src/elements.cc b/src/elements.cc index e0fb6fba09..e133f8f738 100644 --- a/src/elements.cc +++ b/src/elements.cc @@ -115,7 +115,6 @@ static MaybeHandle ThrowArrayLengthRangeError(Isolate* isolate) { Object); } - static void CopyObjectToObjectElements(FixedArrayBase* from_base, ElementsKind from_kind, uint32_t from_start, @@ -586,6 +585,23 @@ class ElementsAccessorBase : public ElementsAccessor { return 0; } + virtual Handle Splice(Handle receiver, + Handle backing_store, + uint32_t start, uint32_t delete_count, + Arguments args, uint32_t add_count) { + return ElementsAccessorSubclass::SpliceImpl(receiver, backing_store, start, + delete_count, args, add_count); + } + + static Handle SpliceImpl(Handle receiver, + Handle backing_store, + uint32_t start, uint32_t delete_count, + Arguments args, uint32_t add_count) { + UNREACHABLE(); + return Handle(); + } + + virtual void SetLength(Handle array, uint32_t length) final { ElementsAccessorSubclass::SetLengthImpl(array, length, handle(array->elements())); @@ -597,23 +613,31 @@ class ElementsAccessorBase : public ElementsAccessor { static Handle ConvertElementsWithCapacity( Handle object, Handle old_elements, ElementsKind from_kind, uint32_t capacity) { + return ConvertElementsWithCapacity( + object, old_elements, from_kind, capacity, + ElementsAccessor::kCopyToEndAndInitializeToHole); + } + + static Handle ConvertElementsWithCapacity( + Handle object, Handle old_elements, + ElementsKind from_kind, uint32_t capacity, int copy_size) { Isolate* isolate = object->GetIsolate(); - Handle elements; + Handle new_elements; if (IsFastDoubleElementsKind(kind())) { - elements = isolate->factory()->NewFixedDoubleArray(capacity); + new_elements = isolate->factory()->NewFixedDoubleArray(capacity); } else { - elements = isolate->factory()->NewUninitializedFixedArray(capacity); + new_elements = isolate->factory()->NewUninitializedFixedArray(capacity); } - int packed = kPackedSizeNotKnown; + int packed_size = kPackedSizeNotKnown; if (IsFastPackedElementsKind(from_kind) && object->IsJSArray()) { - packed = Smi::cast(JSArray::cast(*object)->length())->value(); + packed_size = Smi::cast(JSArray::cast(*object)->length())->value(); } ElementsAccessorSubclass::CopyElementsImpl( - *old_elements, 0, *elements, from_kind, 0, packed, - ElementsAccessor::kCopyToEndAndInitializeToHole); - return elements; + *old_elements, 0, *new_elements, from_kind, 0, packed_size, copy_size); + + return new_elements; } static void GrowCapacityAndConvertImpl(Handle object, @@ -1179,8 +1203,7 @@ class FastElementsAccessor receiver, backing_store, KindTraits::Kind, capacity); } else { // push_size is > 0 and new_length <= elms_len, so backing_store cannot be - // the - // empty_fixed_array. + // the empty_fixed_array. new_elms = backing_store; } @@ -1203,6 +1226,132 @@ class FastElementsAccessor receiver->set_length(Smi::FromInt(new_length)); return new_length; } + + static void MoveElements(Heap* heap, Handle backing_store, + int dst_index, int src_index, int len, + int hole_start, int hole_end) { + UNREACHABLE(); + } + + static Handle SpliceImpl(Handle receiver, + Handle backing_store, + uint32_t start, uint32_t delete_count, + Arguments args, uint32_t add_count) { + Isolate* isolate = receiver->GetIsolate(); + Heap* heap = isolate->heap(); + const uint32_t len = Smi::cast(receiver->length())->value(); + const uint32_t new_length = len - delete_count + add_count; + + if (new_length == 0) { + receiver->set_elements(heap->empty_fixed_array()); + receiver->set_length(Smi::FromInt(0)); + return isolate->factory()->NewJSArrayWithElements( + backing_store, KindTraits::Kind, delete_count); + } + + // construct the result array which holds the deleted elements + Handle deleted_elements = isolate->factory()->NewJSArray( + KindTraits::Kind, delete_count, delete_count); + if (delete_count > 0) { + DisallowHeapAllocation no_gc; + FastElementsAccessorSubclass::CopyElementsImpl( + *backing_store, start, deleted_elements->elements(), KindTraits::Kind, + 0, kPackedSizeNotKnown, delete_count); + } + + // delete and move elements to make space for add_count new elements + bool elms_changed = false; + if (add_count < delete_count) { + elms_changed = SpliceShrinkStep(backing_store, heap, start, delete_count, + add_count, len, new_length); + } else if (add_count > delete_count) { + elms_changed = + SpliceGrowStep(receiver, backing_store, isolate, heap, start, + delete_count, add_count, len, new_length); + } + + // Copy new Elements from args + if (IsFastDoubleElementsKind(KindTraits::Kind)) { + for (uint32_t index = start; index < start + add_count; index++) { + Object* arg = args[3 + index - start]; + FastElementsAccessorSubclass::SetImpl(*backing_store, index, arg); + } + } else { + // FastSmiOrObjectElementsKind + Handle elms = Handle::cast(backing_store); + DisallowHeapAllocation no_gc; + WriteBarrierMode mode = elms->GetWriteBarrierMode(no_gc); + for (uint32_t index = start; index < start + add_count; index++) { + elms->set(index, args[3 + index - start], mode); + } + } + + if (elms_changed) { + receiver->set_elements(*backing_store); + } + receiver->set_length(Smi::FromInt(new_length)); + return deleted_elements; + } + + private: + static bool SpliceShrinkStep(Handle& backing_store, + Heap* heap, uint32_t start, + uint32_t delete_count, uint32_t add_count, + uint32_t len, uint32_t new_length) { + const int move_left_count = len - delete_count - start; + const int move_left_dst_index = start + add_count; + const bool left_trim_array = heap->CanMoveObjectStart(*backing_store) && + (move_left_dst_index < move_left_count); + if (left_trim_array) { + const int delta = delete_count - add_count; + // shift from before the insertion point to the right + FastElementsAccessorSubclass::MoveElements(heap, backing_store, delta, 0, + start, 0, 0); + backing_store = handle(heap->LeftTrimFixedArray(*backing_store, delta)); + return true; + } else { + // No left-trim needed or possible (in this case we left-move and store + // the hole) + FastElementsAccessorSubclass::MoveElements( + heap, backing_store, move_left_dst_index, start + delete_count, + move_left_count, new_length, len); + } + return false; + } + + + static bool SpliceGrowStep(Handle receiver, + Handle& backing_store, + Isolate* isolate, Heap* heap, uint32_t start, + uint32_t delete_count, uint32_t add_count, + uint32_t len, uint32_t new_length) { + // Currently fixed arrays cannot grow too big, so + // we should never hit this case. + DCHECK((add_count - delete_count) <= (Smi::kMaxValue - len)); + // Check if backing_store needs to grow. + if (new_length > static_cast(backing_store->length())) { + // New backing storage is needed. + int capacity = new_length + (new_length >> 1) + 16; + // partially copy all elements up to start + Handle new_elms = + FastElementsAccessorSubclass::ConvertElementsWithCapacity( + receiver, backing_store, KindTraits::Kind, capacity, start); + // Copy the trailing elements after start + delete_count + FastElementsAccessorSubclass::CopyElementsImpl( + *backing_store, start + delete_count, *new_elms, KindTraits::Kind, + start + add_count, kPackedSizeNotKnown, + ElementsAccessor::kCopyToEndAndInitializeToHole); + + backing_store = new_elms; + return true; + } else { + DisallowHeapAllocation no_gc; + FastElementsAccessorSubclass::MoveElements( + heap, backing_store, start + add_count, start + delete_count, + (len - delete_count - start), 0, 0); + } + return false; + } }; @@ -1221,6 +1370,18 @@ class FastSmiOrObjectElementsAccessor return backing_store->get(index); } + static void MoveElements(Heap* heap, Handle backing_store, + int dst_index, int src_index, int len, + int hole_start, int hole_end) { + if (len == 0) return; + Handle dst_elms = Handle::cast(backing_store); + DisallowHeapAllocation no_gc; + heap->MoveElements(*dst_elms, dst_index, src_index, len); + if (hole_start != hole_end) { + dst_elms->FillWithHoles(hole_start, hole_end); + } + } + // NOTE: this method violates the handlified function signature convention: // raw pointer parameters in the function that allocates. // See ElementsAccessor::CopyElements() for details. @@ -1321,6 +1482,19 @@ class FastDoubleElementsAccessor : FastElementsAccessor(name) {} + static void MoveElements(Heap* heap, Handle backing_store, + int dst_index, int src_index, int len, + int hole_start, int hole_end) { + if (len == 0) return; + Handle dst_elms = + Handle::cast(backing_store); + MemMove(dst_elms->data_start() + dst_index, + dst_elms->data_start() + src_index, len * kDoubleSize); + if (hole_start != hole_end) { + dst_elms->FillWithHoles(hole_start, hole_end); + } + } + static void CopyElementsImpl(FixedArrayBase* from, uint32_t from_start, FixedArrayBase* to, ElementsKind from_kind, uint32_t to_start, int packed_size, diff --git a/src/elements.h b/src/elements.h index 0131f0baf0..16be8ecde3 100644 --- a/src/elements.h +++ b/src/elements.h @@ -131,6 +131,11 @@ class ElementsAccessor { Handle backing_store, Object** objects, uint32_t start, int direction) = 0; + virtual Handle Splice(Handle receiver, + Handle backing_store, + uint32_t start, uint32_t delete_count, + Arguments args, uint32_t add_count) = 0; + protected: friend class LookupIterator; diff --git a/test/mjsunit/array-natives-elements.js b/test/mjsunit/array-natives-elements.js index a19a931adb..01a6f21ead 100644 --- a/test/mjsunit/array-natives-elements.js +++ b/test/mjsunit/array-natives-elements.js @@ -151,7 +151,6 @@ function array_natives_test() { assertTrue(%HasFastSmiElements(a3)); assertEquals([1], a3r); assertEquals([2, 2, 3], a3); - a3 = [1.1,2,3]; a3r = a3.splice(0, 0); assertTrue(%HasFastDoubleElements(a3r)); @@ -166,13 +165,12 @@ function array_natives_test() { assertEquals([2, 3], a3); a3 = [1.1,2,3]; a3r = a3.splice(0, 0, 2); - // Commented out since handled in js, which takes the best fit. - // assertTrue(%HasFastDoubleElements(a3r)); - assertTrue(%HasFastSmiElements(a3r)); + assertTrue(%HasFastDoubleElements(a3r)); assertTrue(%HasFastDoubleElements(a3)); assertEquals([], a3r); assertEquals([2, 1.1, 2, 3], a3); a3 = [1.1,2,3]; + assertTrue(%HasFastDoubleElements(a3)); a3r = a3.splice(0, 1, 2); assertTrue(%HasFastDoubleElements(a3r)); assertTrue(%HasFastDoubleElements(a3)); @@ -180,9 +178,7 @@ function array_natives_test() { assertEquals([2, 2, 3], a3); a3 = [1.1,2,3]; a3r = a3.splice(0, 0, 2.1); - // Commented out since handled in js, which takes the best fit. - // assertTrue(%HasFastDoubleElements(a3r)); - assertTrue(%HasFastSmiElements(a3r)); + assertTrue(%HasFastDoubleElements(a3r)); assertTrue(%HasFastDoubleElements(a3)); assertEquals([], a3r); assertEquals([2.1, 1.1, 2, 3], a3); @@ -194,9 +190,7 @@ function array_natives_test() { assertEquals([2.2, 2, 3], a3); a3 = [1,2,3]; a3r = a3.splice(0, 0, 2.1); - // Commented out since handled in js, which takes the best fit. - // assertTrue(%HasFastDoubleElements(a3r)); - assertTrue(%HasFastSmiElements(a3r)); + assertTrue(%HasFastDoubleElements(a3r)); assertTrue(%HasFastDoubleElements(a3)); assertEquals([], a3r); assertEquals([2.1, 1, 2, 3], a3); @@ -206,7 +200,6 @@ function array_natives_test() { assertTrue(%HasFastDoubleElements(a3)); assertEquals([1], a3r); assertEquals([2.2, 2, 3], a3); - a3 = [{},2,3]; a3r = a3.splice(0, 0); assertTrue(%HasFastObjectElements(a3r)); @@ -231,7 +224,6 @@ function array_natives_test() { assertTrue(%HasFastObjectElements(a3)); assertEquals([1], a3r); assertEquals([{}, 2, 3], a3); - a3 = [1.1,2,3]; a3r = a3.splice(0, 0, {}); assertTrue(%HasFastObjectElements(a3r)); @@ -244,6 +236,35 @@ function array_natives_test() { assertTrue(%HasFastObjectElements(a3)); assertEquals([1.1], a3r); assertEquals([{}, 2, 3], a3); + // Splice large objects + var a3 = new Array(1024 * 1024); + a3[1024*1024-1] = 1; + var a3r; + a3r = a3.splice(-1, 1); + assertTrue(%HasFastSmiElements(a3r)); + assertTrue(%HasFastSmiElements(a3)); + assertEquals([1], a3r); + assertEquals(new Array(1024 * 1024 - 1), a3); + var a3 = new Array(1024 * 1024); + a3[0] = 1; + var a3r; + a3r = a3.splice(0, 1); + assertTrue(%HasFastSmiElements(a3r)); + assertTrue(%HasFastSmiElements(a3)); + assertEquals([1], a3r); + assertEquals(new Array(1024 * 1024 - 1), a3); + // Splice array with large enough backing store + a3 = [1.1, 2.2, 3.3]; + a3r = a3.splice(2, 1); + assertTrue(%HasFastDoubleElements(a3r)); + assertTrue(%HasFastDoubleElements(a3)); + assertEquals([3.3], a3r); + assertEquals([1.1, 2.2], a3); + a3r = a3.splice(1, 1, 4.4, 5.5); + assertTrue(%HasFastDoubleElements(a3r)); + assertTrue(%HasFastDoubleElements(a3)); + assertEquals([2.2], a3r); + assertEquals([1.1, 4.4, 5.5], a3); // Pop var a4 = [1,2,3]; diff --git a/test/mjsunit/array-splice.js b/test/mjsunit/array-splice.js index be2b1064e6..f683350f52 100644 --- a/test/mjsunit/array-splice.js +++ b/test/mjsunit/array-splice.js @@ -115,6 +115,11 @@ assertEquals([], array); assertEquals([1, 2, 3, 4, 5, 6, 7], spliced); + array = [1, 2, 3, 4, 5, 6, 7]; + spliced = array.splice(-1e100); + assertEquals([], array); + assertEquals([1, 2, 3, 4, 5, 6, 7], spliced); + array = [1, 2, 3, 4, 5, 6, 7]; spliced = array.splice(-3); assertEquals([1, 2, 3, 4], array); @@ -145,11 +150,21 @@ assertEquals([1, 2, 3, 4, 5, 6, 7], array); assertEquals([], spliced); + array = [1, 2, 3, 4, 5, 6, 7]; + spliced = array.splice(1e100); + assertEquals([1, 2, 3, 4, 5, 6, 7], array); + assertEquals([], spliced); + array = [1, 2, 3, 4, 5, 6, 7]; spliced = array.splice(0, -100); assertEquals([1, 2, 3, 4, 5, 6, 7], array); assertEquals([], spliced); + array = [1, 2, 3, 4, 5, 6, 7]; + spliced = array.splice(0, -1e100); + assertEquals([1, 2, 3, 4, 5, 6, 7], array); + assertEquals([], spliced); + array = [1, 2, 3, 4, 5, 6, 7]; spliced = array.splice(0, -3); assertEquals([1, 2, 3, 4, 5, 6, 7], array); @@ -180,6 +195,11 @@ assertEquals([], array); assertEquals([1, 2, 3, 4, 5, 6, 7], spliced); + array = [1, 2, 3, 4, 5, 6, 7]; + spliced = array.splice(0, 1e100); + assertEquals([], array); + assertEquals([1, 2, 3, 4, 5, 6, 7], spliced); + // Some exotic cases. obj = { toString: function() { throw 'Exception'; } };