From 38f2b4579fc2c8f7fa4e31b544dc93be9dcb031e Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Fri, 2 Mar 2018 04:04:29 +0800 Subject: [PATCH] fix value reference caching in `evaluate` (#2969) fixes #2968 --- lib/compress.js | 53 +++++++++++++++++++++------------------ test/compress/evaluate.js | 31 +++++++++++++++++++++++ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 504d270b..97e4c11d 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -2365,7 +2365,11 @@ merge(Compressor.prototype, { // descendant of AST_Node. AST_Node.DEFMETHOD("evaluate", function(compressor){ if (!compressor.option("evaluate")) return this; - var val = this._eval(compressor, 1); + var cached = []; + var val = this._eval(compressor, cached, 1); + cached.forEach(function(node) { + delete node._eval; + }); if (!val || val instanceof RegExp) return val; if (typeof val == "function" || typeof val == "object") return this; return val; @@ -2401,12 +2405,12 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Array, function(compressor, depth) { + def(AST_Array, function(compressor, cached, depth) { if (compressor.option("unsafe")) { var elements = []; for (var i = 0, len = this.elements.length; i < len; i++) { var element = this.elements[i]; - var value = element._eval(compressor, depth); + var value = element._eval(compressor, cached, depth); if (element === value) return this; elements.push(value); } @@ -2414,7 +2418,7 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Object, function(compressor, depth) { + def(AST_Object, function(compressor, cached, depth) { if (compressor.option("unsafe")) { var val = {}; for (var i = 0, len = this.properties.length; i < len; i++) { @@ -2423,14 +2427,14 @@ merge(Compressor.prototype, { if (key instanceof AST_Symbol) { key = key.name; } else if (key instanceof AST_Node) { - key = key._eval(compressor, depth); + key = key._eval(compressor, cached, depth); if (key === prop.key) return this; } if (typeof Object.prototype[key] === 'function') { return this; } if (prop.value instanceof AST_Function) continue; - val[key] = prop.value._eval(compressor, depth); + val[key] = prop.value._eval(compressor, cached, depth); if (val[key] === prop.value) return this; } return val; @@ -2438,7 +2442,7 @@ merge(Compressor.prototype, { return this; }); var non_converting_unary = makePredicate("! typeof void"); - def(AST_UnaryPrefix, function(compressor, depth) { + def(AST_UnaryPrefix, function(compressor, cached, depth) { var e = this.expression; // Function would be evaluated to an array and so typeof would // incorrectly return 'object'. Hence making is a special case. @@ -2450,7 +2454,7 @@ merge(Compressor.prototype, { return typeof function(){}; } if (!non_converting_unary(this.operator)) depth++; - e = e._eval(compressor, depth); + e = e._eval(compressor, cached, depth); if (e === this.expression) return this; switch (this.operator) { case "!": return !e; @@ -2467,11 +2471,11 @@ merge(Compressor.prototype, { return this; }); var non_converting_binary = makePredicate("&& || === !=="); - def(AST_Binary, function(compressor, depth) { + def(AST_Binary, function(compressor, cached, depth) { if (!non_converting_binary(this.operator)) depth++; - var left = this.left._eval(compressor, depth); + var left = this.left._eval(compressor, cached, depth); if (left === this.left) return this; - var right = this.right._eval(compressor, depth); + var right = this.right._eval(compressor, cached, depth); if (right === this.right) return this; var result; switch (this.operator) { @@ -2505,27 +2509,28 @@ merge(Compressor.prototype, { } return result; }); - def(AST_Conditional, function(compressor, depth) { - var condition = this.condition._eval(compressor, depth); + def(AST_Conditional, function(compressor, cached, depth) { + var condition = this.condition._eval(compressor, cached, depth); if (condition === this.condition) return this; var node = condition ? this.consequent : this.alternative; - var value = node._eval(compressor, depth); + var value = node._eval(compressor, cached, depth); return value === node ? this : value; }); - def(AST_SymbolRef, function(compressor, depth) { + def(AST_SymbolRef, function(compressor, cached, depth) { var fixed = this.fixed_value(); if (!fixed) return this; var value; - if (HOP(fixed, "_eval")) { + if (cached.indexOf(fixed) >= 0) { value = fixed._eval(); } else { this._eval = return_this; - value = fixed._eval(compressor, depth); + value = fixed._eval(compressor, cached, depth); delete this._eval; if (value === fixed) return this; fixed._eval = function() { return value; }; + cached.push(fixed); } if (value && typeof value == "object") { var escaped = this.definition().escaped; @@ -2560,11 +2565,11 @@ merge(Compressor.prototype, { ], }; convert_to_predicate(static_values); - def(AST_PropAccess, function(compressor, depth) { + def(AST_PropAccess, function(compressor, cached, depth) { if (compressor.option("unsafe")) { var key = this.property; if (key instanceof AST_Node) { - key = key._eval(compressor, depth); + key = key._eval(compressor, cached, depth); if (key === this.property) return this; } var exp = this.expression; @@ -2573,7 +2578,7 @@ merge(Compressor.prototype, { if (!(static_values[exp.name] || return_false)(key)) return this; val = global_objs[exp.name]; } else { - val = exp._eval(compressor, depth + 1); + val = exp._eval(compressor, cached, depth + 1); if (!val || val === exp || !HOP(val, key)) return this; if (typeof val == "function") switch (key) { case "name": @@ -2588,12 +2593,12 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Call, function(compressor, depth) { + def(AST_Call, function(compressor, cached, depth) { var exp = this.expression; if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { var key = exp.property; if (key instanceof AST_Node) { - key = key._eval(compressor, depth); + key = key._eval(compressor, cached, depth); if (key === exp.property) return this; } var val; @@ -2602,13 +2607,13 @@ merge(Compressor.prototype, { if (!(static_fns[e.name] || return_false)(key)) return this; val = global_objs[e.name]; } else { - val = e._eval(compressor, depth + 1); + val = e._eval(compressor, cached, depth + 1); if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this; } var args = []; for (var i = 0, len = this.args.length; i < len; i++) { var arg = this.args[i]; - var value = arg._eval(compressor, depth); + var value = arg._eval(compressor, cached, depth); if (arg === value) return this; args.push(value); } diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 614020ea..0547e6d9 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1535,3 +1535,34 @@ issue_2926_2: { } expect_stdout: "function" } + +issue_2968: { + options = { + collapse_vars: true, + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + var c = "FAIL"; + (function() { + (function(a, b) { + a <<= 0; + a && (a[(c = "PASS", 0 >>> (b += 1))] = 0); + })(42, -42); + })(); + console.log(c); + } + expect: { + var c = "FAIL"; + (function() { + b = -(a = 42), + void ((a <<= 0) && (a[(c = "PASS", 0 >>> (b += 1))] = 0)); + var a, b; + })(); + console.log(c); + } + expect_stdout: "PASS" +} -- 2.34.1