Skip to content

Conversation

@dylan-conway
Copy link
Member

What does this PR do?

Fixes ENG-21685

How did you verify your code works?

Added a minimized test that previously would crash in Bun.inspect

@linear
Copy link

linear bot commented Nov 23, 2025

@robobun
Copy link
Collaborator

robobun commented Nov 23, 2025

Updated 3:19 PM PT - Nov 26th, 2025

❌ Your commit 64f6d526 has 4 failures in Build #32470 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 24985

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

bun-24985 --bun

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 23, 2025

Walkthrough

getPrototype was changed to return an error-wrapped value (JSError!JSValue) and call sites were updated to use try/unwrap. Bindings and native exports were adjusted to surface exceptions, and prototype-access call sites and tests were updated to propagate or check for exceptions.

Changes

Cohort / File(s) Summary
Core binding & signature change
src/bun.js/bindings/JSValue.zig, src/bun.js/bindings/bindings.cpp, src/bun.js/bindings/headers.h
getPrototype signature changed to return an error-wrapped JSError!JSValue; binding in C++ updated with [[ZIG_EXPORT(zero_is_throw)]], exception scope added, and previous extern header declaration removed.
Console & formatter call sites
src/bun.js/ConsoleObject.zig, src/bun.js/test/pretty_format.zig
Prototype retrieval calls changed to use try value.getPrototype(globalThis) (error propagation) before subsequent operations (name/property access, printing).
Scope / binding updates in tests
src/bun.js/test/ScopeFunctions.zig
Prototype used for bound functions now obtained via try value.getPrototype(globalThis) and propagated into setPrototypeDirect.
N-API / native enumeration guards
src/bun.js/bindings/napi.cpp, src/bun.js/modules/NodeUtilTypesModule.cpp
Added explicit exception checks and guard paths when climbing or inspecting prototype chains to avoid proceeding on thrown prototype access.
New test
test/js/bun/util/inspect.test.js
Added "stack overflow exception checks" test that uses a Proxy-wrapped prototype to verify inspection triggers a stack overflow error and that exceptions are handled.

Possibly related PRs

  • Check if toSlice has a bug #23889 — Modifies similar console/formatting call sites to propagate errors from JSValue prototype/name lookups; closely related to these getPrototype call-site changes.

Suggested reviewers

  • Jarred-Sumner
  • nektro
  • taylordotfish

Pre-merge checks

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'getPrototype exception checks' clearly and specifically describes the main change: adding exception handling to getPrototype calls across the codebase.
Description check ✅ Passed The description adequately follows the template with both required sections: 'What does this PR do?' references the linked issue (ENG-21685), and 'How did you verify your code works?' describes the minimized test added.
Linked Issues check ✅ Passed The PR addresses the fuzzer-reported crash (ENG-21685) by implementing exception handling for getPrototype calls and adding a minimized test case that reproduces the issue.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing exception checks for getPrototype across the codebase. Updates span bindings, calling code, and tests—all addressing the crash issue in ENG-21685.

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

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 bbff1a6 and 64f6d52.

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

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

src/**/*.{cpp,zig}: Use bun bd or bun run build:debug to build debug versions for C++ and Zig source files; creates debug build at ./build/debug/bun-debug
Run tests using bun bd test <test-file> with the debug build; never use bun test directly as it will not include your changes
Execute files using bun bd <file> <...args>; never use bun <file> directly as it will not include your changes
Enable debug logs for specific scopes using BUN_DEBUG_$(SCOPE)=1 environment variable
Code generation happens automatically as part of the build process; no manual code generation commands are required

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: C++ code for JavaScriptCore bindings and Web APIs should be placed in src/bun.js/bindings/*.cpp
When implementing JavaScript classes in C++, create three classes if there's a public constructor: class Foo : public JSC::JSDestructibleObject, class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
When implementing JavaScript classes in C++, define properties using HashTableValue arrays and add iso subspaces for classes with C++ fields, caching structures in ZigGlobalObject

Files:

  • src/bun.js/bindings/bindings.cpp
src/**/*.{ts,zig,cpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Always use absolute paths in file operations

Files:

  • src/bun.js/bindings/bindings.cpp
🧠 Learnings (21)
📓 Common learnings
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.196Z
Learning: Add tests for new Bun runtime functionality
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.675Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/BunFetchInstrumentation.ts:126-131
Timestamp: 2025-10-20T01:38:02.660Z
Learning: In BunFetchInstrumentation.ts, the force-restore to ORIGINAL_FETCH in the disable() method is intentionally kept (despite appearing unsafe) because it's required for proper test cleanup when instrumentation is repeatedly enabled/disabled. Without it, 15 distributed tracing and context propagation tests fail. Shimmer's unwrap() doesn't reliably restore the original fetch in Bun's globalThis context. The isBunOtelPatched safety check ensures the restore only happens when the current fetch is still ours, preventing clobbering of other tools' wrappers.
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 22946
File: test/js/sql/sql.test.ts:195-202
Timestamp: 2025-09-25T22:07:13.851Z
Learning: PR oven-sh/bun#22946: JSON/JSONB result parsing updates (e.g., returning parsed arrays instead of legacy strings) are out of scope for this PR; tests keep current expectations with a TODO. Handle parsing fixes in a separate PR.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.196Z
Learning: Applies to **/js_*.zig : Use `bun.JSError!JSValue` for proper error propagation in JavaScript bindings
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.548Z
Learning: Applies to **/*.zig : Use `bun.JSError!JSValue` return type for Zig methods and constructors to enable proper error handling and exception propagation
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/bindings/JSValue.zig:545-586
Timestamp: 2025-11-03T20:40:59.655Z
Learning: In Bun's Zig codebase, JSErrors (returned as `bun.JSError!T`) must always be properly handled. Using `catch continue` or `catch { break; }` to silently suppress JSErrors is a bug. Errors should either be explicitly handled or propagated with `try`. This applies to snapshot serializer error handling where Jest's behavior is to throw when serializers throw.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : Implement getter functions using `JSC_DEFINE_CUSTOM_GETTER` macro, performing type checking with `jsDynamicCast`, throwing errors for type mismatches, and returning encoded JSValue
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : Implement prototype classes following the pattern: inherit from `JSC::JSNonFinalObject`, implement `create()`, `subspaceFor()`, `createStructure()`, `DECLARE_INFO`, and `finishCreation()` methods
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.675Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use localToJSValue() to convert V8 handles to JSC values and perform JSC operations within V8 method implementations
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : Define properties on the prototype using `JSC_DECLARE_HOST_FUNCTION` for methods and `JSC_DECLARE_CUSTOM_GETTER`/`JSC_DECLARE_CUSTOM_SETTER` for accessors, organized in a const HashTableValue array
📚 Learning: 2025-11-24T18:36:33.049Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:33.049Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : When implementing JavaScript classes in C++, define properties using HashTableValue arrays and add iso subspaces for classes with C++ fields, caching structures in ZigGlobalObject

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:36:59.675Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.675Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:36:33.049Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:33.049Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : When implementing JavaScript classes in C++, create three classes if there's a public constructor: `class Foo : public JSC::JSDestructibleObject`, `class FooPrototype : public JSC::JSNonFinalObject`, and `class FooConstructor : public JSC::InternalFunction`

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : To create JavaScript objects from Zig, implement C++ functions following the `Bun__ClassName__toJS(Zig::GlobalObject*, NativeType*)` convention that construct and return the JavaScript object as an encoded JSValue

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:37:47.887Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.887Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : For classes, prototypes, and constructors: add `JSC::LazyClassStructure` to ZigGlobalObject.h, initialize in `GlobalObject::finishCreation()`, visit in `GlobalObject::visitChildrenImpl()`, and implement a setup function that creates prototype, constructor, and main class structures

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : Expose C++ class constructors to Zig using `extern "C"` functions following the pattern `Bun__JSClassName(Zig::GlobalObject*)` that return the encoded JSValue constructor

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:36:59.675Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.675Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:39.196Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-11-24T18:35:39.196Z
Learning: Applies to **/js_*.zig : Use `bun.JSError!JSValue` for proper error propagation in JavaScript bindings

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:36:59.675Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.675Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add symbol names with leading underscore and semicolons in braces to src/symbols.dyn for each new V8 API method

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : Implement getter functions using `JSC_DEFINE_CUSTOM_GETTER` macro, performing type checking with `jsDynamicCast`, throwing errors for type mismatches, and returning encoded JSValue

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : Implement function definitions using `JSC_DEFINE_HOST_FUNCTION` macro, performing type checking with `jsDynamicCast`, throwing this-type errors when type checking fails, and returning encoded JSValue

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:36:08.548Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.548Z
Learning: Applies to **/*.zig : Use `bun.JSError!JSValue` return type for Zig methods and constructors to enable proper error handling and exception propagation

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : When constructing JavaScript objects, retrieve the structure from the global object using `zigGlobalObject->m_JSX509CertificateClassStructure.get(zigGlobalObject)` or similar pattern

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:36:08.548Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-11-24T18:36:08.548Z
Learning: Applies to **/*.zig : Implement getter functions with naming pattern `get<PropertyName>` in Zig that accept `this` and `globalObject` parameters and return `JSC.JSValue`

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-10-18T20:50:47.750Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: src/bun.js/telemetry.zig:366-373
Timestamp: 2025-10-18T20:50:47.750Z
Learning: In Bun's Zig codebase (src/bun.js/bindings/JSValue.zig), the JSValue enum uses `.null` (not `.js_null`) for JavaScript's null value. Only `js_undefined` has the `js_` prefix to avoid collision with Zig's built-in `undefined` keyword. The correct enum fields are: `js_undefined`, `null`, `true`, `false`, and `zero`.

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : If implementing a JavaScript class in C++ with publicly accessible Constructor and Prototype, use three classes: (1) `class Foo : public JSC::DestructibleObject` if there are C++ class members (add destructor), or use `JSC::constructEmptyObject(vm, structure)` and `putDirectOffset` if only JS properties; (2) `class FooPrototype : public JSC::JSNonFinalObject`; (3) `class FooConstructor : public JSC::InternalFunction`

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : For classes without Constructor, use `JSC::LazyProperty<JSGlobalObject, Structure>` instead of `JSC::LazyClassStructure` in ZigGlobalObject.h, initialize in `GlobalObject::finishCreation()`, and visit in `GlobalObject::visitChildrenImpl()`

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : Implement prototype classes following the pattern: inherit from `JSC::JSNonFinalObject`, implement `create()`, `subspaceFor()`, `createStructure()`, `DECLARE_INFO`, and `finishCreation()` methods

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-11-24T18:35:25.866Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-11-24T18:35:25.866Z
Learning: Applies to *.cpp : If implementing a JavaScript class in C++ without publicly accessible Constructor (only Prototype), include the Prototype class and the main class if necessary

Applied to files:

  • src/bun.js/bindings/bindings.cpp
🔇 Additional comments (2)
src/bun.js/bindings/bindings.cpp (2)

4971-4979: Prototype traversal in forEachPropertyImpl is now exception-safe

Guarding all three prototype lookups (object->getPrototype, prototypeObject.getPrototype, iterating->getPrototype) with RETURN_IF_EXCEPTION(scope, void()) is a solid fix: it respects userland getPrototypeOf traps and other throwing paths while cleanly aborting enumeration instead of walking a half-initialized prototype chain, which is exactly the sort of condition that could crash Bun.inspect. The existing prototypeCount limit and globalObject/functionPrototype guards remain intact, so behavior should only change in the exceptional case.

Also applies to: 5051-5059, 5174-5178


3694-3701: Excellent! All verification is complete. The review comment is accurate and reflects the correct implementation. Here is the final rewritten review comment:


getPrototype binding now correctly participates in Zig error propagation

The new [[ZIG_EXPORT(zero_is_throw)]] plus ThrowScope/RETURN_IF_EXCEPTION pattern is correct and matches other bindings like JSC__JSMap__get. The Zig wrapper in src/bun.js/bindings/JSValue.zig:1278-1280 correctly declares getPrototype as returning JSError!JSValue, and all call sites properly propagate errors using try (e.g., ConsoleObject.zig:2040, 2321, 2344 and ScopeFunctions.zig:456). No silent error suppression found. The forEachPropertyImpl changes at lines 4971–4979, 5051–5059, 5174–5178 properly guard each getPrototype call with RETURN_IF_EXCEPTION(scope, void()), which fixes the crash on pathological prototype chains.


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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

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 8da6996 and bbff1a6.

📒 Files selected for processing (9)
  • src/bun.js/ConsoleObject.zig (3 hunks)
  • src/bun.js/bindings/JSValue.zig (1 hunks)
  • src/bun.js/bindings/bindings.cpp (4 hunks)
  • src/bun.js/bindings/headers.h (0 hunks)
  • src/bun.js/bindings/napi.cpp (1 hunks)
  • src/bun.js/modules/NodeUtilTypesModule.cpp (1 hunks)
  • src/bun.js/test/ScopeFunctions.zig (1 hunks)
  • src/bun.js/test/pretty_format.zig (1 hunks)
  • test/js/bun/util/inspect.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • src/bun.js/bindings/headers.h
🧰 Additional context used
🧠 Learnings (20)
📓 Common learnings
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/bindings/JSValue.zig:545-586
Timestamp: 2025-11-03T20:40:59.655Z
Learning: In Bun's Zig codebase, JSErrors (returned as `bun.JSError!T`) must always be properly handled. Using `catch continue` or `catch { break; }` to silently suppress JSErrors is a bug. Errors should either be explicitly handled or propagated with `try`. This applies to snapshot serializer error handling where Jest's behavior is to throw when serializers throw.
📚 Learning: 2025-10-20T01:38:02.660Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/BunFetchInstrumentation.ts:126-131
Timestamp: 2025-10-20T01:38:02.660Z
Learning: In BunFetchInstrumentation.ts, the force-restore to ORIGINAL_FETCH in the disable() method is intentionally kept (despite appearing unsafe) because it's required for proper test cleanup when instrumentation is repeatedly enabled/disabled. Without it, 15 distributed tracing and context propagation tests fail. Shimmer's unwrap() doesn't reliably restore the original fetch in Bun's globalThis context. The isBunOtelPatched safety check ensures the restore only happens when the current fetch is still ours, preventing clobbering of other tools' wrappers.

Applied to files:

  • test/js/bun/util/inspect.test.js
📚 Learning: 2025-10-08T13:48:02.430Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: test/js/bun/tarball/extract.test.ts:107-111
Timestamp: 2025-10-08T13:48:02.430Z
Learning: In Bun's test runner, use `expect(async () => { await ... }).toThrow()` to assert async rejections. Unlike Jest/Vitest, Bun does not require `await expect(...).rejects.toThrow()` - the async function wrapper with `.toThrow()` is the correct pattern for async error assertions in Bun tests.

Applied to files:

  • test/js/bun/util/inspect.test.js
📚 Learning: 2025-11-03T20:40:59.655Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/bindings/JSValue.zig:545-586
Timestamp: 2025-11-03T20:40:59.655Z
Learning: In Bun's Zig codebase, JSErrors (returned as `bun.JSError!T`) must always be properly handled. Using `catch continue` or `catch { break; }` to silently suppress JSErrors is a bug. Errors should either be explicitly handled or propagated with `try`. This applies to snapshot serializer error handling where Jest's behavior is to throw when serializers throw.

Applied to files:

  • test/js/bun/util/inspect.test.js
  • src/bun.js/modules/NodeUtilTypesModule.cpp
  • src/bun.js/test/pretty_format.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/ConsoleObject.zig
  • src/bun.js/bindings/napi.cpp
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.

Applied to files:

  • test/js/bun/util/inspect.test.js
📚 Learning: 2025-11-08T04:06:33.198Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24491
File: test/js/bun/transpiler/declare-global.test.ts:17-17
Timestamp: 2025-11-08T04:06:33.198Z
Learning: In Bun test files, `await using` with Bun.spawn() is the preferred pattern for spawned processes regardless of whether they are short-lived or long-running. Do not suggest replacing `await using proc = Bun.spawn(...)` with `const proc = Bun.spawn(...); await proc.exited;`.

Applied to files:

  • test/js/bun/util/inspect.test.js
📚 Learning: 2025-09-20T03:39:41.770Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.770Z
Learning: Bun's test runner supports async describe callbacks, unlike Jest/Vitest where describe callbacks must be synchronous. The syntax `describe("name", async () => { ... })` is valid in Bun.

Applied to files:

  • test/js/bun/util/inspect.test.js
📚 Learning: 2025-09-20T00:58:38.042Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: test/js/valkey/valkey.test.ts:561-564
Timestamp: 2025-09-20T00:58:38.042Z
Learning: For test/js/valkey/valkey.test.ts, do not comment on synchronous throw assertions for async Redis methods (like ctx.redis.set(), ctx.redis.unsubscribe(), etc.) - Bun's Redis client implementation differs from Node.js and can throw synchronously even for async methods. The maintainer has explicitly requested to stop looking at this error pattern.

Applied to files:

  • test/js/bun/util/inspect.test.js
📚 Learning: 2025-09-30T22:53:19.887Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 23117
File: src/bun.js/test/snapshot.zig:265-276
Timestamp: 2025-09-30T22:53:19.887Z
Learning: In Bun's snapshot testing (src/bun.js/test/snapshot.zig), multiple inline snapshots at the same line and column (same call position) must have identical values. However, multiple inline snapshots on the same line at different columns are allowed to have different values. The check is position-specific (line+col), not line-wide.

Applied to files:

  • test/js/bun/util/inspect.test.js
📚 Learning: 2025-10-25T17:20:19.041Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 24063
File: test/js/bun/telemetry/server-header-injection.test.ts:5-20
Timestamp: 2025-10-25T17:20:19.041Z
Learning: In the Bun telemetry codebase, tests are organized into two distinct layers: (1) Internal API tests in test/js/bun/telemetry/ use numeric InstrumentKind enum values to test Zig↔JS injection points and low-level integration; (2) Public API tests in packages/bun-otel/test/ use string InstrumentKind values ("http", "fetch", etc.) to test the public-facing BunSDK and instrumentation APIs. This separation allows internal tests to use efficient numeric enums for refactoring flexibility while the public API maintains a developer-friendly string-based interface.

Applied to files:

  • test/js/bun/util/inspect.test.js
📚 Learning: 2025-10-18T23:43:42.502Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23817
File: src/js/node/test.ts:282-282
Timestamp: 2025-10-18T23:43:42.502Z
Learning: In the Bun repository, the error code generation script (generate-node-errors.ts) always runs during the build process. When reviewing code that uses error code intrinsics like $ERR_TEST_FAILURE, $ERR_INVALID_ARG_TYPE, etc., do not ask to verify whether the generation script has been run or will run, as it is automatically executed.

Applied to files:

  • src/bun.js/modules/NodeUtilTypesModule.cpp
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.

Applied to files:

  • src/bun.js/modules/NodeUtilTypesModule.cpp
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/ConsoleObject.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/napi.cpp
📚 Learning: 2025-10-01T21:49:27.862Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/BunIDLConvert.h:29-42
Timestamp: 2025-10-01T21:49:27.862Z
Learning: In Bun's IDL bindings (src/bun.js/bindings/BunIDLConvert.h), IDLStrictNull intentionally treats both undefined and null as null (using isUndefinedOrNull()), matching WebKit's IDLNull & IDLNullable behavior. This is the correct implementation and should not be changed to only accept null.

Applied to files:

  • src/bun.js/modules/NodeUtilTypesModule.cpp
  • src/bun.js/ConsoleObject.zig
📚 Learning: 2025-10-17T01:21:35.060Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23755
File: src/bun.js/bindings/BunIDLConvert.h:79-95
Timestamp: 2025-10-17T01:21:35.060Z
Learning: In JavaScriptCore (used by Bun), the `toBoolean()` method implements ECMAScript's ToBoolean abstract operation, which is side-effect-free and cannot throw exceptions. It does not execute arbitrary JavaScript or invoke user-defined methods like Symbol.toPrimitive. Exception handling is not needed after toBoolean() calls.

Applied to files:

  • src/bun.js/modules/NodeUtilTypesModule.cpp
📚 Learning: 2025-10-11T15:19:30.301Z
Learnt from: mastermakrela
Repo: oven-sh/bun PR: 19167
File: src/bun.js/api.zig:31-31
Timestamp: 2025-10-11T15:19:30.301Z
Learning: The learnings about `pub const js = JSC.Codegen.JS<ClassName>` and re-exporting toJS/fromJS/fromJSDirect apply to class-based Zig bindings with constructors and prototype methods (e.g., those with `new` constructors). They do NOT apply to simple namespace-style API objects like TOMLObject, YAMLObject, and CSVObject which expose static functions via a `create()` method that returns a plain JS object.

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/ConsoleObject.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-11-03T20:43:06.996Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/test/snapshot.zig:19-19
Timestamp: 2025-11-03T20:43:06.996Z
Learning: In Bun's Zig codebase, when storing JSValue objects in collections like ArrayList, use `jsc.Strong.Optional` (not raw JSValue). When adding values, wrap them with `jsc.Strong.Optional.create(value, globalThis)`. In cleanup code, iterate the collection calling `.deinit()` on each Strong.Optional item before calling `.deinit()` on the ArrayList itself. This pattern automatically handles GC protection. See examples in src/bun.js/test/ScopeFunctions.zig and src/bun.js/node/node_cluster_binding.zig.

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-10-18T20:50:47.750Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: src/bun.js/telemetry.zig:366-373
Timestamp: 2025-10-18T20:50:47.750Z
Learning: In Bun's Zig codebase (src/bun.js/bindings/JSValue.zig), the JSValue enum uses `.null` (not `.js_null`) for JavaScript's null value. Only `js_undefined` has the `js_` prefix to avoid collision with Zig's built-in `undefined` keyword. The correct enum fields are: `js_undefined`, `null`, `true`, `false`, and `zero`.

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/ConsoleObject.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-09-19T19:55:22.427Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: src/valkey/js_valkey_functions.zig:853-854
Timestamp: 2025-09-19T19:55:22.427Z
Learning: In Bun's JSPromise API, `resolvedPromiseValue(globalThis: *JSGlobalObject, value: JSValue) JSValue` returns a JSValue directly (not a JSPromise that needs `.toJS()` called on it). This is different from `resolvedPromise()` which returns a *JSPromise that would need `.toJS()`.

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-09-05T19:49:26.188Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 21728
File: src/valkey/js_valkey_functions.zig:852-867
Timestamp: 2025-09-05T19:49:26.188Z
Learning: In Bun’s Zig code, `.js_undefined` is a valid and preferred JSValue literal for “undefined” (e.g., resolving JSPromise). Do not refactor usages to `jsc.JSValue.jsUndefined()`, especially in src/valkey/js_valkey_functions.zig unsubscribe().

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-09-05T19:49:26.188Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 21728
File: src/valkey/js_valkey_functions.zig:852-867
Timestamp: 2025-09-05T19:49:26.188Z
Learning: In Bun's Zig codebase, `.js_undefined` is a valid way to represent JavaScript's undefined value when working with JSPromise.resolve() and similar JavaScript interop functions. This is the correct pattern to use rather than `jsc.JSValue.jsUndefined()`.

Applied to files:

  • src/bun.js/bindings/JSValue.zig
🪛 Biome (2.1.2)
test/js/bun/util/inspect.test.js

[error] 98-100: This getter should return a value.

(lint/suspicious/useGetterReturn)

🔇 Additional comments (10)
src/bun.js/bindings/JSValue.zig (1)

1278-1279: getPrototype wrapper correctly exposes an error-aware API

The new getPrototype signature and implementation are consistent with other bun.cpp bindings that use JSError!T, ensuring prototype access failures propagate instead of leaking .zero sentinels into callers.

src/bun.js/ConsoleObject.zig (3)

2035-2043: getObjectName now correctly propagates prototype lookup errors

Switching to try value.getPrototype(globalThis) ensures exceptions from Object.getPrototypeOf/proxy traps surface as bun.JSError instead of being silently ignored, while still treating a .null prototype as [Object: null prototype]. This matches the project’s convention of not suppressing JSError!T results. Based on learnings, this is the desired behavior.


2317-2339: Class formatter safely handles exceptions when reading the prototype

Using const proto = try value.getPrototype(this.globalThis); means any error in the class’s prototype chain aborts formatting via JSError instead of flowing a bogus .zero into getClassName, which previously could crash. The rest of the .Class formatting logic is unchanged for successful lookups.


2344-2346: Function formatter now treats prototype failures as real errors

Fetching the function’s prototype with try value.getPrototype(this.globalThis) before calling proto.getName correctly turns prototype-chain failures into JSError rather than undefined behavior on .zero/non-cell values. This keeps the “Function/AsyncFunction/GeneratorFunction” labeling behavior intact when prototype retrieval succeeds.

src/bun.js/bindings/bindings.cpp (2)

3694-3698: Confirm zero_is_throw contract for JSC__JSValue__getPrototype.

Annotating this binding with [[ZIG_EXPORT(zero_is_throw)]] matches the goal of making getPrototype error-propagating into Zig, assuming JSValue::getPrototype returns an empty JSValue on exception (which encodes to 0). The implementation itself is otherwise unchanged and looks fine.

Please just double-check that:

  • The Zig side now treats this as JSError!JSValue (or equivalent) and uses try rather than ignoring errors.
  • There are no remaining call sites that still assume this binding can never throw.

This keeps the new error semantics consistent with other JSError-based bindings.


4967-4974: Exception checks around getPrototype in forEachPropertyImpl look correct.

Adding RETURN_IF_EXCEPTION(scope, void()); after both object->getPrototype(globalObject) and iterating->getPrototype(globalObject) ensures proxy [[GetPrototypeOf]] traps (and other prototype lookups) can’t leave a pending exception during property traversal. Semantics for normal null/object prototypes remain unchanged.

This aligns with the rest of the function’s exception handling and should address the fuzzed Bun.inspect crash path without introducing new behavior changes.

Also applies to: 5168-5170

src/bun.js/modules/NodeUtilTypesModule.cpp (1)

120-145: Safely propagating exceptions from getPrototype in jsFunctionIsError

Adding RETURN_IF_EXCEPTION(scope, {}); after object->getPrototype(globalObject) correctly treats prototype lookup as potentially throwing and bails out via the existing throw scope instead of risking an unhandled VM exception. This is consistent with the updated error‑aware getPrototype semantics.

src/bun.js/bindings/napi.cpp (1)

1820-1841: Correctly mapping prototype access exceptions in napi_get_all_property_names

Refactoring the prototype climb to go through a JSValue plus NAPI_RETURN_IF_EXCEPTION(env); ensures any exception from getPrototype(globalObject) is converted into a napi_pending_exception instead of leaking through the VM, while preserving the existing null‑prototype break behavior.

src/bun.js/test/ScopeFunctions.zig (1)

441-458: Propagating getPrototype errors correctly in bind

Using try (try value.getPrototype(globalThis)) ensures both prototype lookup and setPrototypeDirect are error‑checked and propagated through the bun.JSError!JSValue result of bind, instead of silently swallowing failures. This aligns with the project’s JSError!T handling guidelines. Based on learnings, this is the intended pattern.

src/bun.js/test/pretty_format.zig (1)

716-748: Properly unwrapping error-aware getPrototype in PropertyIterator.handleFirstProperty

Switching to try (try value.getPrototype(globalThis)).getNameProperty(...) makes prototype name lookup fully error‑aware: any failure in retrieving the prototype or its name now propagates through the bun.JSError!void result instead of being ignored, while preserving the previous formatting behavior for non‑Object prototypes. Based on learnings, this is the correct way to handle JSError!JSValue.

Comment on lines +85 to +107
it("stack overflow exception checks", () => {
function probe(value) {
let originalPrototype, newPrototype;
let handler = {
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
};
originalPrototype = Object.getPrototypeOf(value);
newPrototype = new Proxy(originalPrototype, handler);
Object.setPrototypeOf(value, newPrototype);
}
class Foo {
get bar() {
Bun.inspect(this);
}
}
const foo = new Foo();
probe(foo);
expect(() => {
foo.bar(Foo, foo);
}).toThrow("Maximum call stack size exceeded");
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

New regression test for inspect stack overflow looks good; consider silencing the getter lint

The "stack overflow exception checks" test correctly exercises a recursive Bun.inspect path via a proxy‑wrapped prototype and asserts that it now surfaces as "Maximum call stack size exceeded" instead of crashing, which matches the PR goal.

Static analysis is flagging the get bar() accessor for not returning a value. While this is valid JS (it implicitly returns undefined), you might either:

  • make the intent explicit:
class Foo {
  get bar() {
    return Bun.inspect(this);
  }
}
  • or add a Biome ignore comment for this getter, if you prefer the current shape.

Both keep the test behavior while satisfying the linter.

🧰 Tools
🪛 Biome (2.1.2)

[error] 98-100: This getter should return a value.

(lint/suspicious/useGetterReturn)

🤖 Prompt for AI Agents
In test/js/bun/util/inspect.test.js around lines 85 to 107, the getter get bar()
is flagged by the linter for not returning a value; update the getter to
explicitly return Bun.inspect(this) (i.e., make the intent explicit) or add the
appropriate Biome/linter ignore comment above the getter to silence the warning
while preserving the test behavior.

The getPrototype function was missing a proper exception check
using DECLARE_THROW_SCOPE and RETURN_IF_EXCEPTION. This fixes
exception validation when BUN_JSC_validateExceptionChecks is enabled.

Also fixed the exception check pattern in forEachPropertyImpl to check
for exceptions immediately after getPrototype returns, rather than
only in the else branch (when getPrototype returns null/falsy).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@robobun
Copy link
Collaborator

robobun commented Nov 26, 2025

Update

Added additional exception check fixes in bindings.cpp:

  1. JSC__JSValue__getPrototype: Added proper DECLARE_THROW_SCOPE and RETURN_IF_EXCEPTION to satisfy exception validation requirements.

  2. forEachPropertyImpl: Fixed the exception check pattern to check for exceptions immediately after getPrototype returns, rather than only in the else branch (when getPrototype returns null/falsy).

WebKit Fix Required

The remaining exception check failure is in WebKit's ProxyObject::performGetPrototype function. I've created a PR for the oven-sh/WebKit fork:

oven-sh/WebKit#117

The issue is at line 1248-1249 in ProxyObject.cpp:

if (getPrototypeOfMethod.isUndefined()) 
    RELEASE_AND_RETURN(scope, target->getPrototype(globalObject));

target->getPrototype(globalObject) can throw an exception (e.g., stack overflow when the target is also a Proxy with a recursive chain), but RELEASE_AND_RETURN releases the scope without checking for exceptions first.

Once the WebKit fix is merged and a new WebKit build is used, the test should pass with BUN_JSC_validateExceptionChecks=1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants