5353
5454import java .util .ArrayList ;
5555import java .util .EnumSet ;
56- import java .util .HashSet ;
56+ import java .util .HashMap ;
5757import java .util .IdentityHashMap ;
5858import java .util .List ;
5959import java .util .Map ;
@@ -104,7 +104,7 @@ private static class MustExecVisitor extends JsVisitor {
104104
105105 private final List <JsStatement > mustExec = new ArrayList <JsStatement >();
106106
107- public MustExecVisitor () {
107+ MustExecVisitor () {
108108 }
109109
110110 @ Override
@@ -144,6 +144,14 @@ public boolean visit(JsFunction x, JsContext ctx) {
144144 }
145145 }
146146
147+ /**
148+ * Describes how is the result of evaluation used.
149+ */
150+ private enum EvalMode {
151+ BOOL , // result will be coerced to a boolean
152+ VOID // result will be discarded
153+ }
154+
147155 /**
148156 * Does static evals.
149157 *
@@ -153,12 +161,35 @@ public boolean visit(JsFunction x, JsContext ctx) {
153161 */
154162 private class StaticEvalVisitor extends JsModVisitor {
155163
156- private Set <JsExpression > evalBooleanContext = new HashSet <JsExpression >();
164+ /**
165+ * Stores how are expression evaluations used.
166+ * Missing entry = no coercion, difference between null and false matters.
167+ */
168+ private final Map <JsExpression , EvalMode > evalContext = new HashMap <>();
157169
158170 /**
159171 * This is used by {@link #additionCoercesToString}.
160172 */
161- private Map <JsExpression , Boolean > coercesToStringMap = new IdentityHashMap <JsExpression , Boolean >();
173+ private Map <JsExpression , Boolean > coercesToStringMap = new IdentityHashMap <>();
174+
175+ public boolean visit (JsExprStmt x , JsContext ctx ) {
176+ evalContext .put (x .getExpression (), EvalMode .VOID );
177+ return true ;
178+ }
179+
180+ public boolean visit (JsBinaryOperation x , JsContext ctx ) {
181+ if (evalContext .containsKey (x )
182+ && (x .getOperator () == JsBinaryOperator .AND || x .getOperator () == JsBinaryOperator .OR )) {
183+ evalContext .put (x .getArg1 (), EvalMode .BOOL );
184+ evalContext .put (x .getArg2 (), evalContext .get (x ));
185+ } else if (x .getOperator () == JsBinaryOperator .COMMA ) {
186+ evalContext .put (x .getArg1 (), EvalMode .VOID );
187+ if (evalContext .containsKey (x )) {
188+ evalContext .put (x .getArg2 (), evalContext .get (x ));
189+ }
190+ }
191+ return true ;
192+ }
162193
163194 @ Override
164195 public void endVisit (JsBinaryOperation x , JsContext ctx ) {
@@ -167,11 +198,13 @@ public void endVisit(JsBinaryOperation x, JsContext ctx) {
167198 JsExpression arg2 = x .getArg2 ();
168199
169200 JsExpression result = x ;
170-
171- if (op == JsBinaryOperator .AND ) {
172- result = shortCircuitAnd (x );
201+ if (evalContext .get (x ) == EvalMode .VOID
202+ && !arg2 .hasSideEffects () && !op .isAssignment ()) {
203+ result = arg1 ;
204+ } else if (op == JsBinaryOperator .AND ) {
205+ result = shortCircuitAnd (x , evalContext );
173206 } else if (op == JsBinaryOperator .OR ) {
174- result = shortCircuitOr (x );
207+ result = shortCircuitOr (x , evalContext );
175208 } else if (op == JsBinaryOperator .COMMA ) {
176209 result = trySimplifyComma (x );
177210 } else if (op == JsBinaryOperator .EQ || op == JsBinaryOperator .REF_EQ ) {
@@ -195,7 +228,8 @@ public void endVisit(JsBinaryOperation x, JsContext ctx) {
195228 break ;
196229 }
197230 }
198-
231+ evalContext .remove (arg1 );
232+ evalContext .remove (arg2 );
199233 result = maybeReorderOperations (result );
200234
201235 if (result != x ) {
@@ -256,21 +290,27 @@ public void endVisit(JsBlock x, JsContext ctx) {
256290
257291 @ Override
258292 public void endVisit (JsConditional x , JsContext ctx ) {
259- evalBooleanContext .remove (x .getTestExpression ());
293+ evalContext .remove (x .getTestExpression ());
260294
261295 JsExpression condExpr = x .getTestExpression ();
262296 JsExpression thenExpr = x .getThenExpression ();
263297 JsExpression elseExpr = x .getElseExpression ();
264- if (condExpr instanceof CanBooleanEval ) {
298+ if (condExpr instanceof JsBinaryOperation
299+ && ((JsBinaryOperation ) condExpr ).getOperator () == JsBinaryOperator .COMMA ) {
300+ JsBinaryOperation condition = (JsBinaryOperation ) condExpr ;
301+ JsExpression newConditional = accept (new JsConditional (x .getSourceInfo (),
302+ condition .getArg2 (), thenExpr , elseExpr ));
303+ ctx .replaceMe (withSideEffect (newConditional , condition .getArg1 (), x .getSourceInfo ()));
304+ } else if (condExpr instanceof CanBooleanEval ) {
265305 CanBooleanEval condEval = (CanBooleanEval ) condExpr ;
266306 if (condEval .isBooleanTrue ()) {
267307 JsBinaryOperation binOp = new JsBinaryOperation (x .getSourceInfo (),
268- JsBinaryOperator .AND , condExpr , thenExpr );
308+ JsBinaryOperator .COMMA , condExpr , thenExpr );
269309 ctx .replaceMe (accept (binOp ));
270310 } else if (condEval .isBooleanFalse ()) {
271- // e.g. (false() ? then : else) -> false() || else
311+ // e.g. (false() ? then : else) -> ( false() , else)
272312 JsBinaryOperation binOp = new JsBinaryOperation (x .getSourceInfo (),
273- JsBinaryOperator .OR , condExpr , elseExpr );
313+ JsBinaryOperator .COMMA , condExpr , elseExpr );
274314 ctx .replaceMe (accept (binOp ));
275315 }
276316 }
@@ -281,7 +321,7 @@ public void endVisit(JsConditional x, JsContext ctx) {
281321 */
282322 @ Override
283323 public void endVisit (JsDoWhile x , JsContext ctx ) {
284- evalBooleanContext .remove (x .getCondition ());
324+ evalContext .remove (x .getCondition ());
285325
286326 JsExpression expr = x .getCondition ();
287327 if (expr instanceof CanBooleanEval ) {
@@ -304,6 +344,7 @@ public void endVisit(JsDoWhile x, JsContext ctx) {
304344
305345 @ Override
306346 public void endVisit (JsExprStmt x , JsContext ctx ) {
347+ evalContext .remove (x .getExpression ());
307348 if (!x .getExpression ().hasSideEffects ()) {
308349 if (ctx .canRemove ()) {
309350 ctx .removeMe ();
@@ -318,7 +359,7 @@ public void endVisit(JsExprStmt x, JsContext ctx) {
318359 */
319360 @ Override
320361 public void endVisit (JsFor x , JsContext ctx ) {
321- evalBooleanContext .remove (x .getCondition ());
362+ evalContext .remove (x .getCondition ());
322363
323364 JsExpression expr = x .getCondition ();
324365 if (expr instanceof CanBooleanEval ) {
@@ -348,7 +389,7 @@ public void endVisit(JsFor x, JsContext ctx) {
348389 */
349390 @ Override
350391 public void endVisit (JsIf x , JsContext ctx ) {
351- evalBooleanContext .remove (x .getIfExpr ());
392+ evalContext .remove (x .getIfExpr ());
352393
353394 JsExpression condExpr = x .getIfExpr ();
354395 if (condExpr instanceof CanBooleanEval ) {
@@ -402,10 +443,10 @@ public void endVisit(JsIf x, JsContext ctx) {
402443 @ Override
403444 public void endVisit (JsPrefixOperation x , JsContext ctx ) {
404445 if (x .getOperator () == JsUnaryOperator .NOT ) {
405- evalBooleanContext .remove (x .getArg ());
446+ evalContext .remove (x .getArg ());
406447 }
407448
408- if (evalBooleanContext . contains (x )) {
449+ if (evalContext . containsKey (x )) {
409450 if ((x .getOperator () == JsUnaryOperator .NOT )
410451 && (x .getArg () instanceof JsPrefixOperation )) {
411452 JsPrefixOperation arg = (JsPrefixOperation ) x .getArg ();
@@ -422,7 +463,7 @@ public void endVisit(JsPrefixOperation x, JsContext ctx) {
422463 */
423464 @ Override
424465 public void endVisit (JsWhile x , JsContext ctx ) {
425- evalBooleanContext .remove (x .getCondition ());
466+ evalContext .remove (x .getCondition ());
426467
427468 JsExpression expr = x .getCondition ();
428469 if (expr instanceof CanBooleanEval ) {
@@ -443,39 +484,39 @@ public void endVisit(JsWhile x, JsContext ctx) {
443484
444485 @ Override
445486 public boolean visit (JsConditional x , JsContext ctx ) {
446- evalBooleanContext . add (x .getTestExpression ());
487+ evalContext . put (x .getTestExpression (), EvalMode . BOOL );
447488 return true ;
448489 }
449490
450491 @ Override
451492 public boolean visit (JsDoWhile x , JsContext ctx ) {
452- evalBooleanContext . add (x .getCondition ());
493+ evalContext . put (x .getCondition (), EvalMode . BOOL );
453494 return true ;
454495 }
455496
456497 @ Override
457498 public boolean visit (JsFor x , JsContext ctx ) {
458- evalBooleanContext . add (x .getCondition ());
499+ evalContext . put (x .getCondition (), EvalMode . BOOL );
459500 return true ;
460501 }
461502
462503 @ Override
463504 public boolean visit (JsIf x , JsContext ctx ) {
464- evalBooleanContext . add (x .getIfExpr ());
505+ evalContext . put (x .getIfExpr (), EvalMode . BOOL );
465506 return true ;
466507 }
467508
468509 @ Override
469510 public boolean visit (JsPrefixOperation x , JsContext ctx ) {
470511 if (x .getOperator () == JsUnaryOperator .NOT ) {
471- evalBooleanContext . add (x .getArg ());
512+ evalContext . put (x .getArg (), EvalMode . BOOL );
472513 }
473514 return true ;
474515 }
475516
476517 @ Override
477518 public boolean visit (JsWhile x , JsContext ctx ) {
478- evalBooleanContext . add (x .getCondition ());
519+ evalContext . put (x .getCondition (), EvalMode . BOOL );
479520 return true ;
480521 }
481522
@@ -607,41 +648,75 @@ public static int exec(JsProgram program) {
607648 * Simplify short circuit AND expressions.
608649 *
609650 * <pre>
610- * if (true && isWhatever()) -> if (isWhatever()), unless side effects
611- * if (false() && isWhatever()) -> if (false())
651+ * return true() && isWhatever() -> return isWhatever(), unless true() has side effects
652+ * return false() && isWhatever() -> return false()
653+ * </pre>
654+ * In boolean context also
655+ * <pre>
656+ * if (isWhatever() && true()) -> if (isWhatever()) unless true() has side effects
657+ * if (isWhatever() && false()) -> if (false()) unless isWhatever() side effects
612658 * </pre>
613659 */
614- protected static JsExpression shortCircuitAnd (JsBinaryOperation expr ) {
660+ protected static JsExpression shortCircuitAnd (JsBinaryOperation expr ,
661+ Map <JsExpression , EvalMode > evalContext ) {
615662 JsExpression arg1 = expr .getArg1 ();
616663 JsExpression arg2 = expr .getArg2 ();
617664 if (arg1 instanceof CanBooleanEval ) {
618665 CanBooleanEval eval1 = (CanBooleanEval ) arg1 ;
619- if (eval1 .isBooleanTrue () && ! arg1 . hasSideEffects () ) {
620- return arg2 ;
666+ if (eval1 .isBooleanTrue ()) {
667+ return withSideEffect ( arg2 , arg1 , expr . getSourceInfo ()) ;
621668 } else if (eval1 .isBooleanFalse ()) {
622669 return arg1 ;
623670 }
624671 }
672+ if (arg2 instanceof CanBooleanEval && evalContext .containsKey (expr )) {
673+ CanBooleanEval eval2 = (CanBooleanEval ) arg2 ;
674+ if (eval2 .isBooleanTrue () && !arg2 .hasSideEffects ()) {
675+ return arg1 ;
676+ } else if (eval2 .isBooleanFalse ()) {
677+ return withSideEffect (arg2 , arg1 , expr .getSourceInfo ());
678+ }
679+ }
625680 return expr ;
626681 }
627682
683+ private static JsExpression withSideEffect (JsExpression main , JsExpression sideEffect ,
684+ SourceInfo sourceInfo ) {
685+ return !sideEffect .hasSideEffects () ? main
686+ : new JsBinaryOperation (sourceInfo , JsBinaryOperator .COMMA , sideEffect , main );
687+ }
688+
628689 /**
629690 * Simplify short circuit OR expressions.
630691 *
631692 * <pre>
632- * if (true() || isWhatever()) -> if (true())
633- * if (false || isWhatever()) -> if (isWhatever()), unless side effects
693+ * return false() || isWhatever() -> return isWhatever(), unless false() has side effects
694+ * return true() && isWhatever() -> return true()
695+ * </pre>
696+ * In boolean context also
697+ * <pre>
698+ * if (isWhatever() || false()) -> if (isWhatever()) unless false() has side effects
699+ * if (isWhatever() || true()) -> if (true()) unless isWhatever() side effects
634700 * </pre>
635701 */
636- protected static JsExpression shortCircuitOr (JsBinaryOperation expr ) {
702+ protected static JsExpression shortCircuitOr (JsBinaryOperation expr ,
703+ Map <JsExpression , EvalMode > evalContext ) {
637704 JsExpression arg1 = expr .getArg1 ();
638705 JsExpression arg2 = expr .getArg2 ();
639706 if (arg1 instanceof CanBooleanEval ) {
640707 CanBooleanEval eval1 = (CanBooleanEval ) arg1 ;
641- if (eval1 .isBooleanTrue ()) {
708+ if (eval1 .isBooleanFalse ()) {
709+ return withSideEffect (arg2 , arg1 , expr .getSourceInfo ());
710+ } else if (eval1 .isBooleanTrue ()) {
711+ return arg1 ;
712+ }
713+ }
714+ if (arg2 instanceof CanBooleanEval && evalContext .containsKey (expr )) {
715+ CanBooleanEval eval2 = (CanBooleanEval ) arg2 ;
716+ if (eval2 .isBooleanFalse () && !arg2 .hasSideEffects ()) {
642717 return arg1 ;
643- } else if (eval1 . isBooleanFalse () && ! arg1 . hasSideEffects ()) {
644- return arg2 ;
718+ } else if (eval2 . isBooleanTrue ()) {
719+ return withSideEffect ( arg2 , arg1 , expr . getSourceInfo ()) ;
645720 }
646721 }
647722 return expr ;
0 commit comments