Fix try/catch/finally code generation (350312, r=igor/shaver).

This commit is contained in:
brendan%mozilla.org 2006-08-29 23:15:22 +00:00
Родитель 9ddd848246
Коммит d95a636109
8 изменённых файлов: 151 добавлений и 130 удалений

Просмотреть файл

@ -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 - 16)
#define XUL_FASTLOAD_FILE_VERSION (0xfeedbeef - 18)
#define XUL_SERIALIZATION_BUFFER_SIZE (64 * 1024)
#define XUL_DESERIALIZATION_BUFFER_SIZE (8 * 1024)

Просмотреть файл

@ -45,6 +45,18 @@
JS_BEGIN_EXTERN_C
/*
* Crypto-booleans, not visible to script but used internally by the engine.
*
* JSVAL_HOLE is a useful value for identifying a hole in an array. It's also
* used in the interpreter to represent "no exception pending". In general it
* can be used to represent "no value".
*
* JSVAL_ARETURN is used to throw asynchronous return for generator.close().
*/
#define JSVAL_HOLE BOOLEAN_TO_JSVAL(2)
#define JSVAL_ARETURN BOOLEAN_TO_JSVAL(3)
extern JSClass js_BooleanClass;
extern JSObject *

Просмотреть файл

@ -329,11 +329,10 @@ ReportStatementTooLarge(JSContext *cx, JSCodeGenerator *cg)
Note that backpatch chains would present a problem for BuildSpanDepTable,
which inspects bytecode to build cg->spanDeps on demand, when the first
short jump offset overflows. To solve this temporary problem, we emit a
proxy bytecode (JSOP_BACKPATCH; JSOP_BACKPATCH_PUSH for jumps that push a
result on the interpreter's stack, namely JSOP_GOSUB; or JSOP_BACKPATCH_POP
for branch ops) whose nuses/ndefs counts help keep the stack balanced, but
whose opcode format distinguishes its backpatch delta immediate operand from
a normal jump offset.
proxy bytecode (JSOP_BACKPATCH; JSOP_BACKPATCH_POP for branch ops) whose
nuses/ndefs counts help keep the stack balanced, but whose opcode format
distinguishes its backpatch delta immediate operand from a normal jump
offset.
*/
static int
BalanceJumpTargets(JSJumpTarget **jtp)
@ -1352,7 +1351,7 @@ EmitNonLocalJumpFixup(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
case STMT_FINALLY:
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH_PUSH, &GOSUBS(*stmt));
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &GOSUBS(*stmt));
if (jmp < 0)
return JS_FALSE;
break;
@ -4379,8 +4378,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
if (pn->pn_kid3) {
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH_PUSH,
&GOSUBS(stmtInfo));
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &GOSUBS(stmtInfo));
if (jmp < 0)
return JS_FALSE;
@ -4420,7 +4418,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
*
* If there's no catch block without a catchguard, the last
* <offset to next catch block> points to rethrow code. This
* code will GOSUB to the finally code if appropriate, and is
* code will [gosub] to the finally code if appropriate, and is
* also used for the catch-all trynote for capturing exceptions
* thrown from catch{} blocks.
*/
@ -4438,6 +4436,10 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
/* Fix up and clean up previous catch block. */
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, guardJump);
/* Set cx->throwing to protect cx->exception from the GC. */
if (!js_Emit1(cx, cg, JSOP_THROWING) < 0)
return JS_FALSE;
/*
* Emit an unbalanced [leaveblock] for the previous catch,
* whose block object count is saved below.
@ -4472,12 +4474,11 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
/* gosub <finally>, if required */
if (pn->pn_kid3) {
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH_PUSH,
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH,
&GOSUBS(stmtInfo));
if (jmp < 0)
return JS_FALSE;
JS_ASSERT(cg->stackDepth == depth + 1);
cg->stackDepth = depth;
JS_ASSERT(cg->stackDepth == depth);
}
/*
@ -4499,11 +4500,35 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
}
/*
* We emit a [setsp, exception, gosub, throw] block for rethrowing
* when there's no unguarded catch, and also for running finally code
* while letting an uncaught exception pass through.
* We emit a [setsp][gosub] sequence for running finally code while
* letting an uncaught exception pass thrown from within the try in a
* try-finally. The [gosub] and [retsub] opcodes will take care of
* stacking and rethrowing any exception pending across the finally.
*
* For rethrowing after a try-catch(guard)-finally, we have a problem:
* all the guards have mismatched, leaving cx->exception still set but
* cx->throwing clear, so that no exception appears to be pending for
* [gosub] to stack and [retsub] to rethrow. We must emit a special
* [throwing] opcode in front of the [setsp][gosub] finally sequence.
* This opcode will restore cx->throwing to true before running the
* finally.
*
* For rethrowing after a try-catch(guard) without a finally, we emit
* [throwing] before the [setsp][exception][throw] rethrow sequence.
*/
if (pn->pn_kid3 || (lastCatch && lastCatch->pn_kid2)) {
/*
* Last catch guard jumps to the rethrow code sequence if none
* of the guards match. Target guardJump at the beginning of the
* rethrow sequence, just in case a guard expression throws and
* leaves the stack unbalanced.
*/
if (lastCatch && lastCatch->pn_kid2) {
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, GUARDJUMP(stmtInfo));
if (pn->pn_kid3 && !js_Emit1(cx, cg, JSOP_THROWING) < 0)
return JS_FALSE;
}
/*
* Emit another stack fixup, because the catch could itself
* throw an exception in an unbalanced state, and the finally
@ -4512,51 +4537,25 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
* stack fixup.
*/
finallyCatch = CG_OFFSET(cg);
/*
* Last catch guard jumps to the rethrow code sequence if none
* of the guards match. Target guardJump at the beginning of the
* rethrow sequence, just in case a guard expression throws and
* leaves the stack unbalanced.
*
* What's more, we must jump to the front of the rethrow sequence
* for a second reason: to re-sample the pending exception via the
* [exception] opcode, so that it can be saved across the [gosub].
* See below for case 2 in the comment describing finally clause
* stack budget.
*/
if (lastCatch && lastCatch->pn_kid2)
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, GUARDJUMP(stmtInfo));
EMIT_UINT16_IMM_OP(JSOP_SETSP, (jsatomid)depth);
cg->stackDepth = depth;
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_EXCEPTION) < 0) {
return JS_FALSE;
}
if (pn->pn_kid3) {
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH_PUSH,
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH,
&GOSUBS(stmtInfo));
if (jmp < 0)
return JS_FALSE;
/*
* Exception and retsub pc index are modeled as on the stack.
* Decrease cg->stackDepth by one to account for JSOP_RETSUB
* popping the pc index.
*/
JS_ASSERT(cg->stackDepth == depth + 2);
JS_ASSERT((uintN)cg->stackDepth <= cg->maxStackDepth);
cg->stackDepth = depth + 1;
JS_ASSERT(cg->stackDepth == depth);
JS_ASSERT((uintN)depth <= cg->maxStackDepth);
} else {
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_EXCEPTION) < 0 ||
js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_THROW) < 0) {
return JS_FALSE;
}
}
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_THROW) < 0) {
return JS_FALSE;
}
JS_ASSERT(cg->stackDepth == depth);
}
/*
@ -4568,30 +4567,10 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
return JS_FALSE;
/*
* The stack budget must be balanced at this point. There are
* three cases:
*
* 1. try-finally: we need two more slots for the saved exception
* and the JSOP_RETSUB's return pc index that was pushed by
* the JSOP_GOSUB opcode that called this finally clause.
*
* 2. try-catch(guard)...-catch(guard)-finally: we can't know at
* compile time whether a guarded catch caught the exception,
* so we may need to propagate it via re-throw bytecode when
* the finally returns. In this case, if no guard expression
* evaluates to true, we will jump to the rethrow sequence,
* which re-samples cx->exception using JSOP_EXCEPTION, then
* calls the finally subroutine. So in this case as well as
* in case 1, two more slots will already be on the stack.
*
* 3. try-catch-finally or try-catch(guard)...-catch-finally:
* we need one slot for the JSOP_RETSUB's return pc index.
* The unguarded catch is guaranteed to pop the exception,
* i.e., to "catch the exception" -- so we do not need to
* stack it across the finally in order to propagate it --
* unless the catch block explicitly re-throws it! We can't
* know whether this will happen by static analysis, so we
* must always budget for two slots.
* The stack budget must be balanced at this point. All [gosub]
* calls emitted before this point will push two stack slots, one
* for the pending exception (or JSVAL_HOLE if there is no pending
* exception) and one for the [retsub] pc-index.
*/
JS_ASSERT(cg->stackDepth == depth);
cg->stackDepth += 2;
@ -4609,7 +4588,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
}
/* Restore stack depth budget to its balanced state. */
JS_ASSERT(cg->stackDepth == depth + 1);
JS_ASSERT(cg->stackDepth == depth + 2);
cg->stackDepth = depth;
}
if (!js_PopStatementCG(cx, cg))
@ -6390,40 +6369,3 @@ js_FinishTakingTryNotes(JSContext *cx, JSCodeGenerator *cg, JSTryNote *notes)
notes[count].length = CG_OFFSET(cg);
notes[count].catchStart = 0;
}
#if JS_HAS_GENERATORS
jsbytecode *
js_FindFinallyHandler(JSScript *script, jsbytecode *pc)
{
JSTryNote *tn;
ptrdiff_t off;
tn = script->trynotes;
if (!tn)
return NULL;
off = pc - script->main;
if (off < 0)
return NULL;
JS_ASSERT(tn->catchStart != 0);
do {
if ((jsuword)(off - tn->start) < (jsuword)tn->length) {
/*
* We have a handler, is it finally?
*
* Catch bytecode begins with: JSOP_SETSP JSOP_ENTERBLOCK
* Finally bytecode begins with: JSOP_SETSP JSOP_EXCEPTION
*/
pc = script->main + tn->catchStart;
JS_ASSERT(*pc == JSOP_SETSP);
if (pc[js_CodeSpec[JSOP_SETSP].length] == JSOP_EXCEPTION)
return pc;
JS_ASSERT(pc[js_CodeSpec[JSOP_SETSP].length] == JSOP_ENTERBLOCK);
}
} while ((++tn)->catchStart != 0);
return NULL;
}
#endif

Просмотреть файл

@ -5452,12 +5452,18 @@ interrupt:
END_CASE(JSOP_SETSP)
BEGIN_CASE(JSOP_GOSUB)
JS_ASSERT(cx->exception != JSVAL_HOLE);
lval = cx->throwing ? cx->exception : JSVAL_HOLE;
PUSH(lval);
i = PTRDIFF(pc, script->main, jsbytecode) + JSOP_GOSUB_LENGTH;
len = GET_JUMP_OFFSET(pc);
PUSH(INT_TO_JSVAL(i));
END_VARLEN_CASE
BEGIN_CASE(JSOP_GOSUBX)
JS_ASSERT(cx->exception != JSVAL_HOLE);
lval = cx->throwing ? cx->exception : JSVAL_HOLE;
PUSH(lval);
i = PTRDIFF(pc, script->main, jsbytecode) + JSOP_GOSUBX_LENGTH;
len = GET_JUMPX_OFFSET(pc);
PUSH(INT_TO_JSVAL(i));
@ -5466,6 +5472,19 @@ interrupt:
BEGIN_CASE(JSOP_RETSUB)
rval = POP();
JS_ASSERT(JSVAL_IS_INT(rval));
lval = POP();
if (lval != JSVAL_HOLE) {
/*
* Exception was pending during finally, throw it *before* we
* adjust pc, because pc indexes into script->trynotes. This
* turns out not to be necessary, but it seems clearer. And
* it points out a FIXME: 350509, due to Igor Bukanov.
*/
cx->throwing = JS_TRUE;
cx->exception = lval;
ok = JS_FALSE;
goto out;
}
len = JSVAL_TO_INT(rval);
pc = script->main;
END_VARLEN_CASE
@ -5475,6 +5494,11 @@ interrupt:
cx->throwing = JS_FALSE;
END_CASE(JSOP_EXCEPTION)
BEGIN_CASE(JSOP_THROWING)
JS_ASSERT(!cx->throwing);
cx->throwing = JS_TRUE;
END_CASE(JSOP_THROWING)
BEGIN_CASE(JSOP_THROW)
cx->throwing = JS_TRUE;
cx->exception = POP_OPND();
@ -6108,7 +6132,6 @@ interrupt:
#ifdef JS_THREADED_INTERP
L_JSOP_BACKPATCH:
L_JSOP_BACKPATCH_PUSH:
L_JSOP_BACKPATCH_POP:
#else
default:

Просмотреть файл

@ -123,14 +123,6 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp);
extern JSBool
js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen);
/*
* Special unique value to implement asynchronous return for
* generator.close(). Scripts never see it.
*/
#define JSVAL_ARETURN BOOLEAN_TO_JSVAL(JS_TRUE + 1)
#endif
extern JSClass js_GeneratorClass;

Просмотреть файл

@ -937,7 +937,8 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
jsval val;
int stackDummy;
static const char finally_cookie[] = "/*FINALLY*/";
static const char exception_cookie[] = "/*EXCEPTION*/";
static const char retsub_pc_cookie[] = "/*RETSUB_PC*/";
static const char iter_cookie[] = "/*ITER*/";
static const char with_cookie[] = "/*WITH*/";
static const char dot_format[] = "%s.%s";
@ -1238,12 +1239,17 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
* address, popped by JSOP_RETSUB and counted by script->depth
* but not by ss->top (see JSOP_SETSP, below).
*/
todo = Sprint(&ss->sprinter, finally_cookie);
todo = Sprint(&ss->sprinter, exception_cookie);
if (todo < 0 || !PushOff(ss, todo, op))
return JS_FALSE;
todo = Sprint(&ss->sprinter, retsub_pc_cookie);
break;
case JSOP_RETSUB:
rval = POP_STR();
LOCAL_ASSERT(strcmp(rval, finally_cookie) == 0);
LOCAL_ASSERT(strcmp(rval, retsub_pc_cookie) == 0);
lval = POP_STR();
LOCAL_ASSERT(strcmp(lval, exception_cookie) == 0);
todo = -2;
break;
@ -1639,6 +1645,10 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
}
#endif
case JSOP_THROWING:
todo = -2;
break;
case JSOP_THROW:
sn = js_GetSrcNote(jp->script, pc);
todo = -2;

Просмотреть файл

@ -226,8 +226,8 @@ OPDEF(JSOP_INSTANCEOF,112,js_instanceof_str,js_instanceof_str,1,2,1,6,JOF_BYTE|J
OPDEF(JSOP_DEBUGGER, 113,"debugger", NULL, 1, 0, 0, 0, JOF_BYTE)
/* gosub/retsub for finally handling */
OPDEF(JSOP_GOSUB, 114,"gosub", NULL, 3, 0, 1, 0, JOF_JUMP)
OPDEF(JSOP_RETSUB, 115,"retsub", NULL, 1, 1, 0, 0, JOF_BYTE)
OPDEF(JSOP_GOSUB, 114,"gosub", NULL, 3, 0, 0, 0, JOF_JUMP)
OPDEF(JSOP_RETSUB, 115,"retsub", NULL, 1, 0, 0, 0, JOF_BYTE)
/* More exception handling ops. */
OPDEF(JSOP_EXCEPTION, 116,"exception", NULL, 1, 0, 1, 0, JOF_BYTE)
@ -319,7 +319,7 @@ OPDEF(JSOP_IFEQX, 140,"ifeqx", NULL, 5, 1, 0, 0, JOF_JUMPX|
OPDEF(JSOP_IFNEX, 141,"ifnex", NULL, 5, 1, 0, 0, JOF_JUMPX)
OPDEF(JSOP_ORX, 142,"orx", NULL, 5, 1, 0, 0, JOF_JUMPX|JOF_DETECTING)
OPDEF(JSOP_ANDX, 143,"andx", NULL, 5, 1, 0, 0, JOF_JUMPX|JOF_DETECTING)
OPDEF(JSOP_GOSUBX, 144,"gosubx", NULL, 5, 0, 1, 0, JOF_JUMPX)
OPDEF(JSOP_GOSUBX, 144,"gosubx", NULL, 5, 0, 0, 0, JOF_JUMPX)
OPDEF(JSOP_CASEX, 145,"casex", NULL, 5, 1, 0, 0, JOF_JUMPX)
OPDEF(JSOP_DEFAULTX, 146,"defaultx", NULL, 5, 1, 0, 0, JOF_JUMPX)
OPDEF(JSOP_TABLESWITCHX, 147,"tableswitchx",NULL, -1, 1, 0, 0, JOF_TABLESWITCHX|JOF_DETECTING)
@ -328,7 +328,9 @@ OPDEF(JSOP_LOOKUPSWITCHX, 148,"lookupswitchx",NULL, -1, 1, 0, 0, JOF_LOOKUP
/* Placeholders for a real jump opcode set during backpatch chain fixup. */
OPDEF(JSOP_BACKPATCH, 149,"backpatch",NULL, 3, 0, 0, 0, JOF_JUMP|JOF_BACKPATCH)
OPDEF(JSOP_BACKPATCH_POP, 150,"backpatch_pop",NULL, 3, 1, 0, 0, JOF_JUMP|JOF_BACKPATCH)
OPDEF(JSOP_BACKPATCH_PUSH,151,"backpatch_push",NULL, 3, 0, 1, 0, JOF_JUMP|JOF_BACKPATCH)
/* Set cx->throwing where cx->exception was already set, to trigger rethrow. */
OPDEF(JSOP_THROWING, 151,"throwing", NULL, 1, 0, 0, 0, JOF_BYTE)
/* Set and get return value pseudo-register in stack frame. */
OPDEF(JSOP_SETRVAL, 152,"setrval", NULL, 1, 1, 0, 0, JOF_BYTE)

Просмотреть файл

@ -1515,3 +1515,43 @@ js_GetScriptLineExtent(JSScript *script)
}
return 1 + lineno - script->lineno;
}
#if JS_HAS_GENERATORS
jsbytecode *
js_FindFinallyHandler(JSScript *script, jsbytecode *pc)
{
JSTryNote *tn;
ptrdiff_t off;
JSOp op2;
tn = script->trynotes;
if (!tn)
return NULL;
off = pc - script->main;
if (off < 0)
return NULL;
JS_ASSERT(tn->catchStart != 0);
do {
if ((jsuword)(off - tn->start) < (jsuword)tn->length) {
/*
* We have a handler: is it the finally one, or a catch handler?
*
* Catch bytecode begins with: JSOP_SETSP JSOP_ENTERBLOCK
* Finally bytecode begins with: JSOP_SETSP JSOP_(GOSUB|EXCEPTION)
*/
pc = script->main + tn->catchStart;
JS_ASSERT(*pc == JSOP_SETSP);
op2 = pc[JSOP_SETSP_LENGTH];
if (op2 != JSOP_ENTERBLOCK) {
JS_ASSERT(op2 == JSOP_GOSUB || op2 == JSOP_EXCEPTION);
return pc;
}
}
} while ((++tn)->catchStart != 0);
return NULL;
}
#endif