From a5ffe2c23fdfaf13f3466a01d9dd9d590c5e8672 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sat, 15 Jul 2017 15:16:11 +0800 Subject: [PATCH] drop `unused` builtin globals under `unsafe` (#2236) fixes #2233 --- lib/compress.js | 85 ++++++++++++++++++++------------------ lib/scope.js | 8 ---- test/compress/dead-code.js | 79 +++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 48 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 0330c682..6dc7b725 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -689,6 +689,16 @@ merge(Compressor.prototype, { return false; } + function is_undeclared_ref(node) { + return node instanceof AST_SymbolRef && node.definition().undeclared; + } + + var global_names = makePredicate("Array Boolean console Error Function Math Number RegExp Object String"); + AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) { + return !this.definition().undeclared + || compressor.option("unsafe") && global_names(this.name); + }); + function tighten_body(statements, compressor) { var CHANGED, max_iter = 10; do { @@ -758,7 +768,7 @@ merge(Compressor.prototype, { || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression) || node instanceof AST_Debugger || node instanceof AST_IterationStatement && !(node instanceof AST_For) - || node instanceof AST_SymbolRef && node.undeclared() + || node instanceof AST_SymbolRef && !node.is_declared(compressor) || node instanceof AST_Try || node instanceof AST_With || parent instanceof AST_For && node !== parent.init) { @@ -1320,12 +1330,12 @@ merge(Compressor.prototype, { // returns true if this node may be null, undefined or contain `AST_Accessor` (function(def) { AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { - var pure_getters = compressor.option("pure_getters"); - return !pure_getters || this._throw_on_access(pure_getters); + return !compressor.option("pure_getters") + || this._dot_throw(compressor); }); - function is_strict(pure_getters) { - return /strict/.test(pure_getters); + function is_strict(compressor) { + return /strict/.test(compressor.option("pure_getters")); } def(AST_Node, is_strict); @@ -1333,8 +1343,8 @@ merge(Compressor.prototype, { def(AST_Undefined, return_true); def(AST_Constant, return_false); def(AST_Array, return_false); - def(AST_Object, function(pure_getters) { - if (!is_strict(pure_getters)) return false; + def(AST_Object, function(compressor) { + if (!is_strict(compressor)) return false; for (var i = this.properties.length; --i >=0;) if (this.properties[i].value instanceof AST_Accessor) return true; return false; @@ -1344,37 +1354,38 @@ merge(Compressor.prototype, { def(AST_UnaryPrefix, function() { return this.operator == "void"; }); - def(AST_Binary, function(pure_getters) { + def(AST_Binary, function(compressor) { switch (this.operator) { case "&&": - return this.left._throw_on_access(pure_getters); + return this.left._dot_throw(compressor); case "||": - return this.left._throw_on_access(pure_getters) - && this.right._throw_on_access(pure_getters); + return this.left._dot_throw(compressor) + && this.right._dot_throw(compressor); default: return false; } }) - def(AST_Assign, function(pure_getters) { + def(AST_Assign, function(compressor) { return this.operator == "=" - && this.right._throw_on_access(pure_getters); + && this.right._dot_throw(compressor); }) - def(AST_Conditional, function(pure_getters) { - return this.consequent._throw_on_access(pure_getters) - || this.alternative._throw_on_access(pure_getters); + def(AST_Conditional, function(compressor) { + return this.consequent._dot_throw(compressor) + || this.alternative._dot_throw(compressor); }) - def(AST_Sequence, function(pure_getters) { - return this.expressions[this.expressions.length - 1]._throw_on_access(pure_getters); + def(AST_Sequence, function(compressor) { + return this.expressions[this.expressions.length - 1]._dot_throw(compressor); }); - def(AST_SymbolRef, function(pure_getters) { + def(AST_SymbolRef, function(compressor) { if (this.is_undefined) return true; - if (!is_strict(pure_getters)) return false; + if (!is_strict(compressor)) return false; + if (is_undeclared_ref(this) && this.is_declared(compressor)) return false; if (this.is_immutable()) return false; var fixed = this.fixed_value(); - return !fixed || fixed._throw_on_access(pure_getters); + return !fixed || fixed._dot_throw(compressor); }); })(function(node, func) { - node.DEFMETHOD("_throw_on_access", func); + node.DEFMETHOD("_dot_throw", func); }); /* -----[ boolean/negation helpers ]----- */ @@ -1735,11 +1746,8 @@ merge(Compressor.prototype, { }); var global_objs = { Array: Array, - Boolean: Boolean, Math: Math, Number: Number, - RegExp: RegExp, - Object: Object, String: String, }; function convert_to_predicate(obj) { @@ -1776,7 +1784,7 @@ merge(Compressor.prototype, { } var exp = this.expression; var val; - if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (is_undeclared_ref(exp)) { if (!(static_values[exp.name] || return_false)(key)) return this; val = global_objs[exp.name]; } else { @@ -1868,7 +1876,7 @@ merge(Compressor.prototype, { } var val; var e = exp.expression; - if (e instanceof AST_SymbolRef && e.undeclared()) { + if (is_undeclared_ref(e)) { if (!(static_fns[e.name] || return_false)(key)) return this; val = global_objs[e.name]; } else { @@ -2050,7 +2058,7 @@ merge(Compressor.prototype, { || this.expression.has_side_effects(compressor); }); def(AST_SymbolRef, function(compressor){ - return this.undeclared(); + return !this.is_declared(compressor); }); def(AST_SymbolDeclaration, return_false); def(AST_Object, function(compressor){ @@ -2684,8 +2692,8 @@ merge(Compressor.prototype, { } return expression; }); - def(AST_SymbolRef, function() { - return this.undeclared() ? this : null; + def(AST_SymbolRef, function(compressor) { + return this.is_declared(compressor) ? null : this; }); def(AST_Object, function(compressor, first_in_statement){ var values = trim(this.properties, compressor, first_in_statement); @@ -3147,7 +3155,7 @@ merge(Compressor.prototype, { self.args.length = last; } if (compressor.option("unsafe")) { - if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (is_undeclared_ref(exp)) { switch (exp.name) { case "Array": if (self.args.length != 1) { @@ -3274,8 +3282,7 @@ merge(Compressor.prototype, { } } if (compressor.option("unsafe_Func") - && exp instanceof AST_SymbolRef - && exp.undeclared() + && is_undeclared_ref(exp) && exp.name == "Function") { // new Function() => function(){} if (self.args.length == 0) return make_node(AST_Function, self, { @@ -3392,9 +3399,7 @@ merge(Compressor.prototype, { while (name.expression) { name = name.expression; } - if (name instanceof AST_SymbolRef - && name.name == "console" - && name.undeclared()) { + if (is_undeclared_ref(name) && name.name == "console") { return make_node(AST_Undefined, self).optimize(compressor); } } @@ -3415,7 +3420,7 @@ merge(Compressor.prototype, { OPT(AST_New, function(self, compressor){ if (compressor.option("unsafe")) { var exp = self.expression; - if (exp instanceof AST_SymbolRef && exp.undeclared()) { + if (is_undeclared_ref(exp)) { switch (exp.name) { case "Object": case "RegExp": @@ -3709,7 +3714,7 @@ merge(Compressor.prototype, { && self.right instanceof AST_UnaryPrefix && self.right.operator == "typeof") { var expr = self.right.expression; - if (expr instanceof AST_SymbolRef ? !expr.undeclared() + if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor) : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) { self.right = expr; self.left = make_node(AST_Undefined, self.left).optimize(compressor); @@ -4035,7 +4040,7 @@ merge(Compressor.prototype, { } // testing against !self.scope.uses_with first is an optimization if (!compressor.option("ie8") - && self.undeclared() + && is_undeclared_ref(self) && (!self.scope.uses_with || !compressor.find_parent(AST_With))) { switch (self.name) { case "undefined": @@ -4454,7 +4459,7 @@ merge(Compressor.prototype, { && self.expression instanceof AST_Dot && self.expression.property == "prototype") { var exp = self.expression.expression; - if (exp instanceof AST_SymbolRef && exp.undeclared()) switch (exp.name) { + if (is_undeclared_ref(exp)) switch (exp.name) { case "Array": self.expression = make_node(AST_Array, self.expression, { elements: [] diff --git a/lib/scope.js b/lib/scope.js index f8ecedb5..df7b2076 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -373,14 +373,6 @@ AST_Symbol.DEFMETHOD("unreferenced", function(){ && !(this.scope.uses_eval || this.scope.uses_with); }); -AST_Symbol.DEFMETHOD("undeclared", function(){ - return this.definition().undeclared; -}); - -AST_LabelRef.DEFMETHOD("undeclared", return_false); - -AST_Label.DEFMETHOD("undeclared", return_false); - AST_Symbol.DEFMETHOD("definition", function(){ return this.thedef; }); diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 0fcbbe43..abf5297c 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -230,3 +230,82 @@ accessor: { } expect: {} } + +issue_2233_1: { + options = { + pure_getters: "strict", + side_effects: true, + unsafe: true, + } + input: { + Array.isArray; + Boolean; + console.log; + Error.name; + Function.length; + Math.random; + Number.isNaN; + RegExp; + Object.defineProperty; + String.fromCharCode; + } + expect: {} + expect_stdout: true +} + +issue_2233_2: { + options = { + pure_getters: "strict", + reduce_vars: true, + side_effects: true, + unsafe: true, + unused: true, + } + input: { + var RegExp; + Array.isArray; + RegExp; + UndeclaredGlobal; + function foo() { + var Number; + AnotherUndeclaredGlobal; + Math.sin; + Number.isNaN; + } + } + expect: { + var RegExp; + UndeclaredGlobal; + function foo() { + var Number; + AnotherUndeclaredGlobal; + Number.isNaN; + } + } +} + +issue_2233_3: { + options = { + pure_getters: "strict", + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var RegExp; + Array.isArray; + RegExp; + UndeclaredGlobal; + function foo() { + var Number; + AnotherUndeclaredGlobal; + Math.sin; + Number.isNaN; + } + } + expect: { + UndeclaredGlobal; + } +} -- 2.34.1