diff --git a/js/public/CallArgs.h b/js/public/CallArgs.h index b4f2348ccd1..54fa0d68dfb 100644 --- a/js/public/CallArgs.h +++ b/js/public/CallArgs.h @@ -285,6 +285,7 @@ class MOZ_STACK_CLASS CallArgsBase : { protected: unsigned argc_; + bool constructing_; public: /* Returns the number of arguments. */ @@ -314,13 +315,18 @@ class MOZ_STACK_CLASS CallArgsBase : return i < argc_ && !this->argv_[i].isUndefined(); } + MutableHandleValue newTarget() const { + MOZ_ASSERT(constructing_); + return MutableHandleValue::fromMarkedLocation(&this->argv_[argc_]); + } + public: // These methods are publicly exposed, but we're less sure of the interface // here than we'd like (because they're hackish and drop assertions). Try // to avoid using these if you can. Value* array() const { return this->argv_; } - Value* end() const { return this->argv_ + argc_; } + Value* end() const { return this->argv_ + argc_ + constructing_; } }; } // namespace detail @@ -329,13 +335,14 @@ class MOZ_STACK_CLASS CallArgs : public detail::CallArgsBasegetOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW; + /* * Emit code for each argument in order, then emit the JSOP_*CALL or * JSOP_NEW bytecode with a two-byte immediate telling how many args @@ -6459,9 +6461,20 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) if (!emitTree(pn3)) return false; } + + if (isNewOp) { + // Repush the callee as new.target + if (!emitDupAt(this->stackDepth - 1 - (argc + 1))) + return false; + } } else { if (!emitArray(pn2->pn_next, argc, JSOP_SPREADCALLARRAY)) return false; + + if (isNewOp) { + if (!emitDupAt(this->stackDepth - 1 - 2)) + return false; + } } emittingForInit = oldEmittingForInit; diff --git a/js/src/jit-test/tests/basic/newTargetRectifier.js b/js/src/jit-test/tests/basic/newTargetRectifier.js new file mode 100644 index 00000000000..78716869fb2 --- /dev/null +++ b/js/src/jit-test/tests/basic/newTargetRectifier.js @@ -0,0 +1,12 @@ +function testBailoutNewTarget() { + function Inner(ex, forceRectifier) { + bailout(); + assertEq(new.target, ex); + } + + for (let i = 0; i < 1100; i++) + new Inner(Inner); +} + +for (let i = 0; i < 15; i++) + testBailoutNewTarget(); diff --git a/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js b/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js new file mode 100644 index 00000000000..2a413404c17 --- /dev/null +++ b/js/src/jit-test/tests/debug/Frame-newTargetOverflow-01.js @@ -0,0 +1,37 @@ +// Test that Ion frames are invalidated by turning on Debugger. Invalidation +// is unobservable, but we know that Ion scripts cannot handle Debugger +// handlers, so we test for the handlers being called. + +load(libdir + "jitopts.js"); + +if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation)) + quit(); + +withJitOptions(Opts_Ion2NoOffthreadCompilation, function () { + var g = newGlobal(); + var dbg = new Debugger; + + g.toggle = function toggle(d) { + if (d) { + dbg.addDebuggee(g); + + var frame = dbg.getNewestFrame(); + assertEq(frame.implementation, "ion"); + assertEq(frame.constructing, true); + + // overflow args are read from the parent's frame + // ensure we have the right offset to read from those. + assertEq(frame.arguments[1], 15); + } + }; + + g.eval("" + function f(d) { new g(d, 15); }); + + g.eval("" + function g(d) { toggle(d); }); + + g.eval("(" + function test() { + for (var i = 0; i < 5; i++) + f(false); + f(true); + } + ")();"); +}); diff --git a/js/src/jit-test/tests/proxy/testDirectProxyConstruct1.js b/js/src/jit-test/tests/proxy/testDirectProxyConstruct1.js index 31e2ead5209..6117d8b6cbc 100644 --- a/js/src/jit-test/tests/proxy/testDirectProxyConstruct1.js +++ b/js/src/jit-test/tests/proxy/testDirectProxyConstruct1.js @@ -1,9 +1,11 @@ // Forward to the target if the trap is undefined +var p; var target = function (x, y) { + assertEq(new.target, p); this.foo = x + y; } -for (let p of [new Proxy(target, {}), Proxy.revocable(target, {}).proxy]) { +for (p of [new Proxy(target, {}), Proxy.revocable(target, {}).proxy]) { var obj = new p(2, 3); assertEq(obj.foo, 5); assertEq(Object.getPrototypeOf(obj), target.prototype); diff --git a/js/src/jit-test/tests/proxy/testDirectProxyConstruct2.js b/js/src/jit-test/tests/proxy/testDirectProxyConstruct2.js index 4677ae264f4..7f2421bd675 100644 --- a/js/src/jit-test/tests/proxy/testDirectProxyConstruct2.js +++ b/js/src/jit-test/tests/proxy/testDirectProxyConstruct2.js @@ -5,15 +5,17 @@ load(libdir + "asserts.js"); * * Hooks that don't return an object must throw. */ +var p; var target = function () {}; var handler = { - construct: function (target1, args) { + construct: function (target1, args, newTarget) { assertEq(this, handler); assertEq(target1, target); assertEq(args.length, 2); assertEq(args[0], 2); assertEq(args[1], 3); + assertEq(newTarget, p); } } -for (let p of [new Proxy(target, handler), Proxy.revocable(target, handler).proxy]) +for (p of [new Proxy(target, handler), Proxy.revocable(target, handler).proxy]) assertThrowsInstanceOf(function () {new p(2, 3)}, TypeError); diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 898779cf892..de1e86e9da9 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -976,6 +976,8 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, BailoutKindString(bailoutKind)); #endif + bool pushedNewTarget = op == JSOP_NEW; + // If this was the last inline frame, or we are bailing out to a catch or // finally block in this frame, then unpacking is almost done. if (!iter.moreFrames() || catchingException) { @@ -1032,11 +1034,14 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, builder.writeValue(UndefinedValue(), "CallOp FillerThis"); for (uint32_t i = 0; i < numCallArgs; i++) builder.writeValue(UndefinedValue(), "CallOp FillerArg"); + if (pushedNewTarget) + builder.writeValue(UndefinedValue(), "CallOp FillerNewTarget"); - frameSize += (numCallArgs + 2) * sizeof(Value); + frameSize += (numCallArgs + 2 + pushedNewTarget) * sizeof(Value); blFrame->setFrameSize(frameSize); JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize += %d: %d", - (int) ((numCallArgs + 2) * sizeof(Value)), (int) frameSize); + (int) ((numCallArgs + 2 + pushedNewTarget) * sizeof(Value)), + (int) frameSize); } // Set the resume address to the return point from the IC, and set @@ -1228,12 +1233,13 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, } // Align the stack based on the number of arguments. - size_t afterFrameSize = (actualArgc + 1) * sizeof(Value) + JitFrameLayout::Size(); + size_t afterFrameSize = (actualArgc + 1 + pushedNewTarget) * sizeof(Value) + + JitFrameLayout::Size(); if (!builder.maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) return false; - MOZ_ASSERT(actualArgc + 2 <= exprStackSlots); - for (unsigned i = 0; i < actualArgc + 1; i++) { + MOZ_ASSERT(actualArgc + 2 + pushedNewTarget <= exprStackSlots); + for (unsigned i = 0; i < actualArgc + 1 + pushedNewTarget; i++) { size_t argSlot = (script->nfixed() + exprStackSlots) - (i + 1); if (!builder.writeValue(*blFrame->valueSlot(argSlot), "ArgVal")) return false; @@ -1260,7 +1266,7 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, // So get the callee from the specially saved vector. callee = savedCallerArgs[0]; } else { - uint32_t calleeStackSlot = exprStackSlots - uint32_t(actualArgc + 2); + uint32_t calleeStackSlot = exprStackSlots - uint32_t(actualArgc + 2 + pushedNewTarget); size_t calleeOffset = (builder.framePushed() - endOfBaselineJSFrameStack) + ((exprStackSlots - (calleeStackSlot + 1)) * sizeof(Value)); callee = *builder.valuePointerAtStackOffset(calleeOffset); @@ -1331,10 +1337,18 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, #endif // Align the stack based on the number of arguments. - size_t afterFrameSize = (calleeFun->nargs() + 1) * sizeof(Value) + RectifierFrameLayout::Size(); + size_t afterFrameSize = (calleeFun->nargs() + 1 + pushedNewTarget) * sizeof(Value) + + RectifierFrameLayout::Size(); if (!builder.maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) return false; + // Copy new.target, if necessary. + if (pushedNewTarget) { + size_t newTargetOffset = (builder.framePushed() - endOfBaselineStubArgs) + + (actualArgc + 1) * sizeof(Value); + builder.writeValue(*builder.valuePointerAtStackOffset(newTargetOffset), "CopiedNewTarget"); + } + // Push undefined for missing arguments. for (unsigned i = 0; i < (calleeFun->nargs() - actualArgc); i++) { if (!builder.writeValue(UndefinedValue(), "FillerVal")) diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index fcbef01246c..2e41936fe31 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -2737,19 +2737,20 @@ BaselineCompiler::emitCall() { MOZ_ASSERT(IsCallPC(pc)); + bool construct = JSOp(*pc) == JSOP_NEW; uint32_t argc = GET_ARGC(pc); frame.syncStack(0); masm.move32(Imm32(argc), R0.scratchReg()); // Call IC - ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ JSOp(*pc) == JSOP_NEW, + ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct, /* isSpread = */ false); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) return false; // Update FrameInfo. - frame.popn(argc + 2); + frame.popn(2 + argc + construct); frame.push(R0); return true; } @@ -2769,7 +2770,8 @@ BaselineCompiler::emitSpreadCall() return false; // Update FrameInfo. - frame.popn(3); + bool construct = JSOp(*pc) == JSOP_SPREADNEW; + frame.popn(3 + construct); frame.push(R0); return true; } diff --git a/js/src/jit/BaselineFrame.cpp b/js/src/jit/BaselineFrame.cpp index 5969e0e1fb0..d42de65d376 100644 --- a/js/src/jit/BaselineFrame.cpp +++ b/js/src/jit/BaselineFrame.cpp @@ -37,7 +37,7 @@ BaselineFrame::trace(JSTracer* trc, JitFrameIterator& frameIterator) // Mark actual and formal args. if (isNonEvalFunctionFrame()) { unsigned numArgs = js::Max(numActualArgs(), numFormalArgs()); - TraceRootRange(trc, numArgs, argv(), "baseline-args"); + TraceRootRange(trc, numArgs + isConstructing(), argv(), "baseline-args"); } // Mark scope chain, if it exists. diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index fa1a3327592..58b5239f334 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -10248,6 +10248,7 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb if (fun->native() == intrinsic_IsSuspendedStarGenerator) { // This intrinsic only appears in self-hosted code. + MOZ_ASSERT(op != JSOP_NEW); MOZ_ASSERT(argc == 1); JitSpew(JitSpew_BaselineIC, " Generating Call_IsSuspendedStarGenerator stub"); @@ -10319,6 +10320,10 @@ TryAttachStringSplit(JSContext* cx, ICCall_Fallback* stub, HandleScript script, RootedValue thisv(cx, vp[1]); Value* args = vp + 2; + // String.prototype.split will not yield a constructable. + if (JSOp(*pc) == JSOP_NEW) + return true; + if (!IsOptimizableCallStringSplit(callee, thisv, argc, args)) return true; @@ -10364,15 +10369,16 @@ DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, uint // This fallback stub may trigger debug mode toggling. DebugModeOSRVolatileStub stub(frame, stub_); - // Ensure vp array is rooted - we may GC in here. - AutoArrayRooter vpRoot(cx, argc + 2, vp); - RootedScript script(cx, frame->script()); jsbytecode* pc = stub->icEntry()->pc(script); JSOp op = JSOp(*pc); FallbackICSpew(cx, stub, "Call(%s)", js_CodeName[op]); MOZ_ASSERT(argc == GET_ARGC(pc)); + bool constructing = (op == JSOP_NEW); + + // Ensure vp array is rooted - we may GC in here. + AutoArrayRooter vpRoot(cx, argc + 2 + constructing, vp); RootedValue callee(cx, vp[0]); RootedValue thisv(cx, vp[1]); @@ -10386,8 +10392,6 @@ DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, uint return false; } - // Compute construcing and useNewGroup flags. - bool constructing = (op == JSOP_NEW); bool createSingleton = ObjectGroup::useSingletonForNewObject(cx, script, pc); // Try attaching a call stub. @@ -10399,7 +10403,7 @@ DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, uint } if (op == JSOP_NEW) { - if (!InvokeConstructor(cx, callee, argc, args, res)) + if (!InvokeConstructor(cx, callee, argc, args, true, res)) return false; } else if ((op == JSOP_EVAL || op == JSOP_STRICTEVAL) && frame->scopeChain()->global().valueIsEval(callee)) @@ -10448,19 +10452,19 @@ DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_ // This fallback stub may trigger debug mode toggling. DebugModeOSRVolatileStub stub(frame, stub_); - // Ensure vp array is rooted - we may GC in here. - AutoArrayRooter vpRoot(cx, 3, vp); - RootedScript script(cx, frame->script()); jsbytecode* pc = stub->icEntry()->pc(script); JSOp op = JSOp(*pc); + bool constructing = (op == JSOP_SPREADNEW); FallbackICSpew(cx, stub, "SpreadCall(%s)", js_CodeName[op]); + // Ensure vp array is rooted - we may GC in here. + AutoArrayRooter vpRoot(cx, 3 + constructing, vp); + RootedValue callee(cx, vp[0]); RootedValue thisv(cx, vp[1]); RootedValue arr(cx, vp[2]); - - bool constructing = (op == JSOP_SPREADNEW); + RootedValue newTarget(cx, constructing ? vp[3] : NullValue()); // Try attaching a call stub. bool handled = false; @@ -10471,7 +10475,7 @@ DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_ return false; } - if (!SpreadCallOperation(cx, script, pc, thisv, callee, arr, res)) + if (!SpreadCallOperation(cx, script, pc, thisv, callee, arr, newTarget, res)) return false; // Check if debug mode toggling made the stub invalid. @@ -10493,14 +10497,26 @@ DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_ void ICCallStubCompiler::pushCallArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs, - Register argcReg, bool isJitCall) + Register argcReg, bool isJitCall, bool isConstructing) { MOZ_ASSERT(!regs.has(argcReg)); - // Push the callee and |this| too. + // Account for new.target Register count = regs.takeAny(); + masm.mov(argcReg, count); - masm.add32(Imm32(2), count); + + // If we are setting up for a jitcall, we have to align the stack taking + // into account the args and newTarget. We could also count callee and |this|, + // but it's a waste of stack space. Because we want to keep argcReg unchanged, + // just account for newTarget initially, and add the other 2 after assuring + // allignment. + if (isJitCall) { + if (isConstructing) + masm.add32(Imm32(1), count); + } else { + masm.add32(Imm32(2 + isConstructing), count); + } // argPtr initially points to the last argument. Register argPtr = regs.takeAny(); @@ -10512,8 +10528,12 @@ ICCallStubCompiler::pushCallArguments(MacroAssembler& masm, AllocatableGeneralRe // Align the stack such that the JitFrameLayout is aligned on the // JitStackAlignment. - if (isJitCall) - masm.alignJitStackBasedOnNArgs(argcReg); + if (isJitCall) { + masm.alignJitStackBasedOnNArgs(count); + + // Account for callee and |this|, skipped earlier + masm.add32(Imm32(2), count); + } // Push all values, starting at the last one. Label loop, done; @@ -10530,9 +10550,10 @@ ICCallStubCompiler::pushCallArguments(MacroAssembler& masm, AllocatableGeneralRe } void -ICCallStubCompiler::guardSpreadCall(MacroAssembler& masm, Register argcReg, Label* failure) +ICCallStubCompiler::guardSpreadCall(MacroAssembler& masm, Register argcReg, Label* failure, + bool isConstructing) { - masm.unboxObject(Address(BaselineStackReg, ICStackValueOffset), argcReg); + masm.unboxObject(Address(BaselineStackReg, isConstructing * sizeof(Value) + ICStackValueOffset), argcReg); masm.loadPtr(Address(argcReg, NativeObject::offsetOfElements()), argcReg); masm.load32(Address(argcReg, ObjectElements::offsetOfLength()), argcReg); @@ -10547,23 +10568,41 @@ ICCallStubCompiler::guardSpreadCall(MacroAssembler& masm, Register argcReg, Labe void ICCallStubCompiler::pushSpreadCallArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs, - Register argcReg, bool isJitCall) + Register argcReg, bool isJitCall, + bool isConstructing) { - // Push arguments + // Pull the array off the stack before aligning. Register startReg = regs.takeAny(); - Register endReg = regs.takeAny(); - masm.unboxObject(Address(BaselineStackReg, STUB_FRAME_SIZE), startReg); + masm.unboxObject(Address(BaselineStackReg, (isConstructing * sizeof(Value)) + STUB_FRAME_SIZE), startReg); masm.loadPtr(Address(startReg, NativeObject::offsetOfElements()), startReg); + + // Align the stack such that the JitFrameLayout is aligned on the + // JitStackAlignment. + if (isJitCall) { + Register alignReg = argcReg; + if (isConstructing) { + alignReg = regs.takeAny(); + masm.mov(argcReg, alignReg); + masm.addPtr(Imm32(1), alignReg); + } + masm.alignJitStackBasedOnNArgs(alignReg); + if (isConstructing) { + MOZ_ASSERT(alignReg != argcReg); + regs.add(alignReg); + } + } + + // Push newTarget, if necessary + if (isConstructing) + masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE)); + + // Push arguments: set up endReg to point to &array[argc] + Register endReg = regs.takeAny(); masm.mov(argcReg, endReg); static_assert(sizeof(Value) == 8, "Value must be 8 bytes"); masm.lshiftPtr(Imm32(3), endReg); masm.addPtr(startReg, endReg); - // Align the stack such that the JitFrameLayout is aligned on the - // JitStackAlignment. - if (isJitCall) - masm.alignJitStackBasedOnNArgs(argcReg); - // Copying pre-decrements endReg by 8 until startReg is reached Label copyDone; Label copyStart; @@ -10578,8 +10617,8 @@ ICCallStubCompiler::pushSpreadCallArguments(MacroAssembler& masm, regs.add(endReg); // Push the callee and |this|. - masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + 1 * sizeof(Value))); - masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + 2 * sizeof(Value))); + masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + (1 + isConstructing) * sizeof(Value))); + masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + (2 + isConstructing) * sizeof(Value))); } // (see Bug 1149377 comment 31) MSVC 2013 PGO miss-compiles branchTestObjClass @@ -10784,9 +10823,20 @@ ICCall_Fallback::Compiler::generateStubCode(MacroAssembler& masm) // Use BaselineFrameReg instead of BaselineStackReg, because // BaselineFrameReg and BaselineStackReg hold the same value just after // calling enterStubFrame. - masm.pushValue(Address(BaselineFrameReg, 0 * sizeof(Value) + STUB_FRAME_SIZE)); // array - masm.pushValue(Address(BaselineFrameReg, 1 * sizeof(Value) + STUB_FRAME_SIZE)); // this - masm.pushValue(Address(BaselineFrameReg, 2 * sizeof(Value) + STUB_FRAME_SIZE)); // callee + + // newTarget + if (isConstructing_) + masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE)); + + // array + uint32_t valueOffset = isConstructing_; + masm.pushValue(Address(BaselineFrameReg, valueOffset++ * sizeof(Value) + STUB_FRAME_SIZE)); + + // this + masm.pushValue(Address(BaselineFrameReg, valueOffset++ * sizeof(Value) + STUB_FRAME_SIZE)); + + // callee + masm.pushValue(Address(BaselineFrameReg, valueOffset++ * sizeof(Value) + STUB_FRAME_SIZE)); masm.push(BaselineStackReg); masm.push(BaselineStubReg); @@ -10807,7 +10857,7 @@ ICCall_Fallback::Compiler::generateStubCode(MacroAssembler& masm) regs.take(R0.scratchReg()); // argc. - pushCallArguments(masm, regs, R0.scratchReg(), /* isJitCall = */ false); + pushCallArguments(masm, regs, R0.scratchReg(), /* isJitCall = */ false, isConstructing_); masm.push(BaselineStackReg); masm.push(R0.scratchReg()); @@ -10892,14 +10942,17 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) regs.takeUnchecked(BaselineTailCallReg); if (isSpread_) - guardSpreadCall(masm, argcReg, &failure); + guardSpreadCall(masm, argcReg, &failure, isConstructing_); - // Load the callee in R1. - // Stack Layout: [ ..., CalleeVal, ThisVal, Arg0Val, ..., ArgNVal, +ICStackValueOffset+ ] + // Load the callee in R1, accounting for newTarget, if necessary + // Stack Layout: [ ..., CalleeVal, ThisVal, Arg0Val, ..., ArgNVal, [newTarget] +ICStackValueOffset+ ] if (isSpread_) { - masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + ICStackValueOffset), R1); + unsigned skipToCallee = (2 + isConstructing_) * sizeof(Value); + masm.loadValue(Address(BaselineStackReg, skipToCallee + ICStackValueOffset), R1); } else { - BaseValueIndex calleeSlot(BaselineStackReg, argcReg, ICStackValueOffset + sizeof(Value)); + // Account for newTarget, if necessary + unsigned nonArgsSkip = (1 + isConstructing_) * sizeof(Value); + BaseValueIndex calleeSlot(BaselineStackReg, argcReg, ICStackValueOffset + nonArgsSkip); masm.loadValue(calleeSlot, R1); } regs.take(R1); @@ -10959,13 +11012,13 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) masm.push(argcReg); // Stack now looks like: - // [..., Callee, ThisV, Arg0V, ..., ArgNV, StubFrameHeader, ArgC ] + // [..., Callee, ThisV, Arg0V, ..., ArgNV, NewTarget, StubFrameHeader, ArgC ] if (isSpread_) { masm.loadValue(Address(BaselineStackReg, - 2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)), R1); + 3 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)), R1); } else { BaseValueIndex calleeSlot2(BaselineStackReg, argcReg, - sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)); + 2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)); masm.loadValue(calleeSlot2, R1); } masm.push(masm.extractObject(R1, ExtractTemp0)); @@ -10993,11 +11046,11 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) // Save "this" value back into pushed arguments on stack. R0 can be clobbered after that. // Stack now looks like: - // [..., Callee, ThisV, Arg0V, ..., ArgNV, StubFrameHeader ] + // [..., Callee, ThisV, Arg0V, ..., ArgNV, [NewTarget], StubFrameHeader ] if (isSpread_) { - masm.storeValue(R0, Address(BaselineStackReg, sizeof(Value) + STUB_FRAME_SIZE)); + masm.storeValue(R0, Address(BaselineStackReg, (1 + isConstructing_) * sizeof(Value) + STUB_FRAME_SIZE)); } else { - BaseValueIndex thisSlot(BaselineStackReg, argcReg, STUB_FRAME_SIZE); + BaseValueIndex thisSlot(BaselineStackReg, argcReg, STUB_FRAME_SIZE + isConstructing_ * sizeof(Value)); masm.storeValue(R0, thisSlot); } @@ -11011,9 +11064,12 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) // Just need to load the script now. if (isSpread_) { - masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + STUB_FRAME_SIZE), R0); + unsigned skipForCallee = (2 + isConstructing_) * sizeof(Value); + masm.loadValue(Address(BaselineStackReg, skipForCallee + STUB_FRAME_SIZE), R0); } else { - BaseValueIndex calleeSlot3(BaselineStackReg, argcReg, sizeof(Value) + STUB_FRAME_SIZE); + // Account for newTarget, if necessary + unsigned nonArgsSkip = (1 + isConstructing_) * sizeof(Value); + BaseValueIndex calleeSlot3(BaselineStackReg, argcReg, nonArgsSkip + STUB_FRAME_SIZE); masm.loadValue(calleeSlot3, R0); } callee = masm.extractObject(R0, ExtractTemp0); @@ -11039,9 +11095,9 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) // right-to-left so duplicate them on the stack in reverse order. // |this| and callee are pushed last. if (isSpread_) - pushSpreadCallArguments(masm, regs, argcReg, /* isJitCall = */ true); + pushSpreadCallArguments(masm, regs, argcReg, /* isJitCall = */ true, isConstructing_); else - pushCallArguments(masm, regs, argcReg, /* isJitCall = */ true); + pushCallArguments(masm, regs, argcReg, /* isJitCall = */ true, isConstructing_); // The callee is on top of the stack. Pop and unbox it. ValueOperand val = regs.takeAnyValue(); @@ -11113,8 +11169,10 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) // Current stack: [ ThisVal, ARGVALS..., ...STUB FRAME..., <-- BaselineFrameReg // Padding?, ARGVALS..., ThisVal, ActualArgc, Callee, Descriptor ] // - // &ThisVal = BaselineFrameReg + argc * sizeof(Value) + STUB_FRAME_SIZE - BaseValueIndex thisSlotAddr(BaselineFrameReg, argcReg, STUB_FRAME_SIZE); + // &ThisVal = BaselineFrameReg + argc * sizeof(Value) + STUB_FRAME_SIZE + sizeof(Value) + // This last sizeof(Value) accounts for the newTarget on the end of the arguments vector + // which is not reflected in actualArgc + BaseValueIndex thisSlotAddr(BaselineFrameReg, argcReg, STUB_FRAME_SIZE + sizeof(Value)); masm.loadValue(thisSlotAddr, JSReturnOperand); #ifdef DEBUG masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); @@ -11286,13 +11344,14 @@ ICCall_Native::Compiler::generateStubCode(MacroAssembler& masm) regs.takeUnchecked(BaselineTailCallReg); if (isSpread_) - guardSpreadCall(masm, argcReg, &failure); + guardSpreadCall(masm, argcReg, &failure, isConstructing_); // Load the callee in R1. if (isSpread_) { masm.loadValue(Address(BaselineStackReg, ICStackValueOffset + 2 * sizeof(Value)), R1); } else { - BaseValueIndex calleeSlot(BaselineStackReg, argcReg, ICStackValueOffset + sizeof(Value)); + unsigned nonArgsSlots = (1 + isConstructing_) * sizeof(Value); + BaseValueIndex calleeSlot(BaselineStackReg, argcReg, ICStackValueOffset + nonArgsSlots); masm.loadValue(calleeSlot, R1); } regs.take(R1); @@ -11315,9 +11374,9 @@ ICCall_Native::Compiler::generateStubCode(MacroAssembler& masm) // right-to-left so duplicate them on the stack in reverse order. // |this| and callee are pushed last. if (isSpread_) - pushSpreadCallArguments(masm, regs, argcReg, /* isJitCall = */ false); + pushSpreadCallArguments(masm, regs, argcReg, /* isJitCall = */ false, isConstructing_); else - pushCallArguments(masm, regs, argcReg, /* isJitCall = */ false); + pushCallArguments(masm, regs, argcReg, /* isJitCall = */ false, isConstructing_); if (isConstructing_) { // Stack looks like: [ ..., Arg0Val, ThisVal, CalleeVal ] @@ -11390,7 +11449,8 @@ ICCall_ClassHook::Compiler::generateStubCode(MacroAssembler& masm) regs.takeUnchecked(BaselineTailCallReg); // Load the callee in R1. - BaseValueIndex calleeSlot(BaselineStackReg, argcReg, ICStackValueOffset + sizeof(Value)); + unsigned nonArgSlots = (1 + isConstructing_) * sizeof(Value); + BaseValueIndex calleeSlot(BaselineStackReg, argcReg, ICStackValueOffset + nonArgSlots); masm.loadValue(calleeSlot, R1); regs.take(R1); @@ -11412,7 +11472,7 @@ ICCall_ClassHook::Compiler::generateStubCode(MacroAssembler& masm) enterStubFrame(masm, regs.getAny()); regs.add(scratch); - pushCallArguments(masm, regs, argcReg, /* isJitCall = */ false); + pushCallArguments(masm, regs, argcReg, /* isJitCall = */ false, isConstructing_); regs.take(scratch); if (isConstructing_) { diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index 7ceaa16a616..7a826a473e0 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -5522,10 +5522,11 @@ class ICCallStubCompiler : public ICStubCompiler }; void pushCallArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs, - Register argcReg, bool isJitCall); + Register argcReg, bool isJitCall, bool isConstructing = false); void pushSpreadCallArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs, - Register argcReg, bool isJitCall); - void guardSpreadCall(MacroAssembler& masm, Register argcReg, Label* failure); + Register argcReg, bool isJitCall, bool isConstructing); + void guardSpreadCall(MacroAssembler& masm, Register argcReg, Label* failure, + bool isConstructing); Register guardFunApply(MacroAssembler& masm, AllocatableGeneralRegisterSet regs, Register argcReg, bool checkNative, FunApplyThing applyThing, Label* failure); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 95c8ed8a060..2ab009954d4 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2932,12 +2932,12 @@ CodeGenerator::visitCallGetIntrinsicValue(LCallGetIntrinsicValue* lir) callVM(GetIntrinsicValueInfo, lir); } -typedef bool (*InvokeFunctionFn)(JSContext*, HandleObject, uint32_t, Value*, MutableHandleValue); +typedef bool (*InvokeFunctionFn)(JSContext*, HandleObject, bool, uint32_t, Value*, MutableHandleValue); static const VMFunction InvokeFunctionInfo = FunctionInfo(InvokeFunction); void CodeGenerator::emitCallInvokeFunction(LInstruction* call, Register calleereg, - uint32_t argc, uint32_t unusedStack) + bool constructing, uint32_t argc, uint32_t unusedStack) { // Nestle %esp up to the argument vector. // Each path must account for framePushed_ separately, for callVM to be valid. @@ -2945,6 +2945,7 @@ CodeGenerator::emitCallInvokeFunction(LInstruction* call, Register calleereg, pushArg(masm.getStackPointer()); // argv. pushArg(Imm32(argc)); // argc. + pushArg(Imm32(constructing)); // constructing. pushArg(calleereg); // JSFunction*. callVM(InvokeFunctionInfo, call); @@ -2999,7 +3000,8 @@ CodeGenerator::visitCallGeneric(LCallGeneric* call) // Check whether the provided arguments satisfy target argc. // We cannot have lowered to LCallGeneric with a known target. Assert that we didn't // add any undefineds in IonBuilder. NB: MCall::numStackArgs includes |this|. - MOZ_ASSERT(call->numActualArgs() == call->mir()->numStackArgs() - 1); + DebugOnly numNonArgsOnStack = 1 + call->isConstructing(); + MOZ_ASSERT(call->numActualArgs() == call->mir()->numStackArgs() - numNonArgsOnStack); masm.load16ZeroExtend(Address(calleereg, JSFunction::offsetOfNargs()), nargsreg); masm.branch32(Assembler::Above, nargsreg, Imm32(call->numActualArgs()), &thunk); masm.jump(&makeCall); @@ -3026,7 +3028,8 @@ CodeGenerator::visitCallGeneric(LCallGeneric* call) // Handle uncompiled or native functions. masm.bind(&invoke); - emitCallInvokeFunction(call, calleereg, call->numActualArgs(), unusedStack); + emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->numActualArgs(), + unusedStack); masm.bind(&end); @@ -3040,19 +3043,40 @@ CodeGenerator::visitCallGeneric(LCallGeneric* call) } } +typedef bool (*InvokeFunctionShuffleFn)(JSContext*, HandleObject, uint32_t, uint32_t, Value*, + MutableHandleValue); +static const VMFunction InvokeFunctionShuffleInfo = + FunctionInfo(InvokeFunctionShuffleNewTarget); +void +CodeGenerator::emitCallInvokeFunctionShuffleNewTarget(LCallKnown* call, Register calleeReg, + uint32_t numFormals, uint32_t unusedStack) +{ + masm.freeStack(unusedStack); + + pushArg(masm.getStackPointer()); + pushArg(Imm32(numFormals)); + pushArg(Imm32(call->numActualArgs())); + pushArg(calleeReg); + + callVM(InvokeFunctionShuffleInfo, call); + + masm.reserveStack(unusedStack); +} + void CodeGenerator::visitCallKnown(LCallKnown* call) { Register calleereg = ToRegister(call->getFunction()); Register objreg = ToRegister(call->getTempObject()); uint32_t unusedStack = StackOffsetOfPassedArg(call->argslot()); - DebugOnly target = call->getSingleTarget(); + JSFunction* target = call->getSingleTarget(); Label end, uncompiled; // Native single targets are handled by LCallNative. MOZ_ASSERT(!target->isNative()); // Missing arguments must have been explicitly appended by the IonBuilder. - MOZ_ASSERT(target->nargs() <= call->mir()->numStackArgs() - 1); + DebugOnly numNonArgsOnStack = 1 + call->isConstructing(); + MOZ_ASSERT(target->nargs() <= call->mir()->numStackArgs() - numNonArgsOnStack); MOZ_ASSERT_IF(call->mir()->isConstructing(), target->isConstructor()); @@ -3092,7 +3116,10 @@ CodeGenerator::visitCallKnown(LCallKnown* call) // Handle uncompiled functions. masm.bind(&uncompiled); - emitCallInvokeFunction(call, calleereg, call->numActualArgs(), unusedStack); + if (call->isConstructing() && target->nargs() > call->numActualArgs()) + emitCallInvokeFunctionShuffleNewTarget(call, calleereg, target->nargs(), unusedStack); + else + emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->numActualArgs(), unusedStack); masm.bind(&end); @@ -3118,6 +3145,7 @@ CodeGenerator::emitCallInvokeFunction(LApplyArgsGeneric* apply, Register extraSt pushArg(objreg); // argv. pushArg(ToRegister(apply->getArgc())); // argc. + pushArg(Imm32(false)); // isConstrucing. pushArg(ToRegister(apply->getFunction())); // JSFunction*. // This specialization og callVM restore the extraStackSize after the call. diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 610022d8edd..df748f2a9f8 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -130,8 +130,13 @@ class CodeGenerator : public CodeGeneratorSpecific void visitOutOfLineCallPostWriteBarrier(OutOfLineCallPostWriteBarrier* ool); void visitCallNative(LCallNative* call); void emitCallInvokeFunction(LInstruction* call, Register callereg, - uint32_t argc, uint32_t unusedStack); + bool isConstructing, uint32_t argc, + uint32_t unusedStack); void visitCallGeneric(LCallGeneric* call); + void emitCallInvokeFunctionShuffleNewTarget(LCallKnown *call, + Register calleeReg, + uint32_t numFormals, + uint32_t unusedStack); void visitCallKnown(LCallKnown* call); void emitCallInvokeFunction(LApplyArgsGeneric* apply, Register extraStackSize); void emitPushArguments(LApplyArgsGeneric* apply, Register extraStackSpace); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 628e4a42294..e6914a1ce5c 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2562,7 +2562,8 @@ jit::SetEnterJitData(JSContext* cx, EnterJitData& data, RunState& state, AutoVal data.maxArgv = args.base() + 1; } else { MOZ_ASSERT(vals.empty()); - if (!vals.reserve(Max(args.length() + 1, numFormals + 1))) + unsigned numPushedArgs = Max(args.length(), numFormals); + if (!vals.reserve(numPushedArgs + 1 + data.constructing)) return false; // Append |this| and any provided arguments. @@ -2573,7 +2574,10 @@ jit::SetEnterJitData(JSContext* cx, EnterJitData& data, RunState& state, AutoVal while (vals.length() < numFormals + 1) vals.infallibleAppend(UndefinedValue()); - MOZ_ASSERT(vals.length() >= numFormals + 1); + if (data.constructing) + vals.infallibleAppend(args.newTarget()); + + MOZ_ASSERT(vals.length() >= numFormals + 1 + data.constructing); data.maxArgv = vals.begin(); } } else { diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 845165b7372..7fabd10fc82 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -5268,6 +5268,8 @@ IonBuilder::inlineCallsite(const ObjectVector& targets, CallInfo& callInfo) if (target->isSingleton()) { // Replace the function with an MConstant. MConstant* constFun = constant(ObjectValue(*target)); + if (callInfo.constructing() && callInfo.getNewTarget() == callInfo.fun()) + callInfo.setNewTarget(constFun); callInfo.setFun(constFun); } @@ -6136,7 +6138,7 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing) } } - int calleeDepth = -((int)argc + 2); + int calleeDepth = -((int)argc + 2 + constructing); // Acquire known call target if existent. ObjectVector targets(alloc()); @@ -6279,11 +6281,14 @@ IonBuilder::makeCallHelper(JSFunction* target, CallInfo& callInfo) } } - MCall* call = MCall::New(alloc(), target, targetArgs + 1, callInfo.argc(), - callInfo.constructing(), isDOMCall); + MCall* call = MCall::New(alloc(), target, targetArgs + 1 + callInfo.constructing(), + callInfo.argc(), callInfo.constructing(), isDOMCall); if (!call) return nullptr; + if (callInfo.constructing()) + call->addArg(targetArgs + 1, callInfo.getNewTarget()); + // Explicitly pad any missing arguments with |undefined|. // This permits skipping the argumentsRectifier. for (int i = targetArgs; i > (int)callInfo.argc(); i--) { @@ -9440,7 +9445,7 @@ IonBuilder::jsop_rest() } // We know the exact number of arguments the callee pushed. - unsigned numActuals = inlineCallInfo_->argv().length(); + unsigned numActuals = inlineCallInfo_->argc(); unsigned numFormals = info().nargs() - 1; unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0; diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index ace8c2d8707..fd2a8fb6867 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -1244,6 +1244,7 @@ class CallInfo { MDefinition* fun_; MDefinition* thisArg_; + MDefinition* newTargetArg_; MDefinitionVector args_; bool constructing_; @@ -1253,6 +1254,7 @@ class CallInfo CallInfo(TempAllocator& alloc, bool constructing) : fun_(nullptr), thisArg_(nullptr), + newTargetArg_(nullptr), args_(alloc), constructing_(constructing), setter_(false) @@ -1264,6 +1266,9 @@ class CallInfo fun_ = callInfo.fun(); thisArg_ = callInfo.thisArg(); + if (constructing()) + newTargetArg_ = callInfo.getNewTarget(); + if (!args_.appendAll(callInfo.argv())) return false; @@ -1276,6 +1281,10 @@ class CallInfo // Get the arguments in the right order if (!args_.reserve(argc)) return false; + + if (constructing()) + setNewTarget(current->pop()); + for (int32_t i = argc; i > 0; i--) args_.infallibleAppend(current->peek(-i)); current->popn(argc); @@ -1297,13 +1306,16 @@ class CallInfo for (uint32_t i = 0; i < argc(); i++) current->push(getArg(i)); + + if (constructing()) + current->push(getNewTarget()); } uint32_t argc() const { return args_.length(); } uint32_t numFormals() const { - return argc() + 2; + return argc() + 2 + constructing(); } bool setArgs(const MDefinitionVector& args) { @@ -1349,6 +1361,15 @@ class CallInfo return constructing_; } + void setNewTarget(MDefinition* newTarget) { + MOZ_ASSERT(constructing()); + newTargetArg_ = newTarget; + } + MDefinition* getNewTarget() const { + MOZ_ASSERT(newTargetArg_); + return newTargetArg_; + } + bool isSetter() const { return setter_; } @@ -1368,6 +1389,8 @@ class CallInfo void setImplicitlyUsedUnchecked() { fun_->setImplicitlyUsedUnchecked(); thisArg_->setImplicitlyUsedUnchecked(); + if (newTargetArg_) + newTargetArg_->setImplicitlyUsedUnchecked(); for (uint32_t i = 0; i < argc(); i++) getArg(i)->setImplicitlyUsedUnchecked(); } diff --git a/js/src/jit/JitFrameIterator.h b/js/src/jit/JitFrameIterator.h index 64452bf48eb..c6d32286a04 100644 --- a/js/src/jit/JitFrameIterator.h +++ b/js/src/jit/JitFrameIterator.h @@ -746,13 +746,14 @@ class InlineFrameIterator InlineFrameIterator it(cx, this); ++it; unsigned argsObjAdj = it.script()->argumentsHasVarBinding() ? 1 : 0; + bool hasNewTarget = isConstructing(); SnapshotIterator parent_s(it.snapshotIterator()); // Skip over all slots until we get to the last slots // (= arguments slots of callee) the +3 is for [this], [returnvalue], // [scopechain], and maybe +1 for [argsObj] - MOZ_ASSERT(parent_s.numAllocations() >= nactual + 3 + argsObjAdj); - unsigned skip = parent_s.numAllocations() - nactual - 3 - argsObjAdj; + MOZ_ASSERT(parent_s.numAllocations() >= nactual + 3 + argsObjAdj + hasNewTarget); + unsigned skip = parent_s.numAllocations() - nactual - 3 - argsObjAdj - hasNewTarget; for (unsigned j = 0; j < skip; j++) parent_s.skip(); diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index b1371726ae6..3bde7ad2a89 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -1047,8 +1047,9 @@ MarkThisAndArguments(JSTracer* trc, JitFrameLayout* layout) // Trace |this|. TraceRoot(trc, argv, "ion-thisv"); - // Trace actual arguments beyond the formals. Note + 1 for thisv. - for (size_t i = nformals + 1; i < nargs + 1; i++) + // Trace actual arguments and newTarget beyond the formals. Note + 1 for thisv. + bool constructing = CalleeTokenIsConstructing(layout->calleeToken()); + for (size_t i = nformals + 1; i < nargs + 1 + constructing; i++) TraceRoot(trc, &argv[i], "ion-argv"); } @@ -2458,7 +2459,8 @@ InlineFrameIterator::findNextFrame() MOZ_CRASH("Couldn't deduce the number of arguments of an ionmonkey frame"); // Skip over non-argument slots, as well as |this|. - unsigned skipCount = (si_.numAllocations() - 1) - numActualArgs_ - 1; + bool skipNewTarget = JSOp(*pc_) == JSOP_NEW; + unsigned skipCount = (si_.numAllocations() - 1) - numActualArgs_ - 1 - skipNewTarget; for (unsigned j = 0; j < skipCount; j++) si_.skip(); diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 6823ec0356e..23cb4b354e0 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -1533,6 +1533,10 @@ class LJSCallInstructionHelper : public LCallInstructionHelpernumActualArgs(); } + + bool isConstructing() const { + return mir()->isConstructing(); + } }; // Generates a polymorphic callsite, wherein the function being called is diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 5c867650f07..ebed56f2992 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -2882,9 +2882,11 @@ IonBuilder::inlineBoundFunction(CallInfo& nativeCallInfo, JSFunction* target) // Don't optimize if we're constructing and the callee is not a // constructor, so that CallKnown does not have to handle this case // (it should always throw). - if (nativeCallInfo.constructing() && !scriptedTarget->isConstructor()) { + if (nativeCallInfo.constructing() && !scriptedTarget->isConstructor()) + return InliningStatus_NotInlined; + + if (nativeCallInfo.constructing() && nativeCallInfo.getNewTarget() != nativeCallInfo.fun()) return InliningStatus_NotInlined; - } if (gc::IsInsideNursery(scriptedTarget)) return InliningStatus_NotInlined; @@ -2923,6 +2925,11 @@ IonBuilder::inlineBoundFunction(CallInfo& nativeCallInfo, JSFunction* target) for (size_t i = 0; i < nativeCallInfo.argc(); i++) callInfo.argv().infallibleAppend(nativeCallInfo.getArg(i)); + // We only inline when it was not a super-call, so just set the newTarget + // to be the target function, per spec. + if (nativeCallInfo.constructing()) + callInfo.setNewTarget(callInfo.fun()); + if (!makeCall(scriptedTarget, callInfo)) return InliningStatus_Error; diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 8b701d7525e..2da5c843075 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -57,10 +57,10 @@ VMFunction::addToFunctions() } bool -InvokeFunction(JSContext* cx, HandleObject obj, uint32_t argc, Value* argv, +InvokeFunction(JSContext* cx, HandleObject obj, bool constructing, uint32_t argc, Value* argv, MutableHandleValue rval) { - AutoArrayRooter argvRoot(cx, argc + 1, argv); + AutoArrayRooter argvRoot(cx, argc + 1 + constructing, argv); // Data in the argument vector is arranged for a JIT -> JIT call. Value thisv = argv[0]; @@ -70,11 +70,20 @@ InvokeFunction(JSContext* cx, HandleObject obj, uint32_t argc, Value* argv, // When creating this failed / is impossible at caller site, i.e. MagicValue(JS_IS_CONSTRUCTING), // we use InvokeConstructor that creates it at the callee side. if (thisv.isMagic(JS_IS_CONSTRUCTING)) - return InvokeConstructor(cx, ObjectValue(*obj), argc, argvWithoutThis, rval); + return InvokeConstructor(cx, ObjectValue(*obj), argc, argvWithoutThis, true, rval); return Invoke(cx, thisv, ObjectValue(*obj), argc, argvWithoutThis, rval); } +bool +InvokeFunctionShuffleNewTarget(JSContext* cx, HandleObject obj, uint32_t numActualArgs, + uint32_t numFormalArgs, Value* argv, MutableHandleValue rval) +{ + MOZ_ASSERT(numFormalArgs > numActualArgs); + argv[1 + numActualArgs] = argv[1 + numFormalArgs]; + return InvokeFunction(cx, obj, true, numActualArgs, argv, rval); +} + bool CheckOverRecursed(JSContext* cx) { diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 83d79081434..ae6311d387c 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -631,8 +631,10 @@ class AutoDetectInvalidation } }; -bool InvokeFunction(JSContext* cx, HandleObject obj0, uint32_t argc, Value* argv, - MutableHandleValue rval); +bool InvokeFunction(JSContext* cx, HandleObject obj0, bool constructing, uint32_t argc, + Value* argv, MutableHandleValue rval); +bool InvokeFunctionShuffleNewTarget(JSContext* cx, HandleObject obj, uint32_t numActualArgs, + uint32_t numFormalArgs, Value* argv, MutableHandleValue rval); bool CheckOverRecursed(JSContext* cx); bool CheckOverRecursedWithExtra(JSContext* cx, BaselineFrame* frame, diff --git a/js/src/jit/arm/Trampoline-arm.cpp b/js/src/jit/arm/Trampoline-arm.cpp index 5f4211fb6de..82dedef8b12 100644 --- a/js/src/jit/arm/Trampoline-arm.cpp +++ b/js/src/jit/arm/Trampoline-arm.cpp @@ -152,6 +152,16 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type) masm.loadPtr(slot_vp, r10); masm.unboxInt32(Address(r10, 0), r10); + { + Label noNewTarget; + masm.branchTest32(Assembler::Zero, r9, Imm32(CalleeToken_FunctionConstructing), + &noNewTarget); + + masm.add32(Imm32(1), r1); + + masm.bind(&noNewTarget); + } + // Guarantee stack alignment of Jit frames. // // This code moves the stack pointer to the location where it should be when @@ -465,12 +475,29 @@ JitRuntime::generateArgumentsRectifier(JSContext* cx, void** returnAddrOut) masm.ma_sub(r6, r8, r2); - masm.moveValue(UndefinedValue(), r5, r4); + // Get the topmost argument. + masm.ma_alu(sp, lsl(r8, 3), r3, OpAdd); // r3 <- r3 + nargs * 8 + masm.ma_add(r3, Imm32(sizeof(RectifierFrameLayout)), r3); - masm.ma_mov(sp, r3); // Save %sp. - masm.ma_mov(sp, r7); // Save %sp again. + { + Label notConstructing; + + masm.branchTest32(Assembler::Zero, r1, Imm32(CalleeToken_FunctionConstructing), + ¬Constructing); + + // Add sizeof(Value) to overcome |this| + masm.ma_dataTransferN(IsLoad, 64, true, r3, Imm32(8), r4, Offset); + masm.ma_dataTransferN(IsStore, 64, true, sp, Imm32(-8), r4, PreIndex); + + // Include the newly pushed newTarget value in the frame size + // calculated below. + masm.add32(Imm32(1), r6); + + masm.bind(¬Constructing); + } // Push undefined. + masm.moveValue(UndefinedValue(), r5, r4); { Label undefLoopTop; masm.bind(&undefLoopTop); @@ -480,11 +507,6 @@ JitRuntime::generateArgumentsRectifier(JSContext* cx, void** returnAddrOut) masm.ma_b(&undefLoopTop, Assembler::NonZero); } - // Get the topmost argument. - - masm.ma_alu(r3, lsl(r8, 3), r3, OpAdd); // r3 <- r3 + nargs * 8 - masm.ma_add(r3, Imm32(sizeof(RectifierFrameLayout)), r3); - // Push arguments, |nargs| + 1 times (to include |this|). { Label copyLoopTop; diff --git a/js/src/jit/x64/Trampoline-x64.cpp b/js/src/jit/x64/Trampoline-x64.cpp index 49bfa436344..ed2272c57c6 100644 --- a/js/src/jit/x64/Trampoline-x64.cpp +++ b/js/src/jit/x64/Trampoline-x64.cpp @@ -87,6 +87,18 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type) // Remember number of bytes occupied by argument vector masm.mov(reg_argc, r13); + + // if we are constructing, that also needs to include newTarget + { + Label noNewTarget; + masm.branchTest32(Assembler::Zero, token, Imm32(CalleeToken_FunctionConstructing), + &noNewTarget); + + masm.addq(Imm32(1), r13); + + masm.bind(&noNewTarget); + } + masm.shll(Imm32(3), r13); // r13 = argc * sizeof(Value) static_assert(sizeof(Value) == 1 << 3, "Constant is baked in assembly code"); @@ -111,7 +123,7 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type) ***************************************************************/ // r13 still stores the number of bytes in the argument vector. - masm.addq(reg_argv, r13); // r13 points above last argument. + masm.addq(reg_argv, r13); // r13 points above last argument or newTarget // while r13 > rdx, push arguments. { @@ -397,9 +409,19 @@ JitRuntime::generateArgumentsRectifier(JSContext* cx, void** returnAddrOut) masm.andq(Imm32(uint32_t(CalleeTokenMask)), rcx); masm.movzwl(Operand(rcx, JSFunction::offsetOfNargs()), rcx); - // Including |this|, there are (|nformals| + 1) arguments to push to the - // stack. Then we push a JitFrameLayout. We compute the padding expressed - // in the number of extra |undefined| values to push on the stack. + // Stash another copy in r11, since we are going to do destructive operations + // on rcx + masm.mov(rcx, r11); + + static_assert(CalleeToken_FunctionConstructing == 1, + "Ensure that we can use the constructing bit to count the value"); + masm.mov(rax, rdx); + masm.andq(Imm32(uint32_t(CalleeToken_FunctionConstructing)), rdx); + + // Including |this|, and |new.target|, there are (|nformals| + 1 + isConstructing) + // arguments to push to the stack. Then we push a JitFrameLayout. We + // compute the padding expressed in the number of extra |undefined| values + // to push on the stack. static_assert(sizeof(JitFrameLayout) % JitStackAlignment == 0, "No need to consider the JitFrameLayout for aligning the stack"); static_assert(JitStackAlignment % sizeof(Value) == 0, @@ -407,6 +429,7 @@ JitRuntime::generateArgumentsRectifier(JSContext* cx, void** returnAddrOut) MOZ_ASSERT(IsPowerOfTwo(JitStackValueAlignment)); masm.addl(Imm32(JitStackValueAlignment - 1 /* for padding */ + 1 /* for |this| */), rcx); + masm.addl(rdx, rcx); masm.andl(Imm32(~(JitStackValueAlignment - 1)), rcx); // Load the number of |undefined|s to push into %rcx. @@ -456,6 +479,28 @@ JitRuntime::generateArgumentsRectifier(JSContext* cx, void** returnAddrOut) masm.j(Assembler::NonZero, ©LoopTop); } + // if constructing, copy newTarget + { + Label notConstructing; + + masm.branchTest32(Assembler::Zero, rax, Imm32(CalleeToken_FunctionConstructing), + ¬Constructing); + + // thisFrame[numFormals] = prevFrame[argc] + ValueOperand newTarget(r10); + + // +1 for |this|. We want vp[argc], so don't subtract 1 + BaseIndex newTargetSrc(r9, rdx, TimesEight, sizeof(RectifierFrameLayout) + sizeof(Value)); + masm.loadValue(newTargetSrc, newTarget); + + // Again, 1 for |this| + BaseIndex newTargetDest(rsp, r11, TimesEight, sizeof(Value)); + masm.storeValue(newTarget, newTargetDest); + + masm.bind(¬Constructing); + } + + // Caller: // [arg2] [arg1] [this] [[argc] [callee] [descr] [raddr]] <- r9 // diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h index 301a0642619..8ea05e8a37b 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h @@ -624,6 +624,11 @@ class MacroAssemblerX86Shared : public Assembler test32(Operand(address), imm); j(cond, label); } + void branchTest32(Condition cond, const Operand& lhs, Imm32 imm, Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || cond == NotSigned); + test32(lhs, imm); + j(cond, label); + } void jump(Label* label) { jmp(label); diff --git a/js/src/jit/x86/Trampoline-x86.cpp b/js/src/jit/x86/Trampoline-x86.cpp index 5c5492d2668..063e6cefe28 100644 --- a/js/src/jit/x86/Trampoline-x86.cpp +++ b/js/src/jit/x86/Trampoline-x86.cpp @@ -65,8 +65,22 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type) // compiled function. masm.movl(esp, esi); - // eax <- 8*argc, eax is now the offset betwen argv and the last + // Load the number of values to be copied (argc) into eax masm.loadPtr(Address(ebp, ARG_ARGC), eax); + + // If we are constructing, that also needs to include newTarget + { + Label noNewTarget; + masm.loadPtr(Address(ebp, ARG_CALLEETOKEN), edx); + masm.branchTest32(Assembler::Zero, edx, Imm32(CalleeToken_FunctionConstructing), + &noNewTarget); + + masm.addl(Imm32(1), eax); + + masm.bind(&noNewTarget); + } + + // eax <- 8*numValues, eax is now the offset betwen argv and the last value. masm.shll(Imm32(3), eax); // Guarantee stack alignment of Jit frames. @@ -397,6 +411,14 @@ JitRuntime::generateArgumentsRectifier(JSContext* cx, void** returnAddrOut) MOZ_ASSERT(IsPowerOfTwo(JitStackValueAlignment)); masm.addl(Imm32(JitStackValueAlignment - 1 /* for padding */), ecx); + + // Account for newTarget, if necessary. + static_assert(CalleeToken_FunctionConstructing == 1, + "Ensure that we can use the constructing bit to count an extra push"); + masm.mov(eax, edx); + masm.andl(Imm32(CalleeToken_FunctionConstructing), edx); + masm.addl(edx, ecx); + masm.andl(Imm32(~(JitStackValueAlignment - 1)), ecx); masm.subl(esi, ecx); @@ -453,6 +475,31 @@ JitRuntime::generateArgumentsRectifier(JSContext* cx, void** returnAddrOut) masm.j(Assembler::NonZero, ©LoopTop); } + { + Label notConstructing; + + masm.mov(eax, ebx); + masm.branchTest32(Assembler::Zero, ebx, Imm32(CalleeToken_FunctionConstructing), + ¬Constructing); + + BaseValueIndex src(FramePointer, edx, + sizeof(RectifierFrameLayout) + + sizeof(Value) + + sizeof(void*)); + + masm.andl(Imm32(CalleeTokenMask), ebx); + masm.movzwl(Operand(ebx, JSFunction::offsetOfNargs()), ebx); + + BaseValueIndex dst(esp, ebx, sizeof(Value)); + + ValueOperand newTarget(ecx, edi); + + masm.loadValue(src, newTarget); + masm.storeValue(newTarget, dst); + + masm.bind(¬Constructing); + } + // Construct descriptor, accounting for pushed frame pointer above masm.lea(Operand(FramePointer, sizeof(void*)), ebx); masm.subl(esp, ebx); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index ccde14cfafa..ed0379abf84 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4439,7 +4439,7 @@ JS::Construct(JSContext* cx, HandleValue fval, const JS::HandleValueArray& args, assertSameCompartment(cx, fval, args); AutoLastFrameCheck lfc(cx); - return InvokeConstructor(cx, fval, args.length(), args.begin(), rval); + return InvokeConstructor(cx, fval, args.length(), args.begin(), false, rval); } static JSObject* @@ -4454,12 +4454,13 @@ JS_NewHelper(JSContext* cx, HandleObject ctor, const JS::HandleValueArray& input // of object to create, create it, and clamp the return value to an object, // among other details. InvokeConstructor does the hard work. InvokeArgs args(cx); - if (!args.init(inputArgs.length())) + if (!args.init(inputArgs.length(), true)) return nullptr; args.setCallee(ObjectValue(*ctor)); args.setThis(NullValue()); PodCopy(args.array(), inputArgs.begin(), inputArgs.length()); + args.newTarget().setObject(*ctor); if (!InvokeConstructor(cx, args)) return nullptr; diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 393b349bfde..8cd6c57e0d2 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -3191,7 +3191,7 @@ array_of(JSContext* cx, unsigned argc, Value* vp) { RootedValue v(cx); Value argv[1] = {NumberValue(args.length())}; - if (!InvokeConstructor(cx, args.thisv(), 1, argv, &v)) + if (!InvokeConstructor(cx, args.thisv(), 1, argv, false, &v)) return false; obj = ToObject(cx, v); if (!obj) @@ -3286,6 +3286,10 @@ bool js::ArrayConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + + if (args.isConstructing()) + MOZ_ASSERT(args.newTarget().toObject().as().native() == js::ArrayConstructor); + RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array)); if (!group) return false; diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 0fcbd539123..fa4df011ad9 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -1552,7 +1552,7 @@ js::CallOrConstructBoundFunction(JSContext* cx, unsigned argc, Value* vp) const Value& boundThis = fun->getBoundFunctionThis(); InvokeArgs invokeArgs(cx); - if (!invokeArgs.init(args.length() + argslen)) + if (!invokeArgs.init(args.length() + argslen, args.isConstructing())) return false; /* 15.3.4.5.1, 15.3.4.5.2 step 4. */ @@ -1567,6 +1567,14 @@ js::CallOrConstructBoundFunction(JSContext* cx, unsigned argc, Value* vp) if (!constructing) invokeArgs.setThis(boundThis); + /* ES6 9.4.1.2 step 5 */ + if (constructing) { + if (&args.newTarget().toObject() == fun) + invokeArgs.newTarget().setObject(*target); + else + invokeArgs.newTarget().set(args.newTarget()); + } + if (constructing ? !InvokeConstructor(cx, invokeArgs) : !Invoke(cx, invokeArgs)) return false; diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 1ad0abfb579..cd0083a4897 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -448,16 +448,14 @@ GetCustomIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandle if (!Invoke(cx, ObjectValue(*obj), rval, 1, &arg, &rval)) return false; if (rval.isPrimitive()) { - /* - * We are always coming from js::ValueToIterator, and we are no longer on - * trace, so the object we are iterating over is on top of the stack (-1). - */ + // Ignore the stack when throwing. We can't tell whether we were + // supposed to skip over a new.target or not. JSAutoByteString bytes; if (!AtomToPrintableString(cx, name, &bytes)) return false; RootedValue val(cx, ObjectValue(*obj)); ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE, - -1, val, nullptr, bytes.ptr()); + JSDVG_IGNORE_STACK, val, nullptr, bytes.ptr()); return false; } objp.set(&rval.toObject()); diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 031f42088ca..11999abba6c 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -119,9 +119,11 @@ js::StackUses(JSScript* script, jsbytecode* pc) switch (op) { case JSOP_POPN: return GET_UINT16(pc); + case JSOP_NEW: + return 2 + GET_ARGC(pc) + 1; default: /* stack: fun, this, [argc arguments] */ - MOZ_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL || + MOZ_ASSERT(op == JSOP_CALL || op == JSOP_EVAL || op == JSOP_STRICTEVAL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY); return 2 + GET_ARGC(pc); } diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp index 4c6d1c31102..1d4ddb1ad99 100644 --- a/js/src/proxy/CrossCompartmentWrapper.cpp +++ b/js/src/proxy/CrossCompartmentWrapper.cpp @@ -304,6 +304,8 @@ CrossCompartmentWrapper::construct(JSContext* cx, HandleObject wrapper, const Ca if (!cx->compartment()->wrap(cx, args[n])) return false; } + if (!cx->compartment()->wrap(cx, args.newTarget())) + return false; if (!Wrapper::construct(cx, wrapper, args)) return false; } diff --git a/js/src/proxy/DirectProxyHandler.cpp b/js/src/proxy/DirectProxyHandler.cpp index da00c46c797..84ad3d7e322 100644 --- a/js/src/proxy/DirectProxyHandler.cpp +++ b/js/src/proxy/DirectProxyHandler.cpp @@ -82,7 +82,7 @@ DirectProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& { assertEnteredPolicy(cx, proxy, JSID_VOID, CALL); RootedValue target(cx, proxy->as().private_()); - return InvokeConstructor(cx, target, args.length(), args.array(), args.rval()); + return InvokeConstructor(cx, target, args.length(), args.array(), true, args.rval()); } bool diff --git a/js/src/proxy/ScriptedDirectProxyHandler.cpp b/js/src/proxy/ScriptedDirectProxyHandler.cpp index 3df7bf306b6..20f4cdc82a6 100644 --- a/js/src/proxy/ScriptedDirectProxyHandler.cpp +++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp @@ -1038,13 +1038,14 @@ ScriptedDirectProxyHandler::construct(JSContext* cx, HandleObject proxy, const C // step 6 if (trap.isUndefined()) { RootedValue targetv(cx, ObjectValue(*target)); - return InvokeConstructor(cx, targetv, args.length(), args.array(), args.rval()); + return InvokeConstructor(cx, targetv, args.length(), args.array(), true, args.rval()); } // step 8-9 Value constructArgv[] = { ObjectValue(*target), - ObjectValue(*argsArray) + ObjectValue(*argsArray), + args.newTarget() }; RootedValue thisValue(cx, ObjectValue(*handler)); if (!Invoke(cx, thisValue, trap, ArrayLength(constructArgv), constructArgv, args.rval())) diff --git a/js/src/proxy/ScriptedIndirectProxyHandler.cpp b/js/src/proxy/ScriptedIndirectProxyHandler.cpp index 6fd96904cdc..61ff10bc5fa 100644 --- a/js/src/proxy/ScriptedIndirectProxyHandler.cpp +++ b/js/src/proxy/ScriptedIndirectProxyHandler.cpp @@ -467,7 +467,7 @@ CallableScriptedIndirectProxyHandler::construct(JSContext* cx, HandleObject prox MOZ_ASSERT(ccHolder->getClass() == &CallConstructHolder); RootedValue construct(cx, ccHolder->as().getReservedSlot(1)); MOZ_ASSERT(construct.isObject() && construct.toObject().isCallable()); - return InvokeConstructor(cx, construct, args.length(), args.array(), args.rval()); + return InvokeConstructor(cx, construct, args.length(), args.array(), true, args.rval()); } const CallableScriptedIndirectProxyHandler CallableScriptedIndirectProxyHandler::singleton; diff --git a/js/src/tests/ecma_6/Class/newTargetCCW.js b/js/src/tests/ecma_6/Class/newTargetCCW.js new file mode 100644 index 00000000000..9f5f870d40d --- /dev/null +++ b/js/src/tests/ecma_6/Class/newTargetCCW.js @@ -0,0 +1,10 @@ +// Make sure we wrap the new target on CCW construct calls. +var g = newGlobal(); + +let f = g.eval('(function (expected) { this.accept = new.target === expected; })'); + +for (let i = 0; i < 1100; i++) + assertEq(new f(f).accept, true); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/js1_7/extensions/regress-354945-01.js b/js/src/tests/js1_7/extensions/regress-354945-01.js index e842a2c3bd7..76f1a3c82fb 100644 --- a/js/src/tests/js1_7/extensions/regress-354945-01.js +++ b/js/src/tests/js1_7/extensions/regress-354945-01.js @@ -6,7 +6,7 @@ //----------------------------------------------------------------------------- var BUGNUMBER = 354945; var summary = 'Do not crash with new Iterator'; -var expect = 'TypeError: trap __iterator__ for obj returned a primitive value'; +var expect = 'TypeError: trap __iterator__ for ({__iterator__:(function (){ })}) returned a primitive value'; var actual; diff --git a/js/src/tests/js1_7/extensions/regress-354945-02.js b/js/src/tests/js1_7/extensions/regress-354945-02.js index d09f7fe4971..261bf7de179 100644 --- a/js/src/tests/js1_7/extensions/regress-354945-02.js +++ b/js/src/tests/js1_7/extensions/regress-354945-02.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = 'TypeError: trap __iterator__ for obj returned a primitive value'; + expect = 'TypeError: trap __iterator__ for ({__iterator__:(function (){ })}) returned a primitive value'; var obj = {}; obj.__iterator__ = function(){ }; try diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 7c8d15e08bb..10bde96dc16 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -677,8 +677,9 @@ js::Invoke(JSContext* cx, CallArgs args, MaybeConstruct construct) /* MaybeConstruct is a subset of InitialFrameFlags */ InitialFrameFlags initial = (InitialFrameFlags) construct; + unsigned skipForCallee = args.length() + 1 + (construct == CONSTRUCT); if (args.calleev().isPrimitive()) - return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, construct); + return ReportIsNotFunction(cx, args.calleev(), skipForCallee, construct); const Class* clasp = args.callee().getClass(); @@ -691,7 +692,7 @@ js::Invoke(JSContext* cx, CallArgs args, MaybeConstruct construct) MOZ_ASSERT_IF(construct, !args.callee().constructHook()); JSNative call = args.callee().callHook(); if (!call) - return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, construct); + return ReportIsNotFunction(cx, args.calleev(), skipForCallee, construct); return CallJSNative(cx, call, args); } @@ -770,15 +771,18 @@ js::InvokeConstructor(JSContext* cx, CallArgs args) args.setThis(MagicValue(JS_IS_CONSTRUCTING)); + // +2 here and below to pass over |this| and |new.target| if (!args.calleev().isObject()) - return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, CONSTRUCT); + return ReportIsNotFunction(cx, args.calleev(), args.length() + 2, CONSTRUCT); + + MOZ_ASSERT(args.newTarget().isObject()); JSObject& callee = args.callee(); if (callee.is()) { RootedFunction fun(cx, &callee.as()); if (!fun->isConstructor()) - return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, CONSTRUCT); + return ReportIsNotFunction(cx, args.calleev(), args.length() + 2, CONSTRUCT); if (fun->isNative()) return CallJSNativeConstructor(cx, fun->native(), args); @@ -792,22 +796,26 @@ js::InvokeConstructor(JSContext* cx, CallArgs args) JSNative construct = callee.constructHook(); if (!construct) - return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, CONSTRUCT); + return ReportIsNotFunction(cx, args.calleev(), args.length() + 2, CONSTRUCT); return CallJSNativeConstructor(cx, construct, args); } bool js::InvokeConstructor(JSContext* cx, Value fval, unsigned argc, const Value* argv, - MutableHandleValue rval) + bool newTargetInArgv, MutableHandleValue rval) { InvokeArgs args(cx); - if (!args.init(argc)) + if (!args.init(argc, true)) return false; args.setCallee(fval); args.setThis(MagicValue(JS_THIS_POISON)); PodCopy(args.array(), argv, argc); + if (newTargetInArgv) + args.newTarget().set(argv[argc]); + else + args.newTarget().set(fval); if (!InvokeConstructor(cx, args)) return false; @@ -2891,16 +2899,25 @@ CASE(JSOP_STRICTSPREADEVAL) { static_assert(JSOP_SPREADEVAL_LENGTH == JSOP_STRICTSPREADEVAL_LENGTH, "spreadeval and strictspreadeval must be the same size"); - MOZ_ASSERT(REGS.stackDepth() >= 3); + bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW; - HandleValue callee = REGS.stackHandleAt(-3); - HandleValue thisv = REGS.stackHandleAt(-2); - HandleValue arr = REGS.stackHandleAt(-1); - MutableHandleValue ret = REGS.stackHandleAt(-3); - if (!SpreadCallOperation(cx, script, REGS.pc, thisv, callee, arr, ret)) + MOZ_ASSERT(REGS.stackDepth() >= 3u + construct); + + HandleValue callee = REGS.stackHandleAt(-3 - construct); + HandleValue thisv = REGS.stackHandleAt(-2 - construct); + HandleValue arr = REGS.stackHandleAt(-1 - construct); + MutableHandleValue ret = REGS.stackHandleAt(-3 - construct); + + RootedValue& newTarget = rootValue0; + if (construct) + newTarget = REGS.sp[-1]; + else + newTarget = NullValue(); + + if (!SpreadCallOperation(cx, script, REGS.pc, thisv, callee, arr, newTarget, ret)) goto error; - REGS.sp -= 2; + REGS.sp -= 2 + construct; } END_CASE(JSOP_SPREADCALL) @@ -2919,10 +2936,11 @@ CASE(JSOP_FUNCALL) if (REGS.fp()->hasPushedSPSFrame()) cx->runtime()->spsProfiler.updatePC(script, REGS.pc); - MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc)); - CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp); - bool construct = (*REGS.pc == JSOP_NEW); + unsigned argStackSlots = GET_ARGC(REGS.pc) + construct; + + MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc)); + CallArgs args = CallArgsFromSp(argStackSlots, REGS.sp, construct); JSFunction* maybeFun; bool isFunction = IsFunctionObject(args.calleev(), &maybeFun); @@ -4526,16 +4544,17 @@ js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, H bool js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue thisv, - HandleValue callee, HandleValue arr, MutableHandleValue res) + HandleValue callee, HandleValue arr, HandleValue newTarget, MutableHandleValue res) { RootedArrayObject aobj(cx, &arr.toObject().as()); uint32_t length = aobj->length(); JSOp op = JSOp(*pc); + bool constructing = op == JSOP_SPREADNEW; if (length > ARGS_LENGTH_MAX) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, - op == JSOP_SPREADNEW ? JSMSG_TOO_MANY_CON_SPREADARGS - : JSMSG_TOO_MANY_FUN_SPREADARGS); + constructing ? JSMSG_TOO_MANY_CON_SPREADARGS + : JSMSG_TOO_MANY_FUN_SPREADARGS); return false; } @@ -4550,7 +4569,7 @@ js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, Hand InvokeArgs args(cx); - if (!args.init(length)) + if (!args.init(length, constructing)) return false; args.setCallee(callee); @@ -4559,6 +4578,11 @@ js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, Hand if (!GetElements(cx, aobj, length, args.array())) return false; + if (constructing) { + MOZ_ASSERT(newTarget.isObject()); + args.newTarget().set(newTarget); + } + switch (op) { case JSOP_SPREADNEW: if (!InvokeConstructor(cx, args)) diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index be085abdbd2..807ad4434a3 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -95,7 +95,7 @@ InvokeConstructor(JSContext* cx, CallArgs args); /* See the fval overload of Invoke. */ extern bool InvokeConstructor(JSContext* cx, Value fval, unsigned argc, const Value* argv, - MutableHandleValue rval); + bool newTargetInArgv, MutableHandleValue rval); /* * Executes a script with the given scopeChain/this. The 'type' indicates @@ -436,7 +436,7 @@ InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, Handl bool SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue thisv, - HandleValue callee, HandleValue arr, MutableHandleValue res); + HandleValue callee, HandleValue arr, HandleValue newTarget, MutableHandleValue res); JSObject* NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc, diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index f9fac122bbf..06a1a6fd595 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -391,9 +391,9 @@ * Category: Statements * Type: Function * Operands: - * Stack: callee, this, args => rval + * Stack: callee, this, args, newTarget => rval */ \ - macro(JSOP_SPREADNEW, 42, "spreadnew", NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \ + macro(JSOP_SPREADNEW, 42, "spreadnew", NULL, 1, 4, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \ /* * spreadcall variant of JSOP_EVAL * @@ -766,8 +766,8 @@ * Category: Statements * Type: Function * Operands: uint16_t argc - * Stack: callee, this, args[0], ..., args[argc-1] => rval - * nuses: (argc+2) + * Stack: callee, this, args[0], ..., args[argc-1], newTarget => rval + * nuses: (argc+3) */ \ macro(JSOP_NEW, 82, js_new_str, NULL, 3, -1, 1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET) \ /* diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index e53c73b1acf..c23272e53f3 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -292,7 +292,10 @@ InterpreterStack::getCallFrame(JSContext* cx, const CallArgs& args, HandleScript // Pad any missing arguments with |undefined|. MOZ_ASSERT(args.length() < nformal); - nvals += nformal + 2; // Include callee, |this|. + bool isConstructing = *flags & InterpreterFrame::CONSTRUCTING; + unsigned nfunctionState = 2 + isConstructing; // callee, |this|, |new.target| + + nvals += nformal + nfunctionState; uint8_t* buffer = allocateFrame(cx, sizeof(InterpreterFrame) + nvals * sizeof(Value)); if (!buffer) return nullptr; @@ -303,8 +306,11 @@ InterpreterStack::getCallFrame(JSContext* cx, const CallArgs& args, HandleScript mozilla::PodCopy(argv, args.base(), 2 + args.length()); SetValueRangeToUndefined(argv + 2 + args.length(), nmissing); + if (isConstructing) + argv[2 + nformal] = args.newTarget(); + *pargv = argv + 2; - return reinterpret_cast(argv + 2 + nformal); + return reinterpret_cast(argv + nfunctionState + nformal); } MOZ_ALWAYS_INLINE bool diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 489aeabcd91..4b8c2b65b33 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -397,7 +397,7 @@ InterpreterFrame::markValues(JSTracer* trc, Value* sp, jsbytecode* pc) if (hasArgs()) { // Mark callee, |this| and arguments. unsigned argc = Max(numActualArgs(), numFormalArgs()); - TraceRootRange(trc, argc + 2, argv_ - 2, "fp argv"); + TraceRootRange(trc, argc + 2 + isConstructing(), argv_ - 2, "fp argv"); } else { // Mark callee and |this| TraceRootRange(trc, 2, ((Value*)this) - 2, "stack callee and this"); diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index d1d87b47395..6b2ab36bac0 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -938,7 +938,7 @@ class InterpreterRegs void popInlineFrame() { pc = fp_->prevpc(); - sp = fp_->prevsp() - fp_->numActualArgs() - 1; + sp = fp_->prevsp() - fp_->numActualArgs() - 1 - fp_->isConstructing(); fp_ = fp_->prev(); MOZ_ASSERT(fp_); } @@ -1033,12 +1033,14 @@ class InvokeArgs : public JS::CallArgs AutoValueVector v_; public: - explicit InvokeArgs(JSContext* cx) : v_(cx) {} + explicit InvokeArgs(JSContext* cx, bool construct = false) : v_(cx) {} - bool init(unsigned argc) { - if (!v_.resize(2 + argc)) + bool init(unsigned argc, bool construct = false) { + if (!v_.resize(2 + argc + construct)) return false; ImplicitCast(*this) = CallArgsFromVp(argc, v_.begin()); + // Set the internal flag, since we are not initializing from a made array + constructing_ = construct; return true; } }; diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 141e2d63f2d..e5d9ac3f9a2 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,7 +29,7 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 288; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 289; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);