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: test:
strategy: strategy:
matrix: matrix:
node: [ "0.10", "0.12", 4, 6, 8, 10, latest ]
os: [ ubuntu-latest, windows-latest ] os: [ ubuntu-latest, windows-latest ]
node: [ "0.10", 0.12, 4, 6, 8, 10, latest ]
script: [ compress, mocha, release/benchmark, release/jetstream ] 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 }} runs-on: ${{ matrix.os }}
env: env:
NODE: ${{ matrix.node }} 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_Array) return native_fns.Array[name];
if (value instanceof AST_Function) return native_fns.Function[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_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) { function is_modified(compressor, tw, node, value, level, immutable) {
@@ -1384,7 +1384,10 @@ merge(Compressor.prototype, {
function is_last_node(node, parent) { function is_last_node(node, parent) {
if (node instanceof AST_Call) { if (node instanceof AST_Call) {
var fn = node.expression; 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 instanceof AST_Lambda)) return true;
if (fn.collapse_scanning) return false; if (fn.collapse_scanning) return false;
fn.collapse_scanning = true; fn.collapse_scanning = true;
@@ -2528,9 +2531,8 @@ merge(Compressor.prototype, {
return left.is_negative_zero() || right.is_negative_zero(); return left.is_negative_zero() || right.is_negative_zero();
case "*": case "*":
case "/": case "/":
return true;
case "%": case "%":
return left.is_negative_zero(); return true;
default: default:
return false; return false;
} }
@@ -3003,6 +3005,7 @@ merge(Compressor.prototype, {
].concat(object_fns), ].concat(object_fns),
Object: object_fns, Object: object_fns,
RegExp: [ RegExp: [
"exec",
"test", "test",
].concat(object_fns), ].concat(object_fns),
String: [ String: [
@@ -3085,6 +3088,7 @@ merge(Compressor.prototype, {
cached.forEach(function(node) { cached.forEach(function(node) {
delete node._eval; delete node._eval;
}); });
if (ignore_side_effects) return val;
if (!val || val instanceof RegExp) return val; if (!val || val instanceof RegExp) return val;
if (typeof val == "function" || typeof val == "object") return this; if (typeof val == "function" || typeof val == "object") return this;
return val; return val;
@@ -3369,15 +3373,18 @@ merge(Compressor.prototype, {
var args = eval_args(this.args); var args = eval_args(this.args);
if (!args) return this; if (!args) return this;
if (!stat.value) return undefined; if (!stat.value) return undefined;
fn.argnames.forEach(function(sym, i) { if (!all(fn.argnames, function(sym, i) {
var value = args[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() { node._eval = function() {
return value; return value;
}; };
cached.push(node); cached.push(node);
}); });
}); return true;
})) return this;
fn.evaluating = true; fn.evaluating = true;
var val = stat.value._eval(compressor, ignore_side_effects, cached, depth); var val = stat.value._eval(compressor, ignore_side_effects, cached, depth);
delete fn.evaluating; delete fn.evaluating;
@@ -3400,6 +3407,7 @@ merge(Compressor.prototype, {
if (val == null || val === e) return this; if (val == null || val === e) return this;
var native_fn = native_fns[val.constructor.name]; var native_fn = native_fns[val.constructor.name];
if (!native_fn || !native_fn[key]) return this; 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); var args = eval_args(this.args);
if (!args) return this; if (!args) return this;
@@ -3413,6 +3421,8 @@ merge(Compressor.prototype, {
line: this.start.line, line: this.start.line,
col: this.start.col col: this.start.col
}); });
} finally {
if (val instanceof RegExp) val.lastIndex = 0;
} }
} }
return this; return this;
@@ -3909,14 +3919,18 @@ merge(Compressor.prototype, {
} else if (node instanceof AST_Unary) { } else if (node instanceof AST_Unary) {
if (node.write_only) sym = node.expression; if (node.write_only) sym = node.expression;
} }
if (!/strict/.test(compressor.option("pure_getters"))) return sym instanceof AST_SymbolRef && sym; if (/strict/.test(compressor.option("pure_getters"))) {
while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) { while (sym instanceof AST_PropAccess && !sym.expression.may_throw_on_access(compressor)) {
if (sym instanceof AST_Sub) props.unshift(sym.property); if (sym instanceof AST_Sub) props.unshift(sym.property);
sym = sym.expression; 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); return !(sym instanceof AST_SymbolLambda);
}) && sym; })) return;
return sym;
}; };
var in_use = []; var in_use = [];
var in_use_ids = Object.create(null); // avoid expensive linear scans of 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.truthy) return true;
if (node.falsy) return false; if (node.falsy) return false;
if (node.is_truthy()) return true; if (node.is_truthy()) return true;
return node.evaluate(compressor); return node.evaluate(compressor, true);
} }
function is_indexFn(node) { function is_indexFn(node) {

View File

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

View File

@@ -219,7 +219,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
var redef; var redef;
while (redef = new_def.redefined()) new_def = redef; while (redef = new_def.redefined()) new_def = redef;
} else { } 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) { old_def.orig.concat(old_def.references).forEach(function(node) {
node.thedef = new_def; node.thedef = new_def;

View File

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

View File

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

View File

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

View File

@@ -7765,3 +7765,44 @@ issue_3700: {
} }
expect_stdout: "PASS" 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: { expect: {
y(); y();
x() && y(); x() && y();
(x(), 1) && y(); x(), y();
x() && y(); x() && y();
x() && y(); x() && y();
x() && y(); x() && y();
(x(), 0) && y(); x();
} }
} }

View File

@@ -2413,3 +2413,34 @@ issue_3673: {
} }
expect_stdout: "PASS" 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", "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" 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() { function func() {
foo(); foo();
} }
// TODO: optimize to `func(), bar()` func(), 1, bar();
(func(), 1) && bar();
} }
} }

View File

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

View File

@@ -255,3 +255,221 @@ issue_3434_4: {
"false true", "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; var o = this;
for (var k in o) { for (var k in o) {}
0;
}
var a; 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 b = 0;
var expr2 = (0 - 1 - .1 - .1).toString(); var expr2 = (0 - 1 - .1 - .1).toString();

View File

@@ -51,7 +51,7 @@ describe("minify", function() {
"var a=n(3),b=r(12);", "var a=n(3),b=r(12);",
'c("qux",a,b),o(11);', 'c("qux",a,b),o(11);',
].join("")); ].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() { it("Should work with nameCache", function() {
@@ -84,7 +84,7 @@ describe("minify", function() {
"var a=n(3),b=r(12);", "var a=n(3),b=r(12);",
'c("qux",a,b),o(11);', 'c("qux",a,b),o(11);',
].join("")); ].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() { 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;', '"xxyyy";var y={y:2,a:3},a=4;',
'console.log(x.x,y.y,y.a,a);', 'console.log(x.x,y.y,y.a,a);',
].join("")); ].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() { 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 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);', 'var e=function(n){function e(){console.log("B")}o(e,n);return e}(n);',
].join("")); ].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() { 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; if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/label.reduced.js")); 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() { it("Should handle test cases with --toplevel", function() {
var result = reduce_test([ var result = reduce_test([
"var Infinity = 42;", "var Infinity = 42;",
@@ -40,21 +53,56 @@ describe("test/reduce.js", function() {
}); });
if (result.error) throw result.error; if (result.error) throw result.error;
assert.strictEqual(result.code, [ assert.strictEqual(result.code, [
"// Can't reproduce test failure with minify options provided:", "// Can't reproduce test failure",
"// {", "// minify options: {",
'// "toplevel": true', '// "toplevel": true',
"// }", "// }",
].join("\n")); ].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() { it("Should handle test result of NaN", function() {
var result = reduce_test("throw 0 / 0;"); var result = reduce_test("throw 0 / 0;");
if (result.error) throw result.error; if (result.error) throw result.error;
assert.strictEqual(result.code, [ assert.strictEqual(result.code, [
"// Can't reproduce test failure with minify options provided:", "// Can't reproduce test failure",
"// {", "// minify options: {}",
'// "compress": {},',
'// "mangle": false',
"// }",
].join("\n")); ].join("\n"));
}); });
it("Should print correct output for irreducible test case", function() { 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; if (result.error) throw result.error;
assert.strictEqual(result.code, [ assert.strictEqual(result.code, [
"// (beautified)",
"console.log(function f(a) {", "console.log(function f(a) {",
" return f.length;", " return f.length;",
"}());", "}());",
@@ -118,6 +167,7 @@ describe("test/reduce.js", function() {
}); });
if (result.error) throw result.error; if (result.error) throw result.error;
assert.strictEqual(result.code, [ assert.strictEqual(result.code, [
"// (beautified)",
code, code,
"// output: 0.8", "// output: 0.8",
"// 1.6", "// 1.6",
@@ -138,11 +188,41 @@ describe("test/reduce.js", function() {
it("Should reduce infinite loops with reasonable performance", function() { it("Should reduce infinite loops with reasonable performance", function() {
if (semver.satisfies(process.version, "0.10")) return; if (semver.satisfies(process.version, "0.10")) return;
this.timeout(120000); 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 code = [
"var a = 9007199254740992, b = 1;", "for (var a in (1 - .8).toString()) {",
"", " console.log();",
"while (a++ + (1 - b) < a) {",
" 0;",
"}", "}",
].join("\n"); ].join("\n");
var result = reduce_test(code, { var result = reduce_test(code, {
@@ -152,14 +232,16 @@ describe("test/reduce.js", function() {
mangle: false, mangle: false,
}); });
if (result.error) throw result.error; 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, code,
"// output: ", "// (stringified)",
"// minify: Error: Script execution timed out.", '// 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: {", "// options: {",
'// "compress": {', '// "compress": {',
'// "unsafe_math": true', '// "unsafe_math": true',
"// },", '// },',
'// "mangle": false', '// "mangle": false',
"// }", "// }",
].join("\n")); ].join("\n"));

View File

@@ -89,6 +89,22 @@ describe("sourcemaps", function() {
assert.strictEqual(result.code, code); assert.strictEqual(result.code, code);
assert.strictEqual(result.map, '{"version":3,"sources":["0"],"names":["console","log"],"mappings":"AAAAA,QAAQC,IAAI","sourceRoot":"//foo.bar/"}'); 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() { describe("inSourceMap", function() {
it("Should read the given string filename correctly when sourceMapIncludeSources is enabled", 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); try_beautify(transformed.code);
} }
console.log("!!!!!! Failed... round", round); console.log("!!!!!! Failed... round", round);
process.exit(1); return false;
} }
return true;
} }
var num_iterations = ufuzz.num_iterations; 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++) { for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r"); process.stdout.write(round + " of " + num_iterations + "\r");
var code = ufuzz.createTopLevelCode(); var code = ufuzz.createTopLevelCode();
var uglified = UglifyJS.minify(code, { minify_options.forEach(function(options) {
var input = options ? UglifyJS.minify(code, JSON.parse(options)).code : code;
var uglified = UglifyJS.minify(input, {
compress: false, compress: false,
mangle: false, mangle: false,
output: { output: {
ast: true ast: true
} }
}); });
test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()"); var ok = test(uglified.code, uglified.ast.to_mozilla_ast(), "AST_Node.to_mozilla_ast()");
try { try {
test(uglified.code, acorn.parse(code), "acorn.parse()"); ok = test(uglified.code, acorn.parse(input), "acorn.parse()") && ok;
} catch (e) { } catch (e) {
console.log("//============================================================="); console.log("//=============================================================");
console.log("// acorn parser failed... round", round); console.log("// acorn parser failed... round", round);
console.log(e); console.log(e);
console.log("// original code"); console.log("// original code");
console.log(code); console.log(input);
} }
if (!ok) process.exit(1);
});
} }
console.log(); console.log();

View File

@@ -20,7 +20,7 @@ var sandbox = require("./sandbox");
module.exports = function reduce_test(testcase, minify_options, reduce_options) { module.exports = function reduce_test(testcase, minify_options, reduce_options) {
if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string(); 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 || {}; reduce_options = reduce_options || {};
var max_iterations = reduce_options.max_iterations || 1000; var max_iterations = reduce_options.max_iterations || 1000;
var max_timeout = reduce_options.max_timeout || 10000; var max_timeout = reduce_options.max_timeout || 10000;
@@ -36,16 +36,29 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
if (!differs) { if (!differs) {
// same stdout result produced when minified // same stdout result produced when minified
return { return {
code: "// Can't reproduce test failure with minify options provided:" code: [
+ "\n// " + to_comment(minify_options_json) "// Can't reproduce test failure",
"// minify options: " + to_comment(minify_options_json)
].join("\n")
}; };
} else if (differs.timed_out) { } else if (differs.timed_out) {
return { return {
code: "// Can't reproduce test failure within " + max_timeout + "ms:" code: [
+ "\n// " + to_comment(minify_options_json) "// Can't reproduce test failure within " + max_timeout + "ms",
"// minify options: " + to_comment(minify_options_json)
].join("\n")
}; };
} else if (differs.error) { } else if (differs.error) {
return differs; 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 { } else {
max_timeout = Math.min(100 * differs.elapsed, max_timeout); max_timeout = Math.min(100 * differs.elapsed, max_timeout);
// Replace expressions with constants that will be parsed into // 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 // quick ignores
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 (!in_list && node instanceof U.AST_EmptyStatement) return;
if (node instanceof U.AST_Label) return; if (node instanceof U.AST_Label) return;
if (node instanceof U.AST_LabelRef) return; if (node instanceof U.AST_LabelRef) return;
if (!in_list && node instanceof U.AST_SymbolDeclaration) return; if (!in_list && node instanceof U.AST_SymbolDeclaration) return;
if (node instanceof U.AST_Toplevel) return; if (node instanceof U.AST_Toplevel) return;
var parent = tt.parent(); var parent = tt.parent();
if (node instanceof U.AST_SymbolFunarg && parent instanceof U.AST_Accessor) return;
// ensure that the _permute prop is a number. // ensure that the _permute prop is a number.
// can not use `node.start._permute |= 0;` as it will erase fractional part. // 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) { else if (node instanceof U.AST_Binary) {
CHANGED = true; CHANGED = true;
return [ var permute = ((node.start._permute += step) * steps | 0) % 4;
var expr = [
node.left, node.left,
node.right, 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) { else if (node instanceof U.AST_Catch || node instanceof U.AST_Finally) {
// drop catch or finally block // drop catch or finally block
@@ -357,15 +386,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} }
// replace this node // 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, 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 newNode; return newNode;
@@ -445,7 +470,42 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes"); console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes");
} }
} }
testcase = U.minify(testcase, { 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, compress: false,
mangle: false, mangle: false,
output: { output: {
@@ -454,18 +514,16 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
comments: true, comments: true,
}, },
}); });
testcase.code += [ if (result.error) return {
"", code: testcase,
"// output: " + to_comment(differs.unminified_result), };
"// minify: " + to_comment(differs.minified_result), var toplevel = sandbox.has_toplevel(minify_options);
"// options: " + to_comment(minify_options_json), var actual = run_code(result_cache, result.code, toplevel, timeout);
].join("\n").replace(/\u001b\[\d+m/g, ""); if (!sandbox.same_stdout(expected, actual)) return {
return testcase; code: testcase,
} };
}; result.code = "// (beautified)\n" + result.code;
return result;
function to_comment(value) {
return ("" + value).replace(/\n/g, "\n// ");
} }
function has_exit(fn) { function has_exit(fn) {
@@ -543,7 +601,7 @@ function producesDifferentResultWhenMinified(result_cache, code, minify_options,
var minified = U.minify(code, minify_options); var minified = U.minify(code, minify_options);
if (minified.error) return minified; if (minified.error) return minified;
var toplevel = minify_options.toplevel; var toplevel = sandbox.has_toplevel(minify_options);
var elapsed = Date.now(); var elapsed = Date.now();
var unminified_result = run_code(result_cache, code, toplevel, max_timeout); var unminified_result = run_code(result_cache, code, toplevel, max_timeout);
elapsed = Date.now() - elapsed; elapsed = Date.now() - elapsed;

View File

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