fix corner cases in aliasing of global variables (#4039)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 6 Aug 2020 08:39:50 +0000 (09:39 +0100)
committerGitHub <noreply@github.com>
Thu, 6 Aug 2020 08:39:50 +0000 (09:39 +0100)
fixes #4038

lib/compress.js
test/compress/collapse_vars.js
test/compress/reduce_vars.js
test/ufuzz/index.js

index 650a763..7131e10 100644 (file)
@@ -167,7 +167,7 @@ merge(Compressor.prototype, {
         if (def.global) for (var i = 0; i < def.orig.length; i++)
             if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
                 return true;
-        return false;
+        return def.undeclared;
     },
     compress: function(node) {
         node = node.resolve_defines(this);
@@ -1386,6 +1386,7 @@ merge(Compressor.prototype, {
                     var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor);
                     var scan_rhs = foldable(candidate);
                     if (!scan_lhs && !scan_rhs) continue;
+                    var read_toplevel = false;
                     var modify_toplevel = false;
                     // Locate symbols which may execute code outside of scanning range
                     var lvalues = get_lvalues(candidate);
@@ -1548,8 +1549,10 @@ merge(Compressor.prototype, {
                     return lvalues.has(node.name.name) || side_effects && may_modify(node.name);
                 }
                 var sym = is_lhs(node.left, node);
-                if (sym && lvalues.has(sym.name)) return true;
-                if (sym instanceof AST_PropAccess) return true;
+                if (!sym) return false;
+                return lvalues.has(sym.name)
+                    || sym instanceof AST_PropAccess
+                    || read_toplevel && compressor.exposed(sym.definition());
             }
 
             function extract_args() {
@@ -1936,26 +1939,43 @@ merge(Compressor.prototype, {
                 };
             }
 
+            function may_be_global(node) {
+                if (node instanceof AST_SymbolRef) {
+                    node = node.fixed_value();
+                    if (!node) return true;
+                }
+                if (node instanceof AST_Assign) return node.operator == "=" && may_be_global(node.right);
+                return node instanceof AST_PropAccess || node instanceof AST_This;
+            }
+
             function get_lvalues(expr) {
                 var lvalues = new Dictionary();
-                if (candidate instanceof AST_VarDef) lvalues.add(candidate.name.name, lhs);
-                var scan_iife = scope instanceof AST_Toplevel;
+                if (expr instanceof AST_VarDef) lvalues.add(expr.name.name, lhs);
+                var scan_toplevel = scope instanceof AST_Toplevel;
                 var tw = new TreeWalker(function(node) {
-                    if (scan_iife && node.TYPE == "Call") {
-                        var exp = node.expression;
-                        if (exp instanceof AST_PropAccess) return;
-                        if (exp instanceof AST_Function && !exp.contains_this()) return;
-                        modify_toplevel = true;
-                        scan_iife = false;
-                        return;
-                    }
                     var value;
                     if (node instanceof AST_SymbolRef) {
                         value = node.fixed_value() || node;
                     } else if (node instanceof AST_This) {
                         value = node;
                     }
-                    if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0));
+                    if (value) {
+                        lvalues.add(node.name, is_modified(compressor, tw, node, value, 0));
+                    } else if (scan_toplevel) {
+                        if (node.TYPE == "Call") {
+                            if (modify_toplevel) return;
+                            var exp = node.expression;
+                            if (exp instanceof AST_PropAccess) return;
+                            if (exp instanceof AST_Function && !exp.contains_this()) return;
+                            modify_toplevel = true;
+                        } else if (node instanceof AST_PropAccess && may_be_global(node.expression)) {
+                            if (node === lhs && !(expr instanceof AST_Unary)) {
+                                modify_toplevel = true;
+                            } else {
+                                read_toplevel = true;
+                            }
+                        }
+                    }
                 });
                 expr.walk(tw);
                 return lvalues;
index 109bbde..d68c10e 100644 (file)
@@ -8317,3 +8317,61 @@ issue_4012: {
     }
     expect_stdout: "PASS"
 }
+
+global_assign: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        this.A = "FAIL";
+        A = "PASS";
+        B = "FAIL";
+        console.log(A);
+    }
+    expect: {
+        this.A = "FAIL";
+        A = "PASS";
+        B = "FAIL";
+        console.log(A);
+    }
+    expect_stdout: "PASS"
+}
+
+global_read: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 0;
+        a = this.A;
+        A = 1;
+        a ? console.log("FAIL") : console.log("PASS");
+    }
+    expect: {
+        var a = 0;
+        a = this.A;
+        A = 1;
+        a ? console.log("FAIL") : console.log("PASS");
+    }
+    expect_stdout: "PASS"
+}
+
+issue_4038: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        var a = 0;
+        a = this;
+        a = a.A;
+        A = 1;
+        a ? console.log("FAIL") : console.log("PASS");
+    }
+    expect: {
+        var a = 0;
+        a = (a = this).A;
+        A = 1;
+        a ? console.log("FAIL") : console.log("PASS");
+    }
+    expect_stdout: "PASS"
+}
index 85969f9..77782cf 100644 (file)
@@ -7402,7 +7402,27 @@ issue_4030: {
     }
     expect: {
         A = "PASS";
-        console.log("PASS");
+        console.log(A);
+    }
+    expect_stdout: "PASS"
+}
+
+global_assign: {
+    options = {
+        evaluate: true,
+        reduce_vars: true,
+        toplevel: true,
+        unused: true,
+    }
+    input: {
+        A = "FAIL";
+        this.A = "PASS";
+        console.log(A);
+    }
+    expect: {
+        A = "FAIL";
+        this.A = "PASS";
+        console.log(A);
     }
     expect_stdout: "PASS"
 }
index 1123d57..9e8ca8d 100644 (file)
@@ -1175,7 +1175,8 @@ function log(options) {
 }
 
 function sort_globals(code) {
-    return "var " + sandbox.run_code("throw Object.keys(this).sort();" + code).join(",") + ";" + code;
+    var globals = sandbox.run_code("throw Object.keys(this).sort();" + code);
+    return globals.length ? "var " + globals.join(",") + ";" + code : code;
 }
 
 function fuzzy_match(original, uglified) {