Compare commits

...

14 Commits

Author SHA1 Message Date
Alex Lam S.L
d105ab9722 v3.8.1 2020-03-28 01:04:40 +08:00
Alex Lam S.L
b39228892d fix line accounting in multi-line strings (#3752)
fixes #3748
2020-03-21 07:17:41 +08:00
Alex Lam S.L
ff72eaa3c3 improve --reduce-test (#3742)
- ignore difference in error messages
- improve readability on trailing whitespace differences
- improve performance & quality via `console.log()` insertions
2020-03-21 05:50:41 +08:00
Alex Lam S.L
0a1c9b34ce fix corner case in evaluate & ie8 (#3751)
fixes #3750
2020-03-21 00:55:24 +08:00
Alex Lam S.L
03e968be62 improve suspicious option detection (#3749) 2020-03-13 04:03:47 +08:00
Alex Lam S.L
421bb7083a fix corner case in unused (#3747)
fixes #3746
2020-03-06 18:27:42 +00:00
Alex Lam S.L
bdc8ef2218 fix corner case in collapse_vars (#3745)
fixes #3744
2020-03-06 18:27:06 +00:00
Alex Lam S.L
bca52fcba2 speed up CI (#3741) 2020-03-02 22:07:30 +08:00
Alex Lam S.L
d6d31cbb5a improve AST fuzzing (#3740) 2020-03-02 19:38:30 +08:00
Alex Lam S.L
a051846d22 fix corner case in evaluate (#3739)
fixes #3738
2020-03-01 20:34:31 +00:00
Alex Lam S.L
3485472866 avoid reducing setter argument (#3737) 2020-03-01 05:04:21 +00:00
Alex Lam S.L
c8d60d6983 detect toplevel option properly (#3735)
fixes #3730
2020-02-29 17:33:48 +00:00
Alex Lam S.L
6092bf23de fix corner case in evaluate (#3729) 2020-02-19 00:41:10 +00:00
Alex Lam S.L
7052ce5aef fix corner case in evaluate (#3728)
- augment `ufuzz` for further `RegExp` testing
2020-02-18 19:35:37 +00:00
26 changed files with 685 additions and 130 deletions

View File

@@ -4,10 +4,10 @@ jobs:
test:
strategy:
matrix:
node: [ "0.10", "0.12", 4, 6, 8, 10, latest ]
os: [ ubuntu-latest, windows-latest ]
node: [ "0.10", 0.12, 4, 6, 8, 10, latest ]
script: [ compress, mocha, release/benchmark, release/jetstream ]
name: ${{ matrix.os }} ${{ matrix.node }} ${{ matrix.script }}
name: ${{ matrix.node }} ${{ matrix.os }} ${{ matrix.script }}
runs-on: ${{ matrix.os }}
env:
NODE: ${{ matrix.node }}

View File

@@ -315,7 +315,7 @@ merge(Compressor.prototype, {
if (value instanceof AST_Array) return native_fns.Array[name];
if (value instanceof AST_Function) return native_fns.Function[name];
if (value instanceof AST_Object) return native_fns.Object[name];
if (value instanceof AST_RegExp) return native_fns.RegExp[name];
if (value instanceof AST_RegExp) return native_fns.RegExp[name] && !value.value.global;
}
function is_modified(compressor, tw, node, value, level, immutable) {
@@ -1384,7 +1384,10 @@ merge(Compressor.prototype, {
function is_last_node(node, parent) {
if (node instanceof AST_Call) {
var fn = node.expression;
if (fn instanceof AST_SymbolRef) fn = fn.fixed_value();
if (fn instanceof AST_SymbolRef) {
if (fn.definition().recursive_refs > 0) return true;
fn = fn.fixed_value();
}
if (!(fn instanceof AST_Lambda)) return true;
if (fn.collapse_scanning) return false;
fn.collapse_scanning = true;
@@ -2528,9 +2531,8 @@ merge(Compressor.prototype, {
return left.is_negative_zero() || right.is_negative_zero();
case "*":
case "/":
return true;
case "%":
return left.is_negative_zero();
return true;
default:
return false;
}
@@ -3003,6 +3005,7 @@ merge(Compressor.prototype, {
].concat(object_fns),
Object: object_fns,
RegExp: [
"exec",
"test",
].concat(object_fns),
String: [
@@ -3085,6 +3088,7 @@ merge(Compressor.prototype, {
cached.forEach(function(node) {
delete node._eval;
});
if (ignore_side_effects) return val;
if (!val || val instanceof RegExp) return val;
if (typeof val == "function" || typeof val == "object") return this;
return val;
@@ -3369,15 +3373,18 @@ merge(Compressor.prototype, {
var args = eval_args(this.args);
if (!args) return this;
if (!stat.value) return undefined;
fn.argnames.forEach(function(sym, i) {
if (!all(fn.argnames, function(sym, i) {
var value = args[i];
sym.definition().references.forEach(function(node) {
var def = sym.definition();
if (def.orig[def.orig.length - 1] !== sym) return false;
def.references.forEach(function(node) {
node._eval = function() {
return value;
};
cached.push(node);
});
});
return true;
})) return this;
fn.evaluating = true;
var val = stat.value._eval(compressor, ignore_side_effects, cached, depth);
delete fn.evaluating;
@@ -3400,6 +3407,7 @@ merge(Compressor.prototype, {
if (val == null || val === e) return this;
var native_fn = native_fns[val.constructor.name];
if (!native_fn || !native_fn[key]) return this;
if (val instanceof RegExp && val.global && !(e instanceof AST_RegExp)) return this;
}
var args = eval_args(this.args);
if (!args) return this;
@@ -3413,6 +3421,8 @@ merge(Compressor.prototype, {
line: this.start.line,
col: this.start.col
});
} finally {
if (val instanceof RegExp) val.lastIndex = 0;
}
}
return this;
@@ -3909,14 +3919,18 @@ merge(Compressor.prototype, {
} else if (node instanceof AST_Unary) {
if (node.write_only) sym = node.expression;
}
if (!/strict/.test(compressor.option("pure_getters"))) return sym instanceof AST_SymbolRef && sym;
while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
if (sym instanceof AST_Sub) props.unshift(sym.property);
sym = sym.expression;
if (/strict/.test(compressor.option("pure_getters"))) {
while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
if (sym instanceof AST_Sub) props.unshift(sym.property);
sym = sym.expression;
}
}
return sym instanceof AST_SymbolRef && all(sym.definition().orig, function(sym) {
if (!(sym instanceof AST_SymbolRef)) return;
if (compressor.exposed(sym.definition())) return;
if (!all(sym.definition().orig, function(sym) {
return !(sym instanceof AST_SymbolLambda);
}) && sym;
})) return;
return sym;
};
var in_use = [];
var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
@@ -6863,7 +6877,7 @@ merge(Compressor.prototype, {
if (node.truthy) return true;
if (node.falsy) return false;
if (node.is_truthy()) return true;
return node.evaluate(compressor);
return node.evaluate(compressor, true);
}
function is_indexFn(node) {

View File

@@ -241,16 +241,16 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
if (signal_eof && !ch)
throw EX_EOF;
if (NEWLINE_CHARS[ch]) {
S.newline_before = S.newline_before || !in_string;
++S.line;
S.col = 0;
if (!in_string && ch == "\r" && peek() == "\n") {
// treat a \r\n sequence as a single \n
++S.pos;
S.line++;
if (!in_string) S.newline_before = true;
if (ch == "\r" && peek() == "\n") {
// treat `\r\n` as `\n`
S.pos++;
ch = "\n";
}
} else {
++S.col;
S.col++;
}
return ch;
}

View File

@@ -219,7 +219,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
var redef;
while (redef = new_def.redefined()) new_def = redef;
} else {
new_def = self.globals.get(name) || scope.def_variable(node);
new_def = self.globals.get(name);
}
if (new_def) {
new_def.orig.push(node);
} else {
new_def = scope.def_variable(node);
}
old_def.orig.concat(old_def.references).forEach(function(node) {
node.thedef = new_def;

View File

@@ -185,7 +185,7 @@ function makePredicate(words) {
function all(array, predicate) {
for (var i = array.length; --i >= 0;)
if (!predicate(array[i]))
if (!predicate(array[i], i))
return false;
return true;
}

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "3.8.0",
"version": "3.8.1",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -207,8 +207,9 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
});
return false;
} else {
var expected = stdout[options.toplevel ? 1 : 0];
var actual = run_code(result.code, options.toplevel);
var toplevel = sandbox.has_toplevel(options);
var expected = stdout[toplevel ? 1 : 0];
var actual = run_code(result.code, toplevel);
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
actual = expected;
}
@@ -378,7 +379,10 @@ function test_case(test) {
}
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 toplevel = test.options.toplevel;
var toplevel = sandbox.has_toplevel({
compress: test.options,
mangle: test.mangle
});
var actual = stdout[toplevel ? 1 : 0];
if (test.expect_stdout === true) {
test.expect_stdout = actual;

View File

@@ -7765,3 +7765,44 @@ issue_3700: {
}
expect_stdout: "PASS"
}
issue_3744: {
options = {
collapse_vars: true,
inline: true,
reduce_vars: true,
unused: true,
}
input: {
(function f(a) {
({
get p() {
switch (1) {
case 0:
f((a = 2, 3));
case 1:
console.log(function g(b) {
return b || "PASS";
}());
}
}
}).p;
})();
}
expect: {
(function f(a) {
({
get p() {
switch (1) {
case 0:
f();
case 1:
console.log(b || "PASS");
}
var b;
}
}).p;
})();
}
expect_stdout: "PASS"
}

View File

@@ -1240,11 +1240,11 @@ issue_2535_1: {
expect: {
y();
x() && y();
(x(), 1) && y();
x(), y();
x() && y();
x() && y();
x() && y();
(x(), 0) && y();
x();
}
}

View File

@@ -2413,3 +2413,34 @@ issue_3673: {
}
expect_stdout: "PASS"
}
issue_3746: {
options = {
keep_fargs: "strict",
side_effects: true,
unused: true,
}
input: {
try {
A;
} catch (e) {
var e;
}
(function f(a) {
e = a;
})();
console.log("PASS");
}
expect: {
try {
A;
} catch (e) {
var e;
}
(function(a) {
e = a;
})();
console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -2161,3 +2161,16 @@ collapse_vars_regexp: {
"abbb",
]
}
issue_3738: {
options = {
evaluate: true,
}
input: {
console.log(1 / (0 + ([] - 1) % 1));
}
expect: {
console.log(1 / (0 + ([] - 1) % 1));
}
expect_stdout: "Infinity"
}

View File

@@ -2398,3 +2398,25 @@ issue_3703: {
}
expect_stdout: "PASS"
}
issue_3750: {
options = {
evaluate: true,
ie8: true,
}
input: {
(function(a) {
return function a() {
return a && console.log("PASS");
}();
})();
}
expect: {
(function(a) {
return function a() {
return a && console.log("PASS");
}();
})();
}
expect_stdout: "PASS"
}

View File

@@ -16,7 +16,6 @@ wrongly_optimized: {
function func() {
foo();
}
// TODO: optimize to `func(), bar()`
(func(), 1) && bar();
func(), 1, bar();
}
}

View File

@@ -84,6 +84,7 @@ wrongly_optimized: {
options = {
booleans: true,
conditionals: true,
dead_code: true,
evaluate: true,
expression: true,
}
@@ -99,8 +100,8 @@ wrongly_optimized: {
function func() {
foo();
}
// TODO: optimize to `func(), bar()`
if (func(), 1) bar();
func(), 1;
bar();
}
}

View File

@@ -255,3 +255,221 @@ issue_3434_4: {
"false true",
]
}
exec: {
options = {
evaluate: true,
loops: true,
unsafe: true,
}
input: {
while (/a/.exec("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
for (;null;)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
exec_global: {
options = {
evaluate: true,
loops: true,
unsafe: true,
}
input: {
while (/a/g.exec("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
for (;null;)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
test: {
options = {
evaluate: true,
unsafe: true,
}
input: {
while (/a/.test("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
while (false)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
test_global: {
options = {
evaluate: true,
unsafe: true,
}
input: {
while (/a/g.test("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
while (false)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
var_exec: {
options = {
evaluate: true,
loops: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var r = /a/;
while (r.exec("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
var r = /a/;
for (;null;)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
var_exec_global: {
options = {
evaluate: true,
loops: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var r = /a/g;
while (r.exec("aaa"))
console.log("PASS");
}
expect: {
var r = /a/g;
for (;r.exec("aaa");)
console.log("PASS");
}
expect_stdout: [
"PASS",
"PASS",
"PASS",
]
}
var_test: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var r = /a/;
while (r.test("AAA"))
console.log("FAIL");
console.log("PASS");
}
expect: {
var r = /a/;
while (false)
console.log("FAIL");
console.log("PASS");
}
expect_stdout: "PASS"
}
var_test_global: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var r = /a/g;
while (r.test("aaa"))
console.log("PASS");
}
expect: {
var r = /a/g;
while (r.test("aaa"))
console.log("PASS");
}
expect_stdout: [
"PASS",
"PASS",
"PASS",
]
}
lazy_boolean: {
options = {
evaluate: true,
passes: 2,
side_effects: true,
unsafe: true,
}
input: {
/b/.exec({}) && console.log("PASS");
/b/.test({}) && console.log("PASS");
/b/g.exec({}) && console.log("PASS");
/b/g.test({}) && console.log("PASS");
}
expect: {
console.log("PASS");
console.log("PASS");
console.log("PASS");
console.log("PASS");
}
expect_stdout: [
"PASS",
"PASS",
"PASS",
"PASS",
]
}
reset_state_between_evaluate: {
options = {
evaluate: true,
passes: 2,
unsafe: true,
}
input: {
console.log(function() {
for (var a in /[abc4]/g.exec("a"))
return "PASS";
return "FAIL";
}());
}
expect: {
console.log(function() {
for (var a in /[abc4]/g.exec("a"))
return "PASS";
return "FAIL";
}());
}
expect_stdout: "PASS"
}

View File

@@ -1,8 +1,7 @@
// (beautified)
var o = this;
for (var k in o) {
0;
}
for (var k in o) {}
var a;

View File

@@ -0,0 +1,8 @@
console.log(function f(a) {
({
set p(v) {
f++;
}
});
return f.length;
}());

View File

@@ -0,0 +1,20 @@
// (beautified)
console.log(function f(a) {
({
set p(v) {
f++;
}
});
return f.length;
}());
// output: 1
//
// minify: 0
//
// options: {
// "compress": {
// "keep_fargs": false,
// "unsafe": true
// },
// "mangle": false
// }

View File

@@ -1,3 +1,4 @@
// (beautified)
var b = 0;
var expr2 = (0 - 1 - .1 - .1).toString();

View File

@@ -51,7 +51,7 @@ describe("minify", function() {
"var a=n(3),b=r(12);",
'c("qux",a,b),o(11);',
].join(""));
assert.strictEqual(run_code(compressed), run_code(original));
assert.strictEqual(run_code(compressed, true), run_code(original, true));
});
it("Should work with nameCache", function() {
@@ -84,7 +84,7 @@ describe("minify", function() {
"var a=n(3),b=r(12);",
'c("qux",a,b),o(11);',
].join(""));
assert.strictEqual(run_code(compressed), run_code(original));
assert.strictEqual(run_code(compressed, true), run_code(original, true));
});
it("Should avoid cached names when mangling top-level variables", function() {
@@ -113,7 +113,7 @@ describe("minify", function() {
'"xxyyy";var y={y:2,a:3},a=4;',
'console.log(x.x,y.y,y.a,a);',
].join(""));
assert.strictEqual(run_code(compressed), run_code(original));
assert.strictEqual(run_code(compressed, true), run_code(original, true));
});
it("Should avoid cached names when mangling inner-scoped variables", function() {
@@ -137,7 +137,7 @@ describe("minify", function() {
'var o=function(o,n){console.log("extend");o();n()};function n(){console.log("A")}',
'var e=function(n){function e(){console.log("B")}o(e,n);return e}(n);',
].join(""));
assert.strictEqual(run_code(compressed), run_code(original));
assert.strictEqual(run_code(compressed, true), run_code(original, true));
});
it("Should not parse invalid use of reserved words", function() {

View File

@@ -31,6 +31,19 @@ describe("test/reduce.js", function() {
if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/label.reduced.js"));
});
it("Should retain setter arguments", function() {
var result = reduce_test(read("test/input/reduce/setter.js"), {
compress: {
keep_fargs: false,
unsafe: true,
},
mangle: false,
}, {
verbose: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/setter.reduced.js"));
});
it("Should handle test cases with --toplevel", function() {
var result = reduce_test([
"var Infinity = 42;",
@@ -40,21 +53,56 @@ describe("test/reduce.js", function() {
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure with minify options provided:",
"// {",
"// Can't reproduce test failure",
"// minify options: {",
'// "toplevel": true',
"// }",
].join("\n"));
});
it("Should handle test cases with --compress toplevel", function() {
var result = reduce_test([
"var NaN = 42;",
"console.log(NaN);",
].join("\n"), {
compress: {
toplevel: true,
},
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure",
"// minify options: {",
'// "compress": {',
'// "toplevel": true',
"// }",
"// }",
].join("\n"));
});
it("Should handle test cases with --mangle toplevel", function() {
var result = reduce_test([
"var undefined = 42;",
"console.log(undefined);",
].join("\n"), {
mangle: {
toplevel: true,
},
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure",
"// minify options: {",
'// "mangle": {',
'// "toplevel": true',
"// }",
"// }",
].join("\n"));
});
it("Should handle test result of NaN", function() {
var result = reduce_test("throw 0 / 0;");
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure with minify options provided:",
"// {",
'// "compress": {},',
'// "mangle": false',
"// }",
"// Can't reproduce test failure",
"// minify options: {}",
].join("\n"));
});
it("Should print correct output for irreducible test case", function() {
@@ -70,6 +118,7 @@ describe("test/reduce.js", function() {
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// (beautified)",
"console.log(function f(a) {",
" return f.length;",
"}());",
@@ -118,6 +167,7 @@ describe("test/reduce.js", function() {
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// (beautified)",
code,
"// output: 0.8",
"// 1.6",
@@ -138,11 +188,41 @@ describe("test/reduce.js", function() {
it("Should reduce infinite loops with reasonable performance", function() {
if (semver.satisfies(process.version, "0.10")) return;
this.timeout(120000);
var result = reduce_test("while (/9/.test(1 - .8));", {
compress: {
unsafe_math: true,
},
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code.replace(/ timed out after [0-9]+ms/, " timed out."), [
"// (beautified)",
"while (/9/.test(1 - .8)) {}",
"// output: Error: Script execution timed out.",
"// minify: ",
"// options: {",
'// "compress": {',
'// "unsafe_math": true',
"// },",
'// "mangle": false',
"// }",
].join("\n"));
});
it("Should ignore difference in Error.message", function() {
var result = reduce_test("null[function() {\n}];");
if (result.error) throw result.error;
assert.strictEqual(result.code, (semver.satisfies(process.version, "0.10") ? [
"// Can't reproduce test failure",
"// minify options: {}",
] : [
"// No differences except in error message",
"// minify options: {}",
]).join("\n"));
});
it("Should report trailing whitespace difference in stringified format", function() {
var code = [
"var a = 9007199254740992, b = 1;",
"",
"while (a++ + (1 - b) < a) {",
" 0;",
"for (var a in (1 - .8).toString()) {",
" console.log();",
"}",
].join("\n");
var result = reduce_test(code, {
@@ -152,14 +232,16 @@ describe("test/reduce.js", function() {
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code.replace(/ timed out after [0-9]+ms/, " timed out."), [
assert.strictEqual(result.code, [
"// (beautified)",
code,
"// output: ",
"// minify: Error: Script execution timed out.",
"// (stringified)",
'// output: "\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n"',
'// minify: "\\n\\n\\n"',
"// options: {",
'// "compress": {',
'// "unsafe_math": true',
"// },",
'// },',
'// "mangle": false',
"// }",
].join("\n"));

View File

@@ -89,6 +89,22 @@ describe("sourcemaps", function() {
assert.strictEqual(result.code, code);
assert.strictEqual(result.map, '{"version":3,"sources":["0"],"names":["console","log"],"mappings":"AAAAA,QAAQC,IAAI","sourceRoot":"//foo.bar/"}');
});
it("Should produce same source map with DOS or UNIX line endings", function() {
var code = [
'console.log("\\',
'hello",',
'"world");',
];
var dos = UglifyJS.minify(code.join("\r\n"), {
sourceMap: true,
});
if (dos.error) throw dos.error;
var unix = UglifyJS.minify(code.join("\n"), {
sourceMap: true,
});
if (unix.error) throw unix.error;
assert.strictEqual(dos.map, unix.map);
});
describe("inSourceMap", function() {
it("Should read the given string filename correctly when sourceMapIncludeSources is enabled", function() {

View File

@@ -44,30 +44,37 @@ function test(original, estree, description) {
try_beautify(transformed.code);
}
console.log("!!!!!! Failed... round", round);
process.exit(1);
return false;
}
return true;
}
var num_iterations = ufuzz.num_iterations;
var minify_options = require("./ufuzz/options.json").map(JSON.stringify);
minify_options.unshift(null);
for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r");
var code = ufuzz.createTopLevelCode();
var uglified = UglifyJS.minify(code, {
compress: false,
mangle: false,
output: {
ast: true
minify_options.forEach(function(options) {
var input = options ? UglifyJS.minify(code, JSON.parse(options)).code : code;
var uglified = UglifyJS.minify(input, {
compress: false,
mangle: false,
output: {
ast: true
}
});
var ok = test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()");
try {
ok = test(uglified.code, acorn.parse(input), "acorn.parse()") && ok;
} catch (e) {
console.log("//=============================================================");
console.log("// acorn parser failed... round", round);
console.log(e);
console.log("// original code");
console.log(input);
}
if (!ok) process.exit(1);
});
test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()");
try {
test(uglified.code, acorn.parse(code), "acorn.parse()");
} catch (e) {
console.log("//=============================================================");
console.log("// acorn parser failed... round", round);
console.log(e);
console.log("// original code");
console.log(code);
}
}
console.log();

View File

@@ -20,7 +20,7 @@ var sandbox = require("./sandbox");
module.exports = function reduce_test(testcase, minify_options, reduce_options) {
if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string();
minify_options = minify_options || { compress: {}, mangle: false };
minify_options = minify_options || {};
reduce_options = reduce_options || {};
var max_iterations = reduce_options.max_iterations || 1000;
var max_timeout = reduce_options.max_timeout || 10000;
@@ -36,16 +36,29 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
if (!differs) {
// same stdout result produced when minified
return {
code: "// Can't reproduce test failure with minify options provided:"
+ "\n// " + to_comment(minify_options_json)
code: [
"// Can't reproduce test failure",
"// minify options: " + to_comment(minify_options_json)
].join("\n")
};
} else if (differs.timed_out) {
return {
code: "// Can't reproduce test failure within " + max_timeout + "ms:"
+ "\n// " + to_comment(minify_options_json)
code: [
"// Can't reproduce test failure within " + max_timeout + "ms",
"// minify options: " + to_comment(minify_options_json)
].join("\n")
};
} else if (differs.error) {
return differs;
} else if (is_error(differs.unminified_result)
&& is_error(differs.minified_result)
&& differs.unminified_result.name == differs.minified_result.name) {
return {
code: [
"// No differences except in error message",
"// minify options: " + to_comment(minify_options_json)
].join("\n")
};
} else {
max_timeout = Math.min(100 * differs.elapsed, max_timeout);
// Replace expressions with constants that will be parsed into
@@ -71,12 +84,13 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// quick ignores
if (node instanceof U.AST_Accessor) return;
if (node instanceof U.AST_Directive) return;
if (!in_list && node instanceof U.AST_EmptyStatement) return;
if (node instanceof U.AST_Label) return;
if (node instanceof U.AST_LabelRef) return;
if (!in_list && node instanceof U.AST_SymbolDeclaration) return;
if (node instanceof U.AST_Toplevel) return;
var parent = tt.parent();
if (node instanceof U.AST_SymbolFunarg && parent instanceof U.AST_Accessor) return;
// ensure that the _permute prop is a number.
// can not use `node.start._permute |= 0;` as it will erase fractional part.
@@ -112,10 +126,25 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
else if (node instanceof U.AST_Binary) {
CHANGED = true;
return [
var permute = ((node.start._permute += step) * steps | 0) % 4;
var expr = [
node.left,
node.right,
][ ((node.start._permute += step) * steps | 0) % 2 ];
][ permute & 1 ];
if (permute < 2) return expr;
// wrap with console.log()
return new U.AST_Call({
expression: new U.AST_Dot({
expression: new U.AST_SymbolRef({
name: "console",
start: {},
}),
property: "log",
start: {},
}),
args: [ expr ],
start: {},
});
}
else if (node instanceof U.AST_Catch || node instanceof U.AST_Finally) {
// drop catch or finally block
@@ -357,15 +386,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
// replace this node
var newNode = U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
var newNode = is_statement(node) ? new U.AST_EmptyStatement({
start: {},
}) : U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
expression: true,
});
if (is_statement(node)) {
newNode = new U.AST_SimpleStatement({
body: newNode,
start: {},
});
}
newNode.start._permute = ++node.start._permute;
CHANGED = true;
return newNode;
@@ -445,29 +470,62 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes");
}
}
testcase = U.minify(testcase, {
compress: false,
mangle: false,
output: {
beautify: true,
braces: true,
comments: true,
},
});
testcase.code += [
"",
"// output: " + to_comment(differs.unminified_result),
"// minify: " + to_comment(differs.minified_result),
"// options: " + to_comment(minify_options_json),
].join("\n").replace(/\u001b\[\d+m/g, "");
testcase = try_beautify(result_cache, testcase, minify_options, differs.unminified_result, max_timeout);
var lines = [ "" ];
var unminified_result = strip_color_codes(differs.unminified_result);
var minified_result = strip_color_codes(differs.minified_result);
if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) {
lines.push(
"// (stringified)",
"// output: " + JSON.stringify(unminified_result),
"// minify: " + JSON.stringify(minified_result)
);
} else {
lines.push(
"// output: " + to_comment(unminified_result),
"// minify: " + to_comment(minified_result)
);
}
lines.push("// options: " + to_comment(minify_options_json));
testcase.code += lines.join("\n");
return testcase;
}
};
function strip_color_codes(value) {
return ("" + value).replace(/\u001b\[\d+m/g, "");
}
function to_comment(value) {
return ("" + value).replace(/\n/g, "\n// ");
}
function trim_trailing_whitespace(value) {
return ("" + value).replace(/\s+$/, "");
}
function try_beautify(result_cache, testcase, minify_options, expected, timeout) {
var result = U.minify(testcase, {
compress: false,
mangle: false,
output: {
beautify: true,
braces: true,
comments: true,
},
});
if (result.error) return {
code: testcase,
};
var toplevel = sandbox.has_toplevel(minify_options);
var actual = run_code(result_cache, result.code, toplevel, timeout);
if (!sandbox.same_stdout(expected, actual)) return {
code: testcase,
};
result.code = "// (beautified)\n" + result.code;
return result;
}
function has_exit(fn) {
var found = false;
var tw = new U.TreeWalker(function(node) {
@@ -543,7 +601,7 @@ function producesDifferentResultWhenMinified(result_cache, code, minify_options,
var minified = U.minify(code, minify_options);
if (minified.error) return minified;
var toplevel = minify_options.toplevel;
var toplevel = sandbox.has_toplevel(minify_options);
var elapsed = Date.now();
var unminified_result = run_code(result_cache, code, toplevel, max_timeout);
elapsed = Date.now() - elapsed;

View File

@@ -87,3 +87,8 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec
} : function(expected, actual) {
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
};
exports.has_toplevel = function(options) {
return options.toplevel
|| options.mangle && options.mangle.toplevel
|| options.compress && options.compress.toplevel;
};

View File

@@ -742,6 +742,8 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
case p++:
return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
case p++:
return " /[abc4]/g.exec(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) +
") || " + rng(10) + ").toString()[" +
@@ -1010,7 +1012,7 @@ function log_suspects(minify_options, component) {
var defs = default_options[component];
var suspects = Object.keys(defs).filter(function(name) {
var flip = name == "keep_fargs";
if (flip ? name in options : (name in options ? options : defs)[name]) {
if (flip === !(name in options ? options : defs)[name]) {
var m = JSON.parse(JSON.stringify(minify_options));
var o = JSON.parse(JSON.stringify(options));
o[name] = flip;
@@ -1020,7 +1022,7 @@ function log_suspects(minify_options, component) {
errorln("Error testing options." + component + "." + name);
errorln(result.error);
} else {
var r = sandbox.run_code(result.code, m.toplevel);
var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
return sandbox.same_stdout(original_result, r);
}
}
@@ -1034,37 +1036,45 @@ function log_suspects(minify_options, component) {
}
}
function log_rename(options) {
var m = JSON.parse(JSON.stringify(options));
m.rename = false;
var result = UglifyJS.minify(original_code, m);
if (result.error) {
errorln("Error testing options.rename");
errorln(result.error);
} else {
var r = sandbox.run_code(result.code, m.toplevel);
if (sandbox.same_stdout(original_result, r)) {
errorln("Suspicious options:");
errorln(" rename");
errorln();
function log_suspects_global(options) {
var o = {};
UglifyJS.minify("", o);
var suspects = Object.keys(o).filter(function(component) {
return typeof o[component] != "object";
}).filter(function(component) {
var m = JSON.parse(options);
m[component] = false;
var result = UglifyJS.minify(original_code, m);
if (result.error) {
errorln("Error testing options." + component);
errorln(result.error);
} else {
var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
return sandbox.same_stdout(original_result, r);
}
});
if (suspects.length > 0) {
errorln("Suspicious options:");
suspects.forEach(function(name) {
errorln(" " + name);
});
errorln();
}
}
function log(options) {
var options_copy = JSON.parse(options);
options = JSON.parse(options);
var toplevel = sandbox.has_toplevel(JSON.parse(options));
if (!ok) errorln("\n\n\n\n\n\n!!!!!!!!!!\n\n\n");
errorln("//=============================================================");
if (!ok) errorln("// !!!!!! Failed... round " + round);
errorln("// original code");
try_beautify(original_code, options.toplevel, original_result, errorln);
try_beautify(original_code, toplevel, original_result, errorln);
errorln();
errorln();
errorln("//-------------------------------------------------------------");
if (typeof uglify_code == "string") {
errorln("// uglified code");
try_beautify(uglify_code, options.toplevel, uglify_result, errorln);
try_beautify(uglify_code, toplevel, uglify_result, errorln);
errorln();
errorln();
errorln("original result:");
@@ -1072,7 +1082,7 @@ function log(options) {
errorln("uglified result:");
errorln(uglify_result);
errorln("//-------------------------------------------------------------");
var reduced = reduce_test(original_code, options_copy, {
var reduced = reduce_test(original_code, JSON.parse(options), {
verbose: false,
}).code;
if (reduced) {
@@ -1094,11 +1104,11 @@ function log(options) {
}
}
errorln("minify(options):");
errorln(JSON.stringify(options, null, 2));
errorln(JSON.stringify(JSON.parse(options), null, 2));
errorln();
if (!ok && typeof uglify_code == "string") {
Object.keys(default_options).forEach(log_suspects.bind(null, options));
log_rename(options);
Object.keys(default_options).forEach(log_suspects.bind(null, JSON.parse(options)));
log_suspects_global(options);
errorln("!!!!!! Failed... round " + round);
}
}
@@ -1133,16 +1143,17 @@ for (var round = 1; round <= num_iterations; round++) {
if (!errored) orig_result.push(sandbox.run_code(original_code, true));
(errored ? fallback_options : minify_options).forEach(function(options) {
var o = JSON.parse(options);
var toplevel = sandbox.has_toplevel(o);
uglify_code = UglifyJS.minify(original_code, o);
original_result = orig_result[o.toplevel ? 1 : 0];
original_result = orig_result[toplevel ? 1 : 0];
if (!uglify_code.error) {
uglify_code = uglify_code.code;
uglify_result = sandbox.run_code(uglify_code, o.toplevel);
uglify_result = sandbox.run_code(uglify_code, toplevel);
ok = sandbox.same_stdout(original_result, uglify_result);
if (!ok && typeof uglify_result == "string" && o.compress.unsafe_math) {
ok = fuzzy_match(original_result, uglify_result);
if (!ok) {
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"));
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
ok = sandbox.same_stdout(fuzzy_result, uglify_result);
}
}
@@ -1156,7 +1167,7 @@ for (var round = 1; round <= num_iterations; round++) {
else if (errored) {
println("//=============================================================");
println("// original code");
try_beautify(original_code, o.toplevel, original_result, println);
try_beautify(original_code, toplevel, original_result, println);
println();
println();
println("original result:");