Bug 574130: JavaScript spread array initializers, r=jorendorff.
authorBenjamin Peterson <benjamin@python.org>
Wed, 06 Jun 2012 21:53:07 -0500
changeset 98744 34476c720f8fa8624cf2100d1e6fa5aae16d301e
parent 98743 ce0c716baefdd91fa93d4c3b5b36b2d9f81f0440
child 98745 21ff29da4c41c86bc573d31a983557688c6d13f3
push id1729
push userlsblakk@mozilla.com
push dateMon, 16 Jul 2012 20:02:43 +0000
treeherdermozilla-aurora@f4e75e148951 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs574130
milestone16.0a1
Bug 574130: JavaScript spread array initializers, r=jorendorff.
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/jit-test/tests/basic/spread-array-decompile.js
js/src/jit-test/tests/basic/spread-array-evaluation-order.js
js/src/jit-test/tests/basic/spread-array-invalid-syntax.js
js/src/jit-test/tests/basic/spread-array-wrap.js
js/src/jit-test/tests/basic/spread-array.js
js/src/js.msg
js/src/jsanalyze.cpp
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsiter.cpp
js/src/jsopcode.cpp
js/src/jsopcode.tbl
--- 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, &regs.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)