From 855964a87a14308ae97eea35171982fb33bd58ca Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 20 Apr 2019 19:42:41 +0800 Subject: [PATCH] enhance `unsafe` `evaluate` (#3370) --- lib/compress.js | 54 ++++++++++++++++++++++++++++++------ test/compress/evaluate.js | 25 +++++++++++++++++ test/compress/reduce_vars.js | 4 +-- 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 9fdfc43a..b0f1bfba 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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; diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 6d3d3e56..ebdffadb 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -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" +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index fb5038b5..169ae5a5 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -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 -- 2.34.1