fix corner case in call binding (#3128)
authorAlex Lam S.L <alexlamsl@gmail.com>
Wed, 9 May 2018 22:16:35 +0000 (06:16 +0800)
committerGitHub <noreply@github.com>
Wed, 9 May 2018 22:16:35 +0000 (06:16 +0800)
fixes #3127

lib/compress.js
test/compress/issue-782.js
test/compress/issue-973.js

index 8c565c2..d3c2452 100644 (file)
@@ -919,15 +919,21 @@ merge(Compressor.prototype, {
                 type: typeof val
             }));
         }
-    };
+    }
+
+    function needs_unbinding(compressor, val) {
+        return val instanceof AST_PropAccess
+            || compressor.has_directive("use strict")
+                && is_undeclared_ref(val)
+                && val.name == "eval";
+    }
 
     // we shouldn't compress (1,func)(something) to
     // func(something) because that changes the meaning of
     // the func (becomes lexical instead of global).
-    function maintain_this_binding(parent, orig, val) {
+    function maintain_this_binding(compressor, parent, orig, val) {
         if (parent instanceof AST_UnaryPrefix && parent.operator == "delete"
-            || parent.TYPE == "Call" && parent.expression === orig
-                && (val instanceof AST_PropAccess || val instanceof AST_SymbolRef && val.name == "eval")) {
+            || parent.TYPE == "Call" && parent.expression === orig && needs_unbinding(compressor, val)) {
             return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]);
         }
         return val;
@@ -1098,7 +1104,7 @@ merge(Compressor.prototype, {
                         var def = candidate.name.definition();
                         if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
                             def.replaced++;
-                            return maintain_this_binding(parent, node, candidate.value);
+                            return maintain_this_binding(compressor, parent, node, candidate.value);
                         }
                         return make_node(AST_Assign, candidate, {
                             operator: "=",
@@ -3346,7 +3352,7 @@ merge(Compressor.prototype, {
                         }
                         if (value) {
                             props.push(value);
-                            return maintain_this_binding(parent, node, make_sequence(node, props.map(function(prop) {
+                            return maintain_this_binding(compressor, parent, node, make_sequence(node, props.map(function(prop) {
                                 return prop.transform(tt);
                             })));
                         }
@@ -4477,7 +4483,7 @@ merge(Compressor.prototype, {
         var exp = this.expression;
         if (!(exp instanceof AST_Sequence)) return this;
         var tail = exp.tail_node();
-        if (tail instanceof AST_PropAccess && !(this instanceof AST_New)) return this;
+        if (needs_unbinding(compressor, tail) && !(this instanceof AST_New)) return this;
         var expressions = exp.expressions.slice(0, -1);
         var node = this.clone();
         node.expression = tail;
@@ -5028,7 +5034,7 @@ merge(Compressor.prototype, {
         var end = expressions.length - 1;
         trim_right_for_undefined();
         if (end == 0) {
-            self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
+            self = maintain_this_binding(compressor, compressor.parent(), compressor.self(), expressions[0]);
             if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
             return self;
         }
@@ -5347,7 +5353,7 @@ merge(Compressor.prototype, {
                 var ll = fuzzy_eval(self.left);
                 if (!ll) {
                     compressor.warn("Condition left of && always false [{file}:{line},{col}]", self.start);
-                    return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
+                    return maintain_this_binding(compressor, compressor.parent(), compressor.self(), self.left).optimize(compressor);
                 } else if (!(ll instanceof AST_Node)) {
                     compressor.warn("Condition left of && always true [{file}:{line},{col}]", self.start);
                     return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
@@ -5386,7 +5392,7 @@ merge(Compressor.prototype, {
                     return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
                 } else if (!(ll instanceof AST_Node)) {
                     compressor.warn("Condition left of || always true [{file}:{line},{col}]", self.start);
-                    return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
+                    return maintain_this_binding(compressor, compressor.parent(), compressor.self(), self.left).optimize(compressor);
                 }
                 var rr = self.right.evaluate(compressor);
                 if (!rr) {
index 2f72d1a..6e1c92f 100644 (file)
@@ -1,11 +1,31 @@
+remove_sequence: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        (0, 1, eval)();
+        (0, 1, logThis)();
+        (0, 1, _decorators.logThis)();
+    }
+    expect: {
+        eval();
+        logThis();
+        (0, _decorators.logThis)();
+    }
+}
+
 remove_redundant_sequence_items: {
-    options = { side_effects: true };
+    options = {
+        side_effects: true,
+    }
     input: {
+        "use strict";
         (0, 1, eval)();
         (0, 1, logThis)();
         (0, 1, _decorators.logThis)();
     }
     expect: {
+        "use strict";
         (0, eval)();
         logThis();
         (0, _decorators.logThis)();
@@ -13,13 +33,17 @@ remove_redundant_sequence_items: {
 }
 
 dont_remove_this_binding_sequence: {
-    options = { side_effects: true };
+    options = {
+        side_effects: true,
+    }
     input: {
+        "use strict";
         (0, eval)();
         (0, logThis)();
         (0, _decorators.logThis)();
     }
     expect: {
+        "use strict";
         (0, eval)();
         logThis();
         (0, _decorators.logThis)();
index a9fcd84..3eb25fa 100644 (file)
@@ -3,8 +3,9 @@ this_binding_conditionals: {
         conditionals: true,
         evaluate: true,
         side_effects: true,
-    };
+    }
     input: {
+        "use strict";
         (1 && a)();
         (0 || a)();
         (0 || 1 && a)();
@@ -26,6 +27,7 @@ this_binding_conditionals: {
         (1 ? eval : 0)();
     }
     expect: {
+        "use strict";
         a();
         a();
         a();
@@ -53,13 +55,15 @@ this_binding_collapse_vars: {
         collapse_vars: true,
         toplevel: true,
         unused: true,
-    };
+    }
     input: {
+        "use strict";
         var c = a; c();
         var d = a.b; d();
         var e = eval; e();
     }
     expect: {
+        "use strict";
         a();
         (0, a.b)();
         (0, eval)();
@@ -69,31 +73,88 @@ this_binding_collapse_vars: {
 this_binding_side_effects: {
     options = {
         side_effects : true
-    };
+    }
     input: {
-        (function (foo) {
+        (function(foo) {
+            (0, foo)();
+            (0, foo.bar)();
+            (0, eval)("console.log(foo);");
+        }());
+        (function(foo) {
+            "use strict";
             (0, foo)();
             (0, foo.bar)();
-            (0, eval)('console.log(foo);');
+            (0, eval)("console.log(foo);");
         }());
-        (function (foo) {
+        (function(foo) {
             var eval = console;
             (0, foo)();
             (0, foo.bar)();
-            (0, eval)('console.log(foo);');
+            (0, eval)("console.log(foo);");
         }());
     }
     expect: {
-        (function (foo) {
+        (function(foo) {
             foo();
             (0, foo.bar)();
-            (0, eval)('console.log(foo);');
+            eval("console.log(foo);");
         }());
-        (function (foo) {
+        (function(foo) {
+            "use strict";
+            foo();
+            (0, foo.bar)();
+            (0, eval)("console.log(foo);");
+        }());
+        (function(foo) {
             var eval = console;
             foo();
             (0, foo.bar)();
-            (0, eval)('console.log(foo);');
+            eval("console.log(foo);");
+        }());
+    }
+}
+
+this_binding_sequences: {
+    options = {
+        sequences: true,
+        side_effects: true,
+    }
+    input: {
+        console.log(typeof function() {
+            return eval("this");
+        }());
+        console.log(typeof function() {
+            "use strict";
+            return eval("this");
+        }());
+        console.log(typeof function() {
+            return (0, eval)("this");
+        }());
+        console.log(typeof function() {
+            "use strict";
+            return (0, eval)("this");
+        }());
+    }
+    expect: {
+        console.log(typeof function() {
+            return eval("this");
+        }()),
+        console.log(typeof function() {
+            "use strict";
+            return eval("this");
+        }()),
+        console.log(typeof function() {
+            return eval("this");
+        }()),
+        console.log(typeof function() {
+            "use strict";
+            return (0, eval)("this");
         }());
     }
-}
\ No newline at end of file
+    expect_stdout: [
+        "object",
+        "undefined",
+        "object",
+        "object",
+    ]
+}