fix corner cases in `evaluate` & `reduce_vars` (#3879)
authorAlex Lam S.L <alexlamsl@gmail.com>
Mon, 11 May 2020 14:46:00 +0000 (15:46 +0100)
committerGitHub <noreply@github.com>
Mon, 11 May 2020 14:46:00 +0000 (22:46 +0800)
fixes #3878

lib/compress.js
test/compress/evaluate.js

index 1b5d1a3..08abde9 100644 (file)
@@ -858,7 +858,7 @@ merge(Compressor.prototype, {
             d.assignments++;
             var fixed = d.fixed;
             if (!fixed) return;
-            exp.fixed = d.fixed = function() {
+            d.fixed = function() {
                 var value = fixed instanceof AST_Node ? fixed : fixed();
                 return value && make_node(AST_Binary, node, {
                     operator: node.operator.slice(0, -1),
@@ -871,6 +871,13 @@ merge(Compressor.prototype, {
                     })
                 });
             };
+            exp.fixed = node instanceof AST_UnaryPrefix ? d.fixed : function() {
+                var value = fixed instanceof AST_Node ? fixed : fixed();
+                return value && make_node(AST_UnaryPrefix, node, {
+                    operator: "+",
+                    expression: value
+                });
+            };
             if (!safe) return;
             d.references.push(exp);
             mark(tw, d, true);
@@ -3233,6 +3240,12 @@ merge(Compressor.prototype, {
                     && unaryPrefix[this.operator];
             }
         });
+        function modified(sym) {
+            if (!(sym instanceof AST_SymbolRef)) return;
+            sym.definition().references.forEach(function(node) {
+                delete node._eval;
+            });
+        }
         def(AST_Statement, function() {
             throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
         });
@@ -3246,6 +3259,7 @@ merge(Compressor.prototype, {
             if (this.operator != "=") return this;
             var node = this.right;
             var value = node._eval(compressor, ignore_side_effects, cached, depth);
+            modified(this.left);
             return value === node ? this : value;
         });
         def(AST_Sequence, function(compressor, ignore_side_effects, cached, depth) {
@@ -3317,7 +3331,7 @@ merge(Compressor.prototype, {
             if (!non_converting_unary[op]) depth++;
             var v = e._eval(compressor, ignore_side_effects, cached, depth);
             if (v === e) {
-                if (ignore_side_effects && op == "void") return void 0;
+                if (ignore_side_effects && op == "void") return;
                 return this;
             }
             switch (op) {
@@ -3327,7 +3341,7 @@ merge(Compressor.prototype, {
                 // so cannot evaluate reliably
                 if (v instanceof RegExp) return this;
                 return typeof v;
-              case "void": return void v;
+              case "void": return;
               case "~": return ~v;
               case "-": return -v;
               case "+": return +v;
@@ -3335,11 +3349,22 @@ merge(Compressor.prototype, {
               case "--":
                 if (!(e instanceof AST_SymbolRef)) return this;
                 var refs = e.definition().references;
-                if (refs[refs.length - 1] !== e) return this;
-                return HOP(e, "_eval") ? +(op[0] + 1) + +v : v;
+                if (!ignore_side_effects && refs[refs.length - 1] !== e) return this;
+                if (HOP(e, "_eval")) v = +(op[0] + 1) + +v;
+                modified(e);
+                return v;
             }
             return this;
         });
+        def(AST_UnaryPostfix, function(compressor, ignore_side_effects, cached, depth) {
+            var e = this.expression;
+            if (!(e instanceof AST_SymbolRef)) return this;
+            var refs = e.definition().references;
+            if (!ignore_side_effects && refs[refs.length - 1] !== e) return this;
+            var v = e._eval(compressor, ignore_side_effects, cached, depth + 1);
+            modified(e);
+            return v === e ? this : +v;
+        });
         var non_converting_binary = makePredicate("&& || === !==");
         def(AST_Binary, function(compressor, ignore_side_effects, cached, depth) {
             if (!non_converting_binary[this.operator]) depth++;
@@ -3514,14 +3539,14 @@ merge(Compressor.prototype, {
                             }
                             if (node instanceof AST_Scope && node !== fn) return true;
                         }));
-                        if (!found) return void 0;
+                        if (!found) return;
                     }
                     return this;
                 }
                 var args = eval_args(this.args);
-                if (!args) return this;
-                if (!stat.value) return undefined;
-                if (!all(fn.argnames, function(sym, i) {
+                if (!args && !ignore_side_effects) return this;
+                if (!stat.value) return;
+                if (args && !all(fn.argnames, function(sym, i) {
                     var value = args[i];
                     var def = sym.definition();
                     if (def.orig[def.orig.length - 1] !== sym) return false;
@@ -3532,7 +3557,7 @@ merge(Compressor.prototype, {
                         cached.push(node);
                     });
                     return true;
-                })) return this;
+                }) && !ignore_side_effects) return this;
                 fn.evaluating = true;
                 var val = stat.value._eval(compressor, ignore_side_effects, cached, depth);
                 delete fn.evaluating;
index a46d38a..a4bee8c 100644 (file)
@@ -2326,3 +2326,46 @@ void_returns_recursive: {
         "undefined",
     ]
 }
+
+issue_3878_1: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var b = function(a) {
+            return (a = 0) == (a && this > (a += 0));
+        }();
+        console.log(b ? "PASS" : "FAIL");
+    }
+    expect: {
+        var b = function(a) {
+            return (a = 0) == (a && this > (a += 0));
+        }();
+        console.log(b ? "PASS" : "FAIL");
+    }
+    expect_stdout: "PASS"
+}
+
+issue_3878_2: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = "foo";
+        a++ + a;
+        a && a;
+        console.log(a);
+    }
+    expect: {
+        var a = "foo";
+        a++ + a;
+        a;
+        console.log(a);
+    }
+    expect_stdout: "NaN"
+}