improve unsafe comparisons (#3200)
This commit is contained in:
129
lib/compress.js
129
lib/compress.js
@@ -2264,29 +2264,35 @@ merge(Compressor.prototype, {
|
|||||||
|
|
||||||
// methods to determine whether an expression has a boolean result type
|
// methods to determine whether an expression has a boolean result type
|
||||||
(function(def) {
|
(function(def) {
|
||||||
var unary_bool = makePredicate("! delete");
|
|
||||||
var binary_bool = makePredicate("in instanceof == != === !== < <= >= >");
|
|
||||||
def(AST_Node, return_false);
|
def(AST_Node, return_false);
|
||||||
|
def(AST_Assign, function(compressor) {
|
||||||
|
return this.operator == "=" && this.right.is_boolean(compressor);
|
||||||
|
});
|
||||||
|
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_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_Conditional, function(compressor) {
|
||||||
|
return this.consequent.is_boolean(compressor) && this.alternative.is_boolean(compressor);
|
||||||
|
});
|
||||||
|
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() {
|
def(AST_UnaryPrefix, function() {
|
||||||
return unary_bool[this.operator];
|
return unary[this.operator];
|
||||||
});
|
});
|
||||||
def(AST_Binary, function() {
|
|
||||||
return binary_bool[this.operator]
|
|
||||||
|| lazy_op[this.operator]
|
|
||||||
&& this.left.is_boolean()
|
|
||||||
&& this.right.is_boolean();
|
|
||||||
});
|
|
||||||
def(AST_Conditional, function() {
|
|
||||||
return this.consequent.is_boolean() && this.alternative.is_boolean();
|
|
||||||
});
|
|
||||||
def(AST_Assign, function() {
|
|
||||||
return this.operator == "=" && this.right.is_boolean();
|
|
||||||
});
|
|
||||||
def(AST_Sequence, function() {
|
|
||||||
return this.tail_node().is_boolean();
|
|
||||||
});
|
|
||||||
def(AST_True, return_true);
|
|
||||||
def(AST_False, return_true);
|
|
||||||
})(function(node, func) {
|
})(function(node, func) {
|
||||||
node.DEFMETHOD("is_boolean", func);
|
node.DEFMETHOD("is_boolean", func);
|
||||||
});
|
});
|
||||||
@@ -2294,27 +2300,80 @@ merge(Compressor.prototype, {
|
|||||||
// methods to determine if an expression has a numeric result type
|
// methods to determine if an expression has a numeric result type
|
||||||
(function(def) {
|
(function(def) {
|
||||||
def(AST_Node, return_false);
|
def(AST_Node, return_false);
|
||||||
def(AST_Number, return_true);
|
|
||||||
var unary = makePredicate("+ - ~ ++ --");
|
|
||||||
def(AST_Unary, function() {
|
|
||||||
return unary[this.operator];
|
|
||||||
});
|
|
||||||
var binary = makePredicate("- * / % & | ^ << >> >>>");
|
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) {
|
def(AST_Binary, function(compressor) {
|
||||||
return binary[this.operator] || this.operator == "+"
|
return binary[this.operator] || this.operator == "+"
|
||||||
&& this.left.is_number(compressor)
|
&& this.left.is_number(compressor)
|
||||||
&& this.right.is_number(compressor);
|
&& this.right.is_number(compressor);
|
||||||
});
|
});
|
||||||
def(AST_Assign, function(compressor) {
|
var fn = makePredicate([
|
||||||
return binary[this.operator.slice(0, -1)]
|
"charCodeAt",
|
||||||
|| this.operator == "=" && this.right.is_number(compressor);
|
"getDate",
|
||||||
});
|
"getDay",
|
||||||
def(AST_Sequence, function(compressor) {
|
"getFullYear",
|
||||||
return this.tail_node().is_number(compressor);
|
"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) {
|
def(AST_Conditional, function(compressor) {
|
||||||
return this.consequent.is_number(compressor) && this.alternative.is_number(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);
|
||||||
|
});
|
||||||
|
var unary = makePredicate("+ - ~ ++ --");
|
||||||
|
def(AST_Unary, function() {
|
||||||
|
return unary[this.operator];
|
||||||
|
});
|
||||||
})(function(node, func) {
|
})(function(node, func) {
|
||||||
node.DEFMETHOD("is_number", func);
|
node.DEFMETHOD("is_number", func);
|
||||||
});
|
});
|
||||||
@@ -2902,7 +2961,7 @@ merge(Compressor.prototype, {
|
|||||||
var map;
|
var map;
|
||||||
if (expr instanceof AST_Array) {
|
if (expr instanceof AST_Array) {
|
||||||
map = native_fns.Array;
|
map = native_fns.Array;
|
||||||
} else if (expr.is_boolean()) {
|
} else if (expr.is_boolean(compressor)) {
|
||||||
map = native_fns.Boolean;
|
map = native_fns.Boolean;
|
||||||
} else if (expr.is_number(compressor)) {
|
} else if (expr.is_number(compressor)) {
|
||||||
map = native_fns.Number;
|
map = native_fns.Number;
|
||||||
@@ -5247,7 +5306,7 @@ merge(Compressor.prototype, {
|
|||||||
var is_strict_comparison = true;
|
var is_strict_comparison = true;
|
||||||
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
|
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
|
||||||
(self.left.is_number(compressor) && self.right.is_number(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.left.equivalent_to(self.right)) {
|
||||||
self.operator = self.operator.substr(0, 2);
|
self.operator = self.operator.substr(0, 2);
|
||||||
}
|
}
|
||||||
@@ -5328,7 +5387,7 @@ merge(Compressor.prototype, {
|
|||||||
]).optimize(compressor);
|
]).optimize(compressor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (compressor.option("comparisons") && self.is_boolean()) {
|
if (compressor.option("comparisons") && self.is_boolean(compressor)) {
|
||||||
if (!(compressor.parent() instanceof AST_Binary)
|
if (!(compressor.parent() instanceof AST_Binary)
|
||||||
|| compressor.parent() instanceof AST_Assign) {
|
|| compressor.parent() instanceof AST_Assign) {
|
||||||
var negated = make_node(AST_UnaryPrefix, self, {
|
var negated = make_node(AST_UnaryPrefix, self, {
|
||||||
@@ -6102,7 +6161,7 @@ merge(Compressor.prototype, {
|
|||||||
return self;
|
return self;
|
||||||
|
|
||||||
function booleanize(node) {
|
function booleanize(node) {
|
||||||
if (node.is_boolean()) return node;
|
if (node.is_boolean(compressor)) return node;
|
||||||
// !!expression
|
// !!expression
|
||||||
return make_node(AST_UnaryPrefix, node, {
|
return make_node(AST_UnaryPrefix, node, {
|
||||||
operator: "!",
|
operator: "!",
|
||||||
|
|||||||
@@ -295,3 +295,31 @@ issue_2857_6: {
|
|||||||
}
|
}
|
||||||
expect_stdout: "true"
|
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"
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ exports.run_code = function(code, reuse) {
|
|||||||
return ex;
|
return ex;
|
||||||
} finally {
|
} finally {
|
||||||
process.stdout.write = original_write;
|
process.stdout.write = original_write;
|
||||||
if (!reuse || /prototype/.test(code)) {
|
if (!reuse || code.indexOf(".prototype") >= 0) {
|
||||||
context = null;
|
context = null;
|
||||||
} else for (var key in context) {
|
} else for (var key in context) {
|
||||||
delete context[key];
|
delete context[key];
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
|
||||||
var UglifyJS = exports;
|
exports.FILES = [
|
||||||
var FILES = UglifyJS.FILES = [
|
|
||||||
"../lib/utils.js",
|
"../lib/utils.js",
|
||||||
"../lib/ast.js",
|
"../lib/ast.js",
|
||||||
"../lib/parse.js",
|
"../lib/parse.js",
|
||||||
@@ -19,15 +18,12 @@ var FILES = UglifyJS.FILES = [
|
|||||||
});
|
});
|
||||||
|
|
||||||
new Function("MOZ_SourceMap", "exports", function() {
|
new Function("MOZ_SourceMap", "exports", function() {
|
||||||
var code = FILES.map(function(file) {
|
var code = exports.FILES.map(function(file) {
|
||||||
return fs.readFileSync(file, "utf8");
|
return fs.readFileSync(file, "utf8");
|
||||||
});
|
});
|
||||||
code.push("exports.describe_ast = " + describe_ast.toString());
|
code.push("exports.describe_ast = " + describe_ast.toString());
|
||||||
return code.join("\n\n");
|
return code.join("\n\n");
|
||||||
}())(
|
}())(require("source-map"), exports);
|
||||||
require("source-map"),
|
|
||||||
UglifyJS
|
|
||||||
);
|
|
||||||
|
|
||||||
function describe_ast() {
|
function describe_ast() {
|
||||||
var out = OutputStream({ beautify: true });
|
var out = OutputStream({ beautify: true });
|
||||||
@@ -65,11 +61,11 @@ function describe_ast() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function infer_options(options) {
|
function infer_options(options) {
|
||||||
var result = UglifyJS.minify("", options);
|
var result = exports.minify("", options);
|
||||||
return result.error && result.error.defs;
|
return result.error && result.error.defs;
|
||||||
}
|
}
|
||||||
|
|
||||||
UglifyJS.default_options = function() {
|
exports.default_options = function() {
|
||||||
var defs = {};
|
var defs = {};
|
||||||
Object.keys(infer_options({ 0: 0 })).forEach(function(component) {
|
Object.keys(infer_options({ 0: 0 })).forEach(function(component) {
|
||||||
var options = {};
|
var options = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user