fix(js-exec): honor encoding arg in Buffer.from / toString / write / byteLength#256
Merged
Merged
Conversation
…byteLength Add pure-JS encoders/decoders for base64, base64url, hex, latin1/binary, ascii, and utf16le inside BUFFER_MODULE_SOURCE. Dispatch on a normalized encoding string in Buffer.from, Buffer.prototype.toString, Buffer.prototype.write, and Buffer.byteLength so the QuickJS shim matches real Node.js Buffer semantics for every encoding Node supports. - _normEnc: normalizes encoding aliases (binary→latin1, ucs2→utf16le, etc.) - _hexEncode/_hexDecode: lenient hex (stops at first invalid char) - _latin1Encode/_latin1Decode, _asciiEncode/_asciiDecode - _utf16leEncode/_utf16leDecode - _b64Encode/_b64UrlEncode/_b64Decode: forgiving base64 (ignores whitespace, tolerates missing padding, supports base64url alphabet) Adds js-exec.buffer-encoding.test.ts with 29 cases covering encode-only, decode-only, round-trips, binary bytes (0xff/0xfe/0x00), alias names, and forgiving inputs. Adds a guardrail comment above _normEnc explaining why round-trip tests alone would mask a broken encoding implementation.
Replace _b64Decode(value).length with a length-based formula (strip non-alphabet + padding, then floor(len * 3 / 4)). Matches Node's Buffer.byteLength semantics and avoids allocating an array just to count bytes.
…m correctness - Replace _latin1Encode/_asciiEncode with shared _rawEncode (both truncate to low byte; encode semantics are identical) - Fix Buffer.from(ArrayBuffer, byteOffset, length) to respect offset and length args - Fix Buffer.byteLength to throw TypeError for non-string/non-Buffer input instead of returning 0 - Fix Buffer.byteLength base64 regex to correctly handle mid-string '=' as terminator - Fix Buffer.prototype.toString to clamp negative start to 0, matching Node behavior - Fix Buffer.prototype.write to throw RangeError for negative/out-of-range offset
|
@Hazzng is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
Contributor
Author
|
@cramforce i just did some fixes for The Buffer shim inside the js-exec runtime (encoding issues). Would you mind having a look please ? Thanks |
Contributor
|
From codex review: • Findings:
I did not run the project test suite. I only used local Node snippets to verify the expected Node behavior for these edge cases. |
…n, toString negative end
- Buffer.from(ArrayBuffer, offset, length) now shares the backing store as Node does
(previously the inner Uint8Array was copied through the Buffer constructor).
- Buffer.prototype.write throws RangeError for negative length or length > remaining,
matching Node's ERR_OUT_OF_RANGE rather than silently clamping.
- Buffer.prototype.toString clamps negative end to 0 (Node behavior), so
Buffer.from("abc").toString("utf8", 0, -1) returns "" instead of "ab".
Adds edge-case tests covering all three behaviors.
…writes to remaining
Per vercel bot review: Node validates the `length` arg against the buffer's
total size, not the remaining space after `offset`, and clamps actual writes
to the remaining space. The previous fix validated against remaining, which
incorrectly threw for cases like `Buffer.alloc(5).write('abcde', 3, 5)` that
Node accepts (writing 2 bytes).
Adds a regression test for the offset+length clamping case.
Contributor
Author
|
@cramforce are there any updates on this fix as i kinda need this. Thanks |
cramforce
approved these changes
Jun 4, 2026
This was referenced Jun 4, 2026
Merged
nunofgs
pushed a commit
to nunofgs/just-bash
that referenced
this pull request
Jun 9, 2026
…byteLength (vercel-labs#256) * fix(js-exec): honor encoding arg in Buffer.from / toString / write / byteLength Add pure-JS encoders/decoders for base64, base64url, hex, latin1/binary, ascii, and utf16le inside BUFFER_MODULE_SOURCE. Dispatch on a normalized encoding string in Buffer.from, Buffer.prototype.toString, Buffer.prototype.write, and Buffer.byteLength so the QuickJS shim matches real Node.js Buffer semantics for every encoding Node supports. - _normEnc: normalizes encoding aliases (binary→latin1, ucs2→utf16le, etc.) - _hexEncode/_hexDecode: lenient hex (stops at first invalid char) - _latin1Encode/_latin1Decode, _asciiEncode/_asciiDecode - _utf16leEncode/_utf16leDecode - _b64Encode/_b64UrlEncode/_b64Decode: forgiving base64 (ignores whitespace, tolerates missing padding, supports base64url alphabet) Adds js-exec.buffer-encoding.test.ts with 29 cases covering encode-only, decode-only, round-trips, binary bytes (0xff/0xfe/0x00), alias names, and forgiving inputs. Adds a guardrail comment above _normEnc explaining why round-trip tests alone would mask a broken encoding implementation. * perf(js-exec): compute base64 byteLength in O(1) without decoding Replace _b64Decode(value).length with a length-based formula (strip non-alphabet + padding, then floor(len * 3 / 4)). Matches Node's Buffer.byteLength semantics and avoids allocating an array just to count bytes. * refactor(js-exec): consolidate ascii/latin1 encode and fix Buffer shim correctness - Replace _latin1Encode/_asciiEncode with shared _rawEncode (both truncate to low byte; encode semantics are identical) - Fix Buffer.from(ArrayBuffer, byteOffset, length) to respect offset and length args - Fix Buffer.byteLength to throw TypeError for non-string/non-Buffer input instead of returning 0 - Fix Buffer.byteLength base64 regex to correctly handle mid-string '=' as terminator - Fix Buffer.prototype.toString to clamp negative start to 0, matching Node behavior - Fix Buffer.prototype.write to throw RangeError for negative/out-of-range offset * chore: bump patch version for changeset * fix(js-exec): Buffer shim ArrayBuffer sharing, write length validation, toString negative end - Buffer.from(ArrayBuffer, offset, length) now shares the backing store as Node does (previously the inner Uint8Array was copied through the Buffer constructor). - Buffer.prototype.write throws RangeError for negative length or length > remaining, matching Node's ERR_OUT_OF_RANGE rather than silently clamping. - Buffer.prototype.toString clamps negative end to 0 (Node behavior), so Buffer.from("abc").toString("utf8", 0, -1) returns "" instead of "ab". Adds edge-case tests covering all three behaviors. * fix(js-exec): validate Buffer.write length against total size, clamp writes to remaining Per vercel bot review: Node validates the `length` arg against the buffer's total size, not the remaining space after `offset`, and clamps actual writes to the remaining space. The previous fix validated against remaining, which incorrectly threw for cases like `Buffer.alloc(5).write('abcde', 3, 5)` that Node accepts (writing 2 bytes). Adds a regression test for the offset+length clamping case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
Buffershim inside the QuickJS-backedjs-execruntime ignored theencodingargument on every encoding-aware method. All calls silently fell through to UTF-8, sobase64,hex,latin1,ascii, andutf16lewere broken. Encode + decode tests masked this because both sides were no-ops.Failing cases (before fix)
Run inside
js-exec(QuickJS), compared against real Node:Buffer.from([0xff,0xfe,0x00]).toString('base64')"""//4A"Buffer.from([0xff,0xfe,0x00]).toString('hex')"""fffe00"Buffer.from('//4A', 'base64')<Buffer 2f 2f 34 41>(raw utf8 of the string)<Buffer ff fe 00>Buffer.from('68656c6c6f', 'hex').toString()"68656c6c6f""hello"Buffer.byteLength('aGVsbG8=', 'base64')8(utf8 length of input string)5Buffer.from('hello', 'latin1').toString('latin1')0x80–0xff)Root cause: every entry point built the byte array via
_utf8Encode(str)regardless of theencodingargument;toStringsymmetrically decoded as UTF-8.Solution
Added pure-JS encode/decode helpers inside
BUFFER_MODULE_SOURCE(the IIFE that runs in QuickJS whereTextEncoder/atob/btoaare unavailable), and wired them into the four entry points:Buffer.from(str, encoding)buf.toString(encoding)Buffer.byteLength(str, encoding)buf.write(str, offset, length, encoding)Encodings supported:
utf8,base64,base64url,hex,latin1/binary,ascii,utf16le/ucs2. Node's lenient parsing is matched (base64 ignores whitespace + missing padding; hex stops at first invalid char). Unknown encodings throwTypeError.After fix — verified against
node -eByte-for-byte match with
node -erunning the same snippet.Tests
New file
js-exec.buffer-encoding.test.ts— 29 cases:0xff,0xfe,0x00) — these were the broken pathbinary,ucs2,utf-8)Existing
js-exec.node-compat.test.ts(120 tests) stays green — no regressions.Verification
Manual spot-checks against
node -econfirmed byte-for-byte match foraGVsbG8=,hello,//4A,68656c6c6f,ff00ab, andbyteLength= 5.