always keep declarations found in unreachable code

a few more tests and some cleanups.
This commit is contained in:
Mihai Bazon
2012-09-07 15:18:32 +03:00
parent b77574ea1c
commit 919b2733ab
5 changed files with 226 additions and 49 deletions

View File

@@ -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,24 +129,20 @@ 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 tighten_body(statements, compressor) {
statements = do_list(statements, compressor);
statements = eliminate_spurious_blocks(statements);
if (compressor.option("dead_code")) {
statements = eliminate_dead_code(statements, compressor);
}
if (compressor.option("sequences")) {
statements = sequencesize(statements, compressor);
}
return statements;
};
function eliminate_spurious_blocks(statements) {
return statements.reduce(function(a, stat){
if (stat instanceof AST_BlockStatement) {
@@ -158,6 +154,38 @@ function Compressor(options, false_by_default) {
}, []);
};
function tighten_body(statements, compressor) {
statements = do_list(statements, compressor, true);
if (compressor.option("dead_code")) {
statements = eliminate_dead_code(statements, compressor);
}
if (compressor.option("sequences")) {
statements = sequencesize(statements, compressor);
}
return statements;
};
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
}
if (node instanceof AST_Scope) {
// also don't descend any other nested scopes
return true;
}
}));
};
function eliminate_dead_code(statements, compressor) {
var has_quit = false;
return statements.reduce(function(a, stat){
@@ -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){

View File

@@ -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
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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.