Merge pull request #1766 from alexlamsl/harmony-v2.8.21

Merging from master for 2.8.21
This commit is contained in:
Alex Lam S.L
2017-04-02 18:10:58 +08:00
committed by GitHub
25 changed files with 1388 additions and 680 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,43 +48,45 @@ 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,
ecma : 5,
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,
ecma : 5,
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") {
@@ -216,7 +218,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) {
@@ -407,6 +414,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) {
@@ -1071,7 +1090,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);
@@ -1146,8 +1165,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 ]----- */
@@ -1339,7 +1362,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
@@ -2638,6 +2661,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) {
@@ -2649,49 +2673,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);
} }
@@ -2721,9 +2735,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){
@@ -3036,7 +3066,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);
} }
} }
} }
@@ -3112,7 +3142,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
@@ -3151,7 +3181,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()) {
@@ -3175,6 +3205,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 == "%")) {
@@ -3184,8 +3217,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);
@@ -3228,8 +3260,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);
} }
@@ -3242,8 +3274,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.
@@ -3281,42 +3313,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") {
@@ -3333,7 +3330,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)
@@ -3374,24 +3370,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;
@@ -3617,9 +3637,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")) {
@@ -3649,19 +3669,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 = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
@@ -3979,7 +4028,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;
@@ -4000,7 +4049,7 @@ merge(Compressor.prototype, {
}); });
OPT(AST_Yield, function(self, compressor){ OPT(AST_Yield, function(self, compressor){
if (!self.is_star && self.expression instanceof AST_Undefined) { if (self.expression && !self.is_star && is_undefined(self.expression, compressor)) {
self.expression = null; self.expression = null;
} }
return self; return self;

View File

@@ -53,29 +53,29 @@ 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,
ascii_identifiers: undefined, ascii_identifiers: undefined,
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,
preserve_line : false,
screw_ie8 : true,
preamble : null,
quote_style : 0,
keep_quoted_props: false,
shorthand : undefined,
ecma : 5, ecma : 5,
indent_level : 4,
indent_start : 0,
inline_script : 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,
shorthand : undefined,
source_map : null,
space_colon : true,
unescape_regexps : false,
width : 80,
wrap_iife : false, wrap_iife : false,
}, true); }, true);
@@ -559,8 +559,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();
} }
@@ -640,7 +640,7 @@ 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
@@ -652,15 +652,6 @@ function OutputStream(options) {
&& this.operator !== "--"; && this.operator !== "--";
}); });
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)
@@ -1625,24 +1616,7 @@ function OutputStream(options) {
DEFPRINT(AST_SymbolDeclaration, function(self, output){ DEFPRINT(AST_SymbolDeclaration, function(self, output){
self._do_print(output); self._do_print(output);
}); });
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

@@ -845,14 +845,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

@@ -79,12 +79,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

@@ -100,8 +100,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
@@ -490,13 +490,13 @@ 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_classnames: false,
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,
keep_classnames : false
}); });
}); });
@@ -681,12 +681,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;
@@ -302,13 +302,13 @@ pow_with_number_constants: {
var m = 3 ** -10; // Result will be 0.000016935087808430286, which is too long var m = 3 ** -10; // Result will be 0.000016935087808430286, which is too long
} }
expect: { expect: {
var a = 0/0; var a = NaN;
var b = 1; var b = 1;
var c = 1; var c = 1;
var d = 0/0; var d = NaN;
var e = 1/0; var e = 1/0;
var f = 0; var f = 0;
var g = 0/0; var g = NaN;
var h = 1/0; var h = 1/0;
var i = -1/0; var i = -1/0;
var j = .125; var j = .125;
@@ -627,7 +627,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
@@ -896,3 +896,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

@@ -21,13 +21,12 @@ pow_with_number_constants: {
var f = 2 ** -Infinity; var f = 2 ** -Infinity;
} }
expect: { expect: {
// TODO: may need parentheses var a = 5 ** NaN;
var a = 5 ** 0/0;
var b = 42 ** +0; var b = 42 ** +0;
var c = 42 ** -0; var c = 42 ** -0;
var d = 0/0 ** 1; var d = NaN ** 1;
var e = 2 ** 1/0; var e = 2 ** (1/0);
var f = 2 ** -1/0; var f = 2 ** (-1/0);
} }
} }

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();