Bug 574132 - Implement rest parameters for JavaScript. r=jorendorff.
authorBenjamin Peterson <benjamin@python.org>
Wed, 23 May 2012 10:31:35 -0500
changeset 94693 dd094709d5b9
parent 94692 5aa6fad6aa88
child 94694 7ae630f43357
push id9752
push userjorendorff@mozilla.com
push date2012-05-23 15:33 +0000
treeherdermozilla-inbound@dd094709d5b9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs574132
milestone15.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 574132 - Implement rest parameters for JavaScript. r=jorendorff.
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
js/src/jit-test/tests/arguments/rest-arguments-as-parameters.js
js/src/jit-test/tests/arguments/rest-basic.js
js/src/jit-test/tests/arguments/rest-debugger.js
js/src/jit-test/tests/arguments/rest-decompile.js
js/src/jit-test/tests/arguments/rest-disallow-arguments-strict.js
js/src/jit-test/tests/arguments/rest-disallow-arguments.js
js/src/jit-test/tests/arguments/rest-in-Function.js
js/src/jit-test/tests/arguments/rest-invalid-syntax.js
js/src/jit-test/tests/arguments/rest-nested-arguments.js
js/src/jit-test/tests/arguments/rest-nested.js
js/src/jit-test/tests/arguments/rest-underflow.js
js/src/js.msg
js/src/jsapi.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/vm/ArgumentsObject.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -79,17 +79,17 @@ frontend::CompileScript(JSContext *cx, J
         const char* filename;
         unsigned lineno;
 
       public:
         ProbesManager(const char *f, unsigned l) : filename(f), lineno(l) {
             Probes::compileScriptBegin(filename, lineno);
         }
         ~ProbesManager() { Probes::compileScriptEnd(filename, lineno); }
-    }; 
+    };
     ProbesManager probesManager(filename, lineno);
 
     /*
      * The scripted callerFrame can only be given for compile-and-go scripts
      * and non-zero static level requires callerFrame.
      */
     JS_ASSERT_IF(callerFrame, compileAndGo);
     JS_ASSERT_IF(staticLevel != 0, callerFrame);
@@ -226,16 +226,19 @@ frontend::CompileScript(JSContext *cx, J
      * https://bugzilla.mozilla.org/show_bug.cgi?id=336551
      */
     if (pn && onlyXML && !callerFrame) {
         parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_XML_WHOLE_PROGRAM);
         return NULL;
     }
 #endif
 
+    if (!parser.checkForArgumentsAndRest())
+        return NULL;
+
     /*
      * Nowadays the threaded interpreter needs a stop instruction, so we
      * do have to emit that here.
      */
     if (Emit1(cx, &bce, JSOP_STOP) < 0)
         return NULL;
 
     JS_ASSERT(bce.version() == version);
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -5885,16 +5885,29 @@ frontend::EmitTree(JSContext *cx, Byteco
             if (!pn2->isDefn())
                 continue;
             if (!BindNameToSlot(cx, bce, pn2))
                 return JS_FALSE;
             if (JOF_OPTYPE(pn2->getOp()) == JOF_QARG && bce->shouldNoteClosedName(pn2)) {
                 if (!bce->noteClosedArg(pn2))
                     return JS_FALSE;
             }
+            if (pn2->pn_next == pnlast && bce->sc->fun()->hasRest()) {
+                /* Fill rest parameter. */
+                JS_ASSERT(!bce->sc->funArgumentsHasLocalBinding());
+                bce->switchToProlog();
+                if (Emit1(cx, bce, JSOP_REST) < 0)
+                    return false;
+                CheckTypeSet(cx, bce, JSOP_REST);
+                if (!EmitVarOp(cx, pn2, JSOP_SETARG, bce))
+                    return false;
+                if (Emit1(cx, bce, JSOP_POP) < 0)
+                    return false;
+                bce->switchToMain();
+            }
         }
         ok = EmitTree(cx, bce, pnlast);
         break;
       }
 
       case PNK_UPVARS:
         JS_ASSERT(pn->pn_names->count() != 0);
         ok = EmitTree(cx, bce, pn->pn_tree);
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -664,32 +664,46 @@ Parser::functionBody(FunctionBodyType ty
             dn->pn_dflags &= ~PND_PLACEHOLDER;
 
             /* NB: this leaves r invalid so we must break immediately. */
             tc->lexdeps->remove(arguments);
             break;
         }
     }
 
-    /*
-     * Even if 'arguments' isn't explicitly mentioned, dynamic name lookup
-     * forces an 'arguments' binding.
-     */
-    if (tc->sc->bindingsAccessedDynamically() && !tc->sc->bindings.hasBinding(context, arguments)) {
+    bool hasRest = tc->sc->fun()->hasRest();
+    BindingKind bindKind = tc->sc->bindings.lookup(context, arguments, NULL);
+    switch (bindKind) {
+      case NONE:
+        /* Functions with rest parameters are free from arguments. */
+        if (hasRest)
+            break;
+
+        /*
+         * Even if 'arguments' isn't explicitly mentioned, dynamic name lookup
+         * forces an 'arguments' binding.
+         */
+        if (!tc->sc->bindingsAccessedDynamically())
+            break;
         if (!tc->sc->bindings.addVariable(context, arguments))
             return NULL;
-    }
-
-    /*
-     * Now that all possible 'arguments' bindings have been added, note whether
-     * 'arguments' has a local binding and whether it unconditionally needs an
-     * arguments object.
-     */
-    BindingKind bindKind = tc->sc->bindings.lookup(context, arguments, NULL);
-    if (bindKind == VARIABLE || bindKind == CONSTANT) {
+
+        /* 'arguments' is now bound, so fall through. */
+      case VARIABLE:
+      case CONSTANT:        
+        if (hasRest) {
+            reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ARGUMENTS_AND_REST);
+            return NULL;
+        }
+
+        /*
+         * Now that all possible 'arguments' bindings have been added, note whether
+         * 'arguments' has a local binding and whether it unconditionally needs an
+         * arguments object.
+         */
         tc->sc->setFunArgumentsHasLocalBinding();
 
         /* Dynamic scope access destroys all hope of optimization. */
         if (tc->sc->bindingsAccessedDynamically())
             tc->sc->setFunDefinitelyNeedsArgsObj();
 
         /*
          * Check whether any parameters have been assigned within this
@@ -700,23 +714,44 @@ Parser::functionBody(FunctionBodyType ty
          */
         if (tc->sc->inStrictMode()) {
             AtomDeclsIter iter(&tc->decls);
             while (Definition *dn = iter.next()) {
                 if (dn->kind() == Definition::ARG && dn->isAssigned()) {
                     tc->sc->setFunDefinitelyNeedsArgsObj();
                     break;
                 }
-             }
+            }
         }
+        break;
+      case ARGUMENT:
+        break;
     }
 
     return pn;
 }
 
+bool
+Parser::checkForArgumentsAndRest()
+{
+    JS_ASSERT(!tc->sc->inFunction);
+    if (callerFrame && callerFrame->isFunctionFrame() && callerFrame->fun()->hasRest()) {
+        PropertyName *arguments = context->runtime->atomState.argumentsAtom;
+        for (AtomDefnRange r = tc->lexdeps->all(); !r.empty(); r.popFront()) {
+            if (r.front().key() == arguments) {
+                reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ARGUMENTS_AND_REST);
+                return false;
+            }
+        }
+        /* We're not in a function context, so we don't expect any bindings. */
+        JS_ASSERT(tc->sc->bindings.lookup(context, arguments, NULL) == NONE);
+    }
+    return true;
+}
+
 // Create a placeholder Definition node for |atom|.
 // Nb: unlike most functions that are passed a Parser, this one gets a
 // SharedContext passed in separately, because in this case |sc| may not equal
 // |parser->tc->sc|.
 static Definition *
 MakePlaceholder(ParseNode *pn, Parser *parser, SharedContext *sc)
 {
     Definition *dn = (Definition *) NameNode::create(PNK_NAME, pn->pn_atom, parser, sc);
@@ -1245,31 +1280,37 @@ LeaveFunction(ParseNode *fn, Parser *par
     }
 
     funbox->bindings.transfer(funtc->sc->context, &funtc->sc->bindings);
 
     return true;
 }
 
 bool
-Parser::functionArguments(ParseNode **listp)
+Parser::functionArguments(ParseNode **listp, bool &hasRest)
 {
     if (tokenStream.getToken() != TOK_LP) {
         reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_BEFORE_FORMAL);
         return false;
     }
 
+    hasRest = false;
+
     if (!tokenStream.matchToken(TOK_RP)) {
 #if JS_HAS_DESTRUCTURING
         JSAtom *duplicatedArg = NULL;
         bool destructuringArg = false;
         ParseNode *list = NULL;
 #endif
 
         do {
+            if (hasRest) {
+                reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PARAMETER_AFTER_REST);
+                return false;
+            }
             switch (TokenKind tt = tokenStream.getToken()) {
 #if JS_HAS_DESTRUCTURING
               case TOK_LB:
               case TOK_LC:
               {
                 /* See comment below in the TOK_NAME case. */
                 if (duplicatedArg)
                     goto report_dup_and_destructuring;
@@ -1321,16 +1362,28 @@ Parser::functionArguments(ParseNode **li
                     list->makeEmpty();
                     *listp = list;
                 }
                 list->append(item);
                 break;
               }
 #endif /* JS_HAS_DESTRUCTURING */
 
+              case TOK_TRIPLEDOT:
+              {
+                hasRest = true;
+                tt = tokenStream.getToken();
+                if (tt != TOK_NAME) {
+                    if (tt != TOK_ERROR)
+                        reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NO_REST_NAME);
+                    return false;
+                }
+                /* Fall through */
+              }
+
               case TOK_NAME:
               {
                 RootedVar<PropertyName*> name(context, tokenStream.currentToken().name());
 
 #ifdef JS_HAS_DESTRUCTURING
                 /*
                  * ECMA-262 requires us to support duplicate parameter names,
                  * but if the parameter list includes destructuring, we
@@ -1514,20 +1567,23 @@ Parser::functionDef(HandlePropertyName f
 
     if (outertc->sc->inStrictMode())
         funsc.setInStrictMode();    // inherit strict mode from parent
 
     RootedVarFunction fun(context, funbox->function());
 
     /* Now parse formal argument list and compute fun->nargs. */
     ParseNode *prelude = NULL;
-    if (!functionArguments(&prelude))
+    bool hasRest;
+    if (!functionArguments(&prelude, hasRest))
         return NULL;
 
     fun->setArgCount(funsc.bindings.numArgs());
+    if (hasRest)
+        fun->setHasRest();
 
 #if JS_HAS_DESTRUCTURING
     /*
      * If there were destructuring formal parameters, bind the destructured-to
      * local variables now that we've parsed all the regular and destructuring
      * formal parameters. Because js::Bindings::add must be called first for
      * all ARGUMENTs, then all VARIABLEs and CONSTANTs, and finally all UPVARs,
      * we can't bind vars induced by formal parameter destructuring until after
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -141,16 +141,18 @@ struct Parser : private AutoGCRooter
 
     /*
      * Parse a function body.  Pass StatementListBody if the body is a list of
      * statements; pass ExpressionBody if the body is a single expression.
      */
     enum FunctionBodyType { StatementListBody, ExpressionBody };
     ParseNode *functionBody(FunctionBodyType type);
 
+    bool checkForArgumentsAndRest();
+
   private:
     /*
      * JS parsers, from lowest to highest precedence.
      *
      * Each parser must be called during the dynamic scope of a TreeContext
      * object, pointed to by this->tc.
      *
      * Each returns a parse node tree or null on error.
@@ -203,17 +205,17 @@ struct Parser : private AutoGCRooter
     ParseNode *memberExpr(JSBool allowCallSyntax);
     ParseNode *primaryExpr(TokenKind tt, bool afterDoubleDot);
     ParseNode *parenExpr(JSBool *genexp = NULL);
 
     /*
      * Additional JS parsers.
      */
     enum FunctionType { Getter, Setter, Normal };
-    bool functionArguments(ParseNode **list);
+    bool functionArguments(ParseNode **list, bool &hasRest);
 
     ParseNode *functionDef(HandlePropertyName name, FunctionType type, FunctionSyntaxKind kind);
 
     ParseNode *unaryOpExpr(ParseNodeKind kind, JSOp op);
 
     ParseNode *condition();
     ParseNode *comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp,
                                  ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP);
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1514,22 +1514,28 @@ TokenStream::getTokenInternal()
     }
 
     if (c1kind == Dot) {
         c = getCharIgnoreEOL();
         if (JS7_ISDEC(c)) {
             numStart = userbuf.addressOfNextRawChar() - 2;
             goto decimal_dot;
         }
+        if (c == '.') {
+            qc = getCharIgnoreEOL();
+            if (qc == '.') {
+                tt = TOK_TRIPLEDOT;
+                goto out;
+            }
+            ungetCharIgnoreEOL(qc);
 #if JS_HAS_XML_SUPPORT
-        if (c == '.') {
             tt = TOK_DBLDOT;
             goto out;
+#endif
         }
-#endif
         ungetCharIgnoreEOL(c);
         tt = TOK_DOT;
         goto out;
     }
 
     if (c1kind == Equals) {
         if (matchChar('=')) {
             if (matchChar('=')) {
@@ -2152,16 +2158,17 @@ TokenKindToString(TokenKind tt)
       case TOK_PLUS:            return "TOK_PLUS";
       case TOK_MINUS:           return "TOK_MINUS";
       case TOK_STAR:            return "TOK_STAR";
       case TOK_DIV:             return "TOK_DIV";
       case TOK_MOD:             return "TOK_MOD";
       case TOK_INC:             return "TOK_INC";
       case TOK_DEC:             return "TOK_DEC";
       case TOK_DOT:             return "TOK_DOT";
+      case TOK_TRIPLEDOT:       return "TOK_TRIPLEDOT";
       case TOK_LB:              return "TOK_LB";
       case TOK_RB:              return "TOK_RB";
       case TOK_LC:              return "TOK_LC";
       case TOK_RC:              return "TOK_RC";
       case TOK_LP:              return "TOK_LP";
       case TOK_RP:              return "TOK_RP";
       case TOK_NAME:            return "TOK_NAME";
       case TOK_NUMBER:          return "TOK_NUMBER";
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -42,16 +42,17 @@ enum TokenKind {
     TOK_BITAND,                    /* bitwise-and (&) */
     TOK_PLUS,                      /* plus */
     TOK_MINUS,                     /* minus */
     TOK_STAR,                      /* multiply */
     TOK_DIV,                       /* divide */
     TOK_MOD,                       /* modulus */
     TOK_INC, TOK_DEC,              /* increment/decrement (++ --) */
     TOK_DOT,                       /* member operator (.) */
+    TOK_TRIPLEDOT,                 /* for rest arguments (...) */
     TOK_LB, TOK_RB,                /* left and right brackets */
     TOK_LC, TOK_RC,                /* left and right curlies (braces) */
     TOK_LP, TOK_RP,                /* left and right parentheses */
     TOK_NAME,                      /* identifier */
     TOK_NUMBER,                    /* numeric constant */
     TOK_STRING,                    /* string constant */
     TOK_REGEXP,                    /* RegExp constant */
     TOK_TRUE,                      /* true */
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-arguments-as-parameters.js
@@ -0,0 +1,9 @@
+function f1(...arguments) {
+    assertEq("1,2,3", arguments.toString());
+}
+f1(1, 2, 3);
+function f2(arguments, ...rest) {
+    assertEq(arguments, 42);
+    assertEq("1,2,3", rest.toString());
+}
+f2(42, 1, 2, 3);
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-basic.js
@@ -0,0 +1,15 @@
+function check(expected, ...rest) {
+    assertEq(expected.toString(), rest.toString());
+}
+
+assertEq(check.length, 1);
+check([]);
+check(['a', 'b'], 'a', 'b');
+check(['a', 'b', 'c', 'd'], 'a', 'b', 'c', 'd');
+check.apply(null, [['a', 'b'], 'a', 'b'])
+check.call(null, ['a', 'b'], 'a', 'b')
+
+var g = newGlobal('new-compartment');
+g.eval("function f(...rest) { return rest; }");
+var a = g.f(1, 2, 3);
+assertEq(a instanceof g.Array, true);
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-debugger.js
@@ -0,0 +1,17 @@
+var g = newGlobal('new-compartment');
+g.eval("function f(...x) {}");
+var dbg = new Debugger;
+var gw = dbg.addDebuggee(g);
+var fw = gw.getOwnPropertyDescriptor("f").value;
+assertEq(fw.parameterNames.toString(), "x");
+
+var g = newGlobal('new-compartment');
+g.eval("function f(...rest) { debugger; }");
+var dbg = Debugger(g);
+dbg.onDebuggerStatement = function (frame) {
+    var result = frame.eval("arguments");
+    assertEq("throw" in result, true);
+    var result2 = frame.evalWithBindings("exc instanceof SyntaxError", {exc: result.throw});
+    assertEq(result2.return, true);
+};
+g.f();
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-decompile.js
@@ -0,0 +1,2 @@
+g = (function (...rest) { return rest; });
+assertEq(g.toString(), "function (...rest) {\n    return rest;\n}");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-disallow-arguments-strict.js
@@ -0,0 +1,8 @@
+"use strict";
+load(libdir + "asserts.js");
+assertThrowsInstanceOf(function () {
+    eval("(function (...arguments) {})");
+}, SyntaxError);
+assertThrowsInstanceOf(function () {
+    eval("(function (...eval) {})");
+}, SyntaxError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-disallow-arguments.js
@@ -0,0 +1,30 @@
+load(libdir + "asserts.js");
+var ieval = eval;
+
+// Now for a tour of the various ways you can access arguments.
+assertThrowsInstanceOf(function () {
+    ieval("function x(...rest) { arguments; }");
+}, SyntaxError)
+assertThrowsInstanceOf(function () {
+    Function("...rest", "arguments;");
+}, SyntaxError);
+assertThrowsInstanceOf(function (...rest) {
+    eval("arguments;");
+}, SyntaxError);
+assertThrowsInstanceOf(function (...rest) {
+    eval("arguments = 42;");
+}, SyntaxError);
+
+function g(...rest) {
+    assertThrowsInstanceOf(h, Error);
+}
+function h() {
+    g.arguments;
+}
+g();
+
+// eval() is evil, but you can still use it with rest parameters!
+function still_use_eval(...rest) {
+    eval("x = 4");
+}
+still_use_eval();
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-in-Function.js
@@ -0,0 +1,3 @@
+h = Function("a", "b", "c", "...rest", "return rest.toString();");
+assertEq(h.length, 3);
+assertEq(h(1, 2, 3, 4, 5), "4,5");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-invalid-syntax.js
@@ -0,0 +1,12 @@
+load(libdir + "asserts.js");
+var ieval = eval;
+var offenders = [["..."], ["...rest"," x"], ["...rest", "[x]"],
+                 ["...rest", "...rest2"]];
+for (var arglist of offenders) {
+    assertThrowsInstanceOf(function () {
+        ieval("function x(" + arglist.join(", ") + ") {}");
+    }, SyntaxError);
+    assertThrowsInstanceOf(function () {
+        Function.apply(null, arglist.concat("return 0;"));
+    }, SyntaxError);
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-nested-arguments.js
@@ -0,0 +1,7 @@
+function f(...rest) {
+    function nested() {
+        return arguments.length;
+    }
+    return nested;
+}
+assertEq(f()(1, 2, 3), 3);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-nested.js
@@ -0,0 +1,7 @@
+function f(...rest) {
+    function nested () {
+        return rest;
+    }
+    return nested;
+}
+assertEq(f(1, 2, 3)().toString(), [1, 2, 3].toString());
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arguments/rest-underflow.js
@@ -0,0 +1,9 @@
+function f(a, b, c, ...rest) {
+    assertEq(a, 1);
+    assertEq(b, undefined);
+    assertEq(c, undefined);
+    assertEq(Array.isArray(rest), true);
+    assertEq(rest.length, 0);
+    assertEq(Object.getPrototypeOf(rest), Array.prototype);
+}
+f(1);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -340,8 +340,12 @@ MSG_DEF(JSMSG_DEBUG_NOT_SCRIPT_FRAME, 28
 MSG_DEF(JSMSG_CANT_WATCH_PROP,        287, 0, JSEXN_TYPEERR, "properties whose names are objects can't be watched")
 MSG_DEF(JSMSG_CSP_BLOCKED_EVAL,       288, 0, JSEXN_ERR, "call to eval() blocked by CSP")
 MSG_DEF(JSMSG_DEBUG_NO_SCOPE_OBJECT,  289, 0, JSEXN_TYPEERR, "declarative Environments don't have binding objects")
 MSG_DEF(JSMSG_EMPTY_CONSEQUENT,       290, 0, JSEXN_SYNTAXERR, "mistyped ; after conditional?")
 MSG_DEF(JSMSG_NOT_ITERABLE,           291, 1, JSEXN_TYPEERR, "{0} is not iterable")
 MSG_DEF(JSMSG_QUERY_LINE_WITHOUT_URL, 292, 0, JSEXN_TYPEERR, "findScripts query object has 'line' property, but no 'url' property")
 MSG_DEF(JSMSG_QUERY_INNERMOST_WITHOUT_LINE_URL, 293, 0, JSEXN_TYPEERR, "findScripts query object has 'innermost' property without both 'url' and 'line' properties")
 MSG_DEF(JSMSG_DEBUG_VARIABLE_NOT_FOUND, 294, 0, JSEXN_TYPEERR, "variable not found in environment")
+MSG_DEF(JSMSG_PARAMETER_AFTER_REST,   295, 0, JSEXN_SYNTAXERR, "parameter after rest parameter")
+MSG_DEF(JSMSG_NO_REST_NAME,           296, 0, JSEXN_SYNTAXERR, "no parameter name after ...")
+MSG_DEF(JSMSG_ARGUMENTS_AND_REST,     297, 0, JSEXN_SYNTAXERR, "'arguments' object may not be used in conjunction with a rest parameter")
+MSG_DEF(JSMSG_FUNCTION_ARGUMENTS_AND_REST, 298, 0, JSEXN_ERR, "the 'arguments' property of a function with a rest parameter may not be used")
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2187,17 +2187,17 @@ class AutoIdRooter : private AutoGCRoote
                                            if getters/setters are JSNatives */
 
 /* Function flags, internal use only, returned by JS_GetFunctionFlags. */
 #define JSFUN_LAMBDA            0x08    /* expressed, not declared, function */
 #define JSFUN_HEAVYWEIGHT       0x80    /* activation requires a Call object */
 
 #define JSFUN_HEAVYWEIGHT_TEST(f)  ((f) & JSFUN_HEAVYWEIGHT)
 
-/* 0x0100 is unused */
+#define JSFUN_HAS_REST          0x0100  /* function has a rest (...) parameter */
 #define JSFUN_CONSTRUCTOR     0x0200    /* native that can be called as a ctor
                                            without creating a this object */
 
 #define JSFUN_FLAGS_MASK      0x07f8    /* overlay JSFUN_* attributes --
                                            bits 12-15 are used internally to
                                            flag interpreted functions */
 
 #define JSFUN_STUB_GSOPS      0x1000    /* use JS_PropertyStub getter/setter
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -97,16 +97,20 @@ fun_getProperty(JSContext *cx, HandleObj
             break;
     }
     if (iter.done())
         return true;
 
     StackFrame *fp = iter.fp();
 
     if (JSID_IS_ATOM(id, cx->runtime->atomState.argumentsAtom)) {
+        if (fun->hasRest()) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_FUNCTION_ARGUMENTS_AND_REST);
+            return false;
+        }
         /* Warn if strict about f.arguments or equivalent unqualified uses. */
         if (!JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING | JSREPORT_STRICT, js_GetErrorMessage,
                                           NULL, JSMSG_DEPRECATED_USAGE, js_arguments_str)) {
             return false;
         }
 
         ArgumentsObject *argsobj = ArgumentsObject::createUnexpected(cx, fp);
         if (!argsobj)
@@ -286,17 +290,17 @@ fun_resolve(JSContext *cx, HandleObject 
     }
 
     if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom) ||
         JSID_IS_ATOM(id, cx->runtime->atomState.nameAtom)) {
         JS_ASSERT(!IsInternalFunctionObject(obj));
 
         Value v;
         if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom))
-            v.setInt32(fun->nargs);
+            v.setInt32(fun->nargs - fun->hasRest());
         else
             v.setString(fun->atom ? fun->atom : cx->runtime->emptyString);
 
         if (!DefineNativeProperty(cx, fun, id, v, JS_PropertyStub, JS_StrictPropertyStub,
                                   JSPROP_PERMANENT | JSPROP_READONLY, 0, 0)) {
             return false;
         }
         *objp = fun;
@@ -998,16 +1002,18 @@ Function(JSContext *cx, unsigned argc, V
     if (!global->isRuntimeCodeGenEnabled(cx)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CSP_BLOCKED_FUNCTION);
         return false;
     }
 
     Bindings bindings(cx);
     Bindings::StackRoot bindingsRoot(cx, &bindings);
 
+    bool hasRest = false;
+
     const char *filename;
     unsigned lineno;
     JSPrincipals *originPrincipals;
     CurrentScriptFileLineOrigin(cx, &filename, &lineno, &originPrincipals);
     JSPrincipals *principals = PrincipalsForCompiledCode(args, cx);
 
     unsigned n = args.length() ? args.length() - 1 : 0;
     if (n > 0) {
@@ -1087,18 +1093,38 @@ Function(JSContext *cx, unsigned argc, V
         /* The argument string may be empty or contain no tokens. */
         TokenKind tt = ts.getToken();
         if (tt != TOK_EOF) {
             for (;;) {
                 /*
                  * Check that it's a name.  This also implicitly guards against
                  * TOK_ERROR, which was already reported.
                  */
-                if (tt != TOK_NAME)
-                    return OnBadFormal(cx, tt);
+                if (hasRest) {
+                    ReportCompileErrorNumber(cx, &ts, NULL, JSREPORT_ERROR,
+                                             JSMSG_PARAMETER_AFTER_REST);
+                    return false;
+                }
+
+                if (tt != TOK_NAME) {
+                    if (tt == TOK_TRIPLEDOT) {
+                        hasRest = true;
+                        tt = ts.getToken();
+                        if (tt != TOK_NAME) {
+                            if (tt != TOK_ERROR)
+                                ReportCompileErrorNumber(cx, &ts, NULL,
+                                                         JSREPORT_ERROR,
+                                                         JSMSG_NO_REST_NAME);
+                            return false;
+                        }
+                    }
+                    else {
+                        return OnBadFormal(cx, tt);
+                    }
+                }
 
                 /* Check for a duplicate parameter name. */
                 RootedVar<PropertyName*> name(cx, ts.currentToken().name());
                 if (bindings.hasBinding(cx, name)) {
                     JSAutoByteString bytes;
                     if (!js_AtomToPrintableString(cx, name, &bytes))
                         return false;
                     if (!ReportCompileErrorNumber(cx, &ts, NULL,
@@ -1151,16 +1177,19 @@ Function(JSContext *cx, unsigned argc, V
      * Thus 'var x = 42; f = new Function("return x"); print(f())' prints 42,
      * and so would a call to f from another top-level's script or function.
      */
     RootedVarFunction fun(cx, js_NewFunction(cx, NULL, NULL, 0, JSFUN_LAMBDA | JSFUN_INTERPRETED,
                                              global, cx->runtime->atomState.anonymousAtom));
     if (!fun)
         return false;
 
+    if (hasRest)
+        fun->setHasRest();
+
     bool ok = frontend::CompileFunctionBody(cx, fun, principals, originPrincipals,
                                             &bindings, chars, length, filename, lineno,
                                             cx->findVersion());
     args.rval().setObject(*fun);
     return ok;
 }
 
 bool
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -58,16 +58,17 @@ struct JSFunction : public JSObject
                                      use the accessor! */
             JSObject    *env_;    /* environment for new activations;
                                      use the accessor! */
         } i;
         void            *nativeOrScript;
     } u;
     js::HeapPtrAtom  atom;        /* name for diagnostics and decompiling */
 
+    bool hasRest()           const { return flags & JSFUN_HAS_REST; }
     bool isInterpreted()     const { return kind() >= JSFUN_INTERPRETED; }
     bool isNative()          const { return !isInterpreted(); }
     bool isNativeConstructor() const { return flags & JSFUN_CONSTRUCTOR; }
     bool isHeavyweight()     const { return JSFUN_HEAVYWEIGHT_TEST(flags); }
     bool isNullClosure()     const { return kind() == JSFUN_NULL_CLOSURE; }
     bool isFunctionPrototype() const { return flags & JSFUN_PROTOTYPE; }
     bool isInterpretedConstructor() const { return isInterpreted() && !isFunctionPrototype(); }
 
@@ -80,16 +81,21 @@ struct JSFunction : public JSObject
     /* Returns the strictness of this function, which must be interpreted. */
     inline bool inStrictMode() const;
 
     void setArgCount(uint16_t nargs) {
         JS_ASSERT(this->nargs == 0);
         this->nargs = nargs;
     }
 
+    void setHasRest() {
+        JS_ASSERT(!hasRest());
+        this->flags |= JSFUN_HAS_REST;
+    }
+
     /* uint16_t representation bounds number of call object dynamic slots. */
     enum { MAX_ARGS_AND_VARS = 2 * ((1U << 16) - 1) };
 
     /*
      * For an interpreted function, accessors for the initial scope object of
      * activations (stack frames) of the function.
      */
     inline JSObject *environment() const;
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3637,16 +3637,31 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
       case JSOP_ARGUMENTS:
         /* Compute a precise type only when we know the arguments won't escape. */
         if (script->needsArgsObj())
             pushed[0].addType(cx, Type::UnknownType());
         else
             pushed[0].addType(cx, Type::MagicArgType());
         break;
 
+      case JSOP_REST: {
+        TypeSet *types = script->analysis()->bytecodeTypes(pc);
+        types->addSubset(cx, &pushed[0]);
+        if (script->hasGlobal()) {
+            TypeObject *rest = TypeScript::InitObject(cx, script, pc, JSProto_Array);
+            if (!rest)
+                return false;
+            types->addType(cx, Type::ObjectType(rest));
+        } else {
+            types->addType(cx, Type::UnknownType());
+        }
+        break;
+      }
+
+
       case JSOP_SETPROP: {
         jsid id = GetAtomId(cx, script, pc, 0);
         poppedTypes(pc, 1)->addSetProperty(cx, script, pc, poppedTypes(pc, 0), id);
         poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
         break;
       }
 
       case JSOP_LENGTH:
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -1511,17 +1511,16 @@ 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_UNUSED16)
 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)
 ADD_EMPTY_CASE(JSOP_UNUSED24)
@@ -2809,26 +2808,38 @@ BEGIN_CASE(JSOP_LOOKUPSWITCH)
 
   end_lookup_switch:
     len = GET_JUMP_OFFSET(pc2);
 }
 END_VARLEN_CASE
 }
 
 BEGIN_CASE(JSOP_ARGUMENTS)
+    JS_ASSERT(!regs.fp()->fun()->hasRest());
     if (script->needsArgsObj()) {
         ArgumentsObject *obj = ArgumentsObject::create(cx, regs.fp());
         if (!obj)
             goto error;
         PUSH_COPY(ObjectValue(*obj));
     } else {
         PUSH_COPY(MagicValue(JS_OPTIMIZED_ARGUMENTS));
     }
 END_CASE(JSOP_ARGUMENTS)
 
+BEGIN_CASE(JSOP_REST)
+{
+    JSObject *rest = regs.fp()->createRestParameter(cx);
+    if (!rest)
+        goto error;
+    PUSH_COPY(ObjectValue(*rest));
+    if (!SetInitializerObjectType(cx, script, regs.pc, rest))
+        goto error;
+}
+END_CASE(JSOP_REST)
+
 BEGIN_CASE(JSOP_CALLALIASEDVAR)
 BEGIN_CASE(JSOP_GETALIASEDVAR)
 {
     ScopeCoordinate sc = ScopeCoordinate(regs.pc);
     Value &var = AliasedVar(regs.fp(), sc);
     PUSH_COPY(var);
 }
 END_CASE(JSOP_GETALIASEDVAR)
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -5532,16 +5532,18 @@ js_DecompileFunction(JSPrinter *jp)
         ss.printer = NULL;
         jp->script = script;
 #endif
 
         for (unsigned i = 0; i < fun->nargs; i++) {
             if (i > 0)
                 js_puts(jp, ", ");
 
+            if (i == unsigned(fun->nargs) - 1 && fun->hasRest())
+                js_puts(jp, "...");
             JSAtom *param = GetArgOrVarAtom(jp, i);
 
 #if JS_HAS_DESTRUCTURING
 #define LOCAL_ASSERT(expr)      LOCAL_ASSERT_RV(expr, JS_FALSE)
 
             if (!param) {
                 ptrdiff_t todo;
                 const char *lval;
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -501,17 +501,18 @@ OPDEF(JSOP_LENGTH,        217, "length",
  */
 OPDEF(JSOP_HOLE,          218, "hole",         NULL,  1,  0,  1,  0,  JOF_BYTE)
 
 OPDEF(JSOP_UNUSED17,      219,"unused17",      NULL,  1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED24,      220,"unused24",      NULL,  1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED25,      221,"unused25",      NULL,  1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED29,      222,"unused29",      NULL,  1,  0,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED30,      223,"unused30",      NULL,  1,  0,  0,  0,  JOF_BYTE)
-OPDEF(JSOP_UNUSED16,      224,"unused16",      NULL,  1,  0,  0,  0,  JOF_BYTE)
+
+OPDEF(JSOP_REST,          224, "rest",         NULL,  1,  0,  1,  0,  JOF_BYTE|JOF_TYPESET)
 
 /* Pop the stack, convert to a jsid (int or string), and push back. */
 OPDEF(JSOP_TOID,          225, "toid",         NULL,  1,  1,  1,  0,  JOF_BYTE)
 
 /* Push the implicit 'this' value for calls to the associated name. */
 OPDEF(JSOP_IMPLICITTHIS,  226, "implicitthis", "",    5,  0,  1,  0,  JOF_ATOM)
 
 /* This opcode is the target of the entry jump for some loop. */
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -51,16 +51,17 @@ js_PutArgsObject(StackFrame *fp)
         JS_ASSERT(!argsobj.maybeStackFrame());
     }
 }
 
 ArgumentsObject *
 ArgumentsObject::create(JSContext *cx, uint32_t argc, HandleObject callee)
 {
     JS_ASSERT(argc <= StackSpace::ARGS_LENGTH_MAX);
+    JS_ASSERT(!callee->toFunction()->hasRest());
 
     RootedVarObject proto(cx, callee->global().getOrCreateObjectPrototype(cx));
     if (!proto)
         return NULL;
 
     RootedVarTypeObject type(cx);
 
     type = proto->getNewType(cx);
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -166,16 +166,25 @@ StackFrame::initFixupFrame(StackFrame *p
                          UNDERFLOW_ARGS)) == 0);
 
     flags_ = FUNCTION | flags;
     prev_ = prev;
     ncode_ = ncode;
     u.nactual = nactual;
 }
 
+inline JSObject *
+StackFrame::createRestParameter(JSContext *cx)
+{
+    JS_ASSERT(fun()->hasRest());
+    unsigned nformal = fun()->nargs - 1, nactual = numActualArgs();
+    unsigned nrest = (nactual > nformal) ? nactual - nformal : 0;
+    return NewDenseCopiedArray(cx, nrest, actualArgs() + nformal);
+}
+
 inline Value &
 StackFrame::canonicalActualArg(unsigned i) const
 {
     if (i < numFormalArgs())
         return formalArg(i);
     JS_ASSERT(i < numActualArgs());
     return actualArgs()[i];
 }
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -497,16 +497,18 @@ class StackFrame
         return prev_;
     }
 
     inline void resetGeneratorPrev(JSContext *cx);
     inline void resetInlinePrev(StackFrame *prevfp, jsbytecode *prevpc);
 
     inline void initInlineFrame(JSFunction *fun, StackFrame *prevfp, jsbytecode *prevpc);
 
+    inline JSObject *createRestParameter(JSContext *cx);
+
     /*
      * Frame slots
      *
      * A frame's 'slots' are the fixed slots associated with the frame (like
      * local variables) followed by an expression stack holding temporary
      * values. A frame's 'base' is the base of the expression stack.
      */