Change catch clauses to use lexical scope, etc. (336379, r=mrbkap).

This commit is contained in:
brendan%mozilla.org 2006-08-15 07:03:14 +00:00
parent 8ea9c86502
commit 33cfdbad7e
10 changed files with 286 additions and 284 deletions

View File

@ -129,7 +129,7 @@ const char XUL_FASTLOAD_FILE_BASENAME[] = "XUL";
// Increase the subtractor when changing version, say when changing the
// (opaque to FastLoad code) format of JS script, function, regexp, etc.
// XDR serializations.
#define XUL_FASTLOAD_FILE_VERSION (0xfeedbeef - 15)
#define XUL_FASTLOAD_FILE_VERSION (0xfeedbeef - 16)
#define XUL_SERIALIZATION_BUFFER_SIZE (64 * 1024)
#define XUL_DESERIALIZATION_BUFFER_SIZE (8 * 1024)

View File

@ -248,7 +248,7 @@ MSG_DEF(JSMSG_NEWREGEXP_FLAGGED, 165, 0, JSEXN_TYPEERR, "can't supply flags
MSG_DEF(JSMSG_RESERVED_SLOT_RANGE, 166, 0, JSEXN_RANGEERR, "reserved slot index out of range")
MSG_DEF(JSMSG_CANT_DECODE_PRINCIPALS, 167, 0, JSEXN_INTERNALERR, "can't decode JSPrincipals")
MSG_DEF(JSMSG_CANT_SEAL_OBJECT, 168, 1, JSEXN_ERR, "can't seal {0} objects")
MSG_DEF(JSMSG_UNUSED169, 169, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_TOO_MANY_CATCH_VARS, 169, 0, JSEXN_SYNTAXERR, "too many catch variables")
MSG_DEF(JSMSG_BAD_XML_MARKUP, 170, 0, JSEXN_SYNTAXERR, "invalid XML markup")
MSG_DEF(JSMSG_BAD_XML_CHARACTER, 171, 0, JSEXN_SYNTAXERR, "illegal XML character")
MSG_DEF(JSMSG_BAD_DEFAULT_XML_NAMESPACE,172,0,JSEXN_SYNTAXERR, "invalid default XML namespace")

View File

@ -1208,15 +1208,29 @@ js_InStatement(JSTreeContext *tc, JSStmtType type)
}
JSBool
js_InCatchBlock(JSTreeContext *tc, JSAtom *atom)
js_IsGlobalReference(JSTreeContext *tc, JSAtom *atom, JSBool *loopyp)
{
JSStmtInfo *stmt;
JSObject *obj;
JSScope *scope;
*loopyp = JS_FALSE;
for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
if (stmt->type == STMT_CATCH && stmt->atom == atom)
return JS_TRUE;
if (stmt->type == STMT_WITH)
return JS_FALSE;
if (STMT_IS_LOOP(stmt)) {
*loopyp = JS_TRUE;
continue;
}
if (stmt->flags & SIF_SCOPE) {
obj = ATOM_TO_OBJECT(stmt->atom);
JS_ASSERT(LOCKED_OBJ_GET_CLASS(obj) == &js_BlockClass);
scope = OBJ_SCOPE(obj);
if (SCOPE_GET_PROPERTY(scope, ATOM_TO_JSID(atom)))
return JS_FALSE;
}
}
return JS_FALSE;
return JS_TRUE;
}
void
@ -1344,7 +1358,6 @@ EmitNonLocalJumpFixup(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
break;
case STMT_WITH:
case STMT_CATCH:
/* There's a With object on the stack that we need to pop. */
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
@ -1502,48 +1515,46 @@ js_DefineCompileTimeConstant(JSContext *cx, JSCodeGenerator *cg, JSAtom *atom,
* Find a lexically scoped variable (one declared by let, catch, or an array
* comprehension) named by atom, looking in tc's compile-time scopes.
*
* Return null on error. If atom is found, return the statement info record
* in which it was found directly, and set *slotp to its stack slot (if any).
* If atom is not found, return &LL_NOT_FOUND.
* If a WITH statement is reached along the scope stack, return its statement
* info record, so callers can tell that atom is ambiguous. If atom is found,
* set *slotp to its stack slot, and return the statement info record in which
* it was found directly. Otherwise (atom was not found and no WITH statement
* was reached) return null.
*/
static JSStmtInfo LL_NOT_FOUND;
static JSStmtInfo *
LexicalLookup(JSContext *cx, JSTreeContext *tc, JSAtom *atom, jsint *slotp)
LexicalLookup(JSTreeContext *tc, JSAtom *atom, jsint *slotp)
{
JSStmtInfo *stmt;
JSObject *obj, *pobj;
JSProperty *prop;
JSObject *obj;
JSScope *scope;
JSScopeProperty *sprop;
jsval v;
*slotp = -1;
for (stmt = tc->topScopeStmt; stmt; stmt = stmt->downScope) {
if (stmt->type == STMT_WITH)
return stmt;
if (stmt->type == STMT_CATCH) {
if (stmt->atom == atom)
return stmt;
continue;
}
JS_ASSERT(stmt->flags & SIF_SCOPE);
obj = ATOM_TO_OBJECT(stmt->atom);
if (!js_LookupProperty(cx, obj, ATOM_TO_JSID(atom), &pobj, &prop))
return NULL;
if (prop) {
if (pobj != obj) {
stmt = &LL_NOT_FOUND;
} else {
sprop = (JSScopeProperty *) prop;
JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID);
*slotp = OBJ_BLOCK_DEPTH(cx, obj) + sprop->shortid;
}
OBJ_DROP_PROPERTY(cx, pobj, prop);
JS_ASSERT(LOCKED_OBJ_GET_CLASS(obj) == &js_BlockClass);
scope = OBJ_SCOPE(obj);
sprop = SCOPE_GET_PROPERTY(scope, ATOM_TO_JSID(atom));
if (sprop) {
JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID);
/*
* Use LOCKED_OBJ_GET_SLOT since we know obj is single-threaded
* and owned by this compiler activation.
*/
v = LOCKED_OBJ_GET_SLOT(obj, JSSLOT_BLOCK_DEPTH);
JS_ASSERT(JSVAL_IS_INT(v) && JSVAL_TO_INT(v) >= 0);
*slotp = JSVAL_TO_INT(v) + sprop->shortid;
return stmt;
}
}
return &LL_NOT_FOUND;
return NULL;
}
JSBool
@ -1575,13 +1586,9 @@ js_LookupCompileTimeConstant(JSContext *cx, JSCodeGenerator *cg, JSAtom *atom,
obj = fp->varobj;
if (obj == fp->scopeChain) {
/* XXX this will need revising when 'let const' is added. */
stmt = LexicalLookup(cx, &cg->treeContext, atom, &slot);
if (!stmt)
return JS_FALSE;
if (stmt != &LL_NOT_FOUND) {
fp = fp->down;
continue;
}
stmt = LexicalLookup(&cg->treeContext, atom, &slot);
if (stmt)
return JS_TRUE;
ATOM_LIST_SEARCH(ale, &cg->constList, atom);
if (ale) {
@ -1871,17 +1878,10 @@ BindNameToSlot(JSContext *cx, JSTreeContext *tc, JSParseNode *pn)
* block-locals.
*/
atom = pn->pn_atom;
stmt = LexicalLookup(cx, tc, atom, &slot);
if (!stmt)
return JS_FALSE;
if (stmt != &LL_NOT_FOUND) {
stmt = LexicalLookup(tc, atom, &slot);
if (stmt) {
if (stmt->type == STMT_WITH)
return JS_TRUE;
if (stmt->type == STMT_CATCH) {
JS_ASSERT(stmt->atom == atom);
return JS_TRUE;
}
JS_ASSERT(stmt->flags & SIF_SCOPE);
JS_ASSERT(slot >= 0);
@ -4314,18 +4314,17 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
case TOK_TRY:
{
ptrdiff_t start, end, catchStart, guardJump, finallyCatch;
JSParseNode *lastCatch;
ptrdiff_t start, end, catchJump, catchStart, finallyCatch;
intN depth;
JSParseNode *lastCatch;
/* Quell GCC overwarnings. */
end = catchStart = guardJump = finallyCatch = -1;
catchJump = catchStart = finallyCatch = -1;
/*
* Push stmtInfo to track jumps-over-catches and gosubs-to-finally
* for later fixup.
*
* When a finally block is `active' (STMT_FINALLY on the treeContext),
* When a finally block is 'active' (STMT_FINALLY on the treeContext),
* non-local jumps (including jumps-over-catches) result in a GOSUB
* being written into the bytecode stream and fixed-up later (c.f.
* EmitBackPatchOp and BackPatch).
@ -4339,7 +4338,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
* an unbalanced state, and this imbalance causes problems with things
* like function invocation later on.
*
* To fix this, we compute the `balanced' stack depth upon try entry,
* To fix this, we compute the 'balanced' stack depth upon try entry,
* and then restore the stack to this depth when we hit the first catch
* or finally block. We can't just zero the stack, because things like
* for/in and with that are active upon entry to the block keep state
@ -4362,12 +4361,15 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
&GOSUBS(stmtInfo));
if (jmp < 0)
return JS_FALSE;
/* JSOP_RETSUB pops the return pc-index, balancing the stack. */
cg->stackDepth = depth;
}
/* Emit (hidden) jump over catch and/or finally. */
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &CATCHJUMPS(stmtInfo));
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &catchJump);
if (jmp < 0)
return JS_FALSE;
@ -4377,6 +4379,8 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
pn2 = pn->pn_kid2;
lastCatch = NULL;
if (pn2) {
jsint count = -1; /* previous catch block's population */
catchStart = end;
/*
@ -4402,99 +4406,50 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
* thrown from catch{} blocks.
*/
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
JSStmtInfo stmtInfo2;
JSParseNode *catchHead;
ptrdiff_t catchNote;
ptrdiff_t guardJump, catchNote;
if (!UpdateLineNumberNotes(cx, cg, pn3))
return JS_FALSE;
if (guardJump != -1) {
guardJump = GUARDJUMP(stmtInfo);
if (guardJump == -1) {
/* Set stack to original depth (see SETSP comment above). */
EMIT_UINT16_IMM_OP(JSOP_SETSP, (jsatomid)depth);
cg->stackDepth = depth;
} else {
JS_ASSERT(cg->stackDepth == depth);
/* Fix up and clean up previous catch block. */
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, guardJump);
/* Compensate for the [leavewith]. */
cg->stackDepth++;
JS_ASSERT((uintN) cg->stackDepth <= cg->maxStackDepth);
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0) {
/*
* Emit an unbalanced [leaveblock] for the previous catch,
* whose block object count is saved below.
*/
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
}
} else {
/* Set stack to original depth (see SETSP comment above). */
EMIT_UINT16_IMM_OP(JSOP_SETSP, (jsatomid)depth);
cg->stackDepth = depth;
JS_ASSERT(count >= 0);
EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
}
/* Non-negative catchNote offset is length of catchguard. */
catchNote = js_NewSrcNote2(cx, cg, SRC_CATCH, 0);
if (catchNote < 0 ||
js_Emit1(cx, cg, JSOP_NOP) < 0) {
return JS_FALSE;
}
/* Construct the scope holder and push it on. */
ale = js_IndexAtom(cx, CLASS_ATOM(cx, Object), &cg->atomList);
if (!ale)
return JS_FALSE;
EMIT_ATOM_INDEX_OP(JSOP_NAME, ALE_INDEX(ale));
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0 ||
js_Emit1(cx, cg, JSOP_NEWINIT) < 0 ||
js_Emit1(cx, cg, JSOP_EXCEPTION) < 0) {
return JS_FALSE;
}
/* initcatchvar <atomIndex> */
catchHead = pn3->pn_kid1;
ale = js_IndexAtom(cx, catchHead->pn_atom, &cg->atomList);
if (!ale)
return JS_FALSE;
EMIT_ATOM_INDEX_OP(JSOP_INITCATCHVAR, ALE_INDEX(ale));
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_ENTERWITH) < 0) {
return JS_FALSE;
}
/* boolean_expr */
if (pn3->pn_kid2) {
ptrdiff_t guardStart = CG_OFFSET(cg);
if (!js_EmitTree(cx, cg, pn3->pn_kid2))
return JS_FALSE;
if (!js_SetSrcNoteOffset(cx, cg, catchNote, 0,
CG_OFFSET(cg) - guardStart)) {
return JS_FALSE;
}
/* ifeq <next block> */
guardJump = EmitJump(cx, cg, JSOP_IFEQ, 0);
if (guardJump < 0)
return JS_FALSE;
}
/* Emit catch block. */
js_PushStatement(&cg->treeContext, &stmtInfo2, STMT_CATCH,
CG_OFFSET(cg));
stmtInfo2.atom = catchHead->pn_atom;
if (!js_EmitTree(cx, cg, pn3->pn_kid3))
return JS_FALSE;
if (!js_PopStatementCG(cx, cg))
return JS_FALSE;
/*
* Jump over the remaining catch blocks.
* This counts as a non-local jump, so do the finally thing.
* Annotate the JSOP_ENTERBLOCK that's about to be generated
* by the call to js_EmitTree immediately below. Save this
* source note's index in stmtInfo for use by the TOK_CATCH:
* case, where the length of the catch guard is set as the
* note's offset.
*/
/* leavewith, annotated so the decompiler knows to pop */
off = cg->stackDepth - 1;
if (js_NewSrcNote2(cx, cg, SRC_CATCH, off) < 0 ||
js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0) {
catchNote = js_NewSrcNote2(cx, cg, SRC_CATCH, 0);
if (catchNote < 0)
return JS_FALSE;
CATCHNOTE(stmtInfo) = catchNote;
/*
* Emit the lexical scope and catch body. Save the catch's
* block object population via count, for use when targeting
* guardJump at the next catch (the guard mismatch case).
*/
JS_ASSERT(pn3->pn_type == TOK_LEXICALSCOPE);
count = OBJ_BLOCK_COUNT(cx, ATOM_TO_OBJECT(pn3->pn_atom));
if (!js_EmitTree(cx, cg, pn3))
return JS_FALSE;
}
/* gosub <finally>, if required */
if (pn->pn_kid3) {
@ -4506,15 +4461,21 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
cg->stackDepth = depth;
}
/* This will get fixed up to jump to after catch/finally. */
/*
* Jump over the remaining catch blocks. This will get fixed
* up to jump to after catch/finally.
*/
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH,
&CATCHJUMPS(stmtInfo));
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &catchJump);
if (jmp < 0)
return JS_FALSE;
lastCatch = pn3; /* save pointer to last catch */
/*
* Save a pointer to the last catch node to handle try-finally
* and try-catch(guard)-finally special cases.
*/
lastCatch = pn3->pn_expr;
}
}
@ -4546,7 +4507,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
* stack budget.
*/
if (lastCatch && lastCatch->pn_kid2)
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, guardJump);
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, GUARDJUMP(stmtInfo));
EMIT_UINT16_IMM_OP(JSOP_SETSP, (jsatomid)depth);
cg->stackDepth = depth;
@ -4640,7 +4601,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
}
/* Fix up the end-of-try/catch jumps to come here. */
if (!BackPatch(cx, cg, CATCHJUMPS(stmtInfo), CG_NEXT(cg), JSOP_GOTO))
if (!BackPatch(cx, cg, catchJump, CG_NEXT(cg), JSOP_GOTO))
return JS_FALSE;
/*
@ -4666,6 +4627,51 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
break;
}
case TOK_CATCH:
{
ptrdiff_t guardJump;
stmt = cg->treeContext.topStmt;
JS_ASSERT(stmt->type == STMT_BLOCK);
stmt->type = STMT_CATCH;
stmt = stmt->down;
JS_ASSERT(stmt->type == STMT_TRY || stmt->type == STMT_FINALLY);
/* Pick up the pending exception and bind it to the catch variable. */
if (js_Emit1(cx, cg, JSOP_EXCEPTION) < 0)
return JS_FALSE;
EMIT_UINT16_IMM_OP(JSOP_INITCATCHVAR, pn->pn_kid1->pn_slot);
/* Emit the guard expression, if there is one. */
if (pn->pn_kid2) {
ptrdiff_t guardStart = CG_OFFSET(cg);
if (!js_EmitTree(cx, cg, pn->pn_kid2))
return JS_FALSE;
if (!js_SetSrcNoteOffset(cx, cg, CATCHNOTE(*stmt), 0,
CG_OFFSET(cg) - guardStart)) {
return JS_FALSE;
}
/* ifeq <next block> */
guardJump = EmitJump(cx, cg, JSOP_IFEQ, 0);
if (guardJump < 0)
return JS_FALSE;
GUARDJUMP(*stmt) = guardJump;
}
/* Emit the catch body. */
if (!js_EmitTree(cx, cg, pn->pn_kid3))
return JS_FALSE;
/*
* Annotate the JSOP_LEAVEBLOCK that will be emitted as we unwind via
* our TOK_LEXICALSCOPE parent, so the decompiler knows to pop.
*/
off = cg->stackDepth;
if (js_NewSrcNote2(cx, cg, SRC_CATCH, off) < 0)
return JS_FALSE;
break;
}
case TOK_VAR:
if (!EmitVariables(cx, cg, pn, JS_FALSE, &noteIndex))
return JS_FALSE;

View File

@ -103,21 +103,25 @@ struct JSStmtInfo {
ptrdiff_t update; /* loop update offset (top if none) */
ptrdiff_t breaks; /* offset of last break in loop */
ptrdiff_t continues; /* offset of last continue in loop */
JSAtom *atom; /* name of LABEL or CATCH var */
JSAtom *atom; /* name of LABEL, or block scope object */
JSStmtInfo *down; /* info for enclosing statement */
JSStmtInfo *downScope; /* next enclosing lexical scope */
};
#define SIF_BODY_BLOCK 0x0001 /* STMT_BLOCK type is a function body */
#define SIF_SCOPE 0x0002 /* This statement contains a scope. */
#define SIF_SCOPE 0x0002 /* statement has its own lexical scope */
#define SIF_BODY_BLOCK 0x0001 /* STMT_BLOCK type is a function body */
/*
* To reuse space in JSStmtInfo, rename breaks and continues for use during
* try/catch/finally code generation and backpatching. To match most common
* use cases, the macro argument is a struct, not a struct pointer.
* use cases, the macro argument is a struct, not a struct pointer. Only a
* loop, switch, or label statement info record can have breaks and continues,
* and only a for loop has an update backpatch chain, so it's safe to overlay
* these for the "trying" JSStmtTypes.
*/
#define CATCHNOTE(stmt) ((stmt).update)
#define GOSUBS(stmt) ((stmt).breaks)
#define CATCHJUMPS(stmt) ((stmt).continues)
#define GUARDJUMP(stmt) ((stmt).continues)
#define AT_TOP_LEVEL(tc) \
(!(tc)->topStmt || ((tc)->topStmt->flags & SIF_BODY_BLOCK))
@ -361,9 +365,13 @@ js_InStatement(JSTreeContext *tc, JSStmtType type);
/* Test whether we're in a with statement. */
#define js_InWithStatement(tc) js_InStatement(tc, STMT_WITH)
/* Test whether we're in a catch block with exception named by atom. */
/*
* Test whether atom refers to a global variable (or is a reference error).
* Return true in *loopyp if any loops enclose the lexical reference, false
* otherwise.
*/
extern JSBool
js_InCatchBlock(JSTreeContext *tc, JSAtom *atom);
js_IsGlobalReference(JSTreeContext *tc, JSAtom *atom, JSBool *loopyp);
/*
* Push the C-stack-allocated struct at stmt onto the stmtInfo stack.

View File

@ -1981,14 +1981,6 @@ InternNonIntElementId(JSContext *cx, jsval idval, jsid *idp)
# undef JS_THREADED_INTERP
#endif
typedef enum JSOpLength {
#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
op##_LENGTH = length,
#include "jsopcode.tbl"
#undef OPDEF
JSOP_LIMIT_LENGTH
} JSOpLength;
JSBool
js_Interpret(JSContext *cx, jsbytecode *pc, jsval *result)
{
@ -4256,7 +4248,6 @@ interrupt:
case JSOP_GETMETHOD: goto do_JSOP_GETMETHOD;
case JSOP_SETMETHOD: goto do_JSOP_SETMETHOD;
#endif
case JSOP_INITCATCHVAR: goto do_JSOP_INITCATCHVAR;
case JSOP_NAMEDFUNOBJ: goto do_JSOP_NAMEDFUNOBJ;
case JSOP_NUMBER: goto do_JSOP_NUMBER;
case JSOP_OBJECT: goto do_JSOP_OBJECT;
@ -5497,41 +5488,16 @@ interrupt:
/* let the code at out try to catch the exception. */
goto out;
BEGIN_LITOPX_CASE(JSOP_INITCATCHVAR, 0)
/* Load the value into rval, while keeping it live on stack. */
JS_ASSERT(sp - fp->spbase >= 2);
rval = FETCH_OPND(-1);
/* Get the immediate catch variable name into id. */
id = ATOM_TO_JSID(atom);
/* Find the object being initialized at top of stack. */
lval = FETCH_OPND(-2);
JS_ASSERT(JSVAL_IS_OBJECT(lval));
obj = JSVAL_TO_OBJECT(lval);
SAVE_SP_AND_PC(fp);
BEGIN_CASE(JSOP_INITCATCHVAR)
/*
* It's possible for an evil script to substitute a random object
* for the new object. Check to make sure that we don't override a
* readonly property with the below OBJ_DEFINE_PROPERTY.
* The stack must have a block with at least one local slot below
* the exception object.
*/
ok = OBJ_GET_ATTRIBUTES(cx, obj, id, NULL, &attrs);
if (!ok)
goto out;
if (!(attrs & (JSPROP_READONLY | JSPROP_PERMANENT |
JSPROP_GETTER | JSPROP_SETTER))) {
/* Define obj[id] to contain rval and to be permanent. */
ok = OBJ_DEFINE_PROPERTY(cx, obj, id, rval, NULL, NULL,
JSPROP_PERMANENT, NULL);
if (!ok)
goto out;
}
/* Now that we're done with rval, pop it. */
sp--;
END_LITOPX_CASE(JSOP_INITCATCHVAR)
JS_ASSERT(sp - fp->spbase >= 2);
slot = GET_UINT16(pc);
JS_ASSERT(slot + 1 < (uintN)depth);
fp->spbase[slot] = POP_OPND();
END_CASE(JSOP_INITCATCHVAR)
BEGIN_CASE(JSOP_INSTANCEOF)
SAVE_SP_AND_PC(fp);
@ -5994,7 +5960,11 @@ interrupt:
JS_ASSERT(op == JSOP_LEAVEBLOCKEXPR
? fp->spbase + OBJ_BLOCK_DEPTH(cx, obj) == sp - 1
: fp->spbase + OBJ_BLOCK_DEPTH(cx, obj) == sp);
*chainp = OBJ_GET_PARENT(cx, obj);
JS_ASSERT(chainp != &fp->blockChain ||
!*chainp ||
OBJ_GET_CLASS(cx, *chainp) == &js_BlockClass);
}
END_CASE(JSOP_LEAVEBLOCK)

View File

@ -793,9 +793,7 @@ DecompileSwitch(SprintStack *ss, TableEntry *table, uintN tableLength,
jp->indent -= 4;
}
caseExprOff = isCondSwitch
? (ptrdiff_t) js_CodeSpec[JSOP_CONDSWITCH].length
: 0;
caseExprOff = isCondSwitch ? JSOP_CONDSWITCH_LENGTH : 0;
for (i = 0; i < tableLength; i++) {
off = table[i].offset;
@ -940,7 +938,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
#endif
jsval val;
static const char catch_cookie[] = "/*CATCH*/";
static const char finally_cookie[] = "/*FINALLY*/";
static const char iter_cookie[] = "/*ITER*/";
static const char with_cookie[] = "/*WITH*/";
@ -1156,55 +1153,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
js_printf(jp, "\t}\n");
break;
case SRC_CATCH:
jp->indent -= 4;
sn = js_GetSrcNote(jp->script, pc);
pc += oplen;
js_printf(jp, "\t} catch (");
LOCAL_ASSERT(*pc == JSOP_NAME);
pc += js_CodeSpec[JSOP_NAME].length;
LOCAL_ASSERT(*pc == JSOP_PUSHOBJ);
pc += js_CodeSpec[JSOP_PUSHOBJ].length;
LOCAL_ASSERT(*pc == JSOP_NEWINIT);
pc += js_CodeSpec[JSOP_NEWINIT].length;
LOCAL_ASSERT(*pc == JSOP_EXCEPTION);
pc += js_CodeSpec[JSOP_EXCEPTION].length;
if (*pc == JSOP_LITOPX) {
atomIndex = GET_LITERAL_INDEX(pc);
pc += 1 + LITERAL_INDEX_LEN;
LOCAL_ASSERT(*pc == JSOP_INITCATCHVAR);
++pc;
} else {
LOCAL_ASSERT(*pc == JSOP_INITCATCHVAR);
atomIndex = GET_ATOM_INDEX(pc);
pc += js_CodeSpec[JSOP_INITCATCHVAR].length;
}
atom = js_GetAtom(cx, &jp->script->atomMap, atomIndex);
rval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
if (!rval)
return JS_FALSE;
RETRACT(&ss->sprinter, rval);
js_printf(jp, "%s", rval);
LOCAL_ASSERT(*pc == JSOP_ENTERWITH);
pc += js_CodeSpec[JSOP_ENTERWITH].length;
len = js_GetSrcNoteOffset(sn, 0);
if (len) {
js_printf(jp, " if ");
DECOMPILE_CODE(pc, len);
js_printf(jp, "%s", POP_STR());
pc += len;
LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX);
pc += js_CodeSpec[*pc].length;
}
js_printf(jp, ") {\n");
jp->indent += 4;
todo = Sprint(&ss->sprinter, catch_cookie);
len = 0;
break;
case SRC_FUNCDEF:
atom = js_GetAtom(cx, &jp->script->atomMap,
(jsatomid) js_GetSrcNoteOffset(sn, 0));
@ -1327,9 +1275,9 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
case JSOP_EXCEPTION:
/*
* The only other JSOP_EXCEPTION cases occur as part of a code
* sequence that follows a SRC_CATCH-annotated JSOP_NOP or
* precedes a SRC_HIDDEN-annotated JSOP_POP emitted when
* returning from within a finally clause.
* sequence that follows a SRC_CATCH-annotated JSOP_ENTERBLOCK
* or that precedes a SRC_HIDDEN-annotated JSOP_POP emitted
* when returning from within a finally clause.
*/
sn = js_GetSrcNote(jp->script, pc);
LOCAL_ASSERT(sn && SN_TYPE(sn) == SRC_HIDDEN);
@ -1444,11 +1392,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
if (sn && SN_TYPE(sn) == SRC_HIDDEN)
break;
rval = POP_STR();
if (sn && SN_TYPE(sn) == SRC_CATCH) {
LOCAL_ASSERT(strcmp(rval, catch_cookie) == 0);
LOCAL_ASSERT((uintN) js_GetSrcNoteOffset(sn, 0) == ss->top);
break;
}
LOCAL_ASSERT(strcmp(rval, with_cookie) == 0);
jp->indent -= 4;
js_printf(jp, "\t}\n");
@ -1471,6 +1414,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
return JS_FALSE;
}
/* From here on, control must flow through enterblock_out. */
for (sprop = OBJ_SCOPE(obj)->lastProp; sprop;
sprop = sprop->parent) {
if (!(sprop->flags & SPROP_HAS_SHORTID))
@ -1485,15 +1429,54 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
if (!rval ||
!PushOff(ss, STR2OFF(&ss->sprinter, rval), op)) {
ok = JS_FALSE;
break;
goto enterblock_out;
}
}
sn = js_GetSrcNote(jp->script, pc);
if (sn && SN_TYPE(sn) == SRC_CATCH) {
jp->indent -= 4;
js_printf(jp, "\t} catch (");
pc += oplen;
LOCAL_ASSERT(*pc == JSOP_EXCEPTION);
pc += JSOP_EXCEPTION_LENGTH;
LOCAL_ASSERT(*pc == JSOP_INITCATCHVAR);
i = GET_UINT16(pc);
pc += JSOP_INITCATCHVAR_LENGTH;
str = ATOM_TO_STRING(atomv[i]);
rval = QuoteString(&ss->sprinter, str, 0);
if (!rval) {
ok = JS_FALSE;
goto enterblock_out;
}
RETRACT(&ss->sprinter, rval);
js_printf(jp, "%s", rval);
len = js_GetSrcNoteOffset(sn, 0);
if (len) {
js_printf(jp, " if ");
ok = Decompile(ss, pc, len);
if (!ok)
goto enterblock_out;
js_printf(jp, "%s", POP_STR());
pc += len;
LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX);
pc += js_CodeSpec[*pc].length;
}
js_printf(jp, ") {\n");
jp->indent += 4;
len = 0;
}
todo = -2;
enterblock_out:
if (atomv != smallv)
JS_free(cx, atomv);
if (!ok)
return JS_FALSE;
todo = -2;
break;
}
@ -1504,9 +1487,12 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
sn = js_GetSrcNote(jp->script, pc);
todo = -2;
if (sn && SN_TYPE(sn) == SRC_HIDDEN) {
if (sn) {
JS_ASSERT(op == JSOP_LEAVEBLOCK);
break;
if (SN_TYPE(sn) == SRC_HIDDEN)
break;
LOCAL_ASSERT(SN_TYPE(sn) == SRC_CATCH);
LOCAL_ASSERT((uintN)js_GetSrcNoteOffset(sn, 0) == ss->top);
}
if (op == JSOP_LEAVEBLOCKEXPR)
rval = POP_STR();

View File

@ -60,6 +60,14 @@ typedef enum JSOp {
JSOP_LIMIT
} JSOp;
typedef enum JSOpLength {
#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
op##_LENGTH = length,
#include "jsopcode.tbl"
#undef OPDEF
JSOP_LIMIT_LENGTH
} JSOpLength;
/*
* JS bytecode formats.
*/

View File

@ -274,10 +274,10 @@ OPDEF(JSOP_ANONFUNOBJ, 128, "anonfunobj", NULL, 3, 0, 1, 13, JOF_CONST)
OPDEF(JSOP_NAMEDFUNOBJ, 129, "namedfunobj", NULL, 3, 0, 1, 13, JOF_CONST)
/*
* Like JSOP_INITPROP, but specialized to make a DontDelete property for ECMA
* Edition 3 catch variables.
* Like JSOP_SETLOCAL, but specialized to avoid requiring JSOP_POP immediately
* after to throw away the exception value.
*/
OPDEF(JSOP_INITCATCHVAR,130, "initcatchvar",NULL, 3, 1, 0, 0, JOF_CONST)
OPDEF(JSOP_INITCATCHVAR,130, "initcatchvar",NULL, 3, 1, 0, 0, JOF_LOCAL|JOF_NAME|JOF_SET)
/* ECMA-mandated parenthesization opcode, which nulls the reference base register, obj; see jsinterp.c. */
OPDEF(JSOP_GROUP, 131, "group", NULL, 1, 0, 0, 0, JOF_BYTE)
@ -416,7 +416,7 @@ OPDEF(JSOP_TYPEOFEXPR, 198,js_typeof_str, NULL, 1, 1, 1, 10, JOF_BYTE|J
OPDEF(JSOP_ENTERBLOCK, 199,"enterblock", NULL, 3, 0, 0, 0, JOF_CONST)
OPDEF(JSOP_LEAVEBLOCK, 200,"leaveblock", NULL, 3, 0, 0, 0, JOF_UINT16)
OPDEF(JSOP_GETLOCAL, 201,"getlocal", NULL, 3, 0, 1, 13, JOF_LOCAL|JOF_NAME)
OPDEF(JSOP_SETLOCAL, 202,"setlocal", NULL, 3, 1, 1, 1, JOF_LOCAL|JOF_NAME|JOF_SET|JOF_ASSIGNING|JOF_DETECTING)
OPDEF(JSOP_SETLOCAL, 202,"setlocal", NULL, 3, 1, 1, 1, JOF_LOCAL|JOF_NAME|JOF_SET)
OPDEF(JSOP_INCLOCAL, 203,"inclocal", NULL, 3, 0, 1, 10, JOF_LOCAL|JOF_NAME|JOF_INC)
OPDEF(JSOP_DECLOCAL, 204,"declocal", NULL, 3, 0, 1, 10, JOF_LOCAL|JOF_NAME|JOF_DEC)
OPDEF(JSOP_LOCALINC, 205,"localinc", NULL, 3, 0, 1, 10, JOF_LOCAL|JOF_NAME|JOF_INC|JOF_POST)

View File

@ -2922,6 +2922,9 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
lastCatch = NULL;
do {
JSParseNode *pnblock;
BindData data;
/* Check for another catch after unconditional catch. */
if (lastCatch && !lastCatch->pn_kid2) {
js_ReportCompileErrorNumber(cx, ts,
@ -2930,6 +2933,15 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
return NULL;
}
/*
* Create a lexical scope node around the whole catch clause,
* including the head.
*/
pnblock = PushLexicalScope(cx, ts, tc, &stmtInfo);
if (!pnblock)
return NULL;
stmtInfo.type = STMT_CATCH;
/*
* Legal catch forms are:
* catch (lhs)
@ -2940,21 +2952,38 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
pn2 = NewParseNode(cx, ts, PN_TERNARY, tc);
if (!pn2)
return NULL;
pnblock->pn_expr = pn2;
/*
* We use a PN_NAME for the variable node, in pn2->pn_kid1.
* If there is a guard expression, it goes in pn2->pn_kid2.
* Contrary to ECMA Ed. 3, the catch variable is lexically
* scoped, not a property of a new Object instance. This is
* an intentional change that anticipates ECMA Ed. 4.
*/
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_CATCH_IDENTIFIER);
label = CURRENT_TOKEN(ts).t_atom;
data.pn = NULL;
data.ts = ts;
data.obj = ATOM_TO_OBJECT(pnblock->pn_atom);
data.op = JSOP_NOP;
data.binder = BindLet;
data.u.let.index = 0;
data.u.let.overflow = JSMSG_TOO_MANY_CATCH_VARS;
if (!data.binder(cx, &data, label, tc))
return NULL;
pn3 = NewParseNode(cx, ts, PN_NAME, tc);
if (!pn3)
return NULL;
pn3->pn_atom = CURRENT_TOKEN(ts).t_atom;
pn3->pn_atom = label;
pn3->pn_expr = NULL;
pn3->pn_slot = 0;
pn2->pn_kid1 = pn3;
pn2->pn_kid2 = NULL;
#if JS_HAS_CATCH_GUARD
/*
* We use 'catch (x if x === 5)' (not 'catch (x : x === 5)')
@ -2967,19 +2996,16 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
return NULL;
}
#endif
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH);
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH);
js_PushStatement(tc, &stmtInfo, STMT_CATCH, -1);
stmtInfo.atom = pn3->pn_atom;
pn2->pn_kid3 = Statements(cx, ts, tc);
if (!pn2->pn_kid3)
return NULL;
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH);
js_PopStatement(tc);
PN_APPEND(catchList, pn2);
PN_APPEND(catchList, pnblock);
lastCatch = pn2;
} while ((tt = js_GetToken(cx, ts)) == TOK_CATCH);
}
@ -3186,13 +3212,14 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
} else {
if (!stmt) {
/*
* XXX This is a hard case that requires more work. In
* particular, in many cases, we're trying to emit code as
* we go. However, this means that we haven't necessarily
* finished processing all let declarations in the
* implicit top-level block when we emit a reference to
* one of them. For now, punt on this and pretend this is
* a var declaration.
* FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=346749
*
* This is a hard case that requires more work. In particular,
* in many cases, we're trying to emit code as we go. However,
* this means that we haven't necessarily finished processing
* all let declarations in the implicit top-level block when
* we emit a reference to one of them. For now, punt on this
* and pretend this is a var declaration.
*/
CURRENT_TOKEN(ts).type = TOK_VAR;
CURRENT_TOKEN(ts).t_op = JSOP_DEFVAR;
@ -5455,22 +5482,17 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
} else {
JSAtomListElement *ale;
JSStackFrame *fp;
JSStmtInfo *stmt;
JSBool loopy;
/* Measure optimizable global variable uses. */
ATOM_LIST_SEARCH(ale, &tc->decls, pn->pn_atom);
if (ale &&
!(fp = cx->fp)->fun &&
fp->scopeChain == fp->varobj &&
!js_InWithStatement(tc) &&
!js_InCatchBlock(tc, pn->pn_atom)) {
js_IsGlobalReference(tc, pn->pn_atom, &loopy)) {
tc->globalUses++;
for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
if (STMT_IS_LOOP(stmt)) {
tc->loopyGlobalUses++;
break;
}
}
if (loopy)
tc->loopyGlobalUses++;
}
}
}

View File

@ -102,9 +102,11 @@ JS_BEGIN_EXTERN_C
* pn_right: body
* TOK_THROW unary pn_op: JSOP_THROW, pn_kid: exception
* TOK_TRY ternary pn_kid1: try block
* pn_kid2: null or TOK_RESERVED list of catch blocks
* pn_kid2: null or TOK_RESERVED list of
* TOK_LEXICALSCOPE nodes, each with pn_expr pointing
* to a TOK_CATCH node
* pn_kid3: null or finally block
* TOK_CATCH ternary pn_kid1: TOK_NAME, TOK_LB, or TOK_LC cath var node
* TOK_CATCH ternary pn_kid1: TOK_NAME, TOK_LB, or TOK_LC catch var node
* (TOK_LB or TOK_LC if destructuring)
* pn_kid2: null or the catch guard expression
* pn_kid3: catch block statements