Compare commits

...

10 Commits

Author SHA1 Message Date
Alex Lam S.L
30761eede5 v3.10.4 2020-09-07 00:25:54 +08:00
Alex Lam S.L
fb30aeccaf relax ufuzz job timing constraint (#4094) 2020-09-05 19:29:50 +08:00
Alex Lam S.L
226aa1f76b enhance unsafe_math (#4093) 2020-09-04 10:14:39 +08:00
Alex Lam S.L
6e235602fb fix corner case in loops & unused (#4092)
fixes #4091
2020-09-04 01:51:26 +08:00
Alex Lam S.L
980fcbb56b enhance unused (#4090) 2020-09-03 17:41:33 +08:00
Alex Lam S.L
375ebe316d enhance join_vars (#4089) 2020-09-03 01:41:10 +08:00
Alex Lam S.L
2500930234 enhance reduce_vars (#4088) 2020-09-02 11:30:46 +08:00
Alex Lam S.L
2f0da2ff05 reduce AST_ForIn gracefully (#4087) 2020-09-02 08:51:43 +08:00
Alex Lam S.L
83a3cbf151 fix test case runtime accounting (#4086) 2020-09-02 03:23:08 +08:00
Alex Lam S.L
da8d154571 fix corner case in loops & unused (#4085)
fixes #4084
2020-09-02 03:20:58 +08:00
12 changed files with 312 additions and 115 deletions

View File

@@ -325,22 +325,22 @@ merge(Compressor.prototype, {
}
var lhs = is_lhs(node, parent);
if (lhs) return lhs;
if (!immutable
&& parent instanceof AST_Call
&& parent.expression === node
&& !parent.is_expr_pure(compressor)
&& (!(value instanceof AST_Function)
|| !(parent instanceof AST_New) && value.contains_this())) {
return true;
if (parent instanceof AST_Array) return is_modified(compressor, tw, parent, parent, level + 1);
if (parent instanceof AST_Call) {
return !immutable
&& parent.expression === node
&& !parent.is_expr_pure(compressor)
&& (!(value instanceof AST_Function)
|| !(parent instanceof AST_New) && value.contains_this());
}
if (parent instanceof AST_Array) {
return is_modified(compressor, tw, parent, parent, level + 1);
}
if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
if (parent instanceof AST_ForIn) return parent.init === node;
if (parent instanceof AST_ObjectKeyVal) {
if (parent.value !== node) return;
var obj = tw.parent(level + 1);
return is_modified(compressor, tw, obj, obj, level + 2);
}
if (parent instanceof AST_PropAccess && parent.expression === node) {
if (parent instanceof AST_PropAccess) {
if (parent.expression !== node) return;
var prop = read_property(value, parent);
return (!immutable || recursive) && is_modified(compressor, tw, parent, prop, level + 1);
}
@@ -514,33 +514,41 @@ merge(Compressor.prototype, {
|| value instanceof AST_This;
}
function has_escaped(d, node, parent) {
if (parent instanceof AST_Assign) return parent.operator == "=" && parent.right === node;
if (parent instanceof AST_Call) return parent.expression !== node || parent instanceof AST_New;
if (parent instanceof AST_Exit) return parent.value === node && node.scope !== d.scope;
if (parent instanceof AST_VarDef) return parent.value === node;
}
function value_in_use(node, parent) {
if (parent instanceof AST_Array) return true;
if (parent instanceof AST_Binary) return lazy_op[parent.operator];
if (parent instanceof AST_Conditional) return parent.condition !== node;
if (parent instanceof AST_Sequence) return parent.tail_node() === node;
}
function mark_escaped(tw, d, scope, node, value, level, depth) {
var parent = tw.parent(level);
if (value && value.is_constant()) return;
if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right
|| parent instanceof AST_Call && (node !== parent.expression || parent instanceof AST_New)
|| parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
|| parent instanceof AST_VarDef && node === parent.value) {
if (has_escaped(d, node, parent)) {
d.escaped.push(parent);
if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1;
if (!d.escaped.depth || d.escaped.depth > depth) d.escaped.depth = depth;
return;
} else if (parent instanceof AST_Array
|| parent instanceof AST_Binary && lazy_op[parent.operator]
|| parent instanceof AST_Conditional && node !== parent.condition
|| parent instanceof AST_Sequence && node === parent.tail_node()) {
} else if (value_in_use(node, parent)) {
mark_escaped(tw, d, scope, parent, parent, level + 1, depth);
} else if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
} else if (parent instanceof AST_ObjectKeyVal && parent.value === node) {
var obj = tw.parent(level + 1);
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 && parent.expression === node) {
value = read_property(value, parent);
mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1);
if (value) 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_Call && parent.expression === node) return;
if (parent instanceof AST_Sequence && parent.tail_node() !== node) return;
if (parent instanceof AST_SimpleStatement) return;
if (parent instanceof AST_Unary && !unary_side_effects[parent.operator]) return;
d.direct_access = true;
@@ -739,13 +747,11 @@ merge(Compressor.prototype, {
push(tw);
var init = this.init;
init.walk(tw);
if (init instanceof AST_Var) {
init = init.definitions[0].name;
} else while (init instanceof AST_PropAccess) {
init = init.expression.tail_node();
if (init instanceof AST_SymbolRef) {
init.definition().fixed = false;
} else if (init instanceof AST_Var) {
init.definitions[0].name.definition().fixed = false;
}
var def = init.definition();
if (def) def.fixed = false;
this.body.walk(tw);
pop(tw);
tw.in_loop = saved_loop;
@@ -2638,6 +2644,14 @@ merge(Compressor.prototype, {
defs = stat.init;
}
} else if (stat instanceof AST_ForIn) {
if (defs && defs.TYPE == stat.init.TYPE) {
defs.definitions = defs.definitions.concat(stat.init.definitions);
var name = stat.init.definitions[0].name;
var ref = make_node(AST_SymbolRef, name, name);
name.definition().references.push(ref);
stat.init = ref;
CHANGED = true;
}
stat.object = join_assigns_expr(stat.object);
} else if (stat instanceof AST_If) {
stat.condition = join_assigns_expr(stat.condition);
@@ -4298,6 +4312,7 @@ merge(Compressor.prototype, {
return sym;
};
var assign_in_use = Object.create(null);
var for_ins = Object.create(null);
var in_use = [];
var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use
var value_read = Object.create(null);
@@ -4588,23 +4603,37 @@ merge(Compressor.prototype, {
return !def || fn.name && def === fn.name.definition();
}
});
if (head.length == 0 && tail.length == duplicated) {
[].unshift.apply(side_effects, tail.map(function(def) {
AST_Node.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
var sym = def.name.definition();
var ref = make_node(AST_SymbolRef, def.name, def.name);
sym.references.push(ref);
var assign = make_node(AST_Assign, def, {
operator: "=",
left: ref,
right: def.value
});
var index = indexOf_assign(sym, def);
if (index >= 0) assign_in_use[sym.id][index] = assign;
sym.eliminated++;
return assign;
}));
} else if (head.length > 0 || tail.length > 0) {
switch (head.length) {
case 0:
if (tail.length == 0) break;
if (tail.length == duplicated) {
[].unshift.apply(side_effects, tail.map(function(def) {
AST_Node.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
var sym = def.name.definition();
var ref = make_node(AST_SymbolRef, def.name, def.name);
sym.references.push(ref);
var assign = make_node(AST_Assign, def, {
operator: "=",
left: ref,
right: def.value
});
var index = indexOf_assign(sym, def);
if (index >= 0) assign_in_use[sym.id][index] = assign;
sym.eliminated++;
return assign;
}));
break;
}
case 1:
if (tail.length == 0) {
var id = head[0].name.definition().id;
if (id in for_ins) {
node.definitions = head;
for_ins[id].init = node;
break;
}
}
default:
node.definitions = head.concat(tail);
body.push(node);
}
@@ -4613,16 +4642,7 @@ merge(Compressor.prototype, {
body: make_sequence(node, side_effects)
}));
}
switch (body.length) {
case 0:
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
case 1:
return body[0];
default:
return in_list ? List.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
}
return insert_statements(body, node, in_list);
}
if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
// Certain combination of unused name + side effect leads to invalid AST:
@@ -4682,15 +4702,22 @@ merge(Compressor.prototype, {
}
var def = sym.definition();
if (!def) return;
if (def.scope !== self) return;
if (def.id in in_use_ids) return;
if (def.scope !== self && member(def, self.enclosed)) return;
log(sym, "Dropping unused loop variable {name}");
if (for_ins[def.id] === node) delete for_ins[def.id];
var body = [];
var value = node.object.drop_side_effect_free(compressor);
if (!value) return in_list ? List.skip : make_node(AST_EmptyStatement, node);
AST_Node.warn("Side effects in object of for-in loop [{file}:{line},{col}]", value.start);
return make_node(AST_SimpleStatement, node, {
body: value
});
if (value) {
AST_Node.warn("Side effects in object of for-in loop [{file}:{line},{col}]", value.start);
body.push(make_node(AST_SimpleStatement, node, {
body: value
}));
}
if (node.init instanceof AST_Definitions && def.orig[0] instanceof AST_SymbolCatch) {
body.push(node.init);
}
return insert_statements(body, node, in_list);
} else if (node instanceof AST_Sequence) {
if (node.expressions.length == 1) return node.expressions[0];
}
@@ -4723,6 +4750,19 @@ merge(Compressor.prototype, {
};
}
function insert_statements(body, orig, in_list) {
switch (body.length) {
case 0:
return in_list ? List.skip : make_node(AST_EmptyStatement, orig);
case 1:
return body[0];
default:
return in_list ? List.splice(body) : make_node(AST_BlockStatement, orig, {
body: body
});
}
}
function track_assigns(def, node) {
if (def.scope !== self) return false;
if (!def.fixed || !node.fixed) assign_in_use[def.id] = false;
@@ -4800,6 +4840,10 @@ merge(Compressor.prototype, {
return true;
}
if (node instanceof AST_ForIn) {
if (node.init instanceof AST_SymbolRef && scope === self) {
var id = node.init.definition().id;
if (!(id in for_ins)) for_ins[id] = node;
}
if (!drop_vars || !compressor.option("loops")) return;
if (!is_empty(node.body)) return;
if (node.init.has_side_effects(compressor)) return;
@@ -7512,13 +7556,15 @@ merge(Compressor.prototype, {
&& self.left.is_number(compressor)) {
if (self.left.left instanceof AST_Constant) {
var lhs = make_binary(self.left, self.operator, self.left.left, self.right, self.left.left.start, self.right.end);
self = make_binary(self, self.left.operator, lhs, self.left.right);
self = make_binary(self, self.left.operator, try_evaluate(compressor, lhs), self.left.right);
} else if (self.left.right instanceof AST_Constant) {
var rhs = make_binary(self.left, align(self.left.operator, self.operator), self.left.right, self.right, self.left.right.start, self.right.end);
if (self.left.operator != "-"
|| !self.right.value
|| rhs.evaluate(compressor)
|| !self.left.left.is_negative_zero()) {
var op = align(self.left.operator, self.operator);
var rhs = try_evaluate(compressor, make_binary(self.left, op, self.left.right, self.right));
if (rhs.is_constant()
&& !(self.left.operator == "-"
&& self.right.value != 0
&& +rhs.value == 0
&& self.left.left.is_negative_zero())) {
self = make_binary(self, self.left.operator, self.left.left, rhs);
}
}

View File

@@ -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.10.3",
"version": "3.10.4",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -2848,3 +2848,60 @@ issue_4025: {
"1 1 1",
]
}
forin_var_1: {
options = {
unused: true,
}
input: {
var k;
for (k in [ 1, 2 ])
console.log(k);
for (k in { PASS: 3 })
console.log(k);
console.log(k);
}
expect: {
for (var k in [ 1, 2 ])
console.log(k);
for (k in { PASS: 3 })
console.log(k);
console.log(k);
}
expect_stdout: [
"0",
"1",
"PASS",
"PASS",
]
}
forin_var_2: {
options = {
unused: true,
}
input: {
console.log(function() {
switch (0) {
case function() {
for (a in 0);
}:
var b = 0;
}
for (var c = 0; a;);
var a;
}());
}
expect: {
console.log(function() {
switch (0) {
case function() {
for (a in 0);
}:
}
for (; a;);
var a;
}());
}
expect_stdout: "undefined"
}

View File

@@ -1483,8 +1483,7 @@ issue_2663_2: {
}
expect: {
(function() {
var i;
for (i in { a: 1, b: 2, c: 3 })
for (var i in { a: 1, b: 2, c: 3 })
j = i, console.log(j);
var j;
})();

View File

@@ -277,8 +277,8 @@ join_object_assignments_forin: {
}
expect: {
console.log(function() {
var o = { a: "PASS" };
for (var a in o)
var o = { a: "PASS" }, a;
for (a in o)
return o[a];
}());
}

View File

@@ -1009,3 +1009,82 @@ issue_4082: {
}
expect_stdout: "PASS"
}
issue_4084: {
options = {
keep_fargs: "strict",
loops: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function() {
function f(a) {
var b = a++;
for (a in "foo");
}
f();
return typeof a;
}());
}
expect: {
console.log(function() {
(function() {
0;
})();
return typeof a;
}());
}
expect_stdout: "undefined"
}
issue_4091_1: {
options = {
loops: true,
toplevel: true,
unused: true,
}
input: {
try {
throw "FAIL";
} catch (e) {
for (var e in 42);
}
console.log(e && e);
}
expect: {
try {
throw "FAIL";
} catch (e) {
var e;
}
console.log(e && e);
}
expect_stdout: "undefined"
}
issue_4091_2: {
options = {
loops: true,
toplevel: true,
unused: true,
}
input: {
try {
throw "FAIL";
} catch (e) {
for (e in 42);
var e;
}
console.log(e && e);
}
expect: {
try {
throw "FAIL";
} catch (e) {
var e;
}
console.log(e && e);
}
expect_stdout: "undefined"
}

View File

@@ -637,6 +637,22 @@ evaluate_7_unsafe_math: {
]
}
evaluate_8_unsafe_math: {
options = {
evaluate: true,
unsafe_math: true,
}
input: {
var a = [ "42" ];
console.log(a * (1 / 7));
}
expect: {
var a = [ "42" ];
console.log(+a / 7);
}
expect_stdout: "6"
}
NaN_redefined: {
options = {
evaluate: true,

View File

@@ -1,9 +1,3 @@
var o = this;
for (var k in o) L17060: {
a++;
UNUSED: {
console.log(0 - .1 - .1 - .1);
}
var a;
console.log(k);

View File

@@ -1,15 +1,12 @@
// (beautified)
var o = this;
for (var k in o) {}
var a;
console.log(k);
// output: a
console.log(0 - 1 - .1 - .1);
// output: -1.2000000000000002
//
// minify: k
// minify: -1.2
//
// options: {
// "compress": {
// "unsafe_math": true
// },
// "mangle": false
// }

View File

@@ -24,6 +24,9 @@ describe("test/reduce.js", function() {
});
it("Should eliminate unreferenced labels", function() {
var result = reduce_test(read("test/input/reduce/label.js"), {
compress: {
unsafe_math: true,
},
mangle: false,
}, {
verbose: false,

View File

@@ -112,19 +112,18 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// no structural AST changes before this point.
if (node.start._permute >= REPLACEMENTS.length) return;
if (parent instanceof U.AST_Assign
&& parent.left === node
|| parent instanceof U.AST_Unary
&& parent.expression === node
&& ["++", "--", "delete"].indexOf(parent.operator) >= 0) {
// ignore lvalues
// ignore lvalues
if (parent instanceof U.AST_Assign && parent.left === node) return;
if (parent instanceof U.AST_Unary && parent.expression === node) switch (parent.operator) {
case "++":
case "--":
case "delete":
return;
}
if ((parent instanceof U.AST_For || parent instanceof U.AST_ForIn)
&& parent.init === node && node instanceof U.AST_Var) {
// preserve for (var ...)
return node;
}
// preserve for (var xxx; ...)
if (parent instanceof U.AST_For && parent.init === node && node instanceof U.AST_Var) return node;
// preserve for (xxx in ...)
if (parent instanceof U.AST_ForIn && parent.init === node) return node;
// node specific permutations with no parent logic
@@ -452,6 +451,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
node.start = JSON.parse(JSON.stringify(node.start));
node.start._permute = 0;
}));
var before_iterations = testcase;
for (var c = 0; c < max_iterations; ++c) {
if (verbose && pass == 1 && c % 25 == 0) {
log("// reduce test pass " + pass + ", iteration " + c + ": " + testcase.length + " bytes");
@@ -494,7 +494,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
}
}
if (c == 0) break;
if (before_iterations === testcase) break;
if (verbose) {
log("// reduce test pass " + pass + ": " + testcase.length + " bytes");
}
@@ -557,7 +557,7 @@ function try_beautify(testcase, minify_options, expected, result_cache, timeout)
code: testcase,
};
} else {
var actual = run_code(result.code, toplevel, result_cache, timeout);
var actual = run_code(result.code, toplevel, result_cache, timeout).result;
if (!sandbox.same_stdout(expected, actual)) return {
code: testcase,
};
@@ -650,7 +650,15 @@ function wrap_with_console_log(node) {
function run_code(code, toplevel, result_cache, timeout) {
var key = crypto.createHash("sha1").update(code).digest("base64");
return result_cache[key] || (result_cache[key] = sandbox.run_code(code, toplevel, timeout));
var value = result_cache[key];
if (!value) {
var start = Date.now();
result_cache[key] = value = {
result: sandbox.run_code(code, toplevel, timeout),
elapsed: Date.now() - start,
};
}
return value;
}
function compare_run_code(code, minify_options, result_cache, max_timeout) {
@@ -658,21 +666,19 @@ function compare_run_code(code, minify_options, result_cache, max_timeout) {
if (minified.error) return minified;
var toplevel = sandbox.has_toplevel(minify_options);
var elapsed = Date.now();
var unminified_result = run_code(code, toplevel, result_cache, max_timeout);
elapsed = Date.now() - elapsed;
var timeout = Math.min(100 * elapsed, max_timeout);
var minified_result = run_code(minified.code, toplevel, result_cache, timeout);
var unminified = run_code(code, toplevel, result_cache, max_timeout);
var timeout = Math.min(100 * unminified.elapsed, max_timeout);
var minified_result = run_code(minified.code, toplevel, result_cache, timeout).result;
if (sandbox.same_stdout(unminified_result, minified_result)) {
return is_timed_out(unminified_result) && is_timed_out(minified_result) && {
if (sandbox.same_stdout(unminified.result, minified_result)) {
return is_timed_out(unminified.result) && is_timed_out(minified_result) && {
timed_out: true,
};
}
return {
unminified_result: unminified_result,
unminified_result: unminified.result,
minified_result: minified_result,
elapsed: elapsed,
elapsed: unminified.elapsed,
};
}

View File

@@ -27,7 +27,7 @@ process.on("beforeExit", function() {
if (queued > 3) {
process.stdout.write("0");
} else if (now - earliest > 0 && total > 1) {
process.stdout.write(Math.min(20 * (now - earliest) / (total - 1), 6300000).toFixed(0));
process.stdout.write(Math.min(20 * (now - earliest) / (total - 1), 18000000).toFixed(0));
} else {
process.stdout.write("3600000");
}