Compare commits

...

17 Commits

Author SHA1 Message Date
Alex Lam S.L
b7f6b73f4e v2.8.21 2017-04-02 17:07:55 +08:00
Alex Lam S.L
9469c03ac9 fix corner case in switch (#1765) 2017-04-02 17:07:20 +08:00
Alex Lam S.L
d57527697f avoid confusion of NaN & Infinity with catch symbol of the same name (#1763)
fixes #1760
fixes #1761
2017-04-02 16:14:09 +08:00
Alex Lam S.L
f7ca4f2297 fix corner cases in switch and undefined (#1762)
- fix side effects in switch condition for singular blocks
- fix `undefined` confusion with local variable
- gate `OPT(AST_Switch)` with `switches`

fixes #1758
fixes #1759
2017-04-02 14:52:25 +08:00
Alex Lam S.L
c076e7b60d speed up fuzzer code generation (#1757)
- only output one top-level function or statement block
- reduce `rng()` granularity from 2^32 to 65536
- fix overflow in `rng()`
- track `canThrow` during `typeof` creation
2017-04-02 05:11:29 +08:00
Alex Lam S.L
4a55bb0be5 minor tweaks to test/ufuzz.js (#1756)
- count iterations from `1` instead of `0`
- remove `unsafe` from default set of `minify()` tests
- improve usability of help
2017-04-02 03:17:01 +08:00
Alex Lam S.L
28ecea50a6 upgrade fuzzer (#1754)
- configurable set of `minify()` options
- test and report suspects upon failure
- continue after failure if infinite iterations is specified
2017-04-02 02:10:50 +08:00
kzc
9a311705f5 fuzz regexp literals, more constant numbers, typeof expression (#1755) 2017-04-02 02:08:46 +08:00
Alex Lam S.L
ee3fe0f4cd fix switch branch elimination (#1752)
Merge unreachable case body with previous fallthrough case

fixes #1750
2017-04-01 17:19:57 +08:00
Alex Lam S.L
87f6e1b091 minor tweaks to fuzzer (#1751)
- remove `let` as variable name
- employ `crypto.randomBytes()`
2017-04-01 17:09:52 +08:00
Alex Lam S.L
c934fc8142 implement test/sandbox.js (#1749)
- `test/run-tests.js` and `test/ufuzz.js` now shares the same `run_code()` and `same_stdout()`
- re-enable fuzzer to generate top-level `NaN`, `Infinity` & `undefined`
- attempt to show beautified output only when `run_code()` output is preserved
2017-04-01 05:47:11 +08:00
Alex Lam S.L
257ddc3bdb improve compression of undefined, NaN & Infinitiy (#1748)
- migrate transformation logic from `OutputStream` to `Compressor`
- always turn `undefined` into `void 0` (unless `unsafe`)
- always keep `NaN` except when avoiding local variable redefinition
- introduce `keep_infinity` to suppress `1/0` transform, except when avoiding local variable redefinition

supersedes #1723
fixes #1730
2017-04-01 03:02:14 +08:00
Alex Lam S.L
1ddc05725d combine rules for binary boolean operations (#1744) 2017-03-31 18:47:44 +08:00
Peter van der Zee
e6b76a4879 Massive extension of the fuzzer (#1697)
Fix bug where a `throw` was generated without expression

Reenable try/catch/finally and fix them up

Skip serialization errors

Allow function decl in other funcs but not in blocks etc

Rename function to be more appropriate

Fix global functions not getting certain names

Make the canaries more likely to appear as expressions

Add a silly rounding edge case

Add a new canary, `c`, which should only ever be incremented

Refactoring

Fix (another) iife not actually being invoked

When a statement hits recursion max return an expression instead of `;`

When a expression hits recursion max also inc `c`

Generate global code as well as function code

Also fixes some argument juggling related bugs.
No longer reduces the recursion max when generating sub functions.
Generates a function arg.

Add used names to var name pool while in that scope

This is a little wonky, possibly a hack, but since it's synchronous code I think it's alright to do this. The alternative is to slice the varnames array and juggle them through almost all the generator functions and there are various reasons why this patch is a better alternative.

Minify generated code, not beautified code. Prevents beautifier bias.

Prevent unnecessary duplication

Remove serialization protection because I think it got handled elsewhere

Abstract toplevel code generation

Add example line of running test case

Add poor man options parser, and some options

Reindent to 4 spaces

Lower chance of `default` generation

Comment example of testing a case and output improvement

Enable `default` clause appearing at any clause index

Removing some training wheels; dont add parens where we dont absolutely need them

Support `-s1` and `-s2` to force specific statements being generated at that recursion level

Add round number to output when failing. For stats and fun and profit.

Solidify statement depth counting. The argument juggling is real.

Renamed option to something long. -scf was ugly and probably confusing.

Fix missing arguments causing `canThrow` to be truthy, generating crashing code

Generate more binary nested expressions

Add black and white list cli options for statement generation

Allows you to explicitly require or forbid certain statements from/to being made.

```
node test/ufuzz.js --without-stmt switch,try -t 5 -r 5 -V
```

```
node test/ufuzz.js --only-stmt ifelse,expr -t 5 -r 5 -V
```

Similar granularity for expression may be added later.

There can be no comma between names; it just does a split on that arg.

Trim down the binary expression generator

Prevent scoping issues in nodejs by preventing certain names in global space

Oh this list was incomplete?

Allow bin-expr to generate assignments too. More vigilant with storing and reusing vars.

Add more global builtin names

Update wrapper code

Also patch Function valueOf
2017-03-31 17:23:50 +08:00
Alex Lam S.L
a0c3836ba0 sort options in alphabetical order (#1743)
They started off as functional groups I guess, but given the sheer number of options this is becoming too difficult to read.
2017-03-31 16:41:04 +08:00
Alex Lam S.L
f8a71b56fd v2.8.20 2017-03-31 15:27:40 +08:00
Alex Lam S.L
11e9bdc427 fix missing preamble when shebang is absent (#1742) 2017-03-31 15:26:57 +08:00
24 changed files with 1376 additions and 667 deletions

View File

@@ -443,6 +443,9 @@ to set `true`; it's effectively a shortcut for `foo=true`).
integer argument larger than 1 to further reduce code size in some cases. integer argument larger than 1 to further reduce code size in some cases.
Note: raising the number of passes will increase uglify compress time. Note: raising the number of passes will increase uglify compress time.
- `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from
being compressed into `1/0`, which may cause performance issues on Chrome.
### The `unsafe` option ### The `unsafe` option
It enables some transformations that *might* break code logic in certain It enables some transformations that *might* break code logic in certain

View File

@@ -48,42 +48,44 @@ function Compressor(options, false_by_default) {
return new Compressor(options, false_by_default); return new Compressor(options, false_by_default);
TreeTransformer.call(this, this.before, this.after); TreeTransformer.call(this, this.before, this.after);
this.options = defaults(options, { this.options = defaults(options, {
sequences : !false_by_default, angular : false,
properties : !false_by_default, booleans : !false_by_default,
cascade : !false_by_default,
collapse_vars : !false_by_default,
comparisons : !false_by_default,
conditionals : !false_by_default,
dead_code : !false_by_default, dead_code : !false_by_default,
drop_console : false,
drop_debugger : !false_by_default, drop_debugger : !false_by_default,
evaluate : !false_by_default,
expression : false,
global_defs : {},
hoist_funs : !false_by_default,
hoist_vars : false,
if_return : !false_by_default,
join_vars : !false_by_default,
keep_fargs : true,
keep_fnames : false,
keep_infinity : false,
loops : !false_by_default,
negate_iife : !false_by_default,
passes : 1,
properties : !false_by_default,
pure_getters : false,
pure_funcs : null,
reduce_vars : !false_by_default,
screw_ie8 : true,
sequences : !false_by_default,
side_effects : !false_by_default,
switches : !false_by_default,
top_retain : null,
toplevel : !!(options && options["top_retain"]),
unsafe : false, unsafe : false,
unsafe_comps : false, unsafe_comps : false,
unsafe_math : false, unsafe_math : false,
unsafe_proto : false, unsafe_proto : false,
conditionals : !false_by_default,
comparisons : !false_by_default,
evaluate : !false_by_default,
booleans : !false_by_default,
loops : !false_by_default,
unused : !false_by_default, unused : !false_by_default,
toplevel : !!(options && options["top_retain"]),
top_retain : null,
hoist_funs : !false_by_default,
keep_fargs : true,
keep_fnames : false,
hoist_vars : false,
if_return : !false_by_default,
join_vars : !false_by_default,
collapse_vars : !false_by_default,
reduce_vars : !false_by_default,
cascade : !false_by_default,
side_effects : !false_by_default,
pure_getters : false,
pure_funcs : null,
negate_iife : !false_by_default,
screw_ie8 : true,
drop_console : false,
angular : false,
expression : false,
warnings : true, warnings : true,
global_defs : {},
passes : 1,
}, true); }, true);
var pure_funcs = this.options["pure_funcs"]; var pure_funcs = this.options["pure_funcs"];
if (typeof pure_funcs == "function") { if (typeof pure_funcs == "function") {
@@ -215,7 +217,12 @@ merge(Compressor.prototype, {
}) : make_node(AST_EmptyStatement, node); }) : make_node(AST_EmptyStatement, node);
} }
return make_node(AST_SimpleStatement, node, { return make_node(AST_SimpleStatement, node, {
body: node.value || make_node(AST_Undefined, node) body: node.value || make_node(AST_UnaryPrefix, node, {
operator: "void",
expression: make_node(AST_Number, node, {
value: 0
})
})
}); });
} }
if (node instanceof AST_Lambda && node !== self) { if (node instanceof AST_Lambda && node !== self) {
@@ -402,6 +409,18 @@ merge(Compressor.prototype, {
} }
}); });
function find_variable(compressor, name) {
var scope, i = 0;
while (scope = compressor.parent(i++)) {
if (scope instanceof AST_Scope) break;
if (scope instanceof AST_Catch) {
scope = scope.argname.definition().scope;
break;
}
}
return scope.find_variable(name);
}
function make_node(ctor, orig, props) { function make_node(ctor, orig, props) {
if (!props) props = {}; if (!props) props = {};
if (orig) { if (orig) {
@@ -1048,7 +1067,7 @@ merge(Compressor.prototype, {
stat.value = cons_seq(stat.value); stat.value = cons_seq(stat.value);
} }
else if (stat instanceof AST_Exit) { else if (stat instanceof AST_Exit) {
stat.value = cons_seq(make_node(AST_Undefined, stat)); stat.value = cons_seq(make_node(AST_Undefined, stat).transform(compressor));
} }
else if (stat instanceof AST_Switch) { else if (stat instanceof AST_Switch) {
stat.expression = cons_seq(stat.expression); stat.expression = cons_seq(stat.expression);
@@ -1123,8 +1142,12 @@ merge(Compressor.prototype, {
})); }));
}; };
function is_undefined(node) { function is_undefined(node, compressor) {
return node instanceof AST_Undefined || node.is_undefined; return node.is_undefined
|| node instanceof AST_Undefined
|| node instanceof AST_UnaryPrefix
&& node.operator == "void"
&& !node.expression.has_side_effects(compressor);
} }
/* -----[ boolean/negation helpers ]----- */ /* -----[ boolean/negation helpers ]----- */
@@ -1313,7 +1336,7 @@ merge(Compressor.prototype, {
return this; return this;
} }
}); });
var unaryPrefix = makePredicate("! ~ - +"); var unaryPrefix = makePredicate("! ~ - + void");
AST_Node.DEFMETHOD("is_constant", function(){ AST_Node.DEFMETHOD("is_constant", function(){
// Accomodate when compress option evaluate=false // Accomodate when compress option evaluate=false
// as well as the common constant expressions !0 and -1 // as well as the common constant expressions !0 and -1
@@ -2516,6 +2539,7 @@ merge(Compressor.prototype, {
}); });
OPT(AST_Switch, function(self, compressor){ OPT(AST_Switch, function(self, compressor){
if (!compressor.option("switches")) return self;
var branch; var branch;
var value = self.expression.evaluate(compressor); var value = self.expression.evaluate(compressor);
if (value !== self.expression) { if (value !== self.expression) {
@@ -2527,49 +2551,39 @@ merge(Compressor.prototype, {
var body = []; var body = [];
var default_branch; var default_branch;
var exact_match; var exact_match;
var fallthrough;
for (var i = 0, len = self.body.length; i < len && !exact_match; i++) { for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
branch = self.body[i]; branch = self.body[i];
if (branch instanceof AST_Default) { if (branch instanceof AST_Default) {
if (!default_branch) default_branch = branch; if (!default_branch) {
else if (!fallthrough) { default_branch = branch;
extract_declarations_from_unreachable_code(compressor, branch, decl); } else {
continue; eliminate_branch(branch, body[body.length - 1]);
} }
} else if (value !== self.expression) { } else if (value !== self.expression) {
var exp = branch.expression.evaluate(compressor); var exp = branch.expression.evaluate(compressor);
if (exp === value) { if (exp === value) {
exact_match = branch; exact_match = branch;
if (default_branch) { if (default_branch) {
body.splice(body.indexOf(default_branch), 1); var default_index = body.indexOf(default_branch);
extract_declarations_from_unreachable_code(compressor, default_branch, decl); body.splice(default_index, 1);
eliminate_branch(default_branch, body[default_index - 1]);
default_branch = null; default_branch = null;
} }
} else if (exp !== branch.expression && !fallthrough) { } else if (exp !== branch.expression) {
extract_declarations_from_unreachable_code(compressor, branch, decl); eliminate_branch(branch, body[body.length - 1]);
continue; continue;
} }
} }
if (aborts(branch)) { if (aborts(branch)) {
if (body.length > 0 && !fallthrough) {
var prev = body[body.length - 1]; var prev = body[body.length - 1];
if (prev.body.length == branch.body.length if (aborts(prev) && prev.body.length == branch.body.length
&& make_node(AST_BlockStatement, prev, prev).equivalent_to(make_node(AST_BlockStatement, branch, branch))) && make_node(AST_BlockStatement, prev, prev).equivalent_to(make_node(AST_BlockStatement, branch, branch))) {
prev.body = []; prev.body = [];
} }
}
body.push(branch); body.push(branch);
fallthrough = false;
} else {
body.push(branch);
fallthrough = true;
} }
} while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
for (; i < len && fallthrough; i++) {
branch = self.body[i];
exact_match.body = exact_match.body.concat(branch.body);
fallthrough = !aborts(exact_match);
}
while (i < len) extract_declarations_from_unreachable_code(compressor, self.body[i++], decl);
if (body.length > 0) { if (body.length > 0) {
body[0].body = decl.concat(body[0].body); body[0].body = decl.concat(body[0].body);
} }
@@ -2599,9 +2613,25 @@ merge(Compressor.prototype, {
has_break = true; has_break = true;
}); });
self.walk(tw); self.walk(tw);
if (!has_break) return make_node(AST_BlockStatement, self, body[0]).optimize(compressor); if (!has_break) {
body = body[0].body.slice();
body.unshift(make_node(AST_SimpleStatement, self.expression, {
body: self.expression
}));
return make_node(AST_BlockStatement, self, {
body: body
}).optimize(compressor);
}
} }
return self; return self;
function eliminate_branch(branch, prev) {
if (prev && !aborts(prev)) {
prev.body = prev.body.concat(branch.body);
} else {
extract_declarations_from_unreachable_code(compressor, branch, decl);
}
}
}); });
OPT(AST_Try, function(self, compressor){ OPT(AST_Try, function(self, compressor){
@@ -2895,7 +2925,7 @@ merge(Compressor.prototype, {
if (name instanceof AST_SymbolRef if (name instanceof AST_SymbolRef
&& name.name == "console" && name.name == "console"
&& name.undeclared()) { && name.undeclared()) {
return make_node(AST_Undefined, self).transform(compressor); return make_node(AST_Undefined, self).optimize(compressor);
} }
} }
} }
@@ -2971,7 +3001,7 @@ merge(Compressor.prototype, {
} }
} }
} }
if (is_undefined(self.cdr)) { if (is_undefined(self.cdr, compressor)) {
return make_node(AST_UnaryPrefix, self, { return make_node(AST_UnaryPrefix, self, {
operator : "void", operator : "void",
expression : self.car expression : self.car
@@ -3010,7 +3040,7 @@ merge(Compressor.prototype, {
self.expression = e; self.expression = e;
return self; return self;
} else { } else {
return make_node(AST_Undefined, self).transform(compressor); return make_node(AST_Undefined, self).optimize(compressor);
} }
} }
if (compressor.option("booleans") && compressor.in_boolean_context()) { if (compressor.option("booleans") && compressor.in_boolean_context()) {
@@ -3034,6 +3064,9 @@ merge(Compressor.prototype, {
})).optimize(compressor); })).optimize(compressor);
} }
} }
if (self.operator == "-" && e instanceof AST_Infinity) {
e = e.transform(compressor);
}
if (e instanceof AST_Binary if (e instanceof AST_Binary
&& (self.operator == "+" || self.operator == "-") && (self.operator == "+" || self.operator == "-")
&& (e.operator == "*" || e.operator == "/" || e.operator == "%")) { && (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
@@ -3043,8 +3076,7 @@ merge(Compressor.prototype, {
} }
// avoids infinite recursion of numerals // avoids infinite recursion of numerals
if (self.operator != "-" if (self.operator != "-"
|| !(self.expression instanceof AST_Number || !(e instanceof AST_Number || e instanceof AST_Infinity)) {
|| self.expression instanceof AST_Infinity)) {
var ev = self.evaluate(compressor); var ev = self.evaluate(compressor);
if (ev !== self) { if (ev !== self) {
ev = make_node_from_constant(ev, self).optimize(compressor); ev = make_node_from_constant(ev, self).optimize(compressor);
@@ -3087,8 +3119,8 @@ merge(Compressor.prototype, {
OPT(AST_Binary, function(self, compressor){ OPT(AST_Binary, function(self, compressor){
function reversible() { function reversible() {
return self.left instanceof AST_Constant return self.left.is_constant()
|| self.right instanceof AST_Constant || self.right.is_constant()
|| !self.left.has_side_effects(compressor) || !self.left.has_side_effects(compressor)
&& !self.right.has_side_effects(compressor); && !self.right.has_side_effects(compressor);
} }
@@ -3101,8 +3133,8 @@ merge(Compressor.prototype, {
} }
} }
if (commutativeOperators(self.operator)) { if (commutativeOperators(self.operator)) {
if (self.right instanceof AST_Constant if (self.right.is_constant()
&& !(self.left instanceof AST_Constant)) { && !self.left.is_constant()) {
// if right is a constant, whatever side effects the // if right is a constant, whatever side effects the
// left side might have could not influence the // left side might have could not influence the
// result. hence, force switch. // result. hence, force switch.
@@ -3140,42 +3172,7 @@ merge(Compressor.prototype, {
} }
break; break;
} }
if (compressor.option("booleans") && compressor.in_boolean_context()) switch (self.operator) { if (compressor.option("booleans") && self.operator == "+" && compressor.in_boolean_context()) {
case "&&":
var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor);
if (!ll || !rr) {
compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, {
car: self.left,
cdr: make_node(AST_False, self)
}).optimize(compressor);
}
if (ll !== self.left && ll) {
return self.right.optimize(compressor);
}
if (rr !== self.right && rr) {
return self.left.optimize(compressor);
}
break;
case "||":
var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor);
if (ll !== self.left && ll || rr !== self.right && rr) {
compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, {
car: self.left,
cdr: make_node(AST_True, self)
}).optimize(compressor);
}
if (!ll) {
return self.right.optimize(compressor);
}
if (!rr) {
return self.left.optimize(compressor);
}
break;
case "+":
var ll = self.left.evaluate(compressor); var ll = self.left.evaluate(compressor);
var rr = self.right.evaluate(compressor); var rr = self.right.evaluate(compressor);
if (ll && typeof ll == "string") { if (ll && typeof ll == "string") {
@@ -3192,7 +3189,6 @@ merge(Compressor.prototype, {
cdr: make_node(AST_True, self) cdr: make_node(AST_True, self)
}).optimize(compressor); }).optimize(compressor);
} }
break;
} }
if (compressor.option("comparisons") && self.is_boolean()) { if (compressor.option("comparisons") && self.is_boolean()) {
if (!(compressor.parent() instanceof AST_Binary) if (!(compressor.parent() instanceof AST_Binary)
@@ -3233,24 +3229,48 @@ merge(Compressor.prototype, {
if (compressor.option("evaluate")) { if (compressor.option("evaluate")) {
switch (self.operator) { switch (self.operator) {
case "&&": case "&&":
if (self.left.is_constant()) { var ll = self.left.evaluate(compressor);
if (self.left.constant_value(compressor)) { if (!ll) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.right);
} else {
compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start); compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.left); return maintain_this_binding(compressor.parent(), self, self.left).optimize(compressor);
} else if (ll !== self.left) {
compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.right).optimize(compressor);
}
if (compressor.option("booleans") && compressor.in_boolean_context()) {
var rr = self.right.evaluate(compressor);
if (!rr) {
compressor.warn("Boolean && always false [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, {
car: self.left,
cdr: make_node(AST_False, self)
}).optimize(compressor);
} else if (rr !== self.right) {
compressor.warn("Dropping side-effect-free && in boolean context [{file}:{line},{col}]", self.start);
return self.left.optimize(compressor);
} }
} }
break; break;
case "||": case "||":
if (self.left.is_constant()) { var ll = self.left.evaluate(compressor);
if (self.left.constant_value(compressor)) { if (!ll) {
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.left);
} else {
compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start); compressor.warn("Condition left of || always false [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.right); return maintain_this_binding(compressor.parent(), self, self.right).optimize(compressor);
} else if (ll !== self.left) {
compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
return maintain_this_binding(compressor.parent(), self, self.left).optimize(compressor);
}
if (compressor.option("booleans") && compressor.in_boolean_context()) {
var rr = self.right.evaluate(compressor);
if (!rr) {
compressor.warn("Dropping side-effect-free || in boolean context [{file}:{line},{col}]", self.start);
return self.left.optimize(compressor);
} else if (rr !== self.right) {
compressor.warn("Boolean || always true [{file}:{line},{col}]", self.start);
return make_node(AST_Seq, self, {
car: self.left,
cdr: make_node(AST_True, self)
}).optimize(compressor);
} }
} }
break; break;
@@ -3476,9 +3496,9 @@ merge(Compressor.prototype, {
case "undefined": case "undefined":
return make_node(AST_Undefined, self).optimize(compressor); return make_node(AST_Undefined, self).optimize(compressor);
case "NaN": case "NaN":
return make_node(AST_NaN, self); return make_node(AST_NaN, self).optimize(compressor);
case "Infinity": case "Infinity":
return make_node(AST_Infinity, self); return make_node(AST_Infinity, self).optimize(compressor);
} }
} }
if (compressor.option("evaluate") && compressor.option("reduce_vars")) { if (compressor.option("evaluate") && compressor.option("reduce_vars")) {
@@ -3508,19 +3528,48 @@ merge(Compressor.prototype, {
OPT(AST_Undefined, function(self, compressor){ OPT(AST_Undefined, function(self, compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var scope = compressor.find_parent(AST_Scope); var undef = find_variable(compressor, "undefined");
var undef = scope.find_variable("undefined");
if (undef) { if (undef) {
var ref = make_node(AST_SymbolRef, self, { var ref = make_node(AST_SymbolRef, self, {
name : "undefined", name : "undefined",
scope : scope, scope : undef.scope,
thedef : undef thedef : undef
}); });
ref.is_undefined = true; ref.is_undefined = true;
return ref; return ref;
} }
} }
return self; return make_node(AST_UnaryPrefix, self, {
operator: "void",
expression: make_node(AST_Number, self, {
value: 0
})
});
});
OPT(AST_Infinity, function(self, compressor){
var retain = compressor.option("keep_infinity") && !find_variable(compressor, "Infinity");
return retain ? self : make_node(AST_Binary, self, {
operator: "/",
left: make_node(AST_Number, self, {
value: 1
}),
right: make_node(AST_Number, self, {
value: 0
})
});
});
OPT(AST_NaN, function(self, compressor){
return find_variable(compressor, "NaN") ? make_node(AST_Binary, self, {
operator: "/",
left: make_node(AST_Number, self, {
value: 0
}),
right: make_node(AST_Number, self, {
value: 0
})
}) : self;
}); });
var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
@@ -3821,7 +3870,7 @@ merge(Compressor.prototype, {
OPT(AST_RegExp, literals_in_boolean_context); OPT(AST_RegExp, literals_in_boolean_context);
OPT(AST_Return, function(self, compressor){ OPT(AST_Return, function(self, compressor){
if (self.value && is_undefined(self.value)) { if (self.value && is_undefined(self.value, compressor)) {
self.value = null; self.value = null;
} }
return self; return self;

View File

@@ -53,26 +53,26 @@ function is_some_comments(comment) {
function OutputStream(options) { function OutputStream(options) {
options = defaults(options, { options = defaults(options, {
indent_start : 0,
indent_level : 4,
quote_keys : false,
space_colon : true,
ascii_only : false, ascii_only : false,
unescape_regexps : false,
inline_script : false,
width : 80,
max_line_len : false,
beautify : false, beautify : false,
source_map : null,
bracketize : false, bracketize : false,
semicolons : true,
comments : false, comments : false,
shebang : true, indent_level : 4,
preserve_line : false, indent_start : 0,
screw_ie8 : true, inline_script : false,
preamble : null,
quote_style : 0,
keep_quoted_props: false, keep_quoted_props: false,
max_line_len : false,
preamble : null,
preserve_line : false,
quote_keys : false,
quote_style : 0,
screw_ie8 : true,
semicolons : true,
shebang : true,
source_map : null,
space_colon : true,
unescape_regexps : false,
width : 80,
wrap_iife : false, wrap_iife : false,
}, true); }, true);
@@ -510,8 +510,8 @@ function OutputStream(options) {
})); }));
} }
if (comments.length > 0 && output.pos() == 0) { if (output.pos() == 0) {
if (output.option("shebang") && comments[0].type == "comment5") { if (comments.length > 0 && output.option("shebang") && comments[0].type == "comment5") {
output.print("#!" + comments.shift().value + "\n"); output.print("#!" + comments.shift().value + "\n");
output.indent(); output.indent();
} }
@@ -586,21 +586,12 @@ function OutputStream(options) {
return first_in_statement(output); return first_in_statement(output);
}); });
PARENS([ AST_Unary, AST_Undefined ], function(output){ PARENS(AST_Unary, function(output){
var p = output.parent(); var p = output.parent();
return p instanceof AST_PropAccess && p.expression === this return p instanceof AST_PropAccess && p.expression === this
|| p instanceof AST_Call && p.expression === this; || p instanceof AST_Call && p.expression === this;
}); });
PARENS([ AST_Infinity, AST_NaN ], function(output){
var p = output.parent();
return p instanceof AST_PropAccess && p.expression === this
|| p instanceof AST_Call && p.expression === this
|| p instanceof AST_Unary && p.operator != "+" && p.operator != "-"
|| p instanceof AST_Binary && p.right === this
&& (p.operator == "/" || p.operator == "%");
});
PARENS(AST_Seq, function(output){ PARENS(AST_Seq, function(output){
var p = output.parent(); var p = output.parent();
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4) return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
@@ -1258,24 +1249,7 @@ function OutputStream(options) {
var def = self.definition(); var def = self.definition();
output.print_name(def ? def.mangled_name || def.name : self.name); output.print_name(def ? def.mangled_name || def.name : self.name);
}); });
DEFPRINT(AST_Undefined, function(self, output){
output.print("void 0");
});
DEFPRINT(AST_Hole, noop); DEFPRINT(AST_Hole, noop);
DEFPRINT(AST_Infinity, function(self, output){
output.print("1");
output.space();
output.print("/");
output.space();
output.print("0");
});
DEFPRINT(AST_NaN, function(self, output){
output.print("0");
output.space();
output.print("/");
output.space();
output.print("0");
});
DEFPRINT(AST_This, function(self, output){ DEFPRINT(AST_This, function(self, output){
output.print("this"); output.print("this");
}); });

View File

@@ -688,14 +688,14 @@ var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "nam
function parse($TEXT, options) { function parse($TEXT, options) {
options = defaults(options, { options = defaults(options, {
strict : false,
filename : null,
toplevel : null,
expression : false,
html5_comments : true,
bare_returns : false, bare_returns : false,
shebang : true,
cli : false, cli : false,
expression : false,
filename : null,
html5_comments : true,
shebang : true,
strict : false,
toplevel : null,
}); });
var S = { var S = {

View File

@@ -62,12 +62,12 @@ function find_builtins() {
function mangle_properties(ast, options) { function mangle_properties(ast, options) {
options = defaults(options, { options = defaults(options, {
reserved : null, cache: null,
cache : null, debug: false,
only_cache : false, ignore_quoted: false,
regex : null, only_cache: false,
ignore_quoted : false, regex: null,
debug : false reserved: null,
}); });
var reserved = options.reserved; var reserved = options.reserved;

View File

@@ -92,8 +92,8 @@ SymbolDef.prototype = {
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
options = defaults(options, { options = defaults(options, {
cache: null,
screw_ie8: true, screw_ie8: true,
cache: null
}); });
// pass 1: setup scope chaining and handle definitions // pass 1: setup scope chaining and handle definitions
@@ -398,12 +398,12 @@ AST_Symbol.DEFMETHOD("global", function(){
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, { return defaults(options, {
except : [],
eval : false, eval : false,
except : [],
keep_fnames : false,
screw_ie8 : true,
sort : false, // Ignored. Flag retained for backwards compatibility. sort : false, // Ignored. Flag retained for backwards compatibility.
toplevel : false, toplevel : false,
screw_ie8 : true,
keep_fnames : false
}); });
}); });
@@ -576,12 +576,12 @@ var base54 = (function() {
AST_Toplevel.DEFMETHOD("scope_warnings", function(options){ AST_Toplevel.DEFMETHOD("scope_warnings", function(options){
options = defaults(options, { options = defaults(options, {
undeclared : false, // this makes a lot of noise
unreferenced : true,
assign_to_global : true, assign_to_global : true,
eval : true,
func_arguments : true, func_arguments : true,
nested_defuns : true, nested_defuns : true,
eval : true undeclared : false, // this makes a lot of noise
unreferenced : true,
}); });
var tw = new TreeWalker(function(node){ var tw = new TreeWalker(function(node){
if (options.undeclared if (options.undeclared

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"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": "2.8.19", "version": "2.8.21",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -840,8 +840,8 @@ equality_conditionals_false: {
f(0, true, 0), f(0, true, 0),
f(1, 2, 3), f(1, 2, 3),
f(1, null, 3), f(1, null, 3),
f(0/0), f(NaN),
f(0/0, "foo"); f(NaN, "foo");
} }
expect_stdout: true expect_stdout: true
} }
@@ -888,8 +888,8 @@ equality_conditionals_true: {
f(0, true, 0), f(0, true, 0),
f(1, 2, 3), f(1, 2, 3),
f(1, null, 3), f(1, null, 3),
f(0/0), f(NaN),
f(0/0, "foo"); f(NaN, "foo");
} }
expect_stdout: true expect_stdout: true
} }

View File

@@ -52,7 +52,7 @@ and: {
a = 7; a = 7;
a = false; a = false;
a = 0/0; a = NaN;
a = 0; a = 0;
a = void 0; a = void 0;
a = null; a = null;
@@ -67,7 +67,7 @@ and: {
a = 6 << condition && -4.5; a = 6 << condition && -4.5;
a = condition && false; a = condition && false;
a = console.log("b") && 0/0; a = console.log("b") && NaN;
a = console.log("c") && 0; a = console.log("c") && 0;
a = 2 * condition && void 0; a = 2 * condition && void 0;
a = condition + 3 && null; a = condition + 3 && null;
@@ -149,7 +149,7 @@ or: {
a = 6 << condition || -4.5; a = 6 << condition || -4.5;
a = condition || false; a = condition || false;
a = console.log("b") || 0/0; a = console.log("b") || NaN;
a = console.log("c") || 0; a = console.log("c") || 0;
a = 2 * condition || void 0; a = 2 * condition || void 0;
a = condition + 3 || null; a = condition + 3 || null;
@@ -533,7 +533,7 @@ unsafe_array: {
[1, 2, 3, a][0] + 1, [1, 2, 3, a][0] + 1,
2, 2,
3, 3,
0/0, NaN,
"1,21", "1,21",
5, 5,
(void 0)[1] + 1 (void 0)[1] + 1
@@ -802,3 +802,58 @@ issue_1649: {
} }
expect_stdout: "-2"; expect_stdout: "-2";
} }
issue_1760_1: {
options = {
evaluate: true,
}
input: {
!function(a) {
try {
throw 0;
} catch (NaN) {
a = +"foo";
}
console.log(a);
}();
}
expect: {
!function(a) {
try {
throw 0;
} catch (NaN) {
a = 0 / 0;
}
console.log(a);
}();
}
expect_stdout: "NaN"
}
issue_1760_2: {
options = {
evaluate: true,
keep_infinity: true,
}
input: {
!function(a) {
try {
throw 0;
} catch (Infinity) {
a = 123456789 / 0;
}
console.log(a);
}();
}
expect: {
!function(a) {
try {
throw 0;
} catch (Infinity) {
a = 1 / 0;
}
console.log(a);
}();
}
expect_stdout: "Infinity"
}

View File

@@ -193,6 +193,7 @@ assorted_Infinity_NaN_undefined_in_with_scope: {
cascade: true, cascade: true,
side_effects: true, side_effects: true,
sequences: false, sequences: false,
keep_infinity: false,
} }
input: { input: {
var f = console.log; var f = console.log;
@@ -224,10 +225,73 @@ assorted_Infinity_NaN_undefined_in_with_scope: {
}; };
if (o) { if (o) {
f(void 0, void 0); f(void 0, void 0);
f(0/0, 0/0); f(NaN, NaN);
f(1/0, 1/0); f(1/0, 1/0);
f(-1/0, -1/0); f(-1/0, -1/0);
f(0/0, 0/0); f(NaN, NaN);
}
with (o) {
f(undefined, void 0);
f(NaN, 0/0);
f(Infinity, 1/0);
f(-Infinity, -1/0);
f(9 + undefined, 9 + void 0);
}
}
expect_stdout: true
}
assorted_Infinity_NaN_undefined_in_with_scope_keep_infinity: {
options = {
unused: true,
evaluate: true,
dead_code: true,
conditionals: true,
comparisons: true,
booleans: true,
hoist_funs: true,
keep_fargs: true,
if_return: true,
join_vars: true,
cascade: true,
side_effects: true,
sequences: false,
keep_infinity: true,
}
input: {
var f = console.log;
var o = {
undefined : 3,
NaN : 4,
Infinity : 5,
};
if (o) {
f(undefined, void 0);
f(NaN, 0/0);
f(Infinity, 1/0);
f(-Infinity, -(1/0));
f(2 + 7 + undefined, 2 + 7 + void 0);
}
with (o) {
f(undefined, void 0);
f(NaN, 0/0);
f(Infinity, 1/0);
f(-Infinity, -(1/0));
f(2 + 7 + undefined, 2 + 7 + void 0);
}
}
expect: {
var f = console.log, o = {
undefined : 3,
NaN : 4,
Infinity : 5
};
if (o) {
f(void 0, void 0);
f(NaN, NaN);
f(Infinity, 1/0);
f(-Infinity, -1/0);
f(NaN, NaN);
} }
with (o) { with (o) {
f(undefined, void 0); f(undefined, void 0);

View File

@@ -154,12 +154,12 @@ should_warn: {
"WARN: Boolean || always true [test/compress/issue-1261.js:129,23]", "WARN: Boolean || always true [test/compress/issue-1261.js:129,23]",
"WARN: Dropping __PURE__ call [test/compress/issue-1261.js:129,23]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:129,23]",
"WARN: Condition always true [test/compress/issue-1261.js:129,23]", "WARN: Condition always true [test/compress/issue-1261.js:129,23]",
"WARN: Boolean || always true [test/compress/issue-1261.js:130,8]", "WARN: Condition left of || always true [test/compress/issue-1261.js:130,8]",
"WARN: Condition always true [test/compress/issue-1261.js:130,8]", "WARN: Condition always true [test/compress/issue-1261.js:130,8]",
"WARN: Boolean && always false [test/compress/issue-1261.js:131,23]", "WARN: Boolean && always false [test/compress/issue-1261.js:131,23]",
"WARN: Dropping __PURE__ call [test/compress/issue-1261.js:131,23]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:131,23]",
"WARN: Condition always false [test/compress/issue-1261.js:131,23]", "WARN: Condition always false [test/compress/issue-1261.js:131,23]",
"WARN: Boolean && always false [test/compress/issue-1261.js:132,8]", "WARN: Condition left of && always false [test/compress/issue-1261.js:132,8]",
"WARN: Condition always false [test/compress/issue-1261.js:132,8]", "WARN: Condition always false [test/compress/issue-1261.js:132,8]",
"WARN: + in boolean context always true [test/compress/issue-1261.js:133,23]", "WARN: + in boolean context always true [test/compress/issue-1261.js:133,23]",
"WARN: Dropping __PURE__ call [test/compress/issue-1261.js:133,23]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:133,23]",

View File

@@ -0,0 +1,54 @@
case_1: {
options = {
dead_code: true,
evaluate: true,
switches: true,
}
input: {
var a = 0, b = 1;
switch (true) {
case a, true:
default:
b = 2;
case true:
}
console.log(a, b);
}
expect: {
var a = 0, b = 1;
switch (true) {
case a, true:
b = 2;
}
console.log(a, b);
}
expect_stdout: "0 2"
}
case_2: {
options = {
dead_code: true,
evaluate: true,
switches: true,
}
input: {
var a = 0, b = 1;
switch (0) {
default:
b = 2;
case a:
a = 3;
case 0:
}
console.log(a, b);
}
expect: {
var a = 0, b = 1;
switch (0) {
case a:
a = 3;
}
console.log(a, b);
}
expect_stdout: "3 1"
}

View File

@@ -6,7 +6,7 @@ NaN_and_Infinity_must_have_parens: {
} }
expect: { expect: {
(1/0).toString(); (1/0).toString();
(0/0).toString(); NaN.toString();
} }
} }
@@ -24,6 +24,36 @@ NaN_and_Infinity_should_not_be_replaced_when_they_are_redefined: {
} }
} }
NaN_and_Infinity_must_have_parens_evaluate: {
options = {
evaluate: true,
}
input: {
(123456789 / 0).toString();
(+"foo").toString();
}
expect: {
(1/0).toString();
NaN.toString();
}
}
NaN_and_Infinity_should_not_be_replaced_when_they_are_redefined_evaluate: {
options = {
evaluate: true,
}
input: {
var Infinity, NaN;
(123456789 / 0).toString();
(+"foo").toString();
}
expect: {
var Infinity, NaN;
(1/0).toString();
(0/0).toString();
}
}
beautify_off_1: { beautify_off_1: {
options = { options = {
evaluate: true, evaluate: true,

View File

@@ -186,7 +186,7 @@ unary_binary_parenthesis: {
}); });
} }
expect: { expect: {
var v = [ 0, 1, 0/0, 1/0, null, void 0, true, false, "", "foo", /foo/ ]; var v = [ 0, 1, NaN, 1/0, null, void 0, true, false, "", "foo", /foo/ ];
v.forEach(function(x) { v.forEach(function(x) {
v.forEach(function(y) { v.forEach(function(y) {
console.log( console.log(

View File

@@ -77,7 +77,7 @@ sub_properties: {
a[3.14] = 3; a[3.14] = 3;
a.if = 4; a.if = 4;
a["foo bar"] = 5; a["foo bar"] = 5;
a[0/0] = 6; a[NaN] = 6;
a[null] = 7; a[null] = 7;
a[void 0] = 8; a[void 0] = 8;
} }

View File

@@ -1399,6 +1399,8 @@ issue_1670_1: {
evaluate: true, evaluate: true,
dead_code: true, dead_code: true,
reduce_vars: true, reduce_vars: true,
side_effects: true,
switches: true,
unused: true, unused: true,
} }
input: { input: {
@@ -1429,6 +1431,8 @@ issue_1670_2: {
dead_code: true, dead_code: true,
passes: 2, passes: 2,
reduce_vars: true, reduce_vars: true,
side_effects: true,
switches: true,
unused: true, unused: true,
} }
input: { input: {
@@ -1458,6 +1462,8 @@ issue_1670_3: {
evaluate: true, evaluate: true,
dead_code: true, dead_code: true,
reduce_vars: true, reduce_vars: true,
side_effects: true,
switches: true,
unused: true, unused: true,
} }
input: { input: {
@@ -1488,6 +1494,8 @@ issue_1670_4: {
dead_code: true, dead_code: true,
passes: 2, passes: 2,
reduce_vars: true, reduce_vars: true,
side_effects: true,
switches: true,
unused: true, unused: true,
} }
input: { input: {
@@ -1516,6 +1524,8 @@ issue_1670_5: {
evaluate: true, evaluate: true,
keep_fargs: false, keep_fargs: false,
reduce_vars: true, reduce_vars: true,
side_effects: true,
switches: true,
unused: true, unused: true,
} }
input: { input: {
@@ -1544,6 +1554,8 @@ issue_1670_6: {
evaluate: true, evaluate: true,
keep_fargs: false, keep_fargs: false,
reduce_vars: true, reduce_vars: true,
side_effects: true,
switches: true,
unused: true, unused: true,
} }
input: { input: {

View File

@@ -440,3 +440,29 @@ func_def_5: {
} }
expect_stdout: "true" expect_stdout: "true"
} }
issue_1758: {
options = {
sequences: true,
side_effects: true,
}
input: {
console.log(function(c) {
var undefined = 42;
return function() {
c--;
c--, c.toString();
return;
}();
}());
}
expect: {
console.log(function(c) {
var undefined = 42;
return function() {
return c--, c--, c.toString(), void 0;
}();
}());
}
expect_stdout: "undefined"
}

View File

@@ -1,5 +1,10 @@
constant_switch_1: { constant_switch_1: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
switch (1+1) { switch (1+1) {
case 1: foo(); break; case 1: foo(); break;
@@ -13,7 +18,12 @@ constant_switch_1: {
} }
constant_switch_2: { constant_switch_2: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
switch (1) { switch (1) {
case 1: foo(); case 1: foo();
@@ -28,7 +38,12 @@ constant_switch_2: {
} }
constant_switch_3: { constant_switch_3: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
switch (10) { switch (10) {
case 1: foo(); case 1: foo();
@@ -44,7 +59,12 @@ constant_switch_3: {
} }
constant_switch_4: { constant_switch_4: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
switch (2) { switch (2) {
case 1: case 1:
@@ -65,7 +85,12 @@ constant_switch_4: {
} }
constant_switch_5: { constant_switch_5: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
switch (1) { switch (1) {
case 1: case 1:
@@ -94,7 +119,12 @@ constant_switch_5: {
} }
constant_switch_6: { constant_switch_6: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
OUT: { OUT: {
foo(); foo();
@@ -123,7 +153,12 @@ constant_switch_6: {
} }
constant_switch_7: { constant_switch_7: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
OUT: { OUT: {
foo(); foo();
@@ -161,7 +196,12 @@ constant_switch_7: {
} }
constant_switch_8: { constant_switch_8: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
OUT: switch (1) { OUT: switch (1) {
case 1: case 1:
@@ -185,7 +225,12 @@ constant_switch_8: {
} }
constant_switch_9: { constant_switch_9: {
options = { dead_code: true, evaluate: true }; options = {
dead_code: true,
evaluate: true,
side_effects: true,
switches: true,
}
input: { input: {
OUT: switch (1) { OUT: switch (1) {
case 1: case 1:
@@ -210,7 +255,10 @@ constant_switch_9: {
} }
drop_default_1: { drop_default_1: {
options = { dead_code: true }; options = {
dead_code: true,
switches: true,
}
input: { input: {
switch (foo) { switch (foo) {
case 'bar': baz(); case 'bar': baz();
@@ -225,7 +273,10 @@ drop_default_1: {
} }
drop_default_2: { drop_default_2: {
options = { dead_code: true }; options = {
dead_code: true,
switches: true,
}
input: { input: {
switch (foo) { switch (foo) {
case 'bar': baz(); break; case 'bar': baz(); break;
@@ -241,7 +292,10 @@ drop_default_2: {
} }
keep_default: { keep_default: {
options = { dead_code: true }; options = {
dead_code: true,
switches: true,
}
input: { input: {
switch (foo) { switch (foo) {
case 'bar': baz(); case 'bar': baz();
@@ -263,6 +317,8 @@ issue_1663: {
options = { options = {
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true,
switches: true,
} }
input: { input: {
var a = 100, b = 10; var a = 100, b = 10;
@@ -294,6 +350,7 @@ issue_1663: {
drop_case: { drop_case: {
options = { options = {
dead_code: true, dead_code: true,
switches: true,
} }
input: { input: {
switch (foo) { switch (foo) {
@@ -312,6 +369,7 @@ drop_case: {
keep_case: { keep_case: {
options = { options = {
dead_code: true, dead_code: true,
switches: true,
} }
input: { input: {
switch (foo) { switch (foo) {
@@ -332,6 +390,7 @@ issue_376: {
options = { options = {
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
switches: true,
} }
input: { input: {
switch (true) { switch (true) {
@@ -354,6 +413,7 @@ issue_376: {
issue_441_1: { issue_441_1: {
options = { options = {
dead_code: true, dead_code: true,
switches: true,
} }
input: { input: {
switch (foo) { switch (foo) {
@@ -381,6 +441,7 @@ issue_441_1: {
issue_441_2: { issue_441_2: {
options = { options = {
dead_code: true, dead_code: true,
switches: true,
} }
input: { input: {
switch (foo) { switch (foo) {
@@ -414,6 +475,8 @@ issue_1674: {
options = { options = {
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
side_effects: true,
switches: true,
} }
input: { input: {
switch (0) { switch (0) {
@@ -435,6 +498,7 @@ issue_1679: {
options = { options = {
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
switches: true,
} }
input: { input: {
var a = 100, b = 10; var a = 100, b = 10;
@@ -482,6 +546,7 @@ issue_1680_1: {
options = { options = {
dead_code: true, dead_code: true,
evaluate: true, evaluate: true,
switches: true,
} }
input: { input: {
function f(x) { function f(x) {
@@ -522,6 +587,7 @@ issue_1680_1: {
issue_1680_2: { issue_1680_2: {
options = { options = {
dead_code: true, dead_code: true,
switches: true,
} }
input: { input: {
var a = 100, b = 10; var a = 100, b = 10;
@@ -557,6 +623,7 @@ issue_1680_2: {
issue_1690_1: { issue_1690_1: {
options = { options = {
dead_code: true, dead_code: true,
switches: true,
} }
input: { input: {
switch (console.log("PASS")) {} switch (console.log("PASS")) {}
@@ -570,6 +637,7 @@ issue_1690_1: {
issue_1690_2: { issue_1690_2: {
options = { options = {
dead_code: false, dead_code: false,
switches: true,
} }
input: { input: {
switch (console.log("PASS")) {} switch (console.log("PASS")) {}
@@ -585,6 +653,7 @@ if_switch_typeof: {
conditionals: true, conditionals: true,
dead_code: true, dead_code: true,
side_effects: true, side_effects: true,
switches: true,
} }
input: { input: {
if (a) switch(typeof b) {} if (a) switch(typeof b) {}
@@ -597,6 +666,7 @@ if_switch_typeof: {
issue_1698: { issue_1698: {
options = { options = {
side_effects: true, side_effects: true,
switches: true,
} }
input: { input: {
var a = 1; var a = 1;
@@ -618,6 +688,7 @@ issue_1698: {
issue_1705_1: { issue_1705_1: {
options = { options = {
dead_code: true, dead_code: true,
switches: true,
} }
input: { input: {
var a = 0; var a = 0;
@@ -646,6 +717,7 @@ issue_1705_2: {
reduce_vars: true, reduce_vars: true,
sequences: true, sequences: true,
side_effects: true, side_effects: true,
switches: true,
toplevel: true, toplevel: true,
unused: true, unused: true,
} }
@@ -666,6 +738,7 @@ issue_1705_2: {
issue_1705_3: { issue_1705_3: {
options = { options = {
dead_code: true, dead_code: true,
switches: true,
} }
input: { input: {
switch (a) { switch (a) {
@@ -721,3 +794,25 @@ beautify: {
"}", "}",
] ]
} }
issue_1758: {
options = {
dead_code: true,
switches: true,
}
input: {
var a = 1, b = 2;
switch (a--) {
default:
b++;
}
console.log(a, b);
}
expect: {
var a = 1, b = 2;
a--;
b++;
console.log(a, b);
}
expect_stdout: "0 3"
}

View File

@@ -79,5 +79,13 @@ describe("comment filters", function() {
output: { preamble: "/* Build */" } output: { preamble: "/* Build */" }
}).code; }).code;
assert.strictEqual(code, "#!/usr/bin/node\n/* Build */\nvar x=10;"); assert.strictEqual(code, "#!/usr/bin/node\n/* Build */\nvar x=10;");
}) });
it("Should handle preamble without shebang correctly", function() {
var code = UglifyJS.minify("var x = 10;", {
fromString: true,
output: { preamble: "/* Build */" }
}).code;
assert.strictEqual(code, "/* Build */\nvar x=10;");
});
}); });

View File

@@ -4,22 +4,11 @@ var U = require("../tools/node");
var path = require("path"); var path = require("path");
var fs = require("fs"); var fs = require("fs");
var assert = require("assert"); var assert = require("assert");
var vm = require("vm"); var sandbox = require("./sandbox");
var tests_dir = path.dirname(module.filename); var tests_dir = path.dirname(module.filename);
var failures = 0; var failures = 0;
var failed_files = {}; var failed_files = {};
var same_stdout = ~process.version.lastIndexOf("v0.12.", 0) ? function(expected, actual) {
if (typeof expected != typeof actual) return false;
if (typeof expected != "string") {
if (expected.name != actual.name) return false;
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
}
return expected == actual;
} : function(expected, actual) {
return typeof expected == typeof actual && expected.toString() == actual.toString();
};
run_compress_tests(); run_compress_tests();
if (failures) { if (failures) {
@@ -182,11 +171,11 @@ function run_compress_tests() {
} }
} }
if (test.expect_stdout) { if (test.expect_stdout) {
var stdout = run_code(input_code); var stdout = sandbox.run_code(input_code);
if (test.expect_stdout === true) { if (test.expect_stdout === true) {
test.expect_stdout = stdout; test.expect_stdout = stdout;
} }
if (!same_stdout(test.expect_stdout, stdout)) { if (!sandbox.same_stdout(test.expect_stdout, stdout)) {
log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", { log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_formatted, input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR", expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
@@ -197,8 +186,8 @@ function run_compress_tests() {
failures++; failures++;
failed_files[file] = 1; failed_files[file] = 1;
} else { } else {
stdout = run_code(output); stdout = sandbox.run_code(output);
if (!same_stdout(test.expect_stdout, stdout)) { if (!sandbox.same_stdout(test.expect_stdout, stdout)) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", { log("!!! failed\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_formatted, input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR", expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
@@ -330,19 +319,3 @@ function evaluate(code) {
code = make_code(code, { beautify: true }); code = make_code(code, { beautify: true });
return new Function("return(" + code + ")")(); return new Function("return(" + code + ")")();
} }
function run_code(code) {
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
new vm.Script(code).runInNewContext({ console: console }, { timeout: 5000 });
return stdout;
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
}
}

50
test/sandbox.js Normal file
View File

@@ -0,0 +1,50 @@
var vm = require("vm");
var FUNC_TOSTRING = [
"Function.prototype.toString = Function.prototype.valueOf = function() {",
" var ids = [];",
" return function() {",
" var i = ids.indexOf(this);",
" if (i < 0) {",
" i = ids.length;",
" ids.push(this);",
" }",
' return "[Function: __func_" + i + "__]";',
" }",
"}();",
""
].join("\n");
exports.run_code = function(code) {
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
new vm.Script(FUNC_TOSTRING + code).runInNewContext({
console: {
log: function() {
return console.log.apply(console, [].map.call(arguments, function(arg) {
return typeof arg == "function" ? arg.toString() : arg;
}));
}
}
}, { timeout: 30000 });
return stdout;
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
}
};
exports.same_stdout = ~process.version.lastIndexOf("v0.12.", 0) ? function(expected, actual) {
if (typeof expected != typeof actual) return false;
if (typeof expected != "string") {
if (expected.name != actual.name) return false;
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
}
return expected == actual;
} : function(expected, actual) {
return typeof expected == typeof actual && expected.toString() == actual.toString();
};

View File

@@ -2,30 +2,159 @@
// derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee // derived from https://github.com/qfox/uglyfuzzer by Peter van der Zee
"use strict"; "use strict";
// check both cli and file modes of nodejs (!). See #1695 for details. and the various settings of uglify.
// bin/uglifyjs s.js -c && bin/uglifyjs s.js -c passes=3 && bin/uglifyjs s.js -c passes=3 -m
// cat s.js | node && node s.js && bin/uglifyjs s.js -c | node && bin/uglifyjs s.js -c passes=3 | node && bin/uglifyjs s.js -c passes=3 -m | node
// workaround for tty output truncation upon process.exit() // workaround for tty output truncation upon process.exit()
[process.stdout, process.stderr].forEach(function(stream){ [process.stdout, process.stderr].forEach(function(stream){
if (stream._handle && stream._handle.setBlocking) if (stream._handle && stream._handle.setBlocking)
stream._handle.setBlocking(true); stream._handle.setBlocking(true);
}); });
var vm = require("vm"); var UglifyJS = require("..");
var minify = require("..").minify; var randomBytes = require("crypto").randomBytes;
var sandbox = require("./sandbox");
var MAX_GENERATED_FUNCTIONS_PER_RUN = 1; var MAX_GENERATED_TOPLEVELS_PER_RUN = 1;
var MAX_GENERATION_RECURSION_DEPTH = 15; var MAX_GENERATION_RECURSION_DEPTH = 12;
var INTERVAL_COUNT = 100; var INTERVAL_COUNT = 100;
var STMT_BLOCK = 0;
var STMT_IF_ELSE = 1;
var STMT_DO_WHILE = 2;
var STMT_WHILE = 3;
var STMT_FOR_LOOP = 4;
var STMT_SEMI = 5;
var STMT_EXPR = 6;
var STMT_SWITCH = 7;
var STMT_VAR = 8;
var STMT_RETURN_ETC = 9;
var STMT_FUNC_EXPR = 10;
var STMT_TRY = 11;
var STMT_C = 12;
var STMTS_TO_USE = [
STMT_BLOCK,
STMT_IF_ELSE,
STMT_DO_WHILE,
STMT_WHILE,
STMT_FOR_LOOP,
STMT_SEMI,
STMT_EXPR,
STMT_SWITCH,
STMT_VAR,
STMT_RETURN_ETC,
STMT_FUNC_EXPR,
STMT_TRY,
STMT_C,
];
var STMT_ARG_TO_ID = {
block: STMT_BLOCK,
ifelse: STMT_IF_ELSE,
dowhile: STMT_DO_WHILE,
while: STMT_WHILE,
forloop: STMT_FOR_LOOP,
semi: STMT_SEMI,
expr: STMT_EXPR,
switch: STMT_SWITCH,
var: STMT_VAR,
stop: STMT_RETURN_ETC,
funcexpr: STMT_FUNC_EXPR,
try: STMT_TRY,
c: STMT_C,
};
var STMT_FIRST_LEVEL_OVERRIDE = -1;
var STMT_SECOND_LEVEL_OVERRIDE = -1;
var STMT_COUNT_FROM_GLOBAL = true; // count statement depth from nearest function scope or just global scope?
var num_iterations = +process.argv[2] || 1/0;
var verbose = false; // log every generated test
var verbose_interval = false; // log every 100 generated tests
for (var i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) {
case '-v':
verbose = true;
break;
case '-V':
verbose_interval = true;
break;
case '-t':
MAX_GENERATED_TOPLEVELS_PER_RUN = +process.argv[++i];
if (!MAX_GENERATED_TOPLEVELS_PER_RUN) throw new Error('Must generate at least one toplevel per run');
break;
case '-r':
MAX_GENERATION_RECURSION_DEPTH = +process.argv[++i];
if (!MAX_GENERATION_RECURSION_DEPTH) throw new Error('Recursion depth must be at least 1');
break;
case '-s1':
var name = process.argv[++i];
STMT_FIRST_LEVEL_OVERRIDE = STMT_ARG_TO_ID[name];
if (!(STMT_FIRST_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list');
break;
case '-s2':
var name = process.argv[++i];
STMT_SECOND_LEVEL_OVERRIDE = STMT_ARG_TO_ID[name];
if (!(STMT_SECOND_LEVEL_OVERRIDE >= 0)) throw new Error('Unknown statement name; use -? to get a list');
break;
case '--stmt-depth-from-func':
STMT_COUNT_FROM_GLOBAL = false;
break;
case '--only-stmt':
STMTS_TO_USE = process.argv[++i].split(',').map(function(name){ return STMT_ARG_TO_ID[name]; });
break;
case '--without-stmt':
// meh. it runs once it's fine.
process.argv[++i].split(',').forEach(function(name){
var omit = STMT_ARG_TO_ID[name];
STMTS_TO_USE = STMTS_TO_USE.filter(function(id){ return id !== omit; })
});
break;
case '--help':
case '-h':
case '-?':
console.log('** UglifyJS fuzzer help **');
console.log('Valid options (optional):');
console.log('<number>: generate this many cases (if used must be first arg)');
console.log('-v: print every generated test case');
console.log('-V: print every 100th generated test case');
console.log('-t <int>: generate this many toplevels per run (more take longer)');
console.log('-r <int>: maximum recursion depth for generator (higher takes longer)');
console.log('-s1 <statement name>: force the first level statement to be this one (see list below)');
console.log('-s2 <statement name>: force the second level statement to be this one (see list below)');
console.log('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise');
console.log('--only-stmt <statement names>: a comma delimited white list of statements that may be generated');
console.log('--without-stmt <statement names>: a comma delimited black list of statements never to generate');
console.log('List of accepted statement names: ' + Object.keys(STMT_ARG_TO_ID));
console.log('** UglifyJS fuzzer exiting **');
return 0;
default:
// first arg may be a number.
if (i > 2 || !parseInt(process.argv[i], 10)) throw new Error('Unknown argument[' + process.argv[i] + ']; see -h for help');
}
}
var VALUES = [ var VALUES = [
'true', 'true',
'false', 'false',
'22', ' /[a2][^e]+$/ ',
'(-1)',
'(-2)',
'(-3)',
'(-4)',
'(-5)',
'0', '0',
'1',
'2',
'3',
'4',
'5',
'22',
'-0', // 0/-0 !== 0 '-0', // 0/-0 !== 0
'23..toString()', '23..toString()',
'24 .toString()', '24 .toString()',
'25. ', '25. ',
'0x26.toString()', '0x26.toString()',
'(-1)',
'NaN', 'NaN',
'undefined', 'undefined',
'Infinity', 'Infinity',
@@ -35,7 +164,12 @@ var VALUES = [
'([,0].length === 2)', // an array with elisions... this is always true '([,0].length === 2)', // an array with elisions... this is always true
'({})', // wrapped the object causes too many syntax errors in statements '({})', // wrapped the object causes too many syntax errors in statements
'"foo"', '"foo"',
'"bar"' ]; '"bar"',
'"undefined"',
'"object"',
'"number"',
'"function"',
];
var BINARY_OPS_NO_COMMA = [ var BINARY_OPS_NO_COMMA = [
' + ', // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors) ' + ', // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)
@@ -45,6 +179,14 @@ var BINARY_OPS_NO_COMMA = [
'&', '&',
'|', '|',
'^', '^',
'<',
'<=',
'>',
'>=',
'==',
'===',
'!=',
'!==',
'<<', '<<',
'>>', '>>',
'>>>', '>>>',
@@ -90,27 +232,48 @@ var UNARY_OPS = [
' + ' ]; ' + ' ];
var NO_COMMA = true; var NO_COMMA = true;
var COMMA_OK = false;
var MAYBE = true; var MAYBE = true;
var NESTED = true; var MANDATORY = false;
var CAN_THROW = true; var CAN_THROW = true;
var CANNOT_THROW = false; var CANNOT_THROW = false;
var CAN_BREAK = true; var CAN_BREAK = true;
var CANNOT_BREAK = false;
var CAN_CONTINUE = true; var CAN_CONTINUE = true;
var CANNOT_CONTINUE = false;
var CAN_RETURN = false;
var CANNOT_RETURN = true;
var NOT_GLOBAL = true;
var IN_GLOBAL = true;
var ANY_TYPE = false;
var NO_DECL = true;
var DONT_STORE = true;
var VAR_NAMES = [ var VAR_NAMES = [
'foo', 'foo',
'bar', 'bar',
'a', 'a',
'b', 'b',
'c', // prevent redeclaring this, avoid assigning to this
'undefined', // fun! 'undefined', // fun!
'eval', // mmmm, ok, also fun! 'eval', // mmmm, ok, also fun!
'NaN', // mmmm, ok, also fun! 'NaN', // mmmm, ok, also fun!
'Infinity', // the fun never ends! 'Infinity', // the fun never ends!
'arguments', // this one is just creepy 'arguments', // this one is just creepy
'Math', // since Math is assumed to be a non-constructor/function it may trip certain cases 'Math', // since Math is assumed to be a non-constructor/function it may trip certain cases
'let' ]; // maybe omit this, it's more a parser problem than minifier 'parseInt',
'parseFloat',
'isNaN',
'isFinite',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'Object'];
var INITIAL_NAMES_LEN = VAR_NAMES.length;
var TYPEOF_OUTCOMES = [ var TYPEOF_OUTCOMES = [
'function',
'undefined', 'undefined',
'string', 'string',
'number', 'number',
@@ -121,158 +284,169 @@ var TYPEOF_OUTCOMES = [
'symbol', 'symbol',
'crap' ]; 'crap' ];
var FUNC_TOSTRING = [ var loops = 0;
"Function.prototype.toString = function() {", var funcs = 0;
" var ids = [];",
" return function() {",
" var i = ids.indexOf(this);",
" if (i < 0) {",
" i = ids.length;",
" ids.push(this);",
" }",
' return "[Function: __func_" + i + "__]";',
" }",
"}();",
""
].join("\n");
function run_code(code) {
var stdout = "";
var original_write = process.stdout.write;
process.stdout.write = function(chunk) {
stdout += chunk;
};
try {
new vm.Script(FUNC_TOSTRING + code).runInNewContext({
console: {
log: function() {
return console.log.apply(console, [].map.call(arguments, function(arg) {
return typeof arg == "function" ? "[Function]" : arg;
}));
}
}
}, { timeout: 5000 });
return stdout;
} catch (ex) {
return ex;
} finally {
process.stdout.write = original_write;
}
}
function rng(max) { function rng(max) {
return Math.floor(max * Math.random()); var r = randomBytes(2).readUInt16LE(0) / 65536;
return Math.floor(max * r);
} }
function createFunctionDecls(n, recurmax, nested) { function createTopLevelCode() {
if (rng(2) === 0) return createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0);
return createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, IN_GLOBAL, ANY_TYPE, CANNOT_THROW, 0);
}
function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
if (--recurmax < 0) { return ';'; } if (--recurmax < 0) { return ';'; }
var s = ''; var s = '';
while (n-- > 0) { while (n-- > 0) {
s += createFunctionDecl(recurmax, nested) + '\n'; s += createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) + '\n';
} }
return s; return s;
} }
var funcs = 0; function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
function createFunctionDecl(recurmax, nested) {
if (--recurmax < 0) { return ';'; } if (--recurmax < 0) { return ';'; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
var func = funcs++; var func = funcs++;
var name = rng(5) > 0 ? 'f' + func : createVarName(); var namesLenBefore = VAR_NAMES.length;
if (name === 'a' || name === 'b') name = 'f' + func; // quick hack to prevent assignment to func names of being called var name = (inGlobal || rng(5) > 0) ? 'f' + func : createVarName(MANDATORY, noDecl);
if (!nested && name === 'undefined' || name === 'NaN' || name === 'Infinity') name = 'f' + func; // cant redefine these in global space if (name === 'a' || name === 'b' || name === 'c') name = 'f' + func; // quick hack to prevent assignment to func names of being called
var s = ''; var s = '';
if (rng(5) === 1) { if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess. // functions with functions. lower the recursion to prevent a mess.
s = 'function ' + name + '(){' + createFunctionDecls(rng(5) + 1, Math.ceil(recurmax / 2), NESTED) + '}\n'; s = 'function ' + name + '(' + createVarName(MANDATORY) + '){' + createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth) + '}\n';
} else { } else {
// functions with statements // functions with statements
s = 'function ' + name + '(){' + createStatements(3, recurmax) + '}\n'; s = 'function ' + name + '(' + createVarName(MANDATORY) + '){' + createStatements(3, recurmax, canThrow, CANNOT_THROW, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}\n';
} }
if (nested) s = '!' + nested; // avoid "function statements" (decl inside statements) VAR_NAMES.length = namesLenBefore;
else s += name + '();'
if (noDecl) s = '!' + s + '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
// avoid "function statements" (decl inside statements)
else if (inGlobal || rng(10) > 0) s += name + '();'
return s; return s;
} }
function createStatements(n, recurmax, canThrow, canBreak, canContinue) { function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
if (--recurmax < 0) { return ';'; } if (--recurmax < 0) { return ';'; }
var s = ''; var s = '';
while (--n > 0) { while (--n > 0) {
s += createStatement(recurmax, canThrow, canBreak, canContinue); s += createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '\n';
} }
return s; return s;
} }
var loops = 0; function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
function createStatement(recurmax, canThrow, canBreak, canContinue) { ++stmtDepth;
var loop = ++loops; var loop = ++loops;
if (--recurmax < 0) { return ';'; } if (--recurmax < 0) {
switch (rng(16)) { return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';';
case 0: }
return '{' + createStatements(rng(5) + 1, recurmax, canThrow, canBreak, canContinue) + '}';
case 1: // allow to forcefully generate certain structures at first or second recursion level
return 'if (' + createExpression(recurmax) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue) : ''); var target = 0;
case 2: if (stmtDepth === 1 && STMT_FIRST_LEVEL_OVERRIDE >= 0) target = STMT_FIRST_LEVEL_OVERRIDE;
return '{var brake' + loop + ' = 5; do {' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE) + '} while ((' + createExpression(recurmax) + ') && --brake' + loop + ' > 0);}'; else if (stmtDepth === 2 && STMT_SECOND_LEVEL_OVERRIDE >= 0) target = STMT_SECOND_LEVEL_OVERRIDE;
case 3: else target = STMTS_TO_USE[rng(STMTS_TO_USE.length)];
return '{var brake' + loop + ' = 5; while ((' + createExpression(recurmax) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE) + '}';
case 4: switch (target) {
return 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE); case STMT_BLOCK:
case 5: return '{' + createStatements(rng(5) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}';
case STMT_IF_ELSE:
return 'if (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? ' else ' + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : '');
case STMT_DO_WHILE:
return '{var brake' + loop + ' = 5; do {' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth) + '} while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0);}';
case STMT_WHILE:
return '{var brake' + loop + ' = 5; while ((' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && --brake' + loop + ' > 0)' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth) + '}';
case STMT_FOR_LOOP:
return 'for (var brake' + loop + ' = 5; (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') && brake' + loop + ' > 0; --brake' + loop + ')' + createStatement(recurmax, canThrow, CAN_BREAK, CAN_CONTINUE, cannotReturn, stmtDepth);
case STMT_SEMI:
return ';'; return ';';
case 6: case STMT_EXPR:
return createExpression(recurmax) + ';'; return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';';
case 7: case STMT_SWITCH:
// note: case args are actual expressions // note: case args are actual expressions
// note: default does not _need_ to be last // note: default does not _need_ to be last
return 'switch (' + createExpression(recurmax) + ') { ' + createSwitchParts(recurmax, 4) + '}'; return 'switch (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ') { ' + createSwitchParts(recurmax, 4, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + '}';
case 8: case STMT_VAR:
return 'var ' + createVarName() + ';'; switch (rng(3)) {
case 9: case 0:
var name = createVarName(MANDATORY);
if (name === 'c') name = 'a';
return 'var ' + name + ';';
case 1:
// initializer can only have one expression // initializer can only have one expression
return 'var ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ';'; var name = createVarName(MANDATORY);
case 10: if (name === 'c') name = 'b';
return 'var ' + name + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
default:
// initializer can only have one expression // initializer can only have one expression
return 'var ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ', ' + createVarName() + ' = ' + createExpression(recurmax, NO_COMMA) + ';'; var n1 = createVarName(MANDATORY);
case 11: if (n1 === 'c') n1 = 'b';
var n2 = createVarName(MANDATORY);
if (n2 === 'c') n2 = 'b';
return 'var ' + n1 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ', ' + n2 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
}
case STMT_RETURN_ETC:
switch (rng(3)) {
case 1:
if (canBreak && rng(5) === 0) return 'break;'; if (canBreak && rng(5) === 0) return 'break;';
if (canContinue && rng(5) === 0) return 'continue;'; if (canContinue && rng(5) === 0) return 'continue;';
return 'return;'; if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
case 12: return '/*3*/return;';
case 2:
// must wrap in curlies to prevent orphaned `else` statement // must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax) + '}'; if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
return '{ return ' + createExpression(recurmax) + '}'; if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
case 13: return '{ /*1*/ return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
default:
// this is actually more like a parser test, but perhaps it hits some dead code elimination traps // this is actually more like a parser test, but perhaps it hits some dead code elimination traps
// must wrap in curlies to prevent orphaned `else` statement // must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw\n' + createExpression(recurmax) + '}'; // note: you can't `throw` without an expression so don't put a `throw` option in this case
return '{ return\n' + createExpression(recurmax) + '}'; if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
case 14: return '{ /*2*/ return\n' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
}
case STMT_FUNC_EXPR:
// "In non-strict mode code, functions can only be declared at top level, inside a block, or ..." // "In non-strict mode code, functions can only be declared at top level, inside a block, or ..."
// (dont both with func decls in `if`; it's only a parser thing because you cant call them without a block) // (dont both with func decls in `if`; it's only a parser thing because you cant call them without a block)
return '{' + createFunctionDecl(recurmax, NESTED) + '}'; return '{' + createFunction(recurmax, NOT_GLOBAL, NO_DECL, canThrow, stmtDepth) + '}';
case 15: case STMT_TRY:
return ';';
// catch var could cause some problems // catch var could cause some problems
// note: the "blocks" are syntactically mandatory for try/catch/finally // note: the "blocks" are syntactically mandatory for try/catch/finally
var s = 'try {' + createStatement(recurmax, CAN_THROW, canBreak, canContinue) + ' }';
var n = rng(3); // 0=only catch, 1=only finally, 2=catch+finally var n = rng(3); // 0=only catch, 1=only finally, 2=catch+finally
if (n !== 1) s += ' catch (' + createVarName() + ') { ' + createStatements(3, recurmax, canBreak, canContinue) + ' }'; var s = 'try {' + createStatement(recurmax, n === 1 ? CANNOT_THROW : CAN_THROW, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canBreak, canContinue) + ' }'; if (n !== 1) {
// the catch var should only be accessible in the catch clause...
// we have to do go through some trouble here to prevent leaking it
var nameLenBefore = VAR_NAMES.length;
var catchName = createVarName(MANDATORY);
var freshCatchName = VAR_NAMES.length !== nameLenBefore;
s += ' catch (' + catchName + ') { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
if (freshCatchName) VAR_NAMES.splice(nameLenBefore, 1); // remove catch name
}
if (n !== 0) s += ' finally { ' + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + ' }';
return s; return s;
case STMT_C:
return 'c = c + 1;';
default:
throw 'no';
} }
} }
function createSwitchParts(recurmax, n) { function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) {
var hadDefault = false; var hadDefault = false;
var s = ''; var s = '';
while (n-- > 0) { while (n-- > 0) {
hadDefault = n > 0; //hadDefault = n > 0; // disables weird `default` clause positioning (use when handling destabilizes)
if (hadDefault || rng(4) > 0) { if (hadDefault || rng(5) > 0) {
s += '' + s += '' +
'case ' + createExpression(recurmax) + ':\n' + 'case ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':\n' +
createStatements(rng(3) + 1, recurmax, CANNOT_THROW, CAN_BREAK) + createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) +
'\n' + '\n' +
(rng(10) > 0 ? ' break;' : '/* fall-through */') + (rng(10) > 0 ? ' break;' : '/* fall-through */') +
'\n'; '\n';
@@ -280,64 +454,84 @@ function createSwitchParts(recurmax, n) {
hadDefault = true; hadDefault = true;
s += '' + s += '' +
'default:\n' + 'default:\n' +
createStatements(rng(3) + 1, recurmax, CANNOT_THROW, CAN_BREAK) + createStatements(rng(3) + 1, recurmax, canThrow, CAN_BREAK, canContinue, cannotReturn, stmtDepth) +
'\n'; '\n';
} }
} }
return s; return s;
} }
function createExpression(recurmax, noComma) { function createExpression(recurmax, noComma, stmtDepth, canThrow) {
if (--recurmax < 0) { if (--recurmax < 0) {
return createValue(); // note: should return a simple non-recursing expression value! return '(c = 1 + c, ' + createNestedBinaryExpr(recurmax, noComma) + ')'; // note: should return a simple non-recursing expression value!
} }
switch (rng(12)) { // since `a` and `b` are our canaries we want them more frequently than other expressions (1/3rd chance of a canary)
var r = rng(6);
if (r < 1) return 'a++ + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow);
if (r < 2) return '(--b) + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow);
if (r < 3) return '(c = c + 1) + ' + _createExpression(recurmax, noComma, stmtDepth, canThrow); // c only gets incremented
return _createExpression(recurmax, noComma, stmtDepth, canThrow);
}
function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
switch (rng(15)) {
case 0: case 0:
return '(' + createUnaryOp() + (rng(2) === 1 ? 'a' : 'b') + ')'; return createUnaryOp() + (rng(2) === 1 ? 'a' : 'b');
case 1: case 1:
return '(a' + (rng(2) == 1 ? '++' : '--') + ')'; return 'a' + (rng(2) == 1 ? '++' : '--');
case 2: case 2:
// parens needed because assignments aren't valid unless they're the left-most op(s) in an expression
return '(b ' + createAssignment() + ' a)'; return '(b ' + createAssignment() + ' a)';
case 3: case 3:
return '(' + rng(2) + ' === 1 ? a : b)'; return rng(2) + ' === 1 ? a : b';
case 4: case 4:
return createExpression(recurmax, noComma) + createBinaryOp(noComma) + createExpression(recurmax, noComma); return createNestedBinaryExpr(recurmax, noComma) + createBinaryOp(noComma) + createExpression(recurmax, noComma, stmtDepth, canThrow);
case 5: case 5:
return createValue(); return createValue();
case 6: case 6:
return '(' + createExpression(recurmax) + ')'; return '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
case 7: case 7:
return createExpression(recurmax, noComma) + '?(' + createExpression(recurmax) + '):(' + createExpression(recurmax) + ')'; return createExpression(recurmax, noComma, stmtDepth, canThrow) + '?' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':' + createExpression(recurmax, noComma, stmtDepth, canThrow);
case 8: case 8:
var nameLenBefore = VAR_NAMES.length;
var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that.
if (name === 'c') name = 'a';
var s = '';
switch(rng(4)) { switch(rng(4)) {
case 0: case 0:
return '(function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '})()'; s = '(function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '})()';
break;
case 1: case 1:
return '+function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}'; s = '+function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()';
break;
case 2: case 2:
return '!function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}'; s = '!function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()';
case 3: break;
return 'void function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}';
default: default:
return 'void function ' + createVarName(MAYBE) + '(){' + createStatements(rng(5) + 1, recurmax) + '}'; s = 'void function ' + name + '(){' + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}()';
break;
} }
VAR_NAMES.length = nameLenBefore;
return s;
case 9: case 9:
return createTypeofExpr(recurmax); return createTypeofExpr(recurmax, stmtDepth, canThrow);
case 10: case 10:
// you could statically infer that this is just `Math`, regardless of the other expression // you could statically infer that this is just `Math`, regardless of the other expression
// I don't think Uglify does this at this time... // I don't think Uglify does this at this time...
return ''+ return ''+
'new function(){ \n' + 'new function(){ \n' +
(rng(2) === 1 ? createExpression(recurmax) + '\n' : '') + (rng(2) === 1 ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '\n' : '') +
'return Math;\n' + 'return Math;\n' +
'}'; '}';
case 11: case 11:
// more like a parser test but perhaps comment nodes mess up the analysis? // more like a parser test but perhaps comment nodes mess up the analysis?
switch (rng(5)) { // note: parens not needed for post-fix (since that's the default when ambiguous)
// for prefix ops we need parens to prevent accidental syntax errors.
switch (rng(6)) {
case 0: case 0:
return '(a/* ignore */++)'; return 'a/* ignore */++';
case 1: case 1:
return '(b/* ignore */--)'; return 'b/* ignore */--';
case 2: case 2:
return '(++/* ignore */a)'; return '(++/* ignore */a)';
case 3: case 3:
@@ -346,28 +540,53 @@ function createExpression(recurmax, noComma) {
// only groups that wrap a single variable return a "Reference", so this is still valid. // only groups that wrap a single variable return a "Reference", so this is still valid.
// may just be a parser edge case that is invisible to uglify... // may just be a parser edge case that is invisible to uglify...
return '(--(b))'; return '(--(b))';
case 5:
// classic 0.3-0.1 case; 1-0.1-0.1-0.1 is not 0.7 :)
return 'b + 1-0.1-0.1-0.1';
default: default:
return '(--/* ignore */b)'; return '(--/* ignore */b)';
} }
case 12:
return createNestedBinaryExpr(recurmax, noComma);
case 13:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
case 14:
return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
} }
} }
function createTypeofExpr(recurmax) { function createNestedBinaryExpr(recurmax, noComma) {
if (--recurmax < 0) { recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it
return 'typeof undefined === "undefined"'; return _createSimpleBinaryExpr(recurmax, noComma);
}
function _createSimpleBinaryExpr(recurmax, noComma) {
// intentionally generate more hardcore ops
if (--recurmax < 0) return createValue();
var r = rng(30);
if (r === 0) return '(c = c + 1, ' + _createSimpleBinaryExpr(recurmax, noComma) + ')';
var s = _createSimpleBinaryExpr(recurmax, noComma) + createBinaryOp(noComma) + _createSimpleBinaryExpr(recurmax, noComma);
if (r === 1) {
// try to get a generated name reachable from current scope. default to just `a`
var assignee = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || 'a';
return '( ' + assignee + createAssignment() + s + ')';
} }
return s;
}
switch (rng(5)) { function createTypeofExpr(recurmax, stmtDepth, canThrow) {
switch (rng(8)) {
case 0: case 0:
return '(typeof ' + createVarName() + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")'; return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"';
case 1: case 1:
return '(typeof ' + createVarName() + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")'; return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"';
case 2: case 2:
return '(typeof ' + createVarName() + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")'; return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"';
case 3: case 3:
return '(typeof ' + createVarName() + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")'; return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"';
case 4: case 4:
return '(typeof ' + createVarName() + ')'; return 'typeof ' + createVarName(MANDATORY, DONT_STORE);
default:
return '(typeof ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
} }
} }
@@ -388,90 +607,154 @@ function createUnaryOp() {
return UNARY_OPS[rng(UNARY_OPS.length)]; return UNARY_OPS[rng(UNARY_OPS.length)];
} }
function createVarName(maybe) { function createVarName(maybe, dontStore) {
if (!maybe || rng(2) === 1) { if (!maybe || rng(2) === 1) {
return VAR_NAMES[rng(VAR_NAMES.length)] + (rng(5) > 0 ? ++loops : ''); var r = rng(VAR_NAMES.length);
var suffixed = rng(5) > 0;
var name = VAR_NAMES[r] + (suffixed ? '_' + (++loops) : '');
if (!dontStore && suffixed) VAR_NAMES.push(name);
return name;
} }
return ''; return '';
} }
function log(ok) { function try_beautify(code, result) {
console.log("//=============================================================");
if (!ok) console.log("// !!!!!! Failed...");
console.log("// original code");
console.log("//");
console.log(original_code);
console.log();
console.log();
console.log("//-------------------------------------------------------------");
console.log("// original code (beautify'd)");
console.log("//");
console.log(beautify_code);
console.log();
console.log();
console.log("//-------------------------------------------------------------");
console.log("// uglified code");
console.log("//");
console.log(uglify_code);
console.log();
console.log();
console.log("original result:");
console.log(original_result);
console.log("beautified result:");
console.log(beautify_result);
console.log("uglified result:");
console.log(uglify_result);
if (!ok) console.log("!!!!!! Failed...");
}
var num_iterations = +process.argv[2] || 1/0;
var verbose = process.argv[3] === 'v' || process.argv[2] === 'v';
var verbose_interval = process.argv[3] === 'V' || process.argv[2] === 'V';
for (var round = 0; round < num_iterations; round++) {
var parse_error = false;
process.stdout.write(round + " of " + num_iterations + "\r");
var original_code = [
"var a = 100, b = 10;",
createFunctionDecls(rng(MAX_GENERATED_FUNCTIONS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH),
"console.log(a, b);"
].join("\n");
var original_result = run_code(original_code);
try { try {
var beautify_code = minify(original_code, { var beautified = UglifyJS.minify(code, {
fromString: true, fromString: true,
mangle: false,
compress: false, compress: false,
mangle: false,
output: { output: {
beautify: true, beautify: true,
bracketize: true, bracketize: true,
}, },
}).code; }).code;
} catch(e) { if (sandbox.same_stdout(sandbox.run_code(beautified), result)) {
parse_error = 1; console.log("// (beautified)");
console.log(beautified);
return;
} }
var beautify_result = run_code(beautify_code); } catch (e) {
console.log("// !!! beautify failed !!!");
try { console.log(e.stack);
var uglify_code = minify(beautify_code, {
fromString: true,
mangle: true,
compress: {
passes: 3,
},
output: {
//beautify: true,
//bracketize: true,
},
}).code;
} catch(e) {
parse_error = 2;
} }
var uglify_result = run_code(uglify_code); console.log("//");
console.log(code);
var ok = !parse_error && original_result == beautify_result && original_result == uglify_result; }
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(ok);
if (parse_error === 1) console.log('Parse error while beautifying'); function infer_options(ctor) {
if (parse_error === 2) console.log('Parse error while uglifying'); try {
if (!ok) break; ctor({ 0: 0 });
} catch (e) {
return e.defs;
}
}
var default_options = {
compress: infer_options(UglifyJS.Compressor),
mangle: {
"cache": null,
"eval": false,
"keep_fnames": false,
"screw_ie8": true,
"toplevel": false,
},
output: infer_options(UglifyJS.OutputStream),
};
function log_suspects(minify_options, component) {
var options = component in minify_options ? minify_options[component] : true;
if (!options) return;
options = UglifyJS.defaults(options, default_options[component]);
var suspects = Object.keys(default_options[component]).filter(function(name) {
if (options[name]) {
var m = JSON.parse(JSON.stringify(minify_options));
var o = JSON.parse(JSON.stringify(options));
o[name] = false;
m[component] = o;
try {
var r = sandbox.run_code(UglifyJS.minify(original_code, m).code);
return sandbox.same_stdout(original_result, r);
} catch (e) {
console.log("Error testing options." + component + "." + name);
console.log(e);
}
}
});
if (suspects.length > 0) {
console.log("Suspicious", component, "options:");
suspects.forEach(function(name) {
console.log(" " + name);
});
console.log();
}
}
function log(options) {
if (!ok) console.log('\n\n\n\n\n\n!!!!!!!!!!\n\n\n');
console.log("//=============================================================");
if (!ok) console.log("// !!!!!! Failed... round", round);
console.log("// original code");
try_beautify(original_code, original_result);
console.log();
console.log();
console.log("//-------------------------------------------------------------");
if (typeof uglify_code == "string") {
console.log("// uglified code");
try_beautify(uglify_code, uglify_result);
console.log();
console.log();
console.log("original result:");
console.log(original_result);
console.log("uglified result:");
console.log(uglify_result);
} else {
console.log("// !!! uglify failed !!!");
console.log(uglify_code.stack);
}
console.log("minify(options):");
options = JSON.parse(options);
console.log(options);
console.log();
if (!ok) {
Object.keys(default_options).forEach(log_suspects.bind(null, options));
console.log("!!!!!! Failed... round", round);
}
}
var minify_options = require("./ufuzz.json").map(function(options) {
options.fromString = true;
return JSON.stringify(options);
});
var original_code, original_result;
var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r");
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
loops = 0;
funcs = 0;
original_code = [
"var a = 100, b = 10, c = 0;",
createTopLevelCode(),
"console.log(null, a, b, c);" // preceding `null` makes for a cleaner output (empty string still shows up etc)
].join("\n");
minify_options.forEach(function(options) {
try {
uglify_code = UglifyJS.minify(original_code, JSON.parse(options)).code;
} catch (e) {
uglify_code = e;
}
ok = typeof uglify_code == "string";
if (ok) {
original_result = sandbox.run_code(original_code);
uglify_result = sandbox.run_code(uglify_code);
ok = sandbox.same_stdout(original_result, uglify_result);
}
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options);
if (!ok && isFinite(num_iterations)) process.exit(1);
});
} }

33
test/ufuzz.json Normal file
View File

@@ -0,0 +1,33 @@
[
{
"compress": {
"warnings": false
}
},
{
"compress": {
"warnings": false
},
"mangle": false
},
{
"compress": false,
"mangle": true
},
{
"compress": false,
"mangle": false,
"output": {
"beautify": true,
"bracketize": true
}
},
{
"compress": {
"keep_fargs": false,
"passes": 3,
"pure_getters": true,
"warnings": false
}
}
]

View File

@@ -46,21 +46,21 @@ function read_source_map(code) {
UglifyJS.minify = function(files, options) { UglifyJS.minify = function(files, options) {
options = UglifyJS.defaults(options, { options = UglifyJS.defaults(options, {
spidermonkey : false, compress : {},
outSourceMap : null,
outFileName : null,
sourceRoot : null,
inSourceMap : null,
sourceMapUrl : null,
sourceMapInline : false,
fromString : false, fromString : false,
warnings : false, inSourceMap : null,
mangle : {}, mangle : {},
mangleProperties : false, mangleProperties : false,
nameCache : null, nameCache : null,
outFileName : null,
output : null, output : null,
compress : {}, outSourceMap : null,
parse : {} parse : {},
sourceMapInline : false,
sourceMapUrl : null,
sourceRoot : null,
spidermonkey : false,
warnings : false,
}); });
UglifyJS.base54.reset(); UglifyJS.base54.reset();