fix corner cases in `merge_vars` (#4108)
authorAlex Lam S.L <alexlamsl@gmail.com>
Tue, 15 Sep 2020 20:43:01 +0000 (21:43 +0100)
committerGitHub <noreply@github.com>
Tue, 15 Sep 2020 20:43:01 +0000 (04:43 +0800)
fixes #4107
fixes #4109
fixes #4110
fixes #4111

lib/compress.js
test/compress/merge_vars.js

index 51f6526..52ed0e6 100644 (file)
@@ -4309,15 +4309,16 @@ merge(Compressor.prototype, {
 
     AST_Scope.DEFMETHOD("merge_variables", function(compressor) {
         if (!compressor.option("merge_vars")) return;
-        var self = this, segment;
+        var self = this, segment = null;
         var first = [], last = [], index = 0;
+        var declarations = Object.create(null);
         var references = Object.create(null);
         var prev = Object.create(null);
         var tw = new TreeWalker(function(node, descend) {
             if (node instanceof AST_Assign) {
-                if (node.operator != "=") return;
                 var sym = node.left;
                 if (!(sym instanceof AST_SymbolRef)) return;
+                if (node.operator != "=") mark(sym);
                 node.right.walk(tw);
                 mark(sym, true);
                 return true;
@@ -4325,58 +4326,54 @@ merge(Compressor.prototype, {
             if (node instanceof AST_Binary) {
                 if (!lazy_op[node.operator]) return;
                 node.left.walk(tw);
-                var save = segment;
-                segment = node;
+                push();
                 node.right.walk(tw);
-                segment = save;
+                pop();
                 return true;
             }
             if (node instanceof AST_Conditional) {
                 node.condition.walk(tw);
-                var save = segment;
-                segment = node.consequent;
+                push();
                 node.consequent.walk(tw);
-                segment = node.alternative;
+                pop();
+                push();
                 node.alternative.walk(tw);
-                segment = save;
+                pop();
                 return true;
             }
             if (node instanceof AST_For) {
                 if (node.init) node.init.walk(tw);
-                var save = segment;
-                segment = node;
+                push();
                 if (node.condition) node.condition.walk(tw);
                 node.body.walk(tw);
                 if (node.step) node.step.walk(tw);
-                segment = save;
+                pop();
                 return true;
             }
             if (node instanceof AST_ForIn) {
                 node.object.walk(tw);
-                var save = segment;
-                segment = node;
+                push();
                 node.init.walk(tw);
                 node.body.walk(tw);
-                segment = save;
+                pop();
                 return true;
             }
             if (node instanceof AST_If) {
                 node.condition.walk(tw);
-                var save = segment;
-                segment = node;
+                push();
                 node.body.walk(tw);
+                pop();
                 if (node.alternative) {
-                    segment = node.alternative;
+                    push();
                     node.alternative.walk(tw);
+                    pop();
                 }
-                segment = save;
                 return true;
             }
             if (node instanceof AST_IterationStatement) {
-                var save = segment;
-                segment = node;
+                push();
                 descend();
-                segment = save;
+                pop();
                 return true;
             }
             if (node instanceof AST_Scope) {
@@ -4384,35 +4381,32 @@ merge(Compressor.prototype, {
                     references[node.variables.get("arguments").id] = false;
                     if (node.name) references[node.name.definition().id] = false;
                 }
-                var save = segment;
-                segment = node;
+                push();
                 descend();
-                segment = save;
+                pop();
                 return true;
             }
             if (node instanceof AST_Switch) {
                 node.expression.walk(tw);
-                var save = segment, first = true;
+                var first = true;
                 node.body.forEach(function(branch) {
                     if (branch instanceof AST_Default) return;
-                    if (first) {
-                        first = false;
-                    } else {
-                        segment = branch.expression;
-                    }
+                    if (!first) push();
                     branch.expression.walk(tw);
+                    if (!first) pop();
+                    first = false;
                 });
                 node.body.forEach(function(branch) {
-                    segment = branch;
+                    push();
                     branch.body.forEach(function(stat) {
                         stat.walk(tw);
                     });
+                    pop();
                 });
-                segment = save;
                 return true;
             }
             if (node instanceof AST_SymbolFunarg) {
-                mark(node, true);
+                if (!node.__unused) mark(node, true);
                 return true;
             }
             if (node instanceof AST_SymbolRef) {
@@ -4420,16 +4414,19 @@ merge(Compressor.prototype, {
                 return true;
             }
             if (node instanceof AST_Try) {
-                var save = segment;
-                segment = node;
-                node.body.forEach(function(branch) {
-                    branch.walk(tw);
+                push();
+                node.body.forEach(function(stat) {
+                    stat.walk(tw);
                 });
+                pop();
                 if (node.bcatch) {
-                    segment = node.bcatch;
-                    node.bcatch.walk(tw);
+                    references[node.bcatch.argname.definition().id] = false;
+                    push();
+                    node.bcatch.body.forEach(function(stat) {
+                        stat.walk(tw);
+                    });
+                    pop();
                 }
-                segment = save;
                 if (node.bfinally) node.bfinally.walk(tw);
                 return true;
             }
@@ -4441,9 +4438,14 @@ merge(Compressor.prototype, {
                 return true;
             }
             if (node instanceof AST_VarDef) {
-                if (!node.value) return true;
-                node.value.walk(tw);
-                mark(node.name, true);
+                if (node.value) {
+                    node.value.walk(tw);
+                    mark(node.name, true);
+                } else {
+                    var id = node.name.definition().id;
+                    if (!(id in declarations)) declarations[id] = [];
+                    declarations[id].push(node.name);
+                }
                 return true;
             }
         });
@@ -4460,28 +4462,41 @@ merge(Compressor.prototype, {
                 var tail = last.pop();
                 if (!tail) continue;
                 if (tail.index > head.index) continue;
-                if (!references[tail.definition.id]) continue;
-                if (references[def.id].segment !== references[tail.definition.id].segment) {
+                var id = tail.definition.id;
+                if (!references[id]) continue;
+                if (references[def.id].segment !== references[id].segment) {
                     skipped.unshift(tail);
                     continue;
                 }
-                var orig = [], refs = [];
-                references[tail.definition.id].forEach(function(sym) {
-                    push(sym);
+                if (id in declarations) declarations[id].forEach(function(sym) {
+                    sym.thedef = def;
+                    sym.name = def.name;
+                    def.orig.push(sym);
+                });
+                references[id].forEach(function(sym) {
                     sym.thedef = def;
                     sym.name = def.name;
+                    if (sym instanceof AST_SymbolRef) {
+                        def.references.push(sym);
+                    } else {
+                        def.orig.push(sym);
+                    }
                 });
-                references[def.id].forEach(push);
-                def.orig = orig;
-                def.refs = refs;
-                def.eliminated = def.replaced = 0;
                 def.fixed = tail.definition.fixed && def.fixed;
-                merged[tail.definition.id] = def;
+                merged[id] = def;
                 break;
             } while (last.length);
             if (skipped.length) last = last.concat(skipped);
         }
 
+        function push() {
+            segment = Object.create(segment);
+        }
+
+        function pop() {
+            segment = Object.getPrototypeOf(segment);
+        }
+
         function read(def) {
             prev[def.id] = last.length;
             last.push({
@@ -4516,14 +4531,6 @@ merge(Compressor.prototype, {
                 }
             }
         }
-
-        function push(sym) {
-            if (sym instanceof AST_SymbolRef) {
-                refs.push(sym);
-            } else {
-                orig.push(sym);
-            }
-        }
     });
 
     AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
index 4c5f6da..f83643e 100644 (file)
@@ -324,3 +324,129 @@ issue_4103: {
         "NaN",
     ]
 }
+
+issue_4107: {
+    options = {
+        keep_fargs: "strict",
+        merge_vars: true,
+        reduce_vars: true,
+        unused: true,
+    }
+    input: {
+        (function() {
+            function f(b, b, c) {
+                var d = 1 && a, a = console || c;
+                console.log(typeof a);
+            }
+            f();
+        })();
+        console.log(typeof a);
+    }
+    expect: {
+        (function() {
+            (function(c) {
+                var a = console || c;
+                console.log(typeof a);
+            })();
+        })();
+        console.log(typeof a);
+    }
+    expect_stdout: [
+        "object",
+        "undefined",
+    ]
+}
+
+issue_4109: {
+    options = {
+        ie8: true,
+        merge_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = "foo";
+        try {
+            throw "bar";
+        } catch (e) {
+            console.log(e);
+        } finally {
+            var o = a;
+            for (var k in o);
+            (function() {
+                a++;
+            });
+        }
+        console.log(a);
+    }
+    expect: {
+        var a = "foo";
+        try {
+            throw "bar";
+        } catch (e) {
+            console.log(e);
+        } finally {
+            var o = a;
+            for (var k in o);
+            (function() {
+                a++;
+            });
+        }
+        console.log(a);
+    }
+    expect_stdout: [
+        "bar",
+        "foo",
+    ]
+}
+
+issue_4110: {
+    options = {
+        merge_vars: true,
+        toplevel: true,
+    }
+    input: {
+        while (a)
+            var c;
+        var b, a = c += b = a;
+        console.log(b);
+    }
+    expect: {
+        while (a)
+            var c;
+        var b, a = c += b = a;
+        console.log(b);
+    }
+    expect_stdout: "undefined"
+}
+
+issue_4111: {
+    options = {
+        join_vars: true,
+        loops: true,
+        merge_vars: true,
+        toplevel: true,
+    }
+    input: {
+        var a = 0;
+        if (a)
+            a = 0;
+        else
+            for (var b = 0; --b && ++a < 2;) {
+                var o = console, k;
+                for (k in o);
+            }
+        console.log(a);
+    }
+    expect: {
+        var a = 0;
+        if (a)
+            a = 0;
+        else
+            for (var b = 0; --b && ++a < 2;) {
+                var o = console, k;
+                for (k in o);
+            }
+        console.log(a);
+    }
+    expect_stdout: "2"
+}