From: Mihai Bazon Date: Fri, 7 Sep 2012 12:18:32 +0000 (+0300) Subject: always keep declarations found in unreachable code X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=919b2733ab52d3072bb4276210fe442bc0ade5fd;p=UglifyJS.git always keep declarations found in unreachable code a few more tests and some cleanups. --- diff --git a/lib/compress.js b/lib/compress.js index a7563b98..ea0218ed 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -63,7 +63,7 @@ function Compressor(options, false_by_default) { comparations : !false_by_default, evaluate : !false_by_default, booleans : !false_by_default, - dwloops : !false_by_default, + loops : !false_by_default, hoist_funs : !false_by_default, hoist_vars : !false_by_default, @@ -129,15 +129,33 @@ function Compressor(options, false_by_default) { }); }; - function do_list(array, compressor) { + function do_list(array, compressor, splice_blocks) { return MAP(array, function(node){ - return node.squeeze(compressor); + node = node.squeeze(compressor); + if (splice_blocks) { + if (node instanceof AST_BlockStatement) { + return MAP.splice(eliminate_spurious_blocks(node.body)); + } + if (node instanceof AST_EmptyStatement) + return MAP.skip; + } + return node; }); }; + function eliminate_spurious_blocks(statements) { + return statements.reduce(function(a, stat){ + if (stat instanceof AST_BlockStatement) { + a.push.apply(a, stat.body); + } else if (!(stat instanceof AST_EmptyStatement)) { + a.push(stat); + } + return a; + }, []); + }; + function tighten_body(statements, compressor) { - statements = do_list(statements, compressor); - statements = eliminate_spurious_blocks(statements); + statements = do_list(statements, compressor, true); if (compressor.option("dead_code")) { statements = eliminate_dead_code(statements, compressor); } @@ -147,15 +165,25 @@ function Compressor(options, false_by_default) { return statements; }; - function eliminate_spurious_blocks(statements) { - return statements.reduce(function(a, stat){ - if (stat instanceof AST_BlockStatement) { - a.push.apply(a, stat.body); - } else if (!(stat instanceof AST_EmptyStatement)) { - a.push(stat); + function extract_declarations_from_unreachable_code(compressor, stat, target) { + stat.walk(new TreeWalker(function(node){ + if (node instanceof AST_Definitions || node instanceof AST_Defun) { + compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start); + if (node instanceof AST_Definitions) { + node = node.clone(); + node.remove_initializers(); + target.push(node); + } + else if (node instanceof AST_Defun) { + target.push(node); + } + return true; // no point to descend } - return a; - }, []); + if (node instanceof AST_Scope) { + // also don't descend any other nested scopes + return true; + } + })); }; function eliminate_dead_code(statements, compressor) { @@ -166,24 +194,7 @@ function Compressor(options, false_by_default) { a.push(stat); } else { - stat.walk(new TreeWalker(function(node){ - if (node instanceof AST_Definitions || node instanceof AST_Defun) { - compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start); - if (node instanceof AST_Definitions) { - node = node.clone(); - node.remove_initializers(); - a.push(node); - } - else if (node instanceof AST_Defun) { - a.push(node); - } - return true; // no point to descend - } - if (node instanceof AST_Scope) { - // also don't descend any other nested scopes - return true; - } - })); + extract_declarations_from_unreachable_code(compressor, stat, a); }; } else { a.push(stat); @@ -552,18 +563,27 @@ function Compressor(options, false_by_default) { return self.optimize(compressor); }); + function warn_dead_code(node) { + AST_Node.warn("Dropping unreachable code [{line},{col}]", node.start); + }; + AST_DWLoop.DEFMETHOD("optimize", function(compressor){ var self = this; - if (!compressor.option("dwloops")) return self; var cond = self.condition.evaluate(compressor); + self.condition = cond[0]; + if (!compressor.option("loops")) return self; if (cond.length == 2) { if (cond[1]) { return make_node(AST_For, self, { body: self.body }); } else if (self instanceof AST_While) { - AST_Node.warn("Unreachable code [{line},{col}]", self.start); - return make_node(AST_EmptyStatement, self); + if (compressor.option("dead_code")) { + warn_dead_code(self); + var a = []; + extract_declarations_from_unreachable_code(compressor, self.body, a); + return make_node(AST_BlockStatement, self, { body: a }); + } } } return self; @@ -575,7 +595,34 @@ function Compressor(options, false_by_default) { if (self.condition) self.condition = self.condition.squeeze(compressor); if (self.step) self.step = self.step.squeeze(compressor); self.body = self.body.squeeze(compressor); - return self; + return self.optimize(compressor); + }); + + AST_For.DEFMETHOD("optimize", function(compressor){ + var cond = this.condition; + if (cond) { + cond = cond.evaluate(compressor); + this.condition = cond[0]; + } + if (!compressor.option("loops")) return this; + if (this.condition) { + var cond = this.condition.evaluate(compressor); + if (cond.length == 2 && !cond[1]) { + if (compressor.option("dead_code")) { + warn_dead_code(this.body); + var a = []; + if (this.init instanceof AST_Statement) a.push(this.init); + else if (this.init) a.push(make_node(AST_SimpleStatement, this.init, { + body: this.init + })); + extract_declarations_from_unreachable_code(compressor, this.body, a); + return make_node(AST_BlockStatement, this, { + body: a + }); + } + } + } + return this; }); SQUEEZE(AST_ForIn, function(self, compressor){ @@ -625,10 +672,24 @@ function Compressor(options, false_by_default) { if (cond.length == 2) { if (cond[1]) { AST_Node.warn("Condition always true [{line},{col}]", self.condition.start); - return self.body; + if (compressor.option("dead_code")) { + var a = []; + if (self.alternative) { + warn_dead_code(self.alternative); + extract_declarations_from_unreachable_code(compressor, self.alternative, a); + } + a.push(self.body); + return make_node(AST_BlockStatement, self, { body: a }); + } } else { AST_Node.warn("Condition always false [{line},{col}]", self.condition.start); - return self.alternative || make_node(AST_EmptyStatement, self); + if (compressor.option("dead_code")) { + warn_dead_code(self.body); + var a = []; + extract_declarations_from_unreachable_code(compressor, self.body, a); + if (self.alternative) a.push(self.alternative); + return make_node(AST_BlockStatement, self, { body: a }); + } } } if (self.condition instanceof AST_UnaryPrefix @@ -638,6 +699,12 @@ function Compressor(options, false_by_default) { self.body = self.alternative || make_node(AST_EmptyStatement, self); self.alternative = tmp; } + if (self.body instanceof AST_EmptyStatement + && self.alternative instanceof AST_EmptyStatement) { + return make_node(AST_SimpleStatement, self.condition, { + body: self.condition + }); + } if (self.body instanceof AST_SimpleStatement && self.alternative instanceof AST_SimpleStatement) { return make_node(AST_SimpleStatement, self, { @@ -814,11 +881,15 @@ function Compressor(options, false_by_default) { SQUEEZE(AST_UnaryPrefix, function(self, compressor){ // need to determine the context before cloning the node - var bool = compressor.in_boolean_context(); self = self.clone(); - var e = self.expression = self.expression.squeeze(compressor); - if (compressor.option("booleans") && bool) { - switch (self.operator) { + self.expression = self.expression.squeeze(compressor); + return self.optimize(compressor); + }); + + AST_UnaryPrefix.DEFMETHOD("optimize", function(compressor){ + if (compressor.option("booleans") && compressor.in_boolean_context()) { + var e = this.expression; + switch (this.operator) { case "!": if (e instanceof AST_UnaryPrefix && e.operator == "!") { // !!foo ==> foo, if we're in boolean context @@ -828,11 +899,11 @@ function Compressor(options, false_by_default) { case "typeof": // typeof always returns a non-empty string, thus it's // always true in booleans - AST_Node.warn("Boolean expression always true [{line},{col}]", self.start); - return make_node(AST_True, self).optimize(compressor); + AST_Node.warn("Boolean expression always true [{line},{col}]", this.start); + return make_node(AST_True, this).optimize(compressor); } } - return self.evaluate(compressor)[0]; + return this.evaluate(compressor)[0]; }); SQUEEZE(AST_Binary, function(self, compressor){ diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js new file mode 100644 index 00000000..ba5e674d --- /dev/null +++ b/test/compress/conditionals.js @@ -0,0 +1,74 @@ +ifs_1: { + options = { + conditionals: true + }; + input: { + if (foo) bar(); + if (!foo); else bar(); + if (foo); else bar(); + if (foo); else; + } + expect: { + foo&&bar(); + foo&&bar(); + foo||bar(); + foo; + } +} + +ifs_2: { + options = { + conditionals: true + }; + input: { + if (foo) { + x(); + } else if (bar) { + y(); + } else if (baz) { + z(); + } + + if (foo) { + x(); + } else if (bar) { + y(); + } else if (baz) { + z(); + } else { + t(); + } + } + expect: { + foo ? x() : bar ? y() : baz && z(); + foo ? x() : bar ? y() : baz ? z() : t(); + } +} + +ifs_3_should_warn: { + options = { + conditionals : true, + dead_code : true, + evaluate : true, + booleans : true + }; + input: { + if (x && !(x + "1") && y) { // 1 + var qq; + foo(); + } else { + bar(); + } + + if (x || !!(x + "1") || y) { // 2 + foo(); + } else { + var jj; + bar(); + } + } + expect: { + var qq; bar(); // 1 + var jj; foo(); // 2 + } +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index e84b4011..0fd066eb 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -57,3 +57,33 @@ dead_code_2_should_warn: { } } } + +dead_code_constant_boolean_should_warn_more: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true + }; + input: { + while (!((foo && bar) || (x + "0"))) { + console.log("unreachable"); + var foo; + function bar() {} + } + for (var x = 10; x && (y || x) && (!typeof x); ++x) { + asdf(); + foo(); + var moo; + } + } + expect: { + var foo; + function bar() {} + // nothing for the while + // as for the for, it should keep: + var x = 10; + var moo; + } +} diff --git a/test/run-tests.js b/test/run-tests.js index b70e2291..158c9d35 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -119,8 +119,10 @@ function parse_test(file) { }) ); var stat = node.body; - if (stat instanceof U.AST_BlockStatement && stat.body.length == 1) - stat = stat.body[0]; + if (stat instanceof U.AST_BlockStatement) { + if (stat.body.length == 1) stat = stat.body[0]; + else if (stat.body.length == 0) stat = new U.AST_EmptyStatement(); + } test[node.label.name] = stat; return true; } diff --git a/tools/node.js b/tools/node.js index be83d171..c8d3bd8c 100644 --- a/tools/node.js +++ b/tools/node.js @@ -32,7 +32,7 @@ load_global("../lib/compress.js"); load_global("../lib/sourcemap.js"); UglifyJS.AST_Node.warn_function = function(txt) { - sys.debug(txt); + sys.error("WARN: " + txt); }; // XXX: perhaps we shouldn't export everything but heck, I'm lazy.