Repository navigation
Conversation
Node does encoding += '' (ToPrimitive with the default hint) in getEncodingOps, so valueOf() runs before toString() on an object encoding and a Symbol throws a TypeError. parseEncoding and indexOf called toString() (hint string), and Buffer.byteLength ignored every encoding that is not a string primitive. Buffer.byteLength now follows Node's order: a non-string input and an empty string return before the encoding is read, a falsy encoding is utf8, and an unknown encoding after coercion falls back to utf8. Fixes #43426
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: oven-sh/bun/.coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review. WalkthroughChangesNode buffer encoding coercion
Suggested reviewers: Priority: ➖ Normal Severity of issue fixed: Medium 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Beyond the inline findings, I also checked the new encodingToString helper and the restructured byteLength for exception-scope hygiene: every toPrimitive/toString/view()/value() call is followed by RETURN_IF_EXCEPTION, toBoolean on the encoding cannot run user JS, and the string pointer is only read after the coercion that could run user code. I also looked at Bun's internal node:http/net/child_process callers of Buffer.byteLength — they pass string or undefined encodings, so the new object/Symbol coercion path is not reachable from them.
Extended reasoning...
Findings were reported inline, so this body only records what else was examined and ruled out. The C++ helper follows the ThrowScope conventions in the file (RETURN_IF_EXCEPTION after each throwing call, RELEASE_AND_RETURN on the tail call), toBoolean on a JSValue does not enter user code, and str is a JSString the EnsureStillAliveScope keeps rooted across the encoding coercion. The two ruled-out candidates from the hunt (internal callers passing object encodings, and existing callers relying on toString() on an encoding object) do not apply because Bun's built-in modules only pass string/undefined encodings and the new behavior matches Node for the object case. The inline findings (mustMatch third argument, write's validation order, and the indexOf test shape) remain the substantive concerns for a human to weigh.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟣
src/jsc/bindings/JSBuffer.cpp— Callers passing a non-string value and a Symbol encoding to buf.write get ERR_INVALID_ARG_TYPE in Bun where Node throws the Symbol-to-string TypeError. src/jsc/bindings/JSBuffer.cpp:2610 validatesstringbefore parseEncoding at :2619, while Node's write coerces the encoding first (getEncodingOps) and only validates the string inside ops.write. The diff adds the Symbol throw at :339-341 but keeps it behind the string check, so the doubly-invalid ordering now differs in error code as well as message. Fix: on the write paths (:2581 and :2619) coerce and validate the encoding before validateString at :2610, matching Node's ordering so the same error wins on doubly-invalid input.Extended reasoning...
Node's Buffer.prototype.write: offset/length are validated, then
if (!encoding) return this.utf8Write(...), thenconst ops = getEncodingOps(encoding)which doesencoding += ''and throws TypeError "Cannot convert a Symbol value to a string" for a Symbol, and only thenops.write(this, string, ...)where the native binding rejects a non-string with ERR_INVALID_ARG_TYPE. SoBuffer.alloc(4).write(123, 0, 4, Symbol())in Node throws the Symbol TypeError with no code. In Bun :2610 runs Bun::V::validateString first and throws ERR_INVALID_ARG_TYPE for "string"; :2619 is never reached. Likewisebuf.write(123, 0, 4, {})throws ERR_INVALID_ARG_TYPE in Bun but ERR_UNKNOWN_ENCODING in Node. The base branch had the same order, but the PR's stated goal is to match Node's getEncodingOps contract on write, and the new tests at test/js/node/buffer.test.js:4271-4279 only cover a valid string, so the ordering gap is untested. Population: code that catches by error code. The dismissing finder read both but treated 'both TypeErrors' as equivalent; the codes differ.Verification: pre-existing (nit-level). Trigger: buf.write called with a non-string
stringargument together with a Symbol (or any throwing/unknown) encoding in the offset/length form, e.g.Buffer.alloc(4).write(123, 0, 4, Symbol()). Mechanism verified in /home/claude/bun/src/jsc/bindings/JSBuffer.cpp: line 2610Bun::V::validateString(scope, lexicalGlobalObject, stringValue, "string"_s);runs first…
|
On the |
|
Updated 7:08 AM PT - Sep 19th, 2026
❌ @robobun, your commit 687b1a3 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 43442That installs a local version of the PR into your bun-43442 --bun |
|
CI status: the only red lane in build 118306 is |
|
A parallel branch has the same fix: The branch has more tests (14, in
Two results from that branch apply here too, because the C++ is the same:
The new |
Problem
encodingargument gets a different coercion than in Node.Buffer.byteLength("abcd", ["hex"])returns 4 (Node: 2),Buffer.byteLength("abcd", Symbol())returns 4 (Node: TypeError), andbuf.toString({ valueOf: () => "hex" })throwsERR_UNKNOWN_ENCODING(Node: hex output).buf.writeandbuf.indexOfbehave liketoString.getEncodingOpsdoesencoding += ''. That is ToPrimitive with the default hint, sovalueOf()runs beforetoString()and a Symbol throws. Bun'sparseEncoding(JSValue)(src/jsc/bindings/JSBuffer.cpp:334) andindexOf(:1844) used ToString (hint "string").byteLength(:834) ignored every encoding that is not a string primitive.Fix
encodingToString:toPrimitive(NoPreference)thentoString.parseEncoding(JSValue)andindexOfuse it.Buffer.byteLengthfollows Node's order. A non-string input and an empty string return before the encoding is read. A falsy encoding is utf8. Anything else is coerced, and an unknown name falls back to utf8.test/js/node/buffer.test.js, describe "non-string encoding argument is coerced like Node's getEncodingOps" (4 tests, all fail on bun 1.4.3). The whole file passes except for tests that time out under the debug build when the full file runs and pass alone.Background
buf.fillandBuffer.alloc(size, fill, encoding)are unchanged. They coerce an object encoding throughtoStringon purpose where Node throwsERR_INVALID_ARG_TYPE, and the fill tests pin that.Buffer.from(string, encoding)already matches Node and is unchanged.parseEncoding(scope, globalObject, arg1, false)call, which is the call site this helper feeds.Fixes #43426
Notes
Repro output after the fix, same order as the issue table:
2,2,throws TypeError,"616263","616263",2,0. Node v26.3.0 prints the same.Extra cases checked against Node and pinned in the test:
Buffer.byteLength("", Symbol())is 0 andBuffer.byteLength(Buffer.alloc(3), Symbol())is 3 (the encoding is never read),Buffer.byteLength("abcd", {})is 4 (unknown name falls back to utf8), an object withSymbol.toPrimitivereceives hint "default".Self-reviewed: 4 concerns raised. 2 addressed in this body (provenance of the issue, relation to #43432). 2 were about priority, not correctness, and need no code change.
Full
test/js/node/buffer.test.jsrun under the debug build: 678 pass, the failures areevery fill() argument shape,reads match a DataView,BigInt writes match a DataView, andoffset throws when a detaching valueOf runs mid-coercion. Each runs 6 to 10 s in the full file against a 5 s limit, the set changes between runs, and each passes when run alone. They pass on the release build.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/node/buffer.test.js