DSH / Atlas
2026-08-23implementedbug-fixarchived 2026-09-04

Win32 folder-picker paths stop truncating at U+XX00 code units

Win32 目录选择器路径不再在 U+XX00 码元处截断

`readUtf16` in `packages/host/directory-picker-native/src/win32-dialog-bindings.ts` translated the `IFileOpenDialog` result buffer by scanning for a zero byte with `bytes[end] !== 0`. UTF-16LE encodes NUL as two zero bytes, so any BMP code unit whose low byte is zero — U+XX00, such as 开 (U+5F00) — ended the scan early. Selecting a folder like `C:\Users\XIAOPAN\Desktop\安卓开发` returned `C:\Users\XIAOPAN\Desktop\安卓`, and

English

Problem

readUtf16 in packages/host/directory-picker-native/src/win32-dialog-bindings.ts translated the IFileOpenDialog result buffer by scanning for a zero byte with bytes[end] !== 0. UTF-16LE encodes NUL as two zero bytes, so any BMP code unit whose low byte is zero — U+XX00, such as 开 (U+5F00) — ended the scan early. Selecting a folder like C:\Users\XIAOPAN\Desktop\安卓开发 returned C:\Users\XIAOPAN\Desktop\安卓, and the workspace-creation call failed with workspace-invalid-path ... ENOENT.

Decision

The scan ends only when both bytes of a code unit are zero, still advancing two bytes at a time over the same 32KiB koffi.view buffer. A regression test drives readUtf16 through the existing fake koffi COM world with a path containing 安卓开发 (U+5F00), so the termination rule is proven without a real Windows host.

The fix is adopted verbatim from the community patch series on the fix/win32-utf16-nul-truncation branch of the ericcaiwx-star fork — c8aac14703 for the scan fix and e1d6265cb9 for the fixture cleanup — reported in discussion #580 (earlier reported in discussion #563). Both cherry-picks retain the original author, ericcaiwx-star; the upstream fork is the source of record for the patch.

Alternatives considered

Reject the community patch and rewrite the scan locally. Rejected: the patch is minimal, fits the dialog's existing test approach, and a byte-identical cherry-pick preserves provenance and credit.

Decode the whole buffer with toString('utf16le') and split at \0. Rejected: it copies the entire buffer instead of scanning, and the split would still depend on the same two-zero-byte rule.

Ask COM or koffi for a string length. Rejected: the binding surface provides no length; the double-zero scan is the standard UTF-16LE NUL test.

Consequences

  • Any path containing a U+XX00 code unit survives the picker translation; paths with such characters (for example Chinese folder names) can be selected and used to create workspaces.
  • The fix changes no ABI usage, buffer size, or dialog flow; the COM child-process architecture in the Win32 folder dialog note is untouched.
  • Real-dialog rendering and selection remain a manual Windows check; this change's regression test exercises only the byte-to-string translation against the fake COM world. The fixture path is synthetic (C:\fixture\安卓开发) so no real user path appears in the repository.

中文

问题

packages/host/directory-picker-native/src/win32-dialog-bindings.ts 的 readUtf16 用 bytes[end] !== 0 扫描 IFileOpenDialog 结果缓冲区来寻找零字节。UTF-16LE 真正的 NUL 是两个零字节,因此任何低字节为 0 的 BMP 码元——U+XX00,例如「开」(U+5F00)——都会提前结束扫描。选择 C:\Users\XIAOPAN\Desktop\安卓开发 这类目录会得到 C:\Users\XIAOPAN\Desktop\安卓,随后创建工作区的调用以 workspace-invalid-path ... ENOENT 失败。

决策

扫描只有在一个码元的两个字节都为零时才结束,仍按每次两个字节在同一个 32KiB koffi.view 缓冲区上推进。回归测试通过既有的假 koffi COM 世界驱动 readUtf16,路径包含「安卓开发」(U+5F00),从而不依赖真实 Windows 主机验证终止规则。

修复逐字采用 ericcaiwx-star fork 的 fix/win32-utf16-nul-truncation 分支上的社区补丁系列——c8aac14703 是扫描修复,e1d6265cb9 是 fixture 清理——在 discussion #580 报告(更早在 discussion #563 报告)。两次 cherry-pick 均保留原作者 ericcaiwx-star;上游 fork 是补丁的记录来源。

考虑过的替代方案

拒绝社区补丁,本地重写扫描。 拒绝:补丁极小,与目录选择器现有测试方式一致;逐字节一致的 cherry-pick 保留来源与署名。

用 toString('utf16le') 解码整个缓冲区再按 \0 切分。 拒绝:复制整个缓冲区而非扫描,且切分仍依赖同一「双零字节」规则。

向 COM 或 koffi 索取字符串长度。 拒绝:绑定面不提供长度;双零扫描是标准的 UTF-16LE NUL 判定。

后果

  • 任何含 U+XX00 码元的路径组件都能通过选择器转译;含这类字符的路径(例如中文目录名)可以选中并用于创建工作区。
  • 修复不改变 ABI 用法、缓冲区大小或对话框流程;Win32 目录选择器 note 中的 COM 子进程架构不受影响。
  • 真实对话框渲染与选择仍是手动 Windows 检查;本次回归测试只针对假 COM 世界中的字节到字符串转译。fixture 路径为合成路径(C:\fixture\安卓开发),仓库中不出现真实用户路径。