Conversation
|
Warning Review limit reached
Next review available in: 21 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
Comment |
|
Updated 4:05 PM PT - Jul 6th, 2026
❌ @robobun, your commit 637e741 has some failures in 🧪 To try this PR locally: bunx bun-pr 33465That installs a local version of the PR into your bun-33465 --bun |
There was a problem hiding this comment.
LGTM — narrow guard mirroring the detached-buffer check right above it, with thorough tests.
Extended reasoning...
Overview
Adds a zero-length guard to SerializedScriptValue::fromArrayBuffer in src/jsc/bindings/webcore/SerializedScriptValue.cpp so that bun:jsc deserialize() and v8.deserialize() throw on empty input instead of leaking WebCore's internal null-value sentinel. The size computation is hoisted a few lines up (pure move, no semantic change) and a new if (!size) block is inserted that mirrors the existing !arrayBuffer || arrayBuffer->isDetached() guard immediately above it: sets *didFail, conditionally throws via maybeThrowExceptionIfSerializationFailed(..., ValidationError), and returns jsUndefined(). Tests in test/js/bun/jsc/bun-jsc.test.ts cover six empty-buffer shapes across both APIs plus round-trip guards for null and an empty Buffer.
Security risks
None. This tightens input validation (rejects malformed input that was previously silently accepted as null). No new parsing, no new allocation, no untrusted data flows introduced.
Level of scrutiny
Low-to-moderate. The change is ~15 lines of C++ in a Bun-specific entry point — fromArrayBuffer's only caller is functionDeserialize in BunJSCModule.h; structuredClone, postMessage, and IPC go through a different function and are untouched. The new block is a copy of the pattern directly above it, uses the existing ValidationError → TypeError: Unable to deserialize data. path (verified at SerializedScriptValue.cpp:5966-5967), and has correct exception-scope handling (RETURN_IF_EXCEPTION after the maybe-throw). It is a user-facing behavior change, but one that aligns Bun with Node.js and closes the only remaining hole in an otherwise consistent "malformed payload throws" contract.
Other factors
No bugs from the bug hunter. No CODEOWNERS on this path. Tests are comprehensive (parameterized over Buffer/Uint8Array/DataView/ArrayBuffer/SharedArrayBuffer/zero-length-view-of-nonempty-buffer) and include the two obvious regression guards. PR description documents USE_SYSTEM_BUN=1 fail / bun bd pass and BUN_JSC_validateExceptionChecks=1 clean. No prior reviewer comments to address.
CloneDeserializer treats empty data as WebCore's null-value sentinel (SerializedScriptValue::nullValue()), returning (jsNull(), UnspecifiedError), and maybeThrowExceptionIfSerializationFailed deliberately leaves UnspecifiedError unthrown. SerializedScriptValue::fromArrayBuffer, the entry point for caller-supplied bytes behind bun:jsc and node:v8 deserialize(), then handed that null back, so an empty Buffer/Uint8Array/DataView/ArrayBuffer produced a plausible null instead of failing. Bytes handed in by a caller carry no sentinel meaning: zero bytes have no version header, so reject them with the same validation error every other unparsable payload already gets.
08dfbef to
8f367de
Compare
There was a problem hiding this comment.
LGTM — small, well-scoped validation fix that mirrors the existing detached-buffer guard, with thorough test coverage.
Extended reasoning...
Overview
This PR closes a hole in bun:jsc / node:v8 deserialize(): zero-length input previously returned null (via WebCore's null-value sentinel) instead of throwing like every other malformed payload. The fix adds an early if (!size) guard in SerializedScriptValue::fromArrayBuffer (SerializedScriptValue.cpp:6646-6660) that emits ValidationError through the existing maybeThrowExceptionIfSerializationFailed path, and adds a matching test block in test/js/bun/jsc/bun-jsc.test.ts.
Security risks
None. The change tightens input validation on user-supplied bytes rather than loosening it. No new parsing, allocation, or external I/O is introduced.
Level of scrutiny
Low-to-moderate. The new guard is a near-verbatim copy of the detached-ArrayBuffer guard immediately above it (same didFail write, same throwExceptions gate, same jsUndefined() return), and reuses the existing ValidationError → TypeError: Unable to deserialize data. mapping so the error is consistent with other malformed inputs. RETURN_IF_EXCEPTION is present after the throw. The size computation was hoisted a few lines earlier but is a pure read of arrayBuffer->byteLength() with no ordering dependency on the code it moved past. I confirmed fromArrayBuffer's only C++ caller is functionDeserialize in BunJSCModule.h, so structuredClone/postMessage/IPC (which go through SerializedScriptValue::deserialize) are unaffected — matching the PR description.
Other factors
Test coverage is strong: 6 empty-input shapes (including a zero-length view over a non-empty backing buffer) exercised through both bun:jsc and node:v8, plus positive guards that serialize(null) and serialize(Buffer.alloc(0)) still round-trip. The description documents that the new tests fail under USE_SYSTEM_BUN=1 and pass under bun bd, and that BUN_JSC_validateExceptionChecks=1 is clean. The bug-hunting system found no issues, and there are no prior reviewer comments on the PR.
CI triage — diff is green, remaining red is infraThe diff is unchanged throughout: 18 lines in
The recurring |
There was a problem hiding this comment.
LGTM — focused Node compat fix that mirrors the existing detached-buffer guard, with thorough test coverage.
Extended reasoning...
Overview
Adds a zero-length guard to SerializedScriptValue::fromArrayBuffer in src/jsc/bindings/webcore/SerializedScriptValue.cpp so that bun:jsc deserialize() and v8.deserialize() throw on empty input instead of returning null (WebCore's null-value sentinel leaking through). Tests added to test/js/bun/jsc/bun-jsc.test.ts cover six empty-input shapes across both APIs plus regression guards for serialized null and serialized empty Buffer.
Security risks
None. This tightens validation of caller-supplied bytes (fail-closed rather than fail-open). No new parsing, no untrusted-size arithmetic, no allocation.
Level of scrutiny
Low-to-moderate. The C++ change is ~15 lines that copy the exact shape of the detached-buffer guard immediately above it: set *didFail, gate on throwExceptions == Throwing, call maybeThrowExceptionIfSerializationFailed(..., ValidationError) (which throws the same TypeError: Unable to deserialize data. as every other malformed payload), RETURN_IF_EXCEPTION, return jsUndefined(). The size = std::min(...) computation was hoisted above the new guard unchanged. I confirmed fromArrayBuffer's only callers are the two sites in BunJSCModule.h::functionDeserialize, so structuredClone/postMessage/IPC are untouched as claimed.
Other factors
- The PR description includes root-cause analysis, a before/after table,
USE_SYSTEM_BUN=1failure verification,BUN_JSC_validateExceptionChecks=1validation, and green runs on adjacent structured-clone/worker test files. - CI triage shows 107 passed / 0 failed (only agent-starvation expirations, pipeline-wide).
- No CODEOWNERS on the touched paths.
- No prior human review comments to address.
Problem
v8.deserialize()(andbun:jsc'sdeserialize()) manufactures anullout of zero bytes instead of throwing:An empty buffer is what a truncated file, an empty IPC frame, or a zero-length redis/S3 value looks like, which is exactly what the wire-format header check exists to catch. Code that stores
serialize()output and laterdeserialize()s it gets back an ordinary-looking application value instead of an error.Every other malformed payload already throws, so zero-length was the only hole:
Buffer.alloc(0)nullBuffer.from([0])TypeError: Unable to deserialize data.Buffer.from("abc")TypeError: Unable to deserialize data.v8.serialize({a:1}).subarray(0, 2)TypeError: Unable to deserialize data.Cause
CloneDeserializer::deserializemaps an empty buffer to(jsNull(), SerializationReturnCode::UnspecifiedError). That is WebCore's null-value sentinel: an emptym_datais howSerializedScriptValue::nullValue()is represented, somaybeThrowExceptionIfSerializationFaileddeliberately leavesUnspecifiedErrorunthrown.SerializedScriptValue::fromArrayBufferis the entry point for bytes supplied by a caller (its only caller isfunctionDeserializeinBunJSCModule.h, behindbun:jsc'sdeserialize(), whichnode:v8'sdeserialize()wraps). It passed the sentinel straight through and returned thejsNull().Fix
fromArrayBuffernow rejects an empty span before handing it to the deserializer. Bytes from a caller carry no sentinel meaning: zero bytes have no version header, so they fail with the sameValidationErrorevery other unparsable payload already produces (TypeError: Unable to deserialize data.).This covers
Buffer,Uint8Array,DataView,ArrayBuffer,SharedArrayBuffer, and a zero-length view over a non-empty backing buffer.structuredClone(),postMessage(), andchild_processIPC go throughSerializedScriptValue::deserialize, a different function, and are untouched.The thrown error's class and message still differ from Node's (
TypeError: Unable to deserialize data.vsError: Unable to deserialize cloned data due to invalid or unsupported version.). That divergence already applies to every malformed payload, because Bun's wire format is JSC's structured clone rather than V8's; matching Node only for the empty case would have madev8.deserializeinconsistent with itself.Verification
New tests in
test/js/bun/jsc/bun-jsc.test.tscover both APIs, all six empty shapes, and the two things that must keep working: a value that really isnull, and a serialized emptyBuffer.Also green: the whole
bun-jsc.test.tsfile,test/js/web/structured-clone-blob-file.test.ts,test/js/web/workers/structured-clone.test.ts,structuredClone-classes.test.ts,worker-postmessage-transfer.test.ts, and Node'stest/parallel/test-v8-deserialize-buffer.js.BUN_JSC_validateExceptionChecks=1reports no unchecked exception on the new throw path.