From 9b8deff64d957b2804082572340cdd8175eff731 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 24 Apr 2021 00:17:30 +0100 Subject: [PATCH] enhance `pure_getters`, `reduce_vars` & `unused` (#4863) --- lib/compress.js | 76 ++++++++++++++++++----------------- test/compress/classes.js | 2 + test/compress/drop-unused.js | 6 +-- test/compress/functions.js | 2 + test/compress/pure_getters.js | 74 ++++++++++++++++++++++++++++++++++ test/ufuzz/index.js | 8 ++-- 6 files changed, 125 insertions(+), 43 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index fea4459bd1..f0e7ab80b4 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -814,11 +814,12 @@ merge(Compressor.prototype, { def(AST_Assign, function(tw, descend, compressor) { var node = this; var left = node.left; + var right = node.right; var scan = left instanceof AST_Destructured || left instanceof AST_SymbolRef; switch (node.operator) { case "=": - if (left.equivalent_to(node.right) && !left.has_side_effects(compressor)) { - node.right.walk(tw); + if (left.equivalent_to(right) && !left.has_side_effects(compressor)) { + right.walk(tw); walk_prop(left); node.__drop = true; return true; @@ -838,7 +839,7 @@ merge(Compressor.prototype, { walk_assign(); } else { mark_assignment_to_arguments(left); - node.right.walk(tw); + right.walk(tw); } pop(tw); return true; @@ -855,7 +856,7 @@ merge(Compressor.prototype, { return; } var safe = safe_to_read(tw, d); - node.right.walk(tw); + right.walk(tw); if (safe && !left.in_arg && safe_to_assign(tw, d)) { push_ref(d, left); mark(tw, d); @@ -864,7 +865,7 @@ merge(Compressor.prototype, { return make_node(AST_Binary, node, { operator: node.operator.slice(0, -1), left: make_ref(left, fixed), - right: node.right + right: node.right, }); }; left.fixed.assigns = !fixed || !fixed.assigns ? [] : fixed.assigns.slice(); @@ -899,7 +900,7 @@ merge(Compressor.prototype, { } function walk_assign() { - node.right.walk(tw); + right.walk(tw); scan_declaration(tw, compressor, left, function() { return node.right; }, function(sym, fixed, walk) { @@ -911,14 +912,14 @@ merge(Compressor.prototype, { var d = sym.definition(); d.assignments++; if (fixed - && !is_modified(compressor, tw, node, node.right, 0) + && !is_modified(compressor, tw, node, right, 0, is_immutable(right), recursive_ref(tw, d)) && !sym.in_arg && safe_to_assign(tw, d)) { push_ref(d, sym); mark(tw, d); if (d.single_use && left instanceof AST_Destructured) d.single_use = false; tw.loop_ids[d.id] = tw.in_loop; - mark_escaped(tw, d, sym.scope, node, node.right, 0, 1); + mark_escaped(tw, d, sym.scope, node, right, 0, 1); sym.fixed = d.fixed = fixed; sym.fixed.assigns = [ node ]; } else { @@ -3656,11 +3657,11 @@ merge(Compressor.prototype, { // may_throw_on_access() // returns true if this node may be null, undefined or contain `AST_Accessor` (function(def) { - AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { - return !compressor.option("pure_getters") || this._dot_throw(compressor); + AST_Node.DEFMETHOD("may_throw_on_access", function(compressor, force) { + return !compressor.option("pure_getters") || this._dot_throw(compressor, force); }); - function is_strict(compressor) { - return /strict/.test(compressor.option("pure_getters")); + function is_strict(compressor, force) { + return force || /strict/.test(compressor.option("pure_getters")); } def(AST_Node, is_strict); def(AST_Array, return_false); @@ -3683,28 +3684,28 @@ merge(Compressor.prototype, { return this.consequent._dot_throw(compressor) || this.alternative._dot_throw(compressor); }); def(AST_Constant, return_false); - def(AST_Dot, function(compressor) { - if (!is_strict(compressor)) return false; + def(AST_Dot, function(compressor, force) { + if (!is_strict(compressor, force)) return false; var exp = this.expression; if (exp instanceof AST_SymbolRef) exp = exp.fixed_value(); return !(this.property == "prototype" && is_lambda(exp)); }); def(AST_Lambda, return_false); def(AST_Null, return_true); - def(AST_Object, function(compressor) { - return is_strict(compressor) && !all(this.properties, function(prop) { + def(AST_Object, function(compressor, force) { + return is_strict(compressor, force) && !all(this.properties, function(prop) { return prop instanceof AST_ObjectKeyVal; }); }); - def(AST_ObjectIdentity, function(compressor) { - return is_strict(compressor) && !this.scope.new; + def(AST_ObjectIdentity, function(compressor, force) { + return is_strict(compressor, force) && !this.scope.new; }); def(AST_Sequence, function(compressor) { return this.tail_node()._dot_throw(compressor); }); - def(AST_SymbolRef, function(compressor) { + def(AST_SymbolRef, function(compressor, force) { if (this.is_undefined) return true; - if (!is_strict(compressor)) return false; + if (!is_strict(compressor, force)) return false; if (is_undeclared_ref(this) && this.is_declared(compressor)) return false; if (this.is_immutable()) return false; var def = this.definition(); @@ -5817,11 +5818,10 @@ merge(Compressor.prototype, { var assign_as_unused = /keep_assign/.test(compressor.option("unused")) ? return_false : function(node, props) { var sym, nested = false; if (node instanceof AST_Assign) { - if (node.write_only || node.operator == "=") sym = node.left; + if (node.write_only || node.operator == "=") sym = extract_reference(node.left, props); } else if (node instanceof AST_Unary) { - if (node.write_only) sym = node.expression; + if (node.write_only) sym = extract_reference(node.expression, props); } - if (/strict/.test(compressor.option("pure_getters"))) sym = extract_reference(sym, props); if (!(sym instanceof AST_SymbolRef)) return; var def = sym.definition(); if (export_defaults[def.id]) return; @@ -5832,15 +5832,17 @@ merge(Compressor.prototype, { function extract_reference(node, props) { if (node instanceof AST_PropAccess) { var expr = node.expression; - if (!expr.may_throw_on_access(compressor)) { + if (!expr.may_throw_on_access(compressor, true)) { nested = true; if (props && node instanceof AST_Sub) props.unshift(node.property); return extract_reference(expr, props); } } else if (node instanceof AST_Assign && node.operator == "=") { + node.write_only = "p"; var ref = extract_reference(node.right); - if (props) props.assign = node; - return ref; + if (!props) return ref; + props.assign = node; + return ref instanceof AST_SymbolRef ? ref : node.left; } return node; } @@ -6125,6 +6127,10 @@ merge(Compressor.prototype, { value = make_node(AST_Number, node, { value: 0 }); } if (value) { + if (props.assign) { + var assign = props.assign.drop_side_effect_free(compressor); + if (assign) props.unshift(assign); + } if (parent instanceof AST_Sequence && parent.tail_node() !== node) { value = value.drop_side_effect_free(compressor); } @@ -6662,16 +6668,17 @@ merge(Compressor.prototype, { } }, true); }))) { - if (props.assign) { - props.assign.write_only = true; - props.assign.walk(tw); - delete props.assign.write_only; + if (node.write_only === "p" && node.right.may_throw_on_access(compressor, true)) return; + var assign = props.assign; + if (assign) { + assign.write_only = true; + assign.walk(tw); + assign.write_only = "p"; } props.forEach(function(prop) { prop.walk(tw); }); if (node instanceof AST_Assign) { - if (node.write_only === "p" && node.right.may_throw_on_access(compressor)) return; var right = get_rhs(node); if (init && node.write_only === true && node_def.scope === self && !right.has_side_effects(compressor)) { initializations.add(node_def.id, right); @@ -7312,11 +7319,8 @@ merge(Compressor.prototype, { def(AST_Assign, function(compressor) { var left = this.left; if (left instanceof AST_PropAccess) { - var expr = left.expression; - if (expr instanceof AST_Assign && expr.operator == "=" && !expr.may_throw_on_access(compressor)) { - expr.write_only = "p"; - } - if (compressor.has_directive("use strict") && expr.is_constant()) return this; + if (left.expression.may_throw_on_access(compressor, true)) return this; + if (compressor.has_directive("use strict") && left.expression.is_constant()) return this; } if (left.has_side_effects(compressor)) return this; var right = this.right; diff --git a/test/compress/classes.js b/test/compress/classes.js index e8bff7528c..e201bd3d2f 100644 --- a/test/compress/classes.js +++ b/test/compress/classes.js @@ -1546,6 +1546,8 @@ drop_unused_self_reference: { options = { pure_getters: "strict", reduce_vars: true, + sequences: true, + side_effects: true, toplevel: true, unused: true, } diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 4ade08631f..6c4ca291de 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -2371,6 +2371,7 @@ function_parameter_ie8: { issue_3664: { options = { pure_getters: "strict", + side_effects: true, unused: true, } input: { @@ -2381,7 +2382,7 @@ issue_3664: { } expect: { console.log(function() { - var b = ([ b && console.log("FAIL") ].p = 0, 0); + var b = (b && console.log("FAIL"), 0, 0); return "PASS"; }()); } @@ -2391,6 +2392,7 @@ issue_3664: { issue_3673: { options = { pure_getters: "strict", + sequences: true, side_effects: true, toplevel: true, unused: true, @@ -2401,8 +2403,6 @@ issue_3673: { console.log("PASS"); } expect: { - var a; - (a = [ a ]).p = 42; console.log("PASS"); } expect_stdout: "PASS" diff --git a/test/compress/functions.js b/test/compress/functions.js index 3d19f497e0..a9a3b8f611 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -6026,6 +6026,8 @@ drop_unused_self_reference: { options = { pure_getters: "strict", reduce_vars: true, + sequences: true, + side_effects: true, toplevel: true, unused: true, } diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index d1dd07ee00..2a20c339a0 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -1564,3 +1564,77 @@ issue_4803: { } expect_stdout: "PASS" } + +nested_property_assignments_1: { + options = { + pure_getters: "strict", + reduce_vars: true, + sequences: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var f; + ((f = function() { + console.log("FAIL"); + }).p = f).q = console.log("PASS"); + } + expect: { + console.log("PASS"); + } + expect_stdout: "PASS" +} + +nested_property_assignments_2: { + options = { + pure_getters: "strict", + unused: true, + } + input: { + var o = {}; + (function() { + var a; + (o.p = a = {}).q = "PASS"; + })(); + console.log(o.p.q); + } + expect: { + var o = {}; + (function() { + (o.p = {}).q = "PASS"; + })(); + console.log(o.p.q); + } + expect_stdout: "PASS" +} + +nested_property_assignments_3: { + options = { + collapse_vars: true, + pure_getters: true, + side_effects: true, + unused: true, + } + input: { + var o = { p: {} }; + (function(a) { + console && a; + if (console) { + a = a.p; + a.q = a; + } + })(o); + console.log(o.p.q === o.p ? "PASS" : "FAIL"); + } + expect: { + var o = { p: {} }; + (function(a) { + console; + if (console) + (a = a.p).q = a; + })(o); + console.log(o.p.q === o.p ? "PASS" : "FAIL"); + } + expect_stdout: "PASS" +} diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 7210c6b21a..b3650fa55e 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -445,9 +445,9 @@ function addTrailingComma(list) { function createParams(was_async, was_generator, noDuplicate) { var save_async = async; - if (was_async) async = true; + if (!async) async = was_async; var save_generator = generator; - if (was_generator) generator = true; + if (!generator) generator = was_generator; var len = unique_vars.length; var params = []; for (var n = rng(4); --n >= 0;) { @@ -569,9 +569,9 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was function createName() { unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity"); var save_async = async; - if (was_async) async = true; + if (!async) async = was_async; var save_generator = generator; - if (was_generator) generator = true; + if (!generator) generator = was_generator; var name = createVarName(MANDATORY); generator = save_generator; async = save_async;