--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3718,16 +3718,18 @@ ParseNode::getConstantValue(JSContext *c
vp->setBoolean(true);
return true;
case PNK_FALSE:
vp->setBoolean(false);
return true;
case PNK_NULL:
vp->setNull();
return true;
+ case PNK_SPREAD:
+ return false;
case PNK_RB: {
JS_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST));
RootedObject obj(cx, NewDenseAllocatedArray(cx, pn_count));
if (!obj)
return false;
unsigned idx = 0;
@@ -5801,39 +5803,61 @@ EmitArray(JSContext *cx, BytecodeEmitter
/* Emit the usual op needed for decompilation. */
return Emit1(cx, bce, JSOP_ENDINIT) >= 0;
}
#endif /* JS_HAS_GENERATORS */
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) {
+ if (elt->isKind(PNK_SPREAD))
+ nspread++;
+ }
+
ptrdiff_t off = EmitN(cx, bce, JSOP_NEWARRAY, 3);
if (off < 0)
return false;
CheckTypeSet(cx, bce, JSOP_NEWARRAY);
jsbytecode *pc = bce->code(off);
- SET_UINT24(pc, pn->pn_count);
+
+ // For arrays with spread, this is a very pessimistic allocation, the
+ // minimum possible final size.
+ SET_UINT24(pc, pn->pn_count - nspread);
ParseNode *pn2 = pn->pn_head;
jsatomid atomIndex;
+ if (nspread && !EmitNumberOp(cx, 0, bce))
+ return false;
for (atomIndex = 0; pn2; atomIndex++, pn2 = pn2->pn_next) {
- if (!EmitNumberOp(cx, atomIndex, bce))
+ if (!nspread && !EmitNumberOp(cx, atomIndex, bce))
return false;
if (pn2->isKind(PNK_COMMA) && pn2->isArity(PN_NULLARY)) {
if (Emit1(cx, bce, JSOP_HOLE) < 0)
return false;
} else {
- if (!EmitTree(cx, bce, pn2))
+ ParseNode *expr = pn2->isKind(PNK_SPREAD) ? pn2->pn_kid : pn2;
+ if (!EmitTree(cx, bce, expr))
return false;
}
- if (Emit1(cx, bce, JSOP_INITELEM) < 0)
- return false;
+ if (pn2->isKind(PNK_SPREAD)) {
+ if (Emit1(cx, bce, JSOP_SPREAD) < 0)
+ return false;
+ } else if (Emit1(cx, bce, nspread ? JSOP_INITELEM_INC : JSOP_INITELEM) < 0) {
+ return false;
+ }
}
JS_ASSERT(atomIndex == pn->pn_count);
+ if (nspread) {
+ if (NewSrcNote(cx, bce, SRC_CONTINUE) < 0)
+ return false;
+ if (Emit1(cx, bce, JSOP_POP) < 0)
+ return false;
+ }
if (pn->pn_xflags & PNX_ENDCOMMA) {
/* Emit a source note so we know to decompile an extra comma. */
if (NewSrcNote(cx, bce, SRC_CONTINUE) < 0)
return false;
}
/* Emit an op to finish the array and aid in decompilation. */
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -159,16 +159,17 @@ enum ParseNodeKind {
PNK_ARRAYPUSH,
PNK_LEXICALSCOPE,
PNK_LET,
PNK_SEQ,
PNK_FORIN,
PNK_FORHEAD,
PNK_ARGSBODY,
PNK_UPVARS,
+ PNK_SPREAD,
/*
* The following parse node kinds occupy contiguous ranges to enable easy
* range-testing.
*/
/* Equality operators. */
PNK_STRICTEQ,
@@ -235,16 +236,17 @@ enum ParseNodeKind {
* PNK_STATEMENTLIST node for function body
* statements as final element
* pn_count: 1 + number of formal parameters
* PNK_UPVARS nameset pn_names: lexical dependencies (js::Definitions)
* defined in enclosing scopes, or ultimately not
* defined (free variables, either global property
* references or reference errors).
* pn_tree: PNK_ARGSBODY or PNK_STATEMENTLIST node
+ * PNK_SPREAD unary pn_kid: expression being spread
*
* <Statements>
* PNK_STATEMENTLIST list pn_head: list of pn_count statements
* PNK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null.
* In body of a comprehension or desugared generator
* expression, pn_kid2 is PNK_YIELD, PNK_ARRAYPUSH,
* or (if the push was optimized away) empty
* PNK_STATEMENTLIST.
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -6651,16 +6651,17 @@ Parser::primaryExpr(TokenKind tt, bool a
pn->makeEmpty();
#if JS_HAS_GENERATORS
pn->pn_blockid = tc->sc->blockidGen;
#endif
matched = tokenStream.matchToken(TOK_RB, TSF_OPERAND);
if (!matched) {
+ bool spread = false;
for (index = 0; ; index++) {
if (index == StackSpace::ARGS_LENGTH_MAX) {
reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ARRAY_INIT_TOO_BIG);
return NULL;
}
tt = tokenStream.peekToken(TSF_OPERAND);
if (tt == TOK_RB) {
@@ -6669,22 +6670,34 @@ Parser::primaryExpr(TokenKind tt, bool a
}
if (tt == TOK_COMMA) {
/* So CURRENT_TOKEN gets TOK_COMMA and not TOK_LB. */
tokenStream.matchToken(TOK_COMMA);
pn2 = NullaryNode::create(PNK_COMMA, this);
pn->pn_xflags |= PNX_HOLEY | PNX_NONCONST;
} else {
+ ParseNode *spreadNode = NULL;
+ if (tt == TOK_TRIPLEDOT) {
+ spread = true;
+ spreadNode = UnaryNode::create(PNK_SPREAD, this);
+ if (!spreadNode)
+ return NULL;
+ tokenStream.getToken();
+ }
pn2 = assignExpr();
if (pn2) {
if (foldConstants && !FoldConstants(context, pn2, this))
return NULL;
- if (!pn2->isConstant())
+ if (!pn2->isConstant() || spreadNode)
pn->pn_xflags |= PNX_NONCONST;
+ if (spreadNode) {
+ spreadNode->pn_kid = pn2;
+ pn2 = spreadNode;
+ }
}
}
if (!pn2)
return NULL;
pn->append(pn2);
if (tt != TOK_COMMA) {
/* If we didn't already match TOK_COMMA in above case. */
@@ -6727,20 +6740,20 @@ Parser::primaryExpr(TokenKind tt, bool a
*
* Each var declaration in a let-block binds a name in <o> at
* compile time, and allocates a slot on the operand stack at
* runtime via JSOP_ENTERBLOCK. A block-local var is accessed by
* the JSOP_GETLOCAL and JSOP_SETLOCAL ops. These ops have an
* immediate operand, the local slot's stack index from fp->spbase.
*
* The array comprehension iteration step, array.push(i * j) in
- * the example above, is done by <i * j>; JSOP_ARRAYCOMP <array>,
+ * the example above, is done by <i * j>; JSOP_ARRAYPUSH <array>,
* where <array> is the index of array's stack slot.
*/
- if (index == 0 && pn->pn_count != 0 && tokenStream.matchToken(TOK_FOR)) {
+ if (index == 0 && !spread && pn->pn_count != 0 && tokenStream.matchToken(TOK_FOR)) {
ParseNode *pnexp, *pntop;
/* Relabel pn as an array comprehension node. */
pn->setKind(PNK_ARRAYCOMP);
/*
* Remove the comprehension expression from pn's linked list
* and save it via pnexp. We'll re-install it underneath the
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-array-decompile.js
@@ -0,0 +1,14 @@
+var samples = [
+ "[...a]",
+ "[...[1]]",
+ "[1, ...a, 2]",
+ "[1, ...[2, 3], 4]",
+ "[...[1], , ]",
+ "[1, , ...[2]]",
+ "[, 1, ...[2], ...[3], , 4, 5, , ]"
+];
+for (var sample of samples) {
+ var source = "function f() {\n return " + sample + ";\n}";
+ eval(source);
+ assertEq(f.toString(), source);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-array-evaluation-order.js
@@ -0,0 +1,12 @@
+load(libdir + "eqArrayHelper.js");
+
+var check = [];
+function t(token) {
+ check.push(token);
+ return token;
+}
+[3, ...[t(1)],, ...[t(2), t(3)], 34, 42, ...[t(4)]];
+assertEqArray(check, [1, 2, 3, 4]);
+
+var arr = [1, 2, 3];
+assertEqArray([...arr, arr.pop()], [1, 2, 3, 3]);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-array-invalid-syntax.js
@@ -0,0 +1,14 @@
+load(libdir + "asserts.js");
+
+var offenders = [
+ "(1 ... n)",
+ "[1 ... n]",
+ "(...x)",
+ "[...x for (x of y)]",
+ "[...]",
+ "(...)",
+ "[...,]"
+];
+for (var sample of offenders) {
+ assertThrowsInstanceOf(function () { eval(sample); }, SyntaxError);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-array-wrap.js
@@ -0,0 +1,4 @@
+load(libdir + "eqArrayHelper.js");
+
+assertEqArray([...wrap([1])], [1]);
+assertEqArray([1,, ...wrap([2, 3, 4]), 5, ...wrap([6])], [1,, 2, 3, 4, 5, 6]);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/spread-array.js
@@ -0,0 +1,15 @@
+load(libdir + "eqArrayHelper.js");
+
+assertEqArray([...[1, 2, 3]], [1, 2, 3]);
+assertEqArray([1, ...[2, 3, 4], 5], [1, 2, 3, 4, 5]);
+assertEqArray([1, ...[], 2], [1, 2]);
+assertEqArray([1, ...[2, 3], 4, ...[5, 6]], [1, 2, 3, 4, 5, 6]);
+assertEqArray([1, ...[], 2], [1, 2]);
+assertEqArray([1,, ...[2]], [1,, 2]);
+assertEqArray([1,, ...[2],, 3,, 4,], [1,, 2,, 3,, 4,]);
+
+// 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]);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -110,17 +110,17 @@ MSG_DEF(JSMSG_TRAILING_SLASH, 5
MSG_DEF(JSMSG_BAD_CLASS_RANGE, 57, 0, JSEXN_SYNTAXERR, "invalid range in character class")
MSG_DEF(JSMSG_BAD_REGEXP_FLAG, 58, 1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}")
MSG_DEF(JSMSG_NO_INPUT, 59, 5, JSEXN_SYNTAXERR, "no input for /{0}/{1}{2}{3}{4}")
MSG_DEF(JSMSG_CANT_OPEN, 60, 2, JSEXN_ERR, "can't open {0}: {1}")
MSG_DEF(JSMSG_TOO_MANY_FUN_APPLY_ARGS, 61, 0, JSEXN_RANGEERR, "arguments array passed to Function.prototype.apply is too large")
MSG_DEF(JSMSG_UNMATCHED_RIGHT_PAREN, 62, 0, JSEXN_SYNTAXERR, "unmatched ) in regular expression")
MSG_DEF(JSMSG_TOO_BIG_TO_ENCODE, 63, 0, JSEXN_INTERNALERR, "data are to big to encode")
MSG_DEF(JSMSG_ARG_INDEX_OUT_OF_RANGE, 64, 1, JSEXN_RANGEERR, "argument {0} accesses an index that is out of range")
-MSG_DEF(JSMSG_UNUSED65, 65, 0, JSEXN_NONE, "")
+MSG_DEF(JSMSG_SPREAD_TOO_LARGE, 65, 0, JSEXN_RANGEERR, "array too large due to spread operand(s)")
MSG_DEF(JSMSG_UNUSED66, 66, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_UNUSED67, 67, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_BAD_SCRIPT_MAGIC, 68, 0, JSEXN_INTERNALERR, "bad script XDR magic number")
MSG_DEF(JSMSG_PAREN_BEFORE_FORMAL, 69, 0, JSEXN_SYNTAXERR, "missing ( before formal parameters")
MSG_DEF(JSMSG_MISSING_FORMAL, 70, 0, JSEXN_SYNTAXERR, "missing formal parameter")
MSG_DEF(JSMSG_PAREN_AFTER_FORMAL, 71, 0, JSEXN_SYNTAXERR, "missing ) after formal parameters")
MSG_DEF(JSMSG_CURLY_BEFORE_BODY, 72, 0, JSEXN_SYNTAXERR, "missing { before function body")
MSG_DEF(JSMSG_CURLY_AFTER_BODY, 73, 0, JSEXN_SYNTAXERR, "missing } after function body")
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -1462,16 +1462,21 @@ ScriptAnalysis::analyzeSSA(JSContext *cx
case JSOP_MOREITER:
stack[stackDepth - 2].v = code->poppedValues[0];
break;
case JSOP_INITPROP:
stack[stackDepth - 1].v = code->poppedValues[1];
break;
+ case JSOP_SPREAD:
+ case JSOP_INITELEM_INC:
+ stack[stackDepth - 2].v = code->poppedValues[2];
+ break;
+
case JSOP_INITELEM:
stack[stackDepth - 1].v = code->poppedValues[2];
break;
case JSOP_DUP:
stack[stackDepth - 1].v = stack[stackDepth - 2].v = code->poppedValues[0];
break;
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3824,17 +3824,19 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
types->addType(cx, Type::UnknownType());
}
break;
}
case JSOP_ENDINIT:
break;
- case JSOP_INITELEM: {
+ case JSOP_INITELEM:
+ case JSOP_INITELEM_INC:
+ case JSOP_SPREAD: {
const SSAValue &objv = poppedValue(pc, 2);
jsbytecode *initpc = script->code + objv.pushedOffset();
TypeObject *initializer = GetInitializerType(cx, script, initpc);
if (initializer) {
pushed[0].addType(cx, Type::ObjectType(initializer));
if (!initializer->unknownProperties()) {
/*
@@ -3845,23 +3847,34 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
TypeSet *types = initializer->getProperty(cx, JSID_VOID, true);
if (!types)
return false;
if (state.hasGetSet) {
types->addType(cx, Type::UnknownType());
} else if (state.hasHole) {
if (!initializer->unknownProperties())
initializer->setFlags(cx, OBJECT_FLAG_NON_PACKED_ARRAY);
+ } else if (op == JSOP_SPREAD) {
+ // Iterator could put arbitrary things into the array.
+ types->addType(cx, Type::UnknownType());
} else {
poppedTypes(pc, 0)->addSubset(cx, types);
}
}
} else {
pushed[0].addType(cx, Type::UnknownType());
}
+ switch (op) {
+ case JSOP_SPREAD:
+ case JSOP_INITELEM_INC:
+ poppedTypes(pc, 1)->addSubset(cx, &pushed[1]);
+ break;
+ default:
+ break;
+ }
state.hasGetSet = false;
state.hasHole = false;
break;
}
case JSOP_GETTER:
case JSOP_SETTER:
state.hasGetSet = true;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -1014,16 +1014,37 @@ TypeCheckNextBytecode(JSContext *cx, JSS
#ifdef DEBUG
if (cx->typeInferenceEnabled() &&
n == GetBytecodeLength(regs.pc)) {
TypeScript::CheckBytecode(cx, script, regs.pc, regs.sp);
}
#endif
}
+class SpreadContext {
+public:
+ JSContext *cx;
+ RootedObject arr;
+ int32_t *count;
+ SpreadContext(JSContext *cx, JSObject *array, int32_t *count)
+ : cx(cx), arr(cx, array), count(count) {
+ JS_ASSERT(array->isArray());
+ }
+ SpreadContext(SpreadContext &scx)
+ : cx(cx), arr(scx.cx, scx.arr), count(scx.count) {}
+ bool operator ()(JSContext *cx, const Value &item) {
+ if (*count == INT32_MAX) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_SPREAD_TOO_LARGE);
+ return false;
+ }
+ return arr->defineElement(cx, (*count)++, item, NULL, NULL, JSPROP_ENUMERATE);
+ }
+};
+
JS_NEVER_INLINE bool
js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
{
JSAutoResolveFlags rf(cx, RESOLVE_INFER);
gc::MaybeVerifyBarriers(cx, true);
JS_ASSERT(!cx->compartment->activeAnalysis);
@@ -1433,27 +1454,25 @@ js::Interpret(JSContext *cx, StackFrame
switchMask = moreInterrupts ? -1 : 0;
switchOp = int(op);
goto do_switch;
#endif
}
/* No-ops for ease of decompilation. */
ADD_EMPTY_CASE(JSOP_NOP)
-ADD_EMPTY_CASE(JSOP_UNUSED0)
ADD_EMPTY_CASE(JSOP_UNUSED1)
ADD_EMPTY_CASE(JSOP_UNUSED2)
ADD_EMPTY_CASE(JSOP_UNUSED3)
ADD_EMPTY_CASE(JSOP_UNUSED8)
ADD_EMPTY_CASE(JSOP_UNUSED9)
ADD_EMPTY_CASE(JSOP_UNUSED10)
ADD_EMPTY_CASE(JSOP_UNUSED11)
ADD_EMPTY_CASE(JSOP_UNUSED12)
ADD_EMPTY_CASE(JSOP_UNUSED13)
-ADD_EMPTY_CASE(JSOP_UNUSED14)
ADD_EMPTY_CASE(JSOP_UNUSED15)
ADD_EMPTY_CASE(JSOP_UNUSED17)
ADD_EMPTY_CASE(JSOP_UNUSED18)
ADD_EMPTY_CASE(JSOP_UNUSED19)
ADD_EMPTY_CASE(JSOP_UNUSED20)
ADD_EMPTY_CASE(JSOP_UNUSED21)
ADD_EMPTY_CASE(JSOP_UNUSED22)
ADD_EMPTY_CASE(JSOP_UNUSED23)
@@ -3151,16 +3170,17 @@ BEGIN_CASE(JSOP_INITPROP)
JSPROP_ENUMERATE, 0, 0, 0)) {
goto error;
}
regs.sp--;
}
END_CASE(JSOP_INITPROP);
+BEGIN_CASE(JSOP_INITELEM_INC)
BEGIN_CASE(JSOP_INITELEM)
{
/* Pop the element's value into rval. */
JS_ASSERT(regs.stackDepth() >= 3);
const Value &rref = regs.sp[-1];
RootedObject &obj = rootObject0;
@@ -3185,20 +3205,43 @@ BEGIN_CASE(JSOP_INITELEM)
if (JSOp(regs.pc[JSOP_INITELEM_LENGTH]) == JSOP_ENDINIT &&
!js_SetLengthProperty(cx, obj, (uint32_t) (JSID_TO_INT(id) + 1))) {
goto error;
}
} else {
if (!obj->defineGeneric(cx, id, rref, NULL, NULL, JSPROP_ENUMERATE))
goto error;
}
- regs.sp -= 2;
+ if (op == JSOP_INITELEM_INC) {
+ JS_ASSERT(obj->isArray());
+ if (JSID_TO_INT(id) == INT32_MAX) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_SPREAD_TOO_LARGE);
+ return false;
+ }
+ regs.sp[-2].setInt32(JSID_TO_INT(id) + 1);
+ regs.sp--;
+ } else {
+ regs.sp -= 2;
+ }
}
END_CASE(JSOP_INITELEM)
+BEGIN_CASE(JSOP_SPREAD)
+{
+ int32_t count = regs.sp[-2].toInt32();
+ SpreadContext scx(cx, ®s.sp[-3].toObject(), &count);
+ const Value iterable = regs.sp[-1];
+ if (!ForOf(cx, iterable, scx))
+ goto error;
+ regs.sp[-2].setInt32(count);
+ regs.sp--;
+}
+END_CASE(JSOP_SPREAD)
+
{
BEGIN_CASE(JSOP_GOSUB)
PUSH_BOOLEAN(false);
int32_t i = (regs.pc - script->code) + JSOP_GOSUB_LENGTH;
len = GET_JUMP_OFFSET(regs.pc);
PUSH_INT32(i);
END_VARLEN_CASE
}
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -881,17 +881,17 @@ js::ValueToIterator(JSContext *cx, unsig
} else {
/*
* Enumerating over null and undefined gives an empty enumerator.
* This is contrary to ECMA-262 9.9 ToObject, invoked from step 3 of
* the first production in 12.6.4 and step 4 of the second production,
* but it's "web JS" compatible. ES5 fixed for-in to match this de-facto
* standard.
*/
- if ((flags & JSITER_ENUMERATE)) {
+ if (flags & JSITER_ENUMERATE) {
if (!js_ValueToObjectOrNull(cx, *vp, obj.address()))
return false;
/* fall through */
} else {
obj = js_ValueToNonNullObject(cx, *vp);
if (!obj)
return false;
}
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -5102,35 +5102,49 @@ Decompile(SprintStack *ss, jsbytecode *p
(sn && SN_TYPE(sn) == SRC_CONTINUE) ? ", " : "",
inArray ? ']' : '}');
break;
}
{
JSBool isFirst;
const char *maybeComma;
+ const char *maybeSpread;
case JSOP_INITELEM:
+ case JSOP_INITELEM_INC:
+ case JSOP_SPREAD:
+ JS_ASSERT(ss->top >= 3);
isFirst = IsInitializerOp(ss->opcodes[ss->top - 3]);
/* Turn off most parens. */
rval = PopStr(ss, JSOP_SETNAME, &rvalpc);
/* Turn off all parens for xval and lval, which we control. */
- xval = PopStr(ss, JSOP_NOP);
+ xval = PopStr(ss, JSOP_NOP, &xvalpc);
lval = PopStr(ss, JSOP_NOP, &lvalpc);
sn = js_GetSrcNote(jp->script, pc);
if (sn && SN_TYPE(sn) == SRC_INITPROP) {
atom = NULL;
goto do_initprop;
}
maybeComma = isFirst ? "" : ", ";
- todo = Sprint(&ss->sprinter, "%s%s", lval, maybeComma);
+ maybeSpread = op == JSOP_SPREAD ? "..." : "";
+ todo = Sprint(&ss->sprinter, "%s%s%s", lval, maybeComma, maybeSpread);
SprintOpcode(ss, rval, rvalpc, pc, todo);
+ if (op != JSOP_INITELEM && todo != -1) {
+ if (!UpdateDecompiledText(ss, pushpc, todo))
+ return NULL;
+ if (!PushOff(ss, todo, saveop, pushpc))
+ return NULL;
+ if (!PushStr(ss, "", JSOP_NOP))
+ return NULL;
+ todo = -2;
+ }
break;
case JSOP_INITPROP:
LOAD_ATOM(0);
xval = QuoteString(&ss->sprinter, atom, jschar(IsIdentifier(atom) ? 0 : '\''));
if (!xval)
return NULL;
isFirst = IsInitializerOp(ss->opcodes[ss->top - 2]);
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -194,17 +194,17 @@ OPDEF(JSOP_FUNAPPLY, 79, "funapply",
OPDEF(JSOP_OBJECT, 80, "object", NULL, 5, 0, 1, 19, JOF_OBJECT)
/* Pop value and discard it. */
OPDEF(JSOP_POP, 81, "pop", NULL, 1, 1, 0, 2, JOF_BYTE)
/* Call a function as a constructor; operand is argc. */
OPDEF(JSOP_NEW, 82, js_new_str, NULL, 3, -1, 1, 17, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
-OPDEF(JSOP_UNUSED0, 83, "unused0", NULL, 1, 0, 0, 0, JOF_BYTE)
+OPDEF(JSOP_SPREAD, 83, "spread", NULL, 1, 3, 2, 3, JOF_BYTE|JOF_ELEM|JOF_SET)
/* Fast get/set ops for function arguments and local variables. */
OPDEF(JSOP_GETARG, 84, "getarg", NULL, 3, 0, 1, 19, JOF_QARG |JOF_NAME)
OPDEF(JSOP_SETARG, 85, "setarg", NULL, 3, 1, 1, 3, JOF_QARG |JOF_NAME|JOF_SET)
OPDEF(JSOP_GETLOCAL, 86,"getlocal", NULL, 3, 0, 1, 19, JOF_LOCAL|JOF_NAME)
OPDEF(JSOP_SETLOCAL, 87,"setlocal", NULL, 3, 1, 1, 3, JOF_LOCAL|JOF_NAME|JOF_SET|JOF_DETECTING)
/* Push unsigned 16-bit int constant. */
@@ -219,17 +219,17 @@ OPDEF(JSOP_UINT16, 88, "uint16",
* NEWINIT has an extra byte so it can be exchanged with NEWOBJECT during emit.
*/
OPDEF(JSOP_NEWINIT, 89, "newinit", NULL, 5, 0, 1, 19, JOF_UINT8|JOF_TYPESET)
OPDEF(JSOP_NEWARRAY, 90, "newarray", NULL, 4, 0, 1, 19, JOF_UINT24|JOF_TYPESET)
OPDEF(JSOP_NEWOBJECT, 91, "newobject", NULL, 5, 0, 1, 19, JOF_OBJECT|JOF_TYPESET)
OPDEF(JSOP_ENDINIT, 92, "endinit", NULL, 1, 0, 0, 19, JOF_BYTE)
OPDEF(JSOP_INITPROP, 93, "initprop", NULL, 5, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING)
OPDEF(JSOP_INITELEM, 94, "initelem", NULL, 1, 3, 1, 3, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING)
-OPDEF(JSOP_UNUSED14, 95, "unused14", NULL, 1, 0, 0, 0, JOF_BYTE)
+OPDEF(JSOP_INITELEM_INC,95, "initelem_inc", NULL, 1, 3, 2, 3, JOF_BYTE|JOF_ELEM|JOF_SET)
OPDEF(JSOP_UNUSED15, 96, "unused15", NULL, 1, 0, 0, 0, JOF_BYTE)
/* Fast inc/dec ops for args and locals. */
OPDEF(JSOP_INCARG, 97, "incarg", NULL, 3, 0, 1, 15, JOF_QARG |JOF_NAME|JOF_INC|JOF_TMPSLOT3)
OPDEF(JSOP_DECARG, 98, "decarg", NULL, 3, 0, 1, 15, JOF_QARG |JOF_NAME|JOF_DEC|JOF_TMPSLOT3)
OPDEF(JSOP_ARGINC, 99, "arginc", NULL, 3, 0, 1, 15, JOF_QARG |JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3)
OPDEF(JSOP_ARGDEC, 100, "argdec", NULL, 3, 0, 1, 15, JOF_QARG |JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3)