Closed
Bug 460501
Opened 16 years ago
Closed 16 years ago
Round-trip change due to "&&" constant-folding leaving extra parens
Categories
(Core :: JavaScript Engine, defect)
Tracking
()
RESOLVED
FIXED
People
(Reporter: jruderman, Assigned: jorendorff)
References
Details
(Keywords: regression, testcase)
Attachments
(2 files, 2 obsolete files)
8.15 KB,
patch
|
Details | Diff | Splinter Review | |
32.35 KB,
patch
|
brendan
:
review+
|
Details | Diff | Splinter Review |
js> f = (function() { if ((1 && w.valueOf())) {} })
function () {
if ((w.valueOf())) {
}
}
js> eval(uneval(f));
function () {
if (w.valueOf()) {
}
}
This happens with "valueOf" and "eval" but not with most other names.
Assignee | ||
Comment 1•16 years ago
|
||
Brendan, earlier this week you mentioned the possibility of just ripping out TOK_RP throughout. That would fix this, right?
Comment 2•16 years ago
|
||
Yeah, getting rid of JSOP_GROUP would fix this bug -- a bigger project than I can take on right now. You willing to try?
/be
Assignee | ||
Comment 3•16 years ago
|
||
This removes JSOP_GROUP. It retains the TOK_RP nodes but doesn't emit anything for them. Passes regrtests.
The TOK_RP nodes are useless, but I'd like to kill them in a separate patch; I am paranoid about regressions, being unfamiliar with this code, and would appreciate the separate bisect target.
Assignee: general → jorendorff
Attachment #343712 -
Flags: review?(brendan)
Assignee | ||
Comment 4•16 years ago
|
||
Comment on attachment 343712 [details] [diff] [review]
v1
Correction -- I wasn't testing the right thing, and there are several decompiler bugs in v1.
Attachment #343712 -
Flags: review?(brendan)
Assignee | ||
Comment 5•16 years ago
|
||
For your amusement--one of those is the venerable
new (x(y).z)
decompiling as
new x(y).z
js_Decompile, case JSOP_NEW, has a nice comment explaining why that needs to be parenthesized and how the code supposedly handles it, but there's a bug--I think it was unwittingly depending on JSOP_GROUP all along. :-)
Comment 6•16 years ago
|
||
(In reply to comment #5)
> For your amusement--one of those is the venerable
> new (x(y).z)
> decompiling as
> new x(y).z
>
> js_Decompile, case JSOP_NEW, has a nice comment explaining why that needs to be
> parenthesized and how the code supposedly handles it, but there's a bug--I
> think it was unwittingly depending on JSOP_GROUP all along. :-)
The dependency is witting. The other comment under JSOP_GROUP's case is:
case JSOP_GROUP:
cs = &js_CodeSpec[lastop];
if ((cs->prec != 0 &&
cs->prec <= js_CodeSpec[NEXT_OP(pc)].prec) ||
pc[JSOP_GROUP_LENGTH] == JSOP_NULL ||
pc[JSOP_GROUP_LENGTH] == JSOP_NULLTHIS ||
pc[JSOP_GROUP_LENGTH] == JSOP_DUP ||
pc[JSOP_GROUP_LENGTH] == JSOP_IFEQ ||
pc[JSOP_GROUP_LENGTH] == JSOP_IFNE) {
/*
* Force parens if this JSOP_GROUP forced re-association
* against precedence, or if this is a call or constructor
* expression, or if it is destructured (JSOP_DUP), or if
* it is an if or loop condition test.
*
* This is necessary to handle the operator new grammar,
* by which new x(y).z means (new x(y))).z. For example
* new (x(y).z) must decompile with the constructor
* parenthesized, but normal precedence has JSOP_GETPROP
* (for the final .z) higher than JSOP_NEW. In general,
* if the call or constructor expression is parenthesized,
* we preserve parens.
*/
op = JSOP_NAME;
rval = POP_STR();
If you removed this code, then you broke the other code under JSOP_NEW/CALL/EVAL.
/be
Assignee | ||
Comment 7•16 years ago
|
||
This flunks two new tests, in addition to all the ones that were already failing. In both cases, the new breakage is clearly a harmless change in parenthesization due to JSOP_GROUP being removed.
js1_5/decompilation/regress-456964-01.js
expect: o += (i + " = " + object[i] + "\n");
actual: o += i + " = " + object[i] + "\n";
js1_7/decompilation/regress-352026.js
expect: function ( ) { z ( ( yield 3 ) ) ; }
actual: function ( ) { z ( yield 3 ) ; }
I had to tweak the logic for decompiling binary expressions to get
cases like (function () a + (b + c) + d) right.
I changed the precedence of the JSOP_DEL* opcodes from 17 (like new) to 15 (like the prefix operators ! ~ - ++ --), reflecting the precedence of delete expressions rather than the minimum permissible precedence of their subexpressions. This was necessary to force parentheses when decompiling |new (delete foo)| and in turn necessitated changes to how those opcodes are decompiled.
Attachment #343712 -
Attachment is obsolete: true
Attachment #343946 -
Flags: review?(brendan)
Assignee | ||
Comment 8•16 years ago
|
||
(In reply to comment #7)
> This flunks two new tests, in addition to all the ones that were already
> failing.
By this I mean only that tracemonkey is in a failing state, I hope just because we haven't merged recent updates to js/src/public-failures.txt.
The patch is good stuff!
Comment 9•16 years ago
|
||
Comment on attachment 343946 [details] [diff] [review]
v2
Thanks for taking this on. Few can face the decompiler and not blink -- impressive patchwork.
Need to rev jsxdrapi.h's JSXDR_BYTECODE_VERSION when removing opcodes.
>@@ -5132,25 +5131,21 @@ js_Interpret(JSContext *cx)
> }
>
> id = ATOM_TO_JSID(atom);
> if (js_FindPropertyHelper(cx, id, &obj, &obj2, &prop, &entry) < 0)
> goto error;
> if (!prop) {
> /* Kludge to allow (typeof foo == "undefined") tests. */
> endpc = script->code + script->length;
>- for (pc2 = regs.pc + JSOP_NAME_LENGTH; pc2 < endpc; pc2++) {
>- op2 = (JSOp)*pc2;
>- if (op2 == JSOP_TYPEOF) {
>- PUSH_OPND(JSVAL_VOID);
>- len = JSOP_NAME_LENGTH;
>- DO_NEXT_OP(len);
>- }
>- if (op2 != JSOP_GROUP)
>- break;
>+ op2 = (JSOp) *(regs.pc + JSOP_NAME_LENGTH);
Nit: prefer p[i] to the longer *(p + i) form, even when p is a pointer not an array.
>+/*
>+ * To parenthesize correctly in case JSOP_NEW, we must know whether the
>+ * left-hand side contains any non-parenthesized function calls. So when
s/non-/un/
>+ * building a MemberExpression or CallExpression, we set ss->opcodes[n] to
>+ * JSOP_CALL if this is true: |foo().x.y| gets JSOP_CALL, not JSOP_GETPROP.
See below for evolved comment relocation opportunity.
>+ rval = POP_STR_PREC(cs->prec + (inXML ? 0 : cs->format & JOF_LEFTASSOC ? 1 : 0));
>+ lval = POP_STR_PREC(cs->prec + (inXML ? 0 : cs->format & JOF_LEFTASSOC ? 0 : 1));
Nit: parenthesize condition to left of ? as a rule -- but:
Why not use (cs->prec + (inXML || !(cs->format & JOF_LEFTASSOC)) for the second line (!! instead of ! for the first line)? Hardly seems clearer spelled out with ?: but it is certainly longer.
>+ op = JSOP_IFEQ; /* extra parens around assignment */
> js_printf(jp, "\t} while (%s);\n", POP_STR());
Make a POP_COND_STR() to common these op = JSOP_IFEQ hacks? Better, it would use your POP_STR_PREC and not mutate op at all.
>- * This is necessary to handle the operator new grammar,
>- * by which new x(y).z means (new x(y))).z. For example
>- * new (x(y).z) must decompile with the constructor
>- * parenthesized, but normal precedence has JSOP_GETPROP
>- * (for the final .z) higher than JSOP_NEW. In general,
>- * if the call or constructor expression is parenthesized,
>- * we preserve parens.
Want an evolution of this comment to live on, mainly because it shows both cases and discusses precedence relations. JSOP_CALL, JSOP_GETPROP, and the other ops that call PROPAGATE_CALLNESS, all having the same precedence, and this should be commented too.
>diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl
>--- a/js/src/jsopcode.tbl
>+++ b/js/src/jsopcode.tbl
Please update the big precedence table comment above the first OPDEF.
>@@ -5691,37 +5691,18 @@ PrimaryExpr(JSContext *cx, JSTokenStream
> return NULL;
> pn2 = ParenExpr(cx, ts, tc, pn, &genexp);
> if (!pn2)
> return NULL;
> if (genexp)
> return pn2;
>
> MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN);
>- if (pn2->pn_type == TOK_RP ||
>- (js_CodeSpec[pn2->pn_op].prec >= js_CodeSpec[JSOP_GETPROP].prec &&
>- !afterDot)) {
>- /*
>- * Avoid redundant JSOP_GROUP opcodes, for efficiency and mainly
>- * to help the decompiler look ahead from a JSOP_ENDINIT to see a
>- * JSOP_GROUP followed by a POP or POPV. That sequence means the
>- * parentheses are mandatory, to disambiguate object initialisers
>- * as expression statements from block statements.
>- *
>- * Also drop pn if pn2 is a member or a primary expression of any
>- * kind. This is required to avoid generating a JSOP_GROUP that
>- * will null the |obj| interpreter register, causing |this| in any
>- * call of that member expression to bind to the global object.
>- */
>- RecycleTree(pn, tc);
>- pn = pn2;
>- } else {
>- pn->pn_type = TOK_RP;
>- pn->pn_kid = pn2;
>- }
>+ pn->pn_type = TOK_RP;
>+ pn->pn_kid = pn2;
Are you sure code elsewhere (jsemit.cpp) does not depend on this code? I'd defer removing it if you can until that followup bug to remove TOK_RP nodes from the AST.
/be
Assignee | ||
Comment 10•16 years ago
|
||
Assignee | ||
Comment 11•16 years ago
|
||
Adopt all brendan's suggestions.
Attachment #343946 -
Attachment is obsolete: true
Attachment #344096 -
Flags: review?(brendan)
Attachment #343946 -
Flags: review?(brendan)
Assignee | ||
Comment 12•16 years ago
|
||
I see I inadvertently added a second blank line before a comment in jsopcode.cpp. I'll delete that for checkin.
Updated•16 years ago
|
Attachment #344096 -
Flags: review?(brendan) → review+
Comment 13•16 years ago
|
||
Comment on attachment 344096 [details] [diff] [review]
v3
> #define POP_STR() PopStr(ss, op)
>-#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, NULL)
>+#define POP_STR_PREC(prec) PopStrPrec(ss, prec)
>+
>+/*
>+ * Pop a condition expression for if/for/while. JSOP_IFEQ's precedence forces
>+ * extra parens around assignment, which avoids a strict-mode warning.
>+ */
>+#define POP_COND_STR() POP_STR_PREC(js_CodeSpec[JSOP_IFEQ].prec)
Defining POP_COND_STR as PopStr(ss, JSOP_IFEQ) would minimize code size. r=me with that.
/be
Assignee | ||
Comment 14•16 years ago
|
||
Status: NEW → RESOLVED
Closed: 16 years ago
Resolution: --- → FIXED
Reporter | ||
Comment 15•16 years ago
|
||
Before the patch, both of these were stable:
(function() { if(a = b) { } })
(function() { if((a = b)) { } })
After the patch, both decompile to the latter. I think this is an improvement, since it makes a strict warning go away. I'll adjust jsfunfuzz's "checkForExtraParens" to stop complaining.
I don't think this new behavior helps in "for" conditions, though. I filed bug 461108 for that.
Comment 16•16 years ago
|
||
this breaks js1_8_1/decompilation/regress-346642-01.js 14 and 15. jorendorff says that is expected. I'll update the tests in the next push.
Comment 17•16 years ago
|
||
(In reply to comment #7)
> js1_5/decompilation/regress-456964-01.js
> expect: o += (i + " = " + object[i] + "\n");
> actual: o += i + " = " + object[i] + "\n";
the expect expression here is not part of the checked in test. Did you make local revisions?
Comment 18•16 years ago
|
||
http://hg.mozilla.org/mozilla-central/rev/37b3fdbb0f07
/cvsroot/mozilla/js/tests/js1_5/decompilation/regress-460501.js,v <-- regress-460501.js
initial revision: 1.1
Flags: in-testsuite+
Flags: in-litmus-
You need to log in
before you can comment on or make changes to this bug.
Description
•