Follow up #220/#230: drop unused timeout id and emit U+FFFD for malformed UTF-16 - #235
Conversation
DispatchImpl still accepted an id after BabylonJS#220, but Dispatch always passed 0. If a future caller reused an existing id, unordered_map::insert would keep the old Timeout and m_timeMap would gain a second entry pointing at it; Clear() erases only one. Always allocate a fresh id. The UTF-16 decoder dropped a trailing odd byte and passed unpaired surrogates through to Napi::String. The Encoding Standard replacement mode requires U+FFFD for both, in either endianness. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da74bc94-a7dc-4817-bd81-59b5c6b123fc
There was a problem hiding this comment.
🟢 Approval recommended
The changes are focused, match the stated follow-up goals, and are supported by targeted new tests for the UTF-16 edge cases.
Pull request overview
This PR is a follow-up to prior review comments in #220 and #230, tightening TimeoutDispatcher’s timeout-id handling to avoid unsafe reuse paths and aligning the UTF-16 TextDecoder implementation with the Encoding Standard’s replacement behavior for malformed input.
Changes:
- Removes the unused
DispatchImplpath and ensuresTimeoutDispatcher::Dispatchalways allocates a fresh timeout id. - Updates UTF-16 decoding to emit U+FFFD for a trailing odd byte and for unpaired surrogates, matching replacement-mode behavior.
- Adds unit tests covering malformed UTF-16 edge cases for both
utf-16leandutf-16be, and updates the polyfill README accordingly.
File summaries
| File | Description |
|---|---|
| Tests/UnitTests/Scripts/tests.ts | Adds JS tests for malformed UTF-16 replacement behavior in TextDecoder. |
| Polyfills/TextDecoder/Source/TextDecoder.cpp | Implements replacement-mode malformed UTF-16 handling (odd byte + unpaired surrogates). |
| Polyfills/TextDecoder/README.md | Documents the updated malformed UTF-16 replacement behavior. |
| Polyfills/Scheduling/Source/TimeoutDispatcher.h | Removes the now-unused DispatchImpl declaration. |
| Polyfills/Scheduling/Source/TimeoutDispatcher.cpp | Removes DispatchImpl and makes Dispatch always allocate a fresh id to avoid unsafe reuse/collision behavior. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The previous wording said errors were "not detected" while also describing U+FFFD replacement. fatal still does not throw; replacement is the supported behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da74bc94-a7dc-4817-bd81-59b5c6b123fc
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Use try_emplace and only insert into m_timeMap from the owned map entry so a colliding id cannot destroy the Timeout and leave a dangling pointer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: da74bc94-a7dc-4817-bd81-59b5c6b123fc
Summary
Follow-up for two post-merge comments:
TimeoutDispatcher (
#220)DispatchImplstill took anid, but after #220 the only caller isDispatch, which always passed0. Theid == 0branch was therefore dead, and the remaining reuse path is unsafe:m_idMap.insertkeeps the existingTimeouton collision, thenm_timeMap.insertadds a second entry pointing at it, andClear()erases only one.Dispatchnow always allocates a fresh id viaNextTimeoutId()and emplaces the newTimeout.DispatchImplis gone.TextDecoder UTF-16 (
#230)The UTF-16 path dropped a trailing odd byte and created JS strings from unpaired surrogates. Replacement mode in the Encoding Standard requires U+FFFD for both, in either endianness.
DecodeUtf16now:Tests
U+D800then'A') →"\uFFFDA""\uFFFD"Existing valid-pair and BOM tests are unchanged.