Fix decompilation to preserve braces required by let, and also fix switch body block to have scope for let at top level of case statement (349634, r=mrbkap).

This commit is contained in:
brendan%mozilla.org 2006-09-08 05:03:54 +00:00
parent 5c16f2b7d7
commit ca3ef40908
6 changed files with 208 additions and 23 deletions

View File

@ -928,6 +928,7 @@ SrcNotes(JSContext *cx, JSScript *script)
case SRC_PCBASE:
case SRC_PCDELTA:
case SRC_DECL:
case SRC_BRACE:
fprintf(gOutFile, " offset %u", (uintN) js_GetSrcNoteOffset(sn, 0));
break;
case SRC_LABEL:
@ -960,8 +961,12 @@ SrcNotes(JSContext *cx, JSScript *script)
break;
case SRC_CATCH:
delta = (uintN) js_GetSrcNoteOffset(sn, 0);
if (delta)
fprintf(gOutFile, " guard size %u", delta);
if (delta) {
if (script->main[offset] == JSOP_LEAVEBLOCK)
fprintf(gOutFile, " stack depth %u", delta);
else
fprintf(gOutFile, " guard delta %u", delta);
}
break;
default:;
}

View File

@ -1525,7 +1525,10 @@ js_LexicalLookup(JSTreeContext *tc, JSAtom *atom, jsint *slotp, JSBool letdecl)
break;
}
JS_ASSERT(stmt->flags & SIF_SCOPE);
/* Skip "maybe scope" statements that don't contain let bindings. */
if (!(stmt->flags & SIF_SCOPE))
continue;
obj = ATOM_TO_OBJECT(stmt->atom);
JS_ASSERT(LOCKED_OBJ_GET_CLASS(obj) == &js_BlockClass);
scope = OBJ_SCOPE(obj);
@ -2533,6 +2536,10 @@ EmitSwitch(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn,
intN noteIndex;
size_t switchSize, tableSize;
jsbytecode *pc, *savepc;
#if JS_HAS_BLOCK_SCOPE
JSObject *obj;
jsint count;
#endif
/* Try for most optimal, fall back if not dense ints, and per ECMAv2. */
switchOp = JSOP_TABLESWITCH;
@ -2540,15 +2547,65 @@ EmitSwitch(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn,
hasDefault = constPropagated = JS_FALSE;
defaultOffset = -1;
/* Emit code for the discriminant first. */
if (!js_EmitTree(cx, cg, pn->pn_kid1))
/*
* If the switch contains let variables scoped by its body, model the
* resulting block on the stack first, before emitting the discriminant's
* bytecode (in case the discriminant contains a stack-model dependency
* such as a let expression).
*/
pn2 = pn->pn_right;
#if JS_HAS_BLOCK_SCOPE
if (pn2->pn_type == TOK_LEXICALSCOPE) {
atom = pn2->pn_atom;
obj = ATOM_TO_OBJECT(atom);
OBJ_SET_BLOCK_DEPTH(cx, obj, cg->stackDepth);
count = OBJ_BLOCK_COUNT(cx, obj);
cg->stackDepth += count;
if ((uintN)cg->stackDepth > cg->maxStackDepth)
cg->maxStackDepth = cg->stackDepth;
/* Emit JSOP_ENTERBLOCK before code to evaluate the discriminant. */
ale = js_IndexAtom(cx, atom, &cg->atomList);
if (!ale)
return JS_FALSE;
EMIT_ATOM_INDEX_OP(JSOP_ENTERBLOCK, ALE_INDEX(ale));
}
#ifdef __GNUC__
else {
atom = NULL;
count = -1;
}
#endif
#endif
/*
* Emit code for the discriminant first (or nearly first, in the case of a
* switch whose body is a block scope).
*/
if (!js_EmitTree(cx, cg, pn->pn_left))
return JS_FALSE;
/* Switch bytecodes run from here till end of final case. */
top = CG_OFFSET(cg);
#if !JS_HAS_BLOCK_SCOPE
js_PushStatement(&cg->treeContext, stmtInfo, STMT_SWITCH, top);
#else
if (pn2->pn_type == TOK_LC) {
js_PushStatement(&cg->treeContext, stmtInfo, STMT_SWITCH, top);
} else {
/* Recompute top now that the JSOP_ENTERBLOCK has been emitted. */
top = CG_OFFSET(cg);
/* Push the body's block scope after emitting the discriminant. */
js_PushBlockScope(&cg->treeContext, stmtInfo, atom, top);
stmtInfo->type = STMT_SWITCH;
/* Advance pn2 to refer to the switch case list. */
pn2 = pn2->pn_expr;
}
#endif
pn2 = pn->pn_kid2;
caseCount = pn2->pn_count;
tableLength = 0;
table = NULL;
@ -2953,7 +3010,17 @@ EmitSwitch(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn,
out:
if (table)
JS_free(cx, table);
return ok && js_PopStatementCG(cx, cg);
if (ok) {
ok = js_PopStatementCG(cx, cg);
#if JS_HAS_BLOCK_SCOPE
if (ok && pn->pn_right->pn_type == TOK_LEXICALSCOPE) {
EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
cg->stackDepth -= count;
}
#endif
}
return ok;
bad:
ok = JS_FALSE;
@ -4741,13 +4808,19 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
case TOK_CATCH:
{
ptrdiff_t guardJump;
ptrdiff_t catchStart, guardJump;
/* Morph STMT_BLOCK to STMT_CATCH and save the block object atom. */
/*
* Morph STMT_BLOCK to STMT_CATCH, note the block entry code offset,
* and save the block object atom.
*/
stmt = cg->treeContext.topStmt;
JS_ASSERT(stmt->type == STMT_BLOCK && (stmt->flags & SIF_SCOPE));
stmt->type = STMT_CATCH;
catchStart = stmt->update;
atom = stmt->atom;
/* Go up one statement info record to the TRY or FINALLY record. */
stmt = stmt->down;
JS_ASSERT(stmt->type == STMT_TRY || stmt->type == STMT_FINALLY);
@ -4778,11 +4851,10 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
/* 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)) {
CG_OFFSET(cg) - catchStart)) {
return JS_FALSE;
}
/* ifeq <next block> */
@ -4877,11 +4949,27 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
#endif
JS_ASSERT(pn->pn_arity == PN_LIST);
noteIndex = -1;
tmp = CG_OFFSET(cg);
if (pn->pn_extra & PNX_NEEDBRACES) {
noteIndex = js_NewSrcNote2(cx, cg, SRC_BRACE, 0);
if (noteIndex < 0 || js_Emit1(cx, cg, JSOP_NOP) < 0)
return JS_FALSE;
}
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_BLOCK, top);
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
if (!js_EmitTree(cx, cg, pn2))
return JS_FALSE;
}
if (noteIndex >= 0 &&
!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
CG_OFFSET(cg) - tmp)) {
return JS_FALSE;
}
ok = js_PopStatementCG(cx, cg);
break;
@ -5621,6 +5709,27 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
if ((uintN)cg->stackDepth > cg->maxStackDepth)
cg->maxStackDepth = cg->stackDepth;
/*
* If this lexical scope is not for a catch block, let block, or let
* expression, and our container is top-level but not a function body,
* or else a block statement, emit a SRC_BRACE note. Other container
* statements get braces by default from the decompiler.
*/
noteIndex = -1;
tmp = CG_OFFSET(cg);
type = pn->pn_expr->pn_type;
if (type != TOK_CATCH && type != TOK_LET &&
(!(stmt = stmtInfo.down)
? !(cg->treeContext.flags & TCF_IN_FUNCTION)
: stmt->type == STMT_BLOCK)) {
/* There must be no source note already output for the next op. */
JS_ASSERT(CG_NOTE_COUNT(cg) == 0 ||
CG_LAST_NOTE_OFFSET(cg) != CG_OFFSET(cg));
noteIndex = js_NewSrcNote2(cx, cg, SRC_BRACE, 0);
if (noteIndex < 0)
return JS_FALSE;
}
ale = js_IndexAtom(cx, atom, &cg->atomList);
if (!ale)
return JS_FALSE;
@ -5629,6 +5738,12 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
if (!js_EmitTree(cx, cg, pn->pn_expr))
return JS_FALSE;
if (noteIndex >= 0 &&
!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
CG_OFFSET(cg) - tmp)) {
return JS_FALSE;
}
/* Emit the JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR opcode. */
EMIT_UINT16_IMM_OP(pn->pn_op, count);
cg->stackDepth -= count;
@ -6067,6 +6182,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
return ok;
}
/* XXX get rid of offsetBias altogether, it's used only by SRC_FOR */
JS_FRIEND_DATA(JSSrcNoteSpec) js_SrcNoteSpec[] = {
{"null", 0, 0, 0},
{"if", 0, 0, 0},
@ -6078,7 +6194,7 @@ JS_FRIEND_DATA(JSSrcNoteSpec) js_SrcNoteSpec[] = {
{"pcdelta", 1, 0, 1},
{"assignop", 0, 0, 0},
{"cond", 1, 0, 1},
{"unused10", 0, 0, 0},
{"brace", 1, 0, 1},
{"hidden", 0, 0, 0},
{"pcbase", 1, 0, -1},
{"label", 1, 0, 0},
@ -6088,7 +6204,7 @@ JS_FRIEND_DATA(JSSrcNoteSpec) js_SrcNoteSpec[] = {
{"cont2label", 1, 0, 0},
{"switch", 2, 0, 1},
{"funcdef", 1, 0, 0},
{"catch", 1, 11, 1},
{"catch", 1, 0, 1},
{"unused21", 0, 0, 0},
{"newline", 0, 0, 0},
{"setline", 1, 0, 0},

View File

@ -65,8 +65,8 @@ typedef enum JSStmtType {
STMT_LABEL, /* labeled statement: L: s */
STMT_IF, /* if (then) statement */
STMT_ELSE, /* else clause of if statement */
STMT_SWITCH, /* switch statement */
STMT_BLOCK, /* compound statement: { s1[;... sN] } */
STMT_SWITCH, /* switch statement */
STMT_WITH, /* with statement */
STMT_CATCH, /* catch block */
STMT_TRY, /* try block */
@ -109,8 +109,8 @@ struct JSStmtInfo {
JSStmtInfo *downScope; /* next enclosing lexical scope */
};
#define SIF_SCOPE 0x0002 /* statement has its own lexical scope */
#define SIF_BODY_BLOCK 0x0001 /* STMT_BLOCK type is a function body */
#define SIF_SCOPE 0x0001 /* statement has its own lexical scope */
#define SIF_BODY_BLOCK 0x0002 /* STMT_BLOCK type is a function body */
/*
* To reuse space in JSStmtInfo, rename breaks and continues for use during
@ -158,6 +158,7 @@ struct JSTreeContext { /* tree context for semantic checks */
#define TCF_FUN_IS_GENERATOR 0x100 /* parsed yield statement in function */
#define TCF_FUN_FLAGS 0x1E0 /* flags to propagate from FunctionBody */
#define TCF_HAS_DEFXMLNS 0x200 /* default xml namespace = ...; parsed */
#define TCF_HAS_CLOSURE 0x400 /* function statement was parsed */
#define TREE_CONTEXT_INIT(tc) \
((tc)->flags = (tc)->numGlobalVars = 0, \
@ -499,7 +500,8 @@ typedef enum JSSrcNoteType {
gets and sets */
SRC_ASSIGNOP = 8, /* += or another assign-op follows */
SRC_COND = 9, /* JSOP_IFEQ is from conditional ?: operator */
SRC_UNUSED10 = 10, /* unused */
SRC_BRACE = 10, /* mandatory brace, for scope or to avoid
dangling else */
SRC_HIDDEN = 11, /* opcode shouldn't be decompiled */
SRC_PCBASE = 12, /* distance back from annotated get- or setprop
op to first obj.prop.subprop bytecode */

View File

@ -1223,6 +1223,15 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
break;
case SRC_BRACE:
js_printf(jp, "\t{\n");
jp->indent += 4;
len = js_GetSrcNoteOffset(sn, 0);
DECOMPILE_CODE(pc + oplen, len - oplen);
jp->indent -= 4;
js_printf(jp, "\t}\n");
break;
default:;
}
case JSOP_RETRVAL:
@ -1492,10 +1501,25 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
}
sn = js_GetSrcNote(jp->script, pc);
if (sn && SN_TYPE(sn) == SRC_CATCH) {
switch (sn ? SN_TYPE(sn) : SRC_NULL) {
#if JS_HAS_BLOCK_SCOPE
case SRC_BRACE:
js_printf(jp, "\t{\n");
jp->indent += 4;
len = js_GetSrcNoteOffset(sn, 0);
ok = Decompile(ss, pc + oplen, len - oplen);
if (!ok)
goto enterblock_out;
jp->indent -= 4;
js_printf(jp, "\t}\n");
break;
#endif
case SRC_CATCH:
jp->indent -= 4;
js_printf(jp, "\t} catch (");
pc2 = pc;
pc += oplen;
LOCAL_ASSERT(*pc == JSOP_EXCEPTION);
pc += JSOP_EXCEPTION_LENGTH;
@ -1513,6 +1537,8 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
len = js_GetSrcNoteOffset(sn, 0);
if (len) {
len -= PTRDIFF(pc, pc2, jsbytecode);
JS_ASSERT(len > 0);
js_printf(jp, " if ");
ok = Decompile(ss, pc, len);
if (!ok)
@ -1526,6 +1552,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
js_printf(jp, ") {\n");
jp->indent += 4;
len = 0;
break;
}
todo = -2;

View File

@ -601,7 +601,10 @@ HasFinalReturn(JSParseNode *pn)
case TOK_SWITCH:
rv = ENDS_IN_RETURN;
hasDefault = ENDS_IN_OTHER;
for (pn2 = pn->pn_kid2->pn_head; rv && pn2; pn2 = pn2->pn_next) {
pn2 = pn->pn_right;
if (pn2->pn_type == TOK_LEXICALSCOPE)
pn2 = pn2->pn_expr;
for (pn2 = pn2->pn_head; rv && pn2; pn2 = pn2->pn_next) {
if (pn2->pn_type == TOK_DEFAULT)
hasDefault = ENDS_IN_RETURN;
pn3 = pn2->pn_right;
@ -1375,6 +1378,7 @@ FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
* sub-statement.
*/
op = JSOP_CLOSURE;
tc->flags |= TCF_HAS_CLOSURE;
} else {
op = JSOP_NOP;
}
@ -2546,7 +2550,7 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
case TOK_SWITCH:
{
JSParseNode *pn5;
JSParseNode *pn5, *saveBlock;
JSBool seenDefault = JS_FALSE;
pn = NewParseNode(cx, ts, PN_BINARY, tc);
@ -2566,6 +2570,8 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
pn2 = NewParseNode(cx, ts, PN_LIST, tc);
if (!pn2)
return NULL;
saveBlock = tc->blockNode;
tc->blockNode = pn2;
PN_INIT_LIST(pn2);
js_PushStatement(tc, &stmtInfo, STMT_SWITCH, -1);
@ -2640,11 +2646,20 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
pn3->pn_right = pn4;
}
/*
* Handle the case where there was a let declaration in any case in
* the switch body, but not within an inner block. If it replaced
* tc->blockNode with a new block node then we must refresh pn2 and
* then restore tc->blockNode.
*/
if (tc->blockNode != pn2)
pn2 = tc->blockNode;
tc->blockNode = saveBlock;
js_PopStatement(tc);
pn->pn_pos.end = pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
pn->pn_kid1 = pn1;
pn->pn_kid2 = pn2;
pn->pn_left = pn1;
pn->pn_right = pn2;
return pn;
}
@ -3364,6 +3379,11 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
break;
case TOK_LC:
{
uintN oldflags;
oldflags = tc->flags;
tc->flags = oldflags & ~TCF_HAS_CLOSURE;
js_PushStatement(tc, &stmtInfo, STMT_BLOCK, -1);
pn = Statements(cx, ts, tc);
if (!pn)
@ -3371,7 +3391,18 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_COMPOUND);
js_PopStatement(tc);
/*
* If we contain a function statement and our container is top-level
* or another block, flag pn to preserve braces when decompiling.
*/
if ((tc->flags & TCF_HAS_CLOSURE) &&
(!tc->topStmt || tc->topStmt->type == STMT_BLOCK)) {
pn->pn_extra |= PNX_NEEDBRACES;
}
tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS);
return pn;
}
case TOK_EOL:
case TOK_SEMI:

View File

@ -82,7 +82,10 @@ JS_BEGIN_EXTERN_C
* TOK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null
* TOK_SWITCH binary pn_left: discriminant
* pn_right: list of TOK_CASE nodes, with at most one
* TOK_DEFAULT node
* TOK_DEFAULT node, or if there are let bindings
* in the top level of the switch body's cases, a
* TOK_LEXICALSCOPE node that contains the list of
* TOK_CASE nodes.
* TOK_CASE, binary pn_left: case expr or null if TOK_DEFAULT
* TOK_DEFAULT pn_right: TOK_LC node for this case's statements
* pn_val: constant value if lookup or table switch
@ -349,6 +352,7 @@ struct JSParseNode {
#define PNX_ENDCOMMA 0x10 /* array literal has comma at end */
#define PNX_XMLROOT 0x20 /* top-most node in XML literal tree */
#define PNX_GROUPINIT 0x40 /* var [a, b] = [c, d]; unit list */
#define PNX_NEEDBRACES 0x80 /* braces necessary due to closure */
/*
* Move pn2 into pn, preserving pn->pn_pos and pn->pn_offset and handing off