fix corner cases related to `in` (#3964)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 7 Jun 2020 21:23:23 +0000 (22:23 +0100)
committerGitHub <noreply@github.com>
Sun, 7 Jun 2020 21:23:23 +0000 (05:23 +0800)
lib/compress.js
lib/output.js
test/compress/collapse_vars.js
test/compress/pure_funcs.js
test/compress/side_effects.js
test/mocha/operator.js
test/ufuzz/index.js

index 749abef..c8c08a0 100644 (file)
@@ -1464,6 +1464,7 @@ merge(Compressor.prototype, {
             }
 
             function is_last_node(node, parent) {
+                if (node.TYPE == "Binary") return node.operator == "in" && !is_object(node.right.tail_node());
                 if (node instanceof AST_Call) {
                     var fn = node.expression;
                     if (fn instanceof AST_SymbolRef) {
@@ -3824,7 +3825,8 @@ merge(Compressor.prototype, {
         def(AST_Assign, return_true);
         def(AST_Binary, function(compressor) {
             return this.left.has_side_effects(compressor)
-                || this.right.has_side_effects(compressor);
+                || this.right.has_side_effects(compressor)
+                || this.operator == "in" && !is_object(this.right.tail_node());
         });
         def(AST_Block, function(compressor) {
             return any(this.body, compressor);
@@ -5131,6 +5133,15 @@ merge(Compressor.prototype, {
             return this;
         });
         def(AST_Binary, function(compressor, first_in_statement) {
+            if (this.operator == "in" && !is_object(this.right.tail_node())) {
+                var left = this.left.drop_side_effect_free(compressor, first_in_statement);
+                if (left === this.left) return this;
+                var node = this.clone();
+                node.left = left || make_node(AST_Number, this.left, {
+                    value: 0
+                });
+                return node;
+            }
             var right = this.right.drop_side_effect_free(compressor, first_in_statement);
             if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement);
             if (lazy_op[this.operator] && !(right instanceof AST_Function)) {
@@ -6879,8 +6890,14 @@ merge(Compressor.prototype, {
     var indexFns = makePredicate("indexOf lastIndexOf");
     var commutativeOperators = makePredicate("== === != !== * & | ^");
     function is_object(node) {
+        while (node instanceof AST_SymbolRef) {
+            node = node.fixed_value();
+            if (!node) return false;
+            node = node.tail_node();
+        }
         return node instanceof AST_Array
             || node instanceof AST_Lambda
+            || node instanceof AST_New
             || node instanceof AST_Object;
     }
 
@@ -6980,7 +6997,7 @@ merge(Compressor.prototype, {
             else if (self.left instanceof AST_SymbolRef
                 && self.right instanceof AST_SymbolRef
                 && self.left.definition() === self.right.definition()
-                && is_object(self.left.fixed_value())) {
+                && is_object(self.left)) {
                 return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
             }
             break;
index 62050b4..a9e0ca7 100644 (file)
@@ -305,6 +305,7 @@ function OutputStream(options) {
                 || (ch == "/" && ch == prev)
                 || ((ch == "+" || ch == "-") && ch == last)
                 || str == "--" && last == "!"
+                || str == "in" && prev == "/"
                 || last == "--" && ch == ">") {
                 OUTPUT += " ";
                 current_col++;
index 0c232aa..f99e012 100644 (file)
@@ -8164,3 +8164,34 @@ issue_3927: {
     }
     expect_stdout: "PASS"
 }
+
+operator_in: {
+    options = {
+        collapse_vars: true,
+    }
+    input: {
+        function log(msg) {
+            console.log(msg);
+        }
+        var a = "FAIL";
+        try {
+            a = "PASS";
+            0 in null;
+            log("FAIL", a);
+        } catch (e) {}
+        log(a);
+    }
+    expect: {
+        function log(msg) {
+            console.log(msg);
+        }
+        var a = "FAIL";
+        try {
+            a = "PASS";
+            0 in null;
+            log("FAIL", a);
+        } catch (e) {}
+        log(a);
+    }
+    expect_stdout: "PASS"
+}
index d65399f..09f0bd4 100644 (file)
@@ -136,7 +136,7 @@ relational: {
         side_effects :true,
     }
     input: {
-        foo() in foo();
+        foo() in new foo();
         foo() instanceof bar();
         foo() < "bar";
         bar() > foo();
index 6328266..b7ffe47 100644 (file)
@@ -274,3 +274,26 @@ drop_value: {
         foo(), bar();
     }
 }
+
+operator_in: {
+    options = {
+        side_effects: true,
+    }
+    input: {
+        try {
+            "foo" in true;
+            console.log("FAIL");
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect: {
+        try {
+            0 in true;
+            console.log("FAIL");
+        } catch (e) {
+            console.log("PASS");
+        }
+    }
+    expect_stdout: "PASS"
+}
index d36d3a1..8dfea54 100644 (file)
@@ -486,4 +486,19 @@ describe("operator", function() {
             assert.strictEqual(UglifyJS.parse(exp[0]).print_to_string(), exp[1] + ";");
         });
     });
+    it("Should preserve space between /regex/ and `in`", function() {
+        [
+            "/regex/ in {}",
+            "/regex/g in {}",
+            "0 + /regex/ in {}",
+            "0 + /regex/g in {}",
+        ].forEach(function(exp) {
+            var code = UglifyJS.parse(exp).print_to_string();
+            try {
+                assert.strictEqual(UglifyJS.parse(code).print_to_string(), code);
+            } catch (ex) {
+                assert.fail("Failed to reparse: " + exp + "\n" + ex);
+            }
+        });
+    });
 });
index edd2ab8..eec4ab4 100644 (file)
@@ -168,7 +168,7 @@ var VALUES = [
     "this",
 ];
 
-var BINARY_OPS_NO_COMMA = [
+var BINARY_OPS = [
     " + ", // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)
     " - ",
     "/",
@@ -190,9 +190,14 @@ var BINARY_OPS_NO_COMMA = [
     "%",
     "&&",
     "||",
-    "^" ];
-
-var BINARY_OPS = [","].concat(BINARY_OPS_NO_COMMA);
+    "^",
+    ",",
+];
+BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
+BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
+BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
+BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
+BINARY_OPS.push(" in ");
 
 var ASSIGNMENTS = [
     "=",
@@ -879,7 +884,7 @@ function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
 }
 function _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
     return "(" + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow)
-        + createBinaryOp(noComma) + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
+        + createBinaryOp(noComma, canThrow) + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
 }
 function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
     // intentionally generate more hardcore ops
@@ -929,9 +934,12 @@ function createValue() {
     return VALUES[rng(VALUES.length)];
 }
 
-function createBinaryOp(noComma) {
-    if (noComma) return BINARY_OPS_NO_COMMA[rng(BINARY_OPS_NO_COMMA.length)];
-    return BINARY_OPS[rng(BINARY_OPS.length)];
+function createBinaryOp(noComma, canThrow) {
+    var op;
+    do {
+        op = BINARY_OPS[rng(BINARY_OPS.length)];
+    } while (noComma && op == "," || !canThrow && op == " in ");
+    return op;
 }
 
 function createAssignment() {