feat(node:buffer): implement transcode() - #32459
Conversation
`buffer.transcode(source, fromEncoding, toEncoding)` previously threw "Not implemented". Implement it for the encodings Node supports (ascii, latin1/binary, ucs2/utf16le, utf8), substituting '?' for code points the target encoding cannot represent. The dispatch mirrors Node's src/node_i18n.cc: simdutf fast paths for ASCII/Latin-1 -> UCS2 and UTF8 <-> UCS2, with a UTF-16 pivot for the remaining pairs. Invalid arguments throw the same errors as Node (ERR_INVALID_ARG_TYPE, "Unable to transcode Buffer [...]"). Closes oven-sh#24235
WalkthroughReplaces the ChangesBuffer.transcode() implementation
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/jsc/modules/NodeBufferModule.h`:
- Around line 421-441: The transcode function reads the byteLength() at line 426
and copies from typedVector() at line 441 without validating that the source
Uint8Array is not detached. Add a detached-view check immediately after the
source parameter validation (after the dynamicDowncast check) to ensure the
Uint8Array has valid backing storage before attempting to read byteLength or
copy data. Reference the detached-view guard pattern used in neighboring isUtf8
and isAscii paths to implement this validation consistently.
- Around line 396-405: The error check on line 402 that treats zero-length UTF-8
conversion results as U_INVALID_CHAR_FOUND is incorrect because line 396
intentionally truncates odd UCS2 byte lengths to get complete char16_t units,
which can legitimately result in zero-length output (e.g., a one-byte input
produces zero char16_t units). Remove or modify the `if (!length)` error
condition to allow zero-length results as valid outcomes, so that inputs with no
complete code units return an empty buffer instead of an error status, making
this behavior consistent with the generic UCS2 conversion path.
In `@test/js/node/buffer.test.js`:
- Around line 2561-2586: The test assertions need tightening to verify stronger
invariants. First, in the empty input test for BufferModule.transcode with empty
buffer, replace the valid encoding strings with invalid ones to actually verify
that encodings are not being validated when input is empty. Second, in the error
handling loop testing unsupported encodings, add assertions beyond just checking
the error message - specifically assert that the error object has the correct
code property (either ERR_INVALID_ARG_TYPE.code or U_ILLEGAL_ARGUMENT_ERROR) and
verify any errno property that the native implementation sets. This ensures each
assertion can actually fail and tests the complete error metadata rather than
just the message string.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6a368d2d-7cd8-4a0a-b710-889cfe96400a
📒 Files selected for processing (2)
src/jsc/modules/NodeBufferModule.htest/js/node/buffer.test.js
…code() Reject a detached Uint8Array source with ERR_INVALID_STATE before reading byteLength or copying its backing storage, matching the detached-view guard used by isUtf8/isAscii. Return an empty Buffer when a ucs2 source has no complete code units (e.g. a single-byte input) instead of reporting U_INVALID_CHAR_FOUND, mirroring the generic ucs2 conversion path.
|
Addressed the review in cce0575:
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/jsc/modules/NodeBufferModule.h (1)
396-405:⚠️ Potential issue | 🔴 CriticalValidate UTF-16LE length before converter call for malformed input safety.
The code at lines 403–404 allocates a destination buffer sized by
utf8_length_from_utf16le, then unconditionally callsconvert_utf16le_to_utf8with that buffer. If the input contains malformed UTF-16 surrogates,utf8_length_from_utf16lereturns 0, resulting in a zero-sized buffer passed to the converter.While simdutf's
convert_utf16le_to_utf8is documented to return 0 (not write) for malformed input, it is safer to validate the length before the conversion call rather than rely on this contract post-hoc. Add an early check:if (simdutf::utf8_length_from_utf16le(...) == 0)before allocating and converting. Alternatively, usesimdutf::convert_utf16le_to_utf8_with_errorsfor explicit malformed-input detection.The same pattern exists at lines 346–349 in the
transcodeGenericpath and should be fixed in the same PR.Test coverage for malformed surrogates in well-formed UCS2 pairs is absent; the existing test at line 2573 covers only odd-length input.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/jsc/modules/NodeBufferModule.h` around lines 396 - 405, Add an early validation check after calling utf8_length_from_utf16le to ensure the returned length is non-zero before allocating the result buffer and calling convert_utf16le_to_utf8. Store the result of utf8_length_from_utf16le in a variable, check if it equals zero and return an appropriate empty buffer if so, then proceed with the Vector allocation and conversion only if the length is valid. Apply the same fix to the transcodeGeneric path around lines 346-349 where the identical pattern exists. Additionally, add test coverage to verify the handling of malformed UTF-16 surrogates in input that is otherwise well-formed UCS2 pairs, beyond the existing test that only covers odd-length input.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/jsc/modules/NodeBufferModule.h`:
- Around line 396-405: Add an early validation check after calling
utf8_length_from_utf16le to ensure the returned length is non-zero before
allocating the result buffer and calling convert_utf16le_to_utf8. Store the
result of utf8_length_from_utf16le in a variable, check if it equals zero and
return an appropriate empty buffer if so, then proceed with the Vector
allocation and conversion only if the length is valid. Apply the same fix to the
transcodeGeneric path around lines 346-349 where the identical pattern exists.
Additionally, add test coverage to verify the handling of malformed UTF-16
surrogates in input that is otherwise well-formed UCS2 pairs, beyond the
existing test that only covers odd-length input.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: a810c28d-8a9a-4b8f-a01c-8c73814be3d0
📒 Files selected for processing (2)
src/jsc/modules/NodeBufferModule.htest/js/node/buffer.test.js
What does this PR do?
Implements
buffer.transcode(source, fromEncoding, toEncoding), which previously threwError: Not implemented. Fixes #24235.Supports the same encodings as Node.js —
ascii,latin1/binary,ucs2/utf16le,utf8— and substitutes?for code points the target encoding cannot represent (e.g.transcode(Buffer.from('€'), 'utf8', 'ascii')→?).The dispatch mirrors Node's
src/node_i18n.cc:simdutffast paths for ASCII/Latin-1 → UCS2 and UTF8 ↔ UCS2ICU's converter headers (
unicode/ucnv.h) aren't available in the build's header set, so the pivot is implemented directly rather than viaucnv_convertEx. Error behavior matches Node: a non-Uint8Arraysource throwsERR_INVALID_ARG_TYPE, and unsupported/unknown encodings throwUnable to transcode Buffer [U_ILLEGAL_ARGUMENT_ERROR]with matchingcode/errno.How did you verify your code works?
Added coverage in
test/js/node/buffer.test.js(replacing the old "is undefined / Not implemented" assertion):€utf8→ascii =?)test/parallel/test-icu-transcode.js(utf8→latin1/ascii/ucs2, ucs2→utf8 round-trip, ascii/latin1→utf16le)Uint8Arraysource, empty-buffer short-circuit, and theERR_INVALID_ARG_TYPE/U_ILLEGAL_ARGUMENT_ERRORerror pathsAlso ran Node's
€×4000 round-trip stress assertions against the debug build. The new test passes with the debug build and fails against the releasebun(proving it exercises the change); the fullbuffer.test.jsfile passes (506 pass, 0 fail).