support asynchronous test cases properly (#4529)
This commit is contained in:
2
.github/workflows/ufuzz.yml
vendored
2
.github/workflows/ufuzz.yml
vendored
@@ -32,6 +32,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install GNU Core Utilities
|
- name: Install GNU Core Utilities
|
||||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
if: ${{ startsWith(matrix.os, 'macos') }}
|
||||||
|
env:
|
||||||
|
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
brew install coreutils
|
brew install coreutils
|
||||||
|
|||||||
@@ -382,6 +382,7 @@ merge(Compressor.prototype, {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var RE_POSITIVE_INTEGER = /^(0|[1-9][0-9]*)$/;
|
||||||
(function(def) {
|
(function(def) {
|
||||||
def(AST_Node, noop);
|
def(AST_Node, noop);
|
||||||
|
|
||||||
@@ -603,7 +604,7 @@ merge(Compressor.prototype, {
|
|||||||
if (!is_arguments(def)) return;
|
if (!is_arguments(def)) return;
|
||||||
var key = node.property;
|
var key = node.property;
|
||||||
if (key.is_constant()) key = key.value;
|
if (key.is_constant()) key = key.value;
|
||||||
if (!(key instanceof AST_Node) && !/^[1-9]*[0-9]$/.test(key)) return;
|
if (!(key instanceof AST_Node) && !RE_POSITIVE_INTEGER.test(key)) return;
|
||||||
def.reassigned = true;
|
def.reassigned = true;
|
||||||
(key instanceof AST_Node ? def.scope.argnames : [ def.scope.argnames[key] ]).forEach(function(argname) {
|
(key instanceof AST_Node ? def.scope.argnames : [ def.scope.argnames[key] ]).forEach(function(argname) {
|
||||||
if (argname instanceof AST_SymbolFunarg) argname.definition().fixed = false;
|
if (argname instanceof AST_SymbolFunarg) argname.definition().fixed = false;
|
||||||
@@ -8160,7 +8161,7 @@ merge(Compressor.prototype, {
|
|||||||
try {
|
try {
|
||||||
var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
|
var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
|
||||||
return arg.value;
|
return arg.value;
|
||||||
}).join(",") + "){" + self.args[self.args.length - 1].value + "})";
|
}).join() + "){" + self.args[self.args.length - 1].value + "})";
|
||||||
var ast = parse(code);
|
var ast = parse(code);
|
||||||
var mangle = { ie8: compressor.option("ie8") };
|
var mangle = { ie8: compressor.option("ie8") };
|
||||||
ast.figure_out_scope(mangle);
|
ast.figure_out_scope(mangle);
|
||||||
@@ -8183,7 +8184,7 @@ merge(Compressor.prototype, {
|
|||||||
make_node(AST_String, self, {
|
make_node(AST_String, self, {
|
||||||
value: fun.argnames.map(function(arg) {
|
value: fun.argnames.map(function(arg) {
|
||||||
return arg.print_to_string();
|
return arg.print_to_string();
|
||||||
}).join(",")
|
}).join(),
|
||||||
}),
|
}),
|
||||||
make_node(AST_String, self.args[self.args.length - 1], {
|
make_node(AST_String, self.args[self.args.length - 1], {
|
||||||
value: code.get().replace(/^\{|\}$/g, "")
|
value: code.get().replace(/^\{|\}$/g, "")
|
||||||
@@ -10846,7 +10847,7 @@ merge(Compressor.prototype, {
|
|||||||
flush();
|
flush();
|
||||||
values.push(prop);
|
values.push(prop);
|
||||||
}
|
}
|
||||||
if (found && !generated && typeof key == "string" && /^[1-9]*[0-9]$/.test(key)) {
|
if (found && !generated && typeof key == "string" && RE_POSITIVE_INTEGER.test(key)) {
|
||||||
generated = true;
|
generated = true;
|
||||||
if (keys.has(key)) prop = keys.get(key)[0];
|
if (keys.has(key)) prop = keys.get(key)[0];
|
||||||
prop.key = make_node(AST_Number, prop, {
|
prop.key = make_node(AST_Number, prop, {
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
|
|||||||
} else {
|
} else {
|
||||||
var toplevel = sandbox.has_toplevel(options);
|
var toplevel = sandbox.has_toplevel(options);
|
||||||
var expected = stdout[toplevel ? 1 : 0];
|
var expected = stdout[toplevel ? 1 : 0];
|
||||||
var actual = run_code(result.code, toplevel);
|
var actual = sandbox.run_code(result.code, toplevel);
|
||||||
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
|
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
|
||||||
actual = expected;
|
actual = expected;
|
||||||
}
|
}
|
||||||
@@ -244,11 +244,6 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_code(code, toplevel) {
|
|
||||||
var result = sandbox.run_code(code, toplevel);
|
|
||||||
return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_case(test) {
|
function test_case(test) {
|
||||||
log(" Running test [{name}]", { name: test.name });
|
log(" Running test [{name}]", { name: test.name });
|
||||||
U.AST_Node.enable_validation();
|
U.AST_Node.enable_validation();
|
||||||
@@ -380,7 +375,7 @@ function test_case(test) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
|
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
|
||||||
var stdout = [ run_code(input_code), run_code(input_code, true) ];
|
var stdout = [ sandbox.run_code(input_code), sandbox.run_code(input_code, true) ];
|
||||||
var toplevel = sandbox.has_toplevel({
|
var toplevel = sandbox.has_toplevel({
|
||||||
compress: test.options,
|
compress: test.options,
|
||||||
mangle: test.mangle
|
mangle: test.mangle
|
||||||
@@ -409,7 +404,7 @@ function test_case(test) {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
actual = run_code(output_code, toplevel);
|
actual = sandbox.run_code(output_code, toplevel);
|
||||||
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
|
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
|
||||||
log([
|
log([
|
||||||
"!!! failed",
|
"!!! failed",
|
||||||
|
|||||||
@@ -612,22 +612,32 @@ issue_4340: {
|
|||||||
call_expression: {
|
call_expression: {
|
||||||
input: {
|
input: {
|
||||||
console.log(typeof async function(log) {
|
console.log(typeof async function(log) {
|
||||||
(await log)("FAIL");
|
(await log)("foo");
|
||||||
}(console.log).then);
|
}(console.log).then);
|
||||||
|
console.log("bar");
|
||||||
}
|
}
|
||||||
expect_exact: 'console.log(typeof async function(log){(await log)("FAIL")}(console.log).then);'
|
expect_exact: 'console.log(typeof async function(log){(await log)("foo")}(console.log).then);console.log("bar");'
|
||||||
expect_stdout: "function"
|
expect_stdout: [
|
||||||
|
"function",
|
||||||
|
"bar",
|
||||||
|
"foo",
|
||||||
|
]
|
||||||
node_version: ">=8"
|
node_version: ">=8"
|
||||||
}
|
}
|
||||||
|
|
||||||
property_access_expression: {
|
property_access_expression: {
|
||||||
input: {
|
input: {
|
||||||
console.log(typeof async function(con) {
|
console.log(typeof async function(con) {
|
||||||
(await con).log("FAIL");
|
(await con).log("foo");
|
||||||
}(console).then);
|
}(console).then);
|
||||||
|
console.log("bar");
|
||||||
}
|
}
|
||||||
expect_exact: 'console.log(typeof async function(con){(await con).log("FAIL")}(console).then);'
|
expect_exact: 'console.log(typeof async function(con){(await con).log("foo")}(console).then);console.log("bar");'
|
||||||
expect_stdout: "function"
|
expect_stdout: [
|
||||||
|
"function",
|
||||||
|
"bar",
|
||||||
|
"foo",
|
||||||
|
]
|
||||||
node_version: ">=8"
|
node_version: ">=8"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,20 +695,18 @@ reduce_iife_3: {
|
|||||||
input: {
|
input: {
|
||||||
var a = "foo";
|
var a = "foo";
|
||||||
(async function() {
|
(async function() {
|
||||||
console.log(a);
|
console.log(a, await a, a, await a);
|
||||||
console.log(await a);
|
|
||||||
})();
|
})();
|
||||||
a = "bar";
|
a = "bar";
|
||||||
}
|
}
|
||||||
expect: {
|
expect: {
|
||||||
var a = "foo";
|
var a = "foo";
|
||||||
(async function() {
|
(async function() {
|
||||||
console.log(a);
|
console.log(a, await a, a, await a);
|
||||||
console.log(await a);
|
|
||||||
})();
|
})();
|
||||||
a = "bar";
|
a = "bar";
|
||||||
}
|
}
|
||||||
expect_stdout: "foo"
|
expect_stdout: "foo foo bar bar"
|
||||||
node_version: ">=8"
|
node_version: ">=8"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,23 @@ console_log: {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console_log_console: {
|
||||||
|
input: {
|
||||||
|
var log = console.log;
|
||||||
|
log(console);
|
||||||
|
log(typeof console.log);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var log = console.log;
|
||||||
|
log(console);
|
||||||
|
log(typeof console.log);
|
||||||
|
}
|
||||||
|
expect_stdout: [
|
||||||
|
"{ log: 'function(){}' }",
|
||||||
|
"function",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
typeof_arguments: {
|
typeof_arguments: {
|
||||||
options = {
|
options = {
|
||||||
evaluate: true,
|
evaluate: true,
|
||||||
@@ -81,6 +98,38 @@ log_global: {
|
|||||||
expect_stdout: "[object global]"
|
expect_stdout: "[object global]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timers: {
|
||||||
|
options = {
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var count = 0, interval = 1000, duration = 3210;
|
||||||
|
var timer = setInterval(function() {
|
||||||
|
console.log(++count);
|
||||||
|
}, interval);
|
||||||
|
setTimeout(function() {
|
||||||
|
clearInterval(timer);
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var count = 0;
|
||||||
|
var timer = setInterval(function() {
|
||||||
|
console.log(++count);
|
||||||
|
}, 1000);
|
||||||
|
setTimeout(function() {
|
||||||
|
clearInterval(timer);
|
||||||
|
}, 3210);
|
||||||
|
}
|
||||||
|
expect_stdout: [
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
]
|
||||||
|
node_version: ">=0.12"
|
||||||
|
}
|
||||||
|
|
||||||
issue_4054: {
|
issue_4054: {
|
||||||
input: {
|
input: {
|
||||||
console.log({
|
console.log({
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ describe("bin/uglifyjs", function() {
|
|||||||
"--source-map", [
|
"--source-map", [
|
||||||
"names=true",
|
"names=true",
|
||||||
"url=inline",
|
"url=inline",
|
||||||
].join(","),
|
].join(),
|
||||||
].join(" "), function(err, stdout) {
|
].join(" "), function(err, stdout) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
var expected = [
|
var expected = [
|
||||||
@@ -84,7 +84,7 @@ describe("bin/uglifyjs", function() {
|
|||||||
"--source-map", [
|
"--source-map", [
|
||||||
"names=false",
|
"names=false",
|
||||||
"url=inline",
|
"url=inline",
|
||||||
].join(","),
|
].join(),
|
||||||
].join(" "), function(err, stdout) {
|
].join(" "), function(err, stdout) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
var expected = [
|
var expected = [
|
||||||
@@ -171,7 +171,7 @@ describe("bin/uglifyjs", function() {
|
|||||||
"content=" + mapFile,
|
"content=" + mapFile,
|
||||||
"includeSources",
|
"includeSources",
|
||||||
"url=inline",
|
"url=inline",
|
||||||
].join(","),
|
].join(),
|
||||||
].join(" ");
|
].join(" ");
|
||||||
|
|
||||||
var child = exec(command, function(err, stdout) {
|
var child = exec(command, function(err, stdout) {
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
|||||||
} else if (differs.error) {
|
} else if (differs.error) {
|
||||||
differs.warnings = warnings;
|
differs.warnings = warnings;
|
||||||
return differs;
|
return differs;
|
||||||
} else if (is_error(differs.unminified_result)
|
} else if (sandbox.is_error(differs.unminified_result)
|
||||||
&& is_error(differs.minified_result)
|
&& sandbox.is_error(differs.minified_result)
|
||||||
&& differs.unminified_result.name == differs.minified_result.name) {
|
&& differs.unminified_result.name == differs.minified_result.name) {
|
||||||
return {
|
return {
|
||||||
code: [
|
code: [
|
||||||
@@ -558,8 +558,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
|||||||
log(code);
|
log(code);
|
||||||
log(diff.error.stack);
|
log(diff.error.stack);
|
||||||
log("*** Discarding permutation and continuing.");
|
log("*** Discarding permutation and continuing.");
|
||||||
} else if (is_error(diff.unminified_result)
|
} else if (sandbox.is_error(diff.unminified_result)
|
||||||
&& is_error(diff.minified_result)
|
&& sandbox.is_error(diff.minified_result)
|
||||||
&& diff.unminified_result.name == diff.minified_result.name) {
|
&& diff.unminified_result.name == diff.minified_result.name) {
|
||||||
// ignore difference in error messages caused by minification
|
// ignore difference in error messages caused by minification
|
||||||
diff_error_message = testcase;
|
diff_error_message = testcase;
|
||||||
@@ -600,10 +600,10 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
|||||||
}
|
}
|
||||||
var lines = [ "" ];
|
var lines = [ "" ];
|
||||||
if (isNaN(max_timeout)) {
|
if (isNaN(max_timeout)) {
|
||||||
lines.push("// minify error: " + to_comment(strip_color_codes(differs.minified_result.stack)));
|
lines.push("// minify error: " + to_comment(differs.minified_result.stack));
|
||||||
} else {
|
} else {
|
||||||
var unminified_result = strip_color_codes(differs.unminified_result);
|
var unminified_result = differs.unminified_result;
|
||||||
var minified_result = strip_color_codes(differs.minified_result);
|
var minified_result = differs.minified_result;
|
||||||
if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) {
|
if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) {
|
||||||
lines.push(
|
lines.push(
|
||||||
"// (stringified)",
|
"// (stringified)",
|
||||||
@@ -624,10 +624,6 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function strip_color_codes(value) {
|
|
||||||
return ("" + value).replace(/\u001b\[\d+m/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function to_comment(value) {
|
function to_comment(value) {
|
||||||
return ("" + value).replace(/\n/g, "\n// ");
|
return ("" + value).replace(/\n/g, "\n// ");
|
||||||
}
|
}
|
||||||
@@ -665,12 +661,8 @@ function has_loopcontrol(body, loop, label) {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_error(result) {
|
|
||||||
return result && typeof result.name == "string" && typeof result.message == "string";
|
|
||||||
}
|
|
||||||
|
|
||||||
function is_timed_out(result) {
|
function is_timed_out(result) {
|
||||||
return is_error(result) && /timed out/.test(result.message);
|
return sandbox.is_error(result) && /timed out/.test(result.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_statement(node) {
|
function is_statement(node) {
|
||||||
|
|||||||
319
test/sandbox.js
319
test/sandbox.js
@@ -1,103 +1,33 @@
|
|||||||
|
var execSync = require("child_process").execSync;
|
||||||
var semver = require("semver");
|
var semver = require("semver");
|
||||||
var vm = require("vm");
|
var vm = require("vm");
|
||||||
|
|
||||||
var setupContext = new vm.Script([
|
setup_log();
|
||||||
"[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {",
|
var setup_code = "(" + setup + ")(this, " + setup_log + ", " + find_builtins() + ");";
|
||||||
" f.toString = Function.prototype.toString;",
|
exports.has_toplevel = function(options) {
|
||||||
"});",
|
return options.toplevel
|
||||||
"Function.prototype.toString = function() {",
|
|| options.mangle && options.mangle.toplevel
|
||||||
" var id = 100000;",
|
|| options.compress && options.compress.toplevel;
|
||||||
" return function() {",
|
|
||||||
" var n = this.name;",
|
|
||||||
" if (!/^F[0-9]{6}N$/.test(n)) {",
|
|
||||||
' n = "F" + ++id + "N";',
|
|
||||||
].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [
|
|
||||||
' Object.defineProperty(this, "name", {',
|
|
||||||
" get: function() {",
|
|
||||||
" return n;",
|
|
||||||
" }",
|
|
||||||
" });",
|
|
||||||
] : [], [
|
|
||||||
" }",
|
|
||||||
' return "function(){}";',
|
|
||||||
" };",
|
|
||||||
"}();",
|
|
||||||
"this;",
|
|
||||||
]).join("\n"));
|
|
||||||
|
|
||||||
function createContext() {
|
|
||||||
var ctx = vm.createContext(Object.defineProperties({}, {
|
|
||||||
console: { value: { log: log } },
|
|
||||||
global: { get: self },
|
|
||||||
self: { get: self },
|
|
||||||
window: { get: self },
|
|
||||||
}));
|
|
||||||
var global = setupContext.runInContext(ctx);
|
|
||||||
return ctx;
|
|
||||||
|
|
||||||
function self() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function safe_log(arg, level) {
|
|
||||||
if (arg) switch (typeof arg) {
|
|
||||||
case "function":
|
|
||||||
return arg.toString();
|
|
||||||
case "object":
|
|
||||||
if (arg === global) return "[object global]";
|
|
||||||
if (/Error$/.test(arg.name)) return arg.toString();
|
|
||||||
if (typeof arg.then == "function") return "[object Promise]";
|
|
||||||
arg.constructor.toString();
|
|
||||||
if (level--) for (var key in arg) {
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(arg, key);
|
|
||||||
if (!desc || !desc.get && !desc.set) arg[key] = safe_log(arg[key], level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function log(msg) {
|
|
||||||
if (arguments.length == 1 && typeof msg == "string") return console.log("%s", msg);
|
|
||||||
return console.log.apply(console, [].map.call(arguments, function(arg) {
|
|
||||||
return safe_log(arg, 3);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_code(code, toplevel, timeout) {
|
|
||||||
timeout = timeout || 5000;
|
|
||||||
var stdout = "";
|
|
||||||
var original_write = process.stdout.write;
|
|
||||||
process.stdout.write = function(chunk) {
|
|
||||||
stdout += chunk;
|
|
||||||
};
|
};
|
||||||
try {
|
exports.is_error = is_error;
|
||||||
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: timeout });
|
|
||||||
return stdout;
|
|
||||||
} catch (ex) {
|
|
||||||
return ex;
|
|
||||||
} finally {
|
|
||||||
process.stdout.write = original_write;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.run_code = semver.satisfies(process.version, "0.8") ? function(code, toplevel, timeout) {
|
exports.run_code = semver.satisfies(process.version, "0.8") ? function(code, toplevel, timeout) {
|
||||||
var stdout = run_code(code, toplevel, timeout);
|
var stdout = run_code_vm(code, toplevel, timeout);
|
||||||
if (typeof stdout != "string" || !/arguments/.test(code)) return stdout;
|
if (typeof stdout != "string" || !/arguments/.test(code)) return stdout;
|
||||||
do {
|
do {
|
||||||
var prev = stdout;
|
var prev = stdout;
|
||||||
stdout = run_code(code, toplevel, timeout);
|
stdout = run_code_vm(code, toplevel, timeout);
|
||||||
} while (prev !== stdout);
|
} while (prev !== stdout);
|
||||||
return stdout;
|
return stdout;
|
||||||
} : run_code;
|
} : semver.satisfies(process.version, "<0.12") ? run_code_vm : function(code, toplevel, timeout) {
|
||||||
|
if (/\basync([ \t]+[^\s()[\]{},.&|!~=*%/+-]+|[ \t]*\([\s\S]*?\))[ \t]*=>|\b(async[ \t]+function|setInterval|setTimeout)\b/.test(code)) {
|
||||||
function strip_func_ids(text) {
|
return run_code_exec(code, toplevel, timeout);
|
||||||
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
|
} else {
|
||||||
|
return run_code_vm(code, toplevel, timeout);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
|
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
|
||||||
if (typeof expected != typeof actual) return false;
|
if (typeof expected != typeof actual) return false;
|
||||||
if (typeof expected == "object" && typeof expected.name == "string" && typeof expected.message == "string") {
|
if (is_error(expected)) {
|
||||||
if (expected.name !== actual.name) return false;
|
if (expected.name !== actual.name) return false;
|
||||||
if (typeof actual.message != "string") return false;
|
if (typeof actual.message != "string") return false;
|
||||||
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
|
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
|
||||||
@@ -107,8 +37,215 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec
|
|||||||
} : function(expected, actual) {
|
} : function(expected, actual) {
|
||||||
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
|
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
|
||||||
};
|
};
|
||||||
exports.has_toplevel = function(options) {
|
|
||||||
return options.toplevel
|
function is_error(result) {
|
||||||
|| options.mangle && options.mangle.toplevel
|
return result && typeof result.name == "string" && typeof result.message == "string";
|
||||||
|| options.compress && options.compress.toplevel;
|
}
|
||||||
|
|
||||||
|
function strip_color_codes(value) {
|
||||||
|
return value.replace(/\u001b\[\d+m/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function strip_func_ids(text) {
|
||||||
|
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_log() {
|
||||||
|
var inspect = require("util").inspect;
|
||||||
|
if (inspect.defaultOptions) {
|
||||||
|
var log_options = {
|
||||||
|
breakLength: Infinity,
|
||||||
|
colors: false,
|
||||||
|
compact: true,
|
||||||
|
customInspect: false,
|
||||||
|
depth: Infinity,
|
||||||
|
maxArrayLength: Infinity,
|
||||||
|
maxStringLength: Infinity,
|
||||||
|
showHidden: false,
|
||||||
};
|
};
|
||||||
|
for (var name in log_options) {
|
||||||
|
if (name in inspect.defaultOptions) inspect.defaultOptions[name] = log_options[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inspect;
|
||||||
|
}
|
||||||
|
|
||||||
|
function find_builtins() {
|
||||||
|
setup_code = "console.log(Object.keys(this));";
|
||||||
|
var builtins = run_code_vm("");
|
||||||
|
if (semver.satisfies(process.version, ">=0.12")) builtins += ".concat(" + run_code_exec("") + ")";
|
||||||
|
return builtins;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup(global, setup_log, builtins) {
|
||||||
|
[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {
|
||||||
|
f.toString = Function.prototype.toString;
|
||||||
|
});
|
||||||
|
Function.prototype.toString = function() {
|
||||||
|
var configurable = Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable;
|
||||||
|
var id = 100000;
|
||||||
|
return function() {
|
||||||
|
var n = this.name;
|
||||||
|
if (!/^F[0-9]{6}N$/.test(n)) {
|
||||||
|
n = "F" + ++id + "N";
|
||||||
|
if (configurable) Object.defineProperty(this, "name", {
|
||||||
|
get: function() {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "function(){}";
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
var process = global.process;
|
||||||
|
if (process) {
|
||||||
|
var inspect = setup_log();
|
||||||
|
process.on("uncaughtException", function(ex) {
|
||||||
|
var value = ex;
|
||||||
|
if (value instanceof Error) {
|
||||||
|
value = {};
|
||||||
|
for (var name in ex) value[name] = ex[name];
|
||||||
|
}
|
||||||
|
process.stderr.write(inspect(value) + "\n\n-----===== UNCAUGHT EXCEPTION =====-----\n\n");
|
||||||
|
throw ex;
|
||||||
|
}).on("unhandledRejection", function() {});
|
||||||
|
}
|
||||||
|
var log = console.log;
|
||||||
|
var safe_console = {
|
||||||
|
log: function(msg) {
|
||||||
|
if (arguments.length == 1 && typeof msg == "string") return log("%s", msg);
|
||||||
|
return log.apply(null, [].map.call(arguments, function(arg) {
|
||||||
|
return safe_log(arg, {
|
||||||
|
level: 3,
|
||||||
|
original: [],
|
||||||
|
replaced: [],
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var props = {
|
||||||
|
// for Node.js v8
|
||||||
|
console: {
|
||||||
|
get: function() {
|
||||||
|
return safe_console;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
global: { get: self },
|
||||||
|
self: { get: self },
|
||||||
|
window: { get: self },
|
||||||
|
};
|
||||||
|
[
|
||||||
|
// for Node.js v0.12
|
||||||
|
"Buffer",
|
||||||
|
"clearInterval",
|
||||||
|
"clearTimeout",
|
||||||
|
// for Node.js v0.12
|
||||||
|
"DTRACE_NET_STREAM_END",
|
||||||
|
// for Node.js v8
|
||||||
|
"process",
|
||||||
|
"setInterval",
|
||||||
|
"setTimeout",
|
||||||
|
].forEach(function(name) {
|
||||||
|
var value = global[name];
|
||||||
|
props[name] = {
|
||||||
|
get: function() {
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
builtins.forEach(function(name) {
|
||||||
|
try {
|
||||||
|
delete global[name];
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
Object.defineProperties(global, props);
|
||||||
|
// for Node.js v8+
|
||||||
|
global.toString = function() {
|
||||||
|
return "[object global]";
|
||||||
|
};
|
||||||
|
|
||||||
|
function self() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safe_log(arg, cache) {
|
||||||
|
if (arg) switch (typeof arg) {
|
||||||
|
case "function":
|
||||||
|
return arg.toString();
|
||||||
|
case "object":
|
||||||
|
if (arg === global) return "[object global]";
|
||||||
|
if (/Error$/.test(arg.name)) return arg.toString();
|
||||||
|
if (typeof arg.then == "function") return "[object Promise]";
|
||||||
|
arg.constructor.toString();
|
||||||
|
var index = cache.original.indexOf(arg);
|
||||||
|
if (index >= 0) return cache.replaced[index];
|
||||||
|
if (--cache.level < 0) break;
|
||||||
|
var value = {};
|
||||||
|
cache.original.push(arg);
|
||||||
|
cache.replaced.push(value);
|
||||||
|
for (var key in arg) {
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(arg, key);
|
||||||
|
if (desc && (desc.get || desc.set)) {
|
||||||
|
Object.defineProperty(value, key, desc);
|
||||||
|
} else {
|
||||||
|
value[key] = safe_log(arg[key], cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_code_vm(code, toplevel, timeout) {
|
||||||
|
timeout = timeout || 5000;
|
||||||
|
var stdout = "";
|
||||||
|
var original_write = process.stdout.write;
|
||||||
|
process.stdout.write = function(chunk) {
|
||||||
|
stdout += chunk;
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
var ctx = vm.createContext({ console: console });
|
||||||
|
// for Node.js v6
|
||||||
|
vm.runInContext(setup_code, ctx);
|
||||||
|
vm.runInContext(toplevel ? "(function(){" + code + "})();" : code, ctx, { timeout: timeout });
|
||||||
|
return strip_color_codes(stdout);
|
||||||
|
} catch (ex) {
|
||||||
|
return ex;
|
||||||
|
} finally {
|
||||||
|
process.stdout.write = original_write;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_code_exec(code, toplevel, timeout) {
|
||||||
|
if (toplevel) {
|
||||||
|
code = setup_code + "(function(){" + code + "})();";
|
||||||
|
} else {
|
||||||
|
code = code.replace(/^((["'])[^"']*\2(;|$))?/, function(directive) {
|
||||||
|
return directive + setup_code;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return execSync('"' + process.argv[0] + '" --max-old-space-size=4096', {
|
||||||
|
encoding: "utf8",
|
||||||
|
input: code,
|
||||||
|
stdio: "pipe",
|
||||||
|
timeout: timeout || 5000,
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
var msg = ex.message.replace(/\r\n/g, "\n");
|
||||||
|
if (/ETIMEDOUT/.test(msg)) return new Error("Script execution timed out.");
|
||||||
|
var value = msg.slice(msg.indexOf("\n") + 1, msg.indexOf("\n\n-----===== UNCAUGHT EXCEPTION =====-----\n\n"));
|
||||||
|
try {
|
||||||
|
value = vm.runInNewContext("(" + value.replace(/<([1-9][0-9]*) empty items?>/g, function(match, count) {
|
||||||
|
return new Array(+count).join();
|
||||||
|
}) + ")");
|
||||||
|
} catch (e) {}
|
||||||
|
var match = /\n([^:\s]*Error)(?:: ([\s\S]+?))?\n( at [\s\S]+)\n$/.exec(msg);
|
||||||
|
if (!match) return value;
|
||||||
|
ex = new global[match[1]](match[2]);
|
||||||
|
ex.stack = ex.stack.slice(0, ex.stack.indexOf(" at ")) + match[3];
|
||||||
|
for (var name in value) ex[name] = value[name];
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -317,7 +317,6 @@ var VAR_NAMES = [
|
|||||||
"NaN",
|
"NaN",
|
||||||
"Infinity",
|
"Infinity",
|
||||||
"arguments",
|
"arguments",
|
||||||
"Math",
|
|
||||||
"parseInt",
|
"parseInt",
|
||||||
"async",
|
"async",
|
||||||
"await",
|
"await",
|
||||||
@@ -1020,9 +1019,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
|
|||||||
case 0:
|
case 0:
|
||||||
return [
|
return [
|
||||||
"[ ",
|
"[ ",
|
||||||
new Array(rng(3)).join(","),
|
new Array(rng(3)).join(),
|
||||||
getVarName(NO_CONST),
|
getVarName(NO_CONST),
|
||||||
new Array(rng(3)).join(","),
|
new Array(rng(3)).join(),
|
||||||
" ] = ",
|
" ] = ",
|
||||||
createArrayLiteral(recurmax, stmtDepth, canThrow),
|
createArrayLiteral(recurmax, stmtDepth, canThrow),
|
||||||
].join("");
|
].join("");
|
||||||
@@ -1743,24 +1742,49 @@ function log(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sort_globals(code) {
|
function sort_globals(code) {
|
||||||
var globals = sandbox.run_code("throw Object.keys(this).sort();" + code);
|
var globals = sandbox.run_code("throw Object.keys(this).sort(" + function(global) {
|
||||||
|
return function(m, n) {
|
||||||
|
return (n == "toString") - (m == "toString")
|
||||||
|
|| (typeof global[n] == "function") - (typeof global[m] == "function")
|
||||||
|
|| (m < n ? -1 : m > n ? 1 : 0);
|
||||||
|
};
|
||||||
|
} + "(this));" + code);
|
||||||
|
if (!Array.isArray(globals)) {
|
||||||
|
errorln();
|
||||||
|
errorln();
|
||||||
|
errorln("//-------------------------------------------------------------");
|
||||||
|
errorln("// !!! sort_globals() failed !!!");
|
||||||
|
errorln("// expected Array, got:");
|
||||||
|
try {
|
||||||
|
errorln("// " + JSON.stringify(globals));
|
||||||
|
} catch (e) {
|
||||||
|
errorln("// " + globals);
|
||||||
|
}
|
||||||
|
errorln("//");
|
||||||
|
errorln(code);
|
||||||
|
errorln();
|
||||||
|
return code;
|
||||||
|
}
|
||||||
return globals.length ? "var " + globals.map(function(name) {
|
return globals.length ? "var " + globals.map(function(name) {
|
||||||
return name + "=" + name;
|
return name + "=" + name;
|
||||||
}).join(",") + ";" + code : code;
|
}).join() + ";" + code : code;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fuzzy_match(original, uglified) {
|
function fuzzy_match(original, uglified) {
|
||||||
uglified = uglified.split(" ");
|
var m = [], n = [];
|
||||||
var i = uglified.length;
|
if (collect(original, m) !== collect(uglified, n)) return false;
|
||||||
original = original.split(" ", i);
|
for (var i = 0; i < m.length; i++) {
|
||||||
while (--i >= 0) {
|
var a = m[i];
|
||||||
if (original[i] === uglified[i]) continue;
|
var b = n[i];
|
||||||
var a = +original[i];
|
if (Math.abs((b - a) / a) > 1e-10) return false;
|
||||||
var b = +uglified[i];
|
|
||||||
if (Math.abs((b - a) / a) < 1e-10) continue;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
function collect(input, nums) {
|
||||||
|
return input.replace(/-?([1-9][0-9]*(\.[0-9]+)?|0\.[0-9]+)(e-?[1-9][0-9]*)?/ig, function(num) {
|
||||||
|
return "<|" + nums.push(+num) + "|>";
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_error_in(ex) {
|
function is_error_in(ex) {
|
||||||
@@ -1768,7 +1792,7 @@ function is_error_in(ex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function is_error_spread(ex) {
|
function is_error_spread(ex) {
|
||||||
return ex.name == "TypeError" && /Found non-callable @@iterator|is not iterable|undefined is not a function/.test(ex.message);
|
return ex.name == "TypeError" && /Found non-callable @@iterator| is not iterable| is not a function/.test(ex.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_error_recursion(ex) {
|
function is_error_recursion(ex) {
|
||||||
@@ -1782,6 +1806,7 @@ function patch_try_catch(orig, toplevel) {
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
tries: [],
|
tries: [],
|
||||||
} ];
|
} ];
|
||||||
|
var tail_throw = '\nif (typeof UFUZZ_ERROR == "object") throw UFUZZ_ERROR;\n';
|
||||||
var re = /(?:(?:^|[\s{}):;])try|}\s*catch\s*\(([^)[{]+)\)|}\s*finally)\s*(?={)/g;
|
var re = /(?:(?:^|[\s{}):;])try|}\s*catch\s*\(([^)[{]+)\)|}\s*finally)\s*(?={)/g;
|
||||||
while (stack.length) {
|
while (stack.length) {
|
||||||
var code = stack[0].code;
|
var code = stack[0].code;
|
||||||
@@ -1798,7 +1823,7 @@ function patch_try_catch(orig, toplevel) {
|
|||||||
var insert;
|
var insert;
|
||||||
if (/}\s*finally\s*$/.test(match[0])) {
|
if (/}\s*finally\s*$/.test(match[0])) {
|
||||||
tries.shift();
|
tries.shift();
|
||||||
insert = 'if (typeof UFUZZ_ERROR == "object") throw UFUZZ_ERROR;';
|
insert = tail_throw;
|
||||||
} else {
|
} else {
|
||||||
while (tries.length && tries[0].catch) tries.shift();
|
while (tries.length && tries[0].catch) tries.shift();
|
||||||
tries[0].catch = index - offset;
|
tries[0].catch = index - offset;
|
||||||
@@ -1812,7 +1837,7 @@ function patch_try_catch(orig, toplevel) {
|
|||||||
"throw " + match[1] + ";",
|
"throw " + match[1] + ";",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
var new_code = code.slice(0, index) + insert + code.slice(index);
|
var new_code = code.slice(0, index) + insert + code.slice(index) + tail_throw;
|
||||||
var result = sandbox.run_code(new_code, toplevel);
|
var result = sandbox.run_code(new_code, toplevel);
|
||||||
if (typeof result != "object" || typeof result.name != "string" || typeof result.message != "string") {
|
if (typeof result != "object" || typeof result.name != "string" || typeof result.message != "string") {
|
||||||
if (!stack.filled && match[1]) stack.push({
|
if (!stack.filled && match[1]) stack.push({
|
||||||
@@ -1904,8 +1929,12 @@ for (var round = 1; round <= num_iterations; round++) {
|
|||||||
ok = sandbox.same_stdout(sandbox.run_code(sort_globals(original_code)), sandbox.run_code(sort_globals(uglify_code)));
|
ok = sandbox.same_stdout(sandbox.run_code(sort_globals(original_code)), sandbox.run_code(sort_globals(uglify_code)));
|
||||||
}
|
}
|
||||||
// ignore numerical imprecision caused by `unsafe_math`
|
// ignore numerical imprecision caused by `unsafe_math`
|
||||||
if (!ok && o.compress && o.compress.unsafe_math && typeof original_result == "string" && typeof uglify_result == "string") {
|
if (!ok && o.compress && o.compress.unsafe_math && typeof original_result == typeof uglify_result) {
|
||||||
|
if (typeof original_result == "string") {
|
||||||
ok = fuzzy_match(original_result, uglify_result);
|
ok = fuzzy_match(original_result, uglify_result);
|
||||||
|
} else if (sandbox.is_error(original_result)) {
|
||||||
|
ok = original_result.name == uglify_result.name && fuzzy_match(original_result.message, uglify_result.message);
|
||||||
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
|
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
|
||||||
ok = sandbox.same_stdout(fuzzy_result, uglify_result);
|
ok = sandbox.same_stdout(fuzzy_result, uglify_result);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ var actions = require("./actions");
|
|||||||
var child_process = require("child_process");
|
var child_process = require("child_process");
|
||||||
|
|
||||||
var args = [
|
var args = [
|
||||||
"--max-old-space-size=2048",
|
"--max-old-space-size=4096",
|
||||||
"test/ufuzz",
|
"test/ufuzz",
|
||||||
];
|
];
|
||||||
var iterations;
|
var iterations;
|
||||||
|
|||||||
Reference in New Issue
Block a user