From 3ca902258c24209699f0b5bd5b9654252e492272 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 14 May 2017 02:10:34 +0800 Subject: 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 --- lib/ast.js | 4 ++-- lib/compress.js | 49 +++++++++++++++++++++++++++++++------------------ lib/parse.js | 9 +++++++-- lib/scope.js | 5 ----- 4 files changed, 40 insertions(+), 27 deletions(-) (limited to 'lib') diff --git a/lib/ast.js b/lib/ast.js index 57956233..2972b7aa 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -685,8 +685,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(){ diff --git a/lib/compress.js b/lib/compress.js index 892bceef..f168b942 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -368,6 +368,13 @@ merge(Compressor.prototype, { 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); @@ -1220,12 +1227,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) { @@ -1237,7 +1244,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() { @@ -1246,33 +1258,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_Sequence, function(pure_getters) { - return this.expressions[this.expressions.length - 1]._eq_null(pure_getters); + return this.expressions[this.expressions.length - 1]._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 ]----- */ @@ -1813,11 +1825,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); }); @@ -2370,6 +2382,7 @@ merge(Compressor.prototype, { var args = trim(this.args, compressor, first_in_statement); return args && make_sequence(this, 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); @@ -2437,11 +2450,11 @@ merge(Compressor.prototype, { return values && make_sequence(this, 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); diff --git a/lib/parse.js b/lib/parse.js index c432ad7a..f1089501 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1310,10 +1310,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() })); @@ -1322,7 +1327,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() })); diff --git a/lib/scope.js b/lib/scope.js index ea08d775..14ffb46f 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -360,11 +360,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; -- cgit v1.2.3