fix corner case in unused

This commit is contained in:
alexlamsl
2018-07-01 14:34:42 +08:00
parent 6aa750010f
commit 5ffc17d4aa
56 changed files with 1588 additions and 973 deletions

View File

@@ -328,7 +328,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
var body = this.body;
var wrapped_tl = "(function(exports){'$ORIG';})(typeof " + name + "=='undefined'?(" + name + "={}):" + name + ");";
wrapped_tl = parse(wrapped_tl);
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node) {
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function(node) {
if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(body);
}

View File

@@ -425,6 +425,7 @@ merge(Compressor.prototype, {
}
function safe_to_read(tw, def) {
if (def.single_use == "m") return false;
if (tw.safe_ids[def.id]) {
if (def.fixed == null) {
var orig = def.orig[0];
@@ -705,7 +706,7 @@ merge(Compressor.prototype, {
tw.loop_ids[d.id] = tw.in_loop;
}
var value;
if (d.fixed === undefined || !safe_to_read(tw, d) || d.single_use == "m") {
if (d.fixed === undefined || !safe_to_read(tw, d)) {
d.fixed = false;
} else if (d.fixed) {
value = this.fixed_value();
@@ -3415,202 +3416,200 @@ merge(Compressor.prototype, {
});
}
// pass 3: we should drop declarations not in_use
var tt = new TreeTransformer(
function before(node, descend, in_list) {
var parent = tt.parent();
if (drop_vars) {
var props = [], sym = assign_as_unused(node, props);
if (sym instanceof AST_SymbolRef) {
var def = sym.definition();
var in_use = def.id in in_use_ids;
var value = null;
if (node instanceof AST_Assign) {
if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) {
value = node.right;
}
} else if (!in_use) {
value = make_node(AST_Number, node, {
value: 0
});
var tt = new TreeTransformer(function(node, descend, in_list) {
var parent = tt.parent();
if (drop_vars) {
var props = [], sym = assign_as_unused(node, props);
if (sym instanceof AST_SymbolRef) {
var def = sym.definition();
var in_use = def.id in in_use_ids;
var value = null;
if (node instanceof AST_Assign) {
if (!in_use || node.left === sym && def.id in fixed_ids && fixed_ids[def.id] !== node) {
value = node.right;
}
if (value) {
props.push(value);
return maintain_this_binding(compressor, parent, node, make_sequence(node, props.map(function(prop) {
return prop.transform(tt);
})));
}
}
}
if (scope !== self) return;
if (node instanceof AST_Function
&& node.name
&& !compressor.option("keep_fnames")) {
var def = node.name.definition();
// any declarations with same name will overshadow
// name of this anonymous function and can therefore
// never be used anywhere
if (!(def.id in in_use_ids) || def.orig.length > 1) node.name = null;
}
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
var trim = !compressor.option("keep_fargs");
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
if (!(sym.definition().id in in_use_ids)) {
sym.__unused = true;
if (trim) {
a.pop();
compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", template(sym));
}
} else {
trim = false;
}
}
}
if (drop_funcs && node instanceof AST_Defun && node !== self) {
var def = node.name.definition();
if (!(def.id in in_use_ids)) {
compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name));
def.eliminated++;
return make_node(AST_EmptyStatement, node);
}
}
if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
// place uninitialized names at the start
var body = [], head = [], tail = [];
// for unused names whose initialization has
// side effects, we can cascade the init. code
// into the next one, or next statement.
var side_effects = [];
node.definitions.forEach(function(def) {
if (def.value) def.value = def.value.transform(tt);
var sym = def.name.definition();
if (!drop_vars || sym.id in in_use_ids) {
if (def.value && sym.id in fixed_ids && fixed_ids[sym.id] !== def) {
def.value = def.value.drop_side_effect_free(compressor);
}
if (def.name instanceof AST_SymbolVar) {
var var_defs = var_defs_by_id.get(sym.id);
if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
if (def.value) {
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
});
if (fixed_ids[sym.id] === def) {
fixed_ids[sym.id] = assign;
}
side_effects.push(assign.transform(tt));
}
remove(var_defs, def);
sym.eliminated++;
return;
}
}
if (def.value) {
if (side_effects.length > 0) {
if (tail.length > 0) {
side_effects.push(def.value);
def.value = make_sequence(def.value, side_effects);
} else {
body.push(make_node(AST_SimpleStatement, node, {
body: make_sequence(node, side_effects)
}));
}
side_effects = [];
}
tail.push(def);
} else {
head.push(def);
}
} else if (sym.orig[0] instanceof AST_SymbolCatch) {
var value = def.value && def.value.drop_side_effect_free(compressor);
if (value) side_effects.push(value);
def.value = null;
head.push(def);
} else {
var value = def.value && def.value.drop_side_effect_free(compressor);
if (value) {
compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
side_effects.push(value);
} else {
compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
}
sym.eliminated++;
}
});
if (head.length > 0 || tail.length > 0) {
node.definitions = head.concat(tail);
body.push(node);
}
if (side_effects.length > 0) {
body.push(make_node(AST_SimpleStatement, node, {
body: make_sequence(node, side_effects)
}));
}
switch (body.length) {
case 0:
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
case 1:
return body[0];
default:
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body
} else if (!in_use) {
value = make_node(AST_Number, node, {
value: 0
});
}
}
// certain combination of unused name + side effect leads to:
// https://github.com/mishoo/UglifyJS2/issues/44
// https://github.com/mishoo/UglifyJS2/issues/1830
// https://github.com/mishoo/UglifyJS2/issues/1838
// that's an invalid AST.
// We fix it at this stage by moving the `var` outside the `for`.
if (node instanceof AST_For) {
descend(node, this);
var block;
if (node.init instanceof AST_BlockStatement) {
block = node.init;
node.init = block.body.pop();
block.body.push(node);
if (value) {
props.push(value);
return maintain_this_binding(compressor, parent, node, make_sequence(node, props.map(function(prop) {
return prop.transform(tt);
})));
}
if (node.init instanceof AST_SimpleStatement) {
node.init = node.init.body;
} else if (is_empty(node.init)) {
node.init = null;
}
return !block ? node : in_list ? MAP.splice(block.body) : block;
}
if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
descend(node, this);
if (node.body instanceof AST_BlockStatement) {
var block = node.body;
node.body = block.body.pop();
block.body.push(node);
return in_list ? MAP.splice(block.body) : block;
}
return node;
}
if (node instanceof AST_Scope) {
var save_scope = scope;
scope = node;
descend(node, this);
scope = save_scope;
return node;
}
function template(sym) {
return {
name : sym.name,
file : sym.start.file,
line : sym.start.line,
col : sym.start.col
};
}
}
);
if (scope !== self) return;
if (node instanceof AST_Function
&& node.name
&& !compressor.option("keep_fnames")) {
var def = node.name.definition();
// any declarations with same name will overshadow
// name of this anonymous function and can therefore
// never be used anywhere
if (!(def.id in in_use_ids) || def.orig.length > 1) node.name = null;
}
if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
var trim = !compressor.option("keep_fargs");
for (var a = node.argnames, i = a.length; --i >= 0;) {
var sym = a[i];
if (!(sym.definition().id in in_use_ids)) {
sym.__unused = true;
if (trim) {
a.pop();
compressor[sym.unreferenced() ? "warn" : "info"]("Dropping unused function argument {name} [{file}:{line},{col}]", template(sym));
}
} else {
trim = false;
}
}
}
if (drop_funcs && node instanceof AST_Defun && node !== self) {
var def = node.name.definition();
if (!(def.id in in_use_ids)) {
compressor[node.name.unreferenced() ? "warn" : "info"]("Dropping unused function {name} [{file}:{line},{col}]", template(node.name));
def.eliminated++;
return make_node(AST_EmptyStatement, node);
}
}
if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
// place uninitialized names at the start
var body = [], head = [], tail = [];
// for unused names whose initialization has
// side effects, we can cascade the init. code
// into the next one, or next statement.
var side_effects = [];
node.definitions.forEach(function(def) {
if (def.value) def.value = def.value.transform(tt);
var sym = def.name.definition();
if (!drop_vars || sym.id in in_use_ids) {
if (def.value && sym.id in fixed_ids && fixed_ids[sym.id] !== def) {
def.value = def.value.drop_side_effect_free(compressor);
}
if (def.name instanceof AST_SymbolVar) {
var var_defs = var_defs_by_id.get(sym.id);
if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
compressor.warn("Dropping duplicated definition of variable {name} [{file}:{line},{col}]", template(def.name));
if (def.value) {
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
});
if (fixed_ids[sym.id] === def) {
fixed_ids[sym.id] = assign;
}
side_effects.push(assign.transform(tt));
}
remove(var_defs, def);
sym.eliminated++;
return;
}
}
if (def.value) {
if (side_effects.length > 0) {
if (tail.length > 0) {
side_effects.push(def.value);
def.value = make_sequence(def.value, side_effects);
} else {
body.push(make_node(AST_SimpleStatement, node, {
body: make_sequence(node, side_effects)
}));
}
side_effects = [];
}
tail.push(def);
} else {
head.push(def);
}
} else if (sym.orig[0] instanceof AST_SymbolCatch) {
var value = def.value && def.value.drop_side_effect_free(compressor);
if (value) side_effects.push(value);
def.value = null;
head.push(def);
} else {
var value = def.value && def.value.drop_side_effect_free(compressor);
if (value) {
compressor.warn("Side effects in initialization of unused variable {name} [{file}:{line},{col}]", template(def.name));
side_effects.push(value);
} else {
compressor[def.name.unreferenced() ? "warn" : "info"]("Dropping unused variable {name} [{file}:{line},{col}]", template(def.name));
}
sym.eliminated++;
}
});
if (head.length > 0 || tail.length > 0) {
node.definitions = head.concat(tail);
body.push(node);
}
if (side_effects.length > 0) {
body.push(make_node(AST_SimpleStatement, node, {
body: make_sequence(node, side_effects)
}));
}
switch (body.length) {
case 0:
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
case 1:
return body[0];
default:
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
body: body
});
}
}
// certain combination of unused name + side effect leads to:
// https://github.com/mishoo/UglifyJS2/issues/44
// https://github.com/mishoo/UglifyJS2/issues/1830
// https://github.com/mishoo/UglifyJS2/issues/1838
// that's an invalid AST.
// We fix it at this stage by moving the `var` outside the `for`.
if (node instanceof AST_For) {
descend(node, this);
var block;
if (node.init instanceof AST_BlockStatement) {
block = node.init;
node.init = block.body.pop();
block.body.push(node);
}
if (node.init instanceof AST_SimpleStatement) {
node.init = node.init.body;
} else if (is_empty(node.init)) {
node.init = null;
}
return !block ? node : in_list ? MAP.splice(block.body) : block;
}
if (node instanceof AST_LabeledStatement && node.body instanceof AST_For) {
descend(node, this);
if (node.body instanceof AST_BlockStatement) {
var block = node.body;
node.body = block.body.pop();
block.body.push(node);
return in_list ? MAP.splice(block.body) : block;
}
return node;
}
if (node instanceof AST_Scope) {
var save_scope = scope;
scope = node;
descend(node, this);
scope = save_scope;
return node;
}
function template(sym) {
return {
name : sym.name,
file : sym.start.file,
line : sym.start.line,
col : sym.start.col
};
}
});
self.transform(tt);
function verify_safe_usage(def, read, modified) {