Compare commits

...

15 Commits

Author SHA1 Message Date
Alex Lam S.L
70bb304a0a v3.6.0 2019-05-30 15:30:00 +08:00
Alex Lam S.L
9d3b1efd86 fix corner case in assignments (#3430)
fixes #3429
2019-05-30 05:01:53 +08:00
Alex Lam S.L
482e1baea3 enhance assignments & unused (#3428)
closes #3427
2019-05-29 01:21:08 +08:00
Alex Lam S.L
e4f5ba1d29 v3.5.15 2019-05-21 14:26:58 +08:00
Alex Lam S.L
b9053c7a25 fix corner case in keep_fargs (#3424)
fixes #3423
2019-05-21 12:55:34 +08:00
Alex Lam S.L
d357a7aabc v3.5.14 2019-05-20 00:13:06 +08:00
Alex Lam S.L
ae77ebe5a5 fix corner case in arguments (#3421)
fixes #3420
2019-05-19 12:59:40 +08:00
Alex Lam S.L
04439edcec v3.5.13 2019-05-17 14:10:33 +08:00
Alex Lam S.L
a246195412 enhance unsafe comparisons (#3419) 2019-05-17 01:28:18 +08:00
Alex Lam S.L
8939a36bc7 reduce false positives from fuzzing (#3417) 2019-05-16 16:15:03 +08:00
Alex Lam S.L
a21c348d93 improve sandbox fidelity (#3415) 2019-05-15 23:26:57 +08:00
Alex Lam S.L
1f0def10eb fix corner case in comparisons (#3414)
fixes #3413
2019-05-15 01:01:18 +08:00
Alex Lam S.L
f87caac9d8 fix corner case in hoist_props (#3412)
fixes #3411
2019-05-14 19:12:00 +08:00
Alex Lam S.L
d538a73250 enhance side_effects (#3410) 2019-05-14 05:26:40 +08:00
Alex Lam S.L
2e4fbdeb08 enhance keep_fargs (#3409) 2019-05-13 21:58:04 +08:00
19 changed files with 1994 additions and 197 deletions

View File

@@ -1,6 +1,6 @@
UglifyJS is released under the BSD license: UglifyJS is released under the BSD license:
Copyright 2012-2018 (c) Mihai Bazon <mihai.bazon@gmail.com> Copyright 2012-2019 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions modification, are permitted provided that the following conditions

View File

@@ -664,8 +664,9 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
- `join_vars` (default: `true`) -- join consecutive `var` statements - `join_vars` (default: `true`) -- join consecutive `var` statements
- `keep_fargs` (default: `true`) -- Prevents the compressor from discarding unused - `keep_fargs` (default: `strict`) -- Discard unused function arguments. Code
function arguments. You need this for code which relies on `Function.length`. which relies on `Function.length` will break if this is done indiscriminately,
i.e. when passing `true`. Pass `false` to always retain function arguments.
- `keep_fnames` (default: `false`) -- Pass `true` to prevent the - `keep_fnames` (default: `false`) -- Pass `true` to prevent the
compressor from discarding function names. Useful for code relying on compressor from discarding function names. Useful for code relying on

View File

@@ -376,7 +376,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
} }
}, AST_Scope); }, AST_Scope);
var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", { var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
$documentation: "Base class for functions", $documentation: "Base class for functions",
$propdoc: { $propdoc: {
name: "[AST_SymbolDeclaration?] the name of this function", name: "[AST_SymbolDeclaration?] the name of this function",
@@ -614,6 +614,18 @@ var AST_PropAccess = DEFNODE("PropAccess", "expression property", {
$propdoc: { $propdoc: {
expression: "[AST_Node] the “container” expression", expression: "[AST_Node] the “container” expression",
property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node" property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node"
},
getProperty: function() {
var p = this.property;
if (p instanceof AST_Constant) {
return p.getValue();
}
if (p instanceof AST_UnaryPrefix
&& p.operator == "void"
&& p.expression instanceof AST_Constant) {
return;
}
return p;
} }
}); });

View File

@@ -69,7 +69,7 @@ function Compressor(options, false_by_default) {
if_return : !false_by_default, if_return : !false_by_default,
inline : !false_by_default, inline : !false_by_default,
join_vars : !false_by_default, join_vars : !false_by_default,
keep_fargs : true, keep_fargs : false_by_default || "strict",
keep_fnames : false, keep_fnames : false,
keep_infinity : false, keep_infinity : false,
loops : !false_by_default, loops : !false_by_default,
@@ -104,6 +104,17 @@ function Compressor(options, false_by_default) {
} }
} }
if (this.options["inline"] === true) this.options["inline"] = 3; if (this.options["inline"] === true) this.options["inline"] = 3;
var keep_fargs = this.options["keep_fargs"];
this.drop_fargs = keep_fargs == "strict" ? function(lambda, parent) {
if (lambda.length_read) return false;
var name = lambda.name;
if (!name) return parent && parent.TYPE == "Call" && parent.expression === lambda;
if (name.fixed_value() !== lambda) return false;
var def = name.definition();
if (def.direct_access) return false;
var escaped = def.escaped;
return escaped && escaped.depth != 1;
} : keep_fargs ? return_false : return_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") {
this.pure_funcs = pure_funcs; this.pure_funcs = pure_funcs;
@@ -118,6 +129,8 @@ function Compressor(options, false_by_default) {
} else { } else {
this.pure_funcs = return_true; this.pure_funcs = return_true;
} }
var sequences = this.options["sequences"];
this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
var top_retain = this.options["top_retain"]; var top_retain = this.options["top_retain"];
if (top_retain instanceof RegExp) { if (top_retain instanceof RegExp) {
this.top_retain = function(def) { this.top_retain = function(def) {
@@ -141,8 +154,6 @@ function Compressor(options, false_by_default) {
funcs: toplevel, funcs: toplevel,
vars: toplevel vars: toplevel
}; };
var sequences = this.options["sequences"];
this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
} }
Compressor.prototype = new TreeTransformer; Compressor.prototype = new TreeTransformer;
@@ -272,14 +283,19 @@ merge(Compressor.prototype, {
self.transform(tt); self.transform(tt);
}); });
function read_property(obj, key) { function read_property(obj, node) {
key = get_value(key); var key = node.getProperty();
if (key instanceof AST_Node) return; if (key instanceof AST_Node) return;
var value; var value;
if (obj instanceof AST_Array) { if (obj instanceof AST_Array) {
var elements = obj.elements; var elements = obj.elements;
if (key == "length") return make_node_from_constant(elements.length, obj); if (key == "length") return make_node_from_constant(elements.length, obj);
if (typeof key == "number" && key in elements) value = elements[key]; if (typeof key == "number" && key in elements) value = elements[key];
} else if (obj instanceof AST_Lambda) {
if (key == "length") {
obj.length_read = true;
return make_node_from_constant(obj.argnames.length, obj);
}
} else if (obj instanceof AST_Object) { } else if (obj instanceof AST_Object) {
key = "" + key; key = "" + key;
var props = obj.properties; var props = obj.properties;
@@ -326,11 +342,17 @@ merge(Compressor.prototype, {
return is_modified(compressor, tw, obj, obj, level + 2); return is_modified(compressor, tw, obj, obj, level + 2);
} }
if (parent instanceof AST_PropAccess && parent.expression === node) { if (parent instanceof AST_PropAccess && parent.expression === node) {
var prop = read_property(value, parent.property); var prop = read_property(value, parent);
return !immutable && is_modified(compressor, tw, parent, prop, level + 1); return !immutable && is_modified(compressor, tw, parent, prop, level + 1);
} }
} }
function is_arguments(def) {
if (def.name != "arguments") return false;
var orig = def.orig;
return orig.length == 1 && orig[0] instanceof AST_SymbolFunarg;
}
(function(def) { (function(def) {
def(AST_Node, noop); def(AST_Node, noop);
@@ -428,9 +450,9 @@ merge(Compressor.prototype, {
if (def.single_use == "m") return false; if (def.single_use == "m") return false;
if (tw.safe_ids[def.id]) { if (tw.safe_ids[def.id]) {
if (def.fixed == null) { if (def.fixed == null) {
var orig = def.orig[0]; if (is_arguments(def)) return false;
if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false; if (def.global && def.name == "arguments") return false;
def.fixed = make_node(AST_Undefined, orig); def.fixed = make_node(AST_Undefined, def.orig);
} }
return true; return true;
} }
@@ -490,23 +512,24 @@ merge(Compressor.prototype, {
var obj = tw.parent(level + 1); var obj = tw.parent(level + 1);
mark_escaped(tw, d, scope, obj, obj, level + 2, depth); mark_escaped(tw, d, scope, obj, obj, level + 2, depth);
} else if (parent instanceof AST_PropAccess && node === parent.expression) { } else if (parent instanceof AST_PropAccess && node === parent.expression) {
value = read_property(value, parent.property); value = read_property(value, parent);
mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1); mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1);
if (value) return; if (value) return;
} }
if (level > 0) return; if (level > 0) return;
if (parent instanceof AST_Call && node === parent.expression) return;
if (parent instanceof AST_Sequence && node !== parent.tail_node()) return; if (parent instanceof AST_Sequence && node !== parent.tail_node()) return;
if (parent instanceof AST_SimpleStatement) return; if (parent instanceof AST_SimpleStatement) return;
if (parent instanceof AST_Unary && !unary_side_effects[parent.operator]) return;
d.direct_access = true; d.direct_access = true;
} }
function mark_assignment_to_arguments(node) { function mark_assignment_to_arguments(node) {
if (!(node instanceof AST_Sub)) return; if (!(node instanceof AST_Sub)) return;
var expr = node.expression; var expr = node.expression;
var prop = node.property; if (!(expr instanceof AST_SymbolRef)) return;
if (expr instanceof AST_SymbolRef && expr.name == "arguments" && prop instanceof AST_Number) { var def = expr.definition();
expr.definition().reassigned = true; if (is_arguments(def) && node.property instanceof AST_Number) def.reassigned = true;
}
} }
var suppressor = new TreeWalker(function(node) { var suppressor = new TreeWalker(function(node) {
@@ -773,7 +796,7 @@ merge(Compressor.prototype, {
}); });
def(AST_Unary, function(tw, descend) { def(AST_Unary, function(tw, descend) {
var node = this; var node = this;
if (node.operator != "++" && node.operator != "--") return; if (!unary_arithmetic[node.operator]) return;
var exp = node.expression; var exp = node.expression;
if (!(exp instanceof AST_SymbolRef)) { if (!(exp instanceof AST_SymbolRef)) {
mark_assignment_to_arguments(exp); mark_assignment_to_arguments(exp);
@@ -869,9 +892,13 @@ merge(Compressor.prototype, {
return orig.length == 1 && orig[0] instanceof AST_SymbolLambda; return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
}); });
function is_lhs_read_only(lhs) { function is_lhs_read_only(lhs, compressor) {
if (lhs instanceof AST_This) return true; if (lhs instanceof AST_This) return true;
if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda; if (lhs instanceof AST_SymbolRef) {
var def = lhs.definition();
return def.orig[0] instanceof AST_SymbolLambda
|| compressor.exposed(def) && identifier_atom[def.name];
}
if (lhs instanceof AST_PropAccess) { if (lhs instanceof AST_PropAccess) {
lhs = lhs.expression; lhs = lhs.expression;
if (lhs instanceof AST_SymbolRef) { if (lhs instanceof AST_SymbolRef) {
@@ -880,7 +907,7 @@ merge(Compressor.prototype, {
} }
if (!lhs) return true; if (!lhs) return true;
if (lhs.is_constant()) return true; if (lhs.is_constant()) return true;
return is_lhs_read_only(lhs); return is_lhs_read_only(lhs, compressor);
} }
return false; return false;
} }
@@ -1195,7 +1222,7 @@ merge(Compressor.prototype, {
var stop_if_hit = null; var stop_if_hit = null;
var lhs = get_lhs(candidate); var lhs = get_lhs(candidate);
var side_effects = lhs && lhs.has_side_effects(compressor); var side_effects = lhs && lhs.has_side_effects(compressor);
var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs); var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor);
var scan_rhs = foldable(get_rhs(candidate)); var scan_rhs = foldable(get_rhs(candidate));
if (!scan_lhs && !scan_rhs) continue; if (!scan_lhs && !scan_rhs) continue;
// Locate symbols which may execute code outside of scanning range // Locate symbols which may execute code outside of scanning range
@@ -1419,7 +1446,7 @@ merge(Compressor.prototype, {
extract_candidates(expr.expression); extract_candidates(expr.expression);
expr.body.forEach(extract_candidates); expr.body.forEach(extract_candidates);
} else if (expr instanceof AST_Unary) { } else if (expr instanceof AST_Unary) {
if (expr.operator == "++" || expr.operator == "--") { if (unary_arithmetic[expr.operator]) {
candidates.push(hit_stack.slice()); candidates.push(hit_stack.slice());
} else { } else {
extract_candidates(expr.expression); extract_candidates(expr.expression);
@@ -1489,9 +1516,9 @@ merge(Compressor.prototype, {
function mangleable_var(var_def) { function mangleable_var(var_def) {
var value = var_def.value; var value = var_def.value;
if (!(value instanceof AST_SymbolRef)) return; if (!(value instanceof AST_SymbolRef)) return;
if (value.name == "arguments") return;
var def = value.definition(); var def = value.definition();
if (def.undeclared) return; if (def.undeclared) return;
if (is_arguments(def)) return;
return value_def = def; return value_def = def;
} }
@@ -2217,18 +2244,6 @@ merge(Compressor.prototype, {
})); }));
} }
function get_value(key) {
if (key instanceof AST_Constant) {
return key.getValue();
}
if (key instanceof AST_UnaryPrefix
&& key.operator == "void"
&& key.expression instanceof AST_Constant) {
return;
}
return key;
}
function is_undefined(node, compressor) { function is_undefined(node, compressor) {
return node.is_undefined return node.is_undefined
|| node instanceof AST_Undefined || node instanceof AST_Undefined
@@ -2267,8 +2282,7 @@ merge(Compressor.prototype, {
// returns true if this node may be null, undefined or contain `AST_Accessor` // returns true if this node may be null, undefined or contain `AST_Accessor`
(function(def) { (function(def) {
AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
return !compressor.option("pure_getters") return !compressor.option("pure_getters") || this._dot_throw(compressor);
|| this._dot_throw(compressor);
}); });
function is_strict(compressor) { function is_strict(compressor) {
return /strict/.test(compressor.option("pure_getters")); return /strict/.test(compressor.option("pure_getters"));
@@ -2276,7 +2290,15 @@ merge(Compressor.prototype, {
def(AST_Node, is_strict); def(AST_Node, is_strict);
def(AST_Array, return_false); def(AST_Array, return_false);
def(AST_Assign, function(compressor) { def(AST_Assign, function(compressor) {
return this.operator == "=" && this.right._dot_throw(compressor); if (this.operator != "=") return false;
var rhs = this.right;
if (!rhs._dot_throw(compressor)) return false;
var sym = this.left;
if (!(sym instanceof AST_SymbolRef)) return true;
if (rhs instanceof AST_Binary && rhs.operator == "||" && sym.name == rhs.left.name) {
return rhs.right._dot_throw(compressor);
}
return true;
}); });
def(AST_Binary, function(compressor) { def(AST_Binary, function(compressor) {
return lazy_op[this.operator] && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor)); return lazy_op[this.operator] && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor));
@@ -2307,6 +2329,7 @@ merge(Compressor.prototype, {
if (!is_strict(compressor)) return false; if (!is_strict(compressor)) return false;
if (is_undeclared_ref(this) && this.is_declared(compressor)) return false; if (is_undeclared_ref(this) && this.is_declared(compressor)) return false;
if (this.is_immutable()) return false; if (this.is_immutable()) return false;
if (is_arguments(this.definition())) return false;
var fixed = this.fixed_value(); var fixed = this.fixed_value();
if (!fixed) return true; if (!fixed) return true;
this._dot_throw = return_true; this._dot_throw = return_true;
@@ -2334,7 +2357,7 @@ merge(Compressor.prototype, {
case "&&": case "&&":
return this.left.is_defined(compressor) && this.right.is_defined(compressor); return this.left.is_defined(compressor) && this.right.is_defined(compressor);
case "||": case "||":
return this.left.is_defined(compressor) || this.right.is_defined(compressor); return this.left.is_truthy() || this.right.is_defined(compressor);
default: default:
return true; return true;
} }
@@ -2354,7 +2377,7 @@ merge(Compressor.prototype, {
if (this.is_immutable()) return true; if (this.is_immutable()) return true;
var fixed = this.fixed_value(); var fixed = this.fixed_value();
if (!fixed) return false; if (!fixed) return false;
this.is_defined = return_true; this.is_defined = return_false;
var result = fixed.is_defined(compressor); var result = fixed.is_defined(compressor);
delete this.is_defined; delete this.is_defined;
return result; return result;
@@ -2549,6 +2572,7 @@ merge(Compressor.prototype, {
}); });
var lazy_op = makePredicate("&& ||"); var lazy_op = makePredicate("&& ||");
var unary_arithmetic = makePredicate("++ --");
var unary_side_effects = makePredicate("delete ++ --"); var unary_side_effects = makePredicate("delete ++ --");
function is_lhs(node, parent) { function is_lhs(node, parent) {
@@ -3601,7 +3625,7 @@ merge(Compressor.prototype, {
var value = null; var value = null;
if (node instanceof AST_Assign) { if (node instanceof AST_Assign) {
if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) { if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) {
value = node.right; value = get_rhs(node);
} }
} else if (!in_use) { } else if (!in_use) {
value = make_node(AST_Number, node, { value = make_node(AST_Number, node, {
@@ -3621,7 +3645,7 @@ merge(Compressor.prototype, {
node.name = null; node.name = null;
} }
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) { if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
var trim = !compressor.option("keep_fargs"); var trim = compressor.drop_fargs(node, parent);
for (var a = node.argnames, i = a.length; --i >= 0;) { for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i]; var sym = a[i];
if (!(sym.definition().id in in_use_ids)) { if (!(sym.definition().id in in_use_ids)) {
@@ -3812,6 +3836,7 @@ merge(Compressor.prototype, {
}; };
} }
}); });
tt.push(compressor.parent());
self.transform(tt); self.transform(tt);
function verify_safe_usage(def, read, modified) { function verify_safe_usage(def, read, modified) {
@@ -3825,6 +3850,15 @@ merge(Compressor.prototype, {
} }
} }
function get_rhs(assign) {
var rhs = assign.right;
if (!assign.write_only) return rhs;
if (!(rhs instanceof AST_Binary && lazy_op[rhs.operator])) return rhs;
var sym = assign.left;
if (!(sym instanceof AST_SymbolRef) || sym.name != rhs.left.name) return rhs;
return rhs.right.has_side_effects(compressor) ? rhs : rhs.right;
}
function scan_ref_scoped(node, descend) { function scan_ref_scoped(node, descend) {
var node_def, props = [], sym = assign_as_unused(node, props); var node_def, props = [], sym = assign_as_unused(node, props);
if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) { if (sym && self.variables.get(sym.name) === (node_def = sym.definition())) {
@@ -3832,9 +3866,10 @@ merge(Compressor.prototype, {
prop.walk(tw); prop.walk(tw);
}); });
if (node instanceof AST_Assign) { if (node instanceof AST_Assign) {
node.right.walk(tw); var right = get_rhs(node);
right.walk(tw);
if (node.left === sym) { if (node.left === sym) {
if (!node_def.chained && sym.fixed_value(true) === node.right) { if (!node_def.chained && sym.fixed_value(true) === right) {
fixed_ids[node_def.id] = node; fixed_ids[node_def.id] = node;
} }
if (!node.write_only) { if (!node.write_only) {
@@ -4052,6 +4087,16 @@ merge(Compressor.prototype, {
})); }));
return make_sequence(node, assignments); return make_sequence(node, assignments);
} }
if (node instanceof AST_Unary
&& !unary_side_effects[node.operator]
&& node.expression instanceof AST_SymbolRef
&& node.expression.definition().id in defs_by_id) {
node = node.clone();
node.expression = make_node(AST_Object, node, {
properties: []
});
return node;
}
if (node instanceof AST_VarDef && can_hoist(node.name, node.value, 0)) { if (node instanceof AST_VarDef && can_hoist(node.name, node.value, 0)) {
descend(node, this); descend(node, this);
var defs = new Dictionary(); var defs = new Dictionary();
@@ -4068,7 +4113,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) { if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) {
var defs = defs_by_id[node.expression.definition().id]; var defs = defs_by_id[node.expression.definition().id];
if (defs) { if (defs) {
var def = defs.get(get_value(node.property)); var def = defs.get(node.getProperty());
var sym = make_node(AST_SymbolRef, node, { var sym = make_node(AST_SymbolRef, node, {
name: def.name, name: def.name,
scope: node.expression.scope, scope: node.expression.scope,
@@ -4135,12 +4180,14 @@ merge(Compressor.prototype, {
}); });
def(AST_Assign, function(compressor) { def(AST_Assign, function(compressor) {
var left = this.left; var left = this.left;
if (left.has_side_effects(compressor) if (left instanceof AST_PropAccess) {
|| compressor.has_directive("use strict") var expr = left.expression;
&& left instanceof AST_PropAccess if (expr instanceof AST_Assign && !expr.may_throw_on_access(compressor)) {
&& left.expression.is_constant()) { expr.write_only = true;
return this;
} }
if (compressor.has_directive("use strict") && expr.is_constant()) return this;
}
if (left.has_side_effects(compressor)) return this;
this.write_only = true; this.write_only = true;
if (root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) { if (root_expr(left).is_constant_expression(compressor.find_parent(AST_Scope))) {
return this.right.drop_side_effect_free(compressor); return this.right.drop_side_effect_free(compressor);
@@ -4215,8 +4262,9 @@ merge(Compressor.prototype, {
}); });
def(AST_Constant, return_null); def(AST_Constant, return_null);
def(AST_Dot, function(compressor, first_in_statement) { def(AST_Dot, function(compressor, first_in_statement) {
if (this.expression.may_throw_on_access(compressor)) return this; var expr = this.expression;
return this.expression.drop_side_effect_free(compressor, first_in_statement); if (expr.may_throw_on_access(compressor)) return this;
return expr.drop_side_effect_free(compressor, first_in_statement);
}); });
def(AST_Function, function(compressor) { def(AST_Function, function(compressor) {
return this.name && compressor.option("ie8") ? this : null; return this.name && compressor.option("ie8") ? this : null;
@@ -4406,8 +4454,9 @@ merge(Compressor.prototype, {
OPT(AST_For, function(self, compressor) { OPT(AST_For, function(self, compressor) {
if (!compressor.option("loops")) return self; if (!compressor.option("loops")) return self;
if (compressor.option("side_effects") && self.init) { if (compressor.option("side_effects")) {
self.init = self.init.drop_side_effect_free(compressor); if (self.init) self.init = self.init.drop_side_effect_free(compressor);
if (self.step) self.step = self.step.drop_side_effect_free(compressor);
} }
if (self.condition) { if (self.condition) {
var cond = self.condition.evaluate(compressor); var cond = self.condition.evaluate(compressor);
@@ -5527,20 +5576,29 @@ merge(Compressor.prototype, {
self.right = tmp; self.right = tmp;
} }
} }
if (commutativeOperators[self.operator]) { if (commutativeOperators[self.operator] && self.right.is_constant() && !self.left.is_constant()) {
if (self.right.is_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.
if (!(self.left instanceof AST_Binary if (!(self.left instanceof AST_Binary
&& PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) { && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
reverse(); reverse();
} }
} }
}
self = self.lift_sequences(compressor); self = self.lift_sequences(compressor);
if (compressor.option("assignments") && lazy_op[self.operator]) {
var assign = self.right;
// a || (a = x) => a = a || x
// a && (a = x) => a = a && x
if (self.left instanceof AST_SymbolRef
&& assign instanceof AST_Assign
&& assign.operator == "="
&& self.left.equivalent_to(assign.left)) {
self.right = assign.right;
assign.right = self;
return assign;
}
}
if (compressor.option("comparisons")) switch (self.operator) { if (compressor.option("comparisons")) switch (self.operator) {
case "===": case "===":
case "!==": case "!==":
@@ -5938,11 +5996,10 @@ merge(Compressor.prototype, {
} }
} }
} }
if (compressor.option("unsafe") if (compressor.option("unsafe")) {
&& self.right instanceof AST_Call var indexRight = is_indexFn(self.right);
&& self.right.expression instanceof AST_Dot
&& indexFns[self.right.expression.property]) {
if (compressor.option("booleans") if (compressor.option("booleans")
&& indexRight
&& (self.operator == "==" || self.operator == "!=") && (self.operator == "==" || self.operator == "!=")
&& self.left instanceof AST_Number && self.left instanceof AST_Number
&& self.left.getValue() == 0 && self.left.getValue() == 0
@@ -5952,18 +6009,26 @@ merge(Compressor.prototype, {
expression: self.right expression: self.right
}) : self.right).optimize(compressor); }) : self.right).optimize(compressor);
} }
var indexLeft = is_indexFn(self.left);
if (compressor.option("comparisons") && is_indexOf_match_pattern()) { if (compressor.option("comparisons") && is_indexOf_match_pattern()) {
var node = make_node(AST_UnaryPrefix, self, { var node = make_node(AST_UnaryPrefix, self, {
operator: "!", operator: "!",
expression: make_node(AST_UnaryPrefix, self, { expression: make_node(AST_UnaryPrefix, self, {
operator: "~", operator: "~",
expression: self.right expression: indexLeft ? self.left : self.right
}) })
}); });
if (self.operator == "!=" || self.operator == "<=") node = make_node(AST_UnaryPrefix, self, { switch (self.operator) {
case "<":
if (indexLeft) break;
case "<=":
case "!=":
node = make_node(AST_UnaryPrefix, self, {
operator: "!", operator: "!",
expression: node expression: node
}); });
break;
}
return node.optimize(compressor); return node.optimize(compressor);
} }
} }
@@ -6001,17 +6066,26 @@ merge(Compressor.prototype, {
return node.evaluate(compressor); return node.evaluate(compressor);
} }
function is_indexFn(node) {
return node instanceof AST_Call
&& node.expression instanceof AST_Dot
&& indexFns[node.expression.property];
}
function is_indexOf_match_pattern() { function is_indexOf_match_pattern() {
switch (self.operator) { switch (self.operator) {
case ">":
case "<=": case "<=":
// 0 > array.indexOf(string) => !~array.indexOf(string)
// 0 <= array.indexOf(string) => !!~array.indexOf(string) // 0 <= array.indexOf(string) => !!~array.indexOf(string)
return self.left instanceof AST_Number && self.left.getValue() == 0; return indexRight && self.left instanceof AST_Number && self.left.getValue() == 0;
case "<":
// array.indexOf(string) < 0 => !~array.indexOf(string)
if (indexLeft && self.right instanceof AST_Number && self.right.getValue() == 0) return true;
// -1 < array.indexOf(string) => !!~array.indexOf(string)
case "==": case "==":
case "!=": case "!=":
// -1 == array.indexOf(string) => !~array.indexOf(string) // -1 == array.indexOf(string) => !~array.indexOf(string)
// -1 != array.indexOf(string) => !!~array.indexOf(string) // -1 != array.indexOf(string) => !!~array.indexOf(string)
if (!indexRight) return false;
return self.left instanceof AST_Number && self.left.getValue() == -1 return self.left instanceof AST_Number && self.left.getValue() == -1
|| self.left instanceof AST_UnaryPrefix && self.left.operator == "-" || self.left instanceof AST_UnaryPrefix && self.left.operator == "-"
&& self.left.expression instanceof AST_Number && self.left.expression.getValue() == 1; && self.left.expression instanceof AST_Number && self.left.expression.getValue() == 1;
@@ -6635,24 +6709,30 @@ merge(Compressor.prototype, {
} }
} }
} }
var fn; var parent = compressor.parent();
var def, fn, fn_parent;
if (compressor.option("arguments") if (compressor.option("arguments")
&& expr instanceof AST_SymbolRef && expr instanceof AST_SymbolRef
&& expr.name == "arguments" && is_arguments(def = expr.definition())
&& expr.definition().orig.length == 1
&& prop instanceof AST_Number && prop instanceof AST_Number
&& (fn = expr.scope) === compressor.find_parent(AST_Lambda)) { && (fn = expr.scope) === find_lambda()) {
var index = prop.getValue(); var index = prop.getValue();
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete") {
if (!def.deleted) def.deleted = [];
def.deleted[index] = true;
}
var argname = fn.argnames[index]; var argname = fn.argnames[index];
if (argname && compressor.has_directive("use strict")) { if (def.deleted && def.deleted[index]) {
var def = argname.definition(); argname = null;
} else if (argname && compressor.has_directive("use strict")) {
var arg_def = argname.definition();
if (!compressor.option("reduce_vars") if (!compressor.option("reduce_vars")
|| expr.definition().reassigned || def.reassigned
|| def.assignments || arg_def.assignments
|| def.orig.length > 1) { || arg_def.orig.length > 1) {
argname = null; argname = null;
} }
} else if (!argname && !compressor.option("keep_fargs") && index < fn.argnames.length + 5) { } else if (!argname && index < fn.argnames.length + 5 && compressor.drop_fargs(fn, fn_parent)) {
while (index >= fn.argnames.length) { while (index >= fn.argnames.length) {
argname = make_node(AST_SymbolFunarg, fn, { argname = make_node(AST_SymbolFunarg, fn, {
name: fn.make_var_name("argument_" + fn.argnames.length), name: fn.make_var_name("argument_" + fn.argnames.length),
@@ -6665,13 +6745,14 @@ merge(Compressor.prototype, {
if (argname && find_if(function(node) { if (argname && find_if(function(node) {
return node.name === argname.name; return node.name === argname.name;
}, fn.argnames) === argname) { }, fn.argnames) === argname) {
def.reassigned = false;
var sym = make_node(AST_SymbolRef, self, argname); var sym = make_node(AST_SymbolRef, self, argname);
sym.reference({}); sym.reference({});
delete argname.__unused; delete argname.__unused;
return sym; return sym;
} }
} }
if (is_lhs(compressor.self(), compressor.parent())) return self; if (is_lhs(compressor.self(), parent)) return self;
if (key !== prop) { if (key !== prop) {
var sub = self.flatten_object(property, compressor); var sub = self.flatten_object(property, compressor);
if (sub) { if (sub) {
@@ -6720,6 +6801,16 @@ merge(Compressor.prototype, {
return best_of(compressor, ev, self); return best_of(compressor, ev, self);
} }
return self; return self;
function find_lambda() {
var i = 0, p;
while (p = compressor.parent(i++)) {
if (p instanceof AST_Lambda) {
fn_parent = compressor.parent(i);
return p;
}
}
}
}); });
AST_Scope.DEFMETHOD("contains_this", function() { AST_Scope.DEFMETHOD("contains_this", function() {

View File

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

View File

@@ -1,34 +1,46 @@
#! /usr/bin/env node
var assert = require("assert"); var assert = require("assert");
var child_process = require("child_process");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var sandbox = require("./sandbox"); var sandbox = require("./sandbox");
var semver = require("semver"); var semver = require("semver");
var U = require("./node"); var U = require("./node");
var failures = 0; var file = process.argv[2];
var failed_files = Object.create(null);
var minify_options = require("./ufuzz.json").map(JSON.stringify);
var dir = path.resolve(path.dirname(module.filename), "compress"); var dir = path.resolve(path.dirname(module.filename), "compress");
fs.readdirSync(dir).filter(function(name) { if (file) {
return /\.js$/i.test(name); var minify_options = require("./ufuzz.json").map(JSON.stringify);
}).forEach(function(file) {
log("--- {file}", { file: file }); log("--- {file}", { file: file });
var tests = parse_test(path.resolve(dir, file)); var tests = parse_test(path.resolve(dir, file));
for (var i in tests) if (!test_case(tests[i])) { process.exit(Object.keys(tests).filter(function(name) {
failures++; return !test_case(tests[name]);
failed_files[file] = 1; }).length);
} } else {
var files = fs.readdirSync(dir).filter(function(name) {
return /\.js$/i.test(name);
}); });
if (failures) { var failures = 0;
var failed_files = Object.create(null);
(function next() {
var file = files.shift();
if (file) {
child_process.spawn(process.argv[0], [ process.argv[1], file ], {
stdio: [ "ignore", 1, 2 ]
}).on("exit", function(code) {
if (code) {
failures += code;
failed_files[file] = code;
}
next();
});
} else if (failures) {
console.error(); console.error();
console.error("!!! Failed " + failures + " test case(s)."); console.error("!!! Failed " + failures + " test case(s).");
console.error("!!! " + Object.keys(failed_files).join(", ")); console.error("!!! " + Object.keys(failed_files).join(", "));
process.exit(1); process.exit(1);
} }
})();
/* -----[ utils ]----- */ }
function evaluate(code) { function evaluate(code) {
if (code instanceof U.AST_Node) code = make_code(code, { beautify: true }); if (code instanceof U.AST_Node) code = make_code(code, { beautify: true });
@@ -160,7 +172,7 @@ function parse_test(file) {
// Try to reminify original input with standard options // Try to reminify original input with standard options
// to see if it matches expect_stdout. // to see if it matches expect_stdout.
function reminify(orig_options, input_code, input_formatted, expect_stdout) { function reminify(orig_options, input_code, input_formatted, stdout) {
for (var i = 0; i < minify_options.length; i++) { for (var i = 0; i < minify_options.length; i++) {
var options = JSON.parse(minify_options[i]); var options = JSON.parse(minify_options[i]);
if (options.compress) [ if (options.compress) [
@@ -191,11 +203,12 @@ function reminify(orig_options, input_code, input_formatted, expect_stdout) {
}); });
return false; return false;
} else { } else {
var stdout = run_code(result.code); var expected = stdout[options.toplevel ? 1 : 0];
if (typeof expect_stdout != "string" && typeof stdout != "string" && expect_stdout.name == stdout.name) { var actual = run_code(result.code, options.toplevel);
stdout = expect_stdout; if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
actual = expected;
} }
if (!sandbox.same_stdout(expect_stdout, stdout)) { if (!sandbox.same_stdout(expected, actual)) {
log([ log([
"!!! failed running reminified input", "!!! failed running reminified input",
"---INPUT---", "---INPUT---",
@@ -214,10 +227,10 @@ function reminify(orig_options, input_code, input_formatted, expect_stdout) {
input: input_formatted, input: input_formatted,
options: options_formatted, options: options_formatted,
output: result.code, output: result.code,
expected_type: typeof expect_stdout == "string" ? "STDOUT" : "ERROR", expected_type: typeof expected == "string" ? "STDOUT" : "ERROR",
expected: expect_stdout, expected: expected,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: stdout, actual: actual,
}); });
return false; return false;
} }
@@ -226,8 +239,8 @@ function reminify(orig_options, input_code, input_formatted, expect_stdout) {
return true; return true;
} }
function run_code(code) { function run_code(code, toplevel) {
var result = sandbox.run_code(code, true); var result = sandbox.run_code(code, toplevel);
return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result; return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result;
} }
@@ -359,13 +372,14 @@ function test_case(test) {
return false; return false;
} }
} }
if (test.expect_stdout if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
&& (!test.node_version || semver.satisfies(process.version, test.node_version))) { var stdout = [ run_code(input_code), run_code(input_code, true) ];
var stdout = run_code(input_code); var toplevel = test.options.toplevel;
var actual = stdout[toplevel ? 1 : 0];
if (test.expect_stdout === true) { if (test.expect_stdout === true) {
test.expect_stdout = stdout; test.expect_stdout = actual;
} }
if (!sandbox.same_stdout(test.expect_stdout, stdout)) { if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([ log([
"!!! Invalid input or expected stdout", "!!! Invalid input or expected stdout",
"---INPUT---", "---INPUT---",
@@ -380,13 +394,13 @@ function test_case(test) {
input: input_formatted, input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR", expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout, expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: stdout, actual: actual,
}); });
return false; return false;
} }
stdout = run_code(output); actual = run_code(output, toplevel);
if (!sandbox.same_stdout(test.expect_stdout, stdout)) { if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([ log([
"!!! failed", "!!! failed",
"---INPUT---", "---INPUT---",
@@ -401,12 +415,12 @@ function test_case(test) {
input: input_formatted, input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR", expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout, expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR", actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: stdout, actual: actual,
}); });
return false; return false;
} }
if (!reminify(test.options, input_code, input_formatted, test.expect_stdout)) { if (!reminify(test.options, input_code, input_formatted, stdout)) {
return false; return false;
} }
} }

View File

@@ -405,6 +405,52 @@ issue_3273_global_strict_reduce_vars: {
] ]
} }
issue_3273_keep_fargs_false: {
options = {
arguments: true,
keep_fargs: false,
reduce_vars: true,
}
input: {
(function() {
"use strict";
arguments[0]++;
console.log(arguments[0]);
})(0);
}
expect: {
(function(argument_0) {
"use strict";
argument_0++;
console.log(argument_0);
})(0);
}
expect_stdout: "1"
}
issue_3273_keep_fargs_strict: {
options = {
arguments: true,
keep_fargs: "strict",
reduce_vars: true,
}
input: {
(function() {
"use strict";
arguments[0]++;
console.log(arguments[0]);
})(0);
}
expect: {
(function(argument_0) {
"use strict";
argument_0++;
console.log(argument_0);
})(0);
}
expect_stdout: "1"
}
issue_3282_1: { issue_3282_1: {
options = { options = {
arguments: true, arguments: true,
@@ -576,3 +622,157 @@ issue_3282_2_passes: {
} }
expect_stdout: true expect_stdout: true
} }
issue_3420_1: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
console.log(function() {
return function() {
return arguments[0];
};
}().length);
}
expect: {
console.log(function() {
return function() {
return arguments[0];
};
}().length);
}
expect_stdout: "0"
}
issue_3420_2: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
var foo = function() {
delete arguments[0];
};
foo();
}
expect: {
var foo = function() {
delete arguments[0];
};
foo();
}
expect_stdout: true
}
issue_3420_3: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
"use strict";
var foo = function() {
delete arguments[0];
};
foo();
}
expect: {
"use strict";
var foo = function() {
delete arguments[0];
};
foo();
}
expect_stdout: true
}
issue_3420_4: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
!function() {
console.log(arguments[0]);
delete arguments[0];
console.log(arguments[0]);
}(42);
}
expect: {
!function(argument_0) {
console.log(argument_0);
delete arguments[0];
console.log(arguments[0]);
}(42);
}
expect_stdout: [
"42",
"undefined",
]
}
issue_3420_5: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
"use strict";
!function() {
console.log(arguments[0]);
delete arguments[0];
console.log(arguments[0]);
}(42);
}
expect: {
"use strict";
!function(argument_0) {
console.log(argument_0);
delete arguments[0];
console.log(arguments[0]);
}(42);
}
expect_stdout: [
"42",
"undefined",
]
}
issue_3420_6: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
console.log(function() {
return delete arguments[0];
}());
}
expect: {
console.log(function() {
return delete arguments[0];
}());
}
expect_stdout: "true"
}
issue_3420_7: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
"use strict";
console.log(function() {
return delete arguments[0];
}());
}
expect: {
"use strict";
console.log(function() {
return delete arguments[0];
}());
}
expect_stdout: "true"
}

View File

@@ -311,3 +311,65 @@ issue_3375: {
} }
expect_stdout: "string" expect_stdout: "string"
} }
issue_3427: {
options = {
assignments: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var a;
a || (a = {});
})();
}
expect: {}
}
issue_3429_1: {
options = {
assignments: true,
side_effects: true,
unused: true,
}
input: {
var a = "PASS";
(function(b) {
b && (b = a = "FAIL");
})();
console.log(a);
}
expect: {
var a = "PASS";
(function(b) {
b = b && (a = "FAIL");
})();
console.log(a);
}
expect_stdout: "PASS"
}
issue_3429_2: {
options = {
assignments: true,
side_effects: true,
unused: true,
}
input: {
var a;
(function(b) {
b || (b = a = "FAIL");
})(42);
console.log(a);
}
expect: {
var a;
(function(b) {
b = b || (a = "FAIL");
})(42);
console.log(a);
}
expect_stdout: "undefined"
}

View File

@@ -6178,3 +6178,22 @@ assign_undeclared: {
"object", "object",
] ]
} }
Infinity_assignment: {
options = {
collapse_vars: true,
pure_getters: "strict",
unsafe: true,
}
input: {
var Infinity;
Infinity = 42;
console.log(Infinity);
}
expect: {
var Infinity;
Infinity = 42;
console.log(Infinity);
}
expect_stdout: true
}

View File

@@ -373,10 +373,70 @@ unsafe_indexOf: {
unsafe: true, unsafe: true,
} }
input: { input: {
if (Object.keys({ foo: 42 }).indexOf("foo") >= 0) console.log("PASS"); var a = Object.keys({ foo: 42 });
if (a.indexOf("bar") < 0) console.log("PASS");
if (0 > a.indexOf("bar")) console.log("PASS");
if (a.indexOf("foo") >= 0) console.log("PASS");
if (0 <= a.indexOf("foo")) console.log("PASS");
if (a.indexOf("foo") > -1) console.log("PASS");
if (-1 < a.indexOf("foo")) console.log("PASS");
if (a.indexOf("bar") == -1) console.log("PASS");
if (-1 == a.indexOf("bar")) console.log("PASS");
if (a.indexOf("bar") === -1) console.log("PASS");
if (-1 === a.indexOf("bar")) console.log("PASS");
if (a.indexOf("foo") != -1) console.log("PASS");
if (-1 != a.indexOf("foo")) console.log("PASS");
if (a.indexOf("foo") !== -1) console.log("PASS");
if (-1 !== a.indexOf("foo")) console.log("PASS");
} }
expect: { expect: {
if (~Object.keys({ foo: 42 }).indexOf("foo")) console.log("PASS"); var a = Object.keys({ foo: 42 });
if (!~a.indexOf("bar")) console.log("PASS");
if (!~a.indexOf("bar")) console.log("PASS");
if (~a.indexOf("foo")) console.log("PASS");
if (~a.indexOf("foo")) console.log("PASS");
if (~a.indexOf("foo")) console.log("PASS");
if (~a.indexOf("foo")) console.log("PASS");
if (!~a.indexOf("bar")) console.log("PASS");
if (!~a.indexOf("bar")) console.log("PASS");
if (!~a.indexOf("bar")) console.log("PASS");
if (!~a.indexOf("bar")) console.log("PASS");
if (~a.indexOf("foo")) console.log("PASS");
if (~a.indexOf("foo")) console.log("PASS");
if (~a.indexOf("foo")) console.log("PASS");
if (~a.indexOf("foo")) console.log("PASS");
}
expect_stdout: [
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
"PASS",
]
}
issue_3413: {
options = {
comparisons: true,
evaluate: true,
side_effects: true,
}
input: {
var b;
void 0 !== ("" < b || void 0) || console.log("PASS");
}
expect: {
var b;
void 0 !== ("" < b || void 0) || console.log("PASS");
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }

View File

@@ -2028,3 +2028,37 @@ issue_3375: {
} }
expect_stdout: "0 0" expect_stdout: "0 0"
} }
issue_3427_1: {
options = {
sequences: true,
side_effects: true,
unused: true,
}
input: {
(function() {
var a;
a = a || {};
})();
}
expect: {}
}
issue_3427_2: {
options = {
unused: true,
}
input: {
(function() {
var s = "PASS";
console.log(s = s || "FAIL");
})();
}
expect: {
(function() {
var s = "PASS";
console.log(s = s || "FAIL");
})();
}
expect_stdout: "PASS"
}

View File

@@ -862,3 +862,27 @@ issue_3071_3: {
} }
expect_stdout: "2" expect_stdout: "2"
} }
issue_3411: {
options = {
hoist_props: true,
reduce_vars: true,
}
input: {
var c = 1;
!function f() {
var o = {
p: --c && f()
};
+o || console.log("PASS");
}();
}
expect: {
var c = 1;
!function f() {
var o_p = --c && f();
+{} || console.log("PASS");
}();
}
expect_stdout: "PASS"
}

View File

@@ -366,7 +366,7 @@ mangle_catch_redef_3: {
console.log(o); console.log(o);
} }
expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);' expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);'
expect_stdout: "PASS" expect_stdout: true
} }
mangle_catch_redef_3_toplevel: { mangle_catch_redef_3_toplevel: {
@@ -389,10 +389,10 @@ mangle_catch_redef_3_toplevel: {
console.log(o); console.log(o);
} }
expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);' expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);'
expect_stdout: "PASS" expect_stdout: true
} }
mangle_catch_redef_ie8_3: { mangle_catch_redef_3_ie8: {
mangle = { mangle = {
ie8: true, ie8: true,
toplevel: false, toplevel: false,
@@ -412,7 +412,7 @@ mangle_catch_redef_ie8_3: {
console.log(o); console.log(o);
} }
expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);' expect_exact: 'var o="PASS";try{throw 0}catch(o){(function(){function c(){o="FAIL"}c(),c()})()}console.log(o);'
expect_stdout: "PASS" expect_stdout: true
} }
mangle_catch_redef_3_ie8_toplevel: { mangle_catch_redef_3_ie8_toplevel: {
@@ -435,5 +435,5 @@ mangle_catch_redef_3_ie8_toplevel: {
console.log(o); console.log(o);
} }
expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);' expect_exact: 'var c="PASS";try{throw 0}catch(c){(function(){function o(){c="FAIL"}o(),o()})()}console.log(c);'
expect_stdout: "PASS" expect_stdout: true
} }

1157
test/compress/keep_fargs.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -673,3 +673,19 @@ issue_3371: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
step: {
options = {
loops: true,
side_effects: true,
}
input: {
for (var i = 0; i < 42; "foo", i++, "bar");
console.log(i);
}
expect: {
for (var i = 0; i < 42; i++);
console.log(i);
}
expect_stdout: "42"
}

View File

@@ -1161,3 +1161,49 @@ collapse_rhs_lhs: {
} }
expect_stdout: "1 3" expect_stdout: "1 3"
} }
drop_arguments: {
options = {
pure_getters: "strict",
side_effects: true,
}
input: {
(function() {
arguments.slice = function() {
console.log("PASS");
};
arguments[42];
arguments.length;
arguments.slice();
})();
}
expect: {
(function() {
arguments.slice = function() {
console.log("PASS");
};
arguments.slice();
})();
}
expect_stdout: "PASS"
}
issue_3427: {
options = {
assignments: true,
collapse_vars: true,
inline: true,
pure_getters: "strict",
sequences: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
var a;
(function(b) {
b.p = 42;
})(a || (a = {}));
}
expect: {}
}

View File

@@ -12,3 +12,71 @@ console_log: {
"% %s", "% %s",
] ]
} }
typeof_arguments: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var arguments;
console.log((typeof arguments).length);
}
expect: {
var arguments;
console.log((typeof arguments).length);
}
expect_stdout: "6"
}
typeof_arguments_assigned: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var arguments = void 0;
console.log((typeof arguments).length);
}
expect: {
console.log("undefined".length);
}
expect_stdout: "9"
}
toplevel_Infinity_NaN_undefined: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var Infinity = "foo";
var NaN = 42;
var undefined = null;
console.log(Infinity, NaN, undefined);
}
expect: {
console.log("foo", 42, null);
}
expect_stdout: "foo 42 null"
}
log_global: {
input: {
console.log(function() {
return this;
}());
}
expect: {
console.log(function() {
return this;
}());
}
expect_stdout: "[object global]"
}

View File

@@ -1,29 +1,7 @@
var semver = require("semver"); var semver = require("semver");
var vm = require("vm"); var vm = require("vm");
function safe_log(arg, level) { var setupContext = new vm.Script([
if (arg) switch (typeof arg) {
case "function":
return arg.toString();
case "object":
if (/Error$/.test(arg.name)) return arg.toString();
arg.constructor.toString();
if (level--) for (var key in arg) {
var desc = Object.getOwnPropertyDescriptor(arg, key);
if (!desc || !desc.get) arg[key] = safe_log(arg[key], level);
}
}
return arg;
}
function log(msg) {
if (arguments.length == 1 && typeof msg == "string") return console.log("%s", msg);
return console.log.apply(console, [].map.call(arguments, function(arg) {
return safe_log(arg, 3);
}));
}
var func_toString = new vm.Script([
"[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {", "[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {",
" f.toString = Function.prototype.toString;", " f.toString = Function.prototype.toString;",
"});", "});",
@@ -44,40 +22,51 @@ var func_toString = new vm.Script([
' return "function(){}";', ' return "function(){}";',
" };", " };",
"}();", "}();",
"this;",
]).join("\n")); ]).join("\n"));
function createContext() { function createContext() {
var ctx = vm.createContext(Object.defineProperty({}, "console", { value: { log: log } })); var ctx = vm.createContext(Object.defineProperty({}, "console", { value: { log: log } }));
func_toString.runInContext(ctx); var global = setupContext.runInContext(ctx);
return ctx; return ctx;
function safe_log(arg, level) {
if (arg) switch (typeof arg) {
case "function":
return arg.toString();
case "object":
if (arg === global) return "[object global]";
if (/Error$/.test(arg.name)) return arg.toString();
arg.constructor.toString();
if (level--) for (var key in arg) {
var desc = Object.getOwnPropertyDescriptor(arg, key);
if (!desc || !desc.get) arg[key] = safe_log(arg[key], level);
}
}
return arg;
} }
var context; function log(msg) {
exports.run_code = function(code, reuse) { if (arguments.length == 1 && typeof msg == "string") return console.log("%s", msg);
return console.log.apply(console, [].map.call(arguments, function(arg) {
return safe_log(arg, 3);
}));
}
}
exports.run_code = function(code, toplevel) {
var stdout = ""; var stdout = "";
var original_write = process.stdout.write; var original_write = process.stdout.write;
process.stdout.write = function(chunk) { process.stdout.write = function(chunk) {
stdout += chunk; stdout += chunk;
}; };
try { try {
if (!reuse || !context) context = createContext(); vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: 5000 });
vm.runInContext([
"!function() {",
code,
"}();",
].join("\n"), context, { timeout: 5000 });
return stdout; return stdout;
} catch (ex) { } catch (ex) {
return ex; return ex;
} finally { } finally {
process.stdout.write = original_write; process.stdout.write = original_write;
if (!reuse || code.indexOf(".prototype") >= 0) {
context = null;
} else {
vm.runInContext(Object.keys(context).map(function(name) {
return "delete " + name;
}).join("\n"), context);
}
} }
}; };

View File

@@ -969,7 +969,7 @@ function errorln(msg) {
process.stderr.write("\n"); process.stderr.write("\n");
} }
function try_beautify(code, result, printfn) { function try_beautify(code, toplevel, result, printfn) {
var beautified = UglifyJS.minify(code, { var beautified = UglifyJS.minify(code, {
compress: false, compress: false,
mangle: false, mangle: false,
@@ -981,7 +981,7 @@ function try_beautify(code, result, printfn) {
if (beautified.error) { if (beautified.error) {
printfn("// !!! beautify failed !!!"); printfn("// !!! beautify failed !!!");
printfn(beautified.error.stack); printfn(beautified.error.stack);
} else if (sandbox.same_stdout(sandbox.run_code(beautified.code), result)) { } else if (sandbox.same_stdout(sandbox.run_code(beautified.code, toplevel), result)) {
printfn("// (beautified)"); printfn("// (beautified)");
printfn(beautified.code); printfn(beautified.code);
return; return;
@@ -1009,7 +1009,7 @@ function log_suspects(minify_options, component) {
errorln("Error testing options." + component + "." + name); errorln("Error testing options." + component + "." + name);
errorln(result.error.stack); errorln(result.error.stack);
} else { } else {
var r = sandbox.run_code(result.code); var r = sandbox.run_code(result.code, m.toplevel);
return sandbox.same_stdout(original_result, r); return sandbox.same_stdout(original_result, r);
} }
} }
@@ -1031,7 +1031,7 @@ function log_rename(options) {
errorln("Error testing options.rename"); errorln("Error testing options.rename");
errorln(result.error.stack); errorln(result.error.stack);
} else { } else {
var r = sandbox.run_code(result.code); var r = sandbox.run_code(result.code, m.toplevel);
if (sandbox.same_stdout(original_result, r)) { if (sandbox.same_stdout(original_result, r)) {
errorln("Suspicious options:"); errorln("Suspicious options:");
errorln(" rename"); errorln(" rename");
@@ -1045,23 +1045,24 @@ function log(options) {
errorln("//============================================================="); errorln("//=============================================================");
if (!ok) errorln("// !!!!!! Failed... round " + round); if (!ok) errorln("// !!!!!! Failed... round " + round);
errorln("// original code"); errorln("// original code");
try_beautify(original_code, original_result, errorln); try_beautify(original_code, false, original_result, errorln);
errorln(); errorln();
errorln(); errorln();
errorln("//-------------------------------------------------------------"); errorln("//-------------------------------------------------------------");
options = JSON.parse(options);
if (typeof uglify_code == "string") { if (typeof uglify_code == "string") {
errorln("// uglified code"); errorln("// uglified code");
try_beautify(uglify_code, uglify_result, errorln); try_beautify(uglify_code, options.toplevel, uglify_result, errorln);
errorln(); errorln();
errorln(); errorln();
errorln("original result:"); errorln("original result:");
errorln(typeof original_result == "string" ? original_result : original_result.stack); errorln(errored ? original_result.stack : original_result);
errorln("uglified result:"); errorln("uglified result:");
errorln(typeof uglify_result == "string" ? uglify_result : uglify_result.stack); errorln(typeof uglify_result == "string" ? uglify_result : uglify_result.stack);
} else { } else {
errorln("// !!! uglify failed !!!"); errorln("// !!! uglify failed !!!");
errorln(uglify_code.stack); errorln(uglify_code.stack);
if (typeof original_result != "string") { if (errored) {
errorln(); errorln();
errorln(); errorln();
errorln("original stacktrace:"); errorln("original stacktrace:");
@@ -1069,7 +1070,6 @@ function log(options) {
} }
} }
errorln("minify(options):"); errorln("minify(options):");
options = JSON.parse(options);
errorln(JSON.stringify(options, null, 2)); errorln(JSON.stringify(options, null, 2));
errorln(); errorln();
if (!ok && typeof uglify_code == "string") { if (!ok && typeof uglify_code == "string") {
@@ -1084,30 +1084,34 @@ var fallback_options = [ JSON.stringify({
mangle: false mangle: false
}) ]; }) ];
var minify_options = require("./ufuzz.json").map(JSON.stringify); var minify_options = require("./ufuzz.json").map(JSON.stringify);
var original_code, original_result; var original_code, original_result, errored;
var uglify_code, uglify_result, ok; var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) { for (var round = 1; round <= num_iterations; round++) {
process.stdout.write(round + " of " + num_iterations + "\r"); process.stdout.write(round + " of " + num_iterations + "\r");
original_code = createTopLevelCode(); original_code = createTopLevelCode();
original_result = sandbox.run_code(original_code); var orig_result = [ sandbox.run_code(original_code) ];
(typeof original_result != "string" ? fallback_options : minify_options).forEach(function(options) { errored = typeof orig_result[0] != "string";
uglify_code = UglifyJS.minify(original_code, JSON.parse(options)); if (!errored) orig_result.push(sandbox.run_code(original_code, true));
(errored ? fallback_options : minify_options).forEach(function(options) {
var o = JSON.parse(options);
uglify_code = UglifyJS.minify(original_code, o);
original_result = orig_result[o.toplevel ? 1 : 0];
if (!uglify_code.error) { if (!uglify_code.error) {
uglify_code = uglify_code.code; uglify_code = uglify_code.code;
uglify_result = sandbox.run_code(uglify_code); uglify_result = sandbox.run_code(uglify_code, o.toplevel);
ok = sandbox.same_stdout(original_result, uglify_result); ok = sandbox.same_stdout(original_result, uglify_result);
} else { } else {
uglify_code = uglify_code.error; uglify_code = uglify_code.error;
if (typeof original_result != "string") { if (errored) {
ok = uglify_code.name == original_result.name; ok = uglify_code.name == original_result.name;
} }
} }
if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options);
else if (typeof original_result != "string") { else if (errored) {
println("//============================================================="); println("//=============================================================");
println("// original code"); println("// original code");
try_beautify(original_code, original_result, println); try_beautify(original_code, o.toplevel, original_result, println);
println(); println();
println(); println();
println("original result:"); println("original result:");