Merge pull request #1805 from alexlamsl/harmony-v2.8.22

Merging from master for 2.8.22
This commit is contained in:
Alex Lam S.L
2017-04-09 17:27:30 +08:00
committed by GitHub
21 changed files with 1331 additions and 245 deletions

View File

@@ -411,6 +411,8 @@ to set `true`; it's effectively a shortcut for `foo=true`).
- `pure_getters` -- the default is `false`. If you pass `true` for
this, UglifyJS will assume that object property access
(e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects.
Specify `"strict"` to treat `foo.bar` as side-effect-free only when
`foo` is certain to not throw, i.e. not `null` or `undefined`.
- `pure_funcs` -- default `null`. You can pass an array of names and
UglifyJS will assume that those functions do not produce side

View File

@@ -1379,16 +1379,16 @@ TreeWalker.prototype = {
self = p;
}
},
loopcontrol_target: function(label) {
loopcontrol_target: function(node) {
var stack = this.stack;
if (label) for (var i = stack.length; --i >= 0;) {
if (node.label) for (var i = stack.length; --i >= 0;) {
var x = stack[i];
if (x instanceof AST_LabeledStatement && x.label.name == label.name) {
if (x instanceof AST_LabeledStatement && x.label.name == node.label.name)
return x.body;
}
} else for (var i = stack.length; --i >= 0;) {
var x = stack[i];
if (x instanceof AST_Switch || x instanceof AST_IterationStatement)
if (x instanceof AST_IterationStatement
|| node instanceof AST_Break && x instanceof AST_Switch)
return x;
}
}

View File

@@ -72,7 +72,7 @@ function Compressor(options, false_by_default) {
negate_iife : !false_by_default,
passes : 1,
properties : !false_by_default,
pure_getters : false,
pure_getters : !false_by_default && "strict",
pure_funcs : null,
reduce_vars : !false_by_default,
screw_ie8 : true,
@@ -252,9 +252,7 @@ merge(Compressor.prototype, {
AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){
var reduce_vars = rescan && compressor.option("reduce_vars");
var toplevel = compressor.option("toplevel");
var ie8 = !compressor.option("screw_ie8");
var safe_ids = [];
push();
var safe_ids = Object.create(null);
var suppressor = new TreeWalker(function(node) {
if (node instanceof AST_Symbol) {
var d = node.definition();
@@ -263,10 +261,8 @@ merge(Compressor.prototype, {
}
});
var tw = new TreeWalker(function(node, descend){
if (!(node instanceof AST_Directive || node instanceof AST_Constant)) {
node._squeezed = false;
node._optimized = false;
}
node._squeezed = false;
node._optimized = false;
if (reduce_vars) {
if (node instanceof AST_Toplevel) node.globals.each(reset_def);
if (node instanceof AST_Scope) node.variables.each(reset_def);
@@ -274,11 +270,11 @@ merge(Compressor.prototype, {
var d = node.definition();
d.references.push(node);
if (d.fixed === undefined || !is_safe(d)
|| is_modified(node, 0, d.fixed instanceof AST_Lambda)) {
|| is_modified(node, 0, node.fixed_value() instanceof AST_Lambda)) {
d.fixed = false;
}
}
if (ie8 && node instanceof AST_SymbolCatch) {
if (node instanceof AST_SymbolCatch) {
node.definition().fixed = false;
}
if (node instanceof AST_VarDef) {
@@ -287,8 +283,17 @@ merge(Compressor.prototype, {
} else {
var d = node.name.definition();
if (d.fixed == null) {
d.fixed = node.value;
mark_as_safe(d);
if (node.value) {
d.fixed = function() {
return node.value;
};
mark(d, false);
descend();
} else {
d.fixed = null;
}
mark(d, true);
return true;
} else if (node.value) {
d.fixed = false;
}
@@ -300,11 +305,10 @@ merge(Compressor.prototype, {
d.fixed = false;
} else {
d.fixed = node;
mark_as_safe(d);
mark(d, true);
}
var save_ids = safe_ids;
safe_ids = [];
push();
safe_ids = Object.create(null);
descend();
safe_ids = save_ids;
return true;
@@ -319,8 +323,10 @@ merge(Compressor.prototype, {
// So existing transformation rules can work on them.
node.argnames.forEach(function(arg, i) {
var d = arg.definition();
d.fixed = iife.args[i] || make_node(AST_Undefined, iife);
mark_as_safe(d);
d.fixed = function() {
return iife.args[i] || make_node(AST_Undefined, iife);
};
mark(d, true);
});
}
if (node instanceof AST_If || node instanceof AST_DWLoop) {
@@ -368,29 +374,27 @@ merge(Compressor.prototype, {
});
this.walk(tw);
function mark_as_safe(def) {
safe_ids[safe_ids.length - 1][def.id] = true;
function mark(def, safe) {
safe_ids[def.id] = safe;
}
function is_safe(def) {
for (var i = safe_ids.length, id = def.id; --i >= 0;) {
if (safe_ids[i][id]) {
if (def.fixed == null) {
var orig = def.orig[0];
if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
def.fixed = make_node(AST_Undefined, orig);
}
return true;
if (safe_ids[def.id]) {
if (def.fixed == null) {
var orig = def.orig[0];
if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false;
def.fixed = make_node(AST_Undefined, orig);
}
return true;
}
}
function push() {
safe_ids.push(Object.create(null));
safe_ids = Object.create(safe_ids);
}
function pop() {
safe_ids.pop();
safe_ids = Object.getPrototypeOf(safe_ids);
}
function reset_def(def) {
@@ -405,7 +409,7 @@ merge(Compressor.prototype, {
function is_modified(node, level, func) {
var parent = tw.parent(level);
if (isLHS(node, parent)
if (is_lhs(node, parent)
|| !func && parent instanceof AST_Call && parent.expression === node) {
return true;
} else if (parent instanceof AST_PropAccess && parent.expression === node) {
@@ -414,6 +418,12 @@ merge(Compressor.prototype, {
}
});
AST_SymbolRef.DEFMETHOD("fixed_value", function() {
var fixed = this.definition().fixed;
if (!fixed || fixed instanceof AST_Node) return fixed;
return fixed();
});
function find_variable(compressor, name) {
var scope, i = 0;
while (scope = compressor.parent(i++)) {
@@ -474,15 +484,15 @@ merge(Compressor.prototype, {
// func(something) because that changes the meaning of
// the func (becomes lexical instead of global).
function maintain_this_binding(parent, orig, val) {
if (parent instanceof AST_Call && parent.expression === orig) {
if (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name === "eval") {
return make_node(AST_Seq, orig, {
car: make_node(AST_Number, orig, {
value: 0
}),
cdr: val
});
}
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete"
|| parent instanceof AST_Call && parent.expression === orig
&& (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name == "eval")) {
return make_node(AST_Seq, orig, {
car: make_node(AST_Number, orig, {
value: 0
}),
cdr: val
});
}
return val;
}
@@ -702,7 +712,7 @@ merge(Compressor.prototype, {
return statements;
function is_lvalue(node, parent) {
return node instanceof AST_SymbolRef && isLHS(node, parent);
return node instanceof AST_SymbolRef && is_lhs(node, parent);
}
function replace_var(node, parent, is_constant) {
if (is_lvalue(node, parent)) return node;
@@ -916,7 +926,7 @@ merge(Compressor.prototype, {
}
var ab = aborts(stat.body);
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|| (ab instanceof AST_Continue && self === loop_body(lct))
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
@@ -938,7 +948,7 @@ merge(Compressor.prototype, {
}
var ab = aborts(stat.alternative);
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null;
var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null;
if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda)
|| (ab instanceof AST_Continue && self === loop_body(lct))
|| (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) {
@@ -987,7 +997,7 @@ merge(Compressor.prototype, {
extract_declarations_from_unreachable_code(compressor, stat, a);
} else {
if (stat instanceof AST_LoopControl) {
var lct = compressor.loopcontrol_target(stat.label);
var lct = compressor.loopcontrol_target(stat);
if ((stat instanceof AST_Break
&& !(lct instanceof AST_IterationStatement)
&& loop_body(lct) === self) || (stat instanceof AST_Continue
@@ -1173,6 +1183,61 @@ merge(Compressor.prototype, {
&& !node.expression.has_side_effects(compressor);
}
// may_eq_null()
// returns true if this node may evaluate to null or undefined
(function(def) {
AST_Node.DEFMETHOD("may_eq_null", function(compressor) {
var pure_getters = compressor.option("pure_getters");
return !pure_getters || this._eq_null(pure_getters);
});
function is_strict(pure_getters) {
return /strict/.test(pure_getters);
}
def(AST_Node, is_strict);
def(AST_Null, return_true);
def(AST_Undefined, return_true);
def(AST_Constant, return_false);
def(AST_Array, return_false);
def(AST_Object, return_false);
def(AST_Function, return_false);
def(AST_UnaryPostfix, return_false);
def(AST_UnaryPrefix, function() {
return this.operator == "void";
});
def(AST_Binary, function(pure_getters) {
switch (this.operator) {
case "&&":
return this.left._eq_null(pure_getters);
case "||":
return this.left._eq_null(pure_getters)
&& this.right._eq_null(pure_getters);
default:
return false;
}
})
def(AST_Assign, function(pure_getters) {
return this.operator == "="
&& this.right._eq_null(pure_getters);
})
def(AST_Conditional, function(pure_getters) {
return this.consequent._eq_null(pure_getters)
|| this.alternative._eq_null(pure_getters);
})
def(AST_Seq, function(pure_getters) {
return this.cdr._eq_null(pure_getters);
});
def(AST_SymbolRef, function(pure_getters) {
if (this.is_undefined) return true;
if (!is_strict(pure_getters)) return false;
var fixed = this.fixed_value();
return !fixed || fixed._eq_null(pure_getters);
});
})(function(node, func) {
node.DEFMETHOD("_eq_null", func);
});
/* -----[ boolean/negation helpers ]----- */
// methods to determine whether an expression has a boolean result type
@@ -1260,9 +1325,9 @@ merge(Compressor.prototype, {
var unary_side_effects = makePredicate("delete ++ --");
function isLHS(node, parent) {
return parent instanceof AST_Unary && unary_side_effects(parent.operator)
|| parent instanceof AST_Assign && parent.left === node;
function is_lhs(node, parent) {
if (parent instanceof AST_Unary && unary_side_effects(parent.operator)) return parent.expression;
if (parent instanceof AST_Assign && parent.left === node) return node;
}
(function (def){
@@ -1275,7 +1340,7 @@ merge(Compressor.prototype, {
node = parent;
parent = compressor.parent(level++);
} while (parent instanceof AST_PropAccess && parent.expression === node);
if (isLHS(node, parent)) {
if (is_lhs(node, parent)) {
compressor.warn('global_defs ' + this.print_to_string() + ' redefined [{file}:{line},{col}]', this.start);
} else {
return def;
@@ -1305,7 +1370,7 @@ merge(Compressor.prototype, {
}
def(AST_Node, noop);
def(AST_Dot, function(compressor, suffix){
return this.expression._find_defs(compressor, suffix + "." + this.property);
return this.expression._find_defs(compressor, "." + this.property + suffix);
});
def(AST_SymbolRef, function(compressor, suffix){
if (!this.global()) return;
@@ -1525,15 +1590,15 @@ merge(Compressor.prototype, {
if (this._evaluating) throw def;
this._evaluating = true;
try {
var d = this.definition();
if (compressor.option("reduce_vars") && d.fixed) {
var fixed = this.fixed_value();
if (compressor.option("reduce_vars") && fixed) {
if (compressor.option("unsafe")) {
if (!HOP(d.fixed, "_evaluated")) {
d.fixed._evaluated = ev(d.fixed, compressor);
if (!HOP(fixed, "_evaluated")) {
fixed._evaluated = ev(fixed, compressor);
}
return d.fixed._evaluated;
return fixed._evaluated;
}
return ev(d.fixed, compressor);
return ev(fixed, compressor);
}
} finally {
this._evaluating = false;
@@ -1718,7 +1783,7 @@ merge(Compressor.prototype, {
|| this.expression.has_side_effects(compressor);
});
def(AST_SymbolRef, function(compressor){
return this.global() && this.undeclared();
return this.undeclared();
});
def(AST_Object, function(compressor){
return any(this.properties, compressor);
@@ -1733,17 +1798,14 @@ merge(Compressor.prototype, {
return any(this.elements, compressor);
});
def(AST_Dot, function(compressor){
if (!compressor.option("pure_getters")) return true;
return this.expression.has_side_effects(compressor);
return this.expression.may_eq_null(compressor)
|| this.expression.has_side_effects(compressor);
});
def(AST_Sub, function(compressor){
if (!compressor.option("pure_getters")) return true;
return this.expression.has_side_effects(compressor)
return this.expression.may_eq_null(compressor)
|| this.expression.has_side_effects(compressor)
|| this.property.has_side_effects(compressor);
});
def(AST_PropAccess, function(compressor){
return !compressor.option("pure_getters");
});
def(AST_Seq, function(compressor){
return this.car.has_side_effects(compressor)
|| this.cdr.has_side_effects(compressor);
@@ -1790,7 +1852,7 @@ merge(Compressor.prototype, {
OPT(AST_LabeledStatement, function(self, compressor){
if (self.body instanceof AST_Break
&& compressor.loopcontrol_target(self.body.label) === self.body) {
&& compressor.loopcontrol_target(self.body) === self.body) {
return make_node(AST_EmptyStatement, self);
}
return self.label.references.length == 0 ? self.body : self;
@@ -2388,11 +2450,11 @@ merge(Compressor.prototype, {
return values && AST_Seq.from_array(values);
});
def(AST_Dot, function(compressor, first_in_statement){
if (!compressor.option("pure_getters")) return this;
if (this.expression.may_eq_null(compressor)) return this;
return this.expression.drop_side_effect_free(compressor, first_in_statement);
});
def(AST_Sub, function(compressor, first_in_statement){
if (!compressor.option("pure_getters")) return this;
if (this.expression.may_eq_null(compressor)) return this;
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
var property = this.property.drop_side_effect_free(compressor);
@@ -2438,13 +2500,21 @@ merge(Compressor.prototype, {
return make_node(AST_For, self, {
body: self.body
});
} else if (compressor.option("dead_code") && self instanceof AST_While) {
}
if (compressor.option("dead_code") && self instanceof AST_While) {
var a = [];
extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a });
} else {
cond = make_node_from_constant(cond, self.condition).transform(compressor);
self.condition = best_of_expression(cond, self.condition);
}
if (self instanceof AST_Do) {
var has_loop_control = false;
var tw = new TreeWalker(function(node) {
if (node instanceof AST_Scope || has_loop_control) return true;
if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === self)
return has_loop_control = true;
});
self.walk(tw);
if (!has_loop_control) return self.body;
}
}
if (self instanceof AST_While) {
@@ -2470,7 +2540,7 @@ merge(Compressor.prototype, {
var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
if (first instanceof AST_If) {
if (first.body instanceof AST_Break
&& compressor.loopcontrol_target(first.body.label) === compressor.self()) {
&& compressor.loopcontrol_target(first.body) === compressor.self()) {
if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, {
left: self.condition,
@@ -2483,7 +2553,7 @@ merge(Compressor.prototype, {
drop_it(first.alternative);
}
else if (first.alternative instanceof AST_Break
&& compressor.loopcontrol_target(first.alternative.label) === compressor.self()) {
&& compressor.loopcontrol_target(first.alternative) === compressor.self()) {
if (self.condition) {
self.condition = make_node(AST_Binary, self.condition, {
left: self.condition,
@@ -2714,7 +2784,7 @@ merge(Compressor.prototype, {
self.body = body;
while (branch = body[body.length - 1]) {
var stat = branch.body[branch.body.length - 1];
if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self)
if (stat instanceof AST_Break && compressor.loopcontrol_target(stat) === self)
branch.body.pop();
if (branch.body.length || branch instanceof AST_Case
&& (default_branch || branch.expression.has_side_effects(compressor))) break;
@@ -2733,7 +2803,7 @@ merge(Compressor.prototype, {
if (has_break
|| node instanceof AST_Lambda
|| node instanceof AST_SimpleStatement) return true;
if (node instanceof AST_Break && tw.loopcontrol_target(node.label) === self)
if (node instanceof AST_Break && tw.loopcontrol_target(node) === self)
has_break = true;
});
self.walk(tw);
@@ -2819,11 +2889,12 @@ merge(Compressor.prototype, {
if (compressor.option("reduce_vars")
&& exp instanceof AST_SymbolRef) {
var def = exp.definition();
if (def.fixed instanceof AST_Defun) {
def.fixed = make_node(AST_Function, def.fixed, def.fixed).clone(true);
var fixed = exp.fixed_value();
if (fixed instanceof AST_Defun) {
def.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true);
}
if (def.fixed instanceof AST_Function) {
exp = def.fixed;
if (fixed instanceof AST_Function) {
exp = fixed;
if (compressor.option("unused")
&& def.references.length == 1
&& !(def.scope.uses_arguments
@@ -3158,8 +3229,9 @@ merge(Compressor.prototype, {
if (this.expression instanceof AST_Seq) {
var seq = this.expression;
var x = seq.to_array();
this.expression = x.pop();
x.push(this);
var e = this.clone();
e.expression = x.pop();
x.push(e);
seq = AST_Seq.from_array(x).transform(compressor);
return seq;
}
@@ -3172,11 +3244,27 @@ merge(Compressor.prototype, {
});
OPT(AST_UnaryPrefix, function(self, compressor){
var e = self.expression;
if (self.operator == "delete"
&& !(e instanceof AST_SymbolRef
|| e instanceof AST_PropAccess
|| e instanceof AST_NaN
|| e instanceof AST_Infinity
|| e instanceof AST_Undefined)) {
if (e instanceof AST_Seq) {
e = e.to_array();
e.push(make_node(AST_True, self));
return AST_Seq.from_array(e).optimize(compressor);
}
return make_node(AST_Seq, self, {
car: e,
cdr: make_node(AST_True, self)
}).optimize(compressor);
}
var seq = self.lift_sequences(compressor);
if (seq !== self) {
return seq;
}
var e = self.expression;
if (compressor.option("side_effects") && self.operator == "void") {
e = e.drop_side_effect_free(compressor);
if (e) {
@@ -3213,9 +3301,14 @@ merge(Compressor.prototype, {
if (e instanceof AST_Binary
&& (self.operator == "+" || self.operator == "-")
&& (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
self.expression = e.left;
e.left = self;
return e.optimize(compressor);
return make_node(AST_Binary, self, {
operator: e.operator,
left: make_node(AST_UnaryPrefix, e.left, {
operator: self.operator,
expression: e.left
}),
right: e.right
});
}
// avoids infinite recursion of numerals
if (self.operator != "-"
@@ -3234,23 +3327,25 @@ merge(Compressor.prototype, {
if (this.left instanceof AST_Seq) {
var seq = this.left;
var x = seq.to_array();
this.left = x.pop();
x.push(this);
var e = this.clone();
e.left = x.pop();
x.push(e);
return AST_Seq.from_array(x).optimize(compressor);
}
if (this.right instanceof AST_Seq && !this.left.has_side_effects(compressor)) {
var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
var root = this.right;
var root = this.right.clone();
var cursor, seq = root;
while (assign || !seq.car.has_side_effects(compressor)) {
cursor = seq;
if (seq.cdr instanceof AST_Seq) {
seq = seq.cdr;
seq = seq.cdr = seq.cdr.clone();
} else break;
}
if (cursor) {
this.right = cursor.cdr;
cursor.cdr = this;
var e = this.clone();
e.right = cursor.cdr;
cursor.cdr = e;
return root.optimize(compressor);
}
}
@@ -3628,12 +3723,11 @@ merge(Compressor.prototype, {
OPT(AST_SymbolRef, function(self, compressor){
var def = self.resolve_defines(compressor);
if (def) {
return def;
return def.optimize(compressor);
}
// testing against !self.scope.uses_with first is an optimization
if (compressor.option("screw_ie8")
&& self.undeclared()
&& !isLHS(self, compressor.parent())
&& (!self.scope.uses_with || !compressor.find_parent(AST_With))) {
switch (self.name) {
case "undefined":
@@ -3646,13 +3740,13 @@ merge(Compressor.prototype, {
}
if (compressor.option("evaluate") && compressor.option("reduce_vars")) {
var d = self.definition();
if (d.fixed) {
var fixed = self.fixed_value();
if (fixed) {
if (d.should_replace === undefined) {
var init = d.fixed.evaluate(compressor);
if (init !== d.fixed) {
init = make_node_from_constant(init, d.fixed).optimize(compressor);
init = best_of_expression(init, d.fixed);
var value = init.print_to_string().length;
var init = fixed.evaluate(compressor);
if (init !== fixed) {
init = make_node_from_constant(init, fixed);
var value = best_of_expression(init.optimize(compressor), fixed).print_to_string().length;
var name = d.name.length;
var freq = d.references.length;
var overhead = d.global || !freq ? 0 : (name + 2 + value) / freq;
@@ -3662,13 +3756,17 @@ merge(Compressor.prototype, {
}
}
if (d.should_replace) {
return d.should_replace.clone(true);
return best_of_expression(d.should_replace.optimize(compressor), fixed).clone(true);
}
}
}
return self;
});
function is_atomic(lhs, self) {
return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
}
OPT(AST_Undefined, function(self, compressor){
if (compressor.option("unsafe")) {
var undef = find_variable(compressor, "undefined");
@@ -3682,6 +3780,8 @@ merge(Compressor.prototype, {
return ref;
}
}
var lhs = is_lhs(compressor.self(), compressor.parent());
if (lhs && is_atomic(lhs, self)) return self;
return make_node(AST_UnaryPrefix, self, {
operator: "void",
expression: make_node(AST_Number, self, {
@@ -3691,8 +3791,13 @@ merge(Compressor.prototype, {
});
OPT(AST_Infinity, function(self, compressor){
var retain = compressor.option("keep_infinity") && !find_variable(compressor, "Infinity");
return retain ? self : make_node(AST_Binary, self, {
var lhs = is_lhs(compressor.self(), compressor.parent());
if (lhs && is_atomic(lhs, self)) return self;
if (compressor.option("keep_infinity")
&& !(lhs && !is_atomic(lhs, self))
&& !find_variable(compressor, "Infinity"))
return self;
return make_node(AST_Binary, self, {
operator: "/",
left: make_node(AST_Number, self, {
value: 1
@@ -3704,15 +3809,20 @@ merge(Compressor.prototype, {
});
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 lhs = is_lhs(compressor.self(), compressor.parent());
if (lhs && !is_atomic(lhs, self)
|| find_variable(compressor, "NaN")) {
return make_node(AST_Binary, self, {
operator: "/",
left: make_node(AST_Number, self, {
value: 0
}),
right: make_node(AST_Number, self, {
value: 0
})
});
}
return self;
});
var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ];
@@ -3975,7 +4085,7 @@ merge(Compressor.prototype, {
OPT(AST_Dot, function(self, compressor){
var def = self.resolve_defines(compressor);
if (def) {
return def;
return def.optimize(compressor);
}
var prop = self.property;
if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) {

View File

@@ -61,7 +61,7 @@ function OutputStream(options) {
ecma : 5,
indent_level : 4,
indent_start : 0,
inline_script : false,
inline_script : true,
keep_quoted_props: false,
max_line_len : false,
preamble : null,

View File

@@ -53,7 +53,15 @@ function find_builtins() {
objects[new_global] = global[new_global] || new Function();
});
var a = [];
// NaN will be included due to Number.NaN
var a = [
"null",
"true",
"false",
"Infinity",
"-Infinity",
"undefined",
];
[ Object, Array, Function, Number,
String, Boolean, Error, Math,
Date, RegExp, objects.Symbol, ArrayBuffer,
@@ -174,13 +182,12 @@ function mangle_properties(ast, options) {
// only function declarations after this line
function can_mangle(name) {
if (!is_identifier(name)) return false;
if (unmangleable.indexOf(name) >= 0) return false;
if (reserved.indexOf(name) >= 0) return false;
if (options.only_cache) {
return cache.props.has(name);
}
if (/^[0-9.]+$/.test(name)) return false;
if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
return true;
}

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "2.8.21",
"version": "2.8.22",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -1672,6 +1672,7 @@ var_side_effects_3: {
options = {
collapse_vars: true,
pure_getters: true,
unsafe: true,
}
input: {
var print = console.log.bind(console);

View File

@@ -962,3 +962,56 @@ condition_symbol_matches_consequent: {
}
expect_stdout: "3 7 true 4"
}
delete_conditional_1: {
options = {
booleans: true,
conditionals: true,
evaluate: true,
side_effects: true,
}
input: {
console.log(delete (1 ? undefined : x));
console.log(delete (1 ? void 0 : x));
console.log(delete (1 ? Infinity : x));
console.log(delete (1 ? 1 / 0 : x));
console.log(delete (1 ? NaN : x));
console.log(delete (1 ? 0 / 0 : x));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((NaN, !0));
}
expect_stdout: true
}
delete_conditional_2: {
options = {
booleans: true,
conditionals: true,
evaluate: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (0 ? x : undefined));
console.log(delete (0 ? x : void 0));
console.log(delete (0 ? x : Infinity));
console.log(delete (0 ? x : 1 / 0));
console.log(delete (0 ? x : NaN));
console.log(delete (0 ? x : 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((NaN, !0));
}
expect_stdout: true
}

View File

@@ -1055,3 +1055,58 @@ issue_1715_4: {
}
expect_stdout: "1"
}
delete_assign_1: {
options = {
booleans: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
console.log(delete (a = undefined));
console.log(delete (a = void 0));
console.log(delete (a = Infinity));
console.log(delete (a = 1 / 0));
console.log(delete (a = NaN));
console.log(delete (a = 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_assign_2: {
options = {
booleans: true,
keep_infinity: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
console.log(delete (a = undefined));
console.log(delete (a = void 0));
console.log(delete (a = Infinity));
console.log(delete (a = 1 / 0));
console.log(delete (a = NaN));
console.log(delete (a = 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}

View File

@@ -951,3 +951,135 @@ issue_1760_2: {
}
expect_stdout: "Infinity"
}
delete_expr_1: {
options = {
booleans: true,
evaluate: true,
}
input: {
console.log(delete undefined);
console.log(delete void 0);
console.log(delete Infinity);
console.log(delete (1 / 0));
console.log(delete NaN);
console.log(delete (0 / 0));
}
expect: {
console.log(delete undefined);
console.log((void 0, !0));
console.log(delete Infinity);
console.log((1 / 0, !0));
console.log(delete NaN);
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_expr_2: {
options = {
booleans: true,
evaluate: true,
keep_infinity: true,
}
input: {
console.log(delete undefined);
console.log(delete void 0);
console.log(delete Infinity);
console.log(delete (1 / 0));
console.log(delete NaN);
console.log(delete (0 / 0));
}
expect: {
console.log(delete undefined);
console.log((void 0, !0));
console.log(delete Infinity);
console.log((1 / 0, !0));
console.log(delete NaN);
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_binary_1: {
options = {
booleans: true,
evaluate: true,
side_effects: true,
}
input: {
console.log(delete (true && undefined));
console.log(delete (true && void 0));
console.log(delete (true && Infinity));
console.log(delete (true && (1 / 0)));
console.log(delete (true && NaN));
console.log(delete (true && (0 / 0)));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((NaN, !0));
}
expect_stdout: true
}
delete_binary_2: {
options = {
booleans: true,
evaluate: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (false || undefined));
console.log(delete (false || void 0));
console.log(delete (false || Infinity));
console.log(delete (false || (1 / 0)));
console.log(delete (false || NaN));
console.log(delete (false || (0 / 0)));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((NaN, !0));
}
expect_stdout: true
}
Infinity_NaN_undefined_LHS: {
beautify = {
beautify: true,
}
input: {
function f() {
Infinity = Infinity;
++Infinity;
Infinity--;
NaN *= NaN;
++NaN;
NaN--;
undefined |= undefined;
++undefined;
undefined--;
}
}
expect_exact: [
"function f() {",
" Infinity = 1 / 0;",
" ++Infinity;",
" Infinity--;",
" NaN *= NaN;",
" ++NaN;",
" NaN--;",
" undefined |= void 0;",
" ++undefined;",
" undefined--;",
"}",
]
}

View File

@@ -145,3 +145,18 @@ mixed: {
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]',
]
}
issue_1801: {
options = {
booleans: true,
global_defs: {
"CONFIG.FOO.BAR": true,
},
}
input: {
console.log(CONFIG.FOO.BAR);
}
expect: {
console.log(!0);
}
}

View File

@@ -45,11 +45,10 @@ chained_evaluation_2: {
}
expect: {
(function() {
var a = "long piece of string";
(function() {
var c;
c = f(a);
c.bar = a;
var c, b = "long piece of string";
c = f(b);
c.bar = b;
})();
})();
}

105
test/compress/issue-1770.js Normal file
View File

@@ -0,0 +1,105 @@
mangle_props: {
mangle_props = {}
input: {
var obj = {
undefined: 1,
NaN: 2,
Infinity: 3,
"-Infinity": 4,
null: 5,
};
console.log(
obj[void 0],
obj[undefined],
obj["undefined"],
obj[0/0],
obj[NaN],
obj["NaN"],
obj[1/0],
obj[Infinity],
obj["Infinity"],
obj[-1/0],
obj[-Infinity],
obj["-Infinity"],
obj[null],
obj["null"]
);
}
expect: {
var obj = {
undefined: 1,
NaN: 2,
Infinity: 3,
"-Infinity": 4,
null: 5,
};
console.log(
obj[void 0],
obj[void 0],
obj["undefined"],
obj[0/0],
obj[NaN],
obj["NaN"],
obj[1/0],
obj[1/0],
obj["Infinity"],
obj[-1/0],
obj[-1/0],
obj["-Infinity"],
obj[null],
obj["null"]
);
}
expect_stdout: "1 1 1 2 2 2 3 3 3 4 4 4 5 5"
}
numeric_literal: {
beautify = {
beautify: true,
}
mangle_props = {}
input: {
var obj = {
0: 0,
"-0": 1,
42: 2,
"42": 3,
0x25: 4,
"0x25": 5,
1E42: 6,
"1E42": 7,
"1e+42": 8,
};
console.log(obj[-0], obj[-""], obj["-0"]);
console.log(obj[42], obj["42"]);
console.log(obj[0x25], obj["0x25"], obj[37], obj["37"]);
console.log(obj[1E42], obj["1E42"], obj["1e+42"]);
}
expect_exact: [
'var obj = {',
' 0: 0,',
' "-0": 1,',
' 42: 2,',
' "42": 3,',
' 37: 4,',
' a: 5,',
' 1e42: 6,',
' b: 7,',
' "1e+42": 8',
'};',
'',
'console.log(obj[-0], obj[-""], obj["-0"]);',
'',
'console.log(obj[42], obj["42"]);',
'',
'console.log(obj[37], obj["a"], obj[37], obj["37"]);',
'',
'console.log(obj[1e42], obj["b"], obj["1e+42"]);',
]
expect_stdout: [
"0 0 1",
"3 3",
"4 5 4 4",
"8 7 8",
]
}

View File

@@ -0,0 +1,19 @@
unary_prefix: {
options = {
evaluate: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function() {
var x = -(2 / 3);
return x;
}());
}
expect: {
console.log(function() {
return -2 / 3;
}());
}
expect_stdout: true
}

View File

@@ -215,8 +215,7 @@ evaluate: {
a();
for(;;)
c();
// rule disabled due to issue_1532
do d(); while (false);
d();
}
}
@@ -458,3 +457,26 @@ issue_1648: {
}
expect_exact: "function f(){for(x();1;);}"
}
do_switch: {
options = {
evaluate: true,
loops: true,
}
input: {
do {
switch (a) {
case b:
continue;
}
} while (false);
}
expect: {
do {
switch (a) {
case b:
continue;
}
} while (false);
}
}

View File

@@ -0,0 +1,121 @@
strict: {
options = {
pure_getters: "strict",
reduce_vars: false,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
strict_reduce_vars: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
a.prop;
b.prop;
d.prop;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
unsafe: {
options = {
pure_getters: true,
reduce_vars: false,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
d;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
unsafe_reduce_vars: {
options = {
pure_getters: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
var a, b = null, c = {};
a.prop;
b.prop;
c.prop;
d.prop;
null.prop;
(void 0).prop;
undefined.prop;
}
expect: {
var a, b = null, c = {};
d;
null.prop;
(void 0).prop;
(void 0).prop;
}
}
chained: {
options = {
pure_getters: "strict",
side_effects: true,
}
input: {
a.b.c;
}
expect: {
a.b.c;
}
}

View File

@@ -1866,3 +1866,132 @@ delay_def: {
}
expect_stdout: true
}
booleans: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
console.log(function(a) {
if (a != 0);
switch (a) {
case 0:
return "FAIL";
case false:
return "PASS";
}
}(false));
}
expect: {
console.log(function(a) {
if (!1);
switch (!1) {
case 0:
return "FAIL";
case !1:
return "PASS";
}
}(!1));
}
expect_stdout: "PASS"
}
side_effects_assign: {
options = {
evaluate: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
}
input: {
var a = typeof void (a && a.in == 1, 0);
console.log(a);
}
expect: {
var a = typeof void (a && a.in);
console.log(a);
}
expect_stdout: "undefined"
}
pure_getters_1: {
options = {
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
try {
var a = (a.b, 2);
} catch (e) {}
console.log(a);
}
expect: {
try {
var a = (a.b, 2);
} catch (e) {}
console.log(a);
}
expect_stdout: "undefined"
}
pure_getters_2: {
options = {
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a;
var a = a && a.b;
}
expect: {
var a;
var a = a && a.b;
}
}
pure_getters_3: {
options = {
pure_getters: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a;
var a = a && a.b;
}
expect: {
}
}
catch_var: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
}
input: {
try {
throw {};
} catch (e) {
var e;
console.log(!!e);
}
}
expect: {
try {
throw {};
} catch (e) {
var e;
console.log(!!e);
}
}
expect_stdout: "true"
}

View File

@@ -466,3 +466,147 @@ issue_1758: {
}
expect_stdout: "undefined"
}
delete_seq_1: {
options = {
booleans: true,
side_effects: true,
}
input: {
console.log(delete (1, undefined));
console.log(delete (1, void 0));
console.log(delete (1, Infinity));
console.log(delete (1, 1 / 0));
console.log(delete (1, NaN));
console.log(delete (1, 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_seq_2: {
options = {
booleans: true,
side_effects: true,
}
input: {
console.log(delete (1, 2, undefined));
console.log(delete (1, 2, void 0));
console.log(delete (1, 2, Infinity));
console.log(delete (1, 2, 1 / 0));
console.log(delete (1, 2, NaN));
console.log(delete (1, 2, 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((1 / 0, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_seq_3: {
options = {
booleans: true,
keep_infinity: true,
side_effects: true,
}
input: {
console.log(delete (1, 2, undefined));
console.log(delete (1, 2, void 0));
console.log(delete (1, 2, Infinity));
console.log(delete (1, 2, 1 / 0));
console.log(delete (1, 2, NaN));
console.log(delete (1, 2, 0 / 0));
}
expect: {
console.log((void 0, !0));
console.log((void 0, !0));
console.log((Infinity, !0));
console.log((1 / 0, !0));
console.log((NaN, !0));
console.log((0 / 0, !0));
}
expect_stdout: true
}
delete_seq_4: {
options = {
booleans: true,
sequences: true,
side_effects: true,
}
input: {
function f() {}
console.log(delete (f(), undefined));
console.log(delete (f(), void 0));
console.log(delete (f(), Infinity));
console.log(delete (f(), 1 / 0));
console.log(delete (f(), NaN));
console.log(delete (f(), 0 / 0));
}
expect: {
function f() {}
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0));
}
expect_stdout: true
}
delete_seq_5: {
options = {
booleans: true,
keep_infinity: true,
sequences: true,
side_effects: true,
}
input: {
function f() {}
console.log(delete (f(), undefined));
console.log(delete (f(), void 0));
console.log(delete (f(), Infinity));
console.log(delete (f(), 1 / 0));
console.log(delete (f(), NaN));
console.log(delete (f(), 0 / 0));
}
expect: {
function f() {}
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0)),
console.log((f(), !0));
}
expect_stdout: true
}
delete_seq_6: {
options = {
booleans: true,
side_effects: true,
}
input: {
var a;
console.log(delete (1, a));
}
expect: {
var a;
console.log((a, !0));
}
expect_stdout: true
}

View File

@@ -12,7 +12,6 @@ var FUNC_TOSTRING = [
' return "[Function: __func_" + i + "__]";',
" }",
"}();",
""
].join("\n");
exports.run_code = function(code) {
var stdout = "";
@@ -21,15 +20,20 @@ exports.run_code = function(code) {
stdout += chunk;
};
try {
new vm.Script(FUNC_TOSTRING + code).runInNewContext({
vm.runInNewContext([
"!function() {",
FUNC_TOSTRING,
code,
"}();",
].join("\n"), {
console: {
log: function() {
return console.log.apply(console, [].map.call(arguments, function(arg) {
return typeof arg == "function" ? arg.toString() : arg;
return typeof arg == "function" || arg && /Error$/.test(arg.name) ? arg.toString() : arg;
}));
}
}
}, { timeout: 30000 });
}, { timeout: 5000 });
return stdout;
} catch (ex) {
return ex;

View File

@@ -71,6 +71,7 @@ var STMT_COUNT_FROM_GLOBAL = true; // count statement depth from nearest functio
var num_iterations = +process.argv[2] || 1/0;
var verbose = false; // log every generated test
var verbose_interval = false; // log every 100 generated tests
var verbose_error = false;
for (var i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) {
case '-v':
@@ -79,6 +80,9 @@ for (var i = 2; i < process.argv.length; ++i) {
case '-V':
verbose_interval = true;
break;
case '-E':
verbose_error = 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');
@@ -118,6 +122,7 @@ for (var i = 2; i < process.argv.length; ++i) {
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('-E: print generated test case with runtime error');
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)');
@@ -135,6 +140,7 @@ for (var i = 2; i < process.argv.length; ++i) {
}
var VALUES = [
'""',
'true',
'false',
' /[a2][^e]+$/ ',
@@ -221,15 +227,19 @@ var ASSIGNMENTS = [
'>>>=',
'%=' ];
var UNARY_OPS = [
'--',
'++',
var UNARY_SAFE = [
'+',
'-',
'~',
'!',
'void ',
'delete ', // should be safe, even `delete foo` and `delete f()` shouldn't crash
' - ',
' + ' ];
'delete ',
];
var UNARY_POSTFIX = [
'++',
'--',
];
var UNARY_PREFIX = UNARY_POSTFIX.concat(UNARY_SAFE);
var NO_COMMA = true;
var COMMA_OK = false;
@@ -250,26 +260,26 @@ var NO_DECL = true;
var DONT_STORE = true;
var VAR_NAMES = [
'foo',
'bar',
'a',
'a',
'a',
'a',
'b',
'b',
'b',
'b',
'c', // prevent redeclaring this, avoid assigning to this
'undefined', // fun!
'eval', // mmmm, ok, also fun!
'NaN', // mmmm, ok, also fun!
'Infinity', // the fun never ends!
'arguments', // this one is just creepy
'Math', // since Math is assumed to be a non-constructor/function it may trip certain cases
'foo',
'foo',
'bar',
'bar',
'undefined',
'NaN',
'Infinity',
'arguments',
'Math',
'parseInt',
'parseFloat',
'isNaN',
'isFinite',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'Object'];
];
var INITIAL_NAMES_LEN = VAR_NAMES.length;
var TYPEOF_OUTCOMES = [
@@ -306,6 +316,22 @@ function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
return s;
}
function createParams() {
var params = [];
for (var n = rng(4); --n >= 0;) {
params.push(createVarName(MANDATORY));
}
return params.join(', ');
}
function createArgs() {
var args = [];
for (var n = rng(4); --n >= 0;) {
args.push(createValue());
}
return args.join(', ');
}
function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
if (--recurmax < 0) { return ';'; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
@@ -316,17 +342,17 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) {
var s = '';
if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess.
s = 'function ' + name + '(' + createVarName(MANDATORY) + '){' + createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth) + '}\n';
s = 'function ' + name + '(' + createParams() + '){' + createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth) + '}\n';
} else {
// functions with statements
s = 'function ' + name + '(' + createVarName(MANDATORY) + '){' + createStatements(3, recurmax, canThrow, CANNOT_THROW, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}\n';
s = 'function ' + name + '(' + createParams() + '){' + createStatements(3, recurmax, canThrow, CANNOT_THROW, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + '}\n';
}
VAR_NAMES.length = namesLenBefore;
if (noDecl) s = '!' + s + '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
if (noDecl) s = 'var ' + createVarName(MANDATORY) + ' = ' + s + '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ');';
// avoid "function statements" (decl inside statements)
else if (inGlobal || rng(10) > 0) s += name + '();'
else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name + '(' + createArgs() + ');';
return s;
@@ -393,23 +419,27 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
return 'var ' + n1 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ', ' + n2 + ' = ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
}
case STMT_RETURN_ETC:
switch (rng(3)) {
switch (rng(8)) {
case 0:
case 1:
case 2:
case 3:
if (canBreak && rng(5) === 0) return 'break;';
if (canContinue && rng(5) === 0) return 'continue;';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '/*3*/return;';
case 2:
// must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ /*1*/ return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
default:
if (rng(3) == 0) return '/*3*/return;';
return 'return ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
case 4:
// 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
// note: you can't `throw` without an expression so don't put a `throw` option in this case
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ /*2*/ return\n' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
default:
// must wrap in curlies to prevent orphaned `else` statement
if (canThrow && rng(5) === 0) return '{ throw ' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + '}';
if (cannotReturn) return createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ';';
return '{ return ' + 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 ..."
@@ -463,36 +493,44 @@ function createSwitchParts(recurmax, n, canThrow, canBreak, canContinue, cannotR
function createExpression(recurmax, noComma, stmtDepth, canThrow) {
if (--recurmax < 0) {
return '(c = 1 + c, ' + createNestedBinaryExpr(recurmax, noComma) + ')'; // note: should return a simple non-recursing expression value!
return '(c = 1 + c, ' + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')'; // note: should return a simple non-recursing expression value!
}
// 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);
switch (rng(6)) {
case 0:
return '(a++ + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))';
case 1:
return '((--b) + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))';
case 2:
return '((c = c + 1) + (' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + '))'; // c only gets incremented
default:
return '(' + _createExpression(recurmax, noComma, stmtDepth, canThrow) + ')';
}
}
function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
switch (rng(15)) {
case 0:
return createUnaryOp() + (rng(2) === 1 ? 'a' : 'b');
case 1:
return 'a' + (rng(2) == 1 ? '++' : '--');
case 2:
var p = 0;
switch (rng(_createExpression.N)) {
case p++:
case p++:
return createUnaryPrefix() + (rng(2) === 1 ? 'a' : 'b');
case p++:
case p++:
return (rng(2) === 1 ? 'a' : 'b') + createUnaryPostfix();
case p++:
case p++:
// parens needed because assignments aren't valid unless they're the left-most op(s) in an expression
return '(b ' + createAssignment() + ' a)';
case 3:
return 'b ' + createAssignment() + ' a';
case p++:
case p++:
return rng(2) + ' === 1 ? a : b';
case 4:
return createNestedBinaryExpr(recurmax, noComma) + createBinaryOp(noComma) + createExpression(recurmax, noComma, stmtDepth, canThrow);
case 5:
case p++:
case p++:
return createValue();
case 6:
return '(' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
case 7:
case p++:
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createExpression(recurmax, noComma, stmtDepth, canThrow) + '?' + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ':' + createExpression(recurmax, noComma, stmtDepth, canThrow);
case 8:
case p++:
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';
@@ -513,17 +551,18 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
}
VAR_NAMES.length = nameLenBefore;
return s;
case 9:
case p++:
case p++:
return createTypeofExpr(recurmax, stmtDepth, canThrow);
case 10:
// you could statically infer that this is just `Math`, regardless of the other expression
// I don't think Uglify does this at this time...
return ''+
'new function(){ \n' +
(rng(2) === 1 ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + '\n' : '') +
'return Math;\n' +
'}';
case 11:
case p++:
return [
'new function() {',
rng(2) ? '' : createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';',
'return ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ';',
'}'
].join('\n');
case p++:
case p++:
// more like a parser test but perhaps comment nodes mess up the analysis?
// 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.
@@ -533,58 +572,151 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case 1:
return 'b/* ignore */--';
case 2:
return '(++/* ignore */a)';
return '++/* ignore */a';
case 3:
return '(--/* ignore */b)';
return '--/* ignore */b';
case 4:
// 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...
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:
return '(--/* ignore */b)';
return '--/* ignore */b';
}
case 12:
return createNestedBinaryExpr(recurmax, noComma);
case 13:
case p++:
case p++:
return createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
case p++:
case p++:
return createUnarySafePrefix() + '(' + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
case 14:
case p++:
return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) +
") || " + rng(10) + ").toString()[" +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] ";
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '[' +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
return createArrayLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
case p++:
return createObjectLiteral(recurmax, COMMA_OK, stmtDepth, canThrow) + '.' + getDotKey();
case p++:
var name = getVarName();
return name + ' && ' + name + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ']';
case p++:
var name = getVarName();
return name + ' && ' + name + '.' + getDotKey();
}
_createExpression.N = p;
return _createExpression(recurmax, noComma, stmtDepth, canThrow);
}
function createNestedBinaryExpr(recurmax, noComma) {
recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it
return _createSimpleBinaryExpr(recurmax, noComma);
function createArrayLiteral(recurmax, noComma, stmtDepth, canThrow) {
recurmax--;
var arr = "[";
for (var i = rng(6); --i >= 0;) {
// in rare cases produce an array hole element
var element = rng(20) ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) : "";
arr += element + ", ";
}
return arr + "]";
}
function _createSimpleBinaryExpr(recurmax, noComma) {
var SAFE_KEYS = [
"length",
"foo",
"a",
"b",
"c",
"undefined",
"null",
"NaN",
"Infinity",
"in",
"var",
];
var KEYS = [
"''",
'"\t"',
'"-2"',
"0",
"1.5",
"3",
].concat(SAFE_KEYS);
function getDotKey() {
return SAFE_KEYS[rng(SAFE_KEYS.length)];
}
function createObjectLiteral(recurmax, noComma, stmtDepth, canThrow) {
recurmax--;
var obj = "({";
for (var i = rng(6); --i >= 0;) {
var key = KEYS[rng(KEYS.length)];
obj += key + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "), ";
}
return obj + "})";
}
function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it
return _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
}
function _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
return '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow)
+ createBinaryOp(noComma) + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
}
function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
// 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 + ')';
switch (rng(30)) {
case 0:
return '(c = c + 1, ' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case 1:
return '(' + createUnarySafePrefix() + '(' + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + '))';
case 2:
var assignee = getVarName();
return '(' + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
case 3:
var assignee = getVarName();
var expr = '(' + assignee + '[' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)
+ ']' + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
case 4:
var assignee = getVarName();
var expr = '(' + assignee + '.' + getDotKey() + createAssignment()
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ')';
return canThrow && rng(10) == 0 ? expr : '(' + assignee + ' && ' + expr + ')';
default:
return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
}
return s;
}
function createTypeofExpr(recurmax, stmtDepth, canThrow) {
switch (rng(8)) {
case 0:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"';
return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' === "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 1:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"';
return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' !== "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 2:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"';
return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' == "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 3:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE) + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '"';
return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ' != "' + TYPEOF_OUTCOMES[rng(TYPEOF_OUTCOMES.length)] + '")';
case 4:
return 'typeof ' + createVarName(MANDATORY, DONT_STORE);
return '(typeof ' + createVarName(MANDATORY, DONT_STORE) + ')';
default:
return '(typeof ' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ')';
}
@@ -603,16 +735,31 @@ function createAssignment() {
return ASSIGNMENTS[rng(ASSIGNMENTS.length)];
}
function createUnaryOp() {
return UNARY_OPS[rng(UNARY_OPS.length)];
function createUnarySafePrefix() {
return UNARY_SAFE[rng(UNARY_SAFE.length)];
}
function createUnaryPrefix() {
return UNARY_PREFIX[rng(UNARY_PREFIX.length)];
}
function createUnaryPostfix() {
return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)];
}
function getVarName() {
// try to get a generated name reachable from current scope. default to just `a`
return VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)] || 'a';
}
function createVarName(maybe, dontStore) {
if (!maybe || rng(2) === 1) {
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);
if (!maybe || rng(2)) {
var name = VAR_NAMES[rng(VAR_NAMES.length)];
var suffix = rng(3);
if (suffix) {
name += '_' + suffix;
if (!dontStore) VAR_NAMES.push(name);
}
return name;
}
return '';
@@ -755,6 +902,20 @@ for (var round = 1; round <= num_iterations; round++) {
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);
else if (verbose_error && typeof original_result != "string") {
console.log("//=============================================================");
console.log("// original code");
try_beautify(original_code, original_result);
console.log();
console.log();
console.log("original result:");
console.log(original_result);
console.log();
}
if (!ok && isFinite(num_iterations)) {
console.log();
process.exit(1);
}
});
}
console.log();

View File

@@ -1,19 +1,4 @@
[
{
"compress": {
"warnings": false
}
},
{
"compress": {
"warnings": false
},
"mangle": false
},
{
"compress": false,
"mangle": true
},
{
"compress": false,
"mangle": false,
@@ -22,11 +7,33 @@
"bracketize": true
}
},
{
"compress": false
},
{
"compress": {
"warnings": false
},
"mangle": false
},
{
"compress": {
"warnings": false
}
},
{
"compress": {
"toplevel": true,
"warnings": false
},
"mangle": {
"toplevel": true
}
},
{
"compress": {
"keep_fargs": false,
"passes": 3,
"pure_getters": true,
"warnings": false
}
}