fix corner case in `sequences` (#4080)
authorAlex Lam S.L <alexlamsl@gmail.com>
Wed, 26 Aug 2020 12:41:11 +0000 (13:41 +0100)
committerGitHub <noreply@github.com>
Wed, 26 Aug 2020 12:41:11 +0000 (20:41 +0800)
fixes #4079

lib/compress.js
test/compress/sequences.js

index cd790db..90dd0f2 100644 (file)
@@ -1094,11 +1094,14 @@ merge(Compressor.prototype, {
     // func(something) because that changes the meaning of
     // the func (becomes lexical instead of global).
     function maintain_this_binding(compressor, parent, orig, val) {
-        if (parent instanceof AST_UnaryPrefix && parent.operator == "delete"
-            || parent.TYPE == "Call" && parent.expression === orig && needs_unbinding(compressor, val)) {
-            return make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]);
-        }
-        return val;
+        var wrap = false;
+        if (parent.TYPE == "Call") {
+            wrap = parent.expression === orig && needs_unbinding(compressor, val);
+        } else if (parent instanceof AST_UnaryPrefix) {
+            wrap = parent.operator == "delete"
+                || parent.operator == "typeof" && is_undeclared_ref(val);
+        }
+        return wrap ? make_sequence(orig, [ make_node(AST_Number, orig, { value: 0 }), val ]) : val;
     }
 
     function merge_sequence(array, node) {
@@ -6905,72 +6908,70 @@ merge(Compressor.prototype, {
     var SIGN_OPS = makePredicate("+ -");
     var MULTIPLICATIVE_OPS = makePredicate("* / %");
     OPT(AST_UnaryPrefix, function(self, compressor) {
-        var e = self.expression;
+        var op = self.operator;
+        var exp = self.expression;
         if (compressor.option("evaluate")
-            && self.operator == "delete"
-            && !(e instanceof AST_SymbolRef
-                || e instanceof AST_PropAccess
-                || is_identifier_atom(e))) {
-            if (e instanceof AST_Sequence) {
-                e = e.expressions.slice();
-                e.push(make_node(AST_True, self));
-                return make_sequence(self, e).optimize(compressor);
-            }
-            return make_sequence(self, [ e, make_node(AST_True, self) ]).optimize(compressor);
-        }
-        if (compressor.option("sequences")) {
+            && op == "delete"
+            && !(exp instanceof AST_SymbolRef
+                || exp instanceof AST_PropAccess
+                || is_identifier_atom(exp))) {
+            if (exp instanceof AST_Sequence) {
+                exp = exp.expressions.slice();
+                exp.push(make_node(AST_True, self));
+                return make_sequence(self, exp).optimize(compressor);
+            }
+            return make_sequence(self, [ exp, make_node(AST_True, self) ]).optimize(compressor);
+        }
+        if (compressor.option("sequences") && !(op == "typeof" && is_undeclared_ref(exp.tail_node()))) {
             var seq = lift_sequence_in_expression(self, compressor);
             if (seq !== self) return seq.optimize(compressor);
         }
-        if (compressor.option("side_effects") && self.operator == "void") {
-            e = e.drop_side_effect_free(compressor);
-            if (e) {
-                self.expression = e;
-                return self;
-            } else {
-                return make_node(AST_Undefined, self).optimize(compressor);
-            }
+        if (compressor.option("side_effects") && op == "void") {
+            exp = exp.drop_side_effect_free(compressor);
+            if (!exp) return make_node(AST_Undefined, self).optimize(compressor);
+            self.expression = exp;
+            return self;
         }
         if (compressor.option("booleans")) {
-            if (self.operator == "!" && e.is_truthy()) {
-                return make_sequence(self, [ e, make_node(AST_False, self) ]).optimize(compressor);
-            } else if (compressor.in_boolean_context()) switch (self.operator) {
+            if (op == "!" && exp.is_truthy()) {
+                return make_sequence(self, [ exp, make_node(AST_False, self) ]).optimize(compressor);
+            } else if (compressor.in_boolean_context()) switch (op) {
               case "!":
-                if (e instanceof AST_UnaryPrefix && e.operator == "!") {
+                if (exp instanceof AST_UnaryPrefix && exp.operator == "!") {
                     // !!foo => foo, if we're in boolean context
-                    return e.expression;
+                    return exp.expression;
                 }
-                if (e instanceof AST_Binary) {
-                    self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
+                if (exp instanceof AST_Binary) {
+                    self = best_of(compressor, self, exp.negate(compressor, first_in_statement(compressor)));
                 }
                 break;
               case "typeof":
                 // typeof always returns a non-empty string, thus it's
                 // always true in booleans
                 AST_Node.warn("Boolean expression always true [{file}:{line},{col}]", self.start);
-                return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
-                    e,
+                return (exp instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
+                    exp,
                     make_node(AST_True, self)
                 ])).optimize(compressor);
             }
         }
-        if (self.operator == "-" && e instanceof AST_Infinity) e = e.transform(compressor);
+        if (op == "-" && exp instanceof AST_Infinity) exp = exp.transform(compressor);
         if (compressor.option("evaluate")
-            && e instanceof AST_Binary
-            && SIGN_OPS[self.operator]
-            && MULTIPLICATIVE_OPS[e.operator]
-            && (e.left.is_constant() || !e.right.has_side_effects(compressor))) {
+            && exp instanceof AST_Binary
+            && SIGN_OPS[op]
+            && MULTIPLICATIVE_OPS[exp.operator]
+            && (exp.left.is_constant() || !exp.right.has_side_effects(compressor))) {
             return make_node(AST_Binary, self, {
-                operator: e.operator,
-                left: make_node(AST_UnaryPrefix, e.left, {
-                    operator: self.operator,
-                    expression: e.left
+                operator: exp.operator,
+                left: make_node(AST_UnaryPrefix, exp.left, {
+                    operator: op,
+                    expression: exp.left
                 }),
-                right: e.right
+                right: exp.right
             });
         }
         // avoids infinite recursion of numerals
-        return self.operator == "-" && (e instanceof AST_Number || e instanceof AST_Infinity)
+        return op == "-" && (exp instanceof AST_Number || exp instanceof AST_Infinity)
             ? self : try_evaluate(compressor, self);
     });
 
index 32db63d..b819036 100644 (file)
@@ -1155,3 +1155,25 @@ issue_3703: {
     }
     expect_stdout: "PASS"
 }
+
+issue_4079: {
+    options = {
+        sequences: true,
+        side_effects: true,
+    }
+    input: {
+        try {
+            typeof (0, A);
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            A;
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+}