Bug 757676 - Implement JS default parameters. r=jorendorff

This commit is contained in:
Benjamin Peterson 2012-05-26 09:33:53 -04:00
parent a37933ddeb
commit ccc0336692
20 changed files with 392 additions and 17 deletions

View File

@ -5102,6 +5102,11 @@ EmitStatementList(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t
PushStatement(bce->sc, &stmtInfo, STMT_BLOCK, top);
ParseNode *pnchild = pn->pn_head;
// Destructuring is handled in args body for functions with default
// arguments.
if (pn->pn_xflags & PNX_DESTRUCT && bce->sc->fun()->hasDefaults())
pnchild = pnchild->pn_next;
if (pn->pn_xflags & PNX_FUNCDEFS) {
/*
* This block contains top-level function definitions. To ensure
@ -5115,7 +5120,7 @@ EmitStatementList(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t
* mode for scripts does not allow separate emitter passes.
*/
JS_ASSERT(bce->sc->inFunction);
if (pn->pn_xflags & PNX_DESTRUCT) {
if (pn->pn_xflags & PNX_DESTRUCT && !bce->sc->fun()->hasDefaults()) {
/*
* Assign the destructuring arguments before defining any
* functions, see bug 419662.
@ -5860,6 +5865,50 @@ EmitUnary(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return Emit1(cx, bce, op) >= 0;
}
static bool
EmitDefaults(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
JS_ASSERT(pn->isKind(PNK_ARGSBODY));
ParseNode *pnlast = pn->last();
unsigned ndefaults = 0;
for (ParseNode *arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) {
if (arg->pn_expr)
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));
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.
for (ParseNode *arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) {
if (!arg->pn_expr)
continue;
SET_JUMP_OFFSET(pc, bce->offset() - top);
pc += JUMP_OFFSET_LEN;
if (!EmitTree(cx, bce, arg->pn_expr))
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;
}
JS_ASSERT(pc == bce->code(top + tableSize));
SET_JUMP_OFFSET(bce->code(top), bce->offset() - top);
return true;
}
JSBool
frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
@ -5881,7 +5930,50 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
case PNK_ARGSBODY:
{
JSFunction *fun = bce->sc->fun();
ParseNode *pnlast = pn->last();
if (fun->hasDefaults()) {
if (pnlast->pn_xflags & PNX_DESTRUCT) {
JS_ASSERT(pnlast->pn_head->isKind(PNK_SEMI));
// Defaults must be able to access destructured arguments, so do
// that now.
if (!EmitTree(cx, bce, pnlast->pn_head))
return false;
}
ParseNode *rest = NULL;
if (fun->hasRest()) {
JS_ASSERT(!bce->sc->funArgumentsHasLocalBinding());
// Defaults and rest also 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;
@ -5891,8 +5983,9 @@ frontend::EmitTree(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
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)

View File

@ -614,8 +614,8 @@ struct ParseNode {
ObjectBox *objbox; /* block or regexp object */
};
union {
ParseNode *expr; /* function body, var initializer, or
base object of PNK_DOT */
ParseNode *expr; /* function body, var initializer, argument default,
or base object of PNK_DOT */
Definition *lexdef; /* lexical definition for this use */
};
UpvarCookie cookie; /* upvar cookie with absolute frame

View File

@ -1279,13 +1279,14 @@ LeaveFunction(ParseNode *fn, Parser *parser, PropertyName *funName = NULL,
}
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)) {
@ -1308,6 +1309,11 @@ Parser::functionArguments(ParseNode **listp, bool &hasRest)
/* 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;
/*
@ -1407,6 +1413,22 @@ Parser::functionArguments(ParseNode **listp, bool &hasRest)
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 = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT);
if (!def_expr)
return false;
tc->sc->funbox->node->pn_body->last()->pn_expr = def_expr;
} else if (!hasRest && hasDefaults) {
reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT);
return false;
}
break;
}
@ -1566,11 +1588,13 @@ Parser::functionDef(HandlePropertyName funName, FunctionType type, FunctionSynta
/* 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();
@ -4939,7 +4963,7 @@ class GenexpGuard {
}
void endBody();
bool checkValidBody(ParseNode *pn);
bool checkValidBody(ParseNode *pn, unsigned err);
bool maybeNoteGenerator(ParseNode *pn);
};
@ -4957,14 +4981,14 @@ GenexpGuard::endBody()
* generator expression.
*/
bool
GenexpGuard::checkValidBody(ParseNode *pn)
GenexpGuard::checkValidBody(ParseNode *pn, unsigned err = JSMSG_BAD_GENEXP_BODY)
{
TreeContext *tc = parser->tc;
if (tc->yieldCount > startYieldCount) {
ParseNode *errorNode = tc->yieldNode;
if (!errorNode)
errorNode = pn;
parser->reportErrorNumber(errorNode, JSREPORT_ERROR, JSMSG_BAD_GENEXP_BODY, js_yield_str);
parser->reportErrorNumber(errorNode, JSREPORT_ERROR, err, js_yield_str);
return false;
}
@ -5536,6 +5560,24 @@ static const char js_generator_str[] = "generator";
#endif /* JS_HAS_GENERATOR_EXPRS */
#endif /* JS_HAS_GENERATORS */
ParseNode *
Parser::assignExprWithoutYield(unsigned msg)
{
#ifdef JS_HAS_GENERATORS
GenexpGuard yieldGuard(this);
#endif
ParseNode *res = assignExpr();
if (res) {
#ifdef JS_HAS_GENERATORS
if (!yieldGuard.checkValidBody(res, msg)) {
freeTree(res);
res = NULL;
}
#endif
}
return res;
}
JSBool
Parser::argumentList(ParseNode *listNode)
{

View File

@ -180,6 +180,7 @@ struct Parser : private AutoGCRooter
VarContext varContext = HoistVars);
ParseNode *expr();
ParseNode *assignExpr();
ParseNode *assignExprWithoutYield(unsigned err);
ParseNode *condExpr1();
ParseNode *orExpr1();
ParseNode *andExpr1i();
@ -209,7 +210,7 @@ struct Parser : private AutoGCRooter
* 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);

View File

@ -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);

View File

@ -0,0 +1,34 @@
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}");
function f10(a=12) { return arguments; }
assertEq(f10.toString(), "function f10(a = 12) {\n return arguments;\n}");
function f11(a=(0, c=1)) {}
assertEq(f11.length, 1);
var g = eval("(" + f11 + ")");
assertEq(g.length, 1);

View File

@ -0,0 +1,27 @@
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);
function f2(a=[]) { return a; }
assertEq(f2() !== f2(), true);
function f3(a=function () {}) { return a; }
assertEq(f3() !== f3(), true);
function f4(a=Date) { return a; }
assertEq(f4(), Date);
Date = 0;
assertEq(f4(), 0);
function f5(x=FAIL()) {}; // don't throw
var n = 0;
function f6(a=n++) {}
assertEq(n, 0);
function f7([a, b], A=a, B=b) {
assertEq(A, a);
assertEq(B, b);
}
f7([0, 1]);

View File

@ -0,0 +1,6 @@
load(libdir + "asserts.js");
function die() { throw "x"; }
var ok = true;
function f(a = die()) { ok = false; }
assertThrowsValue(f, "x");

View File

@ -0,0 +1,21 @@
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);
assertThrowsInstanceOf(function () {
eval("function f(a=yield 24) {}");
}, SyntaxError);
assertThrowsInstanceOf(function () {
eval("function f(a={a : 19 + (yield 24).prop}) {}");
}, SyntaxError);
function silly_but_okay(a=(function () { yield 97; })) {}

View File

@ -0,0 +1,33 @@
var x = 'global';
function f(a=x) { // local variable x
var x = 'local';
return a;
}
assertEq(f(), undefined);
function g(f=function () { return ++x; }) { // closes on local variable x
var x = 0;
return f;
}
var gf = g();
assertEq(gf(), 1);
assertEq(gf(), 2);
gf = g();
assertEq(gf(), 1);
function h(f=function (s) { return eval(s); }) { // closes on local scope
var x = 'hlocal';
return f;
}
var hf = h();
assertEq(hf('x'), 'hlocal');
assertEq(hf('f'), hf);
assertEq(hf('var x = 3; x'), 3);
function j(expr, v=eval(expr)) {
return v;
}
assertEq(j("expr"), "expr");
assertEq(j("v"), undefined);
assertEq(j("Array"), Array);
assertEq(j("arguments").length, 1);

View File

@ -0,0 +1,10 @@
function f(a=1, b=2, c=3) { return arguments; }
var args = f();
assertEq(args.length, 0);
assertEq("0" in args, false);
args = f(5, 6);
assertEq(args.length, 2);
assertEq(args[1], 6);
args = f(9, 8, 7, 6, 5);
assertEq(args.length, 5);
assertEq(args[4], 5);

View File

@ -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);

View File

@ -349,3 +349,6 @@ MSG_DEF(JSMSG_PARAMETER_AFTER_REST, 295, 0, JSEXN_SYNTAXERR, "parameter after
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")
MSG_DEF(JSMSG_YIELD_IN_DEFAULT, 301, 0, JSEXN_SYNTAXERR, "yield in default expression")

View File

@ -2202,6 +2202,8 @@ class AutoIdRooter : private AutoGCRooter
#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_HAS_DEFAULTS 0x0400 /* function has at least one default
parameter */
#define JSFUN_FLAGS_MASK 0x07f8 /* overlay JSFUN_* attributes --
bits 12-15 are used internally to

View File

@ -63,6 +63,7 @@ struct JSFunction : public JSObject
} 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(); }
@ -91,6 +92,11 @@ struct JSFunction : public JSObject
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) };

View File

@ -3388,6 +3388,7 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
case JSOP_RSH:
case JSOP_LSH:
case JSOP_URSH:
case JSOP_ACTUALSFILLED:
pushed[0].addType(cx, Type::Int32Type());
break;
case JSOP_FALSE:

View File

@ -2812,6 +2812,12 @@ BEGIN_CASE(JSOP_LOOKUPSWITCH)
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()) {

View File

@ -5533,11 +5533,18 @@ js_DecompileFunction(JSPrinter *jp)
jp->script = script;
#endif
jsbytecode *deftable = NULL;
jsbytecode *defbegin = NULL;
int32_t deflen = 0;
uint16_t defstart = 0;
unsigned nformal = fun->nargs - fun->hasRest();
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);
@ -5548,6 +5555,7 @@ js_DecompileFunction(JSPrinter *jp)
ptrdiff_t todo;
const char *lval;
JS_ASSERT(deflen == 0);
LOCAL_ASSERT(*pc == JSOP_GETARG || *pc == JSOP_GETALIASEDVAR);
pc += js_CodeSpec[*pc].length;
LOCAL_ASSERT(*pc == JSOP_DUP);
@ -5572,10 +5580,49 @@ js_DecompileFunction(JSPrinter *jp)
continue;
}
#undef LOCAL_ASSERT
#endif
if (!QuoteString(&jp->sprinter, param, 0)) {
// Compute default parameters.
if ((*pc == JSOP_REST && pc[1] == JSOP_UNDEFINED) ||
*pc == JSOP_ACTUALSFILLED) {
#define SKIP(pc, op) LOCAL_ASSERT(*pc == op); pc += js_CodeSpec[op].length;
JS_ASSERT(fun->hasDefaults());
JS_ASSERT(deflen == 0);
if (fun->hasRest()) {
SKIP(pc, JSOP_REST);
SKIP(pc, JSOP_UNDEFINED);
JS_ASSERT(*pc == JSOP_SETARG || *pc == JSOP_SETALIASEDVAR);
pc += js_CodeSpec[*pc].length;
SKIP(pc, JSOP_POP);
}
SKIP(pc, JSOP_ACTUALSFILLED);
JS_ASSERT(*pc == JSOP_TABLESWITCH);
defbegin = pc;
deflen = GET_JUMP_OFFSET(pc);
pc += JUMP_OFFSET_LEN;
defstart = GET_JUMP_OFFSET(pc);
pc += JUMP_OFFSET_LEN;
pc += JUMP_OFFSET_LEN; // Skip high
deftable = pc;
pc = defbegin + deflen;
if (fun->hasRest()) {
SKIP(pc, JSOP_SETARG);
SKIP(pc, JSOP_POP);
}
#undef SKIP
}
#undef LOCAL_ASSERT
if (fun->hasDefaults() && deflen && i >= defstart && !isRest) {
#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
unsigned exprlength = caseend - casestart - js_CodeSpec[JSOP_POP].length;
if (!DecompileCode(jp, script, casestart, exprlength, 0))
return JS_FALSE;
} else if (!QuoteString(&jp->sprinter, param, 0)) {
ok = JS_FALSE;
break;
}

View File

@ -517,3 +517,5 @@ 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)

View File

@ -25,7 +25,7 @@ namespace js {
* and saved versions. If deserialization fails, the data should be
* invalidated if possible.
*/
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 115);
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 116);
class XDRBuffer {
public: