Bug 762363 - ES6 spread-call syntax: f(...args). r=jorendorff.

This commit is contained in:
Tooru Fujisawa 2013-09-13 18:32:21 +09:00
parent 816764a48e
commit 3cd097471b
30 changed files with 912 additions and 68 deletions

View File

@ -416,7 +416,8 @@ js::DirectEval(JSContext *cx, const CallArgs &args)
AbstractFramePtr caller = iter.abstractFramePtr();
JS_ASSERT(IsBuiltinEvalForScope(caller.scopeChain(), args.calleev()));
JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL);
JS_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
JSOp(*iter.pc()) == JSOP_SPREADEVAL);
JS_ASSERT_IF(caller.isFunctionFrame(),
caller.compartment() == caller.callee()->compartment());

View File

@ -5048,6 +5048,9 @@ EmitDelete(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return true;
}
static bool
EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, uint32_t count);
static bool
EmitCallOrNew(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
@ -5079,18 +5082,20 @@ EmitCallOrNew(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
bool emitArgs = true;
ParseNode *pn2 = pn->pn_head;
bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE;
switch (pn2->getKind()) {
case PNK_NAME:
if (bce->emitterMode == BytecodeEmitter::SelfHosting &&
pn2->name() == cx->names().callFunction)
pn2->name() == cx->names().callFunction &&
!spread)
{
/*
* Special-casing of callFunction to emit bytecode that directly
* invokes the callee with the correct |this| object and arguments.
* callFunction(fun, thisArg, ...args) thus becomes:
* callFunction(fun, thisArg, arg0, arg1) thus becomes:
* - emit lookup for fun
* - emit lookup for thisArg
* - emit lookups for ...args
* - emit lookups for arg0, arg1
*
* argc is set to the amount of actually emitted args and the
* emitting of args below is disabled by setting emitArgs to false.
@ -5176,19 +5181,29 @@ EmitCallOrNew(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
*/
bool oldEmittingForInit = bce->emittingForInit;
bce->emittingForInit = false;
for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
if (!EmitTree(cx, bce, pn3))
return false;
if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
if (!spread) {
for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
if (!EmitTree(cx, bce, pn3))
return false;
if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
return false;
}
} else {
if (!EmitArray(cx, bce, pn2->pn_next, argc))
return false;
}
bce->emittingForInit = oldEmittingForInit;
}
if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
return false;
if (!spread) {
if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
return false;
} else {
if (Emit1(cx, bce, pn->getOp()) < 0)
return false;
}
CheckTypeSet(cx, bce, pn->getOp());
if (pn->isOp(JSOP_EVAL)) {
if (pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_SPREADEVAL)) {
uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
EMIT_UINT16_IMM_OP(JSOP_LINENO, lineNum);
}
@ -5559,7 +5574,29 @@ EmitObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
}
static bool
EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
EmitArrayComp(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
if (!EmitNewInit(cx, bce, JSProto_Array, pn))
return false;
/*
* Pass the new array's stack index to the PNK_ARRAYPUSH case via
* bce->arrayCompDepth, then simply traverse the PNK_FOR node and
* its kids under pn2 to generate this comprehension.
*/
JS_ASSERT(bce->stackDepth > 0);
unsigned saveDepth = bce->arrayCompDepth;
bce->arrayCompDepth = (uint32_t) (bce->stackDepth - 1);
if (!EmitTree(cx, bce, pn->pn_head))
return false;
bce->arrayCompDepth = saveDepth;
/* Emit the usual op needed for decompilation. */
return Emit1(cx, bce, JSOP_ENDINIT) >= 0;
}
static bool
EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, uint32_t count)
{
/*
* Emit code for [a, b, c] that is equivalent to constructing a new
@ -5570,31 +5607,8 @@ EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
* JSOP_SETELEM/JSOP_SETPROP would do.
*/
if (pn->isKind(PNK_ARRAYCOMP)) {
if (!EmitNewInit(cx, bce, JSProto_Array, pn))
return false;
/*
* Pass the new array's stack index to the PNK_ARRAYPUSH case via
* bce->arrayCompDepth, then simply traverse the PNK_FOR node and
* its kids under pn2 to generate this comprehension.
*/
JS_ASSERT(bce->stackDepth > 0);
unsigned saveDepth = bce->arrayCompDepth;
bce->arrayCompDepth = (uint32_t) (bce->stackDepth - 1);
if (!EmitTree(cx, bce, pn->pn_head))
return false;
bce->arrayCompDepth = saveDepth;
/* Emit the usual op needed for decompilation. */
return Emit1(cx, bce, JSOP_ENDINIT) >= 0;
}
if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
return EmitSingletonInitialiser(cx, bce, pn);
int32_t nspread = 0;
for (ParseNode *elt = pn->pn_head; elt; elt = elt->pn_next) {
for (ParseNode *elt = pn; elt; elt = elt->pn_next) {
if (elt->isKind(PNK_SPREAD))
nspread++;
}
@ -5607,9 +5621,9 @@ EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
// For arrays with spread, this is a very pessimistic allocation, the
// minimum possible final size.
SET_UINT24(pc, pn->pn_count - nspread);
SET_UINT24(pc, count - nspread);
ParseNode *pn2 = pn->pn_head;
ParseNode *pn2 = pn;
jsatomid atomIndex;
if (nspread && !EmitNumberOp(cx, 0, bce))
return false;
@ -5635,7 +5649,7 @@ EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
SET_UINT24(bce->code(off), atomIndex);
}
}
JS_ASSERT(atomIndex == pn->pn_count);
JS_ASSERT(atomIndex == count);
if (nspread) {
if (Emit1(cx, bce, JSOP_POP) < 0)
return false;
@ -6064,8 +6078,14 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
}
case PNK_ARRAY:
case PNK_ARRAYCOMP:
ok = EmitArray(cx, bce, pn);
if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
ok = EmitSingletonInitialiser(cx, bce, pn);
else
ok = EmitArray(cx, bce, pn->pn_head, pn->pn_count);
break;
case PNK_ARRAYCOMP:
ok = EmitArrayComp(cx, bce, pn);
break;
case PNK_OBJECT:

View File

@ -2998,7 +2998,8 @@ Parser<FullParseHandler>::makeSetCall(ParseNode *pn, unsigned msg)
{
JS_ASSERT(pn->isKind(PNK_CALL));
JS_ASSERT(pn->isArity(PN_LIST));
JS_ASSERT(pn->isOp(JSOP_CALL) || pn->isOp(JSOP_EVAL) ||
JS_ASSERT(pn->isOp(JSOP_CALL) || pn->isOp(JSOP_SPREADCALL) ||
pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_SPREADEVAL) ||
pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY));
if (!report(ParseStrictError, pc->sc->strict, pn, msg))
@ -5433,8 +5434,8 @@ Parser<FullParseHandler>::checkAndMarkAsIncOperand(ParseNode *kid, TokenKind tt,
!kid->isKind(PNK_DOT) &&
!kid->isKind(PNK_ELEM) &&
!(kid->isKind(PNK_CALL) &&
(kid->isOp(JSOP_CALL) ||
kid->isOp(JSOP_EVAL) ||
(kid->isOp(JSOP_CALL) || kid->isOp(JSOP_SPREADCALL) ||
kid->isOp(JSOP_EVAL) || kid->isOp(JSOP_SPREADEVAL) ||
kid->isOp(JSOP_FUNCALL) ||
kid->isOp(JSOP_FUNAPPLY))))
{
@ -6194,7 +6195,7 @@ Parser<ParseHandler>::assignExprWithoutYield(unsigned msg)
template <typename ParseHandler>
bool
Parser<ParseHandler>::argumentList(Node listNode)
Parser<ParseHandler>::argumentList(Node listNode, bool *isSpread)
{
if (tokenStream.matchToken(TOK_RP, TokenStream::Operand))
return true;
@ -6203,9 +6204,22 @@ Parser<ParseHandler>::argumentList(Node listNode)
bool arg0 = true;
do {
bool spread = false;
uint32_t begin = 0;
if (tokenStream.matchToken(TOK_TRIPLEDOT, TokenStream::Operand)) {
spread = true;
begin = pos().begin;
*isSpread = true;
}
Node argNode = assignExpr();
if (!argNode)
return false;
if (spread) {
argNode = handler.newUnary(PNK_SPREAD, JSOP_NOP, begin, argNode);
if (!argNode)
return null();
}
if (handler.isOperationWithoutParens(argNode, PNK_YIELD) &&
tokenStream.peekToken() == TOK_COMMA) {
@ -6213,7 +6227,7 @@ Parser<ParseHandler>::argumentList(Node listNode)
return false;
}
#if JS_HAS_GENERATOR_EXPRS
if (tokenStream.matchToken(TOK_FOR)) {
if (!spread && tokenStream.matchToken(TOK_FOR)) {
if (pc->lastYieldOffset != startYieldOffset) {
reportWithOffset(ParseError, false, pc->lastYieldOffset,
JSMSG_BAD_GENEXP_BODY, js_yield_str);
@ -6263,8 +6277,13 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
handler.addList(lhs, ctorExpr);
if (tokenStream.matchToken(TOK_LP) && !argumentList(lhs))
return null();
if (tokenStream.matchToken(TOK_LP)) {
bool isSpread = false;
if (!argumentList(lhs, &isSpread))
return null();
if (isSpread)
handler.setOp(lhs, JSOP_SPREADNEW);
}
} else {
lhs = primaryExpr(tt);
if (!lhs)
@ -6297,6 +6316,7 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
if (!nextMember)
return null();
} else if (allowCallSyntax && tt == TOK_LP) {
JSOp op = JSOP_CALL;
nextMember = handler.newList(PNK_CALL, null(), JSOP_CALL);
if (!nextMember)
return null();
@ -6304,7 +6324,7 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
if (JSAtom *atom = handler.isName(lhs)) {
if (atom == context->names().eval) {
/* Select JSOP_EVAL and flag pc as heavyweight. */
handler.setOp(nextMember, JSOP_EVAL);
op = JSOP_EVAL;
pc->sc->setBindingsAccessedDynamically();
/*
@ -6317,19 +6337,23 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
} else if (JSAtom *atom = handler.isGetProp(lhs)) {
/* Select JSOP_FUNAPPLY given foo.apply(...). */
if (atom == context->names().apply) {
handler.setOp(nextMember, JSOP_FUNAPPLY);
op = JSOP_FUNAPPLY;
if (pc->sc->isFunctionBox())
pc->sc->asFunctionBox()->usesApply = true;
} else if (atom == context->names().call) {
handler.setOp(nextMember, JSOP_FUNCALL);
op = JSOP_FUNCALL;
}
}
handler.setBeginPosition(nextMember, lhs);
handler.addList(nextMember, lhs);
if (!argumentList(nextMember))
bool isSpread = false;
if (!argumentList(nextMember, &isSpread))
return null();
if (isSpread)
op = (op == JSOP_EVAL ? JSOP_SPREADEVAL : JSOP_SPREADCALL);
handler.setOp(nextMember, op);
} else {
tokenStream.ungetToken();
return lhs;

View File

@ -544,7 +544,7 @@ class Parser : private AutoGCRooter, public StrictModeGetter
ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP);
bool arrayInitializerComprehensionTail(Node pn);
Node generatorExpr(Node kid);
bool argumentList(Node listNode);
bool argumentList(Node listNode, bool *isSpread);
Node letBlock(LetContext letContext);
Node destructuringExpr(BindData<ParseHandler> *data, TokenKind tt);

View File

@ -76,6 +76,9 @@ check("o[4 + 'h']", "o['4h']");
check("this.x");
check("ieval(undef)", "ieval(...)");
check("ieval.call()", "ieval.call(...)");
check("ieval(...[])", "ieval(...)");
check("ieval(...[undef])", "ieval(...)");
check("ieval(...[undef, undef])", "ieval(...)");
for (let tok of ["|", "^", "&", "==", "!==", "===", "!==", "<", "<=", ">", ">=",
">>", "<<", ">>>", "+", "-", "*", "/", "%"]) {

View File

@ -5,9 +5,14 @@ var offenders = [
"[1 ... n]",
"(...x)",
"[...x for (x of y)]",
"[...x, x for (x of y)]",
"[...]",
"(...)",
"[...,]"
"[...,]",
"[... ...[]]",
"(... ...[])",
"[x, ...]",
"(x, ...)"
];
for (var sample of offenders) {
assertThrowsInstanceOf(function () { eval(sample); }, SyntaxError);

View File

@ -1,3 +1,4 @@
load(libdir + "asserts.js");
load(libdir + "eqArrayHelper.js");
assertEqArray([...[1, 2, 3]], [1, 2, 3]);
@ -10,8 +11,44 @@ assertEqArray([1,, ...[2],, 3,, 4,], [1,, 2,, 3,, 4,]);
assertEqArray([...[1, 2, 3],,,,], [1, 2, 3,,,,]);
assertEqArray([,,...[1, 2, 3],,,,], [,,1,2,3,,,,]);
assertEqArray([...[undefined]], [undefined]);
// other iterable objects
assertEqArray([...Int32Array([1, 2, 3])], [1, 2, 3]);
assertEqArray([..."abc"], ["a", "b", "c"]);
assertEqArray([...[1, 2, 3].iterator()], [1, 2, 3]);
assertEqArray([...Set([1, 2, 3])], [1, 2, 3]);
assertEqArray([...Map([["a", "A"], ["b", "B"], ["c", "C"]])].map(([k, v]) => k + v), ["aA", "bB", "cC"]);
let itr = {
iterator: function() {
return {
i: 1,
next: function() {
if (this.i < 4)
return this.i++;
else
throw StopIteration;
}
};
}
};
assertEqArray([...itr], [1, 2, 3]);
let gen = {
iterator: function() {
for (let i = 1; i < 4; i ++)
yield i;
}
};
assertEqArray([...gen], [1, 2, 3]);
let a, b = [1, 2, 3];
assertEqArray([...a=b], [1, 2, 3]);
// According to the draft spec, null and undefined are to be treated as empty
// arrays. However, they are not iterable. If the spec is not changed to be in
// terms of iterables, these tests should be fixed.
//assertEqArray([1, ...null, 2], [1, 2]);
//assertEqArray([1, ...undefined, 2], [1, 2]);
assertThrowsInstanceOf(() => [...null], TypeError);
assertThrowsInstanceOf(() => [...undefined], TypeError);

View File

@ -0,0 +1,59 @@
load(libdir + "asserts.js");
assertEq(eval(...[]), undefined);
assertEq(eval(...["1 + 2"]), 3);
let a = 10, b = 1;
assertEq(eval(...["a + b"]), 11);
(function() {
let a = 20;
assertEq(eval(...["a + b"]), 21);
})();
with ({ a: 30 }) {
assertEq(eval(...["a + b"]), 31);
}
let line0 = Error().lineNumber;
try { // line0 + 1
eval(...["("]); // line0 + 2
} catch (e) {
assertEq(e.lineNumber, line0 + 2);
}
// other iterable objects
assertEq(eval(...["a + b"].iterator()), 11);
assertEq(eval(...Set(["a + b"])), 11);
let itr = {
iterator: function() {
return {
i: 0,
next: function() {
this.i++;
if (this.i == 1)
return "a + b";
else
throw StopIteration;
}
};
}
};
assertEq(eval(...itr), 11);
let gen = {
iterator: function() {
yield "a + b";
}
};
assertEq(eval(...gen), 11);
let c = ["C"], d = "D";
assertEq(eval(...c=["c[0] + d"]), "c[0] + dD");
// According to the draft spec, null and undefined are to be treated as empty
// arrays. However, they are not iterable. If the spec is not changed to be in
// terms of iterables, these tests should be fixed.
//assertEq(eval("a + b", ...null), 11);
//assertEq(eval("a + b", ...undefined), 11);
assertThrowsInstanceOf(() => eval("a + b", ...null), TypeError);
assertThrowsInstanceOf(() => eval("a + b", ...undefined), TypeError);

View File

@ -0,0 +1,13 @@
load(libdir + "eqArrayHelper.js");
var check = [];
function t(token) {
check.push(token);
return token;
}
let f = (...x) => x;
f(3, ...[t(1)], ...[t(2), t(3)], 34, 42, ...[t(4)]);
assertEqArray(check, [1, 2, 3, 4]);
var arr = [1, 2, 3];
assertEqArray(f(...arr, arr.pop()), [1, 2, 3, 3]);

View File

@ -0,0 +1,93 @@
load(libdir + "asserts.js");
load(libdir + "eqArrayHelper.js");
function checkCommon(f) {
assertEqArray(f.apply(null, ...[[1, 2, 3]]), [1, 2, 3]);
assertEqArray(f.apply(...[null], [1, 2, 3]), [1, 2, 3]);
assertEqArray(f.apply(...[null], ...[[1, 2, 3]]), [1, 2, 3]);
assertEqArray(f.apply(...[null, [1, 2, 3]]), [1, 2, 3]);
// other iterable objects
assertEqArray(f.apply(...Set([null, [1, 2, 3]])), [1, 2, 3]);
assertEqArray(f.apply(...[null, [1, 2, 3]].iterator()), [1, 2, 3]);
let itr = {
iterator: function() {
return {
i: 0,
next: function() {
this.i++;
if (this.i == 1)
return null;
else if (this.i == 2)
return [1, 2, 3];
else
throw StopIteration;
}
};
}
};
assertEqArray(f.apply(...itr), [1, 2, 3]);
let gen = {
iterator: function() {
yield null;
yield [1, 2, 3];
}
};
assertEqArray(f.apply(...gen), [1, 2, 3]);
let a;
assertEqArray(f.apply(null, ...a=[[1, 2, 3]]), [1, 2, 3]);
// According to the draft spec, null and undefined are to be treated as empty
// arrays. However, they are not iterable. If the spec is not changed to be in
// terms of iterables, these tests should be fixed.
//assertEqArray(f.apply(null, ...null, [1, 2, 3]), [1, 2, 3]);
//assertEqArray(f.apply(null, ...undefined, [1, 2, 3]), [1, 2, 3]);
assertThrowsInstanceOf(() => f.apply(null, ...null, [1, 2, 3]), TypeError);
assertThrowsInstanceOf(() => f.apply(null, ...undefined, [1, 2, 3]), TypeError);
}
function checkNormal(f) {
checkCommon(f);
assertEqArray(f.apply(null, ...[[]]), [undefined, undefined, undefined]);
assertEqArray(f.apply(null, ...[[1]]), [1, undefined, undefined]);
assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2, undefined]);
assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3]);
assertEqArray(f.apply(null, ...[[undefined]]), [undefined, undefined, undefined]);
}
checkNormal(function(a, b, c) [a, b, c]);
checkNormal((a, b, c) => [a, b, c]);
function checkDefault(f) {
checkCommon(f);
assertEqArray(f.apply(null, ...[[]]), [-1, -2, -3]);
assertEqArray(f.apply(null, ...[[1]]), [1, -2, -3]);
assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2, -3]);
assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3]);
assertEqArray(f.apply(null, ...[[undefined]]), [-1, -2, -3]);
}
checkDefault(function(a = -1, b = -2, c = -3) [a, b, c]);
checkDefault((a = -1, b = -2, c = -3) => [a, b, c]);
function checkRest(f) {
checkCommon(f);
assertEqArray(f.apply(null, ...[[]]), []);
assertEqArray(f.apply(null, ...[[1]]), [1]);
assertEqArray(f.apply(null, ...[[1, 2]]), [1, 2]);
assertEqArray(f.apply(null, ...[[1, 2, 3, 4]]), [1, 2, 3, 4]);
assertEqArray(f.apply(null, ...[[undefined]]), [undefined]);
// other iterable objects
assertEqArray(f.apply(null, ...Map([[["a", "A"], ["b", "B"]]])).map(([k, v]) => k + v), ["aA", "bB"]);
}
checkRest(function(...x) x);
checkRest((...x) => x);

View File

@ -0,0 +1,11 @@
load(libdir + "eqArrayHelper.js");
function check(f) {
assertEqArray(f.call(...[null], 1, 2, 3), [1, 2, 3]);
assertEqArray(f.call(...[null], 1, ...[2, 3], 4, ...[5, 6]), [1, 2, 3, 4, 5, 6]);
assertEqArray(f.call(...[null, 1], ...[2, 3], 4, ...[5, 6]), [1, 2, 3, 4, 5, 6]);
assertEqArray(f.call(...[null, 1, ...[2, 3], 4, ...[5, 6]]), [1, 2, 3, 4, 5, 6]);
}
check(function(...x) x);
check((...x) => x);

View File

@ -0,0 +1,16 @@
load(libdir + "asserts.js");
var offenders = [
"f(1 ... n)",
"f(...x for (x in y))",
"f(...)",
"f(...,)",
"f(... ...[])",
"f(...x,)",
"f(x, ...)",
"f(...x, x for (x in y))",
"f(x for (x in y), ...x)"
];
for (var sample of offenders) {
assertThrowsInstanceOf(function() { eval(sample); }, SyntaxError);
}

View File

@ -0,0 +1,55 @@
let makeCall = farg => Function("f", "arg", "return f(" + farg + ");");
let makeFunCall = farg => Function("f", "arg", "return f.call(null, " + farg + ");");
let makeNew = farg => Function("f", "arg", "return new f(" + farg + ").length;");
function checkLength(f, makeFn) {
assertEq(makeFn("...[1, 2, 3]")(f), 3);
assertEq(makeFn("1, ...[2], 3")(f), 3);
assertEq(makeFn("1, ...[2], ...[3]")(f), 3);
assertEq(makeFn("1, ...[2, 3]")(f), 3);
assertEq(makeFn("1, ...[], 2, 3")(f), 3);
assertEq(makeFn("...[1]")(f), 1);
assertEq(makeFn("...[1, 2]")(f), 2);
assertEq(makeFn("...[1, 2, 3, 4]")(f), 4);
assertEq(makeFn("1, ...[2, 3, 4], 5")(f), 5);
assertEq(makeFn("...[undefined]")(f), 1);
// other iterable objects
assertEq(makeFn("...arg")(f, Int32Array([1, 2, 3])), 3);
assertEq(makeFn("...arg")(f, "abc"), 3);
assertEq(makeFn("...arg")(f, [1, 2, 3].iterator()), 3);
assertEq(makeFn("...arg")(f, Set([1, 2, 3])), 3);
assertEq(makeFn("...arg")(f, Map([["a", "A"], ["b", "B"], ["c", "C"]])), 3);
let itr = {
iterator: function() {
return {
i: 1,
next: function() {
if (this.i < 4)
return this.i++;
else
throw StopIteration;
}
};
}
};
assertEq(makeFn("...arg")(f, itr), 3);
let gen = {
iterator: function() {
for (let i = 1; i < 4; i ++)
yield i;
}
};
assertEq(makeFn("...arg")(f, gen), 3);
}
checkLength(function(x) arguments.length, makeCall);
checkLength(function(x) arguments.length, makeFunCall);
checkLength((x) => arguments.length, makeCall);
checkLength((x) => arguments.length, makeFunCall);
function lengthClass(x) {
this.length = arguments.length;
}
checkLength(lengthClass, makeNew);

View File

@ -0,0 +1,23 @@
let a = [];
a.length = getMaxArgs() + 1;
let f = function() {
};
try {
f(...a);
} catch (e) {
assertEq(e.message, "too many function arguments");
}
try {
new f(...a);
} catch (e) {
assertEq(e.message, "too many constructor arguments");
}
try {
eval(...a);
} catch (e) {
assertEq(e.message, "too many function arguments");
}

View File

@ -0,0 +1,9 @@
load(libdir + "eqArrayHelper.js");
function g(a, b, c) {
this.value = [a, b, c];
assertEq(Object.getPrototypeOf(this), g.prototype);
assertEq(arguments.callee, g);
}
assertEqArray(new g(...[1, 2, 3]).value, [1, 2, 3]);

View File

@ -0,0 +1,16 @@
load(libdir + "asserts.js");
assertThrowsInstanceOf(() => Math.sin(...true), TypeError);
assertThrowsInstanceOf(() => Math.sin(...false), TypeError);
assertThrowsInstanceOf(() => Math.sin(...new Date()), TypeError);
assertThrowsInstanceOf(() => Math.sin(...Function("")), TypeError);
assertThrowsInstanceOf(() => Math.sin(...function () {}), TypeError);
assertThrowsInstanceOf(() => Math.sin(...(x => x)), TypeError);
assertThrowsInstanceOf(() => Math.sin(...1), TypeError);
assertThrowsInstanceOf(() => Math.sin(...{}), TypeError);
assertThrowsInstanceOf(() => Math.sin(...{ iterator: 10 }), TypeError);
assertThrowsInstanceOf(() => Math.sin(...{ iterator: function() undefined }), TypeError);
assertThrowsInstanceOf(() => Math.sin(...{ iterator: function() this }), TypeError);
assertThrowsValue(() => Math.sin(...{ iterator: function() this, next: function() { throw 10; } }), 10);
assertThrowsInstanceOf(() => Math.sin(.../a/), TypeError);
assertThrowsInstanceOf(() => Math.sin(...new Error()), TypeError);

View File

@ -0,0 +1,18 @@
let a = [];
a.length = 30;
function check(f) {
try {
f();
} catch (e) {
assertEq(e.message, "too much recursion");
}
}
let f = function() f(...a) + 1;
let g = () => g(...a) + 1;
let h = function() new h(...a) + 1;
check(f);
check(g);
check(h);

View File

@ -0,0 +1,26 @@
load(libdir + "asserts.js");
function g() {
}
let a = {
g: function() {
}
};
function check(expr) {
assertThrowsInstanceOf(Function(expr), ReferenceError);
}
check("g(...[]) = 1");
check("a.g(...[]) = 1");
check("eval(...['1']) = 1");
check("[g(...[])] = 1");
check("[a.g(...[])] = 1");
check("[eval(...['1'])] = 1");
check("({y: g(...[])}) = 1");
check("({y: a.g(...[])}) = 1");
check("({y: eval(...['1'])}) = 1");
check("g(...[]) ++");
check("a.g(...[]) ++");
check("eval(...['1']) ++");

View File

@ -0,0 +1,105 @@
"use strict";
let global = this;
let p = {};
let q = {};
let g1 = function() {
assertEq(this, undefined);
};
g1(...[]);
let g2 = x => {
assertEq(this, global);
};
g2(...[]);
let g3 = function() {
assertEq(this, p);
};
g3.apply(p, ...[]);
g3.call(p, ...[]);
g2.apply(p, ...[]);
g2.call(p, ...[]);
let o = {
f1: function() {
assertEq(this, o);
let g1 = function() {
assertEq(this, undefined);
};
g1(...[]);
let g2 = x => {
assertEq(this, o);
};
g2(...[]);
let g3 = function() {
assertEq(this, q);
};
g3.apply(q, ...[]);
g3.call(q, ...[]);
let g4 = x => {
assertEq(this, o);
};
g4.apply(q, ...[]);
g4.call(q, ...[]);
},
f2: x => {
assertEq(this, global);
let g1 = function() {
assertEq(this, undefined);
};
g1(...[]);
let g2 = x => {
assertEq(this, global);
};
g2(...[]);
let g3 = function() {
assertEq(this, q);
};
g3.apply(q, ...[]);
g3.call(q, ...[]);
let g4 = x => {
assertEq(this, global);
};
g4.apply(q, ...[]);
g4.call(q, ...[]);
},
f3: function() {
assertEq(this, p);
let g1 = function() {
assertEq(this, undefined);
};
g1(...[]);
let g2 = x => {
assertEq(this, p);
};
g2(...[]);
let g3 = function() {
assertEq(this, q);
};
g3.apply(q, ...[]);
g3.call(q, ...[]);
let g4 = x => {
assertEq(this, p);
};
g4.apply(q, ...[]);
g4.call(q, ...[]);
}
};
o.f1(...[]);
o.f2(...[]);
o.f3.apply(p, ...[]);
o.f2.apply(p, ...[]);

View File

@ -0,0 +1,123 @@
let global = this;
let p = {};
let q = {};
let g1 = function() {
assertEq(this, global);
assertEq(arguments.callee, g1);
};
g1(...[]);
let g2 = x => {
assertEq(this, global);
// arguments.callee is unbound function object, and following assertion fails.
// see Bug 889158
//assertEq(arguments.callee, g2);
};
g2(...[]);
let g3 = function() {
assertEq(this, p);
assertEq(arguments.callee, g3);
};
g3.apply(p, ...[]);
g3.call(p, ...[]);
g2.apply(p, ...[]);
g2.call(p, ...[]);
let o = {
f1: function() {
assertEq(this, o);
assertEq(arguments.callee, o.f1);
let g1 = function() {
assertEq(this, global);
assertEq(arguments.callee, g1);
};
g1(...[]);
let g2 = x => {
assertEq(this, o);
//assertEq(arguments.callee, g2);
};
g2(...[]);
let g3 = function() {
assertEq(this, q);
assertEq(arguments.callee, g3);
};
g3.apply(q, ...[]);
g3.call(q, ...[]);
let g4 = x => {
assertEq(this, o);
//assertEq(arguments.callee, g4);
};
g4.apply(q, ...[]);
g4.call(q, ...[]);
},
f2: x => {
assertEq(this, global);
//assertEq(arguments.callee, o.f2);
let g1 = function() {
assertEq(this, global);
assertEq(arguments.callee, g1);
};
g1(...[]);
let g2 = x => {
assertEq(this, global);
//assertEq(arguments.callee, g2);
};
g2(...[]);
let g3 = function() {
assertEq(this, q);
assertEq(arguments.callee, g3);
};
g3.apply(q, ...[]);
g3.call(q, ...[]);
let g4 = x => {
assertEq(this, global);
//assertEq(arguments.callee, g4);
};
g4.apply(q, ...[]);
g4.call(q, ...[]);
},
f3: function() {
assertEq(this, p);
assertEq(arguments.callee, o.f3);
let g1 = function() {
assertEq(this, global);
assertEq(arguments.callee, g1);
};
g1(...[]);
let g2 = x => {
assertEq(this, p);
//assertEq(arguments.callee, g2);
};
g2(...[]);
let g3 = function() {
assertEq(this, q);
assertEq(arguments.callee, g3);
};
g3.apply(q, ...[]);
g3.call(q, ...[]);
let g4 = x => {
assertEq(this, p);
//assertEq(arguments.callee, g4);
};
g4.apply(q, ...[]);
g4.call(q, ...[]);
}
};
o.f1(...[]);
o.f2(...[]);
o.f3.apply(p, ...[]);
o.f2.apply(p, ...[]);

View File

@ -0,0 +1,115 @@
load(libdir + "asserts.js");
load(libdir + "eqArrayHelper.js");
let makeCall = farg => Function("f", "arg", "return f(" + farg + ");");
let makeFunCall = farg => Function("f", "arg", "return f.call(null, " + farg + ");");
let makeNew = farg => Function("f", "arg", "return new f(" + farg + ").value;");
function checkCommon(f, makeFn) {
assertEqArray(makeFn("...[1, 2, 3]")(f), [1, 2, 3]);
assertEqArray(makeFn("1, ...[2], 3")(f), [1, 2, 3]);
assertEqArray(makeFn("1, ...[2], ...[3]")(f), [1, 2, 3]);
assertEqArray(makeFn("1, ...[2, 3]")(f), [1, 2, 3]);
assertEqArray(makeFn("1, ...[], 2, 3")(f), [1, 2, 3]);
// other iterable objects
assertEqArray(makeFn("...arg")(f, Int32Array([1, 2, 3])), [1, 2, 3]);
assertEqArray(makeFn("...arg")(f, "abc"), ["a", "b", "c"]);
assertEqArray(makeFn("...arg")(f, [1, 2, 3].iterator()), [1, 2, 3]);
assertEqArray(makeFn("...arg")(f, Set([1, 2, 3])), [1, 2, 3]);
assertEqArray(makeFn("...arg")(f, Map([["a", "A"], ["b", "B"], ["c", "C"]])).map(([k, v]) => k + v), ["aA", "bB", "cC"]);
let itr = {
iterator: function() {
return {
i: 1,
next: function() {
if (this.i < 4)
return this.i++;
else
throw StopIteration;
}
};
}
};
assertEqArray(makeFn("...arg")(f, itr), [1, 2, 3]);
let gen = {
iterator: function() {
for (let i = 1; i < 4; i ++)
yield i;
}
};
assertEqArray(makeFn("...arg")(f, gen), [1, 2, 3]);
assertEqArray(makeFn("...arg=[1, 2, 3]")(f), [1, 2, 3]);
// According to the draft spec, null and undefined are to be treated as empty
// arrays. However, they are not iterable. If the spec is not changed to be in
// terms of iterables, these tests should be fixed.
//assertEqArray(makeFn(1, ...null, 2, 3)(f), [1, 2, 3]);
//assertEqArray(makeFn(1, ...undefined, 2, 3)(f), [1, 2, 3]);
assertThrowsInstanceOf(makeFn("1, ...null, 2, 3"), TypeError);
assertThrowsInstanceOf(makeFn("1, ...undefined, 2, 3"), TypeError);
}
function checkNormal(f, makeFn) {
checkCommon(f, makeFn);
assertEqArray(makeFn("...[]")(f), [undefined, undefined, undefined]);
assertEqArray(makeFn("...[1]")(f), [1, undefined, undefined]);
assertEqArray(makeFn("...[1, 2]")(f), [1, 2, undefined]);
assertEqArray(makeFn("...[1, 2, 3, 4]")(f), [1, 2, 3]);
assertEqArray(makeFn("...[undefined]")(f), [undefined, undefined, undefined]);
}
checkNormal(function(a, b, c) [a, b, c], makeCall);
checkNormal(function(a, b, c) [a, b, c], makeFunCall);
checkNormal((a, b, c) => [a, b, c], makeCall);
checkNormal((a, b, c) => [a, b, c], makeFunCall);
function normalClass(a, b, c) {
this.value = [a, b, c];
assertEq(Object.getPrototypeOf(this), normalClass.prototype);
}
checkNormal(normalClass, makeNew);
function checkDefault(f, makeFn) {
checkCommon(f, makeFn);
assertEqArray(makeFn("...[]")(f), [-1, -2, -3]);
assertEqArray(makeFn("...[1]")(f), [1, -2, -3]);
assertEqArray(makeFn("...[1, 2]")(f), [1, 2, -3]);
assertEqArray(makeFn("...[1, 2, 3, 4]")(f), [1, 2, 3]);
assertEqArray(makeFn("...[undefined]")(f), [-1, -2, -3]);
}
checkDefault(function(a = -1, b = -2, c = -3) [a, b, c], makeCall);
checkDefault(function(a = -1, b = -2, c = -3) [a, b, c], makeFunCall);
checkDefault((a = -1, b = -2, c = -3) => [a, b, c], makeCall);
checkDefault((a = -1, b = -2, c = -3) => [a, b, c], makeFunCall);
function defaultClass(a = -1, b = -2, c = -3) {
this.value = [a, b, c];
assertEq(Object.getPrototypeOf(this), defaultClass.prototype);
}
checkDefault(defaultClass, makeNew);
function checkRest(f, makeFn) {
checkCommon(f, makeFn);
assertEqArray(makeFn("...[]")(f), []);
assertEqArray(makeFn("1, ...[2, 3, 4], 5")(f), [1, 2, 3, 4, 5]);
assertEqArray(makeFn("1, ...[], 2")(f), [1, 2]);
assertEqArray(makeFn("1, ...[2, 3], 4, ...[5, 6]")(f), [1, 2, 3, 4, 5, 6]);
assertEqArray(makeFn("...[undefined]")(f), [undefined]);
}
checkRest(function(...x) x, makeCall);
checkRest(function(...x) x, makeFunCall);
checkRest((...x) => x, makeCall);
checkRest((...x) => x, makeFunCall);
function restClass(...x) {
this.value = x;
assertEq(Object.getPrototypeOf(this), restClass.prototype);
}
checkRest(restClass, makeNew);

View File

@ -412,3 +412,5 @@ MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_FIELD, 358, 1, JSEXN_ERR, "field {0} is
MSG_DEF(JSMSG_GENERATOR_FINISHED, 359, 0, JSEXN_TYPEERR, "generator has already finished")
MSG_DEF(JSMSG_TYPEDOBJECT_TOO_BIG, 360, 0, JSEXN_ERR, "Type is too large to allocate")
MSG_DEF(JSMSG_TYPEDOBJECT_NOT_TYPE_OBJECT, 361, 0, JSEXN_ERR, "Expected a type object")
MSG_DEF(JSMSG_TOO_MANY_CON_SPREADARGS, 362, 0, JSEXN_RANGEERR, "too many constructor arguments")
MSG_DEF(JSMSG_TOO_MANY_FUN_SPREADARGS, 363, 0, JSEXN_RANGEERR, "too many function arguments")

View File

@ -265,6 +265,7 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx)
break;
case JSOP_EVAL:
case JSOP_SPREADEVAL:
canTrackVars = false;
isIonInlineable = false;
break;

View File

@ -1450,9 +1450,12 @@ types::UseNewType(JSContext *cx, JSScript *script, jsbytecode *pc)
* Sub2 lets us continue to distinguish the two subclasses and any extra
* properties added to those prototype objects.
*/
if (JSOp(*pc) != JSOP_NEW)
if (JSOp(*pc) == JSOP_NEW)
pc += JSOP_NEW_LENGTH;
else if (JSOp(*pc) == JSOP_SPREADNEW)
pc += JSOP_SPREADNEW_LENGTH;
else
return false;
pc += JSOP_NEW_LENGTH;
if (JSOp(*pc) == JSOP_SETPROP) {
jsid id = GetAtomId(cx, script, pc, 0);
if (id == id_prototype(cx))

View File

@ -1308,6 +1308,9 @@ ExpressionDecompiler::decompilePC(jsbytecode *pc)
case JSOP_FUNCALL:
return decompilePC(pcstack[-int32_t(GET_ARGC(pc) + 2)]) &&
write("(...)");
case JSOP_SPREADCALL:
return decompilePC(pcstack[-int32_t(3)]) &&
write("(...)");
case JSOP_NEWARRAY:
return write("[]");
case JSOP_REGEXP:
@ -2009,11 +2012,13 @@ js::CallResultEscapes(jsbytecode *pc)
* - call / not / ifeq
*/
if (*pc != JSOP_CALL)
if (*pc == JSOP_CALL)
pc += JSOP_CALL_LENGTH;
else if (*pc == JSOP_SPREADCALL)
pc += JSOP_SPREADCALL_LENGTH;
else
return true;
pc += JSOP_CALL_LENGTH;
if (*pc == JSOP_POP)
return false;

View File

@ -90,9 +90,13 @@ OPDEF(JSOP_DELELEM, 38, "delelem", NULL, 1, 2, 1, JOF_BYTE |JOF_E
OPDEF(JSOP_TYPEOF, 39, js_typeof_str,NULL, 1, 1, 1, JOF_BYTE|JOF_DETECTING)
OPDEF(JSOP_VOID, 40, js_void_str, NULL, 1, 1, 1, JOF_BYTE)
OPDEF(JSOP_UNUSED41, 41, "unused41", NULL, 1, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED42, 42, "unused42", NULL, 1, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED43, 43, "unused43", NULL, 1, 0, 0, JOF_BYTE)
/* spreadcall variant of JSOP_CALL */
OPDEF(JSOP_SPREADCALL,41, "spreadcall", NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
/* spreadcall variant of JSOP_NEW */
OPDEF(JSOP_SPREADNEW, 42, "spreadnew", NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
/* spreadcall variant of JSOP_EVAL */
OPDEF(JSOP_SPREADEVAL,43, "spreadeval", NULL, 1, 3, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
OPDEF(JSOP_UNUSED44, 44, "unused44", NULL, 1, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED45, 45, "unused45", NULL, 1, 0, 0, JOF_BYTE)
OPDEF(JSOP_UNUSED46, 46, "unused46", NULL, 1, 0, 0, JOF_BYTE)

View File

@ -2162,10 +2162,12 @@ js::CurrentScriptFileLineOrigin(JSContext *cx, const char **file, unsigned *line
JSScript *script = NULL;
jsbytecode *pc = NULL;
types::TypeScript::GetPcScript(cx, &script, &pc);
JS_ASSERT(JSOp(*pc) == JSOP_EVAL);
JS_ASSERT(*(pc + JSOP_EVAL_LENGTH) == JSOP_LINENO);
JS_ASSERT(JSOp(*pc) == JSOP_EVAL || JSOp(*pc) == JSOP_SPREADEVAL);
JS_ASSERT(*(pc + (JSOp(*pc) == JSOP_EVAL ? JSOP_EVAL_LENGTH
: JSOP_SPREADEVAL_LENGTH)) == JSOP_LINENO);
*file = script->filename();
*linenop = GET_UINT16(pc + JSOP_EVAL_LENGTH);
*linenop = GET_UINT16(pc + (JSOp(*pc) == JSOP_EVAL ? JSOP_EVAL_LENGTH
: JSOP_SPREADEVAL_LENGTH));
*origin = script->originPrincipals();
return;
}

View File

@ -1471,8 +1471,8 @@ PCToLineNumber(unsigned startLine, jssrcnote *notes, jsbytecode *code, jsbytecod
* executing on cx. If there is no current script executing on cx (e.g., a
* native called directly through JSAPI (e.g., by setTimeout)), NULL and 0 are
* returned as the file and line. Additionally, this function avoids the full
* linear scan to compute line number when the caller guarnatees that the
* script compilation occurs at a JSOP_EVAL.
* linear scan to compute line number when the caller guarantees that the
* script compilation occurs at a JSOP_EVAL/JSOP_SPREADEVAL.
*/
enum LineOption {

View File

@ -2405,6 +2405,61 @@ BEGIN_CASE(JSOP_EVAL)
}
END_CASE(JSOP_EVAL)
BEGIN_CASE(JSOP_SPREADNEW)
BEGIN_CASE(JSOP_SPREADCALL)
if (regs.fp()->hasPushedSPSFrame())
cx->runtime()->spsProfiler.updatePC(script, regs.pc);
/* FALL THROUGH */
BEGIN_CASE(JSOP_SPREADEVAL)
{
JS_ASSERT(regs.stackDepth() >= 3);
RootedObject &aobj = rootObject0;
aobj = &regs.sp[-1].toObject();
uint32_t length = aobj->as<ArrayObject>().length();
if (length > ARGS_LENGTH_MAX) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
op == JSOP_SPREADNEW ? JSMSG_TOO_MANY_CON_SPREADARGS
: JSMSG_TOO_MANY_FUN_SPREADARGS);
goto error;
}
InvokeArgs args(cx);
if (!args.init(length))
return false;
args.setCallee(regs.sp[-3]);
args.setThis(regs.sp[-2]);
if (!GetElements(cx, aobj, length, args.array()))
goto error;
if (op == JSOP_SPREADNEW) {
if (!InvokeConstructor(cx, args))
goto error;
} else if (op == JSOP_SPREADCALL) {
if (!Invoke(cx, args))
goto error;
} else {
JS_ASSERT(op == JSOP_SPREADEVAL);
if (IsBuiltinEvalForScope(regs.fp()->scopeChain(), args.calleev())) {
if (!DirectEval(cx, args))
goto error;
} else {
if (!Invoke(cx, args))
goto error;
}
}
regs.sp -= 2;
regs.sp[-1] = args.rval();
TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
}
END_CASE(JSOP_SPREADCALL)
BEGIN_CASE(JSOP_FUNAPPLY)
{
CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);

View File

@ -22,7 +22,7 @@ namespace js {
* and saved versions. If deserialization fails, the data should be
* invalidated if possible.
*/
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 151);
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 152);
class XDRBuffer {
public: