# 자바스크립트의 억까



아직 휴가라 일하는 날은 아닌데 딱히 땡기는 작업이 없어서 그냥 swc 작업했다.
swc의 미니파이어가 `react-joyride`를 깨는 문제를 보고 있었는데, 진짜 어이없는 케이스라는 걸 깨달았다.
자세한 설명은 https://github.com/swc-project/swc/pull/6509 에 적어놓았으니 여기선 간략하게 설명하겠다.

입력 코드는 다음과 같다.

```js
var fragments = offset.split(/(\+|\-)/).map(function (frag) {
  return frag.trim();
});

var divider = fragments.indexOf(
  find(fragments, function (frag) {
    return frag.search(/,|\s/) !== -1;
  })
);

if (fragments[divider] && fragments[divider].indexOf(",") === -1) {
  console.warn(
    "Offsets separated by white space(s) are deprecated, use a comma (,) instead."
  );
}
```

그리고 문제되는 룰은 `a = foo, use(b(a))`를 `use(b(a = foo))`처럼 바꾸는 룰이었다.
swc minifier는 저거 압축할 때 사이드 이펙트나 변수 간의 관계를 다 따진다.
근데 resolution 순서를 따지진 않았다.
여기서 골 때리는 버그가 생겨났다.

위의 코드에 저 룰을 한번 적용하면

```js
var fragments;
var divider = (fragments = offset.split(/(\+|\-)/).map(function (frag) {
  return frag.trim();
})).indexOf(
  find(fragments, function (frag) {
    return frag.search(/,|\s/) !== -1;
  })
);

if (fragments[divider] && fragments[divider].indexOf(",") === -1) {
  console.warn(
    "Offsets separated by white space(s) are deprecated, use a comma (,) instead."
  );
}
```

처럼 최적화된다.
그런데 저 룰을 한번 더 적용할 수 있는 것처럼 보인다.
`divider`에 할당하는 식이 존재하고 `fragments[divider]`가 존재하니까.
그리고 대부분의 경우에 이건 올바른 최적화가 맞다.
문제는 저게 올바르지 않은 예시 케이스가 존재한다는 것이다.
코드 실행 순서와 resolution 순서가 달라서 그렇다.

`fragments[divider = (fragments = foo)]`의 실행 순서는 `foo` => `fragments = foo` => `divider = (fragments = foo)` => `fragments` => `fragments[divider = (fragments = foo)]`이다.
이거엔 문제가 없다.
문제는 resolution 순서이다.
자바스크립트에서는 `[` 왼쪽의 `fragments`가 먼저 resolve 된다.
따라서 `undefined[divider = (fragments = foo)]`가 되어버리고, 런타임에 익셉션이 발생한다.

참고로 이 문제는 디버거를 붙여도 뭐가 `undefined`인지 안 보여준다.
익셉션이 발생한 시점은 `fragments[divider = (fragments = foo)]`를 **실행**하는 시점이고 이 시점에 `fragments`는 올바른 값을 갖고 있다.
그래서 억까라고 하는 것이다.
디버거에 낚여서 삽질한 거라 짧게 기록해놓기로 했다.

