improve `mangle` (#2948)
authorAlex Lam S.L <alexlamsl@gmail.com>
Fri, 23 Feb 2018 15:51:49 +0000 (23:51 +0800)
committerGitHub <noreply@github.com>
Fri, 23 Feb 2018 15:51:49 +0000 (23:51 +0800)
lib/scope.js
test/compress/issue-1704.js
test/compress/issue-1733.js
test/compress/rename.js
test/compress/screw-ie8.js
test/mocha/minify.js

index 6c883c6..87bfe0d 100644 (file)
@@ -83,8 +83,9 @@ SymbolDef.prototype = {
             var def;
             if (def = this.redefined()) {
                 this.mangled_name = def.mangled_name || def.name;
-            } else
-                this.mangled_name = s.next_mangled(options, this);
+            } else {
+                this.mangled_name = next_mangled_name(s, options, this);
+            }
             if (this.global && cache) {
                 cache.set(this.name, this.mangled_name);
             }
@@ -168,8 +169,8 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
                 var def = scope.find_variable(node);
                 if (node.thedef !== def) {
                     node.thedef = def;
-                    node.reference(options);
                 }
+                node.reference(options);
             }
         }
         else if (node instanceof AST_SymbolCatch) {
@@ -325,56 +326,59 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol, init){
     return symbol.thedef = def;
 });
 
-function next_mangled(scope, options) {
-    var ext = scope.enclosed;
-    out: while (true) {
-        var m = base54(++scope.cname);
-        if (!is_identifier(m)) continue; // skip over "do"
-
-        // https://github.com/mishoo/UglifyJS2/issues/242 -- do not
-        // shadow a name reserved from mangling.
-        if (member(m, options.reserved)) continue;
-
-        // we must ensure that the mangled name does not shadow a name
-        // from some parent scope that is referenced in this or in
-        // inner scopes.
-        for (var i = ext.length; --i >= 0;) {
-            var sym = ext[i];
-            var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
-            if (m == name) continue out;
-        }
-        return m;
+function names_in_use(scope, options) {
+    var names = scope.names_in_use;
+    if (!names) {
+        scope.names_in_use = names = Object.create(scope.mangled_names || null);
+        scope.cname_holes = [];
+        scope.enclosed.forEach(function(def) {
+            if (def.unmangleable(options)) names[def.name] = true;
+        });
     }
+    return names;
 }
 
-AST_Scope.DEFMETHOD("next_mangled", function(options){
-    return next_mangled(this, options);
-});
-
-AST_Toplevel.DEFMETHOD("next_mangled", function(options){
-    var name;
-    do {
-        name = next_mangled(this, options);
-    } while (member(name, this.mangled_names));
-    return name;
-});
-
-AST_Function.DEFMETHOD("next_mangled", function(options, def){
+function next_mangled_name(scope, options, def) {
+    var in_use = names_in_use(scope, options);
+    var holes = scope.cname_holes;
+    var names = Object.create(null);
     // #179, #326
     // in Safari strict mode, something like (function x(x){...}) is a syntax error;
     // a function expression's argument cannot shadow the function expression's name
-
-    var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition();
-
-    // the function's mangled_name is null when keep_fnames is true
-    var tricky_name = tricky_def ? tricky_def.mangled_name || tricky_def.name : null;
-
+    if (scope instanceof AST_Function && scope.name && def.orig[0] instanceof AST_SymbolFunarg) {
+        var tricky_def = scope.name.definition();
+        // the function's mangled_name is null when keep_fnames is true
+        names[tricky_def.mangled_name || tricky_def.name] = true;
+    }
+    var scopes = [ scope ];
+    def.references.forEach(function(sym) {
+        var scope = sym.scope;
+        do {
+            if (scopes.indexOf(scope) < 0) {
+                for (var name in names_in_use(scope, options)) {
+                    names[name] = true;
+                }
+                scopes.push(scope);
+            } else break;
+        } while (scope = scope.parent_scope);
+    });
+    var name;
+    for (var i = 0, len = holes.length; i < len; i++) {
+        name = base54(holes[i]);
+        if (names[name]) continue;
+        holes.splice(i, 1);
+        scope.names_in_use[name] = true;
+        return name;
+    }
     while (true) {
-        var name = next_mangled(this, options);
-        if (!tricky_name || tricky_name != name)
-            return name;
+        name = base54(++scope.cname);
+        if (in_use[name] || !is_identifier(name) || member(name, options.reserved)) continue;
+        if (!names[name]) break;
+        holes.push(scope.cname);
     }
-});
+    scope.names_in_use[name] = true;
+    return name;
+}
 
 AST_Symbol.DEFMETHOD("unmangleable", function(options){
     var def = this.definition();
@@ -419,18 +423,15 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
     // present (and for AST_SymbolRef-s it'll use the mangled name of
     // the AST_SymbolDeclaration that it points to).
     var lname = -1;
-    var to_mangle = [];
-
-    var mangled_names = this.mangled_names = [];
-    if (options.cache) {
-        this.globals.each(collect);
-        if (options.cache.props) {
-            options.cache.props.each(function(mangled_name) {
-                push_uniq(mangled_names, mangled_name);
-            });
-        }
+
+    if (options.cache && options.cache.props) {
+        var mangled_names = this.mangled_names = Object.create(null);
+        options.cache.props.each(function(mangled_name) {
+            mangled_names[mangled_name] = true;
+        });
     }
 
+    var redefined = [];
     var tw = new TreeWalker(function(node, descend){
         if (node instanceof AST_LabeledStatement) {
             // lname is incremented when we get to the AST_Label
@@ -440,8 +441,12 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
             return true;        // don't descend again in TreeWalker
         }
         if (node instanceof AST_Scope) {
-            node.variables.each(collect);
-            return;
+            descend();
+            if (options.cache && node instanceof AST_Toplevel) {
+                node.globals.each(mangle);
+            }
+            node.variables.each(mangle);
+            return true;
         }
         if (node instanceof AST_Label) {
             var name;
@@ -449,17 +454,28 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
             node.mangled_name = name;
             return true;
         }
-        if (!options.ie8 && node instanceof AST_SymbolCatch) {
-            to_mangle.push(node.definition());
-            return;
+        if (!options.ie8 && node instanceof AST_Catch) {
+            var def = node.argname.definition();
+            var redef = def.redefined();
+            if (redef) {
+                redefined.push(def);
+                def.references.forEach(function(ref) {
+                    ref.thedef = redef;
+                    ref.reference(options);
+                    ref.thedef = def;
+                });
+            }
+            descend();
+            if (!redef) mangle(def);
+            return true;
         }
     });
     this.walk(tw);
-    to_mangle.forEach(function(def){ def.mangle(options) });
+    redefined.forEach(mangle);
 
-    function collect(symbol) {
-        if (!member(symbol.name, options.reserved)) {
-            to_mangle.push(symbol);
+    function mangle(def) {
+        if (!member(def.name, options.reserved)) {
+            def.mangle(options);
         }
     }
 });
index 25e4952..043038e 100644 (file)
@@ -104,7 +104,7 @@ mangle_catch_toplevel: {
         }
         console.log(a);
     }
-    expect_exact: 'var o="FAIL";try{throw 1}catch(c){o="PASS"}console.log(o);'
+    expect_exact: 'var c="FAIL";try{throw 1}catch(o){c="PASS"}console.log(c);'
     expect_stdout: "PASS"
 }
 
@@ -148,7 +148,7 @@ mangle_catch_var_toplevel: {
         }
         console.log(a);
     }
-    expect_exact: 'var o="FAIL";try{throw 1}catch(r){var o="PASS"}console.log(o);'
+    expect_exact: 'var r="FAIL";try{throw 1}catch(o){var r="PASS"}console.log(r);'
     expect_stdout: "PASS"
 }
 
@@ -345,3 +345,95 @@ mangle_catch_redef_2_ie8_toplevel: {
     expect_exact: 'try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);'
     expect_stdout: "undefined"
 }
+
+mangle_catch_redef_3: {
+    mangle = {
+        ie8: false,
+        toplevel: false,
+    }
+    input: {
+        var o = "PASS";
+        try {
+            throw 0;
+        } catch (o) {
+            (function() {
+                function f() {
+                    o = "FAIL";
+                }
+                f(), f();
+            })();
+        }
+        console.log(o);
+    }
+    expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);'
+    expect_stdout: "PASS"
+}
+
+mangle_catch_redef_3_toplevel: {
+    mangle = {
+        ie8: false,
+        toplevel: true,
+    }
+    input: {
+        var o = "PASS";
+        try {
+            throw 0;
+        } catch (o) {
+            (function() {
+                function f() {
+                    o = "FAIL";
+                }
+                f(), f();
+            })();
+        }
+        console.log(o);
+    }
+    expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);'
+    expect_stdout: "PASS"
+}
+
+mangle_catch_redef_ie8_3: {
+    mangle = {
+        ie8: true,
+        toplevel: false,
+    }
+    input: {
+        var o = "PASS";
+        try {
+            throw 0;
+        } catch (o) {
+            (function() {
+                function f() {
+                    o = "FAIL";
+                }
+                f(), f();
+            })();
+        }
+        console.log(o);
+    }
+    expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);'
+    expect_stdout: "PASS"
+}
+
+mangle_catch_redef_3_ie8_toplevel: {
+    mangle = {
+        ie8: true,
+        toplevel: true,
+    }
+    input: {
+        var o = "PASS";
+        try {
+            throw 0;
+        } catch (o) {
+            (function() {
+                function f() {
+                    o = "FAIL";
+                }
+                f(), f();
+            })();
+        }
+        console.log(o);
+    }
+    expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);'
+    expect_stdout: "PASS"
+}
index f1e576c..15c4a89 100644 (file)
@@ -15,7 +15,7 @@ function_iife_catch: {
         }
         f();
     }
-    expect_exact: "function f(o){!function(){try{throw 0}catch(c){var o=1;console.log(c,o)}}()}f();"
+    expect_exact: "function f(o){!function(){try{throw 0}catch(o){var c=1;console.log(o,c)}}()}f();"
     expect_stdout: "0 1"
 }
 
@@ -36,7 +36,7 @@ function_iife_catch_ie8: {
         }
         f();
     }
-    expect_exact: "function f(o){!function(){try{throw 0}catch(o){var c=1;console.log(o,c)}}()}f();"
+    expect_exact: "function f(c){!function(){try{throw 0}catch(c){var o=1;console.log(c,o)}}()}f();"
     expect_stdout: "0 1"
 }
 
@@ -61,7 +61,7 @@ function_catch_catch: {
         }
         f();
     }
-    expect_exact: "var o=0;function f(){try{throw 1}catch(c){try{throw 2}catch(o){var o=3;console.log(o)}}console.log(o)}f();"
+    expect_exact: "var o=0;function f(){try{throw 1}catch(o){try{throw 2}catch(c){var c=3;console.log(c)}}console.log(c)}f();"
     expect_stdout: [
         "3",
         "undefined",
index defc6cf..83187df 100644 (file)
@@ -109,7 +109,7 @@ mangle_catch_toplevel: {
         }
         console.log(a);
     }
-    expect_exact: 'var o="FAIL";try{throw 1}catch(c){o="PASS"}console.log(o);'
+    expect_exact: 'var c="FAIL";try{throw 1}catch(o){c="PASS"}console.log(c);'
     expect_stdout: "PASS"
 }
 
@@ -155,7 +155,7 @@ mangle_catch_var_toplevel: {
         }
         console.log(a);
     }
-    expect_exact: 'var o="FAIL";try{throw 1}catch(r){var o="PASS"}console.log(o);'
+    expect_exact: 'var r="FAIL";try{throw 1}catch(o){var r="PASS"}console.log(r);'
     expect_stdout: "PASS"
 }
 
@@ -451,7 +451,7 @@ function_iife_catch: {
         }
         f();
     }
-    expect_exact: "function f(o){!function(){try{throw 0}catch(c){var o=1;console.log(c,o)}}()}f();"
+    expect_exact: "function f(o){!function(){try{throw 0}catch(o){var c=1;console.log(o,c)}}()}f();"
     expect_stdout: "0 1"
 }
 
@@ -473,7 +473,7 @@ function_iife_catch_ie8: {
         }
         f();
     }
-    expect_exact: "function f(o){!function(){try{throw 0}catch(o){var c=1;console.log(o,c)}}()}f();"
+    expect_exact: "function f(c){!function(){try{throw 0}catch(c){var o=1;console.log(c,o)}}()}f();"
     expect_stdout: "0 1"
 }
 
@@ -499,7 +499,7 @@ function_catch_catch: {
         }
         f();
     }
-    expect_exact: "var o=0;function f(){try{throw 1}catch(c){try{throw 2}catch(o){var o=3;console.log(o)}}console.log(o)}f();"
+    expect_exact: "var o=0;function f(){try{throw 1}catch(o){try{throw 2}catch(c){var c=3;console.log(c)}}console.log(c)}f();"
     expect_stdout: [
         "3",
         "undefined",
index 82152b7..b4098b8 100644 (file)
@@ -102,12 +102,12 @@ dont_screw_try_catch: {
         };
     }
     expect: {
-        bad = function(n){
-            return function(t){
+        bad = function(t){
+            return function(n){
                 try{
-                    n()
-                } catch(n) {
-                    t(n)
+                    t()
+                } catch(t) {
+                    n(t)
                 }
             }
         };
@@ -349,11 +349,11 @@ issue_2254_1: {
         try {
             console.log(f("PASS"));
         } catch (e) {}
-        function f(e) {
+        function f(t) {
             try {
                 throw "FAIL";
-            } catch (t) {
-                return e;
+            } catch (e) {
+                return t;
             }
         }
     }
index 1b0052a..02180a5 100644 (file)
@@ -46,10 +46,10 @@ describe("minify", function() {
         assert.strictEqual(compressed, [
             "function n(n){return 3*n}",
             "function r(n){return n/2}",
-            "var o=console.log.bind(console);",
-            'function c(n){o("Foo:",2*n)}',
+            "var c=console.log.bind(console);",
+            'function o(o){c("Foo:",2*o)}',
             "var a=n(3),b=r(12);",
-            'o("qux",a,b),c(11);',
+            'c("qux",a,b),o(11);',
         ].join(""));
         assert.strictEqual(run_code(compressed), run_code(original));
     });
@@ -79,10 +79,10 @@ describe("minify", function() {
         assert.strictEqual(compressed, [
             "function n(n){return 3*n}",
             "function r(n){return n/2}",
-            "var o=console.log.bind(console);",
-            'function c(n){o("Foo:",2*n)}',
+            "var c=console.log.bind(console);",
+            'function o(o){c("Foo:",2*o)}',
             "var a=n(3),b=r(12);",
-            'o("qux",a,b),c(11);',
+            'c("qux",a,b),o(11);',
         ].join(""));
         assert.strictEqual(run_code(compressed), run_code(original));
     });