fix value reference caching in `evaluate` (#2969)
authorAlex Lam S.L <alexlamsl@gmail.com>
Thu, 1 Mar 2018 20:04:29 +0000 (04:04 +0800)
committerGitHub <noreply@github.com>
Thu, 1 Mar 2018 20:04:29 +0000 (04:04 +0800)
fixes #2968

lib/compress.js
test/compress/evaluate.js

index 504d270..97e4c11 100644 (file)
@@ -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);
                 }
index 614020e..0547e6d 100644 (file)
@@ -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"
+}