fix label-related bugs (#1835)

- deep cloning of `AST_LabeledStatement`
- `L:do{...}while(false)`
- empty statement with label within block

extend `test/ufuzz.js`
- generate labels for blocks & loops
- generate for-in statements
- skip suspicious option search if `minify()` errs


fixes #1833
This commit is contained in:
Alex Lam S.L 2017-04-22 22:15:04 +08:00 committed by GitHub
parent 6f954aa3d0
commit ca32a09032
5 changed files with 244 additions and 79 deletions

View File

@ -206,12 +206,13 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
clone: function(deep) { clone: function(deep) {
var node = this._clone(deep); var node = this._clone(deep);
if (deep) { if (deep) {
var refs = node.label.references; var label = node.label;
var label = this.label; var def = this.label;
node.walk(new TreeWalker(function(node) { node.walk(new TreeWalker(function(node) {
if (node instanceof AST_LoopControl if (node instanceof AST_LoopControl
&& node.label && node.label.thedef === label) { && node.label && node.label.thedef === def) {
refs.push(node); node.label.thedef = label;
label.references.push(node);
} }
})); }));
} }

View File

@ -2395,7 +2395,7 @@ merge(Compressor.prototype, {
if (compressor.option("dead_code") && self instanceof AST_While) { if (compressor.option("dead_code") && self instanceof AST_While) {
var a = []; var a = [];
extract_declarations_from_unreachable_code(compressor, self.body, a); extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a }); return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
} }
if (self instanceof AST_Do) { if (self instanceof AST_Do) {
var has_loop_control = false; var has_loop_control = false;
@ -2404,7 +2404,8 @@ merge(Compressor.prototype, {
if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self) if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self)
return has_loop_control = true; return has_loop_control = true;
}); });
self.walk(tw); var parent = compressor.parent();
(parent instanceof AST_LabeledStatement ? parent : self).walk(tw);
if (!has_loop_control) return self.body; if (!has_loop_control) return self.body;
} }
} }
@ -2474,7 +2475,7 @@ merge(Compressor.prototype, {
})); }));
} }
extract_declarations_from_unreachable_code(compressor, self.body, a); extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a }); return make_node(AST_BlockStatement, self, { body: a }).optimize(compressor);
} }
if (cond !== self.condition) { if (cond !== self.condition) {
cond = make_node_from_constant(cond, self.condition).transform(compressor); cond = make_node_from_constant(cond, self.condition).transform(compressor);
@ -2726,9 +2727,9 @@ merge(Compressor.prototype, {
var body = []; var body = [];
if (self.bcatch) extract_declarations_from_unreachable_code(compressor, self.bcatch, body); if (self.bcatch) extract_declarations_from_unreachable_code(compressor, self.bcatch, body);
if (self.bfinally) body = body.concat(self.bfinally.body); if (self.bfinally) body = body.concat(self.bfinally.body);
return body.length > 0 ? make_node(AST_BlockStatement, self, { return make_node(AST_BlockStatement, self, {
body: body body: body
}).optimize(compressor) : make_node(AST_EmptyStatement, self); }).optimize(compressor);
} }
return self; return self;
}); });

View File

@ -190,11 +190,7 @@ function OutputStream(options) {
var might_need_space = false; var might_need_space = false;
var might_need_semicolon = false; var might_need_semicolon = false;
var might_add_newline = 0; var might_add_newline = 0;
var last = null; var last = "";
function last_char() {
return last.charAt(last.length - 1);
};
var ensure_line_len = options.max_line_len ? function() { var ensure_line_len = options.max_line_len ? function() {
if (current_col > options.max_line_len) { if (current_col > options.max_line_len) {
@ -218,10 +214,11 @@ function OutputStream(options) {
function print(str) { function print(str) {
str = String(str); str = String(str);
var ch = str.charAt(0); var ch = str.charAt(0);
var prev = last.charAt(last.length - 1);
if (might_need_semicolon) { if (might_need_semicolon) {
might_need_semicolon = false; might_need_semicolon = false;
if ((!ch || ";}".indexOf(ch) < 0) && !/[;]$/.test(last)) { if (prev == ":" && ch == "}" || (!ch || ";}".indexOf(ch) < 0) && prev != ";") {
if (options.semicolons || requireSemicolonChars(ch)) { if (options.semicolons || requireSemicolonChars(ch)) {
OUTPUT += ";"; OUTPUT += ";";
current_col++; current_col++;
@ -258,7 +255,6 @@ function OutputStream(options) {
} }
if (might_need_space) { if (might_need_space) {
var prev = last_char();
if ((is_identifier_char(prev) if ((is_identifier_char(prev)
&& (is_identifier_char(ch) || ch == "\\")) && (is_identifier_char(ch) || ch == "\\"))
|| (ch == "/" && ch == prev) || (ch == "/" && ch == prev)

134
test/compress/issue-1833.js Normal file
View File

@ -0,0 +1,134 @@
iife_for: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: for (;;) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: for (;;) break L;
}();
}();
}
}
iife_for_in: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: for (var a in x) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: for (var a in x) break L;
}();
}();
}
}
iife_do: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: do {
break L;
} while (1);
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: do {
break L;
} while (1);
}();
}();
}
}
iife_while: {
options = {
negate_iife: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
function g() {
L: while (1) break L;
}
g();
}
f();
}
expect: {
!function() {
!function() {
L: while (1) break L;
}();
}();
}
}
label_do: {
options = {
evaluate: true,
loops: true,
}
input: {
L: do {
continue L;
} while (0);
}
expect: {
L: do {
continue L;
} while (0);
}
}
label_while: {
options = {
evaluate: true,
dead_code: true,
loops: true,
}
input: {
function f() {
L: while (0) continue L;
}
}
expect_exact: "function f(){L:;}"
}

View File

@ -20,49 +20,26 @@ var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 12; var MAX_GENERATION_RECURSION_DEPTH = 12;
var INTERVAL_COUNT = 100; var INTERVAL_COUNT = 100;
var STMT_BLOCK = 0; var STMT_ARG_TO_ID = Object.create(null);
var STMT_IF_ELSE = 1; var STMTS_TO_USE = [];
var STMT_DO_WHILE = 2; function STMT_(name) {
var STMT_WHILE = 3; return STMT_ARG_TO_ID[name] = STMTS_TO_USE.push(STMTS_TO_USE.length) - 1;
var STMT_FOR_LOOP = 4; }
var STMT_SEMI = 5;
var STMT_EXPR = 6; var STMT_BLOCK = STMT_("block");
var STMT_SWITCH = 7; var STMT_IF_ELSE = STMT_("ifelse");
var STMT_VAR = 8; var STMT_DO_WHILE = STMT_("dowhile");
var STMT_RETURN_ETC = 9; var STMT_WHILE = STMT_("while");
var STMT_FUNC_EXPR = 10; var STMT_FOR_LOOP = STMT_("forloop");
var STMT_TRY = 11; var STMT_FOR_IN = STMT_("forin");
var STMT_C = 12; var STMT_SEMI = STMT_("semi");
var STMTS_TO_USE = [ var STMT_EXPR = STMT_("expr");
STMT_BLOCK, var STMT_SWITCH = STMT_("switch");
STMT_IF_ELSE, var STMT_VAR = STMT_("var");
STMT_DO_WHILE, var STMT_RETURN_ETC = STMT_("stop");
STMT_WHILE, var STMT_FUNC_EXPR = STMT_("funcexpr");
STMT_FOR_LOOP, var STMT_TRY = STMT_("try");
STMT_SEMI, var STMT_C = STMT_("c");
STMT_EXPR,
STMT_SWITCH,
STMT_VAR,
STMT_RETURN_ETC,
STMT_FUNC_EXPR,
STMT_TRY,
STMT_C,
];
var STMT_ARG_TO_ID = {
block: STMT_BLOCK,
ifelse: STMT_IF_ELSE,
dowhile: STMT_DO_WHILE,
while: STMT_WHILE,
forloop: STMT_FOR_LOOP,
semi: STMT_SEMI,
expr: STMT_EXPR,
switch: STMT_SWITCH,
var: STMT_VAR,
stop: STMT_RETURN_ETC,
funcexpr: STMT_FUNC_EXPR,
try: STMT_TRY,
c: STMT_C,
};
var STMT_FIRST_LEVEL_OVERRIDE = -1; var STMT_FIRST_LEVEL_OVERRIDE = -1;
var STMT_SECOND_LEVEL_OVERRIDE = -1; var STMT_SECOND_LEVEL_OVERRIDE = -1;
@ -296,6 +273,7 @@ var TYPEOF_OUTCOMES = [
var loops = 0; var loops = 0;
var funcs = 0; var funcs = 0;
var labels = 10000;
function rng(max) { function rng(max) {
var r = randomBytes(2).readUInt16LE(0) / 65536; var r = randomBytes(2).readUInt16LE(0) / 65536;
@ -345,7 +323,7 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
s = 'function ' + name + '(' + createParams() + '){' + createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth) + '}\n'; s = 'function ' + name + '(' + createParams() + '){' + createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth) + '}\n';
} else { } else {
// functions with statements // functions with statements
s = 'function ' + name + '(' + createParams() + '){' + createStatements(3, recurmax, canThrow, CANNOT_THROW, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}\n'; s = 'function ' + name + '(' + createParams() + '){' + createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}\n';
} }
VAR_NAMES.length = namesLenBefore; VAR_NAMES.length = namesLenBefore;
@ -367,6 +345,40 @@ function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotRe
return s; return s;
} }
function enableLoopControl(flag, defaultValue) {
return Array.isArray(flag) && flag.indexOf("") < 0 ? flag.concat("") : flag || defaultValue;
}
function createLabel(canBreak, canContinue) {
var label;
if (rng(10) < 3) {
label = ++labels;
if (Array.isArray(canBreak)) {
canBreak = canBreak.slice();
} else {
canBreak = canBreak ? [ "" ] : [];
}
canBreak.push(label);
if (Array.isArray(canContinue)) {
canContinue = canContinue.slice();
} else {
canContinue = canContinue ? [ "" ] : [];
}
canContinue.push(label);
}
return {
break: canBreak,
continue: canContinue,
target: label ? "L" + label + ": " : ""
};
}
function getLabel(label) {
if (!Array.isArray(label)) return "";
label = label[rng(label.length)];
return label && " L" + label;
}
function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
++stmtDepth; ++stmtDepth;
var loop = ++loops; var loop = ++loops;
@ -382,15 +394,34 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
switch (target) { switch (target) {
case STMT_BLOCK: case STMT_BLOCK:
return '{' + createStatements(rng(5) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}'; var label = createLabel(canBreak);
return label.target + '{' + createStatements(rng(5) + 1, recurmax, canThrow, label.break, canContinue, cannotReturn, stmtDepth) + '}';
case STMT_IF_ELSE: case STMT_IF_ELSE:
return 'if (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : ''); return 'if (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : '');
case STMT_DO_WHILE: case STMT_DO_WHILE:
return '{var brake' + loop + ' = 5; do {' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth) + '} while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0);}'; var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return '{var brake' + loop + ' = 5; ' + label.target + 'do {' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '} while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0);}';
case STMT_WHILE: case STMT_WHILE:
return '{var brake' + loop + ' = 5; while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth) + '}'; var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return '{var brake' + loop + ' = 5; ' + label.target + 'while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}';
case STMT_FOR_LOOP: case STMT_FOR_LOOP:
return 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth); var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return label.target + 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
case STMT_FOR_IN:
var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
var optElementVar = '';
if (rng(5) > 1) {
optElementVar = 'c = 1 + c; var ' + createVarName(MANDATORY) + ' = expr' + loop + '[key' + loop + ']; ';
}
return '{var expr' + loop + ' = ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '; ' + label.target + ' for (var key' + loop + ' in expr' + loop + ') {' + optElementVar + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}}';
case STMT_SEMI: case STMT_SEMI:
return ';'; return ';';
case STMT_EXPR: case STMT_EXPR:
@ -424,8 +455,8 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
case 1: case 1:
case 2: case 2:
case 3: case 3:
if (canBreak && rng(5) === 0) return 'break;'; if (canBreak && rng(5) === 0) return 'break' + getLabel(canBreak) + ';';
if (canContinue && rng(5) === 0) return 'continue;'; if (canContinue && rng(5) === 0) return 'continue' + getLabel(canContinue) + ';';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
if (rng(3) == 0) return '/*3*/return;'; if (rng(3) == 0) return '/*3*/return;';
return 'return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';'; return 'return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
@ -470,25 +501,27 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
var hadDefault = false; var hadDefault = false;
var s = ''; var s = [''];
canBreak = enableLoopControl(canBreak, CAN_BREAK);
while (n-- > 0) { while (n-- > 0) {
//hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes) //hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes)
if (hadDefault || rng(5) > 0) { if (hadDefault || rng(5) > 0) {
s += '' + s.push(
'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':\n' + 'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':',
createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) + createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
'\n' + rng(10) > 0 ? ' break;' : '/* fall-through */',
(rng(10) > 0 ? ' break;' : '/* fall-through */') + ''
'\n'; );
} else { } else {
hadDefault = true; hadDefault = true;
s += '' + s.push(
'default:\n' + 'default:',
createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) + createStatements(rng(3) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
'\n'; ''
);
} }
} }
return s; return s.join('\n');
} }
function createExpression(recurmax, noComma, stmtDepth, canThrow) { function createExpression(recurmax, noComma, stmtDepth, canThrow) {
@ -862,7 +895,7 @@ function log(options) {
options = JSON.parse(options); options = JSON.parse(options);
console.log(options); console.log(options);
console.log(); console.log();
if (!ok) { if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options)); Object.keys(default_options).forEach(log_suspects.bind(null, options));
console.log("!!!!!! Failed... round", round); console.log("!!!!!! Failed... round", round);
} }