# HG changeset patch # Parent 3b86eaa02d913d4ebe39b301a9a39438d7be351a # User Benjamin Peterson Bug 574132: implement harmony rest parameters diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -255,16 +255,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); goto out; } #endif + if (!parser.checkForArgumentsAndRest()) + goto out; + /* * Nowadays the threaded interpreter needs a stop instruction, so we * do have to emit that here. */ if (Emit1(cx, &bce, JSOP_STOP) < 0) goto out; JS_ASSERT(bce.version() == version); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2612,17 +2612,32 @@ frontend::EmitFunctionScript(JSContext * { /* * The decompiler has assumptions about what may occur immediately after * script->main (e.g., in the case of destructuring params). Thus, put the * following ops into the range [script->code, script->main). Note: * execution starts from script->code, so this has no semantic effect. */ - if (bce->sc->funArgumentsHasLocalBinding()) { + if (bce->sc->fun()->hasRest()) { + bce->switchToProlog(); + if (Emit1(cx, bce, JSOP_REST) < 0) + return false; + uint16_t arg = bce->sc->fun()->nargs - 1; + if (bce->sc->bindingsAccessedDynamically()) { + JSAtom *atom = JSID_TO_ATOM(bce->sc->bindings.lastArgument()->propid()); + uint16_t binding = bce->sc->bindings.argToBinding(arg); + if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, binding, atom, bce)) + return false; + } else if (!EmitUnaliasedVarOp(cx, JSOP_SETARG, arg, bce)) + return false; + if (Emit1(cx, bce, JSOP_POP) < 0) + return false; + bce->switchToMain(); + } else if (bce->sc->funArgumentsHasLocalBinding()) { JS_ASSERT(bce->next() == bce->base()); /* See JSScript::argumentsBytecode. */ bce->switchToProlog(); if (Emit1(cx, bce, JSOP_ARGUMENTS) < 0) return false; if (bce->sc->bindingsAccessedDynamically()) { JSAtom *atom = cx->runtime->atomState.argumentsAtom; uint16_t binding = bce->sc->bindings.localToBinding(bce->sc->argumentsLocalSlot()); if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, binding, atom, bce)) diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -697,32 +697,39 @@ 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)) { + BindingKind bindKind = tc->sc->bindings.lookup(context, arguments, NULL); + switch (bindKind) { + case NONE: + /* + * 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) { + /* Fall through */ + case VARIABLE: + case CONSTANT: + if (bindKind != NONE && tc->sc->fun()->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 @@ -733,23 +740,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) + goto complain; + if (tc->sc->bindings.lookup(context, arguments, NULL) != NONE) + goto complain; + } + return true; + complain: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ARGUMENTS_AND_REST); + return false; +} + // 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); @@ -1278,31 +1306,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; @@ -1354,16 +1388,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 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 @@ -1547,20 +1593,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 diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -174,16 +174,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. @@ -236,17 +238,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); diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1549,22 +1549,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('=')) { @@ -2187,16 +2193,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"; diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -76,16 +76,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 */ diff --git a/js/src/jit-test/tests/arguments/rest-basic.js b/js/src/jit-test/tests/arguments/rest-basic.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/arguments/rest-basic.js @@ -0,0 +1,13 @@ +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'); + +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 diff --git a/js/src/jit-test/tests/arguments/rest-debugger.js b/js/src/jit-test/tests/arguments/rest-debugger.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/arguments/rest-debugger.js @@ -0,0 +1,6 @@ +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"); diff --git a/js/src/jit-test/tests/arguments/rest-decompile.js b/js/src/jit-test/tests/arguments/rest-decompile.js 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}"); diff --git a/js/src/jit-test/tests/arguments/rest-disallow-arguments.js b/js/src/jit-test/tests/arguments/rest-disallow-arguments.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/arguments/rest-disallow-arguments.js @@ -0,0 +1,23 @@ +load(libdir + "asserts.js"); +var ieval = eval; +assertThrowsInstanceOf(function () { + ieval("function x(...rest) { arguments; }"); +}, SyntaxError) +assertThrowsInstanceOf(function () { + Function("...rest", "arguments;"); +}, SyntaxError); +function f(...rest) { + eval("arguments;"); +} +assertThrowsInstanceOf(f, SyntaxError); +function still_use_eval(...rest) { + eval("x = 4"); +} +still_use_eval(); +function g(...rest) { + assertThrowsInstanceOf(h, Error); +} +function h() { + g.arguments; +} +g(); diff --git a/js/src/jit-test/tests/arguments/rest-in-Function.js b/js/src/jit-test/tests/arguments/rest-in-Function.js 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"); diff --git a/js/src/jit-test/tests/arguments/rest-invalid-syntax.js b/js/src/jit-test/tests/arguments/rest-invalid-syntax.js 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 in offenders.length) { + assertThrowsInstanceOf(function () { + ieval("function x(" + arglist.join(", ") + ") {}"); + }, SyntaxError); + assertThrowsInstanceOf(function () { + Function.apply(null, arglist.concat("return 0;")); + }, SyntaxError); +} \ No newline at end of file diff --git a/js/src/jit-test/tests/arguments/rest-nested-arguments.js b/js/src/jit-test/tests/arguments/rest-nested-arguments.js 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); diff --git a/js/src/jit-test/tests/arguments/rest-nested.js b/js/src/jit-test/tests/arguments/rest-nested.js 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 diff --git a/js/src/jit-test/tests/arguments/rest-underflow.js b/js/src/jit-test/tests/arguments/rest-underflow.js 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); diff --git a/js/src/js.msg b/js/src/js.msg --- a/js/src/js.msg +++ b/js/src/js.msg @@ -373,8 +373,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") diff --git a/js/src/jsapi.h b/js/src/jsapi.h --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2186,20 +2186,20 @@ class AutoIdRooter : private AutoGCRoote #define JSPROP_SHORTID 0x100 /* set in JS_DefineProperty attrs if getters/setters use a shortid */ #define JSPROP_NATIVE_ACCESSORS 0x08 /* set in JSPropertyDescriptor.flags 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_HAS_REST 0x0100 /* function has a rest (...) parameter */ #define JSFUN_HEAVYWEIGHT_TEST(f) ((f) & JSFUN_HEAVYWEIGHT) -/* 0x0100 is unused */ #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 diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -129,16 +129,20 @@ fun_getProperty(JSContext *cx, JSObject 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) @@ -320,17 +324,17 @@ fun_resolve(JSContext *cx, JSObject *obj } 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; @@ -1030,16 +1034,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) { @@ -1119,18 +1125,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 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, @@ -1183,16 +1209,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 diff --git a/js/src/jsfun.h b/js/src/jsfun.h --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -91,16 +91,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(); } @@ -113,16 +114,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; diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -1742,17 +1742,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) @@ -3040,26 +3039,36 @@ 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()->getRestParameter(cx); + if (!rest) + goto error; + PUSH_COPY(ObjectValue(*rest)); +} +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) diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -5565,16 +5565,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 == 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; diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -534,17 +534,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) /* 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. */ diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -199,16 +199,25 @@ StackFrame::initFixupFrame(StackFrame *p UNDERFLOW_ARGS)) == 0); flags_ = FUNCTION | flags; prev_ = prev; ncode_ = ncode; u.nactual = nactual; } +inline JSObject * +StackFrame::getRestParameter(JSContext *cx) +{ + JS_ASSERT(fun()->hasRest()); + unsigned nformal = fun()->nargs - 1, nactual = numActualArgs(); + unsigned nrest = (nactual > nformal) ? nactual - nformal : 0; + return JS_NewArrayObject(cx, nrest, actualArgs() + nformal); +} + inline Value & StackFrame::canonicalActualArg(unsigned i) const { if (i < numFormalArgs()) return formalArg(i); JS_ASSERT(i < numActualArgs()); return actualArgs()[i]; } diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -522,16 +522,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 *getRestParameter(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. */