fix corner cases in `assignments`, `reduce_vars` & `unused` (#3950)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 4 Jun 2020 20:06:43 +0000 (21:06 +0100)
committerGitHub <noreply@github.com>
Thu, 4 Jun 2020 20:06:43 +0000 (04:06 +0800)
fixes #3949
fixes #3951

lib/compress.js
test/compress/assignments.js [moved from test/compress/assignment.js with 88% similarity]
test/compress/collapse_vars.js
test/compress/drop-unused.js
test/compress/evaluate.js
test/compress/join_vars.js
test/compress/keep_fargs.js
test/compress/reduce_vars.js
test/compress/side_effects.js

index f3fc5e0..17b3ecf 100644 (file)
@@ -384,10 +384,10 @@ merge(Compressor.prototype, {
                 reset_def(tw, compressor, def);
                 if (def.fixed === null) {
                     def.safe_ids = tw.safe_ids;
-                    mark(tw, def, true);
+                    mark(tw, def);
                 } else if (def.fixed) {
                     tw.loop_ids[def.id] = tw.in_loop;
-                    mark(tw, def, true);
+                    mark(tw, def);
                 }
             });
             scope.may_call_this = function() {
@@ -446,8 +446,8 @@ merge(Compressor.prototype, {
             tw.safe_ids = Object.getPrototypeOf(tw.safe_ids);
         }
 
-        function mark(tw, def, safe) {
-            tw.safe_ids[def.id] = safe && {};
+        function mark(tw, def) {
+            tw.safe_ids[def.id] = {};
         }
 
         function push_ref(def, ref) {
@@ -459,10 +459,11 @@ merge(Compressor.prototype, {
             if (def.single_use == "m") return false;
             var safe = tw.safe_ids[def.id];
             if (safe) {
-                if (!HOP(tw.safe_ids, def.id)) safe.read = safe.read ? true : tw.safe_ids;
+                if (!HOP(tw.safe_ids, def.id)) safe.read = safe.read && safe.read !== tw.safe_ids ? true : tw.safe_ids;
                 if (def.fixed == null) {
                     if (is_arguments(def)) return false;
                     if (def.global && def.name == "arguments") return false;
+                    tw.loop_ids[def.id] = null;
                     def.fixed = make_node(AST_Undefined, def.orig[0]);
                     return true;
                 }
@@ -478,20 +479,23 @@ merge(Compressor.prototype, {
                 delete def.safe_ids;
                 return true;
             }
+            if (def.fixed === false) return false;
             var safe = tw.safe_ids[def.id];
             if (!HOP(tw.safe_ids, def.id)) {
                 if (!safe) return false;
-                safe.assign = safe.assign ? true : tw.safe_ids;
+                if (safe.read && def.scope !== tw.find_parent(AST_Scope)) return false;
+                safe.assign = safe.assign && safe.assign !== tw.safe_ids ? true : tw.safe_ids;
             }
-            if (!safe_to_read(tw, def)) return false;
-            if (def.fixed === false) return false;
-            if (def.fixed != null && safe.read && safe.read !== tw.safe_ids) return false;
-            return all(def.orig, function(sym) {
+            if (def.fixed != null && safe.read) {
+                if (safe.read !== tw.safe_ids) return false;
+                if (tw.loop_ids[def.id] !== tw.in_loop) return false;
+            }
+            return safe_to_read(tw, def) && all(def.orig, function(sym) {
                 return !(sym instanceof AST_SymbolLambda);
             });
         }
 
-        function ref_once(tw, compressor, def) {
+        function ref_once(compressor, def) {
             return compressor.option("unused")
                 && !def.scope.pinned()
                 && def.references.length - def.recursive_refs == 1;
@@ -568,22 +572,17 @@ merge(Compressor.prototype, {
             }
             var d = sym.definition();
             d.assignments++;
+            var fixed = d.fixed;
             var eq = node.operator == "=";
             var value = eq ? node.right : node;
             if (is_modified(compressor, tw, node, value, 0)) return;
-            var safe = (eq || safe_to_read(tw, d)) && safe_to_assign(tw, d);
-            var fixed = d.fixed;
-            if (safe) {
+            node.right.walk(tw);
+            if ((eq || safe_to_read(tw, d)) && safe_to_assign(tw, d)) {
                 push_ref(d, sym);
-                mark(tw, d, false);
-                node.right.walk(tw);
-                mark(tw, d, true);
-                if (eq) mark_escaped(tw, d, sym.scope, node, value, 0, 1);
-            } else {
-                descend();
-            }
-            if (fixed !== false && d.fixed !== false) {
+                mark(tw, d);
                 if (eq) {
+                    tw.loop_ids[d.id] = tw.in_loop;
+                    mark_escaped(tw, d, sym.scope, node, value, 0, 1);
                     sym.fixed = d.fixed = function() {
                         return node.right;
                     };
@@ -600,6 +599,9 @@ merge(Compressor.prototype, {
                 }
                 sym.fixed.assigns = eq || !fixed.assigns ? [] : fixed.assigns.slice();
                 sym.fixed.assigns.push(node);
+            } else {
+                sym.walk(tw);
+                d.fixed = false;
             }
             return true;
         });
@@ -739,7 +741,7 @@ merge(Compressor.prototype, {
                     var d = arg.definition();
                     if (d.fixed === undefined && (!fn.uses_arguments || tw.has_directive("use strict"))) {
                         tw.loop_ids[d.id] = tw.in_loop;
-                        mark(tw, d, true);
+                        mark(tw, d);
                         var value = iife.args[i];
                         d.fixed = function() {
                             var j = fn.argnames.indexOf(arg);
@@ -800,7 +802,7 @@ merge(Compressor.prototype, {
                 var recursive = recursive_ref(tw, d);
                 if (recursive) {
                     d.recursive_refs++;
-                } else if (value && ref_once(tw, compressor, d)) {
+                } else if (value && ref_once(compressor, d)) {
                     d.in_loop = tw.loop_ids[d.id] !== tw.in_loop;
                     d.single_use = value instanceof AST_Lambda
                             && !value.pinned()
@@ -864,15 +866,10 @@ merge(Compressor.prototype, {
             }
             var d = exp.definition();
             d.assignments++;
-            var safe = safe_to_read(tw, d) && safe_to_assign(tw, d);
             var fixed = d.fixed;
-            if (safe) {
+            if (safe_to_read(tw, d) && safe_to_assign(tw, d)) {
                 push_ref(d, exp);
-                mark(tw, d, true);
-            } else {
-                descend();
-            }
-            if (fixed !== false && d.fixed !== false) {
+                mark(tw, d);
                 if (fixed == null) fixed = make_node(AST_Undefined, d.orig[0]);
                 d.fixed = function() {
                     var value = fixed instanceof AST_Node ? fixed : fixed();
@@ -901,29 +898,28 @@ merge(Compressor.prototype, {
                     };
                     exp.fixed.assigns = fixed.assigns;
                 }
+            } else {
+                exp.walk(tw);
+                d.fixed = false;
             }
             return true;
         });
         def(AST_VarDef, function(tw, descend) {
             var node = this;
+            if (!node.value) return;
+            descend();
             var d = node.name.definition();
-            if (node.value) {
-                if (safe_to_assign(tw, d)) {
-                    tw.loop_ids[d.id] = tw.in_loop;
-                    mark(tw, d, false);
-                    descend();
-                    mark(tw, d, true);
-                    if (d.fixed !== false) {
-                        d.fixed = function() {
-                            return node.value;
-                        };
-                        d.fixed.assigns = [ node ];
-                    }
-                    return true;
-                } else {
-                    d.fixed = false;
-                }
+            if (safe_to_assign(tw, d)) {
+                mark(tw, d);
+                tw.loop_ids[d.id] = tw.in_loop;
+                d.fixed = function() {
+                    return node.value;
+                };
+                d.fixed.assigns = [ node ];
+            } else {
+                d.fixed = false;
             }
+            return true;
         });
         def(AST_While, function(tw, descend) {
             var saved_loop = tw.in_loop;
@@ -4710,7 +4706,7 @@ merge(Compressor.prototype, {
                         }
                     }
                 }
-                track_assigns(node_def, sym);
+                if (track_assigns(node_def, sym) && is_lhs(sym, node) !== sym) add_assigns(node_def, sym);
                 return true;
             }
             if (node instanceof AST_SymbolRef) {
@@ -7885,16 +7881,22 @@ merge(Compressor.prototype, {
                     && self.right.left.name == self.left.name
                     && ASSIGN_OPS[self.right.operator]) {
                     // x = x - 2 => x -= 2
-                    self.operator = self.right.operator + "=";
-                    self.right = self.right.right;
+                    return make_node(AST_Assign, self, {
+                        operator: self.right.operator + "=",
+                        left: self.left,
+                        right: self.right.right,
+                    }).optimize(compressor);
                 }
                 else if (self.right.right instanceof AST_SymbolRef
                     && self.right.right.name == self.left.name
                     && ASSIGN_OPS_COMMUTATIVE[self.right.operator]
                     && !self.right.left.has_side_effects(compressor)) {
                     // x = 2 & x => x &= 2
-                    self.operator = self.right.operator + "=";
-                    self.right = self.right.left;
+                    return make_node(AST_Assign, self, {
+                        operator: self.right.operator + "=",
+                        left: self.left,
+                        right: self.right.left,
+                    }).optimize(compressor);
                 }
             }
             if ((self.operator == "-=" || self.operator == "+="
similarity index 88%
rename from test/compress/assignment.js
rename to test/compress/assignments.js
index 2230272..0f87f9a 100644 (file)
@@ -407,3 +407,57 @@ issue_3429_2: {
     }
     expect_stdout: "undefined"
 }
+
+issue_3949_1: {
+    options = {
+        assignments: true,
+        evaluate: true,
+        reduce_vars: true,
+    }
+    input: {
+        var a = 42;
+        function f() {
+            var b = a;
+            b = b >> 2;
+            return 100 + b;
+        }
+        console.log(f());
+    }
+    expect: {
+        var a = 42;
+        function f() {
+            var b = a;
+            b >>= 2;
+            return 100 + b;
+        }
+        console.log(f());
+    }
+    expect_stdout: "110"
+}
+
+issue_3949_2: {
+    options = {
+        assignments: true,
+        evaluate: true,
+        reduce_vars: true,
+    }
+    input: {
+        var a = 42;
+        function f() {
+            var b = a;
+            b = 5 & b;
+            return 100 + b;
+        }
+        console.log(f());
+    }
+    expect: {
+        var a = 42;
+        function f() {
+            var b = a;
+            b &= 5;
+            return 100 + b;
+        }
+        console.log(f());
+    }
+    expect_stdout: "100"
+}
index 546e0d3..cea5ca8 100644 (file)
@@ -3001,6 +3001,7 @@ issue_2298: {
     expect: {
         !function() {
             (function() {
+                0;
                 try {
                     !function(b) {
                         (void 0)[1] = "foo";
index b21ca39..f3aeab2 100644 (file)
@@ -2285,7 +2285,7 @@ issue_3598: {
         try {
             (function() {
                 a = "PASS";
-                var c = (void (c.p = 0))[!1];
+                (void ((void 0).p = 0))[!1];
             })();
         } catch (e) {}
         console.log(a);
@@ -2557,10 +2557,9 @@ issue_3899: {
         console.log(typeof a);
     }
     expect: {
-        0;
-        var a = function() {
+        function a() {
             return 2;
-        };
+        }
         console.log(typeof a);
     }
     expect_stdout: "function"
@@ -2625,3 +2624,31 @@ assign_if_assign_read: {
     }
     expect_stdout: "PASS"
 }
+
+issue_3951: {
+    options = {
+        pure_getters: "strict",
+        reduce_vars: true,
+        side_effects: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var a = console.log("PASS");
+        console.log(a);
+        a = "0";
+        console.log(a.p = 0);
+        a && a;
+    }
+    expect: {
+        var a = console.log("PASS");
+        console.log(a);
+        a = "0";
+        console.log(a.p = 0);
+    }
+    expect_stdout: [
+        "PASS",
+        "undefined",
+        "0",
+    ]
+}
index 4e3bc72..fd8deab 100644 (file)
@@ -1579,9 +1579,9 @@ issue_2968_1: {
     expect: {
         var c = "FAIL";
         (function() {
-            b = -(a = 42),
-            void ((a <<= 0) && (a[(c = "PASS", 0 >>> (b += 1))] = 0));
-            var a, b;
+            a = 42,
+            void ((a <<= 0) && (a[(c = "PASS", 0)] = 0));
+            var a;
         })();
         console.log(c);
     }
@@ -2341,10 +2341,7 @@ issue_3878_1: {
         console.log(b ? "PASS" : "FAIL");
     }
     expect: {
-        var b = function(a) {
-            return (a = 0) == (a && this > (a += 0));
-        }();
-        console.log(b ? "PASS" : "FAIL");
+        console.log(true ? "PASS" : "FAIL");
     }
     expect_stdout: "PASS"
 }
@@ -2435,12 +2432,11 @@ issue_3903: {
         console.log(d);
     }
     expect: {
-        var a = "PASS";
         function f(b, c) {
             return console, c;
         }
-        var d = f(f(), a = a);
-        console.log(d);
+        f(f(), "PASS");
+        console.log("PASS");
     }
     expect_stdout: "PASS"
 }
@@ -2649,7 +2645,7 @@ issue_3933: {
     }
     expect: {
         (function(a, b) {
-            1, (b ^= 1), console.log("PASS");
+            1, 1, console.log("PASS");
         })();
     }
     expect_stdout: "PASS"
index 590f997..9f0b35b 100644 (file)
@@ -808,9 +808,9 @@ issue_3795: {
     }
     expect: {
         var a = "FAIL", d = function() {
-            if (a = 42, d) return -1;
+            if (void 0) return -1;
             a = "PASS";
-        }();
+        }(a = 42);
         console.log(a, d);
     }
     expect_stdout: "PASS undefined"
index e173708..e42b397 100644 (file)
@@ -306,6 +306,7 @@ issue_2298: {
     expect: {
         !function() {
             (function() {
+                0;
                 try {
                     !function() {
                         (void 0)[1] = "foo";
index cb7599a..3368cc6 100644 (file)
@@ -1520,8 +1520,7 @@ func_inline: {
     }
     expect: {
         function f() {
-            console.log(1 + h());
-            var h;
+            console.log(1 + (void 0)());
         }
     }
 }
@@ -2671,8 +2670,8 @@ var_assign_6: {
     }
     expect: {
         !function() {
-            var a = function(){}(a = 1);
-            console.log(a);
+            (function(){}());
+            console.log(void 0);
         }();
     }
     expect_stdout: "undefined"
@@ -7111,3 +7110,166 @@ issue_3922: {
     }
     expect_stdout: "PASS"
 }
+
+issue_3949_1: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+    }
+    input: {
+        (function f(a) {
+            var a = void (a = 0, g);
+            function g() {
+                console.log(typeof a);
+            }
+            g();
+        })();
+    }
+    expect: {
+        (function f(a) {
+            var a = void (a = 0, g);
+            function g() {
+                console.log(typeof a);
+            }
+            g();
+        })();
+    }
+    expect_stdout: "undefined"
+}
+
+issue_3949_2: {
+    options = {
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function f(a) {
+            var a = void (a = 0, g);
+            function g() {
+                console.log(typeof a);
+            }
+            g();
+        })();
+    }
+    expect: {
+        (function(a) {
+            a = void (a = 0, g);
+            function g() {
+                console.log(typeof a);
+            }
+            g();
+        })();
+    }
+    expect_stdout: "undefined"
+}
+
+issue_3949_3: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+    }
+    input: {
+        function f() {}
+        for (var a, i = 3; 0 <= --i; ) {
+            a = f;
+            console.log(a === b);
+            var b = a;
+        }
+    }
+    expect: {
+        function f() {}
+        for (var a, i = 3; 0 <= --i; ) {
+            a = f;
+            console.log(a === b);
+            var b = a;
+        }
+    }
+    expect_stdout: [
+        "false",
+        "true",
+        "true",
+    ]
+}
+
+issue_3949_4: {
+    options = {
+        reduce_vars: true,
+        unused: true,
+        toplevel: true,
+    }
+    input: {
+        function f() {}
+        for (var a, i = 3; 0 <= --i; ) {
+            a = f;
+            console.log(a === b);
+            var b = a;
+        }
+    }
+    expect: {
+        function f() {}
+        for (var a, i = 3; 0 <= --i; ) {
+            a = f;
+            console.log(a === b);
+            var b = a;
+        }
+    }
+    expect_stdout: [
+        "false",
+        "true",
+        "true",
+    ]
+}
+
+local_assignment_lambda: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        sequences: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var a = "FAIL";
+        function f() {
+            a = "PASS";
+            console.log(a);
+        }
+        f();
+        f();
+    }
+    expect: {
+        function f() {
+            console.log("PASS");
+        }
+        f(),
+        f();
+    }
+    expect_stdout: [
+        "PASS",
+        "PASS",
+    ]
+}
+
+local_assignment_loop: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        sequences: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        var a = "FAIL";
+        do {
+            a = "PASS";
+            console.log(a);
+        } while (!console);
+    }
+    expect: {
+        do {
+            console.log("PASS");
+        } while (!console);
+    }
+    expect_stdout: "PASS"
+}
index 8c263d4..6328266 100644 (file)
@@ -97,9 +97,8 @@ issue_2233_2: {
         var RegExp;
         UndeclaredGlobal;
         function foo() {
-            var Number;
             AnotherUndeclaredGlobal;
-            Number.isNaN;
+            (void 0).isNaN;
         }
     }
 }