From 3e7873217cad8b6f67839cb06d1e126ca231bc42 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 19 Jan 2018 20:41:57 +0800 Subject: [PATCH] improve `unused` on built-in functions (#2817) --- lib/compress.js | 216 +++++++++++++++++++++------------ test/compress/collapse_vars.js | 23 ++++ test/compress/dead-code.js | 17 +++ test/compress/evaluate.js | 36 ++++-- 4 files changed, 205 insertions(+), 87 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 00593e81..5235a05f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2113,6 +2113,95 @@ merge(Compressor.prototype, { return (first_in_statement(compressor) ? best_of_statement : best_of_expression)(ast1, ast2); } + function convert_to_predicate(obj) { + for (var key in obj) { + obj[key] = makePredicate(obj[key]); + } + } + + var object_fns = [ + "constructor", + "toString", + "valueOf", + ]; + var native_fns = { + Array: [ + "indexOf", + "join", + "lastIndexOf", + "slice", + ].concat(object_fns), + Boolean: object_fns, + Number: [ + "toExponential", + "toFixed", + "toPrecision", + ].concat(object_fns), + Object: object_fns, + RegExp: [ + "test", + ].concat(object_fns), + String: [ + "charAt", + "charCodeAt", + "concat", + "indexOf", + "italics", + "lastIndexOf", + "match", + "replace", + "search", + "slice", + "split", + "substr", + "substring", + "trim", + ].concat(object_fns), + }; + convert_to_predicate(native_fns); + var static_fns = { + Array: [ + "isArray", + ], + Math: [ + "abs", + "acos", + "asin", + "atan", + "ceil", + "cos", + "exp", + "floor", + "log", + "round", + "sin", + "sqrt", + "tan", + "atan2", + "pow", + "max", + "min", + ], + Number: [ + "isFinite", + "isNaN", + ], + Object: [ + "create", + "getOwnPropertyDescriptor", + "getOwnPropertyNames", + "getPrototypeOf", + "isExtensible", + "isFrozen", + "isSealed", + "keys", + ], + String: [ + "fromCharCode", + ], + }; + convert_to_predicate(static_fns); + // methods to evaluate a constant expression (function(def){ // If the node has been successfully reduced to a constant, @@ -2278,13 +2367,9 @@ merge(Compressor.prototype, { Array: Array, Math: Math, Number: Number, + Object: Object, String: String, }; - function convert_to_predicate(obj) { - for (var key in obj) { - obj[key] = makePredicate(obj[key]); - } - } var static_values = { Math: [ "E", @@ -2325,77 +2410,6 @@ merge(Compressor.prototype, { } return this; }); - var object_fns = [ - "constructor", - "toString", - "valueOf", - ]; - var native_fns = { - Array: [ - "indexOf", - "join", - "lastIndexOf", - "slice", - ].concat(object_fns), - Boolean: object_fns, - Number: [ - "toExponential", - "toFixed", - "toPrecision", - ].concat(object_fns), - RegExp: [ - "test", - ].concat(object_fns), - String: [ - "charAt", - "charCodeAt", - "concat", - "indexOf", - "italics", - "lastIndexOf", - "match", - "replace", - "search", - "slice", - "split", - "substr", - "substring", - "trim", - ].concat(object_fns), - }; - convert_to_predicate(native_fns); - var static_fns = { - Array: [ - "isArray", - ], - Math: [ - "abs", - "acos", - "asin", - "atan", - "ceil", - "cos", - "exp", - "floor", - "log", - "round", - "sin", - "sqrt", - "tan", - "atan2", - "pow", - "max", - "min" - ], - Number: [ - "isFinite", - "isNaN", - ], - String: [ - "fromCharCode", - ], - }; - convert_to_predicate(static_fns); def(AST_Call, function(compressor, depth) { var exp = this.expression; if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { @@ -2420,7 +2434,16 @@ merge(Compressor.prototype, { if (arg === value) return this; args.push(value); } - return val[key].apply(val, args); + try { + return val[key].apply(val, args); + } catch (ex) { + compressor.warn("Error evaluating {code} [{file}:{line},{col}]", { + code: this.print_to_string(), + file: this.start.file, + line: this.start.line, + col: this.start.col + }); + } } return this; }); @@ -2511,9 +2534,34 @@ merge(Compressor.prototype, { if (compressor.option("unsafe")) { var expr = this.expression; if (is_undeclared_ref(expr) && global_pure_fns(expr.name)) return true; + if (expr instanceof AST_Dot + && is_undeclared_ref(expr.expression) + && (static_fns[expr.expression.name] || return_false)(expr.property)) { + return true; + } } return this.pure || !compressor.pure_funcs(this); }); + AST_Node.DEFMETHOD("is_call_pure", return_false); + AST_Dot.DEFMETHOD("is_call_pure", function(compressor) { + if (!compressor.option("unsafe")) return; + var expr = this.expression; + var fns = return_false; + if (expr instanceof AST_Array) { + fns = native_fns.Array; + } else if (expr.is_boolean()) { + fns = native_fns.Boolean; + } else if (expr.is_number(compressor)) { + fns = native_fns.Number; + } else if (expr instanceof AST_RegExp) { + fns = native_fns.RegExp; + } else if (expr.is_string(compressor)) { + fns = native_fns.String; + } else if (!this.may_throw_on_access(compressor)) { + fns = native_fns.Object; + } + return fns(this.property); + }); // determine if expression has side effects (function(def){ @@ -2534,8 +2582,12 @@ merge(Compressor.prototype, { return any(this.body, compressor); }); def(AST_Call, function(compressor){ - return !this.is_expr_pure(compressor) - || any(this.args, compressor); + if (!this.is_expr_pure(compressor) + && (!this.expression.is_call_pure(compressor) + || this.expression.has_side_effects(compressor))) { + return true; + } + return any(this.args, compressor); }); def(AST_Switch, function(compressor){ return this.expression.has_side_effects(compressor) @@ -3365,6 +3417,12 @@ merge(Compressor.prototype, { def(AST_This, return_null); def(AST_Call, function(compressor, first_in_statement){ if (!this.is_expr_pure(compressor)) { + if (this.expression.is_call_pure(compressor)) { + var exprs = this.args.slice(); + exprs.unshift(this.expression.expression); + exprs = trim(exprs, compressor, first_in_statement); + return exprs && make_sequence(this, exprs); + } if (this.expression instanceof AST_Function && (!this.expression.name || !this.expression.name.definition().references.length)) { var node = this.clone(); diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 9597b67f..970f822e 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -4089,3 +4089,26 @@ cascade_forin: { "2", ] } + +unsafe_builtin: { + options = { + collapse_vars: true, + pure_getters: "strict", + unsafe: true, + unused: true, + } + input: { + function f(a) { + var b = Math.abs(a); + return Math.pow(b, 2); + } + console.log(f(-1), f(2)); + } + expect: { + function f(a) { + return Math.pow(Math.abs(a), 2); + } + console.log(f(-1), f(2)); + } + expect_stdout: "1 4" +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index afc8c568..b66d5ac1 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -862,3 +862,20 @@ issue_2749: { } expect_stdout: "PASS" } + +unsafe_builtin: { + options = { + side_effects: true, + unsafe: true, + } + input: { + (!w).constructor(x); + Math.abs(y); + [ 1, 2, z ].valueOf(); + } + expect: { + w, x; + y; + z; + } +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 6106fce3..d40880f3 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1194,6 +1194,9 @@ issue_2231_1: { console.log(Object.keys(void 0)); } expect_stdout: true + expect_warnings: [ + "WARN: Error evaluating Object.keys(void 0) [test/compress/evaluate.js:1191,20]", + ] } issue_2231_2: { @@ -1208,6 +1211,23 @@ issue_2231_2: { console.log(Object.getOwnPropertyNames(null)); } expect_stdout: true + expect_warnings: [ + "WARN: Error evaluating Object.getOwnPropertyNames(null) [test/compress/evaluate.js:1208,20]", + ] +} + +issue_2231_3: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log(Object.keys({ foo: "bar" })[0]); + } + expect: { + console.log("foo"); + } + expect_stdout: "foo" } self_comparison_1: { @@ -1330,13 +1350,13 @@ issue_2535_3: { } expect_stdout: true expect_warnings: [ - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1316,20]", - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1317,20]", - "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1318,20]", - "WARN: Condition left of && always false [test/compress/evaluate.js:1318,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1319,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1320,20]", - "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1321,20]", - "WARN: Condition left of || always true [test/compress/evaluate.js:1321,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1336,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1337,20]", + "WARN: Dropping side-effect-free && [test/compress/evaluate.js:1338,20]", + "WARN: Condition left of && always false [test/compress/evaluate.js:1338,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1339,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1340,20]", + "WARN: Dropping side-effect-free || [test/compress/evaluate.js:1341,20]", + "WARN: Condition left of || always true [test/compress/evaluate.js:1341,20]", ] } -- 2.34.1