Repository: swc-project/swc (rust)

Issue:
Minifier doesn't respect side-effect in getter or setter
### Describe the bug

Minifier doesn't respect side-effect in getter or setter

### Input code

```typescript
let getter_effect = 'FAIL';
let setter_effect = 'FAIL';
let proto = {
	get foo() {
		getter_effect = 'PASS';
	},
	set bar(value) {
		setter_effect = 'PASS';
	}
};
let obj1 = {
	__proto__: proto
};
let obj2 = {
	__proto__: proto
};
let unused = obj1.foo;
obj2.bar = 0;

assert.strictEqual(getter_effect, 'PASS');
assert.strictEqual(setter_effect, 'PASS');
```


### Config

```json
{
  "jsc": {
    "transform": {
      "react": {
        "refresh": {}
      }
    },
    "parser": {
      "syntax": "ecmascript",
      "jsx": true
    },
    "target": "es2022",
    "loose": false,
    "minify": {
      "compress": {
        "arguments": false,
        "arrows": true,
        "booleans": true,
        "booleans_as_integers": false,
        "collapse_vars": true,
        "comparisons": true,
        "computed_props": true,
        "conditionals": true,
        "dead_code": true,
        "directives": true,
        "drop_console": false,
        "drop_debugger": true,
        "evaluate": true,
        "expression": false,
        "hoist_funs": false,
        "hoist_props": true,
        "hoist_vars": false,
        "if_return": true,
        "join_vars": true,
        "keep_classnames": false,
        "keep_fargs": true,
        "keep_fnames": false,
        "keep_infinity": false,
        "loops": true,
        "negate_iife": true,
        "properties": true,
        "reduce_funcs": false,
        "reduce_vars": false,
        "side_effects": true,
        "switches": true,
        "typeofs": true,
        "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": true,
        "const_to_let": true,
        "pristine_globals": true
      },
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "isModule": true,
  "minify": false
}
```


### Playground link

https://play.swc.rs/?version=1.3.15&code=H4sIAAAAAAAAA32PsQrCMBCG5%2BQpsrWFUtTR4NBBQXAQ%2BgAhrVdRQqPJxUX67l6NiFLtmLvv%2Fu%2BPARRHQASnoG2hQbESyabc7hLJDe38xO7iLFqa3TmjCNFam2bDg40C92VV0RHrc84oUdTapTdtAkR%2BJHnzvI8qW5%2Fn0aTUU6vUMvo%2FiMU0Ebrg4UDMEFZQW8mHo4LK0HAmOdfeg8PCozs1uL4GbdKvr%2BSvZpn8hfo%2F6AO30%2BKDYwEAAA%3D%3D&config=H4sIAAAAAAAAA21TQbIiIQy9C2sXUy5m4QFm989AIYSWPw3pSoLfLsu7T7CVtnV25PHySF7C1XyzN4erEXKFI1JuAYHzshwiAZ%2F0eLvddmZyxEDtguci7mIOBnx27ClNYnaqpZBQBeWKowGkMXj%2Fa7%2FX2xGRwRyiGxl2JqeS4ty0POZJX%2BF21qSaoQh3niPCH15kd%2BaIOIJW%2Bh5bxzYVgQFoTfU4jm5isGdHPaO95igxlg1UBYKdCKcXtIQkCYuKPbEALliPATqQCLykM6wU1VBKYS2sV3IHAxzrMDT%2FFiac3ViddC243G3QF3veCROLjbXwG7SpdIGWJh%2BsFC2BVCpPzjemsvHhL4DWOTrm4jKsmXc86hy2zPgfVipRhyhzR3XCa1UFBm3OphR7h61qIEmrWwShemgd%2BlX8AW4a4hTAQozqdk%2FmnyT%2BtIrJPAHGHqprLq4zWEL73Kct2lbgA%2FyjVcnrOB54dnJ6x3jORxw%2FJDLICcMHrEYIvoOk23uZ3tFaAqjNEF4uKrewr6kOX9CO7bc9fdaF0Bw7jHjs66ufMrsy9LVsPzpjqA243s27%2F9bfRvHEX4%2BbRfH5W5fEf5vNM1k2BAAA

### Expected behavior

__Notice that terser also generated wrong code, the `obj1.foo` should not be removed.__

![image](https://user-images.githubusercontent.com/49502170/201511024-f6ca0be5-2577-431c-bd77-86bf2e4d4bda.png)



### Actual behavior

```js
let getter_effect = 'FAIL', setter_effect = 'FAIL';
assert.strictEqual(getter_effect, 'PASS'), assert.strictEqual(setter_effect, 'PASS');
```


### Version

1.3.15

### Additional context

_No response_

Pull request description:
fix(es/minifier): should not drop statements which has side-effects
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**

Fix #6422 


Patch:
diff --git a/crates/swc_atoms/words.txt b/crates/swc_atoms/words.txt
index 07b7a5fe01..dcdf635a8a 100644
--- a/crates/swc_atoms/words.txt
+++ b/crates/swc_atoms/words.txt
@@ -1369,3 +1369,4 @@ ychannelselector
 yield
 zoomAndPan
 zoomandpan
+__proto__
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
index 8f3cd3d4a6..a140edf091 100644
--- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
+++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs
@@ -1918,15 +1918,23 @@ where
             #[cfg(feature = "debug")]
             let start = dump(&n.expr, true);
 
-            let expr = self.ignore_return_value(&mut n.expr);
-            n.expr = expr.map(Box::new).unwrap_or_else(|| {
-                report_change!("visit_mut_expr_stmt: Dropped an expression statement");
-
-                #[cfg(feature = "debug")]
-                dump_change_detail!("Removed {}", start);
-
-                undefined(DUMMY_SP)
-            });
+            // Fix https://github.com/swc-project/swc/issues/6422
+            let is_object_lit_with_spread = n
+                .expr
+                .as_object()
+                .map(|object_lit| object_lit.props.iter().any(|prop| prop.is_spread()))
+                .unwrap_or(false);
+
+            if !is_object_lit_with_spread {
+                let expr = self.ignore_return_value(&mut n.expr);
+                n.expr = expr.map(Box::new).unwrap_or_else(|| {
+                    report_change!("visit_mut_expr_stmt: Dropped an expression statement");
+                    #[cfg(feature = "debug")]
+                    dump_change_detail!("Removed {}", start);
+
+                    undefined(DUMMY_SP)
+                });
+            }
         } else {
             match &mut *n.expr {
                 Expr::Seq(e) => {
diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs
index f2aa790f53..c11d36ebb5 100644
--- a/crates/swc_ecma_utils/src/lib.rs
+++ b/crates/swc_ecma_utils/src/lib.rs
@@ -1375,18 +1375,33 @@ pub trait ExprExt {
                         }
                     }
                     Expr::Object(obj) => {
-                        let is_static_accessor = |prop: &PropOrSpread| {
-                            if let PropOrSpread::Prop(prop) = prop {
-                                if let Prop::Getter(_) | Prop::Setter(_) = &**prop {
-                                    true
-                                } else {
-                                    false
-                                }
-                            } else {
-                                false
-                            }
+                        let can_have_side_effect = |prop: &PropOrSpread| match prop {
+                            PropOrSpread::Spread(_) => true,
+                            PropOrSpread::Prop(prop) => match prop.as_ref() {
+                                Prop::Getter(_)
+                                | Prop::Setter(_)
+                                | Prop::Method(_)
+                                | Prop::Shorthand(Ident {
+                                    sym: js_word!("__proto__"),
+                                    ..
+                                })
+                                | Prop::KeyValue(KeyValueProp {
+                                    key:
+                                        PropName::Ident(Ident {
+                                            sym: js_word!("__proto__"),
+                                            ..
+                                        })
+                                        | PropName::Str(Str {
+                                            value: js_word!("__proto__"),
+                                            ..
+                                        })
+                                        | PropName::Computed(_),
+                                    ..
+                                }) => true,
+                                _ => false,
+                            },
                         };
-                        if obj.props.iter().any(is_static_accessor) {
+                        if obj.props.iter().any(can_have_side_effect) {
                             return true;
                         }
                     }


Test patch:
diff --git a/crates/swc/tests/tsc-references/objectSpreadSetonlyAccessor.2.minified.js b/crates/swc/tests/tsc-references/objectSpreadSetonlyAccessor.2.minified.js
index b714550306..80186d6387 100644
--- a/crates/swc/tests/tsc-references/objectSpreadSetonlyAccessor.2.minified.js
+++ b/crates/swc/tests/tsc-references/objectSpreadSetonlyAccessor.2.minified.js
@@ -1,1 +1,6 @@
 //// [objectSpreadSetonlyAccessor.ts]
+({
+    ...{
+        set foo (_v){}
+    }
+});
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/config.json
new file mode 100644
index 0000000000..480beee766
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/config.json
@@ -0,0 +1,5 @@
+{
+    "collapse_vars": true,
+    "unused": true,
+    "toplevel": true
+}
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/input.js
new file mode 100644
index 0000000000..84de0d3034
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/input.js
@@ -0,0 +1,21 @@
+let getter_effect = 'FAIL';
+let setter_effect = 'FAIL';
+let proto = {
+    get foo() {
+        getter_effect = 'PASS';
+    },
+    set bar(value) {
+        setter_effect = 'PASS';
+    }
+};
+let obj1 = {
+    __proto__: proto
+};
+let obj2 = {
+    __proto__: proto
+};
+let unused = obj1.foo;
+obj2.bar = 0;
+
+assert.strictEqual(getter_effect, 'PASS');
+assert.strictEqual(setter_effect, 'PASS');
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/output.js
new file mode 100644
index 0000000000..e3d96e8a2c
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/1/output.js
@@ -0,0 +1,18 @@
+let getter_effect = 'FAIL';
+let setter_effect = 'FAIL';
+let proto = {
+    get foo () {
+        getter_effect = 'PASS';
+    },
+    set bar (value){
+        setter_effect = 'PASS';
+    }
+};
+({
+    __proto__: proto
+}).foo;
+({
+    __proto__: proto
+}).bar = 0;
+assert.strictEqual(getter_effect, 'PASS');
+assert.strictEqual(setter_effect, 'PASS');
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/config.json
new file mode 100644
index 0000000000..8e1ba5e679
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/config.json
@@ -0,0 +1,4 @@
+{
+    "toplevel": true,
+    "unused": true
+}
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/input.js
new file mode 100644
index 0000000000..373eacbebf
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/input.js
@@ -0,0 +1,10 @@
+import assert from 'assert'
+let result = 'FAIL';
+const unused = {
+    ...{
+        get prop() {
+            result = 'PASS';
+        }
+    }
+};
+assert.strictEqual(result, 'PASS');
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/output.js
new file mode 100644
index 0000000000..d899d5cc5b
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/2/output.js
@@ -0,0 +1,10 @@
+import assert from 'assert';
+let result = 'FAIL';
+({
+    ...{
+        get prop () {
+            result = 'PASS';
+        }
+    }
+});
+assert.strictEqual(result, 'PASS');
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/config.json
new file mode 100644
index 0000000000..8e1ba5e679
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/config.json
@@ -0,0 +1,4 @@
+{
+    "toplevel": true,
+    "unused": true
+}
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/input.js
new file mode 100644
index 0000000000..da075da2c7
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/input.js
@@ -0,0 +1,18 @@
+import assert from 'assert'
+let result = 0;
+const unused = {
+    ...{
+        get prop() {
+            result = 1;
+        }
+    },
+    [assert.strictEqual(result, 1)]: null,
+    [result = 2]: null,
+    [assert.strictEqual(result, 2)]: null,
+    ...{
+        get prop() {
+            result = 3;
+        }
+    }
+};
+assert.strictEqual(result, 3);
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/output.js
new file mode 100644
index 0000000000..eaeef3af06
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/6422/3/output.js
@@ -0,0 +1,18 @@
+import assert from 'assert';
+let result = 0;
+({
+    ...{
+        get prop () {
+            result = 1;
+        }
+    },
+    [assert.strictEqual(result, 1)]: null,
+    [result = 2]: null,
+    [assert.strictEqual(result, 2)]: null,
+    ...{
+        get prop () {
+            result = 3;
+        }
+    }
+});
+assert.strictEqual(result, 3);