fix bugs with getter/setter (#1926)
- `reduce_vars` - `side_effects` - property access for object - `AST_SymbolAccessor` as key names enhance `test/ufuzz.js` - add object getter & setter - property assignment to setter - avoid infinite recursion in setter - fix & adjust assignment operators - 50% `=` - 25% `+=` - 2.5% each for the rest - avoid "Invalid array length" - fix `console.log()` - bypass getter - curb recursive reference - deprecate `-E`, always report runtime errors
This commit is contained in:
@@ -798,8 +798,8 @@ var AST_Object = DEFNODE("Object", "properties", {
|
||||
var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
|
||||
$documentation: "Base class for literal object properties",
|
||||
$propdoc: {
|
||||
key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an arbitrary AST_Node.",
|
||||
value: "[AST_Node] property value. For setters and getters this is an AST_Function."
|
||||
key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an AST_SymbolAccessor.",
|
||||
value: "[AST_Node] property value. For setters and getters this is an AST_Accessor."
|
||||
},
|
||||
_walk: function(visitor) {
|
||||
return visitor._visit(this, function(){
|
||||
|
||||
110
lib/compress.js
110
lib/compress.js
@@ -316,25 +316,55 @@ merge(Compressor.prototype, {
|
||||
safe_ids = save_ids;
|
||||
return true;
|
||||
}
|
||||
var iife;
|
||||
if (node instanceof AST_Function
|
||||
&& !node.name
|
||||
&& (iife = tw.parent()) instanceof AST_Call
|
||||
&& iife.expression === node) {
|
||||
// Virtually turn IIFE parameters into variable definitions:
|
||||
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
|
||||
// So existing transformation rules can work on them.
|
||||
node.argnames.forEach(function(arg, i) {
|
||||
var d = arg.definition();
|
||||
if (!node.uses_arguments && d.fixed === undefined) {
|
||||
d.fixed = function() {
|
||||
return iife.args[i] || make_node(AST_Undefined, iife);
|
||||
};
|
||||
mark(d, true);
|
||||
} else {
|
||||
d.fixed = false;
|
||||
}
|
||||
});
|
||||
if (node instanceof AST_Function) {
|
||||
push();
|
||||
var iife;
|
||||
if (!node.name
|
||||
&& (iife = tw.parent()) instanceof AST_Call
|
||||
&& iife.expression === node) {
|
||||
// Virtually turn IIFE parameters into variable definitions:
|
||||
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
|
||||
// So existing transformation rules can work on them.
|
||||
node.argnames.forEach(function(arg, i) {
|
||||
var d = arg.definition();
|
||||
if (!node.uses_arguments && d.fixed === undefined) {
|
||||
d.fixed = function() {
|
||||
return iife.args[i] || make_node(AST_Undefined, iife);
|
||||
};
|
||||
mark(d, true);
|
||||
} else {
|
||||
d.fixed = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
descend();
|
||||
pop();
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Accessor) {
|
||||
var save_ids = safe_ids;
|
||||
safe_ids = Object.create(null);
|
||||
descend();
|
||||
safe_ids = save_ids;
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Binary
|
||||
&& (node.operator == "&&" || node.operator == "||")) {
|
||||
node.left.walk(tw);
|
||||
push();
|
||||
node.right.walk(tw);
|
||||
pop();
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_Conditional) {
|
||||
node.condition.walk(tw);
|
||||
push();
|
||||
node.consequent.walk(tw);
|
||||
pop();
|
||||
push();
|
||||
node.alternative.walk(tw);
|
||||
pop();
|
||||
return true;
|
||||
}
|
||||
if (node instanceof AST_If || node instanceof AST_DWLoop) {
|
||||
node.condition.walk(tw);
|
||||
@@ -1195,12 +1225,12 @@ merge(Compressor.prototype, {
|
||||
&& !node.expression.has_side_effects(compressor);
|
||||
}
|
||||
|
||||
// may_eq_null()
|
||||
// returns true if this node may evaluate to null or undefined
|
||||
// may_throw_on_access()
|
||||
// returns true if this node may be null, undefined or contain `AST_Accessor`
|
||||
(function(def) {
|
||||
AST_Node.DEFMETHOD("may_eq_null", function(compressor) {
|
||||
AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
|
||||
var pure_getters = compressor.option("pure_getters");
|
||||
return !pure_getters || this._eq_null(pure_getters);
|
||||
return !pure_getters || this._throw_on_access(pure_getters);
|
||||
});
|
||||
|
||||
function is_strict(pure_getters) {
|
||||
@@ -1212,7 +1242,12 @@ merge(Compressor.prototype, {
|
||||
def(AST_Undefined, return_true);
|
||||
def(AST_Constant, return_false);
|
||||
def(AST_Array, return_false);
|
||||
def(AST_Object, return_false);
|
||||
def(AST_Object, function(pure_getters) {
|
||||
if (!is_strict(pure_getters)) return false;
|
||||
for (var i = this.properties.length; --i >=0;)
|
||||
if (this.properties[i].value instanceof AST_Accessor) return true;
|
||||
return false;
|
||||
});
|
||||
def(AST_Function, return_false);
|
||||
def(AST_UnaryPostfix, return_false);
|
||||
def(AST_UnaryPrefix, function() {
|
||||
@@ -1221,33 +1256,33 @@ merge(Compressor.prototype, {
|
||||
def(AST_Binary, function(pure_getters) {
|
||||
switch (this.operator) {
|
||||
case "&&":
|
||||
return this.left._eq_null(pure_getters);
|
||||
return this.left._throw_on_access(pure_getters);
|
||||
case "||":
|
||||
return this.left._eq_null(pure_getters)
|
||||
&& this.right._eq_null(pure_getters);
|
||||
return this.left._throw_on_access(pure_getters)
|
||||
&& this.right._throw_on_access(pure_getters);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
})
|
||||
def(AST_Assign, function(pure_getters) {
|
||||
return this.operator == "="
|
||||
&& this.right._eq_null(pure_getters);
|
||||
&& this.right._throw_on_access(pure_getters);
|
||||
})
|
||||
def(AST_Conditional, function(pure_getters) {
|
||||
return this.consequent._eq_null(pure_getters)
|
||||
|| this.alternative._eq_null(pure_getters);
|
||||
return this.consequent._throw_on_access(pure_getters)
|
||||
|| this.alternative._throw_on_access(pure_getters);
|
||||
})
|
||||
def(AST_Seq, function(pure_getters) {
|
||||
return this.cdr._eq_null(pure_getters);
|
||||
return this.cdr._throw_on_access(pure_getters);
|
||||
});
|
||||
def(AST_SymbolRef, function(pure_getters) {
|
||||
if (this.is_undefined) return true;
|
||||
if (!is_strict(pure_getters)) return false;
|
||||
var fixed = this.fixed_value();
|
||||
return !fixed || fixed._eq_null(pure_getters);
|
||||
return !fixed || fixed._throw_on_access(pure_getters);
|
||||
});
|
||||
})(function(node, func) {
|
||||
node.DEFMETHOD("_eq_null", func);
|
||||
node.DEFMETHOD("_throw_on_access", func);
|
||||
});
|
||||
|
||||
/* -----[ boolean/negation helpers ]----- */
|
||||
@@ -1787,11 +1822,11 @@ merge(Compressor.prototype, {
|
||||
return any(this.elements, compressor);
|
||||
});
|
||||
def(AST_Dot, function(compressor){
|
||||
return this.expression.may_eq_null(compressor)
|
||||
return this.expression.may_throw_on_access(compressor)
|
||||
|| this.expression.has_side_effects(compressor);
|
||||
});
|
||||
def(AST_Sub, function(compressor){
|
||||
return this.expression.may_eq_null(compressor)
|
||||
return this.expression.may_throw_on_access(compressor)
|
||||
|| this.expression.has_side_effects(compressor)
|
||||
|| this.property.has_side_effects(compressor);
|
||||
});
|
||||
@@ -2295,6 +2330,7 @@ merge(Compressor.prototype, {
|
||||
var args = trim(this.args, compressor, first_in_statement);
|
||||
return args && AST_Seq.from_array(args);
|
||||
});
|
||||
def(AST_Accessor, return_null);
|
||||
def(AST_Function, return_null);
|
||||
def(AST_Binary, function(compressor, first_in_statement){
|
||||
var right = this.right.drop_side_effect_free(compressor);
|
||||
@@ -2365,11 +2401,11 @@ merge(Compressor.prototype, {
|
||||
return values && AST_Seq.from_array(values);
|
||||
});
|
||||
def(AST_Dot, function(compressor, first_in_statement){
|
||||
if (this.expression.may_eq_null(compressor)) return this;
|
||||
if (this.expression.may_throw_on_access(compressor)) return this;
|
||||
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
||||
});
|
||||
def(AST_Sub, function(compressor, first_in_statement){
|
||||
if (this.expression.may_eq_null(compressor)) return this;
|
||||
if (this.expression.may_throw_on_access(compressor)) return this;
|
||||
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
|
||||
if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement);
|
||||
var property = this.property.drop_side_effect_free(compressor);
|
||||
|
||||
@@ -1320,10 +1320,15 @@ function parse($TEXT, options) {
|
||||
var type = start.type;
|
||||
var name = as_property_name();
|
||||
if (type == "name" && !is("punc", ":")) {
|
||||
var key = new AST_SymbolAccessor({
|
||||
start: S.token,
|
||||
name: as_property_name(),
|
||||
end: prev()
|
||||
});
|
||||
if (name == "get") {
|
||||
a.push(new AST_ObjectGetter({
|
||||
start : start,
|
||||
key : as_atom_node(),
|
||||
key : key,
|
||||
value : create_accessor(),
|
||||
end : prev()
|
||||
}));
|
||||
@@ -1332,7 +1337,7 @@ function parse($TEXT, options) {
|
||||
if (name == "set") {
|
||||
a.push(new AST_ObjectSetter({
|
||||
start : start,
|
||||
key : as_atom_node(),
|
||||
key : key,
|
||||
value : create_accessor(),
|
||||
end : prev()
|
||||
}));
|
||||
|
||||
@@ -361,11 +361,6 @@ AST_Symbol.DEFMETHOD("unmangleable", function(options){
|
||||
return this.definition().unmangleable(options);
|
||||
});
|
||||
|
||||
// property accessors are not mangleable
|
||||
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){
|
||||
return true;
|
||||
});
|
||||
|
||||
// labels are always mangleable
|
||||
AST_Label.DEFMETHOD("unmangleable", function(){
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user