# HG changeset patch # Parent 2552b0541f87e2a0ad1eaa9b3baa443d1efcd4a0 # User Benjamin Peterson Bug 757676: JS default parameters 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 @@ -5854,16 +5854,71 @@ EmitUnary(JSContext *cx, BytecodeEmitter bce->sc->inForInit = false; if (!EmitTree(cx, bce, pn2)) return false; bce->sc->inForInit = oldInForInit; return Emit1(cx, bce, op) >= 0; } +static bool +EmitDefaults(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) +{ + ParseNode *arg; + ParseNode *pnlast = pn->last(); + unsigned ndefaults = 0; + for (arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) + if (arg->isKind(PNK_DEFAULTARG)) + ndefaults++; + JSFunction *fun = bce->sc->fun(); + unsigned nformal = fun->nargs - fun->hasRest(); + EMIT_UINT16_IMM_OP(JSOP_ACTUALSFILLED, nformal - ndefaults); + ptrdiff_t top = bce->offset(); + size_t tableSize = (size_t)(JUMP_OFFSET_LEN * (3 + ndefaults)); + int noteIndex = NewSrcNote3(cx, bce, SRC_SWITCH, 0, 0); + if (noteIndex < 0) + return false; + if (EmitN(cx, bce, JSOP_TABLESWITCH, tableSize) < 0) + return false; + jsbytecode *pc = bce->code(top + JUMP_OFFSET_LEN); + JS_ASSERT(nformal >= ndefaults); + SET_JUMP_OFFSET(pc, nformal - ndefaults); + pc += JUMP_OFFSET_LEN; + SET_JUMP_OFFSET(pc, nformal - 1); + pc += JUMP_OFFSET_LEN; + /* Fill body of switch, which sets defaults where needed. */ + arg = pn->pn_head; + do { + ParseNode *def = arg->pn_next; + if (def->isKind(PNK_DEFAULTARG)) { + SET_JUMP_OFFSET(pc, bce->offset() - top); + pc += JUMP_OFFSET_LEN; + if (!EmitTree(cx, bce, def->pn_kid)) + return false; + if (!BindNameToSlot(cx, bce, arg)) + return false; + if (!EmitVarOp(cx, arg, JSOP_SETARG, bce)) + return false; + if (Emit1(cx, bce, JSOP_POP) < 0) + return false; + if (def == pnlast) + break; + arg = def->pn_next; + } else { + JS_ASSERT(arg->isKind(PNK_NAME)); + arg = def; + } + } while (arg != pnlast); + JS_ASSERT(pc == bce->code(top + tableSize)); + SET_JUMP_OFFSET(bce->code(top), bce->offset() - top); + if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, bce->offset() - top)) + return false; + return true; +} + JSBool frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) { JS_CHECK_RECURSION(cx, return JS_FALSE); EmitLevelManager elm(bce); JSBool ok = true; @@ -5875,28 +5930,62 @@ frontend::EmitTree(JSContext *cx, Byteco switch (pn->getKind()) { case PNK_FUNCTION: ok = EmitFunc(cx, bce, pn); break; case PNK_ARGSBODY: { + JSFunction *fun = bce->sc->fun(); ParseNode *pnlast = pn->last(); + if (fun->hasDefaults()) { + ParseNode *rest = NULL; + if (fun->hasRest()) { + JS_ASSERT(!bce->sc->funArgumentsHasLocalBinding()); + /* + Defaults and rest need special handling. The rest parameter + needs to be undefined while defaults are being processed. To + do this, we create the rest argument and let it sit on the + stack while processing defaults. The rest parameter's slot is + set to undefined for the course of default processing. + */ + rest = pn->pn_head; + while (rest->pn_next != pnlast) + rest = rest->pn_next; + if (Emit1(cx, bce, JSOP_REST) < 0) + return false; + CheckTypeSet(cx, bce, JSOP_REST); + if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return false; + if (!EmitVarOp(cx, rest, JSOP_SETARG, bce)) + return false; + if (Emit1(cx, bce, JSOP_POP) < 0) + return false; + } + if (!EmitDefaults(cx, bce, pn)) + return false; + if (fun->hasRest()) { + if (!EmitVarOp(cx, rest, JSOP_SETARG, bce)) + return false; + if (Emit1(cx, bce, JSOP_POP) < 0) + return false; + } + } for (ParseNode *pn2 = pn->pn_head; pn2 != pnlast; pn2 = pn2->pn_next) { 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. */ + if (pn2->pn_next == pnlast && fun->hasRest() && !fun->hasDefaults()) { + /* Fill rest parameter. We handled the case with defaults above. */ 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) diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -97,16 +97,17 @@ enum ParseNodeKind { PNK_LB, PNK_RB, PNK_STATEMENTLIST, PNK_XMLCURLYEXPR, PNK_RC, PNK_LP, PNK_RP, PNK_NAME, + PNK_DEFAULTARG, PNK_NUMBER, PNK_STRING, PNK_REGEXP, PNK_TRUE, PNK_FALSE, PNK_NULL, PNK_THIS, PNK_FUNCTION, @@ -382,16 +383,17 @@ enum ParseNodeKind { * pn_xflags: PN_ENDCOMMA if extra comma at end * PNK_RC list pn_head: list of pn_count binary PNK_COLON nodes * PNK_COLON binary key-value pair in object initializer or * destructuring lhs * pn_left: property id, pn_right: value * var {x} = object destructuring shorthand shares * PN_NAME node for x on left and right of PNK_COLON * node in PNK_RC's list, has PNX_DESTRUCT flag + * PNK_DEFAULTARG unary holds the default value of a argument * PNK_NAME, name pn_atom: name, string, or object atom * PNK_STRING, pn_op: JSOP_NAME, JSOP_STRING, or JSOP_OBJECT, or * JSOP_REGEXP * PNK_REGEXP If JSOP_NAME, pn_op may be JSOP_*ARG or JSOP_*VAR * with pn_cookie telling (staticLevel, slot) (see * jsscript.h's UPVAR macros) and pn_dflags telling * const-ness and static analysis results * PNK_NAME name If pn_used, PNK_NAME uses the lexdef member instead 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 @@ -1274,23 +1274,24 @@ LeaveFunction(ParseNode *fn, Parser *par } funbox->bindings.transfer(funtc->sc->context, &funtc->sc->bindings); return true; } bool -Parser::functionArguments(ParseNode **listp, bool &hasRest) +Parser::functionArguments(ParseNode **listp, bool &hasDefaults, bool &hasRest) { if (tokenStream.getToken() != TOK_LP) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_BEFORE_FORMAL); return false; } + hasDefaults = false; hasRest = false; if (!tokenStream.matchToken(TOK_RP)) { #if JS_HAS_DESTRUCTURING JSAtom *duplicatedArg = NULL; bool destructuringArg = false; ParseNode *list = NULL; #endif @@ -1303,16 +1304,21 @@ Parser::functionArguments(ParseNode **li 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; + if (hasDefaults) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); + return false; + } + destructuringArg = true; /* * A destructuring formal parameter turns into one or more * local variables initialized from properties of a single * anonymous positional parameter, so here we must tweak our * binder and its data. */ @@ -1402,16 +1408,38 @@ Parser::functionArguments(ParseNode **li } #endif uint16_t slot; if (!tc->sc->bindings.addArgument(context, name, &slot)) return false; if (!DefineArg(tc->sc->funbox->node, name, slot, this)) return false; + + if (tokenStream.matchToken(TOK_ASSIGN)) { + if (hasRest) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_REST_WITH_DEFAULT); + return false; + } + hasDefaults = true; + ParseNode *def_expr = assignExpr(); + if (!def_expr) + return false; + ParseNode *def = UnaryNode::create(PNK_DEFAULTARG, this); + if (!def) { + freeTree(def_expr); + return false; + } + def->pn_kid = def_expr; + tc->sc->funbox->node->pn_body->append(def); + } else if (!hasRest && hasDefaults) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); + return false; + } + break; } default: reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_MISSING_FORMAL); /* FALL THROUGH */ case TOK_ERROR: return false; @@ -1561,21 +1589,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; - bool hasRest; - if (!functionArguments(&prelude, hasRest)) + bool hasRest, hasDefaults; + if (!functionArguments(&prelude, hasDefaults, hasRest)) return NULL; fun->setArgCount(funsc.bindings.numArgs()); + if (hasDefaults) + fun->setHasDefaults(); 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 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 @@ -204,17 +204,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 &hasRest); + bool functionArguments(ParseNode **list, bool &hasDefaults, 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/jit-test/tests/arguments/defaults-basic.js b/js/src/jit-test/tests/arguments/defaults-basic.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/arguments/defaults-basic.js @@ -0,0 +1,22 @@ +function f1(a, bIs, b=3) { + assertEq(a, 1); + assertEq(b, bIs); +} +assertEq(f1.length, 3); +f1(1, 3); +f1(1, 42, 42); +function f2(a, bIs, cIs, b=3, c=4) { + assertEq(a, 1); + assertEq(b, bIs); + assertEq(c, cIs); +} +assertEq(f2.length, 5); +f2(1, 3, 4); +f2(1, 42, 4, 42); +f2(1, 42, 43, 42, 43); +function f3(a, b, c=4) { + assertEq(a, 1); + assertEq(b, undefined); + assertEq(c, 4); +} +f3(1); diff --git a/js/src/jit-test/tests/arguments/defaults-decompile.js b/js/src/jit-test/tests/arguments/defaults-decompile.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/arguments/defaults-decompile.js @@ -0,0 +1,28 @@ +function f1(a=1) {} +assertEq(f1.toString(), "function f1(a=1) {\n}"); +function f2(a=1, b=2, c=3) {} +assertEq(f2.toString(), "function f2(a=1, b=2, c=3) {\n}"); +function f3(a, b, c=1, d=2) {} +assertEq(f3.toString(), "function f3(a, b, c=1, d=2) {\n}"); +function f4(a, [b], c=1) {} +assertEq(f4.toString(), "function f4(a, [b], c=1) {\n}"); +function f5(a, b, c=1, ...rest) {} +assertEq(f5.toString(), "function f5(a, b, c=1, ...rest) {\n}"); +function f6(a, [b], c=1, ...rest) {} +assertEq(f6.toString(), "function f6(a, [b], c=1, ...rest) {\n}"); +function f7(a, c=d = 190) {} +assertEq(f7.toString(), "function f7(a, c=d = 190) {\n}"); +function f8(a=(b = 8)) { + function nested() { + return a + b; + } + return nested; +} +assertEq(f8.toString(), "function f8(a=b = 8) {\n\n\ + function nested() {\n\ + return a + b;\n\ + }\n\n\ + return nested;\n\ +}"); +function f9(a, b, c={complexity : .5, is : 40 + great.prop}, d=[42], ...rest) {} +assertEq(f9.toString(), "function f9(a, b, c={complexity: 0.5, is: 40 + great.prop}, d=[42], ...rest) {\n}"); diff --git a/js/src/jit-test/tests/arguments/defaults-evaluation-order.js b/js/src/jit-test/tests/arguments/defaults-evaluation-order.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/arguments/defaults-evaluation-order.js @@ -0,0 +1,10 @@ +function f1(a, bIs, cIs, dIs, b=a, c=d, d=5) { + assertEq(a, 1); + assertEq(b, bIs); + assertEq(c, cIs); + assertEq(d, dIs); +} +f1(1, 1, undefined, 5); +f1(1, 42, undefined, 5, 42); +f1(1, 42, 43, 5, 42, 43); +f1(1, 42, 43, 44, 42, 43, 44); diff --git a/js/src/jit-test/tests/arguments/defaults-invalid-syntax.js b/js/src/jit-test/tests/arguments/defaults-invalid-syntax.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/arguments/defaults-invalid-syntax.js @@ -0,0 +1,14 @@ +load(libdir + "asserts.js"); + +assertThrowsInstanceOf(function () { + eval("function f(...rest=23) {}"); +}, SyntaxError); +assertThrowsInstanceOf(function () { + eval("function f(a=16, b) {}"); +}, SyntaxError); +assertThrowsInstanceOf(function () { + eval("function f([a]=4) {}"); +}, SyntaxError); +assertThrowsInstanceOf(function () { + eval("function f(a=4, [b]) {}"); +}, SyntaxError); diff --git a/js/src/jit-test/tests/arguments/defaults-with-rest.js b/js/src/jit-test/tests/arguments/defaults-with-rest.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/arguments/defaults-with-rest.js @@ -0,0 +1,19 @@ +load(libdir + "eqArrayHelper.js"); + +function f1(a, bIs, b=3, ...rest) { + assertEq(a, 1); + assertEq(bIs, b); + assertEqArray(rest, []); +} +assertEq(f1.length, 3); +f1(1, 3); +f1(1, 42, 42); +function f2(a=rest, ...rest) { + assertEq(a, undefined); +} +f2(); +function f3(a=rest, ...rest) { + assertEq(a, 1); + assertEqArray(rest, [2, 3, 4]); +} +f3(1, 2, 3, 4); diff --git a/js/src/js.msg b/js/src/js.msg --- a/js/src/js.msg +++ b/js/src/js.msg @@ -344,8 +344,10 @@ MSG_DEF(JSMSG_EMPTY_CONSEQUENT, 29 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") +MSG_DEF(JSMSG_REST_WITH_DEFAULT, 299, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default") +MSG_DEF(JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT, 300, 0, JSEXN_SYNTAXERR, "parameter(s) with default followed by parameter without default") diff --git a/js/src/jsapi.h b/js/src/jsapi.h --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2188,18 +2188,19 @@ class AutoIdRooter : private AutoGCRoote /* 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) #define JSFUN_HAS_REST 0x0100 /* function has a rest (...) parameter */ -#define JSFUN_CONSTRUCTOR 0x0200 /* native that can be called as a ctor +#define JSFUN_CONSTRUCTOR 0x0200 /* native that can be called as a ctor without creating a this object */ +#define JSFUN_HAS_DEFAULTS 0x0400 #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 instead of defaulting to class gsops for property holding function */ diff --git a/js/src/jsfun.h b/js/src/jsfun.h --- 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 hasDefaults() const { return flags & JSFUN_HAS_DEFAULTS; } 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(); } @@ -86,16 +87,21 @@ struct JSFunction : public JSObject this->nargs = nargs; } void setHasRest() { JS_ASSERT(!hasRest()); this->flags |= JSFUN_HAS_REST; } + void setHasDefaults() { + JS_ASSERT(!hasDefaults()); + this->flags |= JSFUN_HAS_DEFAULTS; + } + /* 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/jsinfer.cpp b/js/src/jsinfer.cpp --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -3384,16 +3384,17 @@ ScriptAnalysis::analyzeTypesBytecode(JSC case JSOP_UINT24: case JSOP_BITAND: case JSOP_BITOR: case JSOP_BITXOR: case JSOP_BITNOT: case JSOP_RSH: case JSOP_LSH: case JSOP_URSH: + case JSOP_ACTUALSFILLED: pushed[0].addType(cx, Type::Int32Type()); break; case JSOP_FALSE: case JSOP_TRUE: case JSOP_EQ: case JSOP_NE: case JSOP_LT: case JSOP_LE: diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -2807,16 +2807,22 @@ BEGIN_CASE(JSOP_LOOKUPSWITCH) #undef SEARCH_PAIRS end_lookup_switch: len = GET_JUMP_OFFSET(pc2); } END_VARLEN_CASE } +BEGIN_CASE(JSOP_ACTUALSFILLED) +{ + PUSH_INT32(JS_MAX(regs.fp()->numActualArgs(), GET_UINT16(regs.pc))); +} +END_CASE(JSOP_ACTUALSFILLED) + 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 { diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -5526,68 +5526,130 @@ js_DecompileFunction(JSPrinter *jp) /* Print the parameters. */ jsbytecode *pc = script->main(); jsbytecode *endpc = pc + script->length; JSBool ok = JS_TRUE; #if JS_HAS_DESTRUCTURING ss.printer = NULL; jp->script = script; + jsbytecode *dspc = pc; #endif + + jsbytecode *deftable = NULL; + jsbytecode *defbegin = NULL; + int32_t deflen = 0; + uint16_t defstart = 0; + unsigned nformal = fun->nargs - fun->hasRest(); + + if (fun->hasDefaults()) { +#define SKIP(pc, op) JS_ASSERT(*pc == op); pc += js_CodeSpec[op].length; + jsbytecode *defpc = pc; + if (fun->hasRest()) { + SKIP(defpc, JSOP_REST); + SKIP(defpc, JSOP_UNDEFINED); + JS_ASSERT(*defpc == JSOP_SETARG || *defpc == JSOP_SETALIASEDVAR); + defpc += js_CodeSpec[*defpc].length; + SKIP(defpc, JSOP_POP); + } + SKIP(defpc, JSOP_ACTUALSFILLED); + JS_ASSERT(*defpc == JSOP_TABLESWITCH); + defbegin = defpc; + deflen = GET_JUMP_OFFSET(defpc); + /* Update the pointer to destructuring bytecode. */ + dspc = defpc + deflen; + if (fun->hasRest()) { + SKIP(dspc, JSOP_SETARG); + SKIP(dspc, JSOP_POP); + } + defpc += JUMP_OFFSET_LEN; + defstart = GET_JUMP_OFFSET(defpc); + defpc += JUMP_OFFSET_LEN; + /* Skip high. */ + defpc += JUMP_OFFSET_LEN; + deftable = defpc; + /* Skip table. */ + defpc += JUMP_OFFSET_LEN*(nformal - defstart); + /* defpc now points to the first default expression. */ +#undef SKIP + } for (unsigned i = 0; i < fun->nargs; i++) { if (i > 0) js_puts(jp, ", "); - if (i == unsigned(fun->nargs) - 1 && fun->hasRest()) + bool isRest = fun->hasRest() && i == unsigned(fun->nargs) - 1; + if (isRest) 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; - LOCAL_ASSERT(*pc == JSOP_GETARG || *pc == JSOP_GETALIASEDVAR); - pc += js_CodeSpec[*pc].length; - LOCAL_ASSERT(*pc == JSOP_DUP); + LOCAL_ASSERT(!fun->hasDefaults() || defstart > i); + LOCAL_ASSERT(*dspc == JSOP_GETARG || *dspc == JSOP_GETALIASEDVAR); + dspc += js_CodeSpec[*dspc].length; + LOCAL_ASSERT(*dspc == JSOP_DUP); if (!ss.printer) { ok = InitSprintStack(cx, &ss, jp, StackDepth(script)); if (!ok) break; } - pc = DecompileDestructuring(&ss, pc, endpc); - if (!pc) { + dspc = DecompileDestructuring(&ss, dspc, endpc); + if (!dspc) { ok = JS_FALSE; break; } - LOCAL_ASSERT(*pc == JSOP_POP); - pc += JSOP_POP_LENGTH; + LOCAL_ASSERT(*dspc == JSOP_POP); + dspc += JSOP_POP_LENGTH; lval = PopStr(&ss, JSOP_NOP); todo = jp->sprinter.put(lval); if (todo < 0) { ok = JS_FALSE; break; } continue; } #undef LOCAL_ASSERT #endif if (!QuoteString(&jp->sprinter, param, 0)) { ok = JS_FALSE; break; } + + if (fun->hasDefaults() && i >= defstart && !isRest) { + js_puts(jp, "="); +#define TABLE_OFF(off) GET_JUMP_OFFSET(&deftable[(off)*JUMP_OFFSET_LEN]) + jsbytecode *casestart = defbegin + TABLE_OFF(i - defstart); + jsbytecode *caseend = defbegin + ((i < nformal - 1) ? TABLE_OFF(i - defstart + 1) : deflen); +#undef TABLE_OFF + jsbytecode *exprstop = casestart; + /* Find where the expression ends. */ + int8_t poplen = js_CodeSpec[JSOP_POP].length; + for (exprstop = casestart; exprstop < caseend; exprstop += GetBytecodeLength(exprstop)) + if ((*exprstop == JSOP_SETARG && + exprstop == caseend - js_CodeSpec[JSOP_SETARG].length - poplen) || + (*exprstop == JSOP_SETALIASEDVAR && + exprstop == caseend - js_CodeSpec[JSOP_SETALIASEDVAR].length - poplen)) + break; + JS_ASSERT(exprstop != caseend); + if (!DecompileCode(jp, script, casestart, exprstop - casestart, 0)) + return JS_FALSE; + } } #if JS_HAS_DESTRUCTURING jp->script = NULL; + pc = dspc; #endif if (!ok) return JS_FALSE; js_printf(jp, ") "); if (!(fun->flags & JSFUN_EXPR_CLOSURE)) { js_printf(jp, "{\n"); jp->indent += 4; } diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -512,8 +512,10 @@ OPDEF(JSOP_REST, 224, "rest", /* 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. */ OPDEF(JSOP_LOOPENTRY, 227, "loopentry", NULL, 1, 0, 0, 0, JOF_BYTE) + +OPDEF(JSOP_ACTUALSFILLED, 228, "actualsfilled", NULL, 3, 0, 1, 0, JOF_UINT16)