allow symbol replacement on multiple occurrences (#2472)

- all-or-nothing replacement
- avoid unmangleable names

fixes #2436
This commit is contained in:
Alex Lam S.L
2017-11-12 22:31:47 +08:00
committed by GitHub
parent 346fa12e0e
commit c6cfa04d10
3 changed files with 481 additions and 12 deletions

View File

@@ -796,8 +796,8 @@ merge(Compressor.prototype, {
});
function drop_decl(def) {
def._eliminiated = (def._eliminiated || 0) + 1;
if (def.orig.length == def._eliminiated) {
def.eliminated++;
if (def.orig.length == def.eliminated) {
def.scope.functions.del(def.name);
def.scope.variables.del(def.name);
}
@@ -854,10 +854,14 @@ merge(Compressor.prototype, {
// Locate symbols which may execute code outside of scanning range
var lvalues = get_lvalues(candidate);
if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
var one_off = lhs instanceof AST_Symbol && lhs.definition().references.length == 1;
var replace_all = candidate.multiple;
if (!replace_all && lhs instanceof AST_SymbolRef) {
var def = lhs.definition();
replace_all = def.references.length - def.replaced == 1;
}
var side_effects = value_has_side_effects(candidate);
var hit = candidate.name instanceof AST_SymbolFunarg;
var abort = false, replaced = false, can_replace = !args || !hit;
var abort = false, replaced = 0, can_replace = !args || !hit;
var tt = new TreeTransformer(function(node, descend) {
if (abort) return node;
// Skip nodes before `candidate` as quickly as possible
@@ -886,7 +890,8 @@ merge(Compressor.prototype, {
&& !(node instanceof AST_SymbolDeclaration)
&& !is_lhs(node, parent)
&& lhs.equivalent_to(node)) {
CHANGED = replaced = abort = true;
CHANGED = abort = true;
replaced++;
compressor.info("Collapsing {name} [{file}:{line},{col}]", {
name: node.print_to_string(),
file: node.start.file,
@@ -897,8 +902,13 @@ merge(Compressor.prototype, {
return make_node(AST_UnaryPrefix, candidate, candidate);
}
if (candidate instanceof AST_VarDef) {
if (candidate.multiple) {
abort = false;
return node;
}
var def = candidate.name.definition();
if (def.references.length == 1 && !compressor.exposed(def)) {
if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
def.replaced++;
return maintain_this_binding(parent, node, candidate.value);
}
return make_node(AST_Assign, candidate, {
@@ -922,7 +932,7 @@ merge(Compressor.prototype, {
|| side_effects && !references_in_scope(node.definition()))
|| (sym = lhs_or_def(node))
&& (sym instanceof AST_PropAccess || sym.name in lvalues)
|| (side_effects || !one_off)
|| (side_effects || !replace_all)
&& (parent instanceof AST_Binary && lazy_op(parent.operator)
|| parent instanceof AST_Case
|| parent instanceof AST_Conditional
@@ -935,7 +945,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
});
if (!can_replace) {
for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; j < args.length; j++) {
for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) {
args[j].transform(tt);
}
can_replace = true;
@@ -943,6 +953,33 @@ merge(Compressor.prototype, {
for (var i = stat_index; !abort && i < statements.length; i++) {
statements[i].transform(tt);
}
if (candidate.multiple) {
var def = candidate.name.definition();
if (abort && def.references.length > replaced) replaced = false;
else {
abort = false;
hit = candidate.name instanceof AST_SymbolFunarg;
var value_def = candidate.value.definition();
for (var i = stat_index; !abort && i < statements.length; i++) {
statements[i].transform(new TreeTransformer(function(node) {
if (abort) return node;
if (!hit) {
if (node === candidate) {
hit = true;
return node;
}
return;
}
if (node instanceof AST_SymbolRef && node.name == def.name) {
def.replaced++;
value_def.replaced--;
if (!--replaced) abort = true;
return candidate.value;
}
}));
}
}
}
if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1);
}
}
@@ -956,7 +993,7 @@ merge(Compressor.prototype, {
&& (iife = compressor.parent()) instanceof AST_Call
&& iife.expression === fn) {
var fn_strict = compressor.has_directive("use strict");
if (fn_strict && fn.body.indexOf(fn_strict) < 0) fn_strict = false;
if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
var len = fn.argnames.length;
args = iife.args.slice(len);
var names = Object.create(null);
@@ -1012,12 +1049,22 @@ merge(Compressor.prototype, {
}
}
function mangleable_var(expr) {
var value = expr.value;
if (!(value instanceof AST_SymbolRef)) return false;
if (value.name == "arguments") return false;
if (value.definition().undeclared) return false;
expr.multiple = true;
return true;
}
function get_lhs(expr) {
if (expr instanceof AST_VarDef) {
var def = expr.name.definition();
if (def.orig.length - (def._eliminiated || 0) > 1
&& !(expr.name instanceof AST_SymbolFunarg)
|| def.references.length == 1 && !compressor.exposed(def)) {
var declared = def.orig.length - def.eliminated;
var referenced = def.references.length - def.replaced;
if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)
|| (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) {
return make_node(AST_SymbolRef, expr.name, expr.name);
}
} else {

View File

@@ -46,8 +46,10 @@
function SymbolDef(scope, index, orig) {
this.name = orig.name;
this.orig = [ orig ];
this.eliminated = 0;
this.scope = scope;
this.references = [];
this.replaced = 0;
this.global = false;
this.mangled_name = null;
this.undeclared = false;

View File

@@ -3098,3 +3098,423 @@ issue_2437: {
}();
}
}
issue_2436_1: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
a: 1,
b: 2,
};
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
}(o));
}
expect: {
var o = {
a: 1,
b: 2,
};
console.log({
x: o.a,
y: o.b,
});
}
expect_stdout: true
}
issue_2436_2: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
a: 1,
b: 2,
};
console.log(function(c) {
o.a = 3;
return {
x: c.a,
y: c.b,
};
}(o));
}
expect: {
var o = {
a: 1,
b: 2,
};
console.log(function(c) {
o.a = 3;
return {
x: c.a,
y: c.b,
};
}(o));
}
expect_stdout: true
}
issue_2436_3: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
a: 1,
b: 2,
};
console.log(function(c) {
o = {
a: 3,
b: 4,
};
return {
x: c.a,
y: c.b,
};
}(o));
}
expect: {
var o = {
a: 1,
b: 2,
};
console.log(function(c) {
o = {
a: 3,
b: 4,
};
return {
x: c.a,
y: c.b,
};
}(o));
}
expect_stdout: true
}
issue_2436_4: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
a: 1,
b: 2,
};
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
var o;
}(o));
}
expect: {
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
}({
a: 1,
b: 2,
}));
}
expect_stdout: true
}
issue_2436_5: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
a: 1,
b: 2,
};
console.log(function(o) {
return {
x: o.a,
y: o.b,
};
}(o));
}
expect: {
console.log(function(o) {
return {
x: o.a,
y: o.b,
};
}({
a: 1,
b: 2,
}));
}
expect_stdout: true
}
issue_2436_6: {
options = {
collapse_vars: true,
evaluate: true,
inline: true,
passes: 2,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
unsafe: true,
}
input: {
var o = {
a: 1,
b: 2,
};
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
}(o));
}
expect: {
console.log({
x: 1,
y: 2,
});
}
expect_stdout: true
}
issue_2436_7: {
options = {
collapse_vars: true,
hoist_props: true,
inline: true,
passes: 3,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
a: 1,
b: 2,
};
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
}(o));
}
expect: {
console.log({
x: 1,
y: 2,
});
}
expect_stdout: true
}
issue_2436_8: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
}(o));
}
expect: {
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
}(o));
}
expect_stdout: true
}
issue_2436_9: {
options = {
collapse_vars: true,
inline: true,
pure_getters: "strict",
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = console;
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
}(o));
}
expect: {
var o = console;
console.log(function(c) {
return {
x: c.a,
y: c.b,
};
}(o));
}
expect_stdout: true
}
issue_2436_10: {
options = {
collapse_vars: true,
inline: true,
pure_getters: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var o = {
a: 1,
b: 2,
};
function f(n) {
o = { b: 3 };
return n;
}
console.log(function(c) {
return [
c.a,
f(c.b),
c.b,
];
}(o).join(" "));
}
expect: {
var o = {
a: 1,
b: 2,
};
function f(n) {
o = { b: 3 };
return n;
}
console.log(function(c) {
return [
c.a,
f(c.b),
c.b,
];
}(o).join(" "));
}
expect_stdout: "1 2 2"
}
issue_2436_11: {
options = {
collapse_vars: true,
join_vars: true,
reduce_vars: true,
unused: true,
}
input: {
function matrix() {}
function isCollection() {}
function _randomDataForMatrix() {}
function _randomInt() {}
function f(arg1, arg2) {
if (isCollection(arg1)) {
var size = arg1;
var max = arg2;
var min = 0;
var res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt);
return size && true === size.isMatrix ? matrix(res) : res;
} else {
var min = arg1;
var max = arg2;
return _randomInt(min, max);
}
}
}
expect: {
function matrix() {}
function isCollection() {}
function _randomDataForMatrix() {}
function _randomInt() {}
function f(arg1, arg2) {
if (isCollection(arg1)) {
var size = arg1, max = arg2, min = 0, res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt);
return size && true === size.isMatrix ? matrix(res) : res;
} else {
return _randomInt(min = arg1, max = arg2);
}
}
}
}
issue_2436_12: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function isUndefined() {}
function f() {
var viewValue = this.$$lastCommittedViewValue;
var modelValue = viewValue;
return isUndefined(modelValue) ? modelValue : null;
}
}
expect: {
function isUndefined() {}
function f() {
var modelValue = this.$$lastCommittedViewValue;
return isUndefined(modelValue) ? modelValue : null;
}
}
}