From 33cfdbad7ef0d732ffb3483dfb97c83b930a5e29 Mon Sep 17 00:00:00 2001 From: "brendan%mozilla.org" Date: Tue, 15 Aug 2006 07:03:14 +0000 Subject: [PATCH] Change catch clauses to use lexical scope, etc. (336379, r=mrbkap). --- .../document/public/nsIXULPrototypeCache.h | 2 +- js/src/js.msg | 2 +- js/src/jsemit.c | 286 +++++++++--------- js/src/jsemit.h | 22 +- js/src/jsinterp.c | 54 +--- js/src/jsopcode.c | 116 ++++--- js/src/jsopcode.h | 8 + js/src/jsopcode.tbl | 8 +- js/src/jsparse.c | 66 ++-- js/src/jsparse.h | 6 +- 10 files changed, 286 insertions(+), 284 deletions(-) diff --git a/content/xul/document/public/nsIXULPrototypeCache.h b/content/xul/document/public/nsIXULPrototypeCache.h index 044a1fe6ac86..c8f3265a63e7 100644 --- a/content/xul/document/public/nsIXULPrototypeCache.h +++ b/content/xul/document/public/nsIXULPrototypeCache.h @@ -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) diff --git a/js/src/js.msg b/js/src/js.msg index 13526db38b2f..fbd3145944a1 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -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") diff --git a/js/src/jsemit.c b/js/src/jsemit.c index f6b3fe2787ac..194288345208 100644 --- a/js/src/jsemit.c +++ b/js/src/jsemit.c @@ -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 */ - 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 */ - 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 , 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 */ + 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, ¬eIndex)) return JS_FALSE; diff --git a/js/src/jsemit.h b/js/src/jsemit.h index 1786a1c34d98..5499f4167a09 100644 --- a/js/src/jsemit.h +++ b/js/src/jsemit.h @@ -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. diff --git a/js/src/jsinterp.c b/js/src/jsinterp.c index 302fa7f0546f..5858086acb49 100644 --- a/js/src/jsinterp.c +++ b/js/src/jsinterp.c @@ -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) diff --git a/js/src/jsopcode.c b/js/src/jsopcode.c index 183080649288..ba74a383d212 100644 --- a/js/src/jsopcode.c +++ b/js/src/jsopcode.c @@ -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(); diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 0c5dcdfb2bc8..a197cda11764 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -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. */ diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index 7a572df9805e..c1a02e2d2896 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -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) diff --git a/js/src/jsparse.c b/js/src/jsparse.c index fb8e30b0a9ff..2cd0de6b40aa 100644 --- a/js/src/jsparse.c +++ b/js/src/jsparse.c @@ -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++; } } } diff --git a/js/src/jsparse.h b/js/src/jsparse.h index 73cb580eb0ec..a211e321c2a4 100644 --- a/js/src/jsparse.h +++ b/js/src/jsparse.h @@ -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