Compare commits

...

19 Commits

Author SHA1 Message Date
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
Alex Lam S.L
3bc7cc82bb v3.5.12 2019-05-12 10:40:13 +08:00
Alex Lam S.L
45fbdbc2dc improve tests (#3408) 2019-05-12 09:44:02 +08:00
Alex Lam S.L
54cb678055 fix corner case in assignments (#3407)
fixes #3406
2019-05-12 03:52:46 +08:00
Alex Lam S.L
e88c439eac improve tests (#3405) 2019-05-11 22:06:14 +08:00
Alex Lam S.L
9fc8cd4076 fix corner case in functions (#3403)
fixes #3402
2019-05-11 18:55:45 +08:00
Alex Lam S.L
5476cb8f05 fix corner case in inline (#3401)
fixes #3400
2019-05-10 01:22:44 +08:00
Alex Lam S.L
6a30e1d6be improve tests (#3399) 2019-05-09 07:18:22 +08:00
29 changed files with 2543 additions and 610 deletions

View File

@@ -1,35 +1,46 @@
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libstdc++-4.9-dev
cache: cache:
directories: tmp directories: tmp
language: generic language: generic
matrix: matrix:
fast_finish: true fast_finish: true
sudo: false
env: env:
global: - NODE=0.10 TYPE=compress
- UGLIFYJS_TEST_ALL=1 - NODE=0.10 TYPE=mocha
matrix: - NODE=0.10 TYPE=release/benchmark
- NODEJS_VER=node/0.10 - NODE=0.10 TYPE=release/jetstream
- NODEJS_VER=node/0.12 - NODE=0.12 TYPE=compress
- NODEJS_VER=node/4 - NODE=0.12 TYPE=mocha
- NODEJS_VER=node/6 - NODE=0.12 TYPE=release/benchmark
- NODEJS_VER=node/8 - NODE=0.12 TYPE=release/jetstream
- NODEJS_VER=node/10 - NODE=4 TYPE=compress
- NODEJS_VER=node/latest - NODE=4 TYPE=mocha
- NODE=4 TYPE=release/benchmark
- NODE=4 TYPE=release/jetstream
- NODE=6 TYPE=compress
- NODE=6 TYPE=mocha
- NODE=6 TYPE=release/benchmark
- NODE=6 TYPE=release/jetstream
- NODE=8 TYPE=compress
- NODE=8 TYPE=mocha
- NODE=8 TYPE=release/benchmark
- NODE=8 TYPE=release/jetstream
- NODE=10 TYPE=compress
- NODE=10 TYPE=mocha
- NODE=10 TYPE=release/benchmark
- NODE=10 TYPE=release/jetstream
- NODE=latest TYPE=compress
- NODE=latest TYPE=mocha
- NODE=latest TYPE=release/benchmark
- NODE=latest TYPE=release/jetstream
before_install: before_install:
- git clone --branch v1.4.2 --depth 1 https://github.com/jasongin/nvs.git ~/.nvs - git clone --branch v1.5.2 --depth 1 https://github.com/jasongin/nvs.git ~/.nvs
- . ~/.nvs/nvs.sh - . ~/.nvs/nvs.sh
- nvs --version - nvs --version
install: install:
- nvs add $NODEJS_VER - nvs add node/$NODE
- nvs use $NODEJS_VER - nvs use node/$NODE
- node --version - node --version
- npm --version --no-update-notifier - npm --version --no-update-notifier
- npm install --no-optional --no-save --no-update-notifier - npm install --no-audit --no-optional --no-save --no-update-notifier
script: script:
- npm test --no-update-notifier - node test/$TYPE

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

@@ -1,26 +1,74 @@
environment:
UGLIFYJS_TEST_ALL: 1
matrix:
- NODEJS_VER: node/0.10
- NODEJS_VER: node/0.12
- NODEJS_VER: node/4
- NODEJS_VER: node/6
- NODEJS_VER: node/8
- NODEJS_VER: node/10
- NODEJS_VER: node/latest
install:
- git clone --branch v1.4.2 --depth 1 https://github.com/jasongin/nvs.git %LOCALAPPDATA%\nvs
- set PATH=%LOCALAPPDATA%\nvs;%PATH%
- nvs --version
- nvs add %NODEJS_VER%
- nvs use %NODEJS_VER%
- node --version
- npm --version --no-update-notifier
- npm install --no-optional --no-save --no-update-notifier
build: off build: off
cache: cache:
- tmp - tmp
matrix: matrix:
fast_finish: true fast_finish: true
environment:
matrix:
- NODE: 0.10
TYPE: compress
- NODE: 0.10
TYPE: mocha
- NODE: 0.10
TYPE: release/benchmark
- NODE: 0.10
TYPE: release/jetstream
- NODE: 0.12
TYPE: compress
- NODE: 0.12
TYPE: mocha
- NODE: 0.12
TYPE: release/benchmark
- NODE: 0.12
TYPE: release/jetstream
- NODE: 4
TYPE: compress
- NODE: 4
TYPE: mocha
- NODE: 4
TYPE: release/benchmark
- NODE: 4
TYPE: release/jetstream
- NODE: 6
TYPE: compress
- NODE: 6
TYPE: mocha
- NODE: 6
TYPE: release/benchmark
- NODE: 6
TYPE: release/jetstream
- NODE: 8
TYPE: compress
- NODE: 8
TYPE: mocha
- NODE: 8
TYPE: release/benchmark
- NODE: 8
TYPE: release/jetstream
- NODE: 10
TYPE: compress
- NODE: 10
TYPE: mocha
- NODE: 10
TYPE: release/benchmark
- NODE: 10
TYPE: release/jetstream
- NODE: latest
TYPE: compress
- NODE: latest
TYPE: mocha
- NODE: latest
TYPE: release/benchmark
- NODE: latest
TYPE: release/jetstream
install:
- git clone --branch v1.5.2 --depth 1 https://github.com/jasongin/nvs.git %LOCALAPPDATA%\nvs
- set PATH=%LOCALAPPDATA%\nvs;%PATH%
- nvs --version
- nvs add node/%NODE%
- nvs use node/%NODE%
- node --version
- npm --version --no-update-notifier
- npm install --no-audit --no-optional --no-save --no-update-notifier
test_script: test_script:
- npm test --no-update-notifier - node test/%TYPE%

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
@@ -2307,6 +2322,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 +2350,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 +2370,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 +2565,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) {
@@ -3621,7 +3638,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)) {
@@ -3682,6 +3699,7 @@ merge(Compressor.prototype, {
} else if (compressor.option("functions") } else if (compressor.option("functions")
&& def.value === def.name.fixed_value() && def.value === def.name.fixed_value()
&& def.value instanceof AST_Function && def.value instanceof AST_Function
&& !(def.value.name && def.value.name.definition().assignments)
&& can_rename(def.value, def.name.name) && can_rename(def.value, def.name.name)
&& (!compressor.has_directive("use strict") || parent instanceof AST_Scope)) { && (!compressor.has_directive("use strict") || parent instanceof AST_Scope)) {
AST_Node.warn("Declaring {name} as function [{file}:{line},{col}]", template(def.name)); AST_Node.warn("Declaring {name} as function [{file}:{line},{col}]", template(def.name));
@@ -3811,6 +3829,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) {
@@ -4051,6 +4070,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();
@@ -4067,7 +4096,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,
@@ -4405,8 +4434,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);
@@ -5394,7 +5424,8 @@ merge(Compressor.prototype, {
OPT(AST_UnaryPrefix, function(self, compressor) { OPT(AST_UnaryPrefix, function(self, compressor) {
var e = self.expression; var e = self.expression;
if (self.operator == "delete" if (compressor.option("evaluate")
&& self.operator == "delete"
&& !(e instanceof AST_SymbolRef && !(e instanceof AST_SymbolRef
|| e instanceof AST_PropAccess || e instanceof AST_PropAccess
|| is_identifier_atom(e))) { || is_identifier_atom(e))) {
@@ -5936,11 +5967,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
@@ -5950,18 +5980,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) {
operator: "!", case "<":
expression: node if (indexLeft) break;
}); case "<=":
case "!=":
node = make_node(AST_UnaryPrefix, self, {
operator: "!",
expression: node
});
break;
}
return node.optimize(compressor); return node.optimize(compressor);
} }
} }
@@ -5999,17 +6037,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;
@@ -6102,7 +6149,6 @@ merge(Compressor.prototype, {
})); }));
} else { } else {
value = fixed.optimize(compressor); value = fixed.optimize(compressor);
if (value === fixed) value = fixed.clone(true);
} }
def.replaced++; def.replaced++;
return value; return value;
@@ -6254,6 +6300,7 @@ merge(Compressor.prototype, {
if (compressor.option("dead_code") if (compressor.option("dead_code")
&& self.left instanceof AST_SymbolRef && self.left instanceof AST_SymbolRef
&& (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) { && (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) {
if (self.left.is_immutable()) return strip_assignment();
var level = 0, node, parent = self; var level = 0, node, parent = self;
do { do {
node = parent; node = parent;
@@ -6261,16 +6308,12 @@ merge(Compressor.prototype, {
if (parent instanceof AST_Exit) { if (parent instanceof AST_Exit) {
if (in_try(level, parent)) break; if (in_try(level, parent)) break;
if (is_reachable(def.scope, [ def ])) break; if (is_reachable(def.scope, [ def ])) break;
if (self.operator == "=") return self.right.optimize(compressor);
def.fixed = false; def.fixed = false;
return make_node(AST_Binary, self, { return strip_assignment();
operator: self.operator.slice(0, -1),
left: self.left,
right: self.right
}).optimize(compressor);
} }
} while (parent instanceof AST_Binary && parent.right === node } while (parent instanceof AST_Binary && parent.right === node
|| parent instanceof AST_Sequence && parent.tail_node() === node); || parent instanceof AST_Sequence && parent.tail_node() === node
|| parent instanceof AST_UnaryPrefix);
} }
self = self.lift_sequences(compressor); self = self.lift_sequences(compressor);
if (!compressor.option("assignments")) return self; if (!compressor.option("assignments")) return self;
@@ -6318,6 +6361,14 @@ merge(Compressor.prototype, {
} }
} }
} }
function strip_assignment() {
return (self.operator != "=" ? make_node(AST_Binary, self, {
operator: self.operator.slice(0, -1),
left: self.left,
right: self.right
}) : maintain_this_binding(compressor, compressor.parent(), self, self.right)).optimize(compressor);
}
}); });
OPT(AST_Conditional, function(self, compressor) { OPT(AST_Conditional, function(self, compressor) {
@@ -6629,24 +6680,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),
@@ -6659,13 +6716,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) {
@@ -6714,6 +6772,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.11", "version": "3.5.15",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },
@@ -31,7 +31,7 @@
"semver": "~6.0.0" "semver": "~6.0.0"
}, },
"scripts": { "scripts": {
"test": "node test/run-tests.js" "test": "node test/compress.js && node test/mocha.js"
}, },
"keywords": [ "keywords": [
"cli", "cli",

450
test/compress.js Normal file
View File

@@ -0,0 +1,450 @@
var assert = require("assert");
var child_process = require("child_process");
var fs = require("fs");
var path = require("path");
var sandbox = require("./sandbox");
var semver = require("semver");
var U = require("./node");
var file = process.argv[2];
var dir = path.resolve(path.dirname(module.filename), "compress");
if (file) {
var minify_options = require("./ufuzz.json").map(JSON.stringify);
log("--- {file}", { file: file });
var tests = parse_test(path.resolve(dir, file));
process.exit(Object.keys(tests).filter(function(name) {
return !test_case(tests[name]);
}).length);
} else {
var files = fs.readdirSync(dir).filter(function(name) {
return /\.js$/i.test(name);
});
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("!!! Failed " + failures + " test case(s).");
console.error("!!! " + Object.keys(failed_files).join(", "));
process.exit(1);
}
})();
}
function evaluate(code) {
if (code instanceof U.AST_Node) code = make_code(code, { beautify: true });
return new Function("return(" + code + ")")();
}
function log() {
console.log("%s", tmpl.apply(null, arguments));
}
function make_code(ast, options) {
var stream = U.OutputStream(options);
ast.print(stream);
return stream.get();
}
function parse_test(file) {
var script = fs.readFileSync(file, "utf8");
// TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS2/issues/348
try {
var ast = U.parse(script, {
filename: file
});
} catch (e) {
console.error("Caught error while parsing tests in " + file);
console.error(e);
process.exit(1);
}
var tests = Object.create(null);
var tw = new U.TreeWalker(function(node, descend) {
if (node instanceof U.AST_LabeledStatement
&& tw.parent() instanceof U.AST_Toplevel) {
var name = node.label.name;
if (name in tests) {
throw new Error('Duplicated test name "' + name + '" in ' + file);
}
tests[name] = get_one_test(name, node.body);
return true;
}
if (!(node instanceof U.AST_Toplevel)) croak(node);
});
ast.walk(tw);
return tests;
function croak(node) {
throw new Error(tmpl("Can't understand test file {file} [{line},{col}]\n{code}", {
file: file,
line: node.start.line,
col: node.start.col,
code: make_code(node, { beautify: false })
}));
}
function read_string(stat) {
if (stat.TYPE == "SimpleStatement") {
var body = stat.body;
switch(body.TYPE) {
case "String":
return body.value;
case "Array":
return body.elements.map(function(element) {
if (element.TYPE !== "String")
throw new Error("Should be array of strings");
return element.value;
}).join("\n");
}
}
throw new Error("Should be string or array of strings");
}
function get_one_test(name, block) {
var test = { name: name, options: {} };
var tw = new U.TreeWalker(function(node, descend) {
if (node instanceof U.AST_Assign) {
if (!(node.left instanceof U.AST_SymbolRef)) {
croak(node);
}
var name = node.left.name;
test[name] = evaluate(node.right);
return true;
}
if (node instanceof U.AST_LabeledStatement) {
var label = node.label;
assert.ok([
"input",
"expect",
"expect_exact",
"expect_warnings",
"expect_stdout",
"node_version",
].indexOf(label.name) >= 0, tmpl("Unsupported label {name} [{line},{col}]", {
name: label.name,
line: label.start.line,
col: label.start.col
}));
var stat = node.body;
if (label.name == "expect_exact" || label.name == "node_version") {
test[label.name] = read_string(stat);
} else if (label.name == "expect_stdout") {
var body = stat.body;
if (body instanceof U.AST_Boolean) {
test[label.name] = body.value;
} else if (body instanceof U.AST_Call) {
var ctor = global[body.expression.name];
assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", {
line: label.start.line,
col: label.start.col
}));
test[label.name] = ctor.apply(null, body.args.map(function(node) {
assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", {
line: label.start.line,
col: label.start.col
}));
return node.value;
}));
} else {
test[label.name] = read_string(stat) + "\n";
}
} else {
test[label.name] = stat;
}
return true;
}
});
block.walk(tw);
return test;
}
}
// Try to reminify original input with standard options
// to see if it matches expect_stdout.
function reminify(orig_options, input_code, input_formatted, stdout) {
for (var i = 0; i < minify_options.length; i++) {
var options = JSON.parse(minify_options[i]);
if (options.compress) [
"keep_fargs",
"keep_fnames",
].forEach(function(name) {
if (name in orig_options) {
options.compress[name] = orig_options[name];
}
});
var options_formatted = JSON.stringify(options, null, 4);
var result = U.minify(input_code, options);
if (result.error) {
log([
"!!! failed input reminify",
"---INPUT---",
"{input}",
"---OPTIONS---",
"{options}",
"--ERROR---",
"{error}",
"",
"",
].join("\n"), {
input: input_formatted,
options: options_formatted,
error: result.error,
});
return false;
} else {
var expected = stdout[options.toplevel ? 1 : 0];
var actual = run_code(result.code, options.toplevel);
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
actual = expected;
}
if (!sandbox.same_stdout(expected, actual)) {
log([
"!!! failed running reminified input",
"---INPUT---",
"{input}",
"---OPTIONS---",
"{options}",
"---OUTPUT---",
"{output}",
"---EXPECTED {expected_type}---",
"{expected}",
"---ACTUAL {actual_type}---",
"{actual}",
"",
"",
].join("\n"), {
input: input_formatted,
options: options_formatted,
output: result.code,
expected_type: typeof expected == "string" ? "STDOUT" : "ERROR",
expected: expected,
actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: actual,
});
return false;
}
}
}
return true;
}
function run_code(code, toplevel) {
var result = sandbox.run_code(code, toplevel);
return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result;
}
function test_case(test) {
log(" Running test [{name}]", { name: test.name });
var output_options = test.beautify || {};
var expect;
if (test.expect) {
expect = make_code(to_toplevel(test.expect, test.mangle), output_options);
} else {
expect = test.expect_exact;
}
var input = to_toplevel(test.input, test.mangle);
var input_code = make_code(input);
var input_formatted = make_code(test.input, {
beautify: true,
comments: "all",
keep_quoted_props: true,
quote_style: 3,
});
try {
U.parse(input_code);
} catch (ex) {
log([
"!!! Cannot parse input",
"---INPUT---",
"{input}",
"--PARSE ERROR--",
"{error}",
"",
"",
].join("\n"), {
input: input_formatted,
error: ex,
});
return false;
}
var warnings_emitted = [];
if (test.expect_warnings) {
var expected_warnings = make_code(test.expect_warnings, {
beautify: false,
quote_style: 2, // force double quote to match JSON
});
U.AST_Node.log_function(function(text) {
warnings_emitted.push(text);
}, /"INFO: /.test(expected_warnings));
}
if (test.mangle && test.mangle.properties && test.mangle.properties.keep_quoted) {
var quoted_props = test.mangle.properties.reserved;
if (!Array.isArray(quoted_props)) quoted_props = [];
test.mangle.properties.reserved = quoted_props;
U.reserve_quoted_keys(input, quoted_props);
}
if (test.rename) {
input.figure_out_scope(test.mangle);
input.expand_names(test.mangle);
}
var cmp = new U.Compressor(test.options, true);
var output = cmp.compress(input);
output.figure_out_scope(test.mangle);
if (test.mangle) {
output.compute_char_frequency(test.mangle);
output.mangle_names(test.mangle);
if (test.mangle.properties) {
output = U.mangle_properties(output, test.mangle.properties);
}
}
output = make_code(output, output_options);
if (expect != output) {
log([
"!!! failed",
"---INPUT---",
"{input}",
"---OUTPUT---",
"{output}",
"---EXPECTED---",
"{expected}",
"",
"",
].join("\n"), {
input: input_formatted,
output: output,
expected: expect
});
return false;
}
// expect == output
try {
U.parse(output);
} catch (ex) {
log([
"!!! Test matched expected result but cannot parse output",
"---INPUT---",
"{input}",
"---OUTPUT---",
"{output}",
"--REPARSE ERROR--",
"{error}",
"",
"",
].join("\n"), {
input: input_formatted,
output: output,
error: ex,
});
return false;
}
if (test.expect_warnings) {
warnings_emitted = warnings_emitted.map(function(input) {
return input.split(process.cwd() + path.sep).join("").split(path.sep).join("/");
});
var actual_warnings = JSON.stringify(warnings_emitted);
if (expected_warnings != actual_warnings) {
log([
"!!! failed",
"---INPUT---",
"{input}",
"---EXPECTED WARNINGS---",
"{expected_warnings}",
"---ACTUAL WARNINGS---",
"{actual_warnings}",
"",
"",
].join("\n"), {
input: input_formatted,
expected_warnings: expected_warnings,
actual_warnings: actual_warnings,
});
return false;
}
}
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
var stdout = [ run_code(input_code), run_code(input_code, true) ];
var toplevel = test.options.toplevel;
var actual = stdout[toplevel ? 1 : 0];
if (test.expect_stdout === true) {
test.expect_stdout = actual;
}
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([
"!!! Invalid input or expected stdout",
"---INPUT---",
"{input}",
"---EXPECTED {expected_type}---",
"{expected}",
"---ACTUAL {actual_type}---",
"{actual}",
"",
"",
].join("\n"), {
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: actual,
});
return false;
}
actual = run_code(output, toplevel);
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
log([
"!!! failed",
"---INPUT---",
"{input}",
"---EXPECTED {expected_type}---",
"{expected}",
"---ACTUAL {actual_type}---",
"{actual}",
"",
"",
].join("\n"), {
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof actual == "string" ? "STDOUT" : "ERROR",
actual: actual,
});
return false;
}
if (!reminify(test.options, input_code, input_formatted, stdout)) {
return false;
}
}
return true;
}
function tmpl() {
return U.string_template.apply(null, arguments);
}
function to_toplevel(input, mangle_options) {
if (!(input instanceof U.AST_BlockStatement)) throw new Error("Unsupported input syntax");
var directive = true;
var offset = input.start.line;
var tokens = [];
var toplevel = new U.AST_Toplevel(input.transform(new U.TreeTransformer(function(node) {
if (U.push_uniq(tokens, node.start)) node.start.line -= offset;
if (!directive || node === input) return;
if (node instanceof U.AST_SimpleStatement && node.body instanceof U.AST_String) {
return new U.AST_Directive(node.body);
} else {
directive = false;
}
})));
toplevel.figure_out_scope(mangle_options);
return toplevel;
}

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

@@ -3497,10 +3497,10 @@ issue_2437_1: {
return Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}), return Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}),
result; result;
} }
var req, detectFunc = function(){}; var req = new XMLHttpRequest(), detectFunc = function(){};
(req = new XMLHttpRequest()).onreadystatechange = detectFunc; return req.onreadystatechange = detectFunc,
result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc; result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc,
return req.onreadystatechange = null, result; req.onreadystatechange = null, result;
}()); }());
} }
} }
@@ -3545,8 +3545,8 @@ issue_2437_2: {
if (xhrDesc) if (xhrDesc)
return (req = new XMLHttpRequest()).onreadystatechange, return (req = new XMLHttpRequest()).onreadystatechange,
Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {}); Object.defineProperty(XMLHttpRequest.prototype, "onreadystatechange", xhrDesc || {});
var req; var req = new XMLHttpRequest();
(req = new XMLHttpRequest).onreadystatechange = function(){}, req.onreadystatechange = function(){},
req[SYMBOL_FAKE_ONREADYSTATECHANGE_1], req[SYMBOL_FAKE_ONREADYSTATECHANGE_1],
req.onreadystatechange = null; req.onreadystatechange = null;
}(); }();
@@ -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

@@ -960,3 +960,56 @@ unsafe_string_replace: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3402: {
options = {
dead_code: true,
evaluate: true,
functions: true,
passes: 2,
reduce_vars: true,
side_effects: true,
toplevel: true,
typeofs: true,
unused: true,
}
input: {
var f = function f() {
f = 42;
console.log(typeof f);
};
"function" == typeof f && f();
"function" == typeof f && f();
console.log(typeof f);
}
expect: {
function f() {
console.log(typeof f);
}
f();
f();
console.log(typeof f);
}
expect_stdout: [
"function",
"function",
"function",
]
}
issue_3406: {
options = {
dead_code: true,
}
input: {
console.log(function f(a) {
return delete (f = a);
}());
}
expect: {
console.log(function f(a) {
return delete (0, a);
}());
}
expect_stdout: "true"
}

View File

@@ -797,6 +797,7 @@ assign_chain: {
issue_1583: { issue_1583: {
options = { options = {
keep_fargs: true, keep_fargs: true,
passes: 2,
reduce_funcs: true, reduce_funcs: true,
reduce_vars: true, reduce_vars: true,
unused: true, unused: true,
@@ -1004,7 +1005,7 @@ issue_1715_4: {
delete_assign_1: { delete_assign_1: {
options = { options = {
booleans: true, booleans: true,
side_effects: true, evaluate: true,
toplevel: true, toplevel: true,
unused: true, unused: true,
} }
@@ -1023,7 +1024,7 @@ delete_assign_1: {
console.log((1 / 0, !0)); console.log((1 / 0, !0));
console.log((1 / 0, !0)); console.log((1 / 0, !0));
console.log((NaN, !0)); console.log((NaN, !0));
console.log((0 / 0, !0)); console.log((NaN, !0));
} }
expect_stdout: true expect_stdout: true
} }
@@ -1031,8 +1032,8 @@ delete_assign_1: {
delete_assign_2: { delete_assign_2: {
options = { options = {
booleans: true, booleans: true,
evaluate: true,
keep_infinity: true, keep_infinity: true,
side_effects: true,
toplevel: true, toplevel: true,
unused: true, unused: true,
} }
@@ -1051,7 +1052,7 @@ delete_assign_2: {
console.log((Infinity, !0)); console.log((Infinity, !0));
console.log((1 / 0, !0)); console.log((1 / 0, !0));
console.log((NaN, !0)); console.log((NaN, !0));
console.log((0 / 0, !0)); console.log((NaN, !0));
} }
expect_stdout: true expect_stdout: true
} }
@@ -1144,6 +1145,7 @@ var_catch_toplevel: {
options = { options = {
conditionals: true, conditionals: true,
negate_iife: true, negate_iife: true,
passes: 2,
reduce_funcs: true, reduce_funcs: true,
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,

View File

@@ -2860,10 +2860,10 @@ issue_2437: {
result; result;
} }
function detectFunc() {} function detectFunc() {}
var req; var req = new XMLHttpRequest();
(req = new XMLHttpRequest()).onreadystatechange = detectFunc; return req.onreadystatechange = detectFunc,
result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc; result = req[SYMBOL_FAKE_ONREADYSTATECHANGE_1] === detectFunc,
return req.onreadystatechange = null, result; req.onreadystatechange = null, result;
}()); }());
} }
} }
@@ -3065,3 +3065,86 @@ class_iife: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
issue_3400: {
options = {
collapse_vars: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
unused: true,
}
input: {
(function(f) {
console.log(f()()[0].p);
})(function() {
function g() {
function h(u) {
var o = {
p: u
};
return console.log(o[g]), o;
}
function e() {
return [ 42 ].map(function(v) {
return h(v);
});
}
return e();
}
return g;
});
}
expect: {
void console.log(function g() {
function e() {
return [42].map(function(v) {
return o = {
p: v
}, console.log(o[g]) , o;
var o;
});
}
return e();
}()[0].p);
}
expect_stdout: [
"undefined",
"42",
]
}
issue_3402: {
options = {
evaluate: true,
functions: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
typeofs: true,
unused: true,
}
input: {
var f = function f() {
f = 42;
console.log(typeof f);
};
"function" == typeof f && f();
"function" == typeof f && f();
console.log(typeof f);
}
expect: {
var f = function f() {
f = 42;
console.log(typeof f);
};
f();
f();
console.log(typeof f);
}
expect_stdout: [
"function",
"function",
"function",
]
}

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,29 @@ 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"
}

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

@@ -490,6 +490,7 @@ issue_1758: {
delete_seq_1: { delete_seq_1: {
options = { options = {
booleans: true, booleans: true,
evaluate: true,
side_effects: true, side_effects: true,
} }
input: { input: {
@@ -514,6 +515,7 @@ delete_seq_1: {
delete_seq_2: { delete_seq_2: {
options = { options = {
booleans: true, booleans: true,
evaluate: true,
side_effects: true, side_effects: true,
} }
input: { input: {
@@ -538,6 +540,7 @@ delete_seq_2: {
delete_seq_3: { delete_seq_3: {
options = { options = {
booleans: true, booleans: true,
evaluate: true,
keep_infinity: true, keep_infinity: true,
side_effects: true, side_effects: true,
} }
@@ -563,6 +566,7 @@ delete_seq_3: {
delete_seq_4: { delete_seq_4: {
options = { options = {
booleans: true, booleans: true,
evaluate: true,
sequences: true, sequences: true,
side_effects: true, side_effects: true,
} }
@@ -590,6 +594,7 @@ delete_seq_4: {
delete_seq_5: { delete_seq_5: {
options = { options = {
booleans: true, booleans: true,
evaluate: true,
keep_infinity: true, keep_infinity: true,
sequences: true, sequences: true,
side_effects: true, side_effects: true,
@@ -618,6 +623,7 @@ delete_seq_5: {
delete_seq_6: { delete_seq_6: {
options = { options = {
booleans: true, booleans: true,
evaluate: true,
side_effects: true, side_effects: true,
} }
input: { input: {

View File

@@ -62,8 +62,17 @@ if (typeof phantom == "undefined") {
if (debug) { if (debug) {
console.log("http://localhost:" + port + "/"); console.log("http://localhost:" + port + "/");
} else { } else {
child_process.exec("npm install phantomjs-prebuilt@2.1.14 --no-save", function(error) { child_process.spawn(process.platform == "win32" ? "npm.cmd" : "npm", [
if (error) throw error; "install",
"phantomjs-prebuilt@2.1.14",
"--no-audit",
"--no-optional",
"--no-save",
"--no-update-notifier",
], {
stdio: [ "ignore", 1, 2 ]
}).on("exit", function(code) {
if (code) throw new Error("npm install failed!");
var program = require("phantomjs-prebuilt").exec(process.argv[1], port); var program = require("phantomjs-prebuilt").exec(process.argv[1], port);
program.stdout.pipe(process.stdout); program.stdout.pipe(process.stdout);
program.stderr.pipe(process.stderr); program.stderr.pipe(process.stderr);

View File

@@ -1,50 +0,0 @@
var assert = require("assert");
var semver = require("semver");
var spawn = require("child_process").spawn;
if (!process.env.UGLIFYJS_TEST_ALL) return;
function run(command, args, done) {
spawn(command, args, {
stdio: [ "ignore", 1, 2 ]
}).on("exit", function(code) {
assert.strictEqual(code, 0);
done();
});
}
describe("test/benchmark.js", function() {
this.timeout(10 * 60 * 1000);
[
"-b",
"-b braces",
"-m",
"-mc passes=3",
"-mc passes=3,toplevel",
"-mc passes=3,unsafe",
"-mc keep_fargs=false,passes=3",
"-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto",
].forEach(function(options) {
it("Should pass with options " + options, function(done) {
var args = options.split(/ /);
args.unshift("test/benchmark.js");
run(process.argv[0], args, done);
});
});
});
if (semver.satisfies(process.version, "0.12")) return;
describe("test/jetstream.js", function() {
this.timeout(20 * 60 * 1000);
[
"-mc",
"-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto",
].forEach(function(options) {
it("Should pass with options " + options, function(done) {
var args = options.split(/ /);
args.unshift("test/jetstream.js");
args.push("-b", "beautify=false,webkit");
run(process.argv[0], args, done);
});
});
});

14
test/release/benchmark.js Normal file
View File

@@ -0,0 +1,14 @@
require("./run")([
"-b",
"-b braces",
"-m",
"-mc passes=3",
"-mc passes=3,toplevel",
"-mc passes=3,unsafe",
"-mc keep_fargs=false,passes=3",
"-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto",
].map(function(options) {
var args = options.split(/ /);
args.unshift("test/benchmark.js");
return args;
}));

View File

@@ -0,0 +1,9 @@
require("./run")([
"-mc",
"-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto",
].map(function(options) {
var args = options.split(/ /);
args.unshift("test/jetstream.js");
args.push("-b", "beautify=false,webkit");
return args;
}));

16
test/release/run.js Normal file
View File

@@ -0,0 +1,16 @@
var child_process = require("child_process");
module.exports = function(tasks) {
(function next() {
if (!tasks.length) return;
var args = tasks.shift();
console.log();
console.log("\u001B[36m$> " + args.join(" ") + "\u001B[39m");
child_process.spawn(process.argv[0], args, {
stdio: [ "ignore", 1, 2 ]
}).on("exit", function(code) {
if (code) process.exit(code);
next();
});
})();
};

View File

@@ -1,362 +0,0 @@
#! /usr/bin/env node
var U = require("./node");
var path = require("path");
var fs = require("fs");
var assert = require("assert");
var sandbox = require("./sandbox");
var semver = require("semver");
var tests_dir = path.dirname(module.filename);
var failures = 0;
var failed_files = {};
var minify_options = require("./ufuzz.json").map(JSON.stringify);
run_compress_tests();
if (failures) {
console.error("\n!!! Failed " + failures + " test cases.");
console.error("!!! " + Object.keys(failed_files).join(", "));
process.exit(1);
}
console.log();
require("./mocha.js");
/* -----[ utils ]----- */
function evaluate(code) {
if (code instanceof U.AST_Node) code = make_code(code, { beautify: true });
return new Function("return(" + code + ")")();
}
function log() {
console.log("%s", tmpl.apply(null, arguments));
}
function make_code(ast, options) {
var stream = U.OutputStream(options);
ast.print(stream);
return stream.get();
}
function parse_test(file) {
var script = fs.readFileSync(file, "utf8");
// TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS2/issues/348
try {
var ast = U.parse(script, {
filename: file
});
} catch (e) {
console.log("Caught error while parsing tests in " + file + "\n");
console.log(e);
throw e;
}
var tests = {};
var tw = new U.TreeWalker(function(node, descend) {
if (node instanceof U.AST_LabeledStatement
&& tw.parent() instanceof U.AST_Toplevel) {
var name = node.label.name;
if (name in tests) {
throw new Error('Duplicated test name "' + name + '" in ' + file);
}
tests[name] = get_one_test(name, node.body);
return true;
}
if (!(node instanceof U.AST_Toplevel)) croak(node);
});
ast.walk(tw);
return tests;
function croak(node) {
throw new Error(tmpl("Can't understand test file {file} [{line},{col}]\n{code}", {
file: file,
line: node.start.line,
col: node.start.col,
code: make_code(node, { beautify: false })
}));
}
function read_string(stat) {
if (stat.TYPE == "SimpleStatement") {
var body = stat.body;
switch(body.TYPE) {
case "String":
return body.value;
case "Array":
return body.elements.map(function(element) {
if (element.TYPE !== "String")
throw new Error("Should be array of strings");
return element.value;
}).join("\n");
}
}
throw new Error("Should be string or array of strings");
}
function get_one_test(name, block) {
var test = { name: name, options: {} };
var tw = new U.TreeWalker(function(node, descend) {
if (node instanceof U.AST_Assign) {
if (!(node.left instanceof U.AST_SymbolRef)) {
croak(node);
}
var name = node.left.name;
test[name] = evaluate(node.right);
return true;
}
if (node instanceof U.AST_LabeledStatement) {
var label = node.label;
assert.ok([
"input",
"expect",
"expect_exact",
"expect_warnings",
"expect_stdout",
"node_version",
].indexOf(label.name) >= 0, tmpl("Unsupported label {name} [{line},{col}]", {
name: label.name,
line: label.start.line,
col: label.start.col
}));
var stat = node.body;
if (label.name == "expect_exact" || label.name == "node_version") {
test[label.name] = read_string(stat);
} else if (label.name == "expect_stdout") {
var body = stat.body;
if (body instanceof U.AST_Boolean) {
test[label.name] = body.value;
} else if (body instanceof U.AST_Call) {
var ctor = global[body.expression.name];
assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", {
line: label.start.line,
col: label.start.col
}));
test[label.name] = ctor.apply(null, body.args.map(function(node) {
assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", {
line: label.start.line,
col: label.start.col
}));
return node.value;
}));
} else {
test[label.name] = read_string(stat) + "\n";
}
} else {
test[label.name] = stat;
}
return true;
}
});
block.walk(tw);
return test;
}
}
// Try to reminify original input with standard options
// to see if it matches expect_stdout.
function reminify(orig_options, input_code, input_formatted, expect_stdout) {
for (var i = 0; i < minify_options.length; i++) {
var options = JSON.parse(minify_options[i]);
if (options.compress) [
"keep_fargs",
"keep_fnames",
].forEach(function(name) {
if (name in orig_options) {
options.compress[name] = orig_options[name];
}
});
var options_formatted = JSON.stringify(options, null, 4);
var result = U.minify(input_code, options);
if (result.error) {
log("!!! failed input reminify\n---INPUT---\n{input}\n---OPTIONS---\n{options}\n--ERROR---\n{error}\n\n", {
input: input_formatted,
options: options_formatted,
error: result.error,
});
return false;
} else {
var stdout = run_code(result.code);
if (typeof expect_stdout != "string" && typeof stdout != "string" && expect_stdout.name == stdout.name) {
stdout = expect_stdout;
}
if (!sandbox.same_stdout(expect_stdout, stdout)) {
log("!!! failed running reminified input\n---INPUT---\n{input}\n---OPTIONS---\n{options}\n---OUTPUT---\n{output}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_formatted,
options: options_formatted,
output: result.code,
expected_type: typeof expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout,
});
return false;
}
}
}
return true;
}
function run_code(code) {
var result = sandbox.run_code(code, true);
return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result;
}
function run_compress_tests() {
var dir = path.resolve(tests_dir, "compress");
fs.readdirSync(dir).filter(function(name) {
return /\.js$/i.test(name);
}).forEach(function(file) {
log("--- {file}", { file: file });
function test_case(test) {
log(" Running test [{name}]", { name: test.name });
var output_options = test.beautify || {};
var expect;
if (test.expect) {
expect = make_code(to_toplevel(test.expect, test.mangle), output_options);
} else {
expect = test.expect_exact;
}
var input = to_toplevel(test.input, test.mangle);
var input_code = make_code(input);
var input_formatted = make_code(test.input, {
beautify: true,
comments: "all",
keep_quoted_props: true,
quote_style: 3,
});
try {
U.parse(input_code);
} catch (ex) {
log("!!! Cannot parse input\n---INPUT---\n{input}\n--PARSE ERROR--\n{error}\n\n", {
input: input_formatted,
error: ex,
});
return false;
}
var warnings_emitted = [];
if (test.expect_warnings) {
var expected_warnings = make_code(test.expect_warnings, {
beautify: false,
quote_style: 2, // force double quote to match JSON
});
U.AST_Node.log_function(function(text) {
warnings_emitted.push(text);
}, /"INFO: /.test(expected_warnings));
}
if (test.mangle && test.mangle.properties && test.mangle.properties.keep_quoted) {
var quoted_props = test.mangle.properties.reserved;
if (!Array.isArray(quoted_props)) quoted_props = [];
test.mangle.properties.reserved = quoted_props;
U.reserve_quoted_keys(input, quoted_props);
}
if (test.rename) {
input.figure_out_scope(test.mangle);
input.expand_names(test.mangle);
}
var cmp = new U.Compressor(test.options, true);
var output = cmp.compress(input);
output.figure_out_scope(test.mangle);
if (test.mangle) {
output.compute_char_frequency(test.mangle);
output.mangle_names(test.mangle);
if (test.mangle.properties) {
output = U.mangle_properties(output, test.mangle.properties);
}
}
output = make_code(output, output_options);
if (expect != output) {
log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", {
input: input_formatted,
output: output,
expected: expect
});
return false;
}
// expect == output
try {
U.parse(output);
} catch (ex) {
log("!!! Test matched expected result but cannot parse output\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n--REPARSE ERROR--\n{error}\n\n", {
input: input_formatted,
output: output,
error: ex,
});
return false;
}
if (test.expect_warnings) {
warnings_emitted = warnings_emitted.map(function(input) {
return input.split(process.cwd() + path.sep).join("").split(path.sep).join("/");
});
var actual_warnings = JSON.stringify(warnings_emitted);
if (expected_warnings != actual_warnings) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED WARNINGS---\n{expected_warnings}\n---ACTUAL WARNINGS---\n{actual_warnings}\n\n", {
input: input_formatted,
expected_warnings: expected_warnings,
actual_warnings: actual_warnings,
});
return false;
}
}
if (test.expect_stdout
&& (!test.node_version || semver.satisfies(process.version, test.node_version))) {
var stdout = run_code(input_code);
if (test.expect_stdout === true) {
test.expect_stdout = stdout;
}
if (!sandbox.same_stdout(test.expect_stdout, stdout)) {
log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout,
});
return false;
}
stdout = run_code(output);
if (!sandbox.same_stdout(test.expect_stdout, stdout)) {
log("!!! failed\n---INPUT---\n{input}\n---EXPECTED {expected_type}---\n{expected}\n---ACTUAL {actual_type}---\n{actual}\n\n", {
input: input_formatted,
expected_type: typeof test.expect_stdout == "string" ? "STDOUT" : "ERROR",
expected: test.expect_stdout,
actual_type: typeof stdout == "string" ? "STDOUT" : "ERROR",
actual: stdout,
});
return false;
}
if (!reminify(test.options, input_code, input_formatted, test.expect_stdout)) {
return false;
}
}
return true;
}
var tests = parse_test(path.resolve(dir, file));
for (var i in tests) if (tests.hasOwnProperty(i)) {
if (!test_case(tests[i])) {
failures++;
failed_files[file] = 1;
}
}
});
}
function tmpl() {
return U.string_template.apply(null, arguments);
}
function to_toplevel(input, mangle_options) {
if (!(input instanceof U.AST_BlockStatement)) throw new Error("Unsupported input syntax");
var directive = true;
var offset = input.start.line;
var tokens = [];
var toplevel = new U.AST_Toplevel(input.transform(new U.TreeTransformer(function(node) {
if (U.push_uniq(tokens, node.start)) node.start.line -= offset;
if (!directive || node === input) return;
if (node instanceof U.AST_SimpleStatement && node.body instanceof U.AST_String) {
return new U.AST_Directive(node.body);
} else {
directive = false;
}
})));
toplevel.figure_out_scope(mangle_options);
return toplevel;
}

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;
}
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 context; exports.run_code = function(code, toplevel) {
exports.run_code = function(code, reuse) {
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:");