Skip to main content

Command Palette

Search for a command to run...

자바스크립트의 억까

Published
2 min readView as Markdown
D
I created SWC, an open-source compiler project, and previously worked at Deno and Vercel. These days, I spend a lot of time building Labor0, along with Watchtower and The AI Platform at Zephyr Cloud IO.

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

입력 코드는 다음과 같다.

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 순서를 따지진 않았다. 여기서 골 때리는 버그가 생겨났다.

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

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는 올바른 값을 갖고 있다. 그래서 억까라고 하는 것이다. 디버거에 낚여서 삽질한 거라 짧게 기록해놓기로 했다.

More from this blog