Repository: swc-project/swc (rust)

Issue:
Infinite loop detected during minification
### Describe the bug

Given the input code and options provided, minification results in an infinite loop.  The problem was originally found in an old version of jQueryUI, and reduced to the minimal version reported here.

To repro, run `npx swc -C minify=true -C jsc.minify.compress=true ./source.js` with the Input code provided. You'll get a panic.

### Input code

```typescript
foo({
  bar: function bar(data, baz) {
    if (
      !(baz ? data.quxA : data.quxB) &&
      !(baz ? data.corgeA : data.corgeB) &&
      (baz ? data.get("waldo") : data.waldo)
    ) {
      pass();
    } else if (
      !(baz ? data.quxA : data.quxB) &&
      !(baz ? data.get("waldo") : data.waldo) &&
      (baz ? data.corgeA : data.corgeB)
    ) {
      pass();
    }
  }
});
```


### Config

_No response_

### Playground link

https://play.swc.rs/?version=1.2.157&code=H4sIAAAAAAAAA0vLz9eo5lJQSEosslJIK81LLsnMzwPxNFISSxJ1gKwqTQWQAgjITFPQgHMUNYCyCvYKIJV6haUVjgpWcLaTpoKaGnaVyflF6alwtWAeqmpkxempJRpK5Yk5KflKmjAtYK4mXDmy%2BwoSi4s1NK3h%2FFqF1JziVKo4G7dLcLkdq0eJczYXCNcCRQBlxEmQoAEAAA%3D%3D&config=H4sIAAAAAAAAA22TS5LjIAyG7%2BJ1tj2LPsDs%2BgyUAsJRxoALiXRcqdy9he3GdjI79OmX0AMe3ZVt9%2FnoRsiMuZ54igL37rNDG4BtplG6k8oUeRgYn6dOIPcoVcIf6htSYly9py5QJD%2FVTDaFMSNzPWtECRiFmw5yTt9qSi5qnVMaEOKbbYANRcEe8xZq0zDAyGhukFtEvQ0ycYoHVASdGXMa9%2BHRkVCKajboEJyxyW2NOMpohW64E2keFUXW4o7Q4bn0fZ3gcjXeYCgg2Oz7PAq9tMVdErEYXyK%2FoLXaJXBBS6OrirzJKCXHX801UTzM4h%2Bi1jkAc4Swq3%2FmXndxVPr%2FqCh6XaRMjeqWt6oi9tqcIfKtw1o1ZiFsooyuWKwd2i35Cg8NMTk06L2OuwXzN4m97KqSacTkm1%2FHBn5bwmKa30d1pPUdvMG%2FWpbs97HyAHJ5ZTyFcxreUgSUS3JvWCch6RVmfcL38ZWW6FDnjG7nKFzN2qV%2BtQCxb2%2FtWUFypYLHPJD5D%2F7pntu3W6ZD%2FLXq5jw%2FTxUnkucDAAA%3D

### Expected behavior

Minified code.

### Actual behavior

The CLI panics with the following message:

```ERROR Seems like there's an infinite loop
thread '<unnamed>' panicked at 'Infinite loop detected (current pass = 203)
Code    0:









foo({
    bar: function(data, baz) {
        !(baz ? data.quxA : data.quxB) && !(baz ? data.corgeA : data.corgeB) && (baz ? data.get("waldo") : data.waldo) ? pass() : (baz ? data.quxA : data.quxB) || (baz ? data.get("waldo") : data.waldo) || (baz ? !data.corgeA : !data.corgeB) || pass();
    }
});

Code    1:









foo({
    bar: function(data, baz) {
        !(!(baz ? data.quxA : data.quxB) && !(baz ? data.corgeA : data.corgeB) && (baz ? data.get("waldo") : data.waldo)) ? (baz ? data.quxA : data.quxB) || (baz ? data.get("waldo") : data.waldo) || (baz ? !data.corgeA : !data.corgeB) || pass() : pass();
    }
});

'
```

### Version

@swc/cli: 0.1.57, @swc/core: 1.2.163

### Additional context

_No response_

Pull request:
fix(es/minifier): Fix infinite loops
**Description:**


**Related issue (if exists):**


 - Closes #4249

Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs b/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs
index f899e03ed6..f9a549c5a5 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/conditionals.rs
@@ -34,7 +34,6 @@ where
         }
 
         if negate_cost(&stmt.test, true, false) < 0 {
-            self.changed = true;
             tracing::debug!("if_return: Negating `cond` of an if statement which has cons and alt");
             let ctx = Ctx {
                 in_bool_ctx: true,
diff --git a/crates/swc_ecma_minifier/src/compress/pure/bools.rs b/crates/swc_ecma_minifier/src/compress/pure/bools.rs
index 3d74890cca..42cdfc07e1 100644
--- a/crates/swc_ecma_minifier/src/compress/pure/bools.rs
+++ b/crates/swc_ecma_minifier/src/compress/pure/bools.rs
@@ -48,7 +48,6 @@ impl Pure<'_> {
                         return;
                     }
                     tracing::debug!("bools: Optimizing `!(a && b)` as `!a || !b`");
-                    self.changed = true;
                     self.negate(arg, false, false);
                     *e = *arg.take();
                 }
diff --git a/crates/swc_ecma_minifier/src/compress/pure/conds.rs b/crates/swc_ecma_minifier/src/compress/pure/conds.rs
index 4380fa3dbf..869391fcbd 100644
--- a/crates/swc_ecma_minifier/src/compress/pure/conds.rs
+++ b/crates/swc_ecma_minifier/src/compress/pure/conds.rs
@@ -129,7 +129,6 @@ impl Pure<'_> {
             return;
         }
 
-        self.changed = true;
         tracing::debug!("conditionals: `a ? foo : bar` => `!a ? bar : foo` (considered cost)");
         let start_str = dump(&*cond, false);
 


Test patch:
diff --git a/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/input/.swcrc b/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/input/.swcrc
new file mode 100644
index 0000000000..99f00012f5
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/input/.swcrc
@@ -0,0 +1,7 @@
+{
+    "jsc": {
+        "minify": {
+            "compress": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/input/index.js b/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/input/index.js
new file mode 100644
index 0000000000..1c0a1278b5
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/input/index.js
@@ -0,0 +1,17 @@
+foo({
+    bar: function bar(data, baz) {
+        if (
+            !(baz ? data.quxA : data.quxB) &&
+            !(baz ? data.corgeA : data.corgeB) &&
+            (baz ? data.get("waldo") : data.waldo)
+        ) {
+            pass();
+        } else if (
+            !(baz ? data.quxA : data.quxB) &&
+            !(baz ? data.get("waldo") : data.waldo) &&
+            (baz ? data.corgeA : data.corgeB)
+        ) {
+            pass();
+        }
+    }
+});
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/output/index.js b/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/output/index.js
new file mode 100644
index 0000000000..50406c0e82
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-4xxx/issue-4249/1/output/index.js
@@ -0,0 +1,5 @@
+foo({
+    bar: function(data, baz) {
+        !(!(baz ? data.quxA : data.quxB) && !(baz ? data.corgeA : data.corgeB) && (baz ? data.get("waldo") : data.waldo)) ? (baz ? data.quxA : data.quxB) || (baz ? data.get("waldo") : data.waldo) || (baz ? !data.corgeA : !data.corgeB) || pass() : pass();
+    }
+});
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/analysis-snapshot.rust-debug b/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/analysis-snapshot.rust-debug
new file mode 100644
index 0000000000..ffe9d67598
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/analysis-snapshot.rust-debug
@@ -0,0 +1,194 @@
+TestSnapshot {
+    vars: [
+        (
+            (
+                Atom('bar' type=inline),
+                #2,
+            ),
+            VarUsageInfo {
+                inline_prevented: false,
+                ref_count: 0,
+                cond_init: false,
+                declared: false,
+                declared_count: 0,
+                declared_as_fn_param: false,
+                declared_as_fn_expr: true,
+                assign_count: 0,
+                mutation_by_call_count: 0,
+                usage_count: 0,
+                reassigned_with_assignment: false,
+                reassigned_with_var_decl: false,
+                mutated: false,
+                has_property_access: false,
+                has_property_mutation: false,
+                accessed_props: {},
+                exported: false,
+                used_above_decl: false,
+                is_fn_local: false,
+                used_by_nested_fn: false,
+                executed_multiple_time: false,
+                used_in_cond: false,
+                var_kind: None,
+                var_initialized: false,
+                declared_as_catch_param: false,
+                no_side_effect_for_member_access: false,
+                used_as_callee: false,
+                used_as_arg: false,
+                pure_fn: false,
+                infects: [],
+            },
+        ),
+        (
+            (
+                Atom('baz' type=inline),
+                #2,
+            ),
+            VarUsageInfo {
+                inline_prevented: false,
+                ref_count: 6,
+                cond_init: false,
+                declared: true,
+                declared_count: 1,
+                declared_as_fn_param: true,
+                declared_as_fn_expr: false,
+                assign_count: 0,
+                mutation_by_call_count: 6,
+                usage_count: 6,
+                reassigned_with_assignment: false,
+                reassigned_with_var_decl: false,
+                mutated: true,
+                has_property_access: false,
+                has_property_mutation: false,
+                accessed_props: {},
+                exported: false,
+                used_above_decl: false,
+                is_fn_local: true,
+                used_by_nested_fn: true,
+                executed_multiple_time: false,
+                used_in_cond: true,
+                var_kind: None,
+                var_initialized: false,
+                declared_as_catch_param: false,
+                no_side_effect_for_member_access: false,
+                used_as_callee: false,
+                used_as_arg: false,
+                pure_fn: false,
+                infects: [],
+            },
+        ),
+        (
+            (
+                Atom('data' type=inline),
+                #2,
+            ),
+            VarUsageInfo {
+                inline_prevented: false,
+                ref_count: 12,
+                cond_init: false,
+                declared: true,
+                declared_count: 1,
+                declared_as_fn_param: true,
+                declared_as_fn_expr: false,
+                assign_count: 0,
+                mutation_by_call_count: 0,
+                usage_count: 12,
+                reassigned_with_assignment: false,
+                reassigned_with_var_decl: false,
+                mutated: false,
+                has_property_access: true,
+                has_property_mutation: false,
+                accessed_props: {},
+                exported: false,
+                used_above_decl: false,
+                is_fn_local: true,
+                used_by_nested_fn: true,
+                executed_multiple_time: false,
+                used_in_cond: true,
+                var_kind: None,
+                var_initialized: false,
+                declared_as_catch_param: false,
+                no_side_effect_for_member_access: false,
+                used_as_callee: false,
+                used_as_arg: false,
+                pure_fn: false,
+                infects: [],
+            },
+        ),
+        (
+            (
+                Atom('foo' type=inline),
+                #1,
+            ),
+            VarUsageInfo {
+                inline_prevented: false,
+                ref_count: 1,
+                cond_init: false,
+                declared: false,
+                declared_count: 0,
+                declared_as_fn_param: false,
+                declared_as_fn_expr: false,
+                assign_count: 0,
+                mutation_by_call_count: 0,
+                usage_count: 1,
+                reassigned_with_assignment: false,
+                reassigned_with_var_decl: false,
+                mutated: false,
+                has_property_access: false,
+                has_property_mutation: false,
+                accessed_props: {},
+                exported: false,
+                used_above_decl: true,
+                is_fn_local: true,
+                used_by_nested_fn: false,
+                executed_multiple_time: false,
+                used_in_cond: false,
+                var_kind: None,
+                var_initialized: false,
+                declared_as_catch_param: false,
+                no_side_effect_for_member_access: false,
+                used_as_callee: true,
+                used_as_arg: false,
+                pure_fn: false,
+                infects: [],
+            },
+        ),
+        (
+            (
+                Atom('pass' type=inline),
+                #1,
+            ),
+            VarUsageInfo {
+                inline_prevented: false,
+                ref_count: 2,
+                cond_init: false,
+                declared: false,
+                declared_count: 0,
+                declared_as_fn_param: false,
+                declared_as_fn_expr: false,
+                assign_count: 0,
+                mutation_by_call_count: 2,
+                usage_count: 2,
+                reassigned_with_assignment: false,
+                reassigned_with_var_decl: false,
+                mutated: true,
+                has_property_access: false,
+                has_property_mutation: false,
+                accessed_props: {},
+                exported: false,
+                used_above_decl: true,
+                is_fn_local: true,
+                used_by_nested_fn: true,
+                executed_multiple_time: false,
+                used_in_cond: true,
+                var_kind: None,
+                var_initialized: false,
+                declared_as_catch_param: false,
+                no_side_effect_for_member_access: false,
+                used_as_callee: true,
+                used_as_arg: false,
+                pure_fn: false,
+                infects: [],
+            },
+        ),
+    ],
+}
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/input.js b/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/input.js
new file mode 100644
index 0000000000..1c0a1278b5
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/input.js
@@ -0,0 +1,17 @@
+foo({
+    bar: function bar(data, baz) {
+        if (
+            !(baz ? data.quxA : data.quxB) &&
+            !(baz ? data.corgeA : data.corgeB) &&
+            (baz ? data.get("waldo") : data.waldo)
+        ) {
+            pass();
+        } else if (
+            !(baz ? data.quxA : data.quxB) &&
+            !(baz ? data.get("waldo") : data.waldo) &&
+            (baz ? data.corgeA : data.corgeB)
+        ) {
+            pass();
+        }
+    }
+});
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/output.js b/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/output.js
new file mode 100644
index 0000000000..50406c0e82
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/4249/output.js
@@ -0,0 +1,5 @@
+foo({
+    bar: function(data, baz) {
+        !(!(baz ? data.quxA : data.quxB) && !(baz ? data.corgeA : data.corgeB) && (baz ? data.get("waldo") : data.waldo)) ? (baz ? data.quxA : data.quxB) || (baz ? data.get("waldo") : data.waldo) || (baz ? !data.corgeA : !data.corgeB) || pass() : pass();
+    }
+});