Repository: swc-project/swc (rust)

Issue:
Unintended compressed with if_return flag
### Describe the bug

Assume that the Deferred contained in window.current is resolved outside the script.

```js
setTimeout(() => window.current.resolve('result'), 3000);
```

If normal, 'result' should appear when the logic below is executed.
```js
bug().then(alert)
```

However, minify with swc, the error below occurs.
```
Uncaught TypeError: Cannot read properties of undefined (reading 'resolve')
```
This is because the logic in the try statement changes to 'break' and window.current is immediately deleted in finally.

The problem is reproduced even when only the `if_return` option of compressor is enabled.
This problem does not occur with terser. 


### Input code

```typescript
function Deferred() {
  const deferred = this;
  deferred.promise = new Promise(function (resolve, reject) {
    deferred.resolve = resolve;
    deferred.reject = reject;
  });
}

export async function bug() {
    const s = `next`;
    if (!window[s]) {
        for (window[s] = new Deferred();;)
            if (window.current) await window.current.promise;
            else {
                window.current = window[s];
                try {
                    return await window[s].promise // This line compressed to 'break'. I guess compressor intended jump to 23 line which is looks like same code.
                } finally {
                    delete window.current // Above 'break' makes unintended delete
                }
            }
    }
    return await window[s].promise
}
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es2022",
    "loose": false,
    "minify": {
      "compress": {
        "arguments": false,
        "arrows": false,
        "booleans": false,
        "booleans_as_integers": false,
        "collapse_vars": false,
        "comparisons": false,
        "computed_props": false,
        "conditionals": false,
        "dead_code": false,
        "directives": false,
        "drop_console": false,
        "drop_debugger": false,
        "evaluate": false,
        "expression": false,
        "hoist_funs": false,
        "hoist_props": false,
        "hoist_vars": false,
        "if_return": true,
        "join_vars": false,
        "keep_classnames": false,
        "keep_fargs": false,
        "keep_fnames": false,
        "keep_infinity": false,
        "loops": false,
        "negate_iife": false,
        "properties": false,
        "reduce_funcs": false,
        "reduce_vars": false,
        "side_effects": false,
        "switches": false,
        "typeofs": false,
        "unsafe": false,
        "unsafe_arrows": false,
        "unsafe_comps": false,
        "unsafe_Function": false,
        "unsafe_math": false,
        "unsafe_symbols": false,
        "unsafe_methods": false,
        "unsafe_proto": false,
        "unsafe_regexp": false,
        "unsafe_undefined": false,
        "unused": false,
        "const_to_let": false,
        "pristine_globals": false,
        "passes": 3
      },
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.3.99&code=H4sIAAAAAAAAA31SPU%2FDMBDd8yuOqamEUgnGiAGJhY2BDSHVTS6tm%2BQc%2BYO0qvLfOePEkAbwENn37r17fk7lqLBSETxhhVpjma7hkgAUioyFcizCA9iDNDkDUynrtGqlQYYIe3gJp7Sa9FKNRjUfeAsaj1jYIPuDP%2BLMH3f5Ne5pX7DfeHRY58mQJHjqlLYgzJkKiAN3bp9OQ4J7w%2BQt4clug7SsIL3pJZWqfzPvU69fldKQRmS80ncieb6OrZNQ6M4Kxy3EtxO9kBbm1SmjfMbGhlO7zEp%2BzansIfrJF71Wn39R8EujdZpmblghPtZmA6%2F8kNBIQk6p7Th7w%2B9rFax2GkW9yuAZ9o6rEeZsJFmkkvuOru188919kOgPsjiAF1Sq9rI1ghGt1y4xWzgcoJIkmuYv9yU2aPE6Cjb9uFP8q4wWoRU1GnAUbQXeclyyPIXv%2FzElwyeFShVWFwMAAA%3D%3D&config=H4sIAAAAAAAAA32VTY7bMAyF9zlF4HUXRQrMogformcQFItylFqiIVKZGIPcfei%2FTNpS3sX8%2BMzokaI%2FDsdjc6W2%2BXn8kJ%2FyMNhMkJ%2FPEqExsb1LpIE2WmpzGLj5ttErTcjbnmAOPRbSsM0d8Kyi0%2FfTaVU0PSLBplhjMaTgx9eaLcYhA9FLTKLyyhIhMf2tX1nGdw2cEXuwaQ8ZSyYkhg6yltZi39uBwNxshUdxLRCqRSZaGJwZMg56QnKBAyaJK9iBdaZFBxoLGVoON1CFUk%2BEieSQNezgXLpu7ve%2FHG62L5Y1Kdzn5shfVuAFA7HxRTVjgTUnFlpxOXiTgUueanIur%2BiKIdVkfwDEhd4SJRtVn%2BYML7NVh7vSkLzML48Kl2FXD5qgE2dNCF6zd7IHMge1ZAZXWpj8bXdwxQwKDgx4L0Oj4vfA7UUty%2BMA6DUijbbqMRZgqhdz5dP92MG%2F5KCsT9qaES1f6pTGeEb1Ym1y4Au6nQRpB2MdZ1kb96HOS3IgAwJOTSmkgunasmE0%2FbxD%2F58QuSjyTtP1eNbXxiATP3fyxxp7PFd2tKn7WgnL1j6sCU1EV2a4fg%2Bmxi9b%2FK35StoW9rNqE%2Bj3ppxu5%2BHxCZo7moVbBgAA

### SWC Info output


    Operating System:
        Platform: darwin
        Arch: arm64
        Machine Type: arm64
        Version: Darwin Kernel Version 22.5.0: Thu Jun  8 22:22:23 PDT 2023; root:xnu-8796.121.3~7/RELEASE_ARM64_T6020
        CPU: (12 cores)
            Models: Apple M2 Max

    Binaries:
        Node: 18.15.0
        npm: 9.5.0
        Yarn: N/A
        pnpm: N/A

    Relevant Packages:
        @swc/core: 1.3.99
        @swc/helpers: N/A
        @swc/types: 0.1.5


    SWC Config:
        output: N/A
        .swcrc path: N/A

    Next.js info:
        output: N/A


### Expected behavior

The logic inside the try statement remains.

### Actual behavior

The logic inside the try statement is replaced with 'break'.

### Version

1.3.99

### Additional context

_No response_

Pull request:
fix(es/minifier): Fix `if_return` bug related to `await` and `yield`
**Description:**


**Related issue:**

 - Closes #8324

Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs b/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs
index a85eca749d..02f8f2e258 100644
--- a/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs
+++ b/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs
@@ -6,7 +6,11 @@ use swc_ecma_utils::{extract_var_ids, ExprExt, StmtExt, StmtLike, Value};
 use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
 
 use super::Pure;
-use crate::{compress::util::is_fine_for_if_cons, maybe_par, util::ModuleItemExt};
+use crate::{
+    compress::util::is_fine_for_if_cons,
+    maybe_par,
+    util::{contains_await_or_yield, ModuleItemExt},
+};
 
 /// Methods related to option `dead_code`.
 impl Pure<'_> {
@@ -285,6 +289,10 @@ impl Pure<'_> {
             _ => return,
         };
 
+        if contains_await_or_yield(last) {
+            return;
+        }
+
         fn drop<T: StmtLike>(stmt: &mut T, last: &Stmt, need_break: bool) -> bool {
             match stmt.as_stmt_mut() {
                 Some(s) if s.eq_ignore_span(last) => {
diff --git a/crates/swc_ecma_minifier/src/util/mod.rs b/crates/swc_ecma_minifier/src/util/mod.rs
index c0ee2ab082..1bdefc124b 100644
--- a/crates/swc_ecma_minifier/src/util/mod.rs
+++ b/crates/swc_ecma_minifier/src/util/mod.rs
@@ -211,6 +211,7 @@ where
 
 #[derive(Default)]
 pub(crate) struct LeapFinder {
+    found_await: bool,
     found_yield: bool,
     found_continue_with_label: bool,
     target_label: Option<Id>,
@@ -219,6 +220,12 @@ pub(crate) struct LeapFinder {
 impl Visit for LeapFinder {
     noop_visit_type!();
 
+    fn visit_await_expr(&mut self, n: &AwaitExpr) {
+        n.visit_children_with(self);
+
+        self.found_await = true;
+    }
+
     fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
 
     fn visit_class_method(&mut self, _: &ClassMethod) {}
@@ -249,6 +256,16 @@ impl Visit for LeapFinder {
     }
 }
 
+#[allow(unused)]
+pub(crate) fn contains_await_or_yield<N>(n: &N) -> bool
+where
+    N: VisitWith<LeapFinder>,
+{
+    let mut v = LeapFinder::default();
+    n.visit_with(&mut v);
+    v.found_yield || v.found_await
+}
+
 /// This method returns true only if `T` is `var`. (Not `const` or `let`)
 pub(crate) fn is_hoisted_var_decl_without_init<T>(t: &T) -> bool
 where


Test patch:
diff --git a/crates/swc/tests/fixture/issues-8xxx/8324/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8324/input/.swcrc
new file mode 100644
index 0000000000..c796db25cb
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8324/input/.swcrc
@@ -0,0 +1,65 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": false
+        },
+        "target": "es2022",
+        "loose": false,
+        "minify": {
+            "compress": {
+                "arguments": false,
+                "arrows": false,
+                "booleans": false,
+                "booleans_as_integers": false,
+                "collapse_vars": false,
+                "comparisons": false,
+                "computed_props": false,
+                "conditionals": false,
+                "dead_code": false,
+                "directives": false,
+                "drop_console": false,
+                "drop_debugger": false,
+                "evaluate": false,
+                "expression": false,
+                "hoist_funs": false,
+                "hoist_props": false,
+                "hoist_vars": false,
+                "if_return": true,
+                "join_vars": false,
+                "keep_classnames": false,
+                "keep_fargs": false,
+                "keep_fnames": false,
+                "keep_infinity": false,
+                "loops": false,
+                "negate_iife": false,
+                "properties": false,
+                "reduce_funcs": false,
+                "reduce_vars": false,
+                "side_effects": false,
+                "switches": false,
+                "typeofs": false,
+                "unsafe": false,
+                "unsafe_arrows": false,
+                "unsafe_comps": false,
+                "unsafe_Function": false,
+                "unsafe_math": false,
+                "unsafe_symbols": false,
+                "unsafe_methods": false,
+                "unsafe_proto": false,
+                "unsafe_regexp": false,
+                "unsafe_undefined": false,
+                "unused": false,
+                "const_to_let": false,
+                "pristine_globals": false,
+                "passes": 3
+            },
+            "mangle": false
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": false,
+    "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8324/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8324/input/1.js
new file mode 100644
index 0000000000..0fe78d8887
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8324/input/1.js
@@ -0,0 +1,24 @@
+function Deferred() {
+    const deferred = this;
+    deferred.promise = new Promise(function (resolve, reject) {
+        deferred.resolve = resolve;
+        deferred.reject = reject;
+    });
+}
+
+export async function bug() {
+    const s = `next`;
+    if (!window[s]) {
+        for (window[s] = new Deferred(); ;)
+            if (window.current) await window.current.promise;
+            else {
+                window.current = window[s];
+                try {
+                    return await window[s].promise // This line compressed to 'break'. I guess compressor intended jump to 23 line which is looks like same code.
+                } finally {
+                    delete window.current // Above 'break' makes unintended delete
+                }
+            }
+    }
+    return await window[s].promise
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8324/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8324/output/1.js
new file mode 100644
index 0000000000..21bc88c337
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8324/output/1.js
@@ -0,0 +1,19 @@
+function Deferred() {
+    const deferred = this;
+    deferred.promise = new Promise(function(resolve, reject) {
+        deferred.resolve = resolve, deferred.reject = reject;
+    });
+}
+export async function bug() {
+    const s = "next";
+    if (!window[s]) for(window[s] = new Deferred();;)if (window.current) await window.current.promise;
+    else {
+        window.current = window[s];
+        try {
+            return await window[s].promise;
+        } finally{
+            delete window.current;
+        }
+    }
+    return await window[s].promise;
+}
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8324/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/8324/config.json
new file mode 100644
index 0000000000..76447126ef
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8324/config.json
@@ -0,0 +1,5 @@
+{
+    "defaults": false,
+    "if_return": true,
+    "passes": 3
+}
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8324/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/8324/input.js
new file mode 100644
index 0000000000..0fe78d8887
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8324/input.js
@@ -0,0 +1,24 @@
+function Deferred() {
+    const deferred = this;
+    deferred.promise = new Promise(function (resolve, reject) {
+        deferred.resolve = resolve;
+        deferred.reject = reject;
+    });
+}
+
+export async function bug() {
+    const s = `next`;
+    if (!window[s]) {
+        for (window[s] = new Deferred(); ;)
+            if (window.current) await window.current.promise;
+            else {
+                window.current = window[s];
+                try {
+                    return await window[s].promise // This line compressed to 'break'. I guess compressor intended jump to 23 line which is looks like same code.
+                } finally {
+                    delete window.current // Above 'break' makes unintended delete
+                }
+            }
+    }
+    return await window[s].promise
+}
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8324/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/8324/output.js
new file mode 100644
index 0000000000..baa29ba0d4
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8324/output.js
@@ -0,0 +1,22 @@
+function Deferred() {
+    const deferred = this;
+    deferred.promise = new Promise(function(resolve, reject) {
+        deferred.resolve = resolve;
+        deferred.reject = reject;
+    });
+}
+export async function bug() {
+    const s = "next";
+    if (!window[s]) {
+        for(window[s] = new Deferred();;)if (window.current) await window.current.promise;
+        else {
+            window.current = window[s];
+            try {
+                return await window[s].promise;
+            } finally{
+                delete window.current;
+            }
+        }
+    }
+    return await window[s].promise;
+}