Skip to content

Commit

Permalink
improve unsafe comparisons (#3200)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlamsl committed Jun 27, 2018
1 parent 88c8f4e commit 957d553
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 41 deletions.
121 changes: 90 additions & 31 deletions lib/compress.js
Expand Up @@ -2264,56 +2264,115 @@ 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);
});

// 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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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, {
Expand Down Expand Up @@ -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: "!",
Expand Down
28 changes: 28 additions & 0 deletions test/compress/comparing.js
Expand Up @@ -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"
}
2 changes: 1 addition & 1 deletion test/sandbox.js
Expand Up @@ -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];
Expand Down
14 changes: 5 additions & 9 deletions 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",
Expand All @@ -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 });
Expand Down Expand Up @@ -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 = {};
Expand Down

0 comments on commit 957d553

Please sign in to comment.