Repository: swc-project/swc (rust)

Issue:
minifier: invalid index access evaluation of string represented with surrogate pairs
### Describe the bug

Each surrogate character should be regarded as an individual character.

### Input code

```typescript
console.log('💖'[0]);
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "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": {
        "toplevel": false,
        "keep_classnames": false,
        "keep_fnames": false,
        "keep_private_props": false,
        "ie8": false,
        "safari10": false
      }
    }
  },
  "module": {
    "type": "commonjs"
  },
  "minify": false,
  "isModule": true
}
```


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

https://play.swc.rs/?version=1.5.24&code=H4sIAAAAAAAAA0vOzyvOz0nVy8lP11D%2FMH%2FSNPVog1hNawCrbVbhFwAAAA%3D%3D&config=H4sIAAAAAAAAA42VzW7bMAyA732KwOcdtsOAYg%2Bw255BUCzKUSaJhkilCYq8%2B2jFTtOGNnYJYn78ESmSen%2FZ7boj9d2v3bv8lY%2FRFoJy%2FxYJXTLbs0g66JOlvoSRu28LPdKEvI0ETXS9kY5tGYCbFf2c1buISLCoz7IUcvCXx4A9prEA0YNMpOKvJshMn%2B1nVvBtAlzqo3yPGMHmDWIsmZAZBiia4x5jtCOBOdmieJlOaksg1EJMsDI4MxYcVZ5d4IBZYj5TB9aZHh0oKBToOZxAM5NYYpZJ0lPyadjBvg5Du%2BQv1nCysVpWYsK5XYmcVvF6wEBsfM1aCW9wpQY3OBf3q2XwpgDXkp%2Ftjhjyyp38BZAKREuUbQLNb9Pw0k9r1n7TMmQvLcsXhUt%2Fa1lmGKSoJgSvVHaqDBQO2m0WcLWHqbK9dpwZr5SPggMD3kuvKK7pLXB%2F0ILyZQT0CpD7tV7rqhsw9ylc4dNAbODfkiXrDTZrJMuHdUqXtMe4ESABH9BtKMhVMK7jIlviPK7zmh1Ia4BTVSo18LwEZAAYTWzL8qk3ZDzEoxki7j%2FWxKxwvS%2FhZPPQ5v1hXzKOEU4Q19r4P0Zkk8rZTlNXL5P9NL3wqjWllcELP75%2FejMklZfltyXVJXT1IaHWk9NbIi2UMB%2Bp%2B9Bcno97pC7Qn8W8Vev6D4dGmLPmBgAA

### SWC Info output

_No response_

### Expected behavior

```javascript
console.log("\uD83D");
```

### Actual behavior

```javascript
console.log("\uD83D\uDC96");
```

### Version

1.5.24

### Additional context

_No response_

Pull request description:
fix(es/minifier): Do not index a string with a surrogate pair
**Description:**


**Related issue:**

 - Closes #9008

Patch:
diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs
index 4823a7c59f..87ee73f94d 100644
--- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs
+++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs
@@ -159,13 +159,11 @@ impl SimplifyExpr {
 
                 // 'foo'[1]
                 KnownOp::Index(idx) if (idx as usize) < value.len() => {
-                    self.changed = true;
-
                     if idx < 0 {
+                        self.changed = true;
                         *expr = *undefined(*span)
-                    } else {
-                        let value = nth_char(value, idx as _);
-
+                    } else if let Some(value) = nth_char(value, idx as _) {
+                        self.changed = true;
                         *expr = Expr::Lit(Lit::Str(Str {
                             raw: None,
                             value: value.into(),
@@ -1661,9 +1659,13 @@ where
     ctx.preserve_effects(span, Expr::Lit(Lit::Bool(Bool { value, span })), orig)
 }
 
-fn nth_char(s: &str, mut idx: usize) -> Cow<str> {
+fn nth_char(s: &str, mut idx: usize) -> Option<Cow<str>> {
+    if s.chars().any(|c| c.len_utf16() > 1) {
+        return None;
+    }
+
     if !s.contains("\\ud") && !s.contains("\\uD") {
-        return Cow::Owned(s.chars().nth(idx).unwrap().to_string());
+        return Some(Cow::Owned(s.chars().nth(idx).unwrap().to_string()));
     }
 
     let mut iter = s.chars().peekable();
@@ -1674,7 +1676,7 @@ fn nth_char(s: &str, mut idx: usize) -> Cow<str> {
                 let mut buf = String::new();
                 buf.push('\\');
                 buf.extend(iter.take(5));
-                return Cow::Owned(buf);
+                return Some(Cow::Owned(buf));
             } else {
                 for _ in 0..5 {
                     iter.next();
@@ -1683,7 +1685,7 @@ fn nth_char(s: &str, mut idx: usize) -> Cow<str> {
         }
 
         if idx == 0 {
-            return Cow::Owned(c.to_string());
+            return Some(Cow::Owned(c.to_string()));
         }
 
         idx -= 1;


Test patch:
diff --git a/crates/swc_ecma_minifier/tests/exec.rs b/crates/swc_ecma_minifier/tests/exec.rs
index 8ac36c3f02..30ea00ed50 100644
--- a/crates/swc_ecma_minifier/tests/exec.rs
+++ b/crates/swc_ecma_minifier/tests/exec.rs
@@ -11294,6 +11294,11 @@ fn issue_8964() {
     );
 }
 
+#[test]
+fn issue_9008() {
+    run_default_exec_test("console.log('💖'[0]);")
+}
+
 #[test]
 fn issue_8982_1() {
     run_default_exec_test(