[JSC] Move RegExp.prototype[Symbol.match] to C++
JSTests/stress/regexp-prototype-symbol-match-watchpoint-invalidation.js
RegExp.prototype[Symbol.match]는 String.prototype.match와 직접적인 re[Symbol.match](str) 호출 모두의 진입점입니다. 사양이 요구하는 알고리즘은 겉보기보다 복잡한데, global 또는 sticky regexp에서는 RegExpExec를 반복 호출하면서 ToLength로 lastIndex를 coerce해야 합니다. 빈 match는 AdvanceStringIndex를 통해 건너뛰어야 하며, 이 함수는 Unicode를 인식하고 surrogate pair를 올바르게 처리하도록 되어 있습니다. 또한 exec, flags, lastIndex accessor가 override된 경우의 observable side effect도 빠짐없이 준수해야 합니다. JSC는 이전까지 이 기능을 JS builtin으로 구현해왔으나, 이번 commit에서 native C++로 이식하였습니다.
이번 패치에서는 hasObservableSideEffectsForRegExpMatch와 matchSlow JS helper가 제거되었습니다. 대신 DFG에는 새로운 addRegExpMatchPrimordialChecks 함수가 도입되었으며, C++ 구현에도 전용 advanceStringIndex helper와 regExpMatchSlow fallback 경로가 추가되었습니다. DFG compiler의 새로운 primordial-check 시스템이 fast C++ 경로를 제어합니다. 관련 built-in이 모두 수정되지 않은 상태라면 fast C++ exec loop로 진입하고, 하나라도 수정되어 있으면 regExpMatchSlow로 fallback됩니다.
Before: After:
str.match(re) str.match(re)
└─► String.prototype.match (C++) └─► String.prototype.match (C++)
└─► RegExp.prototype └─► RegExp.prototype
[Symbol.match] ← JS builtin [Symbol.match] ← C++ host
├─ hasObservableSideEffects ├─ addRegExpMatchPrimordialChecks
├─ matchSlow() │ ├─ intact → fast C++ exec loop
└─ exec() loop │ └─ modified → regExpMatchSlow()
└─ advanceStringIndex() ← new C++
Significance
5~25%의 성능 향상 외에도, 이번 변경은 spec이 요구하는 복잡한 로직을 interpreter 수준의 JS builtin에서 C++로 이동시킵니다. 여기에는 lastIndex coercion, 빈 match 진행, Unicode surrogate 처리, observable side effect 감지가 포함됩니다. C++ 구현에서는 spec과의 구현 차이를 발견하기가 더 어렵고, 역사적으로 이런 차이가 exploit 가능한 버그로 이어진 사례가 있습니다. 새로운 primordial check의 정확성이 spec이 요구하는 observable side effect가 실제로 관찰되는지를 결정하게 됩니다.
Audit directions
addRegExpMatchPrimordialChecks. 감시 대상 property 집합이 불완전한 경우를 살펴볼 필요가 있습니다.Symbol.match자체,flags,lastIndexsetter, 서브클래스의exec등이 목록에서 빠진 경우가 이에 해당합니다. 이때 공격자가 감시 범위 밖의 accessor를 수정하면, observable side effect 계약이 위반된 상태임에도 JIT가 fast C++ 경로를 선택하도록 유도할 수 있습니다. 이는 과거 JSC에서 반복적으로 발견된 primordial-check bypass의 전형적인 패턴입니다.advanceStringIndexin C++. C++로 구현된advanceStringIndex에서는 Unicode surrogate pair 처리가 미묘합니다. off-by-one 오류나 잘못된 surrogate 감지가 있으면, 빈 match에서 무한 루프에 진입하거나 문자를 건너뛰는 문제로 이어집니다. 두 경우 모두 역사적으로 exploit 가능한 조건으로 이어진 사례가 있습니다.regExpMatchSlowToLengthordering.regExpMatchSlow가 이제ToLength를 통한lastIndexcoercion을 전담합니다.exec호출에 대한 상대적인ToLength호출 순서가 잘못되면,lastIndex의 observable side effect가 spec이 정한 순서와 다르게 발생할 수 있습니다. 이 경우 type confusion으로 이어질 가능성이 있습니다.