From 957d5537a80fcb4037df21f0dbe16391fd0424ad Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 28 Jun 2018 03:46:19 +0800 Subject: [PATCH] improve `unsafe` `comparisons` (#3200) --- lib/compress.js | 121 +++++++++++++++++++++++++++---------- test/compress/comparing.js | 28 +++++++++ test/sandbox.js | 2 +- tools/node.js | 14 ++--- 4 files changed, 124 insertions(+), 41 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 0268e07f4a..e347169ed7 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2264,29 +2264,35 @@ merge(Compressor.prototype, { // methods to determine whether an expression has a boolean result type (function(def) { - var unary_bool = makePredicate("! delete"); - var binary_bool = makePredicate("in instanceof == != === !== < <= >= >"); def(AST_Node, return_false); - def(AST_UnaryPrefix, function() { - return unary_bool[this.operator]; + def(AST_Assign, function(compressor) { + return this.operator == "=" && this.right.is_boolean(compressor); }); - def(AST_Binary, function() { - return binary_bool[this.operator] - || lazy_op[this.operator] - && this.left.is_boolean() - && this.right.is_boolean(); + var binary = makePredicate("in instanceof == != === !== < <= >= >"); + def(AST_Binary, function(compressor) { + return binary[this.operator] || lazy_op[this.operator] + && this.left.is_boolean(compressor) + && this.right.is_boolean(compressor); }); - def(AST_Conditional, function() { - return this.consequent.is_boolean() && this.alternative.is_boolean(); + def(AST_Boolean, return_true); + var fn = makePredicate("every hasOwnProperty isPrototypeOf propertyIsEnumerable some"); + def(AST_Call, function(compressor) { + if (!compressor.option("unsafe")) return false; + var exp = this.expression; + return exp instanceof AST_Dot && (fn[exp.property] + || exp.property == "test" && exp.expression instanceof AST_RegExp); }); - def(AST_Assign, function() { - return this.operator == "=" && this.right.is_boolean(); + def(AST_Conditional, function(compressor) { + return this.consequent.is_boolean(compressor) && this.alternative.is_boolean(compressor); }); - def(AST_Sequence, function() { - return this.tail_node().is_boolean(); + def(AST_New, return_false); + def(AST_Sequence, function(compressor) { + return this.tail_node().is_boolean(compressor); + }); + var unary = makePredicate("! delete"); + def(AST_UnaryPrefix, function() { + return unary[this.operator]; }); - def(AST_True, return_true); - def(AST_False, return_true); })(function(node, func) { node.DEFMETHOD("is_boolean", func); }); @@ -2294,26 +2300,79 @@ merge(Compressor.prototype, { // methods to determine if an expression has a numeric result type (function(def) { def(AST_Node, return_false); - def(AST_Number, return_true); - var unary = makePredicate("+ - ~ ++ --"); - def(AST_Unary, function() { - return unary[this.operator]; - }); var binary = makePredicate("- * / % & | ^ << >> >>>"); + def(AST_Assign, function(compressor) { + return binary[this.operator.slice(0, -1)] + || this.operator == "=" && this.right.is_number(compressor); + }); def(AST_Binary, function(compressor) { return binary[this.operator] || this.operator == "+" && this.left.is_number(compressor) && this.right.is_number(compressor); }); - def(AST_Assign, function(compressor) { - return binary[this.operator.slice(0, -1)] - || this.operator == "=" && this.right.is_number(compressor); + var fn = makePredicate([ + "charCodeAt", + "getDate", + "getDay", + "getFullYear", + "getHours", + "getMilliseconds", + "getMinutes", + "getMonth", + "getSeconds", + "getTime", + "getTimezoneOffset", + "getUTCDate", + "getUTCDay", + "getUTCFullYear", + "getUTCHours", + "getUTCMilliseconds", + "getUTCMinutes", + "getUTCMonth", + "getUTCSeconds", + "getYear", + "indexOf", + "lastIndexOf", + "localeCompare", + "push", + "search", + "setDate", + "setFullYear", + "setHours", + "setMilliseconds", + "setMinutes", + "setMonth", + "setSeconds", + "setTime", + "setUTCDate", + "setUTCFullYear", + "setUTCHours", + "setUTCMilliseconds", + "setUTCMinutes", + "setUTCMonth", + "setUTCSeconds", + "setYear", + "toExponential", + "toFixed", + "toPrecision", + ]); + def(AST_Call, function(compressor) { + if (!compressor.option("unsafe")) return false; + var exp = this.expression; + return exp instanceof AST_Dot && (fn[exp.property] + || is_undeclared_ref(exp.expression) && exp.expression.name == "Math"); }); + def(AST_Conditional, function(compressor) { + return this.consequent.is_number(compressor) && this.alternative.is_number(compressor); + }); + def(AST_New, return_false); + def(AST_Number, return_true); def(AST_Sequence, function(compressor) { return this.tail_node().is_number(compressor); }); - def(AST_Conditional, function(compressor) { - return this.consequent.is_number(compressor) && this.alternative.is_number(compressor); + var unary = makePredicate("+ - ~ ++ --"); + def(AST_Unary, function() { + return unary[this.operator]; }); })(function(node, func) { node.DEFMETHOD("is_number", func); @@ -2902,7 +2961,7 @@ merge(Compressor.prototype, { var map; if (expr instanceof AST_Array) { map = native_fns.Array; - } else if (expr.is_boolean()) { + } else if (expr.is_boolean(compressor)) { map = native_fns.Boolean; } else if (expr.is_number(compressor)) { map = native_fns.Number; @@ -5247,7 +5306,7 @@ merge(Compressor.prototype, { var is_strict_comparison = true; if ((self.left.is_string(compressor) && self.right.is_string(compressor)) || (self.left.is_number(compressor) && self.right.is_number(compressor)) || - (self.left.is_boolean() && self.right.is_boolean()) || + (self.left.is_boolean(compressor) && self.right.is_boolean(compressor)) || self.left.equivalent_to(self.right)) { self.operator = self.operator.substr(0, 2); } @@ -5328,7 +5387,7 @@ merge(Compressor.prototype, { ]).optimize(compressor); } } - if (compressor.option("comparisons") && self.is_boolean()) { + if (compressor.option("comparisons") && self.is_boolean(compressor)) { if (!(compressor.parent() instanceof AST_Binary) || compressor.parent() instanceof AST_Assign) { var negated = make_node(AST_UnaryPrefix, self, { @@ -6102,7 +6161,7 @@ merge(Compressor.prototype, { return self; function booleanize(node) { - if (node.is_boolean()) return node; + if (node.is_boolean(compressor)) return node; // !!expression return make_node(AST_UnaryPrefix, node, { operator: "!", diff --git a/test/compress/comparing.js b/test/compress/comparing.js index 17b3ac47fd..1ff2956523 100644 --- a/test/compress/comparing.js +++ b/test/compress/comparing.js @@ -295,3 +295,31 @@ issue_2857_6: { } expect_stdout: "true" } + +is_boolean_unsafe: { + options = { + comparisons: true, + unsafe: true, + } + input: { + console.log(/foo/.test("bar") === [].isPrototypeOf({})); + } + expect: { + console.log(/foo/.test("bar") == [].isPrototypeOf({})); + } + expect_stdout: "true" +} + +is_number_unsafe: { + options = { + comparisons: true, + unsafe: true, + } + input: { + console.log(Math.acos(42) !== "foo".charCodeAt(4)); + } + expect: { + console.log(Math.acos(42) != "foo".charCodeAt(4)); + } + expect_stdout: "true" +} diff --git a/test/sandbox.js b/test/sandbox.js index ecf95d31c0..e85ebd79b0 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -79,7 +79,7 @@ exports.run_code = function(code, reuse) { return ex; } finally { process.stdout.write = original_write; - if (!reuse || /prototype/.test(code)) { + if (!reuse || code.indexOf(".prototype") >= 0) { context = null; } else for (var key in context) { delete context[key]; diff --git a/tools/node.js b/tools/node.js index 2e8f2d5c69..3e37092cb3 100644 --- a/tools/node.js +++ b/tools/node.js @@ -1,7 +1,6 @@ var fs = require("fs"); -var UglifyJS = exports; -var FILES = UglifyJS.FILES = [ +exports.FILES = [ "../lib/utils.js", "../lib/ast.js", "../lib/parse.js", @@ -19,15 +18,12 @@ var FILES = UglifyJS.FILES = [ }); new Function("MOZ_SourceMap", "exports", function() { - var code = FILES.map(function(file) { + var code = exports.FILES.map(function(file) { return fs.readFileSync(file, "utf8"); }); code.push("exports.describe_ast = " + describe_ast.toString()); return code.join("\n\n"); -}())( - require("source-map"), - UglifyJS -); +}())(require("source-map"), exports); function describe_ast() { var out = OutputStream({ beautify: true }); @@ -65,11 +61,11 @@ function describe_ast() { } function infer_options(options) { - var result = UglifyJS.minify("", options); + var result = exports.minify("", options); return result.error && result.error.defs; } -UglifyJS.default_options = function() { +exports.default_options = function() { var defs = {}; Object.keys(infer_options({ 0: 0 })).forEach(function(component) { var options = {};