Bug 574130: JavaScript spread array initializers, r=jorendorff.
authorBenjamin Peterson <benjamin@python.org>
Wed, 06 Jun 2012 21:53:07 -0500
changeset 96029 34476c720f8fa8624cf2100d1e6fa5aae16d301e
parent 96028 ce0c716baefdd91fa93d4c3b5b36b2d9f81f0440
child 96030 21ff29da4c41c86bc573d31a983557688c6d13f3
push id22869
push user[email protected]
push dateThu, 07 Jun 2012 09:35:19 +0000
treeherdermozilla-central@3933384d8315 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs574130
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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)