Repository: swc-project/swc (rust)

Issue:
Invalid "compress" output with unary operators
### Describe the bug

When enabling "compress", `swc` makes an optimization by wrongly removing some operations applied to a variable. It seems to happen when the variable is used with an unary operator (see input code).

Consequences can be critical: in the "real life" example below, it results in an infinite loop.

### Input code

```typescript
// minimal example
var n = 0;
n = 2000; // this gets removed in the output
console.log(n++);


// real life example taken from https://github.com/nodeca/pako/blob/master/lib/zlib/adler32.js#L26
const adler32 = (adler, buf, len, pos) => {
  let s1 = (adler & 0xffff) |0,
      s2 = ((adler >>> 16) & 0xffff) |0,
      n = 0;

  while (len !== 0) {
    // Set limit ~ twice less than 5552, to keep
    // s2 in 31-bits, because we force signed ints.
    // in other case %= will fail.
    n = len > 2000 ? 2000 : len;
    len -= n;

    do {
      s1 = (s1 + buf[pos++]) |0;
      s2 = (s2 + s1) |0;
    } while (--n);

    s1 %= 65521;
    s2 %= 65521;
  }

  return (s1 | (s2 << 16)) |0;
};


module.exports = adler32;
```


### Config

```json
{
  "jsc": {
    "minify": {
      "compress": true
    }
  }
}
```


### Playground link

https://play.swc.rs/?version=1.2.125&code=H4sIAAAAAAAAAytLLFLIU7BVMLDmAlFGBgZAVnJ%2BXnF%2BTqpeTn66Rp62tqY1FwDf8XedJwAAAA%3D%3D&config=H4sIAAAAAAAAA22TS5LjIAyG7%2BJ1tjOLPkDv5gyUAsJRjwEXEum4Url7C9vBdtI79PFL6MW9%2B2Lbfdy7ETJjrieeosCt%2B%2BjQBmCbaZTupDJFHgbGx6kTyD1KlfAfvRtSYlxvT12gSH6qkWwKY0bmelaPEjAKNx3knL7VlFzUOqc0IMQ32wAbioI95s3VpmGAkdFcITeP%2Bhpk4hQPqAg6M%2BY07t2jI6EU1WzQIThjk9sKcZTRCl1xJ9I4KoqsyR2hw3Pp%2B9rB5Wm8wlBAsNm3uRX6aPO7JGIxvkR%2BQWu2i%2BOClkJXFXmTUUqOT81XonjoxX9EzXMA5ghhl%2F%2FMvc7iqPS%2FqCh6HaRMjeqUt6wi9lqcIfKtwpo1ZiFsooyuWKwV2i34Cg8FMTk06L22uznzN4m97LKSacTk2722Dfw2hMU0z6U60roHb%2FBT05L9PFYeQC6vjKdwTsNbiIBySe4NayckvcKsK3wbX2mJDrXP6HYXhatZq9SvFiD2bdceFSRXKrjPDZn%2F4N%2FusX27547wv1U4B%2FoBnJuQe%2BgDAAA%3D

### Expected behavior

All operations on the variable are taken into account. With the input code provided, the first example should print `2000`, and the second example should terminate when used.

### Actual behavior

Some operations are removed. With the input code provided, the first example prints `0`, and the second example results in an infinite loop when used.

### Version

1.2.125 and later

### Additional context

_No response_

Pull request description:
fix(es/minifier): Fix analysis of unary expressions
**Description:**

swc_ecma_minifier:
 - Mark update as usage and assignment. (Closes #3265)


**Related issue (if exists):**


Patch:
diff --git a/crates/swc_ecma_minifier/src/analyzer/mod.rs b/crates/swc_ecma_minifier/src/analyzer/mod.rs
index e67edd80d0..2f34f2ecb4 100644
--- a/crates/swc_ecma_minifier/src/analyzer/mod.rs
+++ b/crates/swc_ecma_minifier/src/analyzer/mod.rs
@@ -448,7 +448,12 @@ where
         e.visit_children_with(self);
 
         if let Expr::Ident(i) = e {
-            self.report_usage(i, self.ctx.in_update_arg || self.ctx.in_assign_lhs);
+            if self.ctx.in_update_arg {
+                self.report_usage(i, true);
+                self.report_usage(i, false);
+            } else {
+                self.report_usage(i, self.ctx.in_update_arg || self.ctx.in_assign_lhs);
+            }
         }
     }
 


Test patch:
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/1/input.js b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/1/input.js
new file mode 100644
index 0000000000..049649cc97
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/1/input.js
@@ -0,0 +1,6 @@
+var n = 0;
+n = 2000; // this gets removed in the output
+console.log(n++);
+
+
+export default n;
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/1/output.js b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/1/output.js
new file mode 100644
index 0000000000..68143ea997
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/1/output.js
@@ -0,0 +1,3 @@
+var n = 0;
+n = 2000, console.log(n++);
+export default n;
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/2/input.js b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/2/input.js
new file mode 100644
index 0000000000..08caca4d6f
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/2/input.js
@@ -0,0 +1,26 @@
+// real life example taken from https://github.com/nodeca/pako/blob/master/lib/zlib/adler32.js#L26
+const adler32 = (adler, buf, len, pos) => {
+    let s1 = (adler & 0xffff) | 0,
+        s2 = ((adler >>> 16) & 0xffff) | 0,
+        n = 0;
+
+    while (len !== 0) {
+        // Set limit ~ twice less than 5552, to keep
+        // s2 in 31-bits, because we force signed ints.
+        // in other case %= will fail.
+        n = len > 2000 ? 2000 : len;
+        len -= n;
+
+        do {
+            s1 = (s1 + buf[pos++]) | 0;
+            s2 = (s2 + s1) | 0;
+        } while (--n);
+
+        s1 %= 65521;
+        s2 %= 65521;
+    }
+
+    return (s1 | (s2 << 16)) | 0;
+};
+
+export default adler32;
\ No newline at end of file
diff --git a/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/2/output.js b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/2/output.js
new file mode 100644
index 0000000000..9bbc349877
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/compress/fixture/issues/3256/2/output.js
@@ -0,0 +1,10 @@
+export default ((adler, buf, len, pos)=>{
+    let s1 = 65535 & adler | 0, s2 = adler >>> 16 & 65535 | 0, n = 0;
+    for(; 0 !== len;){
+        len -= n = len > 2000 ? 2000 : len;
+        do s2 = s2 + (s1 = s1 + buf[pos++] | 0) | 0;
+        while (--n)
+        s1 %= 65521, s2 %= 65521;
+    }
+    return s1 | s2 << 16 | 0;
+});
diff --git a/crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js b/crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js
index da1eb64cf2..0d523c8247 100644
--- a/crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js
+++ b/crates/swc_ecma_minifier/tests/projects/output/jquery-1.9.1.js
@@ -606,7 +606,7 @@
             });
             if ("string" == typeof value && value) {
                 for(classes = (value || "").match(core_rnotwhite) || []; i < len; i++)if (cur = 1 === (elem = this[i]).nodeType && (elem.className ? (" " + elem.className + " ").replace(rclass, " ") : " ")) {
-                    for(; clazz = classes[j1++];)0 > cur.indexOf(" " + clazz + " ") && (cur += clazz + " ");
+                    for(j1 = 0; clazz = classes[j1++];)0 > cur.indexOf(" " + clazz + " ") && (cur += clazz + " ");
                     elem.className = jQuery.trim(cur);
                 }
             }
@@ -619,7 +619,7 @@
             });
             if (proceed) {
                 for(classes = (value || "").match(core_rnotwhite) || []; i < len; i++)if (cur = 1 === (elem = this[i]).nodeType && (elem.className ? (" " + elem.className + " ").replace(rclass, " ") : "")) {
-                    for(; clazz = classes[j2++];)for(; cur.indexOf(" " + clazz + " ") >= 0;)cur = cur.replace(" " + clazz + " ", " ");
+                    for(j2 = 0; clazz = classes[j2++];)for(; cur.indexOf(" " + clazz + " ") >= 0;)cur = cur.replace(" " + clazz + " ", " ");
                     elem.className = value ? jQuery.trim(cur) : "";
                 }
             }
@@ -887,7 +887,7 @@
             event = jQuery.event.fix(event);
             var i, ret, handleObj, matched, j, handlerQueue = [], args = core_slice.call(arguments), handlers = (jQuery._data(this, "events") || {})[event.type] || [], special = jQuery.event.special[event.type] || {};
             if (args[0] = event, event.delegateTarget = this, !special.preDispatch || !1 !== special.preDispatch.call(this, event)) {
-                for(handlerQueue = jQuery.event.handlers.call(this, event, handlers); (matched = handlerQueue[i++]) && !event.isPropagationStopped();)for(event.currentTarget = matched.elem; (handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped();)(!event.namespace_re || event.namespace_re.test(handleObj.namespace)) && (event.handleObj = handleObj, event.data = handleObj.data, undefined1 !== (ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args)) && !1 === (event.result = ret) && (event.preventDefault(), event.stopPropagation()));
+                for(handlerQueue = jQuery.event.handlers.call(this, event, handlers), i = 0; (matched = handlerQueue[i++]) && !event.isPropagationStopped();)for(event.currentTarget = matched.elem, j = 0; (handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped();)(!event.namespace_re || event.namespace_re.test(handleObj.namespace)) && (event.handleObj = handleObj, event.data = handleObj.data, undefined1 !== (ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args)) && !1 === (event.result = ret) && (event.preventDefault(), event.stopPropagation()));
                 return special.postDispatch && special.postDispatch.call(this, event), event.result;
             }
         },
@@ -1669,7 +1669,7 @@
                     var elem, j, matcher, setMatched = [], matchedCount = 0, i = "0", unmatched = seed && [], outermost = null != expandContext, contextBackup = outermostContext, elems = seed || byElement && Expr.find.TAG("*", expandContext && context.parentNode || context), dirrunsUnique = dirruns += null == contextBackup ? 1 : Math.random() || 0.1;
                     for(outermost && (outermostContext = context !== document && context, cachedruns = matcherCachedRuns); null != (elem = elems[i]); i++){
                         if (byElement && elem) {
-                            for(; matcher = elementMatchers[j++];)if (matcher(elem, context, xml)) {
+                            for(j = 0; matcher = elementMatchers[j++];)if (matcher(elem, context, xml)) {
                                 results.push(elem);
                                 break;
                             }
@@ -1678,7 +1678,7 @@
                         bySet && ((elem = !matcher && elem) && matchedCount--, seed && unmatched.push(elem));
                     }
                     if (matchedCount += i, bySet && i !== matchedCount) {
-                        for(; matcher = setMatchers[j++];)matcher(unmatched, setMatched, context, xml);
+                        for(j = 0; matcher = setMatchers[j++];)matcher(unmatched, setMatched, context, xml);
                         if (seed) {
                             if (matchedCount > 0) for(; i--;)unmatched[i] || setMatched[i] || (setMatched[i] = pop.call(results));
                             setMatched = condense(setMatched);