From 2abf38bbe6f509dea7482582c53c51e7e21e79b7 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sat, 15 Aug 2015 23:58:24 +0200 Subject: [PATCH] Bug 1195030 - Backout of bug 890329 for breaking email reading in gmail. r=bustage --- js/src/builtin/Array.js | 108 ------- js/src/builtin/Number.js | 4 +- js/src/builtin/Utilities.js | 3 +- js/src/jit/CodeGenerator.cpp | 12 + js/src/jit/CodeGenerator.h | 1 + js/src/jit/IonBuilder.h | 1 + js/src/jit/Lowering.cpp | 10 + js/src/jit/Lowering.h | 1 + js/src/jit/MCallOptimize.cpp | 42 +++ js/src/jit/MIR.h | 36 +++ js/src/jit/MOpcodes.h | 1 + js/src/jit/VMFunctions.cpp | 12 + js/src/jit/VMFunctions.h | 2 + js/src/jit/shared/LIR-shared.h | 28 ++ js/src/jit/shared/LOpcodes-shared.h | 1 + js/src/js.msg | 1 - js/src/jsarray.cpp | 273 +++++++++++++++++- .../Array/splice_length_overflow_throws.js | 14 - js/src/vm/Xdr.h | 4 +- 19 files changed, 425 insertions(+), 129 deletions(-) delete mode 100644 js/src/tests/ecma_6/Array/splice_length_overflow_throws.js diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index a789742b1a8..aa67700fdc3 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -841,111 +841,3 @@ function ArrayToString() { return callFunction(std_Object_toString, array); return callFunction(func, array); } - -//ES 2015, 22.1.3.25 Array.prototype.splice. -function ArraySplice(start, deleteCount /*, ...items */) { - // Steps 1-2. - var O = ToObject(this); - - // Steps 3-4. - // FIXME: Array operations should use ToLength (bug 924058). - var len = ToInteger(O.length); - - // Steps 5-6. - var relativeStart = ToInteger(start); - - // Step 7. - var actualStart = relativeStart < 0 - ? std_Math_max(len + relativeStart, 0) - : std_Math_min(relativeStart, len); - - // Steps 8-10. - var insertCount = 0; - var actualDeleteCount = 0; - var numArgs = arguments.length; - if (numArgs === 1) { - actualDeleteCount = len - actualStart; - } else if (numArgs > 1) { - // Step 10.a. - insertCount = numArgs - 2; - // Steps 10.b-c. - var dc = ToInteger(deleteCount); - // Step 10.d. - actualDeleteCount = std_Math_min(std_Math_max(dc, 0), len - actualStart); - } - - // Step 11. - if (len + insertCount - actualDeleteCount > 2 ** 53 - 1) - ThrowTypeError(JSMSG_BAD_ARRAY_LENGTH_SPLICE); - - // Steps 12-13. - // FIXME: Use ArraySpeciesCreate here. - var A = NewDenseArray(actualDeleteCount); - - // Steps 14-15. - for (var k = 0; k < actualDeleteCount; k++) { - // Step 15.a. - var from = actualStart + k; - // Steps 15.b-d. - if (from in O) - _DefineDataProperty(A, k, O[from]); - } - - // Steps 16-17. - A.length = actualDeleteCount; - - // Step 18 (implicit). - // Step 19. - var itemCount = insertCount; - - // Step 20. - if (itemCount < actualDeleteCount) { - // Steps 20.a-b. - for (var k = actualStart, kMax = len - actualDeleteCount; k < kMax; k++) { - // Step 20.b.i. - var from = k + actualDeleteCount; - // Step 20.b.ii. - var to = k + itemCount; - // Steps 20.b.iii-v. - if (from in O) { - O[to] = O[from]; - } else { - // Step 20.b.vi. - delete O[to]; - } - } - // Steps 20.c-d. - // For packed arrays we can skip these steps: the fact that we don't - // delete the elements one by one isn't visible to content code. - if (!IsPackedArray(O)) { - for (var k = len, kMin = len - actualDeleteCount + itemCount; k > kMin; k--) - delete O[k - 1]; - } - } else if (itemCount > actualDeleteCount) { - // Step 21. - // Steps 21 a-b. - for (var k = len - actualDeleteCount, kMin = actualStart; k > kMin; k--) { - // Step 21.b.i. - var from = k + actualDeleteCount - 1; - // Step 21.b.ii. - var to = k + itemCount - 1; - // Steps 21.b.iii-v. - if (from in O) { - O[to] = O[from]; - } else { - // Step 21.b.vi. - delete O[to]; - } - } - } - - // Steps 22-23. - for (var k = actualStart, itemIndex = 2; itemIndex < numArgs; k++, itemIndex++) - O[k] = arguments[itemIndex]; - - // Steps 24-25. - O.length = len - actualDeleteCount + itemCount; - - // Step 26. - return A; -} diff --git a/js/src/builtin/Number.js b/js/src/builtin/Number.js index 4419ea672a0..07b2be57abe 100644 --- a/js/src/builtin/Number.js +++ b/js/src/builtin/Number.js @@ -70,8 +70,8 @@ function Number_isSafeInteger(number) { if (integer !== number) return false; - // Step 5. - if (std_Math_abs(integer) <= 2 ** 53 - 1) + // Step 5. If abs(integer) <= 2**53 - 1, return true. + if (std_Math_abs(integer) <= 9007199254740991) return true; // Step 6. diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 70e8c430a8c..fc97461a4e1 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -110,7 +110,8 @@ function ToLength(v) { if (v <= 0) return 0; - return std_Math_min(v, 2 ** 53 - 1); + // Math.pow(2, 53) - 1 = 0x1fffffffffffff + return std_Math_min(v, 0x1fffffffffffff); } /* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */ diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 854e0cf4044..398c6c3cc07 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -3438,6 +3438,18 @@ CodeGenerator::visitApplyArgsGeneric(LApplyArgsGeneric* apply) emitPopArguments(apply, extraStackSpace); } +typedef bool (*ArraySpliceDenseFn)(JSContext*, HandleObject, uint32_t, uint32_t); +static const VMFunction ArraySpliceDenseInfo = FunctionInfo(ArraySpliceDense); + +void +CodeGenerator::visitArraySplice(LArraySplice* lir) +{ + pushArg(ToRegister(lir->getDeleteCount())); + pushArg(ToRegister(lir->getStart())); + pushArg(ToRegister(lir->getObject())); + callVM(ArraySpliceDenseInfo, lir); +} + void CodeGenerator::visitBail(LBail* lir) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index a1c0dcbf2d7..c45a37f0442 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -216,6 +216,7 @@ class CodeGenerator : public CodeGeneratorSpecific void emitSetPropertyPolymorphic(LInstruction* lir, Register obj, Register scratch, const ConstantOrRegister& value); void visitSetPropertyPolymorphicV(LSetPropertyPolymorphicV* ins); + void visitArraySplice(LArraySplice* splice); void visitSetPropertyPolymorphicT(LSetPropertyPolymorphicT* ins); void visitAbsI(LAbsI* lir); void visitAtan2D(LAtan2D* lir); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 37c01462da2..b408b457a54 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -753,6 +753,7 @@ class IonBuilder InliningStatus inlineArrayConcat(CallInfo& callInfo); InliningStatus inlineArraySlice(CallInfo& callInfo); InliningStatus inlineArrayJoin(CallInfo& callInfo); + InliningStatus inlineArraySplice(CallInfo& callInfo); // Math natives. InliningStatus inlineMathAbs(CallInfo& callInfo); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index bc7814312bd..4ef4fdeaf35 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -556,6 +556,16 @@ LIRGenerator::visitAssertRecoveredOnBailout(MAssertRecoveredOnBailout* assertion MOZ_CRASH("AssertRecoveredOnBailout nodes are always recovered on bailouts."); } +void +LIRGenerator::visitArraySplice(MArraySplice* ins) +{ + LArraySplice* lir = new(alloc()) LArraySplice(useRegisterAtStart(ins->object()), + useRegisterAtStart(ins->start()), + useRegisterAtStart(ins->deleteCount())); + add(lir, ins); + assignSafepoint(lir, ins); +} + void LIRGenerator::visitGetDynamicName(MGetDynamicName* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 9cb494b8ec7..1c66b85954e 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -99,6 +99,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitLoadArrowThis(MLoadArrowThis* ins); void visitCall(MCall* call); void visitApplyArgs(MApplyArgs* apply); + void visitArraySplice(MArraySplice* splice); void visitBail(MBail* bail); void visitUnreachable(MUnreachable* unreachable); void visitEncodeSnapshot(MEncodeSnapshot* ins); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 6d8250b1e3f..807180dd2de 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -90,6 +90,8 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) return inlineArrayConcat(callInfo); if (native == js::array_slice) return inlineArraySlice(callInfo); + if (native == js::array_splice) + return inlineArraySplice(callInfo); // Math natives. if (native == js::math_abs) @@ -691,6 +693,46 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode) return InliningStatus_Inlined; } +IonBuilder::InliningStatus +IonBuilder::inlineArraySplice(CallInfo& callInfo) +{ + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // Ensure |this|, argument and result are objects. + if (getInlineReturnType() != MIRType_Object) + return InliningStatus_NotInlined; + if (callInfo.thisArg()->type() != MIRType_Object) + return InliningStatus_NotInlined; + if (callInfo.getArg(0)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + if (callInfo.getArg(1)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + // Specialize arr.splice(start, deleteCount) with unused return value and + // avoid creating the result array in this case. + if (!BytecodeIsPopped(pc)) { + trackOptimizationOutcome(TrackedOutcome::CantInlineGeneric); + return InliningStatus_NotInlined; + } + + MArraySplice* ins = MArraySplice::New(alloc(), + callInfo.thisArg(), + callInfo.getArg(0), + callInfo.getArg(1)); + + current->add(ins); + pushConstant(UndefinedValue()); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + return InliningStatus_Inlined; +} + IonBuilder::InliningStatus IonBuilder::inlineArrayJoin(CallInfo& callInfo) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 2174da5e0c8..0c24530affe 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -3813,6 +3813,42 @@ class MCallDOMNative : public MCall virtual void computeMovable() override; }; +// arr.splice(start, deleteCount) with unused return value. +class MArraySplice + : public MTernaryInstruction, + public Mix3Policy, IntPolicy<1>, IntPolicy<2> >::Data +{ + private: + + MArraySplice(MDefinition* object, MDefinition* start, MDefinition* deleteCount) + : MTernaryInstruction(object, start, deleteCount) + { } + + public: + INSTRUCTION_HEADER(ArraySplice) + static MArraySplice* New(TempAllocator& alloc, MDefinition* object, + MDefinition* start, MDefinition* deleteCount) + { + return new(alloc) MArraySplice(object, start, deleteCount); + } + + MDefinition* object() const { + return getOperand(0); + } + + MDefinition* start() const { + return getOperand(1); + } + + MDefinition* deleteCount() const { + return getOperand(2); + } + + bool possiblyCalls() const override { + return true; + } +}; + // fun.apply(self, arguments) class MApplyArgs : public MAryInstruction<3>, diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 3b0abcdc30d..68add57fb7a 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -62,6 +62,7 @@ namespace jit { _(LoadArrowThis) \ _(Call) \ _(ApplyArgs) \ + _(ArraySplice) \ _(Bail) \ _(Unreachable) \ _(EncodeSnapshot) \ diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index fda3385ff5b..7e94a198d9c 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -251,6 +251,18 @@ StringsEqual(JSContext* cx, HandleString lhs, HandleString rhs, bool* res) template bool StringsEqual(JSContext* cx, HandleString lhs, HandleString rhs, bool* res); template bool StringsEqual(JSContext* cx, HandleString lhs, HandleString rhs, bool* res); +bool +ArraySpliceDense(JSContext* cx, HandleObject obj, uint32_t start, uint32_t deleteCount) +{ + JS::AutoValueArray<4> argv(cx); + argv[0].setUndefined(); + argv[1].setObject(*obj); + argv[2].set(Int32Value(start)); + argv[3].set(Int32Value(deleteCount)); + + return js::array_splice_impl(cx, 2, argv.begin(), false); +} + bool ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval) { diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 591252bdd64..0fee01d4fd7 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -688,6 +688,8 @@ bool InitBaselineFrameForOsr(BaselineFrame* frame, InterpreterFrame* interpFrame JSObject* CreateDerivedTypedObj(JSContext* cx, HandleObject descr, HandleObject owner, int32_t offset); +bool ArraySpliceDense(JSContext* cx, HandleObject obj, uint32_t start, uint32_t deleteCount); + bool Recompile(JSContext* cx); bool ForcedRecompile(JSContext* cx); JSString* RegExpReplace(JSContext* cx, HandleString string, HandleObject regexp, diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 144dcaf93c6..fe778f7868b 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -1809,6 +1809,34 @@ class LApplyArgsGeneric : public LCallInstructionHelper +{ + public: + LIR_HEADER(ArraySplice) + + LArraySplice(const LAllocation& object, const LAllocation& start, + const LAllocation& deleteCount) + { + setOperand(0, object); + setOperand(1, start); + setOperand(2, deleteCount); + } + + MArraySplice* mir() const { + return mir_->toArraySplice(); + } + + const LAllocation* getObject() { + return getOperand(0); + } + const LAllocation* getStart() { + return getOperand(1); + } + const LAllocation* getDeleteCount() { + return getOperand(2); + } +}; + class LGetDynamicName : public LCallInstructionHelper { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index 8e1b504a7fd..200e4626242 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -53,6 +53,7 @@ _(NewArray) \ _(NewArrayCopyOnWrite) \ _(NewArrayDynamicLength) \ + _(ArraySplice) \ _(NewObject) \ _(NewTypedObject) \ _(NewDeclEnvObject) \ diff --git a/js/src/js.msg b/js/src/js.msg index d37df5bde81..d9da89c5616 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -61,7 +61,6 @@ MSG_DEF(JSMSG_SPREAD_TOO_LARGE, 0, JSEXN_RANGEERR, "array too large due t MSG_DEF(JSMSG_BAD_WEAKMAP_KEY, 0, JSEXN_TYPEERR, "cannot use the given object as a weak map key") MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER, 1, JSEXN_TYPEERR, "invalid {0} usage") MSG_DEF(JSMSG_BAD_ARRAY_LENGTH, 0, JSEXN_RANGEERR, "invalid array length") -MSG_DEF(JSMSG_BAD_ARRAY_LENGTH_SPLICE, 0, JSEXN_TYPEERR, "resulting array length too large") MSG_DEF(JSMSG_REDECLARED_VAR, 2, JSEXN_TYPEERR, "redeclaration of {0} {1}") MSG_DEF(JSMSG_UNDECLARED_VAR, 1, JSEXN_REFERENCEERR, "assignment to undeclared variable {0}") MSG_DEF(JSMSG_GETTER_ONLY, 0, JSEXN_TYPEERR, "setting a property that has only a getter") diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 1c3dccf9524..3e8f8a6c810 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2261,6 +2261,277 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp) return true; } +/* + * Returns true if this is a dense or unboxed array whose |count| properties + * starting from |startingIndex| may be accessed (get, set, delete) directly + * through its contiguous vector of elements without fear of getters, setters, + * etc. along the prototype chain, or of enumerators requiring notification of + * modifications. + */ +static inline bool +CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t count, JSContext* cx) +{ + /* If the desired properties overflow dense storage, we can't optimize. */ + if (UINT32_MAX - startingIndex < count) + return false; + + /* There's no optimizing possible if it's not an array. */ + if (!arr->is() && !arr->is()) + return false; + + /* + * Don't optimize if the array might be in the midst of iteration. We + * rely on this to be able to safely move dense array elements around with + * just a memmove (see NativeObject::moveDenseArrayElements), without worrying + * about updating any in-progress enumerators for properties implicitly + * deleted if a hole is moved from one location to another location not yet + * visited. See bug 690622. + */ + ObjectGroup* arrGroup = arr->getGroup(cx); + if (MOZ_UNLIKELY(!arrGroup || arrGroup->hasAllFlags(OBJECT_FLAG_ITERATED))) + return false; + + /* + * Another potential wrinkle: what if the enumeration is happening on an + * object which merely has |arr| on its prototype chain? + */ + if (arr->isDelegate()) + return false; + + /* + * Now watch out for getters and setters along the prototype chain or in + * other indexed properties on the object. (Note that non-writable length + * is subsumed by the initializedLength comparison.) + */ + return !ObjectMayHaveExtraIndexedProperties(arr) && + startingIndex + count <= GetAnyBoxedOrUnboxedInitializedLength(arr); +} + +/* ES5 15.4.4.12. */ +bool +js::array_splice(JSContext* cx, unsigned argc, Value* vp) +{ + return array_splice_impl(cx, argc, vp, true); +} + +bool +js::array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUsed) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + /* Step 1. */ + RootedObject obj(cx, ToObject(cx, args.thisv())); + if (!obj) + return false; + + /* Steps 3-4. */ + uint32_t len; + if (!GetLengthProperty(cx, obj, &len)) + return false; + + /* Step 5. */ + double relativeStart; + if (!ToInteger(cx, args.get(0), &relativeStart)) + return false; + + /* Step 6. */ + uint32_t actualStart; + if (relativeStart < 0) + actualStart = Max(len + relativeStart, 0.0); + else + actualStart = Min(relativeStart, double(len)); + + /* Step 7. */ + uint32_t actualDeleteCount; + if (args.length() != 1) { + double deleteCountDouble; + RootedValue cnt(cx, args.length() >= 2 ? args[1] : Int32Value(0)); + if (!ToInteger(cx, cnt, &deleteCountDouble)) + return false; + actualDeleteCount = Min(Max(deleteCountDouble, 0.0), double(len - actualStart)); + } else { + /* + * Non-standard: if start was specified but deleteCount was omitted, + * delete to the end of the array. See bug 668024 for discussion. + */ + actualDeleteCount = len - actualStart; + } + + MOZ_ASSERT(len - actualStart >= actualDeleteCount); + + /* Steps 2, 8-9. */ + RootedObject arr(cx); + if (CanOptimizeForDenseStorage(obj, actualStart, actualDeleteCount, cx)) { + if (returnValueIsUsed) { + arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, actualDeleteCount); + if (!arr) + return false; + DebugOnly result = + CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, 0, actualStart, actualDeleteCount); + MOZ_ASSERT(result.value == DenseElementResult::Success); + } + } else { + arr = NewFullyAllocatedArrayTryReuseGroup(cx, obj, actualDeleteCount); + if (!arr) + return false; + + RootedValue fromValue(cx); + for (uint32_t k = 0; k < actualDeleteCount; k++) { + bool hole; + if (!CheckForInterrupt(cx) || + !GetElement(cx, obj, actualStart + k, &hole, &fromValue) || + (!hole && !DefineElement(cx, arr, k, fromValue))) + { + return false; + } + } + } + + /* Step 11. */ + uint32_t itemCount = (args.length() >= 2) ? (args.length() - 2) : 0; + + if (itemCount < actualDeleteCount) { + /* Step 12: the array is being shrunk. */ + uint32_t sourceIndex = actualStart + actualDeleteCount; + uint32_t targetIndex = actualStart + itemCount; + uint32_t finalLength = len - actualDeleteCount + itemCount; + + if (CanOptimizeForDenseStorage(obj, 0, len, cx)) { + /* Steps 12(a)-(b). */ + DenseElementResult result = + MoveAnyBoxedOrUnboxedDenseElements(cx, obj, targetIndex, sourceIndex, + len - sourceIndex); + MOZ_ASSERT(result != DenseElementResult::Incomplete); + if (result == DenseElementResult::Failure) + return false; + + /* Steps 12(c)-(d). */ + SetAnyBoxedOrUnboxedInitializedLength(cx, obj, finalLength); + } else { + /* + * This is all very slow if the length is very large. We don't yet + * have the ability to iterate in sorted order, so we just do the + * pessimistic thing and let CheckForInterrupt handle the + * fallout. + */ + + /* Steps 12(a)-(b). */ + RootedValue fromValue(cx); + for (uint32_t from = sourceIndex, to = targetIndex; from < len; from++, to++) { + if (!CheckForInterrupt(cx)) + return false; + + bool hole; + if (!GetElement(cx, obj, from, &hole, &fromValue)) + return false; + if (hole) { + if (!DeletePropertyOrThrow(cx, obj, to)) + return false; + } else { + if (!SetArrayElement(cx, obj, to, fromValue)) + return false; + } + } + + /* Steps 12(c)-(d). */ + for (uint32_t k = len; k > finalLength; k--) { + if (!DeletePropertyOrThrow(cx, obj, k - 1)) + return false; + } + } + } else if (itemCount > actualDeleteCount) { + /* Step 13. */ + + /* + * Optimize only if the array is already dense and we can extend it to + * its new length. It would be wrong to extend the elements here for a + * number of reasons. + * + * First, this could cause us to fall into the fast-path below. This + * would cause elements to be moved into places past the non-writable + * length. And when the dense initialized length is updated, that'll + * cause the |in| operator to think that those elements actually exist, + * even though, properly, setting them must fail. + * + * Second, extending the elements here will trigger assertions inside + * ensureDenseElements that the elements aren't being extended past the + * length of a non-writable array. This is because extending elements + * will extend capacity -- which might extend them past a non-writable + * length, violating the |capacity <= length| invariant for such + * arrays. And that would make the various JITted fast-path method + * implementations of [].push, [].unshift, and so on wrong. + * + * If the array length is non-writable, this method *will* throw. For + * simplicity, have the slow-path code do it. (Also note that the slow + * path may validly *not* throw -- if all the elements being moved are + * holes.) + */ + if (obj->is()) { + Rooted arr(cx, &obj->as()); + if (arr->lengthIsWritable()) { + DenseElementResult result = + arr->ensureDenseElements(cx, arr->length(), itemCount - actualDeleteCount); + if (result == DenseElementResult::Failure) + return false; + } + } + + if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) { + DenseElementResult result = + MoveAnyBoxedOrUnboxedDenseElements(cx, obj, actualStart + itemCount, + actualStart + actualDeleteCount, + len - (actualStart + actualDeleteCount)); + MOZ_ASSERT(result != DenseElementResult::Incomplete); + if (result == DenseElementResult::Failure) + return false; + + /* Steps 12(c)-(d). */ + SetAnyBoxedOrUnboxedInitializedLength(cx, obj, len + itemCount - actualDeleteCount); + } else { + RootedValue fromValue(cx); + for (double k = len - actualDeleteCount; k > actualStart; k--) { + if (!CheckForInterrupt(cx)) + return false; + + double from = k + actualDeleteCount - 1; + double to = k + itemCount - 1; + + bool hole; + if (!GetElement(cx, obj, from, &hole, &fromValue)) + return false; + + if (hole) { + if (!DeletePropertyOrThrow(cx, obj, to)) + return false; + } else { + if (!SetArrayElement(cx, obj, to, fromValue)) + return false; + } + } + } + } + + /* Step 10. */ + Value* items = args.array() + 2; + + /* Steps 14-15. */ + for (uint32_t k = actualStart, i = 0; i < itemCount; i++, k++) { + if (!SetArrayElement(cx, obj, k, HandleValue::fromMarkedLocation(&items[i]))) + return false; + } + + /* Step 16. */ + double finalLength = double(len) - actualDeleteCount + itemCount; + if (!SetLengthProperty(cx, obj, finalLength)) + return false; + + /* Step 17. */ + if (returnValueIsUsed) + args.rval().setObject(*arr); + + return true; +} + template DenseElementResult ArrayConcatDenseKernel(JSContext* cx, JSObject* obj1, JSObject* obj2, JSObject* result) @@ -2758,7 +3029,7 @@ static const JSFunctionSpec array_methods[] = { JS_FN("pop", array_pop, 0,JSFUN_GENERIC_NATIVE), JS_FN("shift", array_shift, 0,JSFUN_GENERIC_NATIVE), JS_FN("unshift", array_unshift, 1,JSFUN_GENERIC_NATIVE), - JS_SELF_HOSTED_FN("splice", "ArraySplice", 2,0), + JS_FN("splice", array_splice, 2,JSFUN_GENERIC_NATIVE), /* Pythonic sequence methods. */ JS_FN("concat", array_concat, 1,JSFUN_GENERIC_NATIVE), diff --git a/js/src/tests/ecma_6/Array/splice_length_overflow_throws.js b/js/src/tests/ecma_6/Array/splice_length_overflow_throws.js deleted file mode 100644 index cf7385e2d23..00000000000 --- a/js/src/tests/ecma_6/Array/splice_length_overflow_throws.js +++ /dev/null @@ -1,14 +0,0 @@ -var a = {}; -a[2 ** 53 - 2] = 1; -a.length = 2 ** 53 - 1; - -var exception; -try { - [].splice.call(a, 2 ** 53 - 2, 0, 2, 3, 4, 5); -} catch (e) { - exception = e; -} -reportCompare(a[2 ** 53 - 2], 1); -reportCompare(a.length, 2 ** 53 - 1); -reportCompare(exception instanceof TypeError, true, "Array#splice throws TypeError for length overflows"); -reportCompare(exception.message.indexOf('array length') > -1, true, "Array#splice throws correct error for length overflows"); diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index b5ee231eece..10e9422d87a 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,11 +29,11 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 300; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 301; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 408, +static_assert(JSErr_Limit == 407, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "