fix escape analysis on AST_PropAccess (#2636)
This commit is contained in:
@@ -418,29 +418,27 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mark_escaped(tw, d, scope, node, value, level) {
|
function mark_escaped(tw, d, scope, node, value, level, depth) {
|
||||||
var parent = tw.parent(level);
|
var parent = tw.parent(level);
|
||||||
if (value) {
|
if (value && value.is_constant()) return;
|
||||||
if (value.is_constant()) return;
|
|
||||||
if (level > 0 && value.is_constant_expression(scope)) return;
|
|
||||||
}
|
|
||||||
if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right
|
if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right
|
||||||
|| parent instanceof AST_Call && node !== parent.expression
|
|| parent instanceof AST_Call && node !== parent.expression
|
||||||
|| parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
|
|| parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope
|
||||||
|| parent instanceof AST_VarDef && node === parent.value) {
|
|| parent instanceof AST_VarDef && node === parent.value) {
|
||||||
d.escaped = true;
|
if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1;
|
||||||
|
if (!d.escaped || d.escaped > depth) d.escaped = depth;
|
||||||
return;
|
return;
|
||||||
} else if (parent instanceof AST_Array
|
} else if (parent instanceof AST_Array
|
||||||
|| parent instanceof AST_Binary && lazy_op(parent.operator)
|
|| parent instanceof AST_Binary && lazy_op(parent.operator)
|
||||||
|| parent instanceof AST_Conditional && node !== parent.condition
|
|| parent instanceof AST_Conditional && node !== parent.condition
|
||||||
|| parent instanceof AST_Sequence && node === parent.tail_node()) {
|
|| parent instanceof AST_Sequence && node === parent.tail_node()) {
|
||||||
mark_escaped(tw, d, scope, parent, parent, level + 1);
|
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 && node === parent.value) {
|
||||||
var obj = tw.parent(level + 1);
|
var obj = tw.parent(level + 1);
|
||||||
mark_escaped(tw, d, scope, obj, obj, level + 2);
|
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 && node === parent.expression) {
|
||||||
value = read_property(value, parent.property);
|
value = read_property(value, parent.property);
|
||||||
mark_escaped(tw, d, scope, parent, value, level + 1);
|
mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1);
|
||||||
if (value) return;
|
if (value) return;
|
||||||
}
|
}
|
||||||
if (level == 0) d.direct_access = true;
|
if (level == 0) d.direct_access = true;
|
||||||
@@ -637,7 +635,7 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mark_escaped(tw, d, this.scope, this, value, 0);
|
mark_escaped(tw, d, this.scope, this, value, 0, 1);
|
||||||
});
|
});
|
||||||
def(AST_Toplevel, function(tw, descend, compressor) {
|
def(AST_Toplevel, function(tw, descend, compressor) {
|
||||||
this.globals.each(function(def) {
|
this.globals.each(function(def) {
|
||||||
@@ -1909,7 +1907,7 @@ merge(Compressor.prototype, {
|
|||||||
// descendant of AST_Node.
|
// descendant of AST_Node.
|
||||||
AST_Node.DEFMETHOD("evaluate", function(compressor){
|
AST_Node.DEFMETHOD("evaluate", function(compressor){
|
||||||
if (!compressor.option("evaluate")) return this;
|
if (!compressor.option("evaluate")) return this;
|
||||||
var val = this._eval(compressor);
|
var val = this._eval(compressor, 1);
|
||||||
return !val || val instanceof RegExp || typeof val != "object" ? val : this;
|
return !val || val instanceof RegExp || typeof val != "object" ? val : this;
|
||||||
});
|
});
|
||||||
var unaryPrefix = makePredicate("! ~ - + void");
|
var unaryPrefix = makePredicate("! ~ - + void");
|
||||||
@@ -1928,22 +1926,17 @@ merge(Compressor.prototype, {
|
|||||||
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
|
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
|
||||||
});
|
});
|
||||||
def(AST_Lambda, return_this);
|
def(AST_Lambda, return_this);
|
||||||
function ev(node, compressor) {
|
|
||||||
if (!compressor) throw new Error("Compressor must be passed");
|
|
||||||
|
|
||||||
return node._eval(compressor);
|
|
||||||
};
|
|
||||||
def(AST_Node, return_this);
|
def(AST_Node, return_this);
|
||||||
def(AST_Constant, function(){
|
def(AST_Constant, function(){
|
||||||
return this.getValue();
|
return this.getValue();
|
||||||
});
|
});
|
||||||
def(AST_Array, function(compressor){
|
def(AST_Array, function(compressor, depth) {
|
||||||
if (compressor.option("unsafe")) {
|
if (compressor.option("unsafe")) {
|
||||||
var elements = [];
|
var elements = [];
|
||||||
for (var i = 0, len = this.elements.length; i < len; i++) {
|
for (var i = 0, len = this.elements.length; i < len; i++) {
|
||||||
var element = this.elements[i];
|
var element = this.elements[i];
|
||||||
if (element instanceof AST_Function) continue;
|
if (element instanceof AST_Function) continue;
|
||||||
var value = ev(element, compressor);
|
var value = element._eval(compressor, depth);
|
||||||
if (element === value) return this;
|
if (element === value) return this;
|
||||||
elements.push(value);
|
elements.push(value);
|
||||||
}
|
}
|
||||||
@@ -1951,7 +1944,7 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
});
|
});
|
||||||
def(AST_Object, function(compressor){
|
def(AST_Object, function(compressor, depth) {
|
||||||
if (compressor.option("unsafe")) {
|
if (compressor.option("unsafe")) {
|
||||||
var val = {};
|
var val = {};
|
||||||
for (var i = 0, len = this.properties.length; i < len; i++) {
|
for (var i = 0, len = this.properties.length; i < len; i++) {
|
||||||
@@ -1960,21 +1953,21 @@ merge(Compressor.prototype, {
|
|||||||
if (key instanceof AST_Symbol) {
|
if (key instanceof AST_Symbol) {
|
||||||
key = key.name;
|
key = key.name;
|
||||||
} else if (key instanceof AST_Node) {
|
} else if (key instanceof AST_Node) {
|
||||||
key = ev(key, compressor);
|
key = key._eval(compressor, depth);
|
||||||
if (key === prop.key) return this;
|
if (key === prop.key) return this;
|
||||||
}
|
}
|
||||||
if (typeof Object.prototype[key] === 'function') {
|
if (typeof Object.prototype[key] === 'function') {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
if (prop.value instanceof AST_Function) continue;
|
if (prop.value instanceof AST_Function) continue;
|
||||||
val[key] = ev(prop.value, compressor);
|
val[key] = prop.value._eval(compressor, depth);
|
||||||
if (val[key] === prop.value) return this;
|
if (val[key] === prop.value) return this;
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
});
|
});
|
||||||
def(AST_UnaryPrefix, function(compressor){
|
def(AST_UnaryPrefix, function(compressor, depth) {
|
||||||
var e = this.expression;
|
var e = this.expression;
|
||||||
// Function would be evaluated to an array and so typeof would
|
// Function would be evaluated to an array and so typeof would
|
||||||
// incorrectly return 'object'. Hence making is a special case.
|
// incorrectly return 'object'. Hence making is a special case.
|
||||||
@@ -1985,7 +1978,7 @@ merge(Compressor.prototype, {
|
|||||||
&& e.fixed_value() instanceof AST_Lambda)) {
|
&& e.fixed_value() instanceof AST_Lambda)) {
|
||||||
return typeof function(){};
|
return typeof function(){};
|
||||||
}
|
}
|
||||||
e = ev(e, compressor);
|
e = e._eval(compressor, depth);
|
||||||
if (e === this.expression) return this;
|
if (e === this.expression) return this;
|
||||||
switch (this.operator) {
|
switch (this.operator) {
|
||||||
case "!": return !e;
|
case "!": return !e;
|
||||||
@@ -2001,10 +1994,10 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
});
|
});
|
||||||
def(AST_Binary, function(compressor){
|
def(AST_Binary, function(compressor, depth) {
|
||||||
var left = ev(this.left, compressor);
|
var left = this.left._eval(compressor, depth);
|
||||||
if (left === this.left) return this;
|
if (left === this.left) return this;
|
||||||
var right = ev(this.right, compressor);
|
var right = this.right._eval(compressor, depth);
|
||||||
if (right === this.right) return this;
|
if (right === this.right) return this;
|
||||||
var result;
|
var result;
|
||||||
switch (this.operator) {
|
switch (this.operator) {
|
||||||
@@ -2038,30 +2031,32 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
def(AST_Conditional, function(compressor){
|
def(AST_Conditional, function(compressor, depth) {
|
||||||
var condition = ev(this.condition, compressor);
|
var condition = this.condition._eval(compressor, depth);
|
||||||
if (condition === this.condition) return this;
|
if (condition === this.condition) return this;
|
||||||
var node = condition ? this.consequent : this.alternative;
|
var node = condition ? this.consequent : this.alternative;
|
||||||
var value = ev(node, compressor);
|
var value = node._eval(compressor, depth);
|
||||||
return value === node ? this : value;
|
return value === node ? this : value;
|
||||||
});
|
});
|
||||||
def(AST_SymbolRef, function(compressor){
|
def(AST_SymbolRef, function(compressor, depth) {
|
||||||
var fixed = this.fixed_value();
|
var fixed = this.fixed_value();
|
||||||
if (!fixed) return this;
|
if (!fixed) return this;
|
||||||
this._eval = return_this;
|
var value;
|
||||||
var value = ev(fixed, compressor);
|
if (HOP(fixed, "_eval")) {
|
||||||
if (value === fixed) {
|
value = fixed._eval();
|
||||||
|
} else {
|
||||||
|
this._eval = return_this;
|
||||||
|
value = fixed._eval(compressor, depth);
|
||||||
delete this._eval;
|
delete this._eval;
|
||||||
return this;
|
if (value === fixed) return this;
|
||||||
|
fixed._eval = function() {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (!HOP(fixed, "_eval")) fixed._eval = function() {
|
if (value && typeof value == "object") {
|
||||||
return value;
|
var escaped = this.definition().escaped;
|
||||||
};
|
if (escaped && depth > escaped) return this;
|
||||||
if (value && typeof value == "object" && this.definition().escaped) {
|
|
||||||
delete this._eval;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
this._eval = fixed._eval;
|
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
var global_objs = {
|
var global_objs = {
|
||||||
@@ -2095,11 +2090,11 @@ merge(Compressor.prototype, {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
convert_to_predicate(static_values);
|
convert_to_predicate(static_values);
|
||||||
def(AST_PropAccess, function(compressor){
|
def(AST_PropAccess, function(compressor, depth) {
|
||||||
if (compressor.option("unsafe")) {
|
if (compressor.option("unsafe")) {
|
||||||
var key = this.property;
|
var key = this.property;
|
||||||
if (key instanceof AST_Node) {
|
if (key instanceof AST_Node) {
|
||||||
key = ev(key, compressor);
|
key = key._eval(compressor, depth);
|
||||||
if (key === this.property) return this;
|
if (key === this.property) return this;
|
||||||
}
|
}
|
||||||
var exp = this.expression;
|
var exp = this.expression;
|
||||||
@@ -2108,7 +2103,7 @@ merge(Compressor.prototype, {
|
|||||||
if (!(static_values[exp.name] || return_false)(key)) return this;
|
if (!(static_values[exp.name] || return_false)(key)) return this;
|
||||||
val = global_objs[exp.name];
|
val = global_objs[exp.name];
|
||||||
} else {
|
} else {
|
||||||
val = ev(exp, compressor);
|
val = exp._eval(compressor, depth + 1);
|
||||||
if (!val || val === exp || !HOP(val, key)) return this;
|
if (!val || val === exp || !HOP(val, key)) return this;
|
||||||
}
|
}
|
||||||
return val[key];
|
return val[key];
|
||||||
@@ -2186,12 +2181,12 @@ merge(Compressor.prototype, {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
convert_to_predicate(static_fns);
|
convert_to_predicate(static_fns);
|
||||||
def(AST_Call, function(compressor){
|
def(AST_Call, function(compressor, depth) {
|
||||||
var exp = this.expression;
|
var exp = this.expression;
|
||||||
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
|
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
|
||||||
var key = exp.property;
|
var key = exp.property;
|
||||||
if (key instanceof AST_Node) {
|
if (key instanceof AST_Node) {
|
||||||
key = ev(key, compressor);
|
key = key._eval(compressor, depth);
|
||||||
if (key === exp.property) return this;
|
if (key === exp.property) return this;
|
||||||
}
|
}
|
||||||
var val;
|
var val;
|
||||||
@@ -2200,13 +2195,13 @@ merge(Compressor.prototype, {
|
|||||||
if (!(static_fns[e.name] || return_false)(key)) return this;
|
if (!(static_fns[e.name] || return_false)(key)) return this;
|
||||||
val = global_objs[e.name];
|
val = global_objs[e.name];
|
||||||
} else {
|
} else {
|
||||||
val = ev(e, compressor);
|
val = e._eval(compressor, depth + 1);
|
||||||
if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this;
|
if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this;
|
||||||
}
|
}
|
||||||
var args = [];
|
var args = [];
|
||||||
for (var i = 0, len = this.args.length; i < len; i++) {
|
for (var i = 0, len = this.args.length; i < len; i++) {
|
||||||
var arg = this.args[i];
|
var arg = this.args[i];
|
||||||
var value = ev(arg, compressor);
|
var value = arg._eval(compressor, depth);
|
||||||
if (arg === value) return this;
|
if (arg === value) return this;
|
||||||
args.push(value);
|
args.push(value);
|
||||||
}
|
}
|
||||||
@@ -3083,7 +3078,7 @@ merge(Compressor.prototype, {
|
|||||||
if (node instanceof AST_VarDef) {
|
if (node instanceof AST_VarDef) {
|
||||||
var sym = node.name, def, value;
|
var sym = node.name, def, value;
|
||||||
if (sym.scope === self
|
if (sym.scope === self
|
||||||
&& !(def = sym.definition()).escaped
|
&& (def = sym.definition()).escaped != 1
|
||||||
&& !def.single_use
|
&& !def.single_use
|
||||||
&& !def.direct_access
|
&& !def.direct_access
|
||||||
&& !top_retain(def)
|
&& !top_retain(def)
|
||||||
@@ -4698,7 +4693,7 @@ merge(Compressor.prototype, {
|
|||||||
if (d.single_use && fixed instanceof AST_Function) {
|
if (d.single_use && fixed instanceof AST_Function) {
|
||||||
if (d.scope !== self.scope
|
if (d.scope !== self.scope
|
||||||
&& (!compressor.option("reduce_funcs")
|
&& (!compressor.option("reduce_funcs")
|
||||||
|| d.escaped
|
|| d.escaped == 1
|
||||||
|| fixed.inlined)) {
|
|| fixed.inlined)) {
|
||||||
d.single_use = false;
|
d.single_use = false;
|
||||||
} else if (recursive_ref(compressor, d)) {
|
} else if (recursive_ref(compressor, d)) {
|
||||||
|
|||||||
@@ -3422,6 +3422,37 @@ escaped_prop_1: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
escaped_prop_2: {
|
escaped_prop_2: {
|
||||||
|
options = {
|
||||||
|
collapse_vars: true,
|
||||||
|
evaluate: true,
|
||||||
|
inline: true,
|
||||||
|
passes: 2,
|
||||||
|
pure_getters: "strict",
|
||||||
|
reduce_funcs: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
side_effects: true,
|
||||||
|
toplevel: true,
|
||||||
|
unsafe: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var obj = { o: { a: 1 } };
|
||||||
|
(function(o) {
|
||||||
|
o.a++;
|
||||||
|
})(obj.o);
|
||||||
|
(function(o) {
|
||||||
|
console.log(o.a);
|
||||||
|
})(obj.o);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var obj = { o: { a: 1 } };
|
||||||
|
obj.o.a++;
|
||||||
|
console.log(obj.o.a);
|
||||||
|
}
|
||||||
|
expect_stdout: "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
escaped_prop_3: {
|
||||||
options = {
|
options = {
|
||||||
reduce_funcs: true,
|
reduce_funcs: true,
|
||||||
reduce_vars: true,
|
reduce_vars: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user