Skip to content

TextDecoder: emit U+FFFD for malformed UTF-16 - #235

Open
bkaradzic-microsoft wants to merge 2 commits into
BabylonJS:mainfrom
bkaradzic-microsoft:fix/post-merge-followups
Open

TextDecoder: emit U+FFFD for malformed UTF-16#235
bkaradzic-microsoft wants to merge 2 commits into
BabylonJS:mainfrom
bkaradzic-microsoft:fix/post-merge-followups

Conversation

@bkaradzic-microsoft

@bkaradzic-microsoft bkaradzic-microsoft commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #230 (comment).

The UTF-16 decoder previously dropped a trailing odd byte and created JavaScript strings from unpaired surrogates. Replacement mode in the Encoding Standard requires U+FFFD for both, in either endianness.

DecodeUtf16 now:

  • emits U+FFFD for a leftover odd byte
  • emits U+FFFD for a lone lead or trail surrogate
  • treats a lead followed by a non-trail as one replacement, then reprocesses the second unit
  • treats an unpaired lead plus a leftover odd byte as a single end-of-queue replacement

The README describes this behavior and the still-unsupported fatal option.

Timer cleanup after rebase

Correction to the original description, per Gary's review: Dispatch always passed 0, so the id == 0 branch was always taken. The nonzero ID-reuse path was dead.

The latest main already includes the timer refactor from #232, which removes DispatchImpl and guards against duplicate-ID insertion. The rebase keeps that implementation unchanged and drops this PR's superseded timer changes. The remaining diff contains only TextDecoder, its documentation, and the UTF-16 regression cases.

Regression coverage

  • Trailing odd byte, both endiannesses
  • Lone lead / lone trail, both endiannesses
  • Lead + BMP (U+D800 then 'A') produces "\uFFFDA"
  • Lead + leftover odd byte produces a single "\uFFFD"

Existing valid-pair and BOM cases are unchanged.

Copilot AI lite review requested due to automatic review settings September 3, 2026 22:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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 DispatchImpl path and ensures TimeoutDispatcher::Dispatch always 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-16le and utf-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.

Comment thread Polyfills/TextDecoder/README.md Outdated
@bkaradzic-microsoft
bkaradzic-microsoft requested a lite review from Copilot September 3, 2026 22:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Polyfills/Scheduling/Source/TimeoutDispatcher.cpp Outdated

@bghgary bghgary left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Reviewed by Copilot on behalf of @bghgary]

Please correct the PR description: id == 0 was always taken; the nonzero ID-reuse path was dead.

bkaradzic and others added 2 commits September 10, 2026 17:56
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
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
@bkaradzic-microsoft bkaradzic-microsoft changed the title Follow up #220/#230: drop unused timeout id and emit U+FFFD for malformed UTF-16 TextDecoder: emit U+FFFD for malformed UTF-16 Sep 11, 2026
@bkaradzic-microsoft

Copy link
Copy Markdown
Member Author

@bghgary Corrected the description: id == 0 was always taken; the nonzero ID-reuse path was dead.

I also rebased this PR onto the latest main. #232 already includes the timer cleanup and duplicate-ID guard, so I retained the upstream scheduling implementation unchanged and dropped the superseded timer changes. The remaining diff is only the malformed UTF-16 fix, its README updates, and the regression cases; the PR title now reflects that scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants