Repository: swc-project/swc (rust)

Issue:
minifier: inlining arithmetic assignment expression can result in different output
### Describe the bug

Changing `x *= y` to `x = y * x` can make different result if expression `y` change the value of `x`.

### Input code

```typescript
"use strict";
const k = (() => {
    let x = '';
    x *= x-- / x; 
    return x;
})();
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "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": "commonjs"
  },
  "minify": false,
  "isModule": false
}
```


### Playground link (or link to the minimal reproduction)

https://play.swc.rs/?version=1.5.2&code=H4sIAAAAAAAAAx3IQQqAIBBG4b2n%2BHHjGEgHGOwy4iIKA2eEgejuiW%2F3PT%2BkQrSfRT278jRRXMggisgHXofZXRU2Zwi8bNgyLCXsMMZaverobdJ9kSL%2FsQJtalcAAAA%3D&config=H4sIAAAAAAAAA32UO5LbMAyG%2Bz2FR3WKjMscIF3OwKFJUKZDEhoC9Fqz47sHeviRNeROwocfIAEQXx%2B7XXci1%2F3afcmn%2FAy2EtT7v1hoLGwvYunAZUuuxoG7Hzd6ogkFmwhm03UhHdvaA88q2v%2Fc71dFlxAJborVlmOJYXzO6TAPFYiebGKVkC1DYfpfv7KKnxPg2p7tB8QEtrwhxpKJhaGHqgV2mJIdCMzZViXKdFJbI6GWYoKNwZuh4qDy4iNHLJLzlXqw3jj0oKBYwXE8gyaTXCIrJNdT7jNjD4fW93Ofv6nhbFOzrOSEy9wSOa0S9YiR2IRWtBIucKMGC1yL%2B10Zg6nArZZX3Qlj2ejJXwCpQLJExWbQ4s4eQeZpSx3eKmMJMrI8KlzmW7tlgV6KamIMSmWnykDlqHWzgm8Opso67Tgr3igfRQ8GQpBZUULTZ2R31JLyOAAGBUh%2FbdCmagHm%2Fgo3%2BPQg3uDfckvWB2z1yJaP25TGfMD0JkEGPqJ%2F4yCtYNzGVbbEZdjmrXiQ0QCvujSawesSkAfAaNK8L19mQ56HRDR9wsNjTawO1%2Fsezrb0j%2Fe%2BrOKP1aHL6NsM1yU%2F9XdazdKOjOVE3cPztorvp%2B8i%2FbnJl9jXf3MIldo2BgAA

### SWC Info output

_No response_

### Expected behavior

k is `-0`

### Actual behavior

k is `0`

### Version

1.5.2

### Additional context

_No response_

Pull request:
fix(es/minifier): Consider side effects of operands of binary expressions
**Description:**



**Related issue:**

 - Closes #8923


Patch:
diff --git a/crates/swc_ecma_minifier/src/compress/pure/bools.rs b/crates/swc_ecma_minifier/src/compress/pure/bools.rs
index 5bb1124a86..b26b16300f 100644
--- a/crates/swc_ecma_minifier/src/compress/pure/bools.rs
+++ b/crates/swc_ecma_minifier/src/compress/pure/bools.rs
@@ -523,7 +523,8 @@ impl Pure<'_> {
                     .as_bin()
                     .filter(|b| b.op.precedence() == op.precedence())
                     .is_none()
-                && !left.may_have_side_effects(&self.expr_ctx));
+                && !left.may_have_side_effects(&self.expr_ctx)
+                && !right.may_have_side_effects(&self.expr_ctx));
 
         if can_swap {
             report_change!("Swapping operands of binary expession");


Test patch:
diff --git a/crates/swc_ecma_minifier/tests/benches-full/d3.js b/crates/swc_ecma_minifier/tests/benches-full/d3.js
index 8d5a1e91f3..48e80809ed 100644
--- a/crates/swc_ecma_minifier/tests/benches-full/d3.js
+++ b/crates/swc_ecma_minifier/tests/benches-full/d3.js
@@ -1672,7 +1672,7 @@
             else {
                 var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1), b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0);
                 S = (Math.log(Math.sqrt(b1 * b1 + 1) - b1) - r0) / rho, i = function(t) {
-                    var x, x1, s = t * S, coshr0 = cosh(r0), u = w0 / (rho2 * d1) * (((x = Math.exp(2 * (x = rho * s + r0))) - 1) / (x + 1) * coshr0 - ((x1 = Math.exp(x1 = r0)) - 1 / x1) / 2);
+                    var x, x1, s = t * S, coshr0 = cosh(r0), u = w0 / (rho2 * d1) * (coshr0 * (((x = Math.exp(2 * (x = rho * s + r0))) - 1) / (x + 1)) - ((x1 = Math.exp(x1 = r0)) - 1 / x1) / 2);
                     return [
                         ux0 + u * dx,
                         uy0 + u * dy,
diff --git a/crates/swc_ecma_minifier/tests/benches-full/echarts.js b/crates/swc_ecma_minifier/tests/benches-full/echarts.js
index 65b7c4b830..43d0c5fd66 100644
--- a/crates/swc_ecma_minifier/tests/benches-full/echarts.js
+++ b/crates/swc_ecma_minifier/tests/benches-full/echarts.js
@@ -1158,7 +1158,7 @@
         },
         elasticInOut: function(k) {
             var s, a = 0.1;
-            return 0 === k ? 0 : 1 === k ? 1 : (!a || a < 1 ? (a = 1, s = 0.1) : s = 0.4 * Math.asin(1 / a) / (2 * Math.PI), (k *= 2) < 1) ? -(a * Math.pow(2, 10 * (k -= 1)) * Math.sin(2 * Math.PI * (k - s) / 0.4) * 0.5) : a * Math.pow(2, -10 * (k -= 1)) * Math.sin(2 * Math.PI * (k - s) / 0.4) * 0.5 + 1;
+            return 0 === k ? 0 : 1 === k ? 1 : (!a || a < 1 ? (a = 1, s = 0.1) : s = 0.4 * Math.asin(1 / a) / (2 * Math.PI), (k *= 2) < 1) ? -0.5 * (a * Math.pow(2, 10 * (k -= 1)) * Math.sin(2 * Math.PI * (k - s) / 0.4)) : a * Math.pow(2, -10 * (k -= 1)) * Math.sin(2 * Math.PI * (k - s) / 0.4) * 0.5 + 1;
         },
         backIn: function(k) {
             return k * k * (2.70158 * k - 1.70158);
diff --git a/crates/swc_ecma_minifier/tests/benches-full/victory.js b/crates/swc_ecma_minifier/tests/benches-full/victory.js
index 08164beb4e..98482a633d 100644
--- a/crates/swc_ecma_minifier/tests/benches-full/victory.js
+++ b/crates/swc_ecma_minifier/tests/benches-full/victory.js
@@ -2345,7 +2345,7 @@
                 else {
                     var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + 4 * d2) / (2 * w0 * 2 * d1), b1 = (w1 * w1 - w0 * w0 - 4 * d2) / (2 * w1 * 2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0);
                     S = (Math.log(Math.sqrt(b1 * b1 + 1) - b1) - r0) / rho, i = function(t) {
-                        var x, x1, s = t * S, coshr0 = cosh(r0), u = w0 / (2 * d1) * (((x = Math.exp(2 * (x = rho * s + r0))) - 1) / (x + 1) * coshr0 - ((x1 = Math.exp(x1 = r0)) - 1 / x1) / 2);
+                        var x, x1, s = t * S, coshr0 = cosh(r0), u = w0 / (2 * d1) * (coshr0 * (((x = Math.exp(2 * (x = rho * s + r0))) - 1) / (x + 1)) - ((x1 = Math.exp(x1 = r0)) - 1 / x1) / 2);
                         return [
                             ux0 + u * dx,
                             uy0 + u * dy,
@@ -22643,7 +22643,7 @@
                                 firstChild && "stack" === firstChild.type.role && (barWidth = (firstChild.props.children && firstChild.props.children[0]).props.barWidth, dataLength = firstChild.props.children.length);
                                 var width = barWidth || this.getWidth(props, children.length, dataLength);
                                 return {
-                                    x: width * children.length / 2 + (offset - (children.length - 1) / 2 * width)
+                                    x: width * children.length / 2 + (offset - width * ((children.length - 1) / 2))
                                 };
                             }
                         }
@@ -29100,7 +29100,7 @@
                                 x: function() {
                                     if (!textAnchor || "middle" === textAnchor) return flyoutCenter.x;
                                     var sign = "end" === textAnchor ? -1 : 1;
-                                    return flyoutCenter.x - labelSize.width / 2 * sign;
+                                    return flyoutCenter.x - sign * (labelSize.width / 2);
                                 }() + (flyoutPadding.left - flyoutPadding.right) / 2,
                                 y: flyoutCenter.y + (flyoutPadding.top - flyoutPadding.bottom) / 2,
                                 verticalAnchor: "middle",
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8923/config.json b/crates/swc_ecma_minifier/tests/fixture/issues/8923/config.json
new file mode 100644
index 0000000000..7e144b291f
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8923/config.json
@@ -0,0 +1,47 @@
+{
+    "defaults": true,
+    "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
+}
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8923/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/8923/input.js
new file mode 100644
index 0000000000..56fb98437c
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8923/input.js
@@ -0,0 +1,6 @@
+"use strict";
+const k = (() => {
+    let x = '';
+    x *= x-- / x;
+    return x;
+})();
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/8923/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/8923/output.js
new file mode 100644
index 0000000000..e0dab60b5e
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/fixture/issues/8923/output.js
@@ -0,0 +1,5 @@
+"use strict";
+const k = (()=>{
+    let x = '';
+    return x * (x-- / x);
+})();