diff --git a/lib/compress.js b/lib/compress.js index fe8be762..9f17efb5 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -695,26 +695,24 @@ merge(Compressor.prototype, { var CHANGED, max_iter = 10; do { CHANGED = false; - statements = eliminate_spurious_blocks(statements); + eliminate_spurious_blocks(statements); if (compressor.option("dead_code")) { - statements = eliminate_dead_code(statements, compressor); + eliminate_dead_code(statements, compressor); } if (compressor.option("if_return")) { - statements = handle_if_return(statements, compressor); + handle_if_return(statements, compressor); } if (compressor.sequences_limit > 0) { - statements = sequencesize(statements, compressor); + sequencesize(statements, compressor); } if (compressor.option("join_vars")) { - statements = join_consecutive_vars(statements, compressor); + join_consecutive_vars(statements, compressor); } if (compressor.option("collapse_vars")) { - statements = collapse(statements, compressor); + collapse(statements, compressor); } } while (CHANGED && max_iter-- > 0); - return statements; - // Search from right to left for assignment-like expressions: // - `var a = x;` // - `a = x;` @@ -752,6 +750,7 @@ merge(Compressor.prototype, { var parent = tt.parent(); if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left) || node instanceof AST_Await + || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression) || node instanceof AST_Debugger || node instanceof AST_Destructuring || node instanceof AST_IterationStatement && !(node instanceof AST_For) @@ -818,7 +817,6 @@ merge(Compressor.prototype, { if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1); } } - return statements; function extract_candidates(expr) { if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor) @@ -921,59 +919,60 @@ merge(Compressor.prototype, { function eliminate_spurious_blocks(statements) { var seen_dirs = []; - return statements.reduce(function(a, stat){ + for (var i = 0; i < statements.length;) { + var stat = statements[i]; if (stat instanceof AST_BlockStatement && all(stat.body, can_be_evicted_from_block)) { CHANGED = true; - a.push.apply(a, eliminate_spurious_blocks(stat.body)); + eliminate_spurious_blocks(stat.body); + [].splice.apply(statements, [i, 1].concat(stat.body)); + i += stat.body.length; } else if (stat instanceof AST_EmptyStatement) { CHANGED = true; + statements.splice(i, 1); } else if (stat instanceof AST_Directive) { if (seen_dirs.indexOf(stat.value) < 0) { - a.push(stat); + i++; seen_dirs.push(stat.value); } else { CHANGED = true; + statements.splice(i, 1); } - } else { - a.push(stat); - } - return a; - }, []); - }; + } else i++; + } + } function handle_if_return(statements, compressor) { var self = compressor.self(); var multiple_if_returns = has_multiple_if_returns(statements); var in_lambda = self instanceof AST_Lambda; - var ret = []; // Optimized statements, build from tail to front - loop: for (var i = statements.length; --i >= 0;) { + for (var i = statements.length; --i >= 0;) { var stat = statements[i]; - switch (true) { - case (in_lambda && stat instanceof AST_Return && !stat.value && ret.length == 0): + var next = statements[i + 1]; + + if (in_lambda && stat instanceof AST_Return && !stat.value && !next) { CHANGED = true; - // note, ret.length is probably always zero - // because we drop unreachable code before this - // step. nevertheless, it's good to check. - continue loop; - case stat instanceof AST_If: + statements.length--; + continue; + } + + if (stat instanceof AST_If) { var ab = aborts(stat.body); if (can_merge_flow(ab)) { if (ab.label) { remove(ab.label.thedef.references, ab); } CHANGED = true; - var funs = extract_functions_from_statement_array(ret); - var body = as_statement_array_with_return(stat.body, ab); stat = stat.clone(); stat.condition = stat.condition.negate(compressor); + var body = as_statement_array_with_return(stat.body, ab); stat.body = make_node(AST_BlockStatement, stat, { - body: as_statement_array(stat.alternative).concat(ret) + body: as_statement_array(stat.alternative).concat(extract_functions()) }); stat.alternative = make_node(AST_BlockStatement, stat, { body: body }); - ret = [ stat.transform(compressor) ].concat(funs); - continue loop; + statements[i] = stat.transform(compressor); + continue; } var ab = aborts(stat.alternative); @@ -982,81 +981,71 @@ merge(Compressor.prototype, { remove(ab.label.thedef.references, ab); } CHANGED = true; - var funs = extract_functions_from_statement_array(ret); stat = stat.clone(); stat.body = make_node(AST_BlockStatement, stat.body, { - body: as_statement_array(stat.body).concat(ret) + body: as_statement_array(stat.body).concat(extract_functions()) }); var body = as_statement_array_with_return(stat.alternative, ab); stat.alternative = make_node(AST_BlockStatement, stat.alternative, { body: body }); - ret = [ stat.transform(compressor) ].concat(funs); - continue loop; + statements[i] = stat.transform(compressor); + continue; } + } - if (stat.body instanceof AST_Return) { - var value = stat.body.value; - //--- - // pretty silly case, but: - // if (foo()) return; return; ==> foo(); return; - if ((in_lambda && ret.length == 0 || ret[0] instanceof AST_Return && !ret[0].value) - && !value && !stat.alternative) { - CHANGED = true; - var cond = make_node(AST_SimpleStatement, stat.condition, { - body: stat.condition - }); - ret.unshift(cond); - continue loop; - } - //--- - // if (foo()) return x; return y; ==> return foo() ? x : y; - if (ret[0] instanceof AST_Return && value && ret[0].value && !stat.alternative) { - CHANGED = true; - stat = stat.clone(); - stat.alternative = ret[0]; - ret[0] = stat.transform(compressor); - continue loop; - } - //--- - // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined; - if (multiple_if_returns && (ret.length == 0 || ret[0] instanceof AST_Return) - && value && !stat.alternative && in_lambda) { - CHANGED = true; - stat = stat.clone(); - stat.alternative = ret[0] || make_node(AST_Return, stat, { - value: null - }); - ret[0] = stat.transform(compressor); - continue loop; - } - //--- - // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e; - // - // if sequences is not enabled, this can lead to an endless loop (issue #866). - // however, with sequences on this helps producing slightly better output for - // the example code. - if (compressor.option("sequences") - && i > 0 && statements[i - 1] instanceof AST_If && statements[i - 1].body instanceof AST_Return - && ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement - && !stat.alternative) { - CHANGED = true; - ret.push(make_node(AST_Return, ret[0], { - value: null - }).transform(compressor)); - ret.unshift(stat); - continue loop; - } + if (stat instanceof AST_If && stat.body instanceof AST_Return) { + var value = stat.body.value; + //--- + // pretty silly case, but: + // if (foo()) return; return; ==> foo(); return; + if (!value && !stat.alternative + && (in_lambda && !next || next instanceof AST_Return && !next.value)) { + CHANGED = true; + statements[i] = make_node(AST_SimpleStatement, stat.condition, { + body: stat.condition + }); + continue; + } + //--- + // if (foo()) return x; return y; ==> return foo() ? x : y; + if (value && !stat.alternative && next instanceof AST_Return && next.value) { + CHANGED = true; + stat = stat.clone(); + stat.alternative = next; + statements.splice(i, 2, stat.transform(compressor)); + continue; + } + //--- + // if (foo()) return x; [ return ; ] ==> return foo() ? x : undefined; + if (multiple_if_returns && in_lambda && value && !stat.alternative + && (!next || next instanceof AST_Return)) { + CHANGED = true; + stat = stat.clone(); + stat.alternative = next || make_node(AST_Return, stat, { + value: null + }); + statements.splice(i, next ? 2 : 1, stat.transform(compressor)); + continue; + } + //--- + // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e; + // + // if sequences is not enabled, this can lead to an endless loop (issue #866). + // however, with sequences on this helps producing slightly better output for + // the example code. + var prev = statements[i - 1]; + if (compressor.option("sequences") && in_lambda && !stat.alternative + && prev instanceof AST_If && prev.body instanceof AST_Return + && i + 2 == statements.length && next instanceof AST_SimpleStatement) { + CHANGED = true; + statements.push(make_node(AST_Return, next, { + value: null + }).transform(compressor)); + continue; } - - ret.unshift(stat); - break; - default: - ret.unshift(stat); - break; } } - return ret; function has_multiple_if_returns(statements) { var n = 0; @@ -1074,15 +1063,29 @@ merge(Compressor.prototype, { } function can_merge_flow(ab) { - if (!ab || !all(ret, function(stat) { - return !(stat instanceof AST_Const || stat instanceof AST_Let); - })) return false; + if (!ab) return false; + for (var j = i + 1, len = statements.length; j < len; j++) { + var stat = statements[j]; + if (stat instanceof AST_Const || stat instanceof AST_Let) return false; + } var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab) : null; return ab instanceof AST_Return && in_lambda && is_return_void(ab.value) || ab instanceof AST_Continue && self === loop_body(lct) || ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct; } + function extract_functions() { + var tail = statements.slice(i + 1); + statements.length = i + 1; + return tail.filter(function(stat) { + if (stat instanceof AST_Defun) { + statements.push(stat); + return false; + } + return true; + }); + } + function as_statement_array_with_return(node, ab) { var body = as_statement_array(node).slice(0, -1); if (ab.value) { @@ -1092,49 +1095,52 @@ merge(Compressor.prototype, { } return body; } - }; + } function eliminate_dead_code(statements, compressor) { - var has_quit = false; - var orig = statements.length; + var has_quit; var self = compressor.self(); - statements = statements.reduce(function(a, stat){ - if (has_quit) { - extract_declarations_from_unreachable_code(compressor, stat, a); - } else { - if (stat instanceof AST_LoopControl) { - var lct = compressor.loopcontrol_target(stat); - if ((stat instanceof AST_Break - && !(lct instanceof AST_IterationStatement) - && loop_body(lct) === self) || (stat instanceof AST_Continue - && loop_body(lct) === self)) { - if (stat.label) { - remove(stat.label.thedef.references, stat); - } - } else { - a.push(stat); + for (var i = 0, n = 0, len = statements.length; i < len; i++) { + var stat = statements[i]; + if (stat instanceof AST_LoopControl) { + var lct = compressor.loopcontrol_target(stat); + if (stat instanceof AST_Break + && !(lct instanceof AST_IterationStatement) + && loop_body(lct) === self + || stat instanceof AST_Continue + && loop_body(lct) === self) { + if (stat.label) { + remove(stat.label.thedef.references, stat); } } else { - a.push(stat); + statements[n++] = stat; } - if (aborts(stat)) has_quit = true; + } else { + statements[n++] = stat; } - return a; - }, []); - CHANGED = statements.length != orig; - return statements; - }; + if (aborts(stat)) { + has_quit = statements.slice(i + 1); + break; + } + } + statements.length = n; + CHANGED = n != len; + if (has_quit) has_quit.forEach(function(stat) { + extract_declarations_from_unreachable_code(compressor, stat, statements); + }); + } function sequencesize(statements, compressor) { - if (statements.length < 2) return statements; - var seq = [], ret = []; + if (statements.length < 2) return; + var seq = [], n = 0; function push_seq() { if (!seq.length) return; var body = make_sequence(seq[0], seq); - ret.push(make_node(AST_SimpleStatement, body, { body: body })); + statements[n++] = make_node(AST_SimpleStatement, body, { body: body }); seq = []; - }; - statements.forEach(function(stat){ + } + for (var i = 0, len = statements.length; i < len; i++) { + var stat = statements[i]; if (stat instanceof AST_SimpleStatement) { if (seq.length >= compressor.sequences_limit) push_seq(); var body = stat.body; @@ -1142,18 +1148,18 @@ merge(Compressor.prototype, { if (body) merge_sequence(seq, body); } else { push_seq(); - ret.push(stat); + statements[n++] = stat; } - }); + } push_seq(); - ret = sequencesize_2(ret, compressor); - CHANGED = ret.length != statements.length; - return ret; - }; + statements.length = n; + sequencesize_2(statements, compressor); + CHANGED = statements.length != len; + } function sequencesize_2(statements, compressor) { function cons_seq(right) { - ret.pop(); + n--; var left = prev.body; if (!(left instanceof AST_Sequence)) { left = make_node(AST_Sequence, left, { @@ -1163,8 +1169,9 @@ merge(Compressor.prototype, { merge_sequence(left.expressions, right); return left.transform(compressor); }; - var ret = [], prev = null; - statements.forEach(function(stat){ + var n = 0, prev; + for (var i = 0, len = statements.length; i < len; i++) { + var stat = statements[i]; if (prev) { if (stat instanceof AST_For) { try { @@ -1177,7 +1184,7 @@ merge(Compressor.prototype, { } else if (!stat.init) { stat.init = prev.body.drop_side_effect_free(compressor); - ret.pop(); + n--; } } catch(ex) { if (ex !== cons_seq) throw ex; @@ -1199,15 +1206,16 @@ merge(Compressor.prototype, { stat.expression = cons_seq(stat.expression); } } - ret.push(stat); + statements[n++] = stat; prev = stat instanceof AST_SimpleStatement ? stat : null; - }); - return ret; - }; + } + statements.length = n; + } function join_consecutive_vars(statements, compressor) { - var prev = null; - return statements.reduce(function(a, stat){ + for (var i = 0, j = -1, len = statements.length; i < len; i++) { + var stat = statements[i]; + var prev = statements[j]; if (stat instanceof AST_Definitions && prev && prev.TYPE == stat.TYPE) { prev.definitions = prev.definitions.concat(stat.definitions); CHANGED = true; @@ -1216,35 +1224,19 @@ merge(Compressor.prototype, { && prev instanceof AST_Var && (!stat.init || stat.init.TYPE == prev.TYPE)) { CHANGED = true; - a.pop(); if (stat.init) { stat.init.definitions = prev.definitions.concat(stat.init.definitions); } else { stat.init = prev; } - a.push(stat); - prev = stat; + statements[j] = stat; } else { - prev = stat; - a.push(stat); + statements[++j] = stat; } - return a; - }, []); - }; - - }; - - function extract_functions_from_statement_array(statements) { - var funs = []; - for (var i = statements.length - 1; i >= 0; --i) { - var stat = statements[i]; - if (stat instanceof AST_Defun) { - statements.splice(i, 1); - funs.unshift(stat); } - } - return funs; + statements.length = j + 1; + }; } function extract_declarations_from_unreachable_code(compressor, stat, target) { @@ -2050,13 +2042,12 @@ merge(Compressor.prototype, { }); OPT(AST_Block, function(self, compressor){ - if (self.body instanceof AST_Node) { return self; } - self.body = tighten_body(self.body, compressor); + if (!(self.body instanceof AST_Node)) tighten_body(self.body, compressor); return self; }); OPT(AST_BlockStatement, function(self, compressor){ - self.body = tighten_body(self.body, compressor); + tighten_body(self.body, compressor); switch (self.body.length) { case 1: if (!compressor.has_directive("use strict") && compressor.parent() instanceof AST_If @@ -3057,7 +3048,7 @@ merge(Compressor.prototype, { }); OPT(AST_Try, function(self, compressor){ - self.body = tighten_body(self.body, compressor); + tighten_body(self.body, compressor); if (self.bcatch && self.bfinally && all(self.bfinally.body, is_empty)) self.bfinally = null; if (all(self.body, is_empty)) { var body = []; @@ -3114,18 +3105,18 @@ merge(Compressor.prototype, { OPT(AST_Call, function(self, compressor){ var exp = self.expression; - if (compressor.option("reduce_vars") && exp instanceof AST_SymbolRef) { - var fixed = exp.fixed_value(); - if (fixed instanceof AST_Function) exp = fixed; - } + var fn = exp; if (compressor.option("unused") - && exp instanceof AST_Function - && !exp.uses_arguments - && !exp.uses_eval) { + && (fn instanceof AST_Function + || compressor.option("reduce_vars") + && fn instanceof AST_SymbolRef + && (fn = fn.fixed_value()) instanceof AST_Function) + && !fn.uses_arguments + && !fn.uses_eval) { var pos = 0, last = 0; for (var i = 0, len = self.args.length; i < len; i++) { - var trim = i >= exp.argnames.length; - if (trim || exp.argnames[i].__unused) { + var trim = i >= fn.argnames.length; + if (trim || fn.argnames[i].__unused) { var node = self.args[i].drop_side_effect_free(compressor); if (node) { self.args[pos++] = node; @@ -3330,15 +3321,15 @@ merge(Compressor.prototype, { } } } - if (exp instanceof AST_Function && !self.expression.is_generator && !self.expression.async) { - var stat = exp.body[0]; - if (compressor.option("inline") && stat instanceof AST_Return) { - var value = stat && stat.value; - if (!value || value.is_constant_expression()) { - var args = self.args.concat(value || make_node(AST_Undefined, self)); - return make_sequence(self, args).transform(compressor); - } + var stat = fn instanceof AST_Function && fn.body[0]; + if (compressor.option("inline") && stat instanceof AST_Return) { + var value = stat.value; + if (!value || value.is_constant_expression()) { + var args = self.args.concat(value || make_node(AST_Undefined, self)); + return make_sequence(self, args).transform(compressor); } + } + if (exp instanceof AST_Function && !exp.is_generator && !exp.async) { if (compressor.option("inline") && !exp.name && exp.body.length == 1 @@ -3361,16 +3352,19 @@ merge(Compressor.prototype, { if (exp.argnames.length > 0) { fn.body.push(make_node(AST_Var, self, { definitions: exp.argnames.map(function(sym, i) { + var arg = self.args[i]; return make_node(AST_VarDef, sym, { name: sym, - value: self.args[i] || make_node(AST_Undefined, self) + value: arg ? arg.clone(true) : make_node(AST_Undefined, self) }); }) })); } if (self.args.length > exp.argnames.length) { fn.body.push(make_node(AST_SimpleStatement, self, { - body: make_sequence(self, self.args.slice(exp.argnames.length)) + body: make_sequence(self, self.args.slice(exp.argnames.length).map(function(node) { + return node.clone(true); + })) })); } fn.body.push(make_node(AST_Return, self, { @@ -3381,18 +3375,21 @@ merge(Compressor.prototype, { if (body.length == 1 && body[0] instanceof AST_Return) { value = body[0].value; if (!value) return make_node(AST_Undefined, self); - value.walk(new TreeWalker(function(node) { + var tw = new TreeWalker(function(node) { if (value === self) return true; - if (node instanceof AST_SymbolRef && matches(node.scope.find_variable(node)) - || node instanceof AST_This && matches(node)) { + if (node instanceof AST_SymbolRef) { + var ref = node.scope.find_variable(node); + if (ref && ref.scope.parent_scope === fn.parent_scope) { + value = self; + return true; + } + } + if (node instanceof AST_This && !tw.find_parent(AST_Scope)) { value = self; return true; } - - function matches(ref) { - return ref && ref.scope.parent_scope === fn.parent_scope; - } - })); + }); + value.walk(tw); if (value !== self) value = best_of(compressor, value, self); } else { value = self; @@ -3531,6 +3528,7 @@ merge(Compressor.prototype, { field = "left"; } } else if (cdr instanceof AST_Call + && !(left instanceof AST_PropAccess && cdr.expression.equivalent_to(left)) || cdr instanceof AST_PropAccess || cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) { field = "expression"; diff --git a/package.json b/package.json index a6a54d10..8449680e 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "https://github.com/mishoo/UglifyJS2/tree/harmony", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.0.17", + "version": "3.0.18", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 7d321fcf..34b74908 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -1294,3 +1294,47 @@ issue_2063: { var a; } } + +issue_2105: { + options = { + collapse_vars: true, + inline: true, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + !function(factory) { + factory(); + }( function() { + return function(fn) { + fn()().prop(); + }( function() { + function bar() { + var quux = function() { + console.log("PASS"); + }, foo = function() { + console.log; + quux(); + }; + return { prop: foo }; + } + return bar; + } ); + }); + } + expect: { + !void function() { + var quux = function() { + console.log("PASS"); + }; + return { + prop: function() { + console.log; + quux(); + } + }; + }().prop(); + } + expect_stdout: "PASS" +} diff --git a/test/compress/functions.js b/test/compress/functions.js index ae8dfb07..c6de3276 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -414,3 +414,99 @@ inner_ref: { } expect_stdout: "1 undefined" } + +issue_2107: { + options = { + cascade: true, + collapse_vars: true, + inline: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + var c = 0; + !function() { + c++; + }(c++ + new function() { + this.a = 0; + var a = (c = c + 1) + (c = 1 + c); + return c++ + a; + }()); + console.log(c); + } + expect: { + var c = 0; + c++, new function() { + this.a = 0, c = 1 + (c += 1), c++; + }(), c++, console.log(c); + } + expect_stdout: "5" +} + +issue_2114_1: { + options = { + collapse_vars: true, + if_return: true, + inline: true, + keep_fargs: false, + side_effects: true, + unused: true, + } + input: { + var c = 0; + !function(a) { + a = 0; + }([ { + 0: c = c + 1, + length: c = 1 + c + }, typeof void function a() { + var b = function f1(a) { + }(b && (b.b += (c = c + 1, 0))); + }() ]); + console.log(c); + } + expect: { + var c = 0; + !function() { + 0; + }((c += 1, c = 1 + c, function() { + var b = void (b && (b.b += (c += 1, 0))); + }())); + console.log(c); + } + expect_stdout: "2" +} + +issue_2114_2: { + options = { + collapse_vars: true, + if_return: true, + inline: true, + keep_fargs: false, + passes: 2, + side_effects: true, + unused: true, + } + input: { + var c = 0; + !function(a) { + a = 0; + }([ { + 0: c = c + 1, + length: c = 1 + c + }, typeof void function a() { + var b = function f1(a) { + }(b && (b.b += (c = c + 1, 0))); + }() ]); + console.log(c); + } + expect: { + var c = 0; + c = 1 + (c += 1), function() { + var b = void (b && (b.b += (c += 1, 0))); + }(); + console.log(c); + } + expect_stdout: "2" +} diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index 0ca6a804..81a96b73 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -178,3 +178,66 @@ impure_getter_2: { } expect: {} } + +issue_2110_1: { + options = { + cascade: true, + pure_getters: "strict", + sequences: true, + side_effects: true, + reduce_vars: true, + unused: true, + } + input: { + function f() { + function f() {} + function g() { + return this; + } + f.g = g; + return f.g(); + } + console.log(typeof f()); + } + expect: { + function f() { + function f() {} + return f.g = function() { + return this; + }, f.g(); + } + console.log(typeof f()); + } + expect_stdout: "function" +} + +issue_2110_2: { + options = { + collapse_vars: true, + pure_getters: "strict", + reduce_vars: true, + unused: true, + } + input: { + function f() { + function f() {} + function g() { + return this; + } + f.g = g; + return f.g(); + } + console.log(typeof f()); + } + expect: { + function f() { + function f() {} + f.g = function() { + return this; + }; + return f.g(); + } + console.log(typeof f()); + } + expect_stdout: "function" +}