enhance dead_code & side_effects (#5840)

closes #5794
This commit is contained in:
Alex Lam S.L
2024-06-19 01:36:04 +03:00
committed by GitHub
parent dc7aa32172
commit 9c80456634
5 changed files with 159 additions and 24 deletions

View File

@@ -779,11 +779,11 @@ to be `false` and all symbol names will be omitted.
overhead (compression will be slower). Make sure symbols under `pure_funcs` overhead (compression will be slower). Make sure symbols under `pure_funcs`
are also under `mangle.reserved` to avoid mangling. are also under `mangle.reserved` to avoid mangling.
- `pure_getters` (default: `"strict"`) — If you pass `true` for - `pure_getters` (default: `"strict"`) — Pass `true` for UglifyJS to assume that
this, UglifyJS will assume that object property access object property access (e.g. `foo.bar` or `a[42]`) does not throw exception or
(e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects. alter program states via getter function. Pass `"strict"` to allow dropping or
Specify `"strict"` to treat `foo.bar` as side-effect-free only when reordering `foo.bar` only if `foo` is not `null` or `undefined` and is safe to
`foo` is certain to not throw, i.e. not `null` or `undefined`. access as a variable. Pass `false` to retain all property accesses.
- `reduce_funcs` (default: `true`) — Allows single-use functions to be - `reduce_funcs` (default: `true`) — Allows single-use functions to be
inlined as function expressions when permissible allowing further inlined as function expressions when permissible allowing further

View File

@@ -169,8 +169,6 @@ DEF_BITPROPS(AST_Node, [
"private", "private",
// AST_Call // AST_Call
"pure", "pure",
// AST_Assign
"redundant",
// AST_Node // AST_Node
"single_use", "single_use",
// AST_ClassProperty // AST_ClassProperty

View File

@@ -638,15 +638,30 @@ Compressor.prototype.compress = function(node) {
} }
function push(tw, sequential) { function push(tw, sequential) {
var defined_ids = Object.create(tw.defined_ids);
var safe_ids = Object.create(tw.safe_ids); var safe_ids = Object.create(tw.safe_ids);
if (!sequential) safe_ids.seq = {}; if (!sequential) {
defined_ids.seq = {};
safe_ids.seq = {};
}
tw.defined_ids = defined_ids;
tw.safe_ids = safe_ids; tw.safe_ids = safe_ids;
} }
function pop(tw) { function pop(tw) {
tw.defined_ids = Object.getPrototypeOf(tw.defined_ids);
tw.safe_ids = Object.getPrototypeOf(tw.safe_ids); tw.safe_ids = Object.getPrototypeOf(tw.safe_ids);
} }
function access(tw, def) {
tw.defined_ids[def.id] = [ tw.defined_ids.seq ];
}
function assign(tw, def) {
var defined = tw.defined_ids[def.id];
if (defined) defined[0] = false;
}
function mark(tw, def) { function mark(tw, def) {
tw.safe_ids[def.id] = {}; tw.safe_ids[def.id] = {};
} }
@@ -939,9 +954,13 @@ Compressor.prototype.compress = function(node) {
return fixed_node; return fixed_node;
}, visit); }, visit);
walk_lambda(fn, tw); walk_lambda(fn, tw);
var defined_ids = tw.defined_ids;
var safe_ids = tw.safe_ids; var safe_ids = tw.safe_ids;
pop_scope(tw, fn); pop_scope(tw, fn);
if (!aborts) tw.safe_ids = safe_ids; if (!aborts) {
tw.defined_ids = defined_ids;
tw.safe_ids = safe_ids;
}
return true; return true;
function visit(node, fixed) { function visit(node, fixed) {
@@ -966,10 +985,10 @@ Compressor.prototype.compress = function(node) {
var scan = ld || left instanceof AST_Destructured; var scan = ld || left instanceof AST_Destructured;
switch (node.operator) { switch (node.operator) {
case "=": case "=":
if (ld) assign(tw, ld);
if (left.equals(right) && !left.has_side_effects(compressor)) { if (left.equals(right) && !left.has_side_effects(compressor)) {
right.walk(tw); right.walk(tw);
walk_prop(left); walk_prop(left);
node.redundant = true;
return true; return true;
} }
if (ld && right instanceof AST_LambdaExpression) { if (ld && right instanceof AST_LambdaExpression) {
@@ -990,6 +1009,7 @@ Compressor.prototype.compress = function(node) {
case "||=": case "||=":
case "??=": case "??=":
var lazy = true; var lazy = true;
if (ld) assign(tw, ld);
default: default:
if (!scan) { if (!scan) {
mark_assignment_to_arguments(left); mark_assignment_to_arguments(left);
@@ -1103,7 +1123,7 @@ Compressor.prototype.compress = function(node) {
def(AST_BlockScope, function(tw, descend, compressor) { def(AST_BlockScope, function(tw, descend, compressor) {
reset_block_variables(tw, compressor, this); reset_block_variables(tw, compressor, this);
}); });
def(AST_Call, function(tw, descend) { def(AST_Call, function(tw) {
var node = this; var node = this;
var exp = node.expression; var exp = node.expression;
if (exp instanceof AST_LambdaExpression) { if (exp instanceof AST_LambdaExpression) {
@@ -1134,6 +1154,7 @@ Compressor.prototype.compress = function(node) {
if (fixed instanceof AST_Lambda) { if (fixed instanceof AST_Lambda) {
mark_fn_def(tw, exp.definition(), fixed); mark_fn_def(tw, exp.definition(), fixed);
} else { } else {
tw.defined_ids.seq = {};
tw.find_parent(AST_Scope).may_call_this(); tw.find_parent(AST_Scope).may_call_this();
} }
return true; return true;
@@ -1238,6 +1259,12 @@ Compressor.prototype.compress = function(node) {
tw.in_loop = save_loop; tw.in_loop = save_loop;
return true; return true;
}); });
def(AST_Dot, function(tw, descend) {
descend();
var expr = this.expression;
if (expr instanceof AST_SymbolRef) access(tw, expr.definition());
return true;
});
def(AST_For, function(tw, descend, compressor) { def(AST_For, function(tw, descend, compressor) {
var node = this; var node = this;
reset_block_variables(tw, compressor, node); reset_block_variables(tw, compressor, node);
@@ -1336,10 +1363,13 @@ Compressor.prototype.compress = function(node) {
return true; return true;
}); });
def(AST_Sub, function(tw) { def(AST_Sub, function(tw) {
if (!this.optional) return; var node = this;
this.expression.walk(tw); if (!node.optional) return;
var expr = node.expression;
expr.walk(tw);
if (expr instanceof AST_SymbolRef) access(tw, expr.definition());
push(tw, true); push(tw, true);
this.property.walk(tw); node.property.walk(tw);
pop(tw); pop(tw);
return true; return true;
}); });
@@ -1390,6 +1420,8 @@ Compressor.prototype.compress = function(node) {
var d = ref.definition(); var d = ref.definition();
var fixed = d.fixed || d.last_ref && d.last_ref.fixed; var fixed = d.fixed || d.last_ref && d.last_ref.fixed;
push_ref(d, ref); push_ref(d, ref);
var defined = tw.defined_ids[d.id];
if (defined && defined[0] === tw.defined_ids.seq) ref.defined = true;
if (d.references.length == 1 && !d.fixed && d.orig[0] instanceof AST_SymbolDefun) { if (d.references.length == 1 && !d.fixed && d.orig[0] instanceof AST_SymbolDefun) {
tw.loop_ids[d.id] = tw.in_loop; tw.loop_ids[d.id] = tw.in_loop;
} }
@@ -1618,6 +1650,9 @@ Compressor.prototype.compress = function(node) {
reset_flags(node); reset_flags(node);
return node.reduce_vars(tw, descend, compressor); return node.reduce_vars(tw, descend, compressor);
} : reset_flags); } : reset_flags);
// Side-effect tracking on sequential property access
tw.defined_ids = Object.create(null);
tw.defined_ids.seq = {};
// Flow control for visiting lambda definitions // Flow control for visiting lambda definitions
tw.fn_scanning = null; tw.fn_scanning = null;
tw.fn_visited = []; tw.fn_visited = [];
@@ -4712,6 +4747,7 @@ Compressor.prototype.compress = function(node) {
return this.tail_node()._dot_throw(compressor); return this.tail_node()._dot_throw(compressor);
}); });
def(AST_SymbolRef, function(compressor, force) { def(AST_SymbolRef, function(compressor, force) {
if (this.defined) return false;
if (this.is_undefined) return true; if (this.is_undefined) return true;
if (!is_strict(compressor, force)) return false; if (!is_strict(compressor, force)) return false;
if (is_undeclared_ref(this) && this.is_declared(compressor)) return false; if (is_undeclared_ref(this) && this.is_declared(compressor)) return false;
@@ -12955,16 +12991,14 @@ Compressor.prototype.compress = function(node) {
if (compressor.option("dead_code")) { if (compressor.option("dead_code")) {
if (self.left instanceof AST_PropAccess) { if (self.left instanceof AST_PropAccess) {
if (self.operator == "=") { if (self.operator == "=") {
if (self.redundant) {
var exprs = [ self.left.expression ];
if (self.left instanceof AST_Sub) exprs.push(self.left.property);
exprs.push(self.right);
return make_sequence(self, exprs).optimize(compressor);
}
if (self.left.equals(self.right) && !self.left.has_side_effects(compressor)) {
return self.right;
}
var exp = self.left.expression; var exp = self.left.expression;
if (self.left.equals(self.right)) {
var defined = exp.defined;
exp.defined = false;
var drop_lhs = !self.left.has_side_effects(compressor);
exp.defined = defined;
if (drop_lhs) return self.right;
}
if (exp instanceof AST_Lambda if (exp instanceof AST_Lambda
|| !compressor.has_directive("use strict") || !compressor.has_directive("use strict")
&& exp instanceof AST_Constant && exp instanceof AST_Constant

View File

@@ -1312,7 +1312,7 @@ issue_5653: {
} }
expect: { expect: {
console.log((a => { console.log((a => {
return console, +{}; return +{};
})()); })());
} }
expect_stdout: "NaN" expect_stdout: "NaN"

View File

@@ -724,3 +724,106 @@ retain_instanceof: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
drop_access: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
}
input: {
var o = {};
o.p;
try {
(function() {
o.q;
})();
console.log("PASS");
} catch (e) {
console.log("FAIL");
}
}
expect: {
var o = {};
o.p;
try {
console.log("PASS");
} catch (e) {
console.log("FAIL");
}
}
expect_stdout: "PASS"
}
keep_access: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
}
input: {
var o = {};
o.p;
o = null;
try {
(function() {
o.q;
})();
console.log("FAIL");
} catch (e) {
console.log("PASS");
}
}
expect: {
var o = {};
o.p;
o = null;
try {
(function() {
o.q;
})();
console.log("FAIL");
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
}
keep_access_after_call: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
}
input: {
var o = {};
o.p;
o.q;
f();
try {
o.r;
console.log("FAIL");
} catch (e) {
console.log("PASS");
}
function f() {
o = null;
}
}
expect: {
var o = {};
o.p;
f();
try {
o.r;
console.log("FAIL");
} catch (e) {
console.log("PASS");
}
function f() {
o = null;
}
}
expect_stdout: "PASS"
}