improve test/reduce (#3710)

- suppress several instances of malformed AST generation
- improve resilience & reporting against malformed ASTs
This commit is contained in:
Alex Lam S.L
2020-02-09 08:07:55 +00:00
committed by GitHub
parent 44499a6643
commit 70551febc8
2 changed files with 108 additions and 42 deletions

View File

@@ -57,4 +57,10 @@ describe("test/reduce.js", function() {
assert.ok(err instanceof Error); assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "DefaultsError: `unsafe_regex` is not a supported option"); assert.strictEqual(err.stack.split(/\n/)[0], "DefaultsError: `unsafe_regex` is not a supported option");
}); });
it("Should report on test case with invalid syntax", function() {
var result = reduce_test("var 0 = 1;");
var err = result.error;
assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Name expected");
});
}); });

View File

@@ -52,11 +52,12 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
if (CHANGED) return; if (CHANGED) return;
// quick ignores // quick ignores
if (node instanceof U.AST_Toplevel) return;
if (node instanceof U.AST_Accessor) return; if (node instanceof U.AST_Accessor) return;
if (node instanceof U.AST_Directive) return; if (node instanceof U.AST_Directive) return;
// if (node instanceof U.AST_Var) return; if (node instanceof U.AST_Label) return;
// if (node instanceof U.AST_VarDef) return; if (node instanceof U.AST_LabelRef) return;
if (node instanceof U.AST_SymbolDeclaration) return;
if (node instanceof U.AST_Toplevel) return;
var parent = tt.parent(); var parent = tt.parent();
@@ -85,12 +86,17 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
CHANGED = true; CHANGED = true;
return null; return null;
} }
if ((parent instanceof U.AST_For || parent instanceof U.AST_ForIn)
&& parent.init === node && node instanceof U.AST_Var) {
// preserve for (var ...)
return node;
}
// node specific permutations with no parent logic // node specific permutations with no parent logic
if (node instanceof U.AST_Array) { if (node instanceof U.AST_Array) {
var expr = node.elements[0]; var expr = node.elements[0];
if (expr) { if (expr && !(expr instanceof U.AST_Hole)) {
node.start._permute++; node.start._permute++;
CHANGED = true; CHANGED = true;
return expr; return expr;
@@ -151,12 +157,9 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
expr = node.condition; // AST_While - fall through expr = node.condition; // AST_While - fall through
} }
} }
if (expr) { if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true; CHANGED = true;
return expr instanceof U.AST_Statement ? expr : new U.AST_SimpleStatement({ return to_statement(expr);
body: expr,
start: {},
});
} }
} }
else if (node instanceof U.AST_PropAccess) { else if (node instanceof U.AST_PropAccess) {
@@ -177,12 +180,9 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
node.body, node.body,
][ (node.start._permute * steps | 0) % 4 ]; ][ (node.start._permute * steps | 0) % 4 ];
node.start._permute += step; node.start._permute += step;
if (expr) { if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true; CHANGED = true;
return expr instanceof U.AST_Statement ? expr : new U.AST_SimpleStatement({ return to_statement(expr);
body: expr,
start: {},
});
} }
} }
else if (node instanceof U.AST_ForIn) { else if (node instanceof U.AST_ForIn) {
@@ -192,12 +192,9 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
node.body, node.body,
][ (node.start._permute * steps | 0) % 3 ]; ][ (node.start._permute * steps | 0) % 3 ];
node.start._permute += step; node.start._permute += step;
if (expr) { if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true; CHANGED = true;
return expr instanceof U.AST_Statement ? expr : new U.AST_SimpleStatement({ return to_statement(expr);
body: expr,
start: {},
});
} }
} }
else if (node instanceof U.AST_If) { else if (node instanceof U.AST_If) {
@@ -224,15 +221,15 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
var expr = [ var expr = [
node.expression, // switch expression node.expression, // switch expression
node.body[0] && node.body[0].expression, // first case expression or undefined node.body[0] && node.body[0].expression, // first case expression or undefined
node.body[0] && node.body[0].body[0], // first case body statement or undefined node.body[0] && node.body[0], // first case body or undefined
][ (node.start._permute * steps | 0) % 3 ]; ][ (node.start._permute * steps | 0) % 4 ];
node.start._permute += step; node.start._permute += step;
if (expr) { if (expr && (!(expr instanceof U.AST_Statement) || !has_loopcontrol(expr, node, parent))) {
CHANGED = true; CHANGED = true;
return expr instanceof U.AST_Statement ? expr : new U.AST_SimpleStatement({ return expr instanceof U.AST_SwitchBranch ? new U.AST_BlockStatement({
body: expr, body: expr.body.slice(),
start: {}, start: {},
}); }) : to_statement(expr);
} }
} }
else if (node instanceof U.AST_Try) { else if (node instanceof U.AST_Try) {
@@ -277,7 +274,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// replace or skip statement // replace or skip statement
if (node instanceof U.AST_Statement) { if (node instanceof U.AST_Statement) {
if (node instanceof U.AST_LabeledStatement if (node instanceof U.AST_LabeledStatement
&& node.body instanceof U.AST_Statement) { && node.body instanceof U.AST_Statement
&& !has_loopcontrol(node.body, node.body, node)) {
// replace labelled statement with its non-labelled body // replace labelled statement with its non-labelled body
node.start._permute = REPLACEMENTS.length; node.start._permute = REPLACEMENTS.length;
CHANGED = true; CHANGED = true;
@@ -287,15 +285,46 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
CHANGED = true; CHANGED = true;
return List.skip; return List.skip;
} }
// remove this node unless its the sole element of a (transient) sequence
if (!(parent instanceof U.AST_Sequence) || parent.expressions.length > 1) {
node.start._permute++;
CHANGED = true;
return List.skip;
}
} }
// replace or remove this node depending on whether it's in a list // replace this node
var newNode = new U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], { var newNode = new U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
expression: true, expression: true,
}); });
if (is_statement(node)) {
newNode = new U.AST_SimpleStatement({
body: newNode,
start: {},
});
}
newNode.start._permute = ++node.start._permute; newNode.start._permute = ++node.start._permute;
CHANGED = true; CHANGED = true;
return in_list ? List.skip : newNode; return newNode;
}, function(node, in_list) {
if (node instanceof U.AST_Sequence) {
// expand single-element sequence
if (node.expressions.length == 1) return node.expressions[0];
}
else if (node instanceof U.AST_Try) {
// expand orphaned try block
if (!node.bcatch && !node.bfinally) return new U.AST_BlockStatement({
body: node.body,
start: {}
});
}
else if (node instanceof U.AST_Var) {
// remove empty var statement
if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({
start: {}
});
}
}); });
for (var pass = 1; pass <= 3; ++pass) { for (var pass = 1; pass <= 3; ++pass) {
@@ -321,16 +350,23 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// AST is not well formed. // AST is not well formed.
// no harm done - just log the error, ignore latest change and continue iterating. // no harm done - just log the error, ignore latest change and continue iterating.
console.error("*** Error generating code from AST."); console.error("*** Error generating code from AST.");
console.error(ex.stack); console.error(ex);
console.error("*** Discarding permutation and continuing."); console.error("*** Discarding permutation and continuing.");
continue;
} }
if (code) {
var diff = producesDifferentResultWhenMinified(code, minify_options, timeout); var diff = producesDifferentResultWhenMinified(code, minify_options, timeout);
if (diff) { if (diff) {
if (diff.timed_out) { if (diff.timed_out) {
// can't trust the validity of `code_ast` and `code` when timed out. // can't trust the validity of `code_ast` and `code` when timed out.
// no harm done - just ignore latest change and continue iterating. // no harm done - just ignore latest change and continue iterating.
if (timeout < max_timeout) timeout += 250; if (timeout < max_timeout) timeout += 250;
} else if (diff.error) {
// something went wrong during minify() - could be malformed AST or genuine bug.
// no harm done - just log code & error, ignore latest change and continue iterating.
console.error("*** Error during minification.");
console.error(code);
console.error(diff.error);
console.error("*** Discarding permutation and continuing.");
} else { } else {
// latest permutation is valid, so use it as the basis of new changes // latest permutation is valid, so use it as the basis of new changes
testcase_ast = code_ast; testcase_ast = code_ast;
@@ -339,7 +375,6 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} }
} }
} }
}
if (c == 0) break; if (c == 0) break;
if (verbose) { if (verbose) {
console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes"); console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes");
@@ -365,6 +400,31 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
return result; return result;
}; };
function has_loopcontrol(body, loop, label) {
var found = false;
var tw = new U.TreeWalker(function(node) {
if (found) return true;
if (node instanceof U.AST_LoopControl && this.loopcontrol_target(node) === loop) {
return found = true;
}
});
if (label instanceof U.AST_LabeledStatement) tw.push(label);
tw.push(loop);
body.walk(tw);
return found;
}
function is_statement(node) {
return node instanceof U.AST_Statement && !(node instanceof U.AST_Function);
}
function to_statement(node) {
return is_statement(node) ? node : new U.AST_SimpleStatement({
body: node,
start: {},
});
}
function producesDifferentResultWhenMinified(code, minify_options, timeout) { function producesDifferentResultWhenMinified(code, minify_options, timeout) {
var minified = U.minify(code, minify_options); var minified = U.minify(code, minify_options);
if (minified.error) return minified; if (minified.error) return minified;