Compare commits

...

41 Commits

Author SHA1 Message Date
Alex Lam S.L
d13b71297e v3.8.0 2020-02-18 20:32:37 +08:00
Alex Lam S.L
457f958af3 improve --reduce-test (#3727)
- print out Node.js and OS information
2020-02-17 20:56:22 +00:00
Alex Lam S.L
53517db3e4 speed up --reduce-test (#3726)
- avoid pathological test case branches via adaptive time-out
- use initial test case elapsed time to adjust maximum time-out
- index output cache using hash instead of raw source
2020-02-17 15:35:07 +00:00
Alex Lam S.L
c13caf4876 speed up --reduce-test via result caching (#3725) 2020-02-15 22:43:34 +00:00
kzc
fbfa6178a6 improve --reduce-test (#3722)
- hoist body of functions and IIFEs
- simplify var declarations
2020-02-15 20:22:33 +00:00
Alex Lam S.L
5315dd95b0 minor cleanup (#3723) 2020-02-15 17:55:26 +00:00
Marco Gonzalez
31a7bf2a22 Updated "Output options" > "comments" in README.md (#3717)
Expanded the current documentation to include:

- What the value of `"some"` means based on `lib/output.js`.
- Information about the `Function` overload parameters and expected output.
2020-02-15 15:10:58 +00:00
Alex Lam S.L
f0a29902ac enhance properties (#3721) 2020-02-15 13:04:44 +00:00
Alex Lam S.L
0d820e4c0a workaround RegExp formatting bugs (#3720) 2020-02-15 05:26:48 +00:00
Alex Lam S.L
f01f580d6c improve --reduce-test (#3719)
- cover missing cases when eliminating unreferenced labels
- format multi-line outputs correctly
2020-02-14 02:47:20 +00:00
Alex Lam S.L
c01ff76288 improve code reuse (#3718) 2020-02-13 05:16:10 +00:00
Alex Lam S.L
83a42716c3 fix corner case in unused (#3716) 2020-02-12 23:46:16 +00:00
Alex Lam S.L
2557148bba increase mocha --reduce-test timeout (#3715) 2020-02-12 02:25:04 +00:00
Alex Lam S.L
dd22eda888 enhance evaluate (#3714) 2020-02-12 01:01:17 +00:00
Alex Lam S.L
f4c77886e7 add test for --reduce-test (#3712) 2020-02-09 23:21:46 +00:00
Alex Lam S.L
df547ffd97 improve test reduction (#3711)
- scan `AST_SymbolFunarg`
- scan `console.log(...)`
2020-02-09 20:42:36 +00:00
Alex Lam S.L
70551febc8 improve test/reduce (#3710)
- suppress several instances of malformed AST generation
- improve resilience & reporting against malformed ASTs
2020-02-09 08:07:55 +00:00
Alex Lam S.L
44499a6643 fix corner cases in test/reduce (#3709) 2020-02-07 02:41:07 +00:00
Alex Lam S.L
470a7d4df1 improve reduction of AST_BlockStatement (#3708) 2020-02-06 21:20:05 +00:00
Alex Lam S.L
551420132c export missing API for AST manipulation (#3707) 2020-02-06 18:46:25 +00:00
kzc
b0040ba654 implement CLI --reduce-test and reduce tests in ufuzz (#3705) 2020-02-06 02:50:59 +00:00
Alex Lam S.L
c93ca6ee53 fix corner case in ie8 & reduce_vars (#3706)
fixes #3703
2020-02-05 20:03:22 +00:00
Alex Lam S.L
df506439b1 fix corner case in sequences (#3704)
fixes #3703
2020-02-04 04:57:32 +00:00
Alex Lam S.L
36b2d35bf3 v3.7.7 2020-02-02 00:24:50 +00:00
Alex Lam S.L
79c60032a5 fix corner case in collapse_vars (#3701)
fixes #3700
2020-01-30 09:04:44 +08:00
Alex Lam S.L
a3754068dd fix corner case in collapse_vars (#3699)
fixes #3698
2020-01-30 00:08:53 +08:00
Alex Lam S.L
2ba5f391e0 enhance collapse_vars (#3697) 2020-01-29 08:52:20 +08:00
Alex Lam S.L
87119e44a0 fix corner case in sign propagation (#3696)
- migrate de-facto functionality to `evaluate`

fixes #3695
2020-01-28 22:44:18 +08:00
Alex Lam S.L
b499e03f82 enhance conditionals (#3694) 2020-01-28 12:33:21 +08:00
Alex Lam S.L
a478f275e4 enhance sequences (#3693) 2020-01-28 09:58:01 +08:00
Alex Lam S.L
e9e76dcf04 fix corner case in string concatenations (#3692)
- migrate de-facto compression to `conditionals` & `strings`

fixes #3689
2020-01-28 07:33:11 +08:00
Alex Lam S.L
0dcedad2d5 fix corner case in booleans (#3691)
fixes #3690
2020-01-28 02:04:44 +08:00
Alex Lam S.L
36a430cd1e v3.7.6 2020-01-19 11:02:58 +00:00
Alex Lam S.L
41a6eb892a fix corner case in evaluate (#3685)
fixes #3684
2020-01-16 01:51:37 +08:00
Alex Lam S.L
91d87ae663 fix corner case in unsafe_math (#3683)
fixes #3682
2020-01-15 04:05:58 +08:00
Alex Lam S.L
5beb7e4797 v3.7.5 2020-01-12 11:12:11 +08:00
Alex Lam S.L
46caaa82ba enhance collapse_vars (#3680)
closes #3679
2020-01-10 04:28:43 +08:00
Alex Lam S.L
5d258259a4 introduce --output-opts CLI option (#3678)
closes #3675
2020-01-08 20:44:03 +08:00
Alex Lam S.L
14c35739dd fix corner case in unsafe_math (#3677)
fixes #3676
2020-01-08 10:28:10 +08:00
Alex Lam S.L
f5ceff6e4b fix corner case in unused (#3674)
fixes #3673
2020-01-07 20:06:25 +08:00
Alex Lam S.L
4d6771b9b1 fix corner case in collapse_vars (#3672)
fixes #3671
2020-01-07 19:34:16 +08:00
38 changed files with 2321 additions and 398 deletions

View File

@@ -87,6 +87,7 @@ a double dash to prevent input files being used as option arguments:
`wrap_iife` Wrap IIFEs in parenthesis. Note: you may
want to disable `negate_iife` under
compressor options.
-O, --output-opts [options] Specify output options (`beautify` disabled by default).
-o, --output <file> Output file path (default STDOUT). Specify `ast` or
`spidermonkey` to write UglifyJS or SpiderMonkey AST
as JSON to STDOUT respectively.
@@ -735,6 +736,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
example: `/*@__PURE__*/foo();`
- `strings` (default: `true`) -- compact string concatenations.
- `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches
- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or
@@ -845,8 +848,14 @@ can pass additional arguments that control the code output:
statement.
- `comments` (default `false`) -- pass `true` or `"all"` to preserve all
comments, `"some"` to preserve some comments, a regular expression string
(e.g. `/^!/`) or a function.
comments, `"some"` to preserve multi-line comments that contain `@cc_on`,
`@license`, or `@preserve` (case-insensitive), a regular expression string
(e.g. `/^!/`), or a function which returns `boolean`, e.g.
```js
function(node, comment) {
return comment.value.indexOf("@type " + node.TYPE) >= 0;
}
```
- `indent_level` (default `4`)

View File

@@ -36,6 +36,7 @@ program.option("-c, --compress [options]", "Enable compressor/specify compressor
program.option("-m, --mangle [options]", "Mangle names/specify mangler options.", parse_js());
program.option("--mangle-props [options]", "Mangle properties/specify mangler options.", parse_js());
program.option("-b, --beautify [options]", "Beautify output/specify output options.", parse_js());
program.option("-O, --output-opts [options]", "Output options (beautify disabled).", parse_js());
program.option("-o, --output <file>", "Output file (default STDOUT).");
program.option("--comments [filter]", "Preserve copyright comments in the output.");
program.option("--config-file <file>", "Read minify() options from JSON file.");
@@ -53,6 +54,7 @@ program.option("--toplevel", "Compress and/or mangle variables in toplevel scope
program.option("--verbose", "Print diagnostic messages.");
program.option("--warn", "Print warning messages.");
program.option("--wrap <name>", "Embed everything as a function with “exports” corresponding to “name” globally.");
program.option("--reduce-test", "Reduce a standalone `console.log` based test case.");
program.arguments("[files...]").parseArgv(process.argv);
if (program.configFile) {
options = JSON.parse(read_file(program.configFile));
@@ -93,6 +95,10 @@ if (program.beautify) {
options.output.beautify = true;
}
}
if (program.outputOpts) {
if (program.beautify) fatal("--beautify cannot be used with --output-opts");
options.output = typeof program.outputOpts == "object" ? program.outputOpts : {};
}
if (program.comments) {
if (typeof options.output != "object") options.output = {};
options.output.comments = typeof program.comments == "string" ? program.comments : "some";
@@ -210,7 +216,15 @@ function run() {
} catch (ex) {
fatal(ex);
}
var result = UglifyJS.minify(files, options);
if (program.reduceTest) {
// load on demand - assumes dev tree checked out
var reduce_test = require("../test/reduce");
var testcase = files[0] || files[Object.keys(files)[0]];
var result = reduce_test(testcase, options, {verbose: true});
}
else {
var result = UglifyJS.minify(files, options);
}
if (result.error) {
var ex = result.error;
if (ex.name == "SyntaxError") {

View File

@@ -169,10 +169,7 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
}, AST_Statement);
function walk_body(node, visitor) {
var body = node.body;
if (body instanceof AST_Statement) {
body._walk(visitor);
} else body.forEach(function(node) {
node.body.forEach(function(node) {
node._walk(visitor);
});
}
@@ -351,7 +348,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
filename: "wrap=" + JSON.stringify(name)
}).transform(new TreeTransformer(function(node) {
if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(body);
return List.splice(body);
}
}));
},
@@ -370,7 +367,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
filename: "enclose=" + JSON.stringify(args_values)
}).transform(new TreeTransformer(function(node) {
if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(body);
return List.splice(body);
}
}));
}

File diff suppressed because it is too large Load Diff

View File

@@ -783,6 +783,8 @@ function OutputStream(options) {
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) {
var value = this.value;
// https://github.com/mishoo/UglifyJS2/issues/115
// https://github.com/mishoo/UglifyJS2/pull/1009
if (value < 0 || /^0/.test(make_num(value))) {
return true;
}
@@ -1352,11 +1354,18 @@ function OutputStream(options) {
DEFPRINT(AST_RegExp, function(self, output) {
var regexp = self.value;
var str = regexp.toString();
var end = str.lastIndexOf("/");
if (regexp.raw_source) {
str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/"));
str = "/" + regexp.raw_source + str.slice(end);
} else if (end == 1) {
str = "/(?:)" + str.slice(end);
} else if (str.indexOf("/", 1) < end) {
str = "/" + str.slice(1, end).replace(/\\\\|[^/]?\//g, function(match) {
return match[0] == "\\" ? match : match.slice(0, -1) + "\\/";
}) + str.slice(end);
}
output.print(output.to_utf8(str).replace(/\\(?:\0(?![0-9])|[^\0])/g, function(seq) {
switch (seq[1]) {
output.print(output.to_utf8(str).replace(/\\(?:\0(?![0-9])|[^\0])/g, function(match) {
switch (match[1]) {
case "\n": return "\\n";
case "\r": return "\\r";
case "\t": return "\t";
@@ -1366,7 +1375,7 @@ function OutputStream(options) {
case "\x0B": return "\v";
case "\u2028": return "\\u2028";
case "\u2029": return "\\u2029";
default: return seq;
default: return match;
}
}).replace(/[\n\r\u2028\u2029]/g, function(c) {
switch (c) {

View File

@@ -52,7 +52,7 @@ TreeTransformer.prototype = new TreeWalker;
(function(DEF) {
function do_list(list, tw) {
return MAP(list, function(node) {
return List(list, function(node) {
return node.transform(tw, true);
});
}

View File

@@ -113,8 +113,8 @@ function return_true() { return true; }
function return_this() { return this; }
function return_null() { return null; }
var MAP = (function() {
function MAP(a, f, backwards) {
var List = (function() {
function List(a, f, backwards) {
var ret = [], top = [], i;
function doit() {
var val = f(a[i], i);
@@ -149,14 +149,14 @@ var MAP = (function() {
}
return top.concat(ret);
}
MAP.at_top = function(val) { return new AtTop(val) };
MAP.splice = function(val) { return new Splice(val) };
MAP.last = function(val) { return new Last(val) };
var skip = MAP.skip = {};
function AtTop(val) { this.v = val }
function Splice(val) { this.v = val }
function Last(val) { this.v = val }
return MAP;
List.at_top = function(val) { return new AtTop(val); };
List.splice = function(val) { return new Splice(val); };
List.last = function(val) { return new Last(val); };
var skip = List.skip = {};
function AtTop(val) { this.v = val; }
function Splice(val) { this.v = val; }
function Last(val) { this.v = val; }
return List;
})();
function push_uniq(array, el) {

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.7.4",
"version": "3.8.0",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -16,6 +16,7 @@ holes_and_undefined: {
constant_join: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
@@ -65,6 +66,7 @@ constant_join: {
constant_join_2: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
@@ -94,9 +96,11 @@ constant_join_2: {
constant_join_3: {
options = {
evaluate: true,
strings: true,
unsafe: true,
}
input: {
var foo, bar, baz;
var a = [ null ].join();
var b = [ , ].join();
var c = [ , 1, , 3 ].join();
@@ -111,6 +115,7 @@ constant_join_3: {
var l = [ foo, bar + "baz" ].join("");
}
expect: {
var foo, bar, baz;
var a = "";
var b = "";
var c = ",1,,3";

View File

@@ -131,3 +131,25 @@ issue_3658: {
}
expect_stdout: "PASS"
}
issue_3690: {
options = {
booleans: true,
unused: true,
}
input: {
console.log(function(a) {
return function() {
return a = [ this ];
}() ? "PASS" : "FAIL";
}());
}
expect: {
console.log(function(a) {
return function() {
return 1;
}() ? "PASS" : "FAIL";
}());
}
expect_stdout: "PASS"
}

View File

@@ -7464,3 +7464,304 @@ issue_3651: {
}
expect_stdout: "PASS"
}
issue_3671: {
options = {
collapse_vars: true,
}
input: {
var a = 0;
try {
a++;
A += 0;
a = 1 + a;
} catch (e) {
console.log(a);
}
}
expect: {
var a = 0;
try {
a++;
A += 0;
a = 1 + a;
} catch (e) {
console.log(a);
}
}
expect_stdout: "1"
}
call_1: {
options = {
collapse_vars: true,
}
input: {
(function(a) {
a = console;
(function() {})();
a.log("PASS");
})();
}
expect: {
(function(a) {
(function() {})();
(a = console).log("PASS");
})();
}
expect_stdout: "PASS"
}
call_1_symbol: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
(function(a) {
function f() {}
a = console;
f();
a.log(typeof f);
})();
}
expect: {
(function(a) {
function f() {}
f();
(a = console).log(typeof f);
})();
}
expect_stdout: "function"
}
call_2: {
options = {
collapse_vars: true,
}
input: {
(function(a) {
a = console;
(function() {
return 42;
console.log("FAIL");
})();
a.log("PASS");
})();
}
expect: {
(function(a) {
(function() {
return 42;
console.log("FAIL");
})();
(a = console).log("PASS");
})();
}
expect_stdout: "PASS"
}
call_2_symbol: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
(function(a) {
function f() {
return 42;
console.log("FAIL");
}
a = console;
f();
a.log(typeof f);
})();
}
expect: {
(function(a) {
function f() {
return 42;
console.log("FAIL");
}
f();
(a = console).log(typeof f);
})();
}
expect_stdout: "function"
}
call_3: {
options = {
collapse_vars: true,
}
input: {
(function(a) {
a = console;
(function() {
a = {
log: function() {
console.log("PASS");
}
}
})();
a.log("FAIL");
})();
}
expect: {
(function(a) {
a = console;
(function() {
a = {
log: function() {
console.log("PASS");
}
}
})();
a.log("FAIL");
})();
}
expect_stdout: "PASS"
}
call_3_symbol: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
(function(a) {
function f() {
a = {
log: function() {
console.log(typeof f);
}
}
}
a = console;
f();
a.log("FAIL");
})();
}
expect: {
(function(a) {
function f() {
a = {
log: function() {
console.log(typeof f);
}
}
}
a = console;
f();
a.log("FAIL");
})();
}
expect_stdout: "function"
}
issue_3698_1: {
options = {
collapse_vars: true,
}
input: {
var log = console.log;
var a, b = 0, c = 0;
(function() {
a = b;
})(b++, (b++, c++));
log(a, b, c);
}
expect: {
var log = console.log;
var a, b = 0, c = 0;
(function() {
a = b;
})(b++, (b++, c++));
log(a, b, c);
}
expect_stdout: "2 2 1"
}
issue_3698_2: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
var log = console.log;
var a, b = 0, c = 0, d = 1;
(function f() {
a = b;
d-- && f();
})(b++, (b++, c++));
log(a, b, c, d);
}
expect: {
var log = console.log;
var a, b = 0, c = 0, d = 1;
(function f() {
a = b;
d-- && f();
})(b++, (b++, c++));
log(a, b, c, d);
}
expect_stdout: "2 2 1 -1"
}
issue_3698_3: {
options = {
collapse_vars: true,
reduce_vars: true,
}
input: {
var a = 0, b = 0;
(function f(c) {
{
b++;
var bar_1 = (b = 1 + b, c = 0);
a-- && f();
}
})();
console.log(b);
}
expect: {
var a = 0, b = 0;
(function f(c) {
var bar_1 = (b = 1 + ++b, c = 0);
a-- && f();
})();
console.log(b);
}
expect_stdout: "2"
}
issue_3700: {
options = {
collapse_vars: true,
}
input: {
var a = "FAIL";
try {
a = "PASS";
(function() {
throw 0;
})();
a = 1 + a;
} catch (e) {
}
console.log(a);
}
expect: {
var a = "FAIL";
try {
a = "PASS";
(function() {
throw 0;
})();
a = 1 + a;
} catch (e) {
}
console.log(a);
}
expect_stdout: "PASS"
}

View File

@@ -26,7 +26,9 @@ concat_1: {
}
concat_2: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + (2 + 3),
@@ -55,7 +57,9 @@ concat_2: {
}
concat_3: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + 2 + (3 + 4 + 5),
@@ -84,7 +88,9 @@ concat_3: {
}
concat_4: {
options = {}
options = {
strings: true,
}
input: {
console.log(
1 + "2" + (3 + 4 + 5),
@@ -113,7 +119,9 @@ concat_4: {
}
concat_5: {
options = {}
options = {
strings: true,
}
input: {
console.log(
"1" + 2 + (3 + 4 + 5),
@@ -142,7 +150,9 @@ concat_5: {
}
concat_6: {
options = {}
options = {
strings: true,
}
input: {
console.log(
"1" + "2" + (3 + 4 + 5),
@@ -171,6 +181,9 @@ concat_6: {
}
concat_7: {
options = {
strings: true,
}
input: {
console.log(
"" + 1,
@@ -197,6 +210,9 @@ concat_7: {
}
concat_8: {
options = {
strings: true,
}
input: {
console.log(
1 + "",
@@ -221,3 +237,20 @@ concat_8: {
}
expect_stdout: true
}
issue_3689: {
options = {
strings: true,
}
input: {
console.log(function(a) {
return a + ("" + (a[0] = 0));
}([]));
}
expect: {
console.log(function(a) {
return a + ("" + (a[0] = 0));
}([]));
}
expect_stdout: "00"
}

View File

@@ -294,6 +294,45 @@ cond_5: {
}
}
cond_6: {
options = {
booleans: true,
conditionals: true,
}
input: {
x ? a : b;
x ? a : a;
x ? y ? a : b : c;
x ? y ? a : a : b;
x ? y ? a : b : b;
x ? y ? a : b : a;
x ? y ? a : a : a;
x ? a : y ? b : c;
x ? a : y ? a : b;
x ? a : y ? b : b;
x ? a : y ? b : a;
x ? a : y ? a : a;
}
expect: {
x ? a : b;
x, a;
x ? y ? a : b : c;
x ? (y, a) : b;
x && y ? a : b;
!x || y ? a : b;
x && y, a;
x ? a : y ? b : c;
x || y ? a : b;
x ? a : (y, b);
!x && y ? b : a;
!x && y, a;
}
}
cond_7: {
options = {
conditionals: true,
@@ -726,6 +765,24 @@ cond_11: {
expect_stdout: "foo bar"
}
cond_12: {
options = {
conditionals: true,
}
input: {
x ? y && a : a;
x ? y || a : a;
x ? a : y && a;
x ? a : y || a;
}
expect: {
(!x || y) && a;
x && y || a;
(x || y) && a;
!x && y || a;
}
}
ternary_boolean_consequent: {
options = {
booleans: true,

View File

@@ -830,6 +830,7 @@ issue_3552: {
unreachable_assign: {
options = {
dead_code: true,
strings: true,
}
input: {
console.log(A = "P" + (A = "A" + (B = "S" + (A = B = "S"))), A, B);

View File

@@ -1191,10 +1191,10 @@ issue_2105_1: {
input: {
!function(factory) {
factory();
}( function() {
}(function() {
return function(fn) {
fn()().prop();
}( function() {
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
@@ -1205,7 +1205,7 @@ issue_2105_1: {
return { prop: foo };
}
return bar;
} );
});
});
}
expect: {
@@ -1235,10 +1235,10 @@ issue_2105_2: {
input: {
!function(factory) {
factory();
}( function() {
}(function() {
return function(fn) {
fn()().prop();
}( function() {
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
@@ -1249,7 +1249,7 @@ issue_2105_2: {
return { prop: foo };
}
return bar;
} );
});
});
}
expect: {
@@ -1258,6 +1258,44 @@ issue_2105_2: {
expect_stdout: "PASS"
}
issue_2105_3: {
options = {
inline: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
!function(factory) {
factory();
}(function() {
return function(fn) {
fn()().prop();
}(function() {
function bar() {
var quux = function() {
console.log("PASS");
}, foo = function() {
console.log;
quux();
};
return { prop: foo };
}
return bar;
});
});
}
expect: {
!void void {
prop: function() {
console.log;
void console.log("PASS");
}
}.prop();
}
expect_stdout: "PASS"
}
issue_2226_1: {
options = {
side_effects: true,
@@ -2330,7 +2368,7 @@ function_parameter_ie8: {
(function() {
(function f() {
console.log("PASS");
})(0);
})();
})();
}
expect_stdout: "PASS"
@@ -2355,3 +2393,23 @@ issue_3664: {
}
expect_stdout: "PASS"
}
issue_3673: {
options = {
pure_getters: "strict",
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
(a = [ a ]).p = 42;
console.log("PASS");
}
expect: {
var a;
(a = [ a ]).p = 42;
console.log("PASS");
}
expect_stdout: "PASS"
}

View File

@@ -1276,7 +1276,7 @@ issue_2630_3: {
(function() {
(function f1(a) {
f2();
--x >= 0 && f1({});
--x >= 0 && f1();
})(a++);
function f2() {
a++;
@@ -2244,7 +2244,7 @@ issue_3076: {
var c = "PASS";
(function(b) {
var n = 2;
while (--b + (e = void 0, e && (c = "FAIL"), e = 5, 1).toString() && --n > 0);
while (--b + (e = void 0, e && (c = "FAIL"), e = 5, 1..toString()) && --n > 0);
var e;
})(2),
console.log(c);
@@ -3700,3 +3700,88 @@ pr_3595_4: {
}
expect_stdout: "PASS"
}
issue_3679_1: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var f = function() {};
f.g = function() {
console.log("PASS");
};
f.g();
})();
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
}
issue_3679_2: {
options = {
collapse_vars: true,
inline: true,
passes: 2,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
"use strict";
var f = function() {};
f.g = function() {
console.log("PASS");
};
f.g();
})();
}
expect: {
(function() {
"use strict";
console.log("PASS");
})();
}
expect_stdout: "PASS"
}
issue_3679_3: {
options = {
collapse_vars: true,
inline: true,
functions: true,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var f = function() {};
f.p = "PASS";
f.g = function() {
console.log(f.p);
};
f.g();
})();
}
expect: {
(function() {
function f() {};
f.p = "PASS";
(f.g = function() {
console.log(f.p);
})();
})();
}
expect_stdout: "PASS"
}

View File

@@ -2361,3 +2361,40 @@ issue_3542: {
}
expect_stdout: "1"
}
issue_3703: {
options = {
ie8: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS";
function f() {
var b;
function g() {
a = "FAIL";
}
var c = g;
function h() {
f;
}
a ? b |= c : b.p;
}
f();
console.log(a);
}
expect: {
var a = "PASS";
(function() {
var b;
var c = function g() {
a = "FAIL";
};
a ? b |= c : b.p;
})();
console.log(a);
}
expect_stdout: "PASS"
}

View File

@@ -46,7 +46,7 @@ mangle_props: {
obj[1/0],
obj["Infinity"],
obj[-1/0],
obj[-1/0],
obj[-(1/0)],
obj["-Infinity"],
obj[null],
obj["null"]

View File

@@ -1,98 +1,111 @@
issue_269_1: {
options = {
options = {
unsafe: true,
}
input: {
f(
String(x),
Number(x),
Boolean(x),
input: {
var x = {};
console.log(
String(x),
Number(x),
Boolean(x),
String(),
Number(),
Boolean()
);
}
expect: {
f(
x + '', +x, !!x,
'', 0, false
);
}
String(),
Number(),
Boolean()
);
}
expect: {
var x = {};
console.log(
x + "", +x, !!x,
"", 0, false
);
}
expect_stdout: true
}
issue_269_dangers: {
options = {
options = {
unsafe: true,
}
input: {
f(
String(x, x),
Number(x, x),
Boolean(x, x)
);
}
expect: {
f(String(x, x), Number(x, x), Boolean(x, x));
}
input: {
var x = {};
console.log(
String(x, x),
Number(x, x),
Boolean(x, x)
);
}
expect: {
var x = {};
console.log(String(x, x), Number(x, x), Boolean(x, x));
}
expect_stdout: true
}
issue_269_in_scope: {
options = {
options = {
unsafe: true,
}
input: {
var String, Number, Boolean;
f(
String(x),
Number(x, x),
Boolean(x)
);
}
expect: {
var String, Number, Boolean;
f(String(x), Number(x, x), Boolean(x));
}
input: {
var String, Number, Boolean;
var x = {};
console.log(
String(x),
Number(x, x),
Boolean(x)
);
}
expect: {
var String, Number, Boolean;
var x = {};
console.log(String(x), Number(x, x), Boolean(x));
}
expect_stdout: true
}
strings_concat: {
options = {
options = {
strings: true,
unsafe: true,
}
input: {
f(
String(x + 'str'),
String('str' + x)
);
}
expect: {
f(
x + 'str',
'str' + x
);
}
input: {
var x = {};
console.log(
String(x + "str"),
String("str" + x)
);
}
expect: {
var x = {};
console.log(
x + "str",
"str" + x
);
}
expect_stdout: true
}
regexp: {
options = {
options = {
evaluate: true,
unsafe: true,
}
input: {
RegExp("foo");
RegExp("bar", "ig");
RegExp(foo);
RegExp("bar", ig);
RegExp("should", "fail");
}
expect: {
/foo/;
/bar/ig;
RegExp(foo);
RegExp("bar", ig);
RegExp("should", "fail");
}
expect_warnings: [
'WARN: Error converting RegExp("should","fail") [test/compress/issue-269.js:5,2]',
]
input: {
RegExp("foo");
RegExp("bar", "ig");
RegExp(foo);
RegExp("bar", ig);
RegExp("should", "fail");
}
expect: {
/foo/;
/bar/ig;
RegExp(foo);
RegExp("bar", ig);
RegExp("should", "fail");
}
expect_warnings: [
'WARN: Error converting RegExp("should","fail") [test/compress/issue-269.js:5,8]',
]
}

View File

@@ -728,7 +728,7 @@ issue_2630_3: {
(function() {
(function f1() {
f2();
--x >= 0 && f1({});
--x >= 0 && f1();
})(a++);
function f2() {
a++;
@@ -1369,7 +1369,7 @@ recursive_iife_1: {
}
expect: {
console.log(function f(a, b) {
return b || f("FAIL", "PASS");
return b || f(0, "PASS");
}());
}
expect_stdout: "PASS"
@@ -1388,7 +1388,7 @@ recursive_iife_2: {
}
expect: {
console.log(function f(a, b) {
return b || f("FAIL", "PASS");
return b || f(0, "PASS");
}(0, 0));
}
expect_stdout: "PASS"
@@ -1416,7 +1416,7 @@ recursive_iife_3: {
var a = 1, c = "PASS";
(function() {
(function f(b, d, e) {
a-- && f(null, 42, 0);
a-- && f(0, 42, 0);
e && (c = "FAIL");
d && d.p;
})();

View File

@@ -669,6 +669,9 @@ issue_1710: {
}
unary_binary_parenthesis: {
options = {
evaluate: true,
}
input: {
var v = [ 0, 1, NaN, Infinity, null, undefined, true, false, "", "foo", /foo/ ];
v.forEach(function(x) {
@@ -1075,11 +1078,11 @@ issue_3653: {
}
expect: {
console.log(0 - (console && 0));
console.log(0 - (console && 0) + 0);
console.log(0 - (console && 0));
console.log(0 - (0 - (console && 0)));
console.log(0 - (console && 0));
console.log(1 / (0 - (console && 0)));
console.log(0 - (console && 0) + 0);
console.log(0 - (console && 0));
console.log(0 - (console && 0));
console.log(0 - (console && 0));
console.log(0 - (console && 0));
@@ -1135,3 +1138,116 @@ issue_3655: {
"0",
]
}
issue_3676_1: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = [];
console.log(false - (a - (a[1] = 42)));
}
expect: {
var a = [];
console.log(false - (a - (a[1] = 42)));
}
expect_stdout: "NaN"
}
issue_3676_2: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a;
console.log(false - ((a = []) - (a[1] = 42)));
}
expect: {
var a;
console.log(false - ((a = []) - (a[1] = 42)));
}
expect_stdout: "NaN"
}
issue_3682_1: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = -0;
console.log(1 / (a - 1 + 1));
}
expect: {
var a = -0;
console.log(1 / (a - 1 + 1));
}
expect_stdout: "Infinity"
}
issue_3682_2: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = -0, b = 1;
console.log(1 / (a - (b - b)));
}
expect: {
var a = -0, b = 1;
console.log(1 / (a - (b - b)));
}
expect_stdout: "-Infinity"
}
issue_3682_3: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = -0, b = 1, c = -1;
console.log(1 / (a - (+b + +c)));
}
expect: {
var a = -0, b = 1, c = -1;
console.log(1 / (a - (+b + +c)));
}
expect_stdout: "-Infinity"
}
issue_3684: {
options = {
evaluate: true,
}
input: {
console.log(1 / (-1 * (0 & console) + 0));
console.log(1 / ((0 & console) / -1 + 0));
}
expect: {
console.log(1 / (-1 * (0 & console) + 0));
console.log(1 / ((0 & console) / -1 + 0));
}
expect_stdout: [
"Infinity",
"Infinity",
]
}
issue_3695: {
options = {
evaluate: true,
}
input: {
var a = [];
console.log(+(a * (a[0] = false)));
}
expect: {
var a = [];
console.log(+(a * (a[0] = false)));
}
expect_stdout: "NaN"
}

View File

@@ -817,6 +817,29 @@ issue_2208_5: {
expect_stdout: "42"
}
issue_2208_6: {
options = {
inline: true,
properties: true,
side_effects: true,
}
input: {
a = 42;
console.log(("FAIL", {
p: function() {
return this.a;
}
}.p)());
}
expect: {
a = 42;
console.log(function() {
return this.a;
}());
}
expect_stdout: "42"
}
issue_2256: {
options = {
side_effects: true,

View File

@@ -2071,13 +2071,8 @@ issue_1670_6: {
}
expect: {
(function(a) {
switch (1) {
case a = 1:
console.log(a);
break;
default:
console.log(2);
}
a = 1;
console.log(a);
})(1);
}
expect_stdout: "1"

View File

@@ -186,3 +186,72 @@ issue_3434_3: {
/\nfo\n[\n]o\bbb/;
}
}
issue_3434_4: {
options = {
evaluate: true,
unsafe: true,
}
input: {
[
[ "", RegExp("") ],
[ "/", RegExp("/") ],
[ "//", RegExp("//") ],
[ "\/", RegExp("\\/") ],
[ "///", RegExp("///") ],
[ "/\/", RegExp("/\\/") ],
[ "\//", RegExp("\\//") ],
[ "\\/", RegExp("\\\\/") ],
[ "////", RegExp("////") ],
[ "//\/", RegExp("//\\/") ],
[ "/\//", RegExp("/\\//") ],
[ "/\\/", RegExp("/\\\\/") ],
[ "\///", RegExp("\\///") ],
[ "\/\/", RegExp("\\/\\/") ],
[ "\\//", RegExp("\\\\//") ],
[ "\\\/", RegExp("\\\\\\/") ],
].forEach(function(test) {
console.log(test[1].test("\\"), test[1].test(test[0]));
});
}
expect: {
[
[ "", /(?:)/ ],
[ "/", /\// ],
[ "//", /\/\// ],
[ "/", /\// ],
[ "///", /\/\/\// ],
[ "//", /\/\// ],
[ "//", /\/\// ],
[ "\\/", /\\\// ],
[ "////", /\/\/\/\// ],
[ "///", /\/\/\// ],
[ "///", /\/\/\// ],
[ "/\\/", /\/\\\// ],
[ "///", /\/\/\// ],
[ "//", /\/\// ],
[ "\\//", /\\\/\// ],
[ "\\/", /\\\// ],
].forEach(function(test) {
console.log(test[1].test("\\"), test[1].test(test[0]));
});
}
expect_stdout: [
"true true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
"false true",
]
}

View File

@@ -910,15 +910,23 @@ call: {
console.log(this === b ? "bar" : "baz");
};
(a, b)();
(a, b).c();
(a, b.c)();
(a, b)["c"]();
(a, b["c"])();
(a, function() {
console.log(this === a);
})();
new (a, b)();
new (a, b).c();
new (a, b.c)();
new (a, b)["c"]();
new (a, b["c"])();
new (a, function() {
console.log(this === a);
})();
console.log(typeof (a, b).c);
console.log(typeof (a, b)["c"]);
}
expect: {
var a = function() {
@@ -931,23 +939,39 @@ call: {
console.log(this === b ? "bar" : "baz");
},
b(),
b.c(),
(a, b.c)(),
b["c"](),
(a, b["c"])(),
function() {
console.log(this === a);
}(),
new b(),
new b.c(),
new b.c(),
new b["c"](),
new b["c"](),
new function() {
console.log(this === a);
}();
}(),
console.log((a, typeof b.c)),
console.log((a, typeof b["c"]));
}
expect_stdout: [
"foo",
"bar",
"baz",
"bar",
"baz",
"true",
"foo",
"baz",
"baz",
"baz",
"baz",
"false",
"function",
"function",
]
}
@@ -1069,3 +1093,22 @@ issue_3490_2: {
}
expect_stdout: "PASS 42"
}
issue_3703: {
options = {
evaluate: true,
sequences: true,
unsafe: true,
}
input: {
var a = "FAIL";
while ((a = "PASS", 0).foo = 0);
console.log(a);
}
expect: {
var a = "FAIL";
while (a = "PASS", (0).foo = 0);
console.log(a);
}
expect_stdout: "PASS"
}

View File

@@ -1,6 +1,7 @@
exports["Compressor"] = Compressor;
exports["defaults"] = defaults;
exports["JS_Parse_Error"] = JS_Parse_Error;
exports["List"] = List;
exports["mangle_properties"] = mangle_properties;
exports["minify"] = minify;
exports["OutputStream"] = OutputStream;

View File

@@ -0,0 +1,17 @@
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
function f() {
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
}

View File

@@ -1,17 +1 @@
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
function f() {
if (x) foo();
if (x) foo(); else baz();
if (x) foo(); else if (y) bar(); else baz();
if (x) if (y) foo(); else bar(); else baz();
if (x) foo(); else if (y) bar(); else if (z) baz(); else moo();
}
if(x)foo();if(x)foo();else baz();if(x)foo();else if(y)bar();else baz();if(x)if(y)foo();else bar();else baz();if(x)foo();else if(y)bar();else if(z)baz();else moo();function f(){if(x)foo();if(x)foo();else baz();if(x)foo();else if(y)bar();else baz();if(x)if(y)foo();else bar();else baz();if(x)foo();else if(y)bar();else if(z)baz();else moo()}

View File

@@ -0,0 +1,9 @@
var o = this;
for (var k in o) L17060: {
a++;
}
var a;
console.log(k);

View File

@@ -0,0 +1,16 @@
var o = this;
for (var k in o) {
0;
}
var a;
console.log(k);
// output: a
//
// minify: k
//
// options: {
// "mangle": false
// }

View File

@@ -0,0 +1,18 @@
var _calls_ = 10, a = 100, b = 10, c = 0;
function f0(b_1, a, undefined_2) {
a++ + ++b;
{
var expr2 = (b + 1 - .1 - .1 - .1 || a || 3).toString();
L20778: for (var key2 in expr2) {
(c = c + 1) + [ --b + b_1, typeof f0 == "function" && --_calls_ >= 0 && f0(--b + typeof (undefined_2 = 1 === 1 ? a : b), --b + {
c: (c = c + 1) + null
}, a++ + (typeof f0 == "function" && --_calls_ >= 0 && f0(typeof (c = 1 + c, 3 / "a" * ("c" >>> 23..toString()) >= (b_1 && (b_1[(c = c + 1) + a--] = (- -0,
true + {})))), 3, 25))), 1 === 1 ? a : b ];
}
}
}
var a_1 = f0([ , 0 ].length === 2);
console.log(null, a, b, c, Infinity, NaN, undefined);

View File

@@ -0,0 +1,19 @@
var b = 0;
var expr2 = (0 - 1 - .1 - .1).toString();
for (var key2 in expr2) {
--b;
}
console.log(b);
// output: -19
//
// minify: -4
//
// options: {
// "compress": {
// "unsafe_math": true
// },
// "mangle": false
// }

View File

@@ -176,7 +176,7 @@ describe("bin/uglifyjs", function() {
var command = uglifyjscmd + ' test/input/issue-1482/input.js -b';
exec(command, function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
assert.strictEqual(stdout, read("test/input/issue-1482/beautify.js"));
done();
});
});
@@ -188,6 +188,22 @@ describe("bin/uglifyjs", function() {
done();
});
});
it("Should work with `--output-opts`", function(done) {
var command = uglifyjscmd + ' test/input/issue-1482/input.js -O';
exec(command, function(err, stdout) {
if (err) throw err;
assert.strictEqual(stdout, read("test/input/issue-1482/default.js"));
done();
});
});
it("Should fail when both --beautify & --output-opts are specified", function(done) {
var command = uglifyjscmd + " test/input/issue-520/input.js -bO";
exec(command, function(err, stdout, stderr) {
assert.ok(err);
assert.strictEqual(stderr, "ERROR: --beautify cannot be used with --output-opts\n");
done();
});
});
it("Should process inline source map", function(done) {
var command = [
uglifyjscmd,

167
test/mocha/reduce.js Normal file
View File

@@ -0,0 +1,167 @@
var assert = require("assert");
var exec = require("child_process").exec;
var fs = require("fs");
var reduce_test = require("../reduce");
var semver = require("semver");
function read(path) {
return fs.readFileSync(path, "utf8");
}
describe("test/reduce.js", function() {
this.timeout(60000);
it("Should reduce test case", function() {
var result = reduce_test(read("test/input/reduce/unsafe_math.js"), {
compress: {
unsafe_math: true,
},
mangle: false,
}, {
verbose: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/unsafe_math.reduced.js"));
});
it("Should eliminate unreferenced labels", function() {
var result = reduce_test(read("test/input/reduce/label.js"), {
mangle: false,
}, {
verbose: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, read("test/input/reduce/label.reduced.js"));
});
it("Should handle test cases with --toplevel", function() {
var result = reduce_test([
"var Infinity = 42;",
"console.log(Infinity);",
].join("\n"), {
toplevel: true,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"// Can't reproduce test failure with minify options provided:",
"// {",
'// "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',
"// }",
].join("\n"));
});
it("Should print correct output for irreducible test case", function() {
var result = reduce_test([
"console.log(function f(a) {",
" return f.length;",
"}());",
].join("\n"), {
compress: {
keep_fargs: false,
},
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
"console.log(function f(a) {",
" return f.length;",
"}());",
"// output: 1",
"// ",
"// minify: 0",
"// ",
"// options: {",
'// "compress": {',
'// "keep_fargs": false',
"// },",
'// "mangle": false',
"// }",
].join("\n"));
});
it("Should fail when invalid option is supplied", function() {
var result = reduce_test("", {
compress: {
unsafe_regex: true,
},
});
var err = result.error;
assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "DefaultsError: `unsafe_regex` is not a supported option");
});
it("Should report on test case with invalid syntax", function() {
var result = reduce_test("var 0 = 1;");
var err = result.error;
assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Name expected");
});
it("Should format multi-line output correctly", function() {
var code = [
"var a = 0;",
"",
"for (var b in [ 1, 2, 3 ]) {",
" a = +a + 1 - .2;",
" console.log(a);",
"}",
].join("\n");
var result = reduce_test(code, {
compress: {
unsafe_math: true,
},
mangle: false,
});
if (result.error) throw result.error;
assert.strictEqual(result.code, [
code,
"// output: 0.8",
"// 1.6",
"// 2.4",
"// ",
"// minify: 0.8",
"// 1.6",
"// 2.4000000000000004",
"// ",
"// options: {",
'// "compress": {',
'// "unsafe_math": true',
"// },",
'// "mangle": false',
"// }",
].join("\n"));
});
it("Should reduce infinite loops with reasonable performance", function() {
if (semver.satisfies(process.version, "0.10")) return;
this.timeout(120000);
var code = [
"var a = 9007199254740992, b = 1;",
"",
"while (a++ + (1 - b) < a) {",
" 0;",
"}",
].join("\n");
var result = reduce_test(code, {
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."), [
code,
"// output: ",
"// minify: Error: Script execution timed out.",
"// options: {",
'// "compress": {',
'// "unsafe_math": true',
"// },",
'// "mangle": false',
"// }",
].join("\n"));
});
});

564
test/reduce.js Normal file
View File

@@ -0,0 +1,564 @@
var crypto = require("crypto");
var U = require("./node");
var List = U.List;
var os = require("os");
var sandbox = require("./sandbox");
// Reduce a ufuzz-style `console.log` based test case by iteratively replacing
// AST nodes with various permutations. Each AST_Statement in the tree is also
// speculatively dropped to determine whether it is needed. If the altered
// tree and the last known good tree produce the same non-nil error-free output
// after being run, then the permutation survives to the next generation and
// is the basis for subsequent iterations. The test case is reduced as a
// consequence of complex expressions being replaced with simpler ones.
// This function assumes that the testcase will not result in a parse or
// runtime Error. Note that a reduced test case will have different runtime
// output - it is not functionally equivalent to the original. The only criteria
// is that once the generated reduced test case is run without minification, it
// will produce different output from the code minified with `minify_options`.
// Returns a `minify` result object with an additonal boolean property `reduced`.
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 };
reduce_options = reduce_options || {};
var max_iterations = reduce_options.max_iterations || 1000;
var max_timeout = reduce_options.max_timeout || 10000;
var verbose = reduce_options.verbose;
var minify_options_json = JSON.stringify(minify_options, null, 2);
var result_cache = Object.create(null);
// the initial timeout to assess the viability of the test case must be large
var differs = producesDifferentResultWhenMinified(result_cache, testcase, minify_options, max_timeout);
if (verbose) {
console.error("// Node.js " + process.version + " on " + os.platform() + " " + os.arch());
}
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)
};
} else if (differs.timed_out) {
return {
code: "// Can't reproduce test failure within " + max_timeout + "ms:"
+ "\n// " + to_comment(minify_options_json)
};
} else if (differs.error) {
return differs;
} else {
max_timeout = Math.min(100 * differs.elapsed, max_timeout);
// Replace expressions with constants that will be parsed into
// AST_Nodes as required. Each AST_Node has its own permutation count,
// so these replacements can't be shared.
// Although simpler replacements are generally faster and better,
// feel free to experiment with a different replacement set.
var REPLACEMENTS = [
// "null", "''", "false", "'foo'", "undefined", "9",
"1", "0",
];
// There's a relationship between each node's _permute counter and
// REPLACEMENTS.length which is why fractional _permutes were needed.
// One could scale all _permute operations by a factor of `steps`
// to only deal with integer operations, but this works well enough.
var steps = 4; // must be a power of 2
var step = 1 / steps; // 0.25 is exactly representable in floating point
var tt = new U.TreeTransformer(function(node, descend, in_list) {
if (CHANGED) return;
// quick ignores
if (node instanceof U.AST_Accessor) return;
if (node instanceof U.AST_Directive) 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();
// ensure that the _permute prop is a number.
// can not use `node.start._permute |= 0;` as it will erase fractional part.
if (typeof node.start._permute === "undefined") node.start._permute = 0;
// if node reached permutation limit - skip over it.
// no structural AST changes before this point.
if (node.start._permute >= REPLACEMENTS.length) return;
if (parent instanceof U.AST_Assign
&& parent.left === node
|| parent instanceof U.AST_Unary
&& parent.expression === node
&& ["++", "--", "delete"].indexOf(parent.operator) >= 0) {
// ignore lvalues
return;
}
if ((parent instanceof U.AST_For || parent instanceof U.AST_ForIn)
&& parent.init === node && node instanceof U.AST_Var) {
// preserve for (var ...)
return node;
}
// node specific permutations with no parent logic
if (node instanceof U.AST_Array) {
var expr = node.elements[0];
if (expr && !(expr instanceof U.AST_Hole)) {
node.start._permute++;
CHANGED = true;
return expr;
}
}
else if (node instanceof U.AST_Binary) {
CHANGED = true;
return [
node.left,
node.right,
][ ((node.start._permute += step) * steps | 0) % 2 ];
}
else if (node instanceof U.AST_Catch || node instanceof U.AST_Finally) {
// drop catch or finally block
node.start._permute++;
CHANGED = true;
return null;
}
else if (node instanceof U.AST_Conditional) {
CHANGED = true;
return [
node.condition,
node.consequent,
node.alternative,
][ ((node.start._permute += step) * steps | 0) % 3 ];
}
else if (node instanceof U.AST_BlockStatement) {
if (in_list) {
node.start._permute++;
CHANGED = true;
return List.splice(node.body);
}
}
else if (node instanceof U.AST_Call) {
var expr = [
node.expression,
node.args[0],
null, // intentional
][ ((node.start._permute += step) * steps | 0) % 3 ];
if (expr) {
CHANGED = true;
return expr;
}
if (node.expression instanceof U.AST_Function) {
// hoist and return expressions from the IIFE function expression
var body = node.expression.body;
node.expression.body = [];
var seq = [];
body.forEach(function(node) {
var expr = expr instanceof U.AST_Exit ? node.value : node.body;
if (expr instanceof U.AST_Node && !is_statement(expr)) {
// collect expressions from each statements' body
seq.push(expr);
}
});
CHANGED = true;
return to_sequence(seq);
}
}
else if (node instanceof U.AST_Defun) {
switch (((node.start._permute += step) * steps | 0) % 2) {
case 0:
CHANGED = true;
return List.skip;
case 1:
if (!has_exit(node)) {
// hoist function declaration body
var body = node.body;
node.body = [];
body.push(node); // retain function with empty body to be dropped later
CHANGED = true;
return List.splice(body);
}
}
}
else if (node instanceof U.AST_DWLoop) {
var expr = [
node.condition,
node.body,
null, // intentional
][ (node.start._permute * steps | 0) % 3 ];
node.start._permute += step;
if (!expr) {
if (node.body[0] instanceof U.AST_Break) {
if (node instanceof U.AST_Do) {
CHANGED = true;
return List.skip;
}
expr = node.condition; // AST_While - fall through
}
}
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true;
return to_statement(expr);
}
}
else if (node instanceof U.AST_PropAccess) {
var expr = [
node.expression,
node.property instanceof U.AST_Node && node.property,
][ node.start._permute++ % 2 ];
if (expr) {
CHANGED = true;
return expr;
}
}
else if (node instanceof U.AST_For) {
var expr = [
node.init,
node.condition,
node.step,
node.body,
][ (node.start._permute * steps | 0) % 4 ];
node.start._permute += step;
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true;
return to_statement(expr);
}
}
else if (node instanceof U.AST_ForIn) {
var expr = [
node.init,
node.object,
node.body,
][ (node.start._permute * steps | 0) % 3 ];
node.start._permute += step;
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
CHANGED = true;
return to_statement(expr);
}
}
else if (node instanceof U.AST_If) {
var expr = [
node.condition,
node.body,
node.alternative,
][ (node.start._permute * steps | 0) % 3 ];
node.start._permute += step;
if (expr) {
// replace if statement with its condition, then block or else block
CHANGED = true;
return to_statement(expr);
}
}
else if (node instanceof U.AST_Object) {
// first property's value
var expr = node.properties[0] instanceof U.AST_ObjectKeyVal && node.properties[0].value;
if (expr) {
node.start._permute++;
CHANGED = true;
return expr;
}
}
else if (node instanceof U.AST_SimpleStatement) {
if (node.body instanceof U.AST_Call && node.body.expression instanceof U.AST_Function) {
// hoist simple statement IIFE function expression body
node.start._permute++;
if (!has_exit(node.body.expression)) {
var body = node.body.expression.body;
node.body.expression.body = [];
CHANGED = true;
return List.splice(body);
}
}
}
else if (node instanceof U.AST_Switch) {
var expr = [
node.expression, // switch expression
node.body[0] && node.body[0].expression, // first case expression or undefined
node.body[0] && node.body[0], // first case body or undefined
][ (node.start._permute * steps | 0) % 4 ];
node.start._permute += step;
if (expr && (!(expr instanceof U.AST_Statement) || !has_loopcontrol(expr, node, parent))) {
CHANGED = true;
return expr instanceof U.AST_SwitchBranch ? new U.AST_BlockStatement({
body: expr.body.slice(),
start: {},
}) : to_statement(expr);
}
}
else if (node instanceof U.AST_Try) {
var body = [
node.body,
node.bcatch && node.bcatch.body,
node.bfinally && node.bfinally.body,
null, // intentional
][ (node.start._permute * steps | 0) % 4 ];
node.start._permute += step;
if (body) {
// replace try statement with try block, catch block, or finally block
CHANGED = true;
return new U.AST_BlockStatement({
body: body,
start: {},
});
} else {
// replace try with a break or return if first in try statement
if (node.body[0] instanceof U.AST_Break
|| node.body[0] instanceof U.AST_Return) {
CHANGED = true;
return node.body[0];
}
}
}
else if (node instanceof U.AST_Unary) {
node.start._permute++;
CHANGED = true;
return node.expression;
}
else if (node instanceof U.AST_Var) {
if (node.definitions.length == 1 && node.definitions[0].value) {
// first declaration value
node.start._permute++;
CHANGED = true;
return to_statement(node.definitions[0].value);
}
}
else if (node instanceof U.AST_LabeledStatement) {
if (node.body instanceof U.AST_Statement
&& !has_loopcontrol(node.body, node.body, node)) {
// replace labelled statement with its non-labelled body
node.start._permute = REPLACEMENTS.length;
CHANGED = true;
return node.body;
}
}
if (in_list) {
// special case to drop object properties and switch branches
if (parent instanceof U.AST_Object
|| parent instanceof U.AST_Switch && parent.expression != node) {
node.start._permute++;
CHANGED = true;
return List.skip;
}
// replace or skip statement
if (node instanceof U.AST_Statement) {
node.start._permute++;
CHANGED = true;
return List.skip;
}
// remove this node unless its the sole element of a (transient) sequence
if (!(parent instanceof U.AST_Sequence) || parent.expressions.length > 1) {
node.start._permute++;
CHANGED = true;
return List.skip;
}
}
// replace this node
var newNode = 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;
}, function(node, in_list) {
if (node instanceof U.AST_Sequence) {
// expand single-element sequence
if (node.expressions.length == 1) return node.expressions[0];
}
else if (node instanceof U.AST_Try) {
// expand orphaned try block
if (!node.bcatch && !node.bfinally) return new U.AST_BlockStatement({
body: node.body,
start: {},
});
}
else if (node instanceof U.AST_Var) {
// remove empty var statement
if (node.definitions.length == 0) return in_list ? List.skip : new U.AST_EmptyStatement({
start: {},
});
}
});
for (var pass = 1; pass <= 3; ++pass) {
var testcase_ast = U.parse(testcase);
testcase_ast.walk(new U.TreeWalker(function(node) {
// unshare start props to retain visit data between iterations
node.start = JSON.parse(JSON.stringify(node.start));
node.start._permute = 0;
}));
for (var c = 0; c < max_iterations; ++c) {
if (verbose) {
if (pass == 1 && c % 25 == 0) {
console.error("// reduce test pass "
+ pass + ", iteration " + c + ": " + testcase.length + " bytes");
}
}
var CHANGED = false;
var code_ast = testcase_ast.clone(true).transform(tt);
if (!CHANGED) break;
try {
var code = code_ast.print_to_string();
} catch (ex) {
// AST is not well formed.
// no harm done - just log the error, ignore latest change and continue iterating.
console.error("*** Error generating code from AST.");
console.error(ex);
console.error("*** Discarding permutation and continuing.");
continue;
}
var diff = producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout);
if (diff) {
if (diff.timed_out) {
// can't trust the validity of `code_ast` and `code` when timed out.
// no harm done - just ignore latest change and continue iterating.
} else if (diff.error) {
// something went wrong during minify() - could be malformed AST or genuine bug.
// no harm done - just log code & error, ignore latest change and continue iterating.
console.error("*** Error during minification.");
console.error(code);
console.error(diff.error);
console.error("*** Discarding permutation and continuing.");
} else if (is_error(diff.unminified_result)
&& is_error(diff.minified_result)
&& diff.unminified_result.name == diff.minified_result.name) {
// ignore difference in error messages caused by minification
} else {
// latest permutation is valid, so use it as the basis of new changes
testcase_ast = code_ast;
testcase = code;
differs = diff;
}
}
}
if (c == 0) break;
if (verbose) {
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, "");
return testcase;
}
};
function to_comment(value) {
return ("" + value).replace(/\n/g, "\n// ");
}
function has_exit(fn) {
var found = false;
var tw = new U.TreeWalker(function(node) {
if (found) return found;
if (node instanceof U.AST_Exit) {
return found = true;
}
if (node instanceof U.AST_Scope && node !== fn) {
return true; // don't descend into nested functions
}
});
fn.walk(tw);
return found;
}
function has_loopcontrol(body, loop, label) {
var found = false;
var tw = new U.TreeWalker(function(node) {
if (found) return true;
if (node instanceof U.AST_LoopControl && this.loopcontrol_target(node) === loop) {
return found = true;
}
});
if (label instanceof U.AST_LabeledStatement) tw.push(label);
tw.push(loop);
body.walk(tw);
return found;
}
function is_error(result) {
return typeof result == "object" && typeof result.name == "string" && typeof result.message == "string";
}
function is_timed_out(result) {
return is_error(result) && /timed out/.test(result);
}
function is_statement(node) {
return node instanceof U.AST_Statement && !(node instanceof U.AST_Function);
}
function merge_sequence(array, node) {
if (node instanceof U.AST_Sequence) {
array.push.apply(array, node.expressions);
} else {
array.push(node);
}
return array;
}
function to_sequence(expressions) {
if (expressions.length == 0) return new U.AST_Number({value: 0, start: {}});
if (expressions.length == 1) return expressions[0];
return new U.AST_Sequence({
expressions: expressions.reduce(merge_sequence, []),
start: {},
});
}
function to_statement(node) {
return is_statement(node) ? node : new U.AST_SimpleStatement({
body: node,
start: {},
});
}
function run_code(result_cache, code, toplevel, timeout) {
var key = crypto.createHash("sha1").update(code).digest("base64");
return result_cache[key] || (result_cache[key] = sandbox.run_code(code, toplevel, timeout));
}
function producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout) {
var minified = U.minify(code, minify_options);
if (minified.error) return minified;
var toplevel = minify_options.toplevel;
var elapsed = Date.now();
var unminified_result = run_code(result_cache, code, toplevel, max_timeout);
elapsed = Date.now() - elapsed;
var timeout = Math.min(100 * elapsed, max_timeout);
var minified_result = run_code(result_cache, minified.code, toplevel, timeout);
if (sandbox.same_stdout(unminified_result, minified_result)) {
return is_timed_out(unminified_result) && is_timed_out(minified_result) && {
timed_out: true,
};
}
return {
unminified_result: unminified_result,
minified_result: minified_result,
elapsed: elapsed,
};
}
Error.stackTraceLimit = Infinity;

View File

@@ -54,14 +54,15 @@ function createContext() {
}
}
exports.run_code = function(code, toplevel) {
exports.run_code = function(code, toplevel, timeout) {
timeout = timeout || 5000;
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: 5000 });
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: timeout });
return stdout;
} catch (ex) {
return ex;
@@ -76,8 +77,9 @@ function strip_func_ids(text) {
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
if (typeof expected != typeof actual) return false;
if (typeof expected != "string") {
if (expected.name != actual.name) return false;
if (typeof expected == "object" && typeof expected.name == "string" && typeof expected.message == "string") {
if (expected.name !== actual.name) return false;
if (typeof actual.message != "string") return false;
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
}

View File

@@ -10,6 +10,7 @@ require("../../tools/exit");
var UglifyJS = require("../..");
var randomBytes = require("crypto").randomBytes;
var sandbox = require("../sandbox");
var reduce_test = require("../reduce");
var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 12;
@@ -1051,6 +1052,7 @@ function log_rename(options) {
}
function log(options) {
var options_copy = JSON.parse(options);
options = JSON.parse(options);
if (!ok) errorln("\n\n\n\n\n\n!!!!!!!!!!\n\n\n");
errorln("//=============================================================");
@@ -1069,6 +1071,18 @@ function log(options) {
errorln(original_result);
errorln("uglified result:");
errorln(uglify_result);
errorln("//-------------------------------------------------------------");
var reduced = reduce_test(original_code, options_copy, {
verbose: false,
}).code;
if (reduced) {
errorln();
errorln("// reduced test case (output will differ)");
errorln();
errorln(reduced);
errorln();
errorln("//-------------------------------------------------------------");
}
} else {
errorln("// !!! uglify failed !!!");
errorln(uglify_code);

View File

@@ -1,4 +1,5 @@
exports["Dictionary"] = Dictionary;
exports["List"] = List;
exports["minify"] = minify;
exports["parse"] = parse;
exports["push_uniq"] = push_uniq;