Skip to content

refactor: move ReadableStream-related functions from ZigGlobalObject.cpp to ReadableStream.cpp#23547

Merged
Jarred-Sumner merged 4 commits into
mainfrom
claude/move-readablestream-functions
Oct 12, 2025
Merged

refactor: move ReadableStream-related functions from ZigGlobalObject.cpp to ReadableStream.cpp#23547
Jarred-Sumner merged 4 commits into
mainfrom
claude/move-readablestream-functions

Conversation

@robobun

@robobun robobun commented Oct 12, 2025

Copy link
Copy Markdown
Collaborator

Summary

This PR moves all ReadableStream-related functions from ZigGlobalObject.cpp to ReadableStream.cpp for better code organization and maintainability.

Changes

Moved 17 functions from ZigGlobalObject.cpp to ReadableStream.cpp:

Core ReadableStream Functions

  • ReadableStream__tee - with invokeReadableStreamFunction helper (converted to lambda)
  • ReadableStream__cancel
  • ReadableStream__detach
  • ReadableStream__isDisturbed
  • ReadableStream__isLocked
  • ReadableStreamTag__tagged

Stream Creation & Conversion

  • ZigGlobalObject__createNativeReadableStream
  • ZigGlobalObject__readableStreamToArrayBufferBody
  • ZigGlobalObject__readableStreamToArrayBuffer
  • ZigGlobalObject__readableStreamToBytes
  • ZigGlobalObject__readableStreamToText
  • ZigGlobalObject__readableStreamToFormData
  • ZigGlobalObject__readableStreamToJSON
  • ZigGlobalObject__readableStreamToBlob

Host Functions

  • functionReadableStreamToArrayBuffer
  • functionReadableStreamToBytes

Technical Details

File Changes

  • ReadableStream.cpp: Added necessary includes and all ReadableStream functions
  • ReadableStream.h: Added forward declarations for code generator functions
  • ZigGlobalObject.cpp: Removed moved functions (394 lines removed)

Implementation Notes

  • Added proper namespace declarations (using namespace JSC; using namespace WebCore;) to all extern "C" functions
  • Used correct namespace qualifiers for types (e.g., Bun::IDLRawAny, Bun::AbortError)
  • Added required includes: WebCoreJSBuiltins.h, ZigGlobalObject.h, ZigGeneratedClasses.h, helpers.h, BunClientData.h, BunIDLConvert.h

Testing

  • ✅ Builds successfully with debug configuration
  • ✅ All functions maintain identical behavior
  • ✅ No API changes

Benefits

  1. Better code organization: ReadableStream functionality is now consolidated in one place
  2. Improved maintainability: Easier to find and modify ReadableStream-related code
  3. Reduced file size: ZigGlobalObject.cpp is now ~400 lines smaller

🤖 Generated with Claude Code

…cpp to ReadableStream.cpp

Moved all ReadableStream-related extern C functions and helper functions from ZigGlobalObject.cpp to ReadableStream.cpp for better code organization. This includes:

- invokeReadableStreamFunction helper (now local to ReadableStream__tee)
- ReadableStream__tee
- ReadableStream__cancel
- ReadableStream__detach
- ReadableStream__isDisturbed
- ReadableStream__isLocked
- ReadableStreamTag__tagged
- ZigGlobalObject__createNativeReadableStream
- ZigGlobalObject__readableStreamToArrayBufferBody
- ZigGlobalObject__readableStreamToArrayBuffer
- ZigGlobalObject__readableStreamToBytes
- ZigGlobalObject__readableStreamToText
- ZigGlobalObject__readableStreamToFormData
- ZigGlobalObject__readableStreamToJSON
- ZigGlobalObject__readableStreamToBlob
- functionReadableStreamToArrayBuffer
- functionReadableStreamToBytes

Added necessary includes and namespace declarations to ensure proper compilation.
@robobun

robobun commented Oct 12, 2025

Copy link
Copy Markdown
Collaborator Author
Updated 3:52 PM PT - Oct 12th, 2025

❌ Your commit 0b488d10 has 2 failures in Build #29004 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 23547

That installs a local version of the PR into your bun-23547 executable, so you can run:

bun-23547 --bun

The code generator function declarations are not needed in the header file since they're only used within ReadableStream.cpp.
@coderabbitai

coderabbitai Bot commented Oct 12, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Moved ReadableStream implementations: removed ReadableStream host/extern declarations from ZigGlobalObject.cpp and added full native ReadableStream externs and conversion/utility implementations in webcore/ReadableStream.cpp.

Changes

Cohort / File(s) Summary
ReadableStream glue added in WebCore
src/bun.js/bindings/webcore/ReadableStream.cpp
Adds extern "C" APIs and wrappers: ReadableStream__tee, ReadableStream__cancel, ReadableStream__detach, ReadableStream__isDisturbed, ReadableStream__isLocked, ReadableStreamTag__tagged, ZigGlobalObject__createNativeReadableStream, and ZigGlobalObject__readableStreamTo{ArrayBuffer,Bytes,Text,FormData,JSON,Blob} plus host functions functionReadableStreamToArrayBuffer and functionReadableStreamToBytes.
ZigGlobalObject cleanup
src/bun.js/bindings/ZigGlobalObject.cpp
Removes ReadableStream-related host function declarations/definitions and extern declarations: functionReadableStreamTo*, ReadableStream__*, ReadableStreamTag__tagged, ZigGlobalObject__readableStreamTo*, and ZigGlobalObject__createNativeReadableStream.

Possibly related PRs

Suggested reviewers

  • Jarred-Sumner
  • cirospaciari
  • zackradisic

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description does not adhere to the repository’s required template since it uses custom section headings like “## Summary” and “## Changes” instead of the mandated “### What does this PR do?” and “### How did you verify your code works?”, thus missing the exact template structure required by the repo. Please update the description to use the repository’s template by adding the “### What does this PR do?” section with a concise summary of the change and the “### How did you verify your code works?” section with details on testing and validation steps.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title succinctly and accurately describes the primary change of relocating ReadableStream-related functions from ZigGlobalObject.cpp into ReadableStream.cpp, following the repository’s conventional commit style and providing clarity to readers.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b8b9d70 and c7c2642.

📒 Files selected for processing (3)
  • src/bun.js/bindings/ZigGlobalObject.cpp (0 hunks)
  • src/bun.js/bindings/webcore/ReadableStream.cpp (2 hunks)
  • src/bun.js/bindings/webcore/ReadableStream.h (1 hunks)
💤 Files with no reviewable changes (1)
  • src/bun.js/bindings/ZigGlobalObject.cpp
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/webcore/ReadableStream.h
  • src/bun.js/bindings/webcore/ReadableStream.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: When a JS class has a public constructor, implement Foo, FooPrototype, and FooConstructor (JSDestructibleObject, JSNonFinalObject, InternalFunction)
Define class properties using HashTableValue arrays in C++ bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for C++-backed classes

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for C++-backed classes
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for C++-backed classes

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
🧬 Code graph analysis (1)
src/bun.js/bindings/webcore/ReadableStream.cpp (3)
src/bun.js/bindings/webcore/ReadableStream.h (5)
  • WebCore (34-115)
  • isLocked (55-66)
  • JSValue (90-93)
  • JSValue (95-98)
  • JSValue (100-103)
src/bun.js/bindings/BunClientData.h (1)
  • clientData (200-203)
src/js/builtins/ReadableStream.ts (4)
  • readableStreamToText (122-138)
  • readableStreamToFormData (300-309)
  • readableStreamToJSON (312-331)
  • readableStreamToBlob (334-342)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (11)
src/bun.js/bindings/webcore/ReadableStream.cpp (11)

267-319: ReadableStream__tee: approach looks good; error propagation preserved.

Lambda isolates throw scope, reports errors under ASSERT, and converts to two JSValues. LGTM.


470-487: createNativeReadableStream: overall fine.

Pattern matches other helpers (lookup builtin, call via CallData, assert exception invariant). LGTM.


489-529: readableStreamToArrayBufferBody: solid caching + type checks.

Function caches generator, validates Promise, and returns encoded promise with throw scope. LGTM.


530-536: ZigGlobalObject__readableStreamToArrayBuffer wrapper: LGTM.

Thin wrapper over Body function; consistent with others.


537-576: readableStreamToBytes: mirrors ArrayBuffer path correctly.

Caching and Promise validation are consistent. LGTM.


600-622: readableStreamToFormData: caching + args pass-through look good.

LGTM.


623-644: readableStreamToJSON: caching and dispatch look good.

LGTM.


645-666: readableStreamToBlob: consistent with others.

LGTM.


667-683: functionReadableStreamToArrayBuffer: argument validation present.

LGTM.


684-699: functionReadableStreamToBytes: argument validation present.

LGTM.


321-336: Verify cancel parity for unlocked streams
The early-return in ReadableStream__cancel prevents cancel on unlocked streams; spec expects cancel to always produce a promise. Confirm behavior and update if needed.

Comment thread src/bun.js/bindings/webcore/ReadableStream.cpp
Comment on lines +338 to +353
extern "C" void ReadableStream__detach(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject)
{
using namespace JSC;
using namespace WebCore;

auto value = JSC::JSValue::decode(possibleReadableStream);
if (value.isEmpty() || !value.isCell())
return;

auto* readableStream = static_cast<WebCore::JSReadableStream*>(value.asCell());
if (!readableStream) [[unlikely]]
return;
readableStream->setNativePtr(globalObject->vm(), jsNumber(-1));
readableStream->setNativeType(0);
readableStream->setDisturbed(true);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ReadableStream__detach updates JS objects without JSLock.

Acquire JSLock before mutating JSReadableStream’s state.

Apply:

 extern "C" void ReadableStream__detach(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject)
 {
     using namespace JSC;
     using namespace WebCore;

-    auto value = JSC::JSValue::decode(possibleReadableStream);
+    auto& vm = JSC::getVM(globalObject);
+    JSC::JSLockHolder lock(vm);
+    auto value = JSC::JSValue::decode(possibleReadableStream);
     if (value.isEmpty() || !value.isCell())
         return;

-    auto* readableStream = static_cast<WebCore::JSReadableStream*>(value.asCell());
+    auto* readableStream = jsDynamicCast<WebCore::JSReadableStream*>(value.asCell());
     if (!readableStream) [[unlikely]]
         return;
-    readableStream->setNativePtr(globalObject->vm(), jsNumber(-1));
+    readableStream->setNativePtr(vm, jsNumber(-1));
     readableStream->setNativeType(0);
     readableStream->setDisturbed(true);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extern "C" void ReadableStream__detach(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject)
{
using namespace JSC;
using namespace WebCore;
auto value = JSC::JSValue::decode(possibleReadableStream);
if (value.isEmpty() || !value.isCell())
return;
auto* readableStream = static_cast<WebCore::JSReadableStream*>(value.asCell());
if (!readableStream) [[unlikely]]
return;
readableStream->setNativePtr(globalObject->vm(), jsNumber(-1));
readableStream->setNativeType(0);
readableStream->setDisturbed(true);
}
extern "C" void ReadableStream__detach(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject)
{
using namespace JSC;
using namespace WebCore;
auto& vm = JSC::getVM(globalObject);
JSC::JSLockHolder lock(vm);
auto value = JSC::JSValue::decode(possibleReadableStream);
if (value.isEmpty() || !value.isCell())
return;
auto* readableStream = jsDynamicCast<WebCore::JSReadableStream*>(value.asCell());
if (!readableStream) [[unlikely]]
return;
readableStream->setNativePtr(vm, jsNumber(-1));
readableStream->setNativeType(0);
readableStream->setDisturbed(true);
}
🤖 Prompt for AI Agents
In src/bun.js/bindings/webcore/ReadableStream.cpp around lines 338 to 353, the
function ReadableStream__detach mutates JSReadableStream state without holding
the JSLock; acquire a JSC::JSLock (scoped) using the VM from globalObject
(globalObject->vm()) before calling setNativePtr, setNativeType, and
setDisturbed, then release the lock after those mutations so the JS object is
modified under the required lock.

Comment thread src/bun.js/bindings/webcore/ReadableStream.cpp Outdated
Comment on lines +399 to +405
auto iterable = object->getIfPropertyExists(globalObject, vm.propertyNames->asyncIteratorSymbol);
RETURN_IF_EXCEPTION(throwScope, {});
if (iterable && iterable.isCallable()) {
fn = iterable;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Incorrect RETURN_IF_EXCEPTION return value for int32_t function.

Using {} returns 0, but this function uses -1 as the error sentinel elsewhere. Return -1 on exception for consistency.

Apply:

-            RETURN_IF_EXCEPTION(throwScope, {});
+            RETURN_IF_EXCEPTION(throwScope, -1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto iterable = object->getIfPropertyExists(globalObject, vm.propertyNames->asyncIteratorSymbol);
RETURN_IF_EXCEPTION(throwScope, {});
if (iterable && iterable.isCallable()) {
fn = iterable;
}
}
auto iterable = object->getIfPropertyExists(globalObject, vm.propertyNames->asyncIteratorSymbol);
RETURN_IF_EXCEPTION(throwScope, -1);
if (iterable && iterable.isCallable()) {
fn = iterable;
}
}
🤖 Prompt for AI Agents
In src/bun.js/bindings/webcore/ReadableStream.cpp around lines 399 to 405, the
RETURN_IF_EXCEPTION call currently returns {} which evaluates to 0, but this
function uses -1 as the error sentinel elsewhere; change the
RETURN_IF_EXCEPTION(throwScope, {}) invocation to
RETURN_IF_EXCEPTION(throwScope, -1) so exceptions propagate the correct -1 error
value and keep behavior consistent with other return sites.

Comment on lines +497 to +501
auto* function = globalObject->m_readableStreamToArrayBuffer.get();
if (!function) {
function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToArrayBufferCodeGenerator(vm)), globalObject);
globalObject->m_readableStreamToArrayBuffer.set(vm, globalObject, function);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Confirm Zig::GlobalObject has WriteBarriers for cached functions and visits them.

Ensure m_readableStreamTo{ArrayBuffer,Bytes,Text,FormData,JSON,Blob} are fields on Zig::GlobalObject, initialized to null, and visited in visitChildren to avoid GC issues.

Run:

Based on learnings

Also applies to: 546-549, 586-591, 610-613, 633-636, 655-658


🏁 Script executed:

#!/bin/bash
# Confirm fields exist
rg -nP 'm_readableStreamTo(ArrayBuffer|Bytes|Text|FormData|JSON|Blob)\b'
# Confirm visitChildren visits them
rg -n -C3 --type=cpp 'visitChildren\(' | rg -n -C2 'm_readableStreamTo(ArrayBuffer|Bytes|Text|FormData|JSON|Blob)'

Length of output: 2483


🏁 Script executed:

#!/bin/bash
rg -n -C10 'visitChildren' src/bun.js/bindings/ZigGlobalObject.cpp

Length of output: 2062


🏁 Script executed:

#!/bin/bash
rg -n -C5 'm_readableStreamToArrayBuffer' src/bun.js/bindings/ZigGlobalObject.cpp
rg -n 'visitGlobalObjectMember' src/bun.js/bindings/ZigGlobalObject.cpp

Length of output: 544


Ensure m_readableStreamTo WriteBarriers are visited in visitChildrenImpl*
In src/bun.js/bindings/ZigGlobalObject.cpp’s GlobalObject::visitChildrenImpl, add

visitGlobalObjectMember(visitor, thisObject->m_readableStreamToArrayBuffer);
visitGlobalObjectMember(visitor, thisObject->m_readableStreamToBytes);
visitGlobalObjectMember(visitor, thisObject->m_readableStreamToText);
visitGlobalObjectMember(visitor, thisObject->m_readableStreamToFormData);
visitGlobalObjectMember(visitor, thisObject->m_readableStreamToJSON);
visitGlobalObjectMember(visitor, thisObject->m_readableStreamToBlob);

after the existing visitGlobalObjectMember calls to prevent these cached JSFunction pointers from being reclaimed by the GC.

🤖 Prompt for AI Agents
In src/bun.js/bindings/webcore/ReadableStream.cpp around lines 497-501, the code
caches JSFunction pointers on the global object (m_readableStreamToArrayBuffer
and related functions) but the garbage collector visitChildrenImpl in
src/bun.js/bindings/ZigGlobalObject.cpp does not currently visit those
write-barrier members; update GlobalObject::visitChildrenImpl to call
visitGlobalObjectMember for m_readableStreamToArrayBuffer,
m_readableStreamToBytes, m_readableStreamToText, m_readableStreamToFormData,
m_readableStreamToJSON, and m_readableStreamToBlob after the existing
visitGlobalObjectMember calls so the GC marks these cached JSFunction pointers
and prevents them from being reclaimed.

Comment on lines +578 to +599
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToText(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
{
using namespace JSC;
using namespace WebCore;
auto& vm = JSC::getVM(globalObject);

JSC::JSFunction* function = nullptr;
if (auto readableStreamToText = globalObject->m_readableStreamToText.get()) {
function = readableStreamToText;
} else {
function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToTextCodeGenerator(vm)), globalObject);

globalObject->m_readableStreamToText.set(vm, globalObject, function);
}

JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer();
arguments.append(JSValue::decode(readableStreamValue));

auto callData = JSC::getCallData(function);
return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Align readableStreamToText with others (validate Promise).

Unlike Bytes/ArrayBuffer, this path doesn’t assert the result is a Promise. Add the same checks for type safety.

Apply:

 extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToText(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
 {
     using namespace JSC;
     using namespace WebCore;
     auto& vm = JSC::getVM(globalObject);
 
-    JSC::JSFunction* function = nullptr;
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+    JSC::JSFunction* function = nullptr;
@@
-    auto callData = JSC::getCallData(function);
-    return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments));
+    auto callData = JSC::getCallData(function);
+    JSValue result = call(globalObject, function, callData, JSC::jsUndefined(), arguments);
+    if (!result || result.isUndefinedOrNull()) [[unlikely]]
+        RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+    JSObject* object = result.getObject();
+    if (!object) {
+        throwTypeError(globalObject, throwScope, "Expected object"_s);
+        return {};
+    }
+    auto* promise = jsDynamicCast<JSPromise*>(object);
+    if (!promise) {
+        throwTypeError(globalObject, throwScope, "Expected promise"_s);
+        return {};
+    }
+    RELEASE_AND_RETURN(throwScope, JSValue::encode(promise));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToText(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
{
using namespace JSC;
using namespace WebCore;
auto& vm = JSC::getVM(globalObject);
JSC::JSFunction* function = nullptr;
if (auto readableStreamToText = globalObject->m_readableStreamToText.get()) {
function = readableStreamToText;
} else {
function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToTextCodeGenerator(vm)), globalObject);
globalObject->m_readableStreamToText.set(vm, globalObject, function);
}
JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer();
arguments.append(JSValue::decode(readableStreamValue));
auto callData = JSC::getCallData(function);
return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments));
}
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToText(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
{
using namespace JSC;
using namespace WebCore;
auto& vm = JSC::getVM(globalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSC::JSFunction* function = nullptr;
if (auto readableStreamToText = globalObject->m_readableStreamToText.get()) {
function = readableStreamToText;
} else {
function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToTextCodeGenerator(vm)), globalObject);
globalObject->m_readableStreamToText.set(vm, globalObject, function);
}
JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer();
arguments.append(JSValue::decode(readableStreamValue));
auto callData = JSC::getCallData(function);
JSValue result = call(globalObject, function, callData, JSC::jsUndefined(), arguments);
if (!result || result.isUndefinedOrNull()) [[unlikely]]
RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
JSObject* object = result.getObject();
if (!object) {
throwTypeError(globalObject, throwScope, "Expected object"_s);
return {};
}
auto* promise = jsDynamicCast<JSPromise*>(object);
if (!promise) {
throwTypeError(globalObject, throwScope, "Expected promise"_s);
return {};
}
RELEASE_AND_RETURN(throwScope, JSValue::encode(promise));
}
🤖 Prompt for AI Agents
In src/bun.js/bindings/webcore/ReadableStream.cpp around lines 578-599, the
readableStreamToText trampoline invokes the JS function but does not validate
that the result is a Promise like the Bytes/ArrayBuffer paths do; add the same
runtime/type checks after the call: verify the returned JSValue is an object and
specifically a Promise (use the project's existing helpers used by other
bindings to test Promise-ness), and if it's not, raise/return a clear error (or
assert) consistent with the other bindings; update control flow to handle the
non-Promise case identically to the other implementations.

Comment on lines +107 to +114
// Forward declarations for code generators
JSC::FunctionExecutable* readableStreamReadableStreamToArrayBufferCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToBytesCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToTextCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToFormDataCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToJSONCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToBlobCodeGenerator(JSC::VM&);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing declaration for JSC::FunctionExecutable in header (compile-time error).

The header uses JSC::FunctionExecutable* without declaring the type or including its header. Add a forward declaration (or include the proper JSC header).

Apply this diff:

 #include "JSReadableStream.h"

+namespace JSC {
+class FunctionExecutable;
+}
+
 // Forward declarations for code generators
 JSC::FunctionExecutable* readableStreamReadableStreamToArrayBufferCodeGenerator(JSC::VM&);
 JSC::FunctionExecutable* readableStreamReadableStreamToBytesCodeGenerator(JSC::VM&);
 JSC::FunctionExecutable* readableStreamReadableStreamToTextCodeGenerator(JSC::VM&);
 JSC::FunctionExecutable* readableStreamReadableStreamToFormDataCodeGenerator(JSC::VM&);
 JSC::FunctionExecutable* readableStreamReadableStreamToJSONCodeGenerator(JSC::VM&);
 JSC::FunctionExecutable* readableStreamReadableStreamToBlobCodeGenerator(JSC::VM&);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Forward declarations for code generators
JSC::FunctionExecutable* readableStreamReadableStreamToArrayBufferCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToBytesCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToTextCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToFormDataCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToJSONCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToBlobCodeGenerator(JSC::VM&);
#include "JSReadableStream.h"
namespace JSC {
class FunctionExecutable;
}
// Forward declarations for code generators
JSC::FunctionExecutable* readableStreamReadableStreamToArrayBufferCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToBytesCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToTextCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToFormDataCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToJSONCodeGenerator(JSC::VM&);
JSC::FunctionExecutable* readableStreamReadableStreamToBlobCodeGenerator(JSC::VM&);
🤖 Prompt for AI Agents
In src/bun.js/bindings/webcore/ReadableStream.h around lines 107 to 114, the
header declares pointers to JSC::FunctionExecutable but doesn't declare that
type; add a forward declaration for JSC::FunctionExecutable (for example, add
"namespace JSC { class FunctionExecutable; }" above these function declarations)
or alternatively include the appropriate JSC header that defines
FunctionExecutable so the compiler knows the type before these prototypes.

@coderabbitai

coderabbitai Bot commented Oct 12, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

ReadableStream interop functions and helpers were removed from src/bun.js/bindings/ZigGlobalObject.cpp and reintroduced under src/bun.js/bindings/webcore/ReadableStream.cpp as extern "C" bindings and host functions. The change relocates tee/cancel/detach/state queries, tagging, native creation, and multiple conversion utilities (ArrayBuffer/Bytes/Text/FormData/JSON/Blob).

Changes

Cohort / File(s) Summary
Relocate ReadableStream bindings out of ZigGlobalObject
src/bun.js/bindings/ZigGlobalObject.cpp
Removes ReadableStream interop: tee, cancel, detach, isDisturbed/isLocked, tagging, native stream creation, and conversion helpers (to ArrayBuffer/Bytes/Text/FormData/JSON/Blob), plus related host-function declarations and wrappers.
Add WebCore ReadableStream bindings and conversions
src/bun.js/bindings/webcore/ReadableStream.cpp
Adds extern "C" bindings and host functions for ReadableStream tee/cancel/detach/state, tagging, native creation, and conversions to ArrayBuffer/Bytes/Text/FormData/JSON/Blob, with required includes and error/exception handling.

Possibly related PRs

Suggested reviewers

  • Jarred-Sumner
  • cirospaciari

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description does not follow the repository’s template because it lacks the required “### What does this PR do?” and “### How did you verify your code works?” headings and instead uses different section titles, making it unclear how the description aligns with the expected structure. Please update the description to include the exact headings “### What does this PR do?” and “### How did you verify your code works?”, moving or renaming existing content under these sections to match the repository template.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely describes the primary change of moving ReadableStream-related functions from one file to another, matching the pull request content and summarizing the main refactor.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bun.js/bindings/webcore/ReadableStream.cpp (1)

26-28: Include root.h first to satisfy lints

Add root.h at the top of binding .cpp files.

As per coding guidelines

+#include "root.h"
 #include "config.h"
 #include "ReadableStream.h"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b8b9d70 and ce66a11.

📒 Files selected for processing (2)
  • src/bun.js/bindings/ZigGlobalObject.cpp (0 hunks)
  • src/bun.js/bindings/webcore/ReadableStream.cpp (2 hunks)
💤 Files with no reviewable changes (1)
  • src/bun.js/bindings/ZigGlobalObject.cpp
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: When a JS class has a public constructor, implement Foo, FooPrototype, and FooConstructor (JSDestructibleObject, JSNonFinalObject, InternalFunction)
Define class properties using HashTableValue arrays in C++ bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for C++-backed classes

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
🧠 Learnings (8)
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for C++-backed classes

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
🧬 Code graph analysis (1)
src/bun.js/bindings/webcore/ReadableStream.cpp (3)
src/bun.js/bindings/webcore/ReadableStream.h (5)
  • WebCore (34-107)
  • isLocked (55-66)
  • JSValue (90-93)
  • JSValue (95-98)
  • JSValue (100-103)
src/bun.js/bindings/BunClientData.h (1)
  • clientData (200-203)
src/js/builtins/ReadableStream.ts (4)
  • readableStreamToText (122-138)
  • readableStreamToFormData (300-309)
  • readableStreamToJSON (312-331)
  • readableStreamToBlob (334-342)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (5)
src/bun.js/bindings/webcore/ReadableStream.cpp (5)

267-319: LGTM: extern "C" ReadableStream__tee moved and scoped safely

Argument checks, builtin lookup, call, and conversion look correct; out-params filled only on success.


321-337: LGTM: ReadableStream__cancel parity retained

Type check, locked check, and cancel via WebCore helper look consistent with prior behavior.


470-488: LGTM: createNativeReadableStream wrapper

Builtin lookup + call + exception assertions look correct.


489-576: LGTM: readableStreamTo{ArrayBuffer,Bytes,Text,FormData,JSON,Blob} wrappers

Lazy init and caching of builtins, argument marshalling, and promise/type checks are consistent.

Also applies to: 578-666


667-699: LGTM: host functions forward to cached helpers

Argument guard then delegate; matches surrounding patterns.

Comment thread src/bun.js/bindings/webcore/ReadableStream.cpp
Comment thread src/bun.js/bindings/webcore/ReadableStream.cpp Outdated
Comment on lines +399 to +401
auto iterable = object->getIfPropertyExists(globalObject, vm.propertyNames->asyncIteratorSymbol);
RETURN_IF_EXCEPTION(throwScope, {});
if (iterable && iterable.isCallable()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Return -1 on exception in ReadableStreamTag__tagged

Returning {} here yields 0 (success code). Use -1 for error like other paths.

-            RETURN_IF_EXCEPTION(throwScope, {});
+            RETURN_IF_EXCEPTION(throwScope, -1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto iterable = object->getIfPropertyExists(globalObject, vm.propertyNames->asyncIteratorSymbol);
RETURN_IF_EXCEPTION(throwScope, {});
if (iterable && iterable.isCallable()) {
auto iterable = object->getIfPropertyExists(globalObject, vm.propertyNames->asyncIteratorSymbol);
RETURN_IF_EXCEPTION(throwScope, -1);
if (iterable && iterable.isCallable()) {
🤖 Prompt for AI Agents
In src/bun.js/bindings/webcore/ReadableStream.cpp around lines 399-401, the
RETURN_IF_EXCEPTION call currently uses {} which ends up returning 0 (success);
change the exceptional return to -1 to indicate an error like the other error
paths — i.e., replace the {} in RETURN_IF_EXCEPTION(throwScope, {}) with -1 so
the ReadableStreamTag__tagged function returns -1 on exception.

Following Bun's convention of including root.h as the first include.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (6)
src/bun.js/bindings/webcore/ReadableStream.cpp (6)

26-27: Past review comment addressed: root.h included.

The file now correctly includes "root.h" as the first header, satisfying the coding guidelines for C++ binding files.


340-355: Fix unsafe cast and acquire JSLock before mutating JS objects.

Two critical issues remain from previous reviews:

  1. Line 349 uses unsafe static_cast instead of jsDynamicCast, risking memory corruption
  2. No JSLock is acquired before mutating JSReadableStream state (lines 352-354), risking race conditions

Apply this diff to fix both issues:

 extern "C" void ReadableStream__detach(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject)
 {
     using namespace JSC;
     using namespace WebCore;
 
+    auto& vm = JSC::getVM(globalObject);
+    JSC::JSLockHolder lock(vm);
+
     auto value = JSC::JSValue::decode(possibleReadableStream);
     if (value.isEmpty() || !value.isCell())
         return;
 
-    auto* readableStream = static_cast<WebCore::JSReadableStream*>(value.asCell());
+    auto* readableStream = jsDynamicCast<WebCore::JSReadableStream*>(value);
     if (!readableStream) [[unlikely]]
         return;
-    readableStream->setNativePtr(globalObject->vm(), jsNumber(-1));
+    readableStream->setNativePtr(vm, jsNumber(-1));
     readableStream->setNativeType(0);
     readableStream->setDisturbed(true);
 }

357-358: Remove redundant forward declaration.

The forward declaration on line 357 is immediately followed by the function definition. Remove the duplicate to reduce noise.

Apply this diff:

-extern "C" bool ReadableStream__isDisturbed(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject);
 extern "C" bool ReadableStream__isDisturbed(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject)

367-368: Remove redundant forward declaration.

The forward declaration on line 367 is immediately followed by the function definition. Remove the duplicate to reduce noise.

Apply this diff:

-extern "C" bool ReadableStream__isLocked(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject);
 extern "C" bool ReadableStream__isLocked(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject)

401-402: Return -1 on exception for consistency with error sentinel.

Line 402 returns {} which evaluates to 0 (success code), but this function uses -1 as the error sentinel throughout (lines 387, 410, 415, 427, 432). This inconsistency could cause incorrect error handling.

Apply this diff:

-            RETURN_IF_EXCEPTION(throwScope, {});
+            RETURN_IF_EXCEPTION(throwScope, -1);

580-600: Validate Promise result for consistency with other converters.

Unlike readableStreamToArrayBuffer and readableStreamToBytes, this function doesn't validate that the result is a Promise. While the TypeScript code ensures it returns a Promise, adding the same validation would improve type safety and consistency across all converter functions.

Apply this diff to add Promise validation:

 extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToText(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
 {
     using namespace JSC;
     using namespace WebCore;
     auto& vm = JSC::getVM(globalObject);
 
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+
     JSC::JSFunction* function = nullptr;
     if (auto readableStreamToText = globalObject->m_readableStreamToText.get()) {
         function = readableStreamToText;
     } else {
         function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToTextCodeGenerator(vm)), globalObject);
 
         globalObject->m_readableStreamToText.set(vm, globalObject, function);
     }
 
     JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer();
     arguments.append(JSValue::decode(readableStreamValue));
 
     auto callData = JSC::getCallData(function);
-    return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments));
+    JSValue result = call(globalObject, function, callData, JSC::jsUndefined(), arguments);
+    
+    if (!result || result.isUndefinedOrNull()) [[unlikely]]
+        RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+    
+    JSObject* object = result.getObject();
+    if (!object) {
+        throwTypeError(globalObject, throwScope, "Expected object"_s);
+        return {};
+    }
+    
+    auto* promise = jsDynamicCast<JSPromise*>(object);
+    if (!promise) {
+        throwTypeError(globalObject, throwScope, "Expected promise"_s);
+        return {};
+    }
+    
+    RELEASE_AND_RETURN(throwScope, JSValue::encode(promise));
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c7c2642 and 17d4063.

📒 Files selected for processing (1)
  • src/bun.js/bindings/webcore/ReadableStream.cpp (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: When a JS class has a public constructor, implement Foo, FooPrototype, and FooConstructor (JSDestructibleObject, JSNonFinalObject, InternalFunction)
Define class properties using HashTableValue arrays in C++ bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for C++-backed classes

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
🧠 Learnings (11)
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.cpp : Include "root.h" at the top of C++ binding files to satisfy lints

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/**/*.{h,cpp} : Use JSC::WriteBarrier for heap-allocated references and implement visitChildren() for custom heap objects

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.cpp : Initialize the LazyClassStructure in GlobalObject::finishCreation and visit it in GlobalObject::visitChildrenImpl

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for C++-backed classes

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
🧬 Code graph analysis (1)
src/bun.js/bindings/webcore/ReadableStream.cpp (3)
src/bun.js/bindings/webcore/ReadableStream.h (5)
  • WebCore (34-107)
  • isLocked (55-66)
  • JSValue (90-93)
  • JSValue (95-98)
  • JSValue (100-103)
src/bun.js/bindings/BunClientData.h (1)
  • clientData (200-203)
src/js/builtins/ReadableStream.ts (4)
  • readableStreamToText (122-138)
  • readableStreamToFormData (300-309)
  • readableStreamToJSON (312-331)
  • readableStreamToBlob (334-342)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (9)
src/bun.js/bindings/webcore/ReadableStream.cpp (9)

37-48: LGTM!

The new includes are appropriate for the moved ReadableStream functions and provide the necessary types and utilities.


269-321: LGTM!

The ReadableStream__tee implementation is correct:

  • Properly uses jsDynamicCast for type safety
  • Lambda acquires JSLock before mutating JS objects
  • Correctly uses Bun::IDLRawAny for IDL conversion
  • Exception handling returns appropriate bool values

323-338: LGTM!

The ReadableStream__cancel implementation correctly:

  • Uses jsDynamicCast for safe type checking
  • Uses namespace-qualified Bun::AbortError
  • Guards against canceling unlocked streams

472-489: LGTM!

The ZigGlobalObject__createNativeReadableStream implementation correctly uses builtin functions and proper exception handling via EXCEPTION_ASSERT.


532-537: LGTM!

Simple wrapper function that correctly delegates to ZigGlobalObject__readableStreamToArrayBufferBody.


539-578: LGTM!

The ZigGlobalObject__readableStreamToBytes implementation correctly mirrors the ArrayBuffer pattern with proper Promise validation and caching.


669-684: LGTM!

The host function correctly validates argument count and delegates to the implementation function.


686-701: LGTM!

The host function correctly validates argument count and delegates to the implementation function, mirroring the pattern used in functionReadableStreamToArrayBuffer.


491-530: LGTM – WriteBarriers for m_readableStreamToArrayBuffer (and related) are included via FOR_EACH_GLOBALOBJECT_GC_MEMBER and visited in GlobalObject::visitChildrenImpl.

Comment on lines +602 to +623
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToFormData(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue, JSC::EncodedJSValue contentTypeValue)
{
using namespace JSC;
using namespace WebCore;
auto& vm = JSC::getVM(globalObject);

JSC::JSFunction* function = nullptr;
if (auto readableStreamToFormData = globalObject->m_readableStreamToFormData.get()) {
function = readableStreamToFormData;
} else {
function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToFormDataCodeGenerator(vm)), globalObject);

globalObject->m_readableStreamToFormData.set(vm, globalObject, function);
}

JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer();
arguments.append(JSValue::decode(readableStreamValue));
arguments.append(JSValue::decode(contentTypeValue));

auto callData = JSC::getCallData(function);
return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Validate Promise result for consistency with ArrayBuffer/Bytes.

Like readableStreamToText, this function doesn't validate that the result is a Promise. Consider adding the same validation as readableStreamToArrayBuffer and readableStreamToBytes for consistency and type safety.

Comment on lines +625 to +645
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToJSON(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
{
using namespace JSC;
using namespace WebCore;
auto& vm = JSC::getVM(globalObject);

JSC::JSFunction* function = nullptr;
if (auto readableStreamToJSON = globalObject->m_readableStreamToJSON.get()) {
function = readableStreamToJSON;
} else {
function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToJSONCodeGenerator(vm)), globalObject);

globalObject->m_readableStreamToJSON.set(vm, globalObject, function);
}

JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer();
arguments.append(JSValue::decode(readableStreamValue));

auto callData = JSC::getCallData(function);
return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Validate Promise result for consistency with ArrayBuffer/Bytes.

Like readableStreamToText and readableStreamToFormData, this function doesn't validate that the result is a Promise. Consider adding the same validation as readableStreamToArrayBuffer and readableStreamToBytes for consistency and type safety.

Comment on lines +647 to +667
extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToBlob(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
{
using namespace JSC;
using namespace WebCore;
auto& vm = JSC::getVM(globalObject);

JSC::JSFunction* function = nullptr;
if (auto readableStreamToBlob = globalObject->m_readableStreamToBlob.get()) {
function = readableStreamToBlob;
} else {
function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToBlobCodeGenerator(vm)), globalObject);

globalObject->m_readableStreamToBlob.set(vm, globalObject, function);
}

JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer();
arguments.append(JSValue::decode(readableStreamValue));

auto callData = JSC::getCallData(function);
return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Validate Promise result for consistency with ArrayBuffer/Bytes.

Like the other converter functions, this doesn't validate that the result is a Promise. Consider adding the same validation as readableStreamToArrayBuffer and readableStreamToBytes for consistency and type safety.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (5)
src/bun.js/bindings/webcore/ReadableStream.cpp (5)

337-349: CRITICAL: Fix unsafe cast and add JSLock before mutating JS object state.

This function still has two critical issues flagged in previous reviews:

  1. Unsafe cast at line 343: Using static_cast from JSCell* to JSReadableStream* can cause memory corruption. Must use jsDynamicCast.
  2. Missing JSLock: Mutating JSReadableStream state (lines 346-348) without holding JSLock can cause race conditions.

Apply this diff to fix both issues:

 extern "C" void ReadableStream__detach(JSC::EncodedJSValue possibleReadableStream, Zig::GlobalObject* globalObject)
 {
+    using namespace JSC;
+    using namespace WebCore;
+    
+    auto& vm = JSC::getVM(globalObject);
+    JSC::JSLockHolder lock(vm);
+    
     auto value = JSC::JSValue::decode(possibleReadableStream);
     if (value.isEmpty() || !value.isCell())
         return;

-    auto* readableStream = static_cast<WebCore::JSReadableStream*>(value.asCell());
+    auto* readableStream = jsDynamicCast<WebCore::JSReadableStream*>(value);
     if (!readableStream) [[unlikely]]
         return;
-    readableStream->setNativePtr(globalObject->vm(), jsNumber(-1));
+    readableStream->setNativePtr(vm, jsNumber(-1));
     readableStream->setNativeType(0);
     readableStream->setDisturbed(true);
 }

384-386: Fix inconsistent error return value.

Line 385 returns {} (which evaluates to 0) on exception, but this function uses -1 as the error sentinel elsewhere (lines 370, 393, 398, 410, 415). This inconsistency can cause incorrect error handling.

Apply this diff:

-            RETURN_IF_EXCEPTION(throwScope, {});
+            RETURN_IF_EXCEPTION(throwScope, -1);

555-573: Missing Promise validation for consistency with ArrayBuffer/Bytes.

Unlike readableStreamToArrayBuffer and readableStreamToBytes, this function doesn't validate that the result is a Promise (line 572). Consider adding the same validation for consistency and type safety.

Apply this diff to add validation:

 extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToText(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue)
 {
     auto& vm = JSC::getVM(globalObject);
+    auto throwScope = DECLARE_THROW_SCOPE(vm);

     JSC::JSFunction* function = nullptr;
     if (auto readableStreamToText = globalObject->m_readableStreamToText.get()) {
         function = readableStreamToText;
     } else {
         function = JSFunction::create(vm, globalObject, static_cast<JSC::FunctionExecutable*>(readableStreamReadableStreamToTextCodeGenerator(vm)), globalObject);

         globalObject->m_readableStreamToText.set(vm, globalObject, function);
     }

     JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer();
     arguments.append(JSValue::decode(readableStreamValue));

     auto callData = JSC::getCallData(function);
-    return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments));
+    JSValue result = call(globalObject, function, callData, JSC::jsUndefined(), arguments);
+    
+    if (!result || result.isUndefinedOrNull()) [[unlikely]]
+        RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+        
+    JSObject* object = result.getObject();
+    if (!object) [[unlikely]] {
+        throwTypeError(globalObject, throwScope, "Expected object"_s);
+        return {};
+    }
+    
+    auto* promise = jsDynamicCast<JSPromise*>(object);
+    if (!promise) [[unlikely]] {
+        throwTypeError(globalObject, throwScope, "Expected promise"_s);
+        return {};
+    }
+    
+    RELEASE_AND_RETURN(throwScope, JSValue::encode(promise));
 }

575-614: Missing Promise validation for consistency with ArrayBuffer/Bytes.

Both readableStreamToFormData (lines 575-594) and readableStreamToJSON (lines 596-614) don't validate that the result is a Promise, unlike readableStreamToArrayBuffer and readableStreamToBytes. Consider adding the same validation for consistency and type safety.


616-634: Missing Promise validation for consistency with ArrayBuffer/Bytes.

Like the other converter functions, readableStreamToBlob doesn't validate that the result is a Promise. Consider adding the same validation as readableStreamToArrayBuffer and readableStreamToBytes for consistency and type safety.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 17d4063 and 0b488d1.

📒 Files selected for processing (1)
  • src/bun.js/bindings/webcore/ReadableStream.cpp (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: When a JS class has a public constructor, implement Foo, FooPrototype, and FooConstructor (JSDestructibleObject, JSNonFinalObject, InternalFunction)
Define class properties using HashTableValue arrays in C++ bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for C++-backed classes

Files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
🧠 Learnings (11)
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.cpp : Include "root.h" at the top of C++ binding files to satisfy lints

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/**/*.{h,cpp} : Use JSC::WriteBarrier for heap-allocated references and implement visitChildren() for custom heap objects

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.cpp : Initialize the LazyClassStructure in GlobalObject::finishCreation and visit it in GlobalObject::visitChildrenImpl

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-10-04T09:51:30.294Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T09:51:30.294Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for C++-backed classes

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.js/bindings/webcore/ReadableStream.cpp
🧬 Code graph analysis (1)
src/bun.js/bindings/webcore/ReadableStream.cpp (3)
src/bun.js/bindings/webcore/ReadableStream.h (5)
  • WebCore (34-107)
  • isLocked (55-66)
  • JSValue (90-93)
  • JSValue (95-98)
  • JSValue (100-103)
src/bun.js/bindings/BunClientData.h (1)
  • clientData (200-203)
src/js/builtins/ReadableStream.ts (4)
  • readableStreamToText (122-138)
  • readableStreamToFormData (300-309)
  • readableStreamToJSON (312-331)
  • readableStreamToBlob (334-342)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (4)
src/bun.js/bindings/webcore/ReadableStream.cpp (4)

351-362: LGTM!

Both ReadableStream__isDisturbed and ReadableStream__isLocked correctly use jsDynamicCast for type safety and properly delegate to the WebCore ReadableStream methods.


455-470: LGTM!

The function correctly creates a native ReadableStream using the builtin function with proper exception handling.


511-553: LGTM!

Both ZigGlobalObject__readableStreamToArrayBuffer and ZigGlobalObject__readableStreamToBytes correctly implement the conversion pattern with proper Promise validation.


636-662: LGTM!

Both host function wrappers (functionReadableStreamToArrayBuffer and functionReadableStreamToBytes) correctly validate argument count and delegate to their respective body functions.

Comment on lines +284 to +305
auto invokeReadableStreamFunction = [](JSC::JSGlobalObject* lexicalGlobalObject, const JSC::Identifier& identifier, JSC::JSValue thisValue, const JSC::MarkedArgumentBuffer& arguments) -> std::optional<JSC::JSValue> {
JSC::VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::JSLockHolder lock(vm);

auto function = lexicalGlobalObject->get(lexicalGlobalObject, identifier);
scope.assertNoExceptionExceptTermination();
if (scope.exception()) [[unlikely]]
return {};
ASSERT(function.isCallable());

auto callData = JSC::getCallData(function);
auto result = JSC::call(lexicalGlobalObject, function, callData, thisValue, arguments);
#if ASSERT_ENABLED
if (scope.exception()) [[unlikely]] {
Bun__reportError(lexicalGlobalObject, JSC::JSValue::encode(scope.exception()));
}
#endif
EXCEPTION_ASSERT(!scope.exception() || vm.hasPendingTerminationException());
RETURN_IF_EXCEPTION(scope, {});
return result;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting the lambda to file scope as a helper function.

The invokeReadableStreamFunction lambda is defined inline but only used once. While functional, extracting it to a file-scoped helper would improve readability and allow reuse if needed in the future. However, this is an optional improvement and not a blocker.

Comment on lines +329 to +331
if (!WebCore::ReadableStream::isLocked(globalObject, readableStream)) {
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify the cancellation logic is correct.

The code returns early if the stream is NOT locked (line 329), then proceeds to cancel only if the stream IS locked (line 334). This logic seems inverted from typical ReadableStream semantics, where you can only cancel an unlocked stream. Please verify this is intentional and correct.


🏁 Script executed:

#!/bin/bash
# Description: Search for other usages of ReadableStream::cancel to verify the locking semantics.

# Search for calls to ReadableStream::cancel and check if they verify lock state
rg -nP -C5 'ReadableStream::cancel\('

Length of output: 3166


🏁 Script executed:

#!/bin/bash
# Inspect isLocked occurrences
rg -nP -C3 'isLocked' src/bun.js/bindings/webcore/ReadableStream.cpp

# Inspect static cancel implementation and instance cancel
rg -nP -C5 'void ReadableStream::cancel\(' src/bun.js/bindings/webcore/ReadableStream.cpp

Length of output: 2639


🏁 Script executed:

#!/bin/bash
rg -nP 'extern "C".*ReadableStream__cancel' -C3 src/bun.js/bindings/webcore/ReadableStream.cpp

Length of output: 319


Remove unintended isLocked guard in ReadableStream__cancel. The check at lines 329–331 prevents cancelling unlocked streams; per Streams spec, cancel must be invoked unconditionally. Delete those three lines so ReadableStream::cancel always runs.

🤖 Prompt for AI Agents
In src/bun.js/bindings/webcore/ReadableStream.cpp around lines 329–331, remove
the unintended isLocked guard that prevents calling ReadableStream::cancel on
unlocked streams; delete the three lines checking
WebCore::ReadableStream::isLocked(globalObject, readableStream) and its early
return so ReadableStream::cancel is invoked unconditionally, then rebuild/run
stream tests to verify behavior.

@Jarred-Sumner Jarred-Sumner merged commit caa4f54 into main Oct 12, 2025
48 of 58 checks passed
@Jarred-Sumner Jarred-Sumner deleted the claude/move-readablestream-functions branch October 12, 2025 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants