enhance `unsafe` `evaluate` (#3370)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sat, 20 Apr 2019 11:42:41 +0000 (19:42 +0800)
committerGitHub <noreply@github.com>
Sat, 20 Apr 2019 11:42:41 +0000 (19:42 +0800)
lib/compress.js
test/compress/evaluate.js
test/compress/reduce_vars.js

index 9fdfc43..b0f1bfb 100644 (file)
@@ -312,8 +312,22 @@ merge(Compressor.prototype, {
         return value instanceof AST_SymbolRef && value.fixed_value() || value;
     }
 
+    function is_read_only_fn(value, name) {
+        if (value instanceof AST_Boolean) return native_fns.Boolean[name];
+        if (value instanceof AST_Number) return native_fns.Number[name];
+        if (value instanceof AST_String) return native_fns.String[name];
+        if (name == "valueOf") return false;
+        if (value instanceof AST_Array) return native_fns.Array[name];
+        if (value instanceof AST_Function) return native_fns.Function[name];
+        if (value instanceof AST_Object) return native_fns.Object[name];
+        if (value instanceof AST_RegExp) return native_fns.RegExp[name];
+    }
+
     function is_modified(compressor, tw, node, value, level, immutable) {
         var parent = tw.parent(level);
+        if (compressor.option("unsafe") && parent instanceof AST_Dot && is_read_only_fn(value, parent.property)) {
+            return;
+        }
         var lhs = is_lhs(node, parent);
         if (lhs) return lhs;
         if (!immutable
@@ -344,7 +358,7 @@ merge(Compressor.prototype, {
             def.assignments = 0;
             def.chained = false;
             def.direct_access = false;
-            def.escaped = false;
+            def.escaped = [];
             def.fixed = !def.scope.pinned()
                 && !compressor.exposed(def)
                 && !(def.init instanceof AST_Function && def.init !== def.scope)
@@ -483,8 +497,9 @@ merge(Compressor.prototype, {
                 || parent instanceof AST_Call && (node !== parent.expression || parent instanceof AST_New)
                 || parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
                 || parent instanceof AST_VarDef && node === parent.value) {
+                d.escaped.push(parent);
                 if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1;
-                if (!d.escaped || d.escaped > depth) d.escaped = depth;
+                if (!d.escaped.depth || d.escaped.depth > depth) d.escaped.depth = depth;
                 return;
             } else if (parent instanceof AST_Array
                 || parent instanceof AST_Binary && lazy_op[parent.operator]
@@ -742,8 +757,8 @@ merge(Compressor.prototype, {
                         d.fixed = false;
                     }
                 }
+                mark_escaped(tw, d, this.scope, this, value, 0, 1);
             }
-            mark_escaped(tw, d, this.scope, this, value, 0, 1);
             var parent;
             if (d.fixed instanceof AST_Defun
                 && !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) {
@@ -1579,9 +1594,14 @@ merge(Compressor.prototype, {
                     lvalues[candidate.name.name] = lhs;
                 }
                 var tw = new TreeWalker(function(node) {
-                    var sym = root_expr(node);
-                    if (sym instanceof AST_SymbolRef || sym instanceof AST_This) {
-                        lvalues[sym.name] = lvalues[sym.name] || is_modified(compressor, tw, node, node, 0);
+                    var value;
+                    if (node instanceof AST_SymbolRef) {
+                        value = node.fixed_value() || node;
+                    } else if (node instanceof AST_This) {
+                        value = node;
+                    }
+                    if (value && !lvalues[node.name]) {
+                        lvalues[node.name] = is_modified(compressor, tw, node, value, 0);
                     }
                 });
                 expr.walk(tw);
@@ -2847,9 +2867,25 @@ merge(Compressor.prototype, {
             }
             if (value && typeof value == "object") {
                 var escaped = this.definition().escaped;
-                if (escaped && depth > escaped) return this;
+                switch (escaped.length) {
+                  case 0:
+                    break;
+                  case 1:
+                    if (contains_ref(escaped[0], this)) break;
+                  default:
+                    if (depth > escaped.depth) return this;
+                }
             }
             return value;
+
+            function contains_ref(expr, ref) {
+                var found = false;
+                expr.walk(new TreeWalker(function(node) {
+                    if (found) return true;
+                    if (node === ref) return found = true;
+                }));
+                return found;
+            }
         });
         var global_objs = {
             Array: Array,
@@ -3983,7 +4019,7 @@ merge(Compressor.prototype, {
                 var def = sym.definition();
                 if (def.assignments != count) return;
                 if (def.direct_access) return;
-                if (def.escaped == 1) return;
+                if (def.escaped.depth == 1) return;
                 if (def.references.length == count) return;
                 if (def.single_use) return;
                 if (top_retain(def)) return;
@@ -5863,7 +5899,7 @@ merge(Compressor.prototype, {
             var single_use = def.single_use && !(parent instanceof AST_Call && parent.is_expr_pure(compressor));
             if (single_use && fixed instanceof AST_Lambda) {
                 if (def.scope !== self.scope
-                    && (!compressor.option("reduce_funcs") || def.escaped == 1 || fixed.inlined)) {
+                    && (!compressor.option("reduce_funcs") || def.escaped.depth == 1 || fixed.inlined)) {
                     single_use = false;
                 } else if (recursive_ref(compressor, def)) {
                     single_use = false;
index 6d3d3e5..ebdffad 100644 (file)
@@ -1687,3 +1687,28 @@ try_increment: {
     }
     expect_stdout: "1"
 }
+
+unsafe_escaped: {
+    options = {
+        evaluate: true,
+        inline: true,
+        passes: 3,
+        reduce_vars: true,
+        side_effects: true,
+        unsafe: true,
+        unused: true,
+    }
+    input: {
+        (function(a) {
+            console.log(function(index) {
+                return a[index];
+            }(function(term) {
+                return a.indexOf(term);
+            }("PASS")));
+        })([ "PASS" ]);
+    }
+    expect: {
+        console.log("PASS");
+    }
+    expect_stdout: "PASS"
+}
index fb5038b..169ae5a 100644 (file)
@@ -281,8 +281,8 @@ unsafe_evaluate_escaped: {
         console.log(function(){ var o={p:3},a=[o]; console.log(a[0].p++); return o.p; }());
     }
     expect: {
-        console.log(function(){ var o={p:1}; console.log(o, o.p); return o.p; }());
-        console.log(function(){ var o={p:2}; console.log(o.p, o); return o.p; }());
+        console.log(function(){ var o={p:1}; console.log(o, 1); return o.p; }());
+        console.log(function(){ var o={p:2}; console.log(2, o); return o.p; }());
         console.log(function(){ var o={p:3},a=[o]; console.log(a[0].p++); return o.p; }());
     }
     expect_stdout: true