Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49ea629f3f | ||
|
|
13c72a986c | ||
|
|
08af3eae44 | ||
|
|
27bdcbbd83 | ||
|
|
2c4d7d66ef | ||
|
|
d1cc5270a3 | ||
|
|
75c5b6029b | ||
|
|
fa14a9cfcd | ||
|
|
aeb9ea5ac2 | ||
|
|
798841be82 | ||
|
|
cc6eb4b15f | ||
|
|
14eee81dc6 | ||
|
|
55ebb27878 | ||
|
|
87046410ef | ||
|
|
f9b3198714 | ||
|
|
48b62393a4 | ||
|
|
a00f8dade7 | ||
|
|
9daa2fb6f5 | ||
|
|
8d81d264f4 | ||
|
|
5ef7060098 | ||
|
|
938368ba21 | ||
|
|
fe2f1965d6 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
git clone --branch v1.5.4 --depth 1 https://github.com/jasongin/nvs.git ~/.nvs
|
||||
while ! timeout 60 bash -c '. ~/.nvs/nvs.sh add $NODE && nvs use $NODE'; do
|
||||
cd ~/.nvs
|
||||
git clean -xdf
|
||||
while !(git clean -xdf); do echo "'git clean' failed - retrying..."; done
|
||||
cd -
|
||||
done
|
||||
. ~/.nvs/nvs.sh --version
|
||||
|
||||
3
.github/workflows/ufuzz.yml
vendored
3
.github/workflows/ufuzz.yml
vendored
@@ -1,5 +1,6 @@
|
||||
name: Fuzzing
|
||||
on:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "*/8 * * * *"
|
||||
jobs:
|
||||
@@ -18,7 +19,7 @@ jobs:
|
||||
git clone --branch v1.5.4 --depth 1 https://github.com/jasongin/nvs.git ~/.nvs
|
||||
while ! timeout 60 bash -c '. ~/.nvs/nvs.sh add 10 && nvs use 10'; do
|
||||
cd ~/.nvs
|
||||
git clean -xdf
|
||||
while !(git clean -xdf); do echo "'git clean' failed - retrying..."; done
|
||||
cd -
|
||||
done
|
||||
. ~/.nvs/nvs.sh --version
|
||||
|
||||
112
lib/compress.js
112
lib/compress.js
@@ -738,8 +738,7 @@ merge(Compressor.prototype, {
|
||||
var value = iife.args[i];
|
||||
d.fixed = function() {
|
||||
var j = fn.argnames.indexOf(arg);
|
||||
if (j < 0) return value;
|
||||
return iife.args[j] || make_node(AST_Undefined, iife);
|
||||
return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife);
|
||||
};
|
||||
tw.loop_ids[d.id] = tw.in_loop;
|
||||
mark(tw, d, true);
|
||||
@@ -1302,7 +1301,7 @@ merge(Compressor.prototype, {
|
||||
value_def.replaced++;
|
||||
return List.skip;
|
||||
}
|
||||
return get_rvalue(candidate);
|
||||
return rvalue;
|
||||
case 1:
|
||||
if (!assign_used && node.body === candidate) {
|
||||
hit = true;
|
||||
@@ -1321,7 +1320,7 @@ merge(Compressor.prototype, {
|
||||
if (is_lhs(node, multi_replacer.parent())) return node;
|
||||
def.replaced++;
|
||||
value_def.replaced--;
|
||||
return get_rvalue(candidate).clone();
|
||||
return rvalue.clone();
|
||||
}
|
||||
// Skip (non-executed) functions and (leading) default case in switch statements
|
||||
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
|
||||
@@ -1345,13 +1344,14 @@ merge(Compressor.prototype, {
|
||||
var lhs = get_lhs(candidate);
|
||||
var side_effects = lhs && lhs.has_side_effects(compressor);
|
||||
var scan_lhs = lhs && !side_effects && !is_lhs_read_only(lhs, compressor);
|
||||
var scan_rhs = foldable(get_rhs(candidate));
|
||||
var scan_rhs = foldable(candidate);
|
||||
if (!scan_lhs && !scan_rhs) continue;
|
||||
var modify_toplevel = false;
|
||||
// Locate symbols which may execute code outside of scanning range
|
||||
var lvalues = get_lvalues(candidate);
|
||||
var lhs_local = is_lhs_local(lhs);
|
||||
if (!side_effects) side_effects = value_has_side_effects(candidate);
|
||||
var rvalue = get_rvalue(candidate);
|
||||
if (!side_effects) side_effects = value_has_side_effects();
|
||||
var replace_all = replace_all_symbols(candidate);
|
||||
var may_throw = candidate.may_throw(compressor) ? in_try ? function(node) {
|
||||
return node.has_side_effects(compressor);
|
||||
@@ -1422,6 +1422,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
|
||||
function should_stop(node, parent) {
|
||||
if (node === rvalue) return true;
|
||||
if (parent instanceof AST_For) return node !== parent.init;
|
||||
if (node instanceof AST_Assign) {
|
||||
return node.operator != "=" && lhs.equivalent_to(node.left);
|
||||
@@ -1429,8 +1430,7 @@ merge(Compressor.prototype, {
|
||||
if (node instanceof AST_Call) {
|
||||
if (!(lhs instanceof AST_PropAccess)) return false;
|
||||
if (!lhs.equivalent_to(node.expression)) return false;
|
||||
var rhs = get_rvalue(candidate);
|
||||
return !(rhs instanceof AST_Function && !rhs.contains_this());
|
||||
return !(rvalue instanceof AST_Function && !rvalue.contains_this());
|
||||
}
|
||||
if (node instanceof AST_Debugger) return true;
|
||||
if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name;
|
||||
@@ -1836,10 +1836,6 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
|
||||
function get_rhs(expr) {
|
||||
return candidate instanceof AST_Assign && candidate.operator == "=" && candidate.right;
|
||||
}
|
||||
|
||||
function invariant(expr) {
|
||||
if (expr instanceof AST_Array) return false;
|
||||
if (expr instanceof AST_Binary && lazy_op[expr.operator]) {
|
||||
@@ -1854,7 +1850,14 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
|
||||
function foldable(expr) {
|
||||
if (!expr) return false;
|
||||
var lhs_ids = Object.create(null);
|
||||
var marker = new TreeWalker(function(node) {
|
||||
if (node instanceof AST_SymbolRef) lhs_ids[node.definition().id] = true;
|
||||
});
|
||||
while (expr instanceof AST_Assign && expr.operator == "=") {
|
||||
expr.left.walk(marker);
|
||||
expr = expr.right;
|
||||
}
|
||||
if (expr instanceof AST_SymbolRef) {
|
||||
var value = expr.evaluate(compressor);
|
||||
if (value === expr) return rhs_exact_match;
|
||||
@@ -1868,12 +1871,9 @@ merge(Compressor.prototype, {
|
||||
if (!(lhs instanceof AST_SymbolRef)) return false;
|
||||
if (!invariant(expr)) return false;
|
||||
var circular;
|
||||
var def = lhs.definition();
|
||||
expr.walk(new TreeWalker(function(node) {
|
||||
if (circular) return true;
|
||||
if (node instanceof AST_SymbolRef && node.definition() === def) {
|
||||
circular = true;
|
||||
}
|
||||
if (node instanceof AST_SymbolRef && lhs_ids[node.definition().id]) circular = true;
|
||||
}));
|
||||
return !circular && rhs_exact_match;
|
||||
|
||||
@@ -1969,9 +1969,9 @@ merge(Compressor.prototype, {
|
||||
|| candidate instanceof AST_Assign && candidate.operator != "="));
|
||||
}
|
||||
|
||||
function value_has_side_effects(expr) {
|
||||
if (expr instanceof AST_Unary) return false;
|
||||
return get_rvalue(expr).has_side_effects(compressor);
|
||||
function value_has_side_effects() {
|
||||
if (candidate instanceof AST_Unary) return false;
|
||||
return rvalue.has_side_effects(compressor);
|
||||
}
|
||||
|
||||
function replace_all_symbols(expr) {
|
||||
@@ -2507,7 +2507,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (prop instanceof AST_Node) break;
|
||||
prop = "" + prop;
|
||||
var diff = compressor.has_directive("use strict") ? function(node) {
|
||||
var diff = prop == "__proto__" || compressor.has_directive("use strict") ? function(node) {
|
||||
return node.key != prop && node.key.name != prop;
|
||||
} : function(node) {
|
||||
return node.key.name != prop;
|
||||
@@ -3255,11 +3255,21 @@ merge(Compressor.prototype, {
|
||||
&& unaryPrefix[this.operator];
|
||||
}
|
||||
});
|
||||
function modified(sym) {
|
||||
if (!(sym instanceof AST_SymbolRef)) return;
|
||||
sym.definition().references.forEach(function(node) {
|
||||
delete node._eval;
|
||||
});
|
||||
var scan_modified = new TreeWalker(function(node) {
|
||||
if (node instanceof AST_Assign) modified(node.left);
|
||||
if (node instanceof AST_Unary && unary_arithmetic[node.operator]) modified(node.expression);
|
||||
});
|
||||
function modified(node) {
|
||||
if (node instanceof AST_Dot) {
|
||||
modified(node.expression);
|
||||
} else if (node instanceof AST_Sub) {
|
||||
modified(node.expression);
|
||||
node.property.walk(scan_modified);
|
||||
} else if (node instanceof AST_SymbolRef) {
|
||||
node.definition().references.forEach(function(ref) {
|
||||
delete ref._eval;
|
||||
});
|
||||
}
|
||||
}
|
||||
def(AST_Statement, function() {
|
||||
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
|
||||
@@ -3279,12 +3289,11 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
def(AST_Sequence, function(compressor, ignore_side_effects, cached, depth) {
|
||||
if (!ignore_side_effects) return this;
|
||||
var tail = this.tail_node();
|
||||
this.walk(new TreeWalker(function(node) {
|
||||
if (node === tail) return true;
|
||||
if (node instanceof AST_Assign) modified(node.left);
|
||||
if (node instanceof AST_Unary && unary_arithmetic[node.operator]) modified(node.expression);
|
||||
}));
|
||||
var exprs = this.expressions;
|
||||
for (var i = 0, last = exprs.length - 1; i < last; i++) {
|
||||
exprs[i].walk(scan_modified);
|
||||
}
|
||||
var tail = exprs[last];
|
||||
var value = tail._eval(compressor, ignore_side_effects, cached, depth);
|
||||
return value === tail ? this : value;
|
||||
});
|
||||
@@ -3565,8 +3574,10 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
var args = eval_args(this.args);
|
||||
if (!args && !ignore_side_effects) return this;
|
||||
if (!stat.value) return;
|
||||
if (args && !all(fn.argnames, function(sym, i) {
|
||||
var val = stat.value;
|
||||
if (!val) return;
|
||||
var cached_args = [];
|
||||
if (!args || all(fn.argnames, function(sym, i) {
|
||||
var value = args[i];
|
||||
var def = sym.definition();
|
||||
if (def.orig[def.orig.length - 1] !== sym) return false;
|
||||
@@ -3574,15 +3585,18 @@ merge(Compressor.prototype, {
|
||||
node._eval = function() {
|
||||
return value;
|
||||
};
|
||||
cached.push(node);
|
||||
cached_args.push(node);
|
||||
});
|
||||
return true;
|
||||
}) && !ignore_side_effects) return this;
|
||||
fn.evaluating = true;
|
||||
var val = stat.value._eval(compressor, ignore_side_effects, cached, depth);
|
||||
delete fn.evaluating;
|
||||
if (val === stat.value) return this;
|
||||
return val;
|
||||
}) || ignore_side_effects) {
|
||||
fn.evaluating = true;
|
||||
val = val._eval(compressor, ignore_side_effects, cached, depth);
|
||||
delete fn.evaluating;
|
||||
}
|
||||
cached_args.forEach(function(node) {
|
||||
delete node._eval;
|
||||
});
|
||||
return val === stat.value ? this : val;
|
||||
} else if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
|
||||
var key = exp.property;
|
||||
if (key instanceof AST_Node) {
|
||||
@@ -4358,6 +4372,7 @@ merge(Compressor.prototype, {
|
||||
} else if (compressor.option("functions")
|
||||
&& !compressor.option("ie8")
|
||||
&& var_defs.length == 1
|
||||
&& sym.assignments == 0
|
||||
&& def.value === def.name.fixed_value()
|
||||
&& def.value instanceof AST_Function
|
||||
&& !(def.value.name && def.value.name.definition().assignments)
|
||||
@@ -6227,9 +6242,9 @@ merge(Compressor.prototype, {
|
||||
&& !fn.uses_arguments
|
||||
&& !fn.pinned()
|
||||
&& !(fn.name && fn instanceof AST_Function)
|
||||
&& (value = can_flatten_body(stat))
|
||||
&& (exp === fn
|
||||
|| !recursive_ref(compressor, def = exp.definition()) && fn.is_constant_expression(exp.scope))
|
||||
&& (value = can_flatten_body(stat))
|
||||
&& !fn.contains_this()) {
|
||||
if (can_substitute_directly()) {
|
||||
var args = self.args.slice();
|
||||
@@ -7508,6 +7523,8 @@ merge(Compressor.prototype, {
|
||||
scope.inlined = true;
|
||||
} while (scope = scope.parent_scope);
|
||||
}
|
||||
} else if (compressor.option("ie8") && fixed.name && def !== fixed.name.definition()) {
|
||||
single_use = false;
|
||||
}
|
||||
if (single_use) fixed.parent_scope = self.scope;
|
||||
} else if (!fixed || !fixed.is_constant_expression()) {
|
||||
@@ -7566,6 +7583,12 @@ merge(Compressor.prototype, {
|
||||
}));
|
||||
} else {
|
||||
value = fixed.optimize(compressor);
|
||||
if (value === fixed) value = value.transform(new TreeTransformer(function(node, descend) {
|
||||
if (node instanceof AST_Scope) return node;
|
||||
node = node.clone();
|
||||
descend(node, this);
|
||||
return node;
|
||||
}));
|
||||
}
|
||||
def.replaced++;
|
||||
return value;
|
||||
@@ -7727,6 +7750,10 @@ merge(Compressor.prototype, {
|
||||
if (compressor.option("dead_code")) {
|
||||
if (self.left instanceof AST_PropAccess) {
|
||||
if (self.operator == "=") {
|
||||
if (self.left.equivalent_to(self.right)
|
||||
&& (self.left instanceof AST_Dot || !self.left.property.has_side_effects(compressor))) {
|
||||
return self.right;
|
||||
}
|
||||
var exp = self.left.expression;
|
||||
if (exp instanceof AST_Lambda
|
||||
|| !compressor.has_directive("use strict")
|
||||
@@ -7739,6 +7766,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
} else if (self.left instanceof AST_SymbolRef) {
|
||||
if (self.operator == "=" && self.left.equivalent_to(self.right)) return self.right;
|
||||
if (self.left.is_immutable()) return strip_assignment();
|
||||
var def = self.left.definition();
|
||||
var scope = def.scope.resolve();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
||||
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
|
||||
"license": "BSD-2-Clause",
|
||||
"version": "3.9.3",
|
||||
"version": "3.9.4",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ var zlib = require("zlib");
|
||||
var args = process.argv.slice(2);
|
||||
if (!args.length) args.push("-mc");
|
||||
args.unshift("bin/uglifyjs");
|
||||
args.push("--validate", "--timings");
|
||||
args.push("--timings");
|
||||
var urls = [
|
||||
"https://code.jquery.com/jquery-3.4.1.js",
|
||||
"https://code.angularjs.org/1.7.8/angular.js",
|
||||
|
||||
@@ -8015,3 +8015,96 @@ issue_3891: {
|
||||
}
|
||||
expect_stdout: "function"
|
||||
}
|
||||
|
||||
issue_3894: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
inline: true,
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function log(msg) {
|
||||
console.log(msg ? "FAIL" : "PASS");
|
||||
}
|
||||
var a, c;
|
||||
(function(b) {
|
||||
a = c = 0,
|
||||
log(b);
|
||||
})(-0);
|
||||
log(a);
|
||||
log(c);
|
||||
}
|
||||
expect: {
|
||||
function log(msg) {
|
||||
console.log(msg ? "FAIL" : "PASS");
|
||||
}
|
||||
var a, c;
|
||||
void log(-(a = c = 0));
|
||||
log(a);
|
||||
log(c);
|
||||
}
|
||||
expect_stdout: [
|
||||
"PASS",
|
||||
"PASS",
|
||||
"PASS",
|
||||
]
|
||||
}
|
||||
|
||||
issue_3897: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a = 0;
|
||||
(function() {
|
||||
function f(b) {
|
||||
b = a = 1 + a;
|
||||
a = 1 + a;
|
||||
console.log(b);
|
||||
}
|
||||
f();
|
||||
})();
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a = 0;
|
||||
(function() {
|
||||
function f(b) {
|
||||
b = a = 1 + a;
|
||||
a = 1 + a;
|
||||
console.log(b);
|
||||
}
|
||||
f();
|
||||
})();
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: [
|
||||
"1",
|
||||
"2",
|
||||
]
|
||||
}
|
||||
|
||||
issue_3908: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
conditionals: true,
|
||||
reduce_vars: true,
|
||||
side_effects: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
if (console) {
|
||||
var o = {
|
||||
p: !1
|
||||
}, a = o;
|
||||
}
|
||||
console.log("PASS");
|
||||
}
|
||||
expect: {
|
||||
console && 0;
|
||||
console.log("PASS");
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -1168,3 +1168,28 @@ redundant_assignments: {
|
||||
}
|
||||
expect_stdout: "PASS PASS"
|
||||
}
|
||||
|
||||
self_assignments: {
|
||||
options = {
|
||||
dead_code: true,
|
||||
}
|
||||
input: {
|
||||
var a = "PASS", b = 0, l = [ "FAIL", "PASS" ], o = { p: "PASS" };
|
||||
a = a;
|
||||
l[0] = l[0];
|
||||
l[b] = l[b];
|
||||
l[b++] = l[b++];
|
||||
o.p = o.p;
|
||||
console.log(a, b, l[0], o.p);
|
||||
}
|
||||
expect: {
|
||||
var a = "PASS", b = 0, l = [ "FAIL", "PASS" ], o = { p: "PASS" };
|
||||
a;
|
||||
l[0];
|
||||
l[b];
|
||||
l[b++] = l[b++];
|
||||
o.p;
|
||||
console.log(a, b, l[0], o.p);
|
||||
}
|
||||
expect_stdout: "PASS 2 PASS PASS"
|
||||
}
|
||||
|
||||
@@ -2527,7 +2527,40 @@ issue_3802_2: {
|
||||
}
|
||||
expect: {
|
||||
0;
|
||||
function a() {};
|
||||
var a = function() {};
|
||||
console.log(typeof a);
|
||||
}
|
||||
expect_stdout: "function"
|
||||
}
|
||||
|
||||
issue_3899: {
|
||||
options = {
|
||||
assignments: true,
|
||||
evaluate: true,
|
||||
functions: true,
|
||||
inline: true,
|
||||
join_vars: true,
|
||||
passes: 2,
|
||||
reduce_vars: true,
|
||||
side_effects: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a = 0;
|
||||
a = a + 1;
|
||||
var a = function f(b) {
|
||||
return function() {
|
||||
return b;
|
||||
};
|
||||
}(2);
|
||||
console.log(typeof a);
|
||||
}
|
||||
expect: {
|
||||
++a;
|
||||
var a = function() {
|
||||
return 2;
|
||||
};
|
||||
console.log(typeof a);
|
||||
}
|
||||
expect_stdout: "function"
|
||||
|
||||
@@ -2418,3 +2418,70 @@ issue_3887: {
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_3903: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a = "PASS";
|
||||
function f(b, c) {
|
||||
return console, c;
|
||||
}
|
||||
var d = f(f(), a = a);
|
||||
console.log(d);
|
||||
}
|
||||
expect: {
|
||||
var a = "PASS";
|
||||
function f(b, c) {
|
||||
return console, c;
|
||||
}
|
||||
var d = f(f(), a = a);
|
||||
console.log(d);
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_3905: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
passes: 2,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
(function(a, a) {
|
||||
return console.log(a = 0), a && console.log("FAIL");
|
||||
})("foo", 42);
|
||||
}
|
||||
expect: {
|
||||
(function(a, a) {
|
||||
return console.log(a = 0), a && console.log("FAIL");
|
||||
})("foo", 42);
|
||||
}
|
||||
expect_stdout: "0"
|
||||
}
|
||||
|
||||
issue_3920: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a = function(b) {
|
||||
return (b[b = 0] = 0) >= (b ? 0 : 1);
|
||||
}("foo");
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a = function(b) {
|
||||
return (b[b = 0] = 0) >= (b ? 0 : 1);
|
||||
}("foo");
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: "false"
|
||||
}
|
||||
|
||||
@@ -4680,3 +4680,28 @@ issue_3852: {
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_3911: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
conditionals: true,
|
||||
inline: true,
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function f() {
|
||||
return function() {
|
||||
if (a) (a++, b += a);
|
||||
f();
|
||||
};
|
||||
}
|
||||
var a = f, b;
|
||||
console.log("PASS");
|
||||
}
|
||||
expect: {
|
||||
console.log("PASS");
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -2489,3 +2489,36 @@ issue_3889: {
|
||||
}
|
||||
expect_stdout: "undefined"
|
||||
}
|
||||
|
||||
issue_3918: {
|
||||
options = {
|
||||
conditionals: true,
|
||||
ie8: true,
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
mangle = {
|
||||
ie8: true,
|
||||
}
|
||||
input: {
|
||||
if (console.log("PASS")) {
|
||||
var a = function f() {
|
||||
f.p;
|
||||
try {
|
||||
console.log("FAIL");
|
||||
} catch (e) {}
|
||||
}, b = a;
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
var a;
|
||||
console.log("PASS") && (a = function f() {
|
||||
f.p;
|
||||
try {
|
||||
console.log("FAIL");
|
||||
} catch (o) {}
|
||||
}, a);
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -1023,3 +1023,35 @@ issue_3856: {
|
||||
}
|
||||
expect_stdout: "undefined"
|
||||
}
|
||||
|
||||
issue_3916: {
|
||||
options = {
|
||||
join_vars: true,
|
||||
}
|
||||
input: {
|
||||
var o = {};
|
||||
o.p = "PASS";
|
||||
o.__proto__ = 42;
|
||||
o.q = "FAIL";
|
||||
o.__proto__ = {
|
||||
p: "FAIL",
|
||||
q: "PASS",
|
||||
};
|
||||
o.__proto__ = "foo";
|
||||
console.log(typeof o.__proto__, o.p, delete o.q, o.q);
|
||||
}
|
||||
expect: {
|
||||
var o = {
|
||||
p: "PASS",
|
||||
__proto__: 42,
|
||||
q: "FAIL",
|
||||
};
|
||||
o.__proto__ = {
|
||||
p: "FAIL",
|
||||
q: "PASS",
|
||||
};
|
||||
o.__proto__ = "foo";
|
||||
console.log(typeof o.__proto__, o.p, delete o.q, o.q);
|
||||
}
|
||||
expect_stdout: "object PASS true PASS"
|
||||
}
|
||||
|
||||
@@ -7068,3 +7068,57 @@ issue_3880: {
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_3894: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
inline: true,
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
function log(msg) {
|
||||
console.log(msg ? "FAIL" : "PASS");
|
||||
}
|
||||
var a;
|
||||
(function(b) {
|
||||
a = 0,
|
||||
log(b);
|
||||
})(-0);
|
||||
}
|
||||
expect: {
|
||||
function log(msg) {
|
||||
console.log(msg ? "FAIL" : "PASS");
|
||||
}
|
||||
var a;
|
||||
void log(-(a = 0));
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_3922: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
keep_fargs: "strict",
|
||||
pure_getters: "strict",
|
||||
reduce_vars: true,
|
||||
side_effects: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
(function(a) {
|
||||
var b;
|
||||
b && b[c];
|
||||
a |= this;
|
||||
console.log("PASS");
|
||||
var c = a.undefined;
|
||||
})();
|
||||
}
|
||||
expect: {
|
||||
(function() {
|
||||
0;
|
||||
console.log("PASS");
|
||||
})();
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -4,19 +4,17 @@ var List = U.List;
|
||||
var os = require("os");
|
||||
var sandbox = require("./sandbox");
|
||||
|
||||
// Reduce a ufuzz-style `console.log` based test case by iteratively replacing
|
||||
// AST nodes with various permutations. Each AST_Statement in the tree is also
|
||||
// speculatively dropped to determine whether it is needed. If the altered
|
||||
// tree and the last known good tree produce the same non-nil error-free output
|
||||
// after being run, then the permutation survives to the next generation and
|
||||
// is the basis for subsequent iterations. The test case is reduced as a
|
||||
// consequence of complex expressions being replaced with simpler ones.
|
||||
// This function assumes that the testcase will not result in a parse or
|
||||
// runtime Error. Note that a reduced test case will have different runtime
|
||||
// output - it is not functionally equivalent to the original. The only criteria
|
||||
// is that once the generated reduced test case is run without minification, it
|
||||
// will produce different output from the code minified with `minify_options`.
|
||||
// Returns a `minify` result object with an additonal boolean property `reduced`.
|
||||
// Reduce a test case by iteratively replacing AST nodes with various
|
||||
// permutations. Each AST_Statement in the tree is also speculatively dropped
|
||||
// to determine whether it is needed. If the altered tree and the last known
|
||||
// good tree produce the same output after being run, then the permutation
|
||||
// survives to the next generation and is the basis for subsequent iterations.
|
||||
// The test case is reduced as a consequence of complex expressions being
|
||||
// replaced with simpler ones. Note that a reduced test case will have
|
||||
// different runtime output - it is not functionally equivalent to the
|
||||
// original. The only criteria is that once the generated reduced test case is
|
||||
// run without minification, it will produce different output from the code
|
||||
// minified with `minify_options`. Returns a `minify` result object.
|
||||
|
||||
Error.stackTraceLimit = Infinity;
|
||||
module.exports = function reduce_test(testcase, minify_options, reduce_options) {
|
||||
|
||||
@@ -9,6 +9,6 @@ require("./run")([
|
||||
"-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");
|
||||
args.unshift("test/benchmark.js", "--validate");
|
||||
return args;
|
||||
}));
|
||||
|
||||
@@ -1018,6 +1018,7 @@ function log_suspects(minify_options, component) {
|
||||
if (!options) return;
|
||||
if (typeof options != "object") options = {};
|
||||
var defs = default_options[component];
|
||||
var toplevel = sandbox.has_toplevel(minify_options);
|
||||
var suspects = Object.keys(defs).filter(function(name) {
|
||||
var flip = name == "keep_fargs";
|
||||
if (flip !== (name in options ? options : defs)[name]) {
|
||||
@@ -1025,12 +1026,15 @@ function log_suspects(minify_options, component) {
|
||||
var o = JSON.parse(JSON.stringify(options));
|
||||
o[name] = flip;
|
||||
m[component] = o;
|
||||
m.validate = true;
|
||||
var result = UglifyJS.minify(original_code, m);
|
||||
if (result.error) {
|
||||
if (typeof uglify_code != "string") {
|
||||
return !sandbox.same_stdout(uglify_code, result.error);
|
||||
} else if (result.error) {
|
||||
errorln("Error testing options." + component + "." + name);
|
||||
errorln(result.error);
|
||||
} else {
|
||||
var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
|
||||
var r = sandbox.run_code(result.code, toplevel);
|
||||
return !sandbox.same_stdout(uglify_result, r);
|
||||
}
|
||||
}
|
||||
@@ -1044,18 +1048,21 @@ function log_suspects(minify_options, component) {
|
||||
}
|
||||
}
|
||||
|
||||
function log_suspects_global(options) {
|
||||
function log_suspects_global(options, toplevel) {
|
||||
var suspects = Object.keys(default_options).filter(function(component) {
|
||||
return typeof default_options[component] != "object";
|
||||
}).filter(function(component) {
|
||||
var m = JSON.parse(options);
|
||||
m[component] = false;
|
||||
m.validate = true;
|
||||
var result = UglifyJS.minify(original_code, m);
|
||||
if (result.error) {
|
||||
if (typeof uglify_code != "string") {
|
||||
return !sandbox.same_stdout(uglify_code, result.error);
|
||||
} else if (result.error) {
|
||||
errorln("Error testing options." + component);
|
||||
errorln(result.error);
|
||||
} else {
|
||||
var r = sandbox.run_code(result.code, sandbox.has_toplevel(m));
|
||||
var r = sandbox.run_code(result.code, toplevel);
|
||||
return !sandbox.same_stdout(uglify_result, r);
|
||||
}
|
||||
});
|
||||
@@ -1074,7 +1081,37 @@ function log(options) {
|
||||
errorln("//=============================================================");
|
||||
if (!ok) errorln("// !!!!!! Failed... round " + round);
|
||||
errorln("// original code");
|
||||
try_beautify(original_code, toplevel, original_result, errorln);
|
||||
var beautified = UglifyJS.minify(original_code, {
|
||||
compress: false,
|
||||
mangle: false,
|
||||
output: {
|
||||
beautify: true,
|
||||
braces: true,
|
||||
},
|
||||
});
|
||||
if (beautified.error) {
|
||||
errorln("// !!! beautify failed !!!");
|
||||
errorln(beautified.error);
|
||||
errorln("//");
|
||||
errorln(original_code);
|
||||
} else {
|
||||
var uglified = UglifyJS.minify(beautified.code, JSON.parse(options));
|
||||
var expected, actual;
|
||||
if (typeof uglify_code != "string" || uglified.error) {
|
||||
expected = uglify_code;
|
||||
actual = uglified.error;
|
||||
} else {
|
||||
expected = uglify_result;
|
||||
actual = sandbox.run_code(uglified.code, toplevel);
|
||||
}
|
||||
if (sandbox.same_stdout(expected, actual)) {
|
||||
errorln("// (beautified)");
|
||||
errorln(beautified.code);
|
||||
} else {
|
||||
errorln("//");
|
||||
errorln(original_code);
|
||||
}
|
||||
}
|
||||
errorln();
|
||||
errorln();
|
||||
errorln("//-------------------------------------------------------------");
|
||||
@@ -1112,12 +1149,12 @@ function log(options) {
|
||||
errorln("minify(options):");
|
||||
errorln(JSON.stringify(JSON.parse(options), null, 2));
|
||||
errorln();
|
||||
if (!ok && typeof uglify_code == "string") {
|
||||
if (!ok) {
|
||||
Object.keys(default_options).filter(function(component) {
|
||||
var defs = default_options[component];
|
||||
return defs && typeof defs == "object";
|
||||
}).forEach(log_suspects.bind(null, JSON.parse(options)));
|
||||
log_suspects_global(options);
|
||||
log_suspects_global(options, toplevel);
|
||||
errorln("!!!!!! Failed... round " + round);
|
||||
}
|
||||
}
|
||||
@@ -1140,19 +1177,19 @@ function skip_infinite_recursion(orig, toplevel) {
|
||||
var code = orig;
|
||||
var tries = [];
|
||||
var offset = 0;
|
||||
var re = /(?:(?:^|[\s{};])try|}\s*catch\s*\(([^)]+)\)|}\s*finally)\s*(?={)/g;
|
||||
var re = /(?:(?:^|[\s{}):;])try|}\s*catch\s*\(([^)]+)\)|}\s*finally)\s*(?={)/g;
|
||||
var match;
|
||||
while (match = re.exec(code)) {
|
||||
if (/}\s*finally\s*$/.test(match[0])) {
|
||||
tries.shift();
|
||||
continue;
|
||||
}
|
||||
if (tries.length && tries[0].catch) tries.shift();
|
||||
var index = match.index + match[0].length + 1;
|
||||
if (/(?:^|[\s{};])try\s*$/.test(match[0])) {
|
||||
if (/(?:^|[\s{}):;])try\s*$/.test(match[0])) {
|
||||
tries.unshift({ try: index - offset });
|
||||
continue;
|
||||
}
|
||||
while (tries.length && tries[0].catch) tries.shift();
|
||||
tries[0].catch = index;
|
||||
var insert = "throw " + match[1] + ".ufuzz_skip || (" + match[1] + ".ufuzz_skip = " + tries[0].try + "), " + match[1] + ";";
|
||||
var new_code = code.slice(0, index) + insert + code.slice(index);
|
||||
|
||||
Reference in New Issue
Block a user