From 20e4f8277fc69b275600770c5d41fa1b75b9984f Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 3 Jul 2017 02:10:56 +0800 Subject: [PATCH] refactor `throw` usage within `compress` (#2193) Eliminate exceptional constructs from normal control flow. --- lib/compress.js | 205 ++++++++++++++++++++++++++---------------------- 1 file changed, 111 insertions(+), 94 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index eb3ba756..db45c56b 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1198,21 +1198,21 @@ merge(Compressor.prototype, { for (var i = 0, len = statements.length; i < len; i++) { var stat = statements[i]; if (prev) { - if (stat instanceof AST_For) { - try { - prev.body.walk(new TreeWalker(function(node){ - if (node instanceof AST_Binary && node.operator == "in") - throw cons_seq; - })); - if (stat.init && !(stat.init instanceof AST_Definitions)) { - stat.init = cons_seq(stat.init); + if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) { + var abort = false; + prev.body.walk(new TreeWalker(function(node) { + if (abort) return true; + if (node instanceof AST_Binary && node.operator == "in") { + abort = true; + return true; } - else if (!stat.init) { + })); + if (!abort) { + if (stat.init) stat.init = cons_seq(stat.init); + else { stat.init = prev.body.drop_side_effect_free(compressor); n--; } - } catch(ex) { - if (ex !== cons_seq) throw ex; } } else if (stat instanceof AST_If) { @@ -1532,13 +1532,8 @@ merge(Compressor.prototype, { // descendant of AST_Node. AST_Node.DEFMETHOD("evaluate", function(compressor){ if (!compressor.option("evaluate")) return this; - try { - var val = this._eval(compressor); - return !val || val instanceof RegExp || typeof val != "object" ? val : this; - } catch(ex) { - if (ex !== def) throw ex; - return this; - } + var val = this._eval(compressor); + return !val || val instanceof RegExp || typeof val != "object" ? val : this; }); var unaryPrefix = makePredicate("! ~ - + void"); AST_Node.DEFMETHOD("is_constant", function(){ @@ -1584,27 +1579,28 @@ merge(Compressor.prototype, { def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); - def(AST_Lambda, function(){ - throw def; - }); + 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, function(){ - throw def; // not constant - }); + def(AST_Node, return_this); def(AST_Constant, function(){ return this.getValue(); }); def(AST_Array, function(compressor){ if (compressor.option("unsafe")) { - return this.elements.map(function(element) { - return ev(element, compressor); - }); + var elements = []; + for (var i = 0, len = this.elements.length; i < len; i++) { + var element = this.elements[i]; + var value = ev(element, compressor); + if (element === value) return this; + elements.push(value); + } + return elements; } - throw def; + return this; }); def(AST_Object, function(compressor){ if (compressor.option("unsafe")) { @@ -1616,105 +1612,121 @@ merge(Compressor.prototype, { key = key.name; } else if (key instanceof AST_Node) { key = ev(key, compressor); + if (key === prop.key) return this; } if (typeof Object.prototype[key] === 'function') { - throw def; + return this; } val[key] = ev(prop.value, compressor); + if (val[key] === prop.value) return this; } return val; } - throw def; + return this; }); def(AST_UnaryPrefix, function(compressor){ - var e = this.expression; + // Function would be evaluated to an array and so typeof would + // incorrectly return 'object'. Hence making is a special case. + if (this.operator == "typeof" && this.expression instanceof AST_Function) { + return typeof function(){}; + } + var e = ev(this.expression, compressor); + if (e === this.expression) return this; switch (this.operator) { - case "!": return !ev(e, compressor); + case "!": return !e; case "typeof": - // Function would be evaluated to an array and so typeof would - // incorrectly return 'object'. Hence making is a special case. - if (e instanceof AST_Function) return typeof function(){}; - - e = ev(e, compressor); - // typeof returns "object" or "function" on different platforms // so cannot evaluate reliably - if (e instanceof RegExp) throw def; - + if (e instanceof RegExp) return this; return typeof e; - case "void": return void ev(e, compressor); - case "~": return ~ev(e, compressor); - case "-": return -ev(e, compressor); - case "+": return +ev(e, compressor); + case "void": return void e; + case "~": return ~e; + case "-": return -e; + case "+": return +e; } - throw def; + return this; }); - def(AST_Binary, function(c){ - var left = this.left, right = this.right, result; + def(AST_Binary, function(compressor){ + var left = ev(this.left, compressor); + if (left === this.left) return this; + var right = ev(this.right, compressor); + if (right === this.right) return this; + var result; switch (this.operator) { - case "&&" : result = ev(left, c) && ev(right, c); break; - case "||" : result = ev(left, c) || ev(right, c); break; - case "|" : result = ev(left, c) | ev(right, c); break; - case "&" : result = ev(left, c) & ev(right, c); break; - case "^" : result = ev(left, c) ^ ev(right, c); break; - case "+" : result = ev(left, c) + ev(right, c); break; - case "*" : result = ev(left, c) * ev(right, c); break; - case "/" : result = ev(left, c) / ev(right, c); break; - case "%" : result = ev(left, c) % ev(right, c); break; - case "-" : result = ev(left, c) - ev(right, c); break; - case "<<" : result = ev(left, c) << ev(right, c); break; - case ">>" : result = ev(left, c) >> ev(right, c); break; - case ">>>" : result = ev(left, c) >>> ev(right, c); break; - case "==" : result = ev(left, c) == ev(right, c); break; - case "===" : result = ev(left, c) === ev(right, c); break; - case "!=" : result = ev(left, c) != ev(right, c); break; - case "!==" : result = ev(left, c) !== ev(right, c); break; - case "<" : result = ev(left, c) < ev(right, c); break; - case "<=" : result = ev(left, c) <= ev(right, c); break; - case ">" : result = ev(left, c) > ev(right, c); break; - case ">=" : result = ev(left, c) >= ev(right, c); break; + case "&&" : result = left && right; break; + case "||" : result = left || right; break; + case "|" : result = left | right; break; + case "&" : result = left & right; break; + case "^" : result = left ^ right; break; + case "+" : result = left + right; break; + case "*" : result = left * right; break; + case "/" : result = left / right; break; + case "%" : result = left % right; break; + case "-" : result = left - right; break; + case "<<" : result = left << right; break; + case ">>" : result = left >> right; break; + case ">>>" : result = left >>> right; break; + case "==" : result = left == right; break; + case "===" : result = left === right; break; + case "!=" : result = left != right; break; + case "!==" : result = left !== right; break; + case "<" : result = left < right; break; + case "<=" : result = left <= right; break; + case ">" : result = left > right; break; + case ">=" : result = left >= right; break; default: - throw def; + return this; } - if (isNaN(result) && c.find_parent(AST_With)) { + if (isNaN(result) && compressor.find_parent(AST_With)) { // leave original expression as is - throw def; + return this; } return result; }); def(AST_Conditional, function(compressor){ - return ev(this.condition, compressor) - ? ev(this.consequent, compressor) - : ev(this.alternative, compressor); + var condition = ev(this.condition, compressor); + if (condition === this.condition) return this; + var node = condition ? this.consequent : this.alternative; + var value = ev(node, compressor); + return value === node ? this : value; }); def(AST_SymbolRef, function(compressor){ - if (!compressor.option("reduce_vars") || this._evaluating) throw def; - this._evaluating = true; - try { - var fixed = this.fixed_value(); - if (!fixed) throw def; - var value = ev(fixed, compressor); - if (!HOP(fixed, "_eval")) fixed._eval = function() { - return value; - }; - if (value && typeof value == "object" && this.definition().escaped) throw def; + if (!compressor.option("reduce_vars")) return this; + this._eval = return_this; + var fixed = this.fixed_value(); + if (!fixed) { + delete this._eval; + return this; + } + var value = ev(fixed, compressor); + if (value === fixed) { + delete this._eval; + return this; + } + if (!HOP(fixed, "_eval")) fixed._eval = function() { return value; - } finally { - this._evaluating = false; + }; + if (value && typeof value == "object" && this.definition().escaped) { + delete this._eval; + return this; } + this._eval = fixed._eval; + return value; }); def(AST_PropAccess, function(compressor){ if (compressor.option("unsafe")) { var key = this.property; if (key instanceof AST_Node) { key = ev(key, compressor); + if (key === this.property) return this; } var val = ev(this.expression, compressor); + if (val === this.expression) return this; if (val && HOP(val, key)) { return val[key]; } } - throw def; + return this; }); var object_fns = [ 'constructor', @@ -1760,19 +1772,24 @@ merge(Compressor.prototype, { var key = exp.property; if (key instanceof AST_Node) { key = ev(key, compressor); + if (key === exp.property) return this; } var val = ev(exp.expression, compressor); + if (val === exp.expression) return this; if ((val && native_fns[val.constructor.name] || return_false)(key)) { - return val[key].apply(val, this.args.map(function(arg) { - return ev(arg, compressor); - })); + var args = []; + for (var i = 0, len = this.args.length; i < len; i++) { + var arg = this.args[i]; + var value = ev(arg, compressor); + if (arg === value) return this; + args.push(value); + } + return val[key].apply(val, args); } } - throw def; - }); - def(AST_New, function(compressor){ - throw def; + return this; }); + def(AST_New, return_this); })(function(node, func){ node.DEFMETHOD("_eval", func); }); -- 2.34.1