Skip to content

process.report: validate arguments and embed user error in getReport() - #34405

Closed
robobun wants to merge 9 commits into
mainfrom
claude/farm/30382c9e/process-report-validation
Closed

robobun wants to merge 9 commits into
mainfrom
claude/farm/30382c9e/process-report-validation

Conversation

@robobun

@robobun robobun commented Jul 16, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes process.report argument validation and error embedding to match Node.js.

Repro

const r = process.report;
const g = r.getReport(new Error("marker-xyzzy"));
console.log(JSON.stringify(g.javascriptStack).includes("marker-xyzzy")); // node: true; bun: false
console.log(g.javascriptStack.message); // node: "Error: marker-xyzzy"; bun: "Error [ERR_SYNTHETIC]: JavaScript Callstack"

for (const v of [42, "x", null]) {
  try { r.getReport(v); console.log("accepted", v); }  // bun: all accepted
  catch (e) { console.log(e.code, v); }                // node: ERR_INVALID_ARG_TYPE x3
}
for (const [p, v] of [["compact", 1], ["directory", 123], ["signal", "NOTASIG"]]) {
  try { r[p] = v; console.log(`set ${p}:`, r[p]); }    // bun: all stick
  catch (e) { console.log(`set ${p}:`, e.code); }      // node: ERR_INVALID_ARG_TYPE / ERR_UNKNOWN_SIGNAL
}

Cause

Process_functionGetReport never read callFrame arguments, so the passed error was discarded and the synthetic current stack was substituted. The process.report configuration properties were plain putDirect data properties with no setter validation, so any value was accepted verbatim. The old constructor also wrote the "SIGUSR2" default onto the "excludeEnv" key instead of "signal", and omitted excludeNetwork entirely.

Fix

  • getReport(err): when err is provided it must be a non-null object (ERR_INVALID_ARG_TYPE otherwise). Its .stack is split into javascriptStack.message (first line) and javascriptStack.stack (remaining trimmed lines), and its own enumerable properties (other than stack/message) are stringified into javascriptStack.errorProperties. If .stack is not a string, Node's "No stack." / ["Unavailable."] shape is used. Omitting err keeps the existing synthetic behavior.
  • writeReport(file, err): validates the (string?, object?) / (object) overloads the same way Node does. The underlying write is still a no-op.
  • The nine configuration properties (compact, directory, filename, signal, reportOnFatalError, reportOnSignal, reportOnUncaughtException, excludeEnv, excludeNetwork) are now native accessors backed by fields on Process. Setters run validateBoolean / validateString / validateSignalName, throwing ERR_INVALID_ARG_TYPE or ERR_UNKNOWN_SIGNAL. reportOnUncaughtException uses its own storage so it no longer aliases the setUncaughtExceptionCaptureCallback "already set" flag.
  • The javascriptStack builder is factored into a single constructReportJavaScriptStack shared by the POSIX and Windows report paths.

Verification

bun bd test test/js/node/process/process.test.js -t "process.report" passes (18 tests) on linux-x64 and windows-x64; the new cases fail under USE_SYSTEM_BUN=1.


no test proof · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/node/process/process.test.js

process.report.getReport(err) previously ignored its argument entirely,
always emitting the synthetic ERR_SYNTHETIC stack. It now validates that
err is an object (ERR_INVALID_ARG_TYPE otherwise) and embeds the error's
own .stack and own enumerable properties into javascriptStack, matching
Node.js.

The configuration properties on process.report (compact, directory,
filename, signal, reportOnFatalError, reportOnSignal,
reportOnUncaughtException, excludeEnv, excludeNetwork) are now native
accessors with Node-compatible type validation (ERR_INVALID_ARG_TYPE for
boolean/string mismatches, ERR_UNKNOWN_SIGNAL for an unrecognized signal
name). Previously they were plain data properties that accepted any value.

This also fixes two incidental bugs in the old constructor: the default
for 'signal' was being written to the 'excludeEnv' key, and
'excludeNetwork' was missing entirely.

writeReport() now validates its (file, err) overloads the same way Node
does; the underlying write is still a no-op.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Refactors process.report to build error-aware JavaScript stacks, expose configurable report settings through accessors, validate getReport() and writeReport() arguments, and add comprehensive API and configuration tests.

Process report behavior

Layer / File(s) Summary
Error-aware report generation
src/jsc/bindings/BunProcess.cpp, src/jsc/bindings/BunProcessReportObjectWindows.cpp, test/js/node/process/process.test.js
Report generation now uses supplied error messages, stacks, and enumerable properties, or synthesizes an ERR_SYNTHETIC stack when no error is provided.
Report configuration accessors
src/jsc/bindings/BunProcess.h, src/jsc/bindings/BunProcess.cpp, test/js/node/process/process.test.js
Report settings gain internal state, custom accessors, and getReport/writeReport methods, with default and assignment validation tests.
writeReport argument handling
src/jsc/bindings/BunProcess.cpp, test/js/node/process/process.test.js
writeReport() normalizes optional file and error arguments and tests invalid and valid input types.

Possibly related PRs

  • oven-sh/bun#34229: Both update the process.report exposure and report construction paths.
  • oven-sh/bun#34400: Both modify report construction and environment/network exclusion handling.
  • oven-sh/bun#34401: Both modify writeReport() argument handling and shared report construction.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main change: process.report argument validation and embedding user errors in getReport().
Description check ✅ Passed The description covers the PR purpose, cause/fix details, and verification results, so it satisfies the template intent.

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

@robobun

robobun commented Jul 16, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 7:39 PM PT - Jul 16th, 2026

❌ @robobun, your commit 0f8584f has 3 failures in Build #74208 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34405

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

bun-34405 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. process metadata properties are writable unlike Node.js #34228 - PR converts process.report config properties from writable data properties to native custom accessor properties with validation, directly addressing the report that process.report properties are writable unlike Node.js

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #34228

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. process: fix report.excludeEnv copy-paste, add signal/excludeNetwork, honor in getReport() #34400 - Fixes the same excludeEnv/signal/excludeNetwork constructor bug and wires the same getReport() changes in BunProcess.cpp
  2. node:process: fix fabricated values in process.report.getReport() #34403 - Overlapping getReport() fixes in the same files (BunProcess.cpp, Windows report builder, process.test.js)

🤖 Generated with Claude Code

@robobun

robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Not duplicates of #34400 or #34403; the three are complementary:

The only functional overlap is the signal/excludeNetwork default fix, which this PR subsumes by replacing those data properties with accessors. Whichever of this and #34400 lands second will need a small rebase around constructProcessReportObject.

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/jsc/bindings/BunProcess.cpp`:
- Around line 2649-2650: The excludeEnv and excludeNetwork settings are exposed
but not applied when generating reports. Update POSIX report assembly in
src/jsc/bindings/BunProcess.cpp lines 2649-2650 to consult both fields and omit
the protected sections, update
src/jsc/bindings/BunProcessReportObjectWindows.cpp line 46 to pass process
through the equivalent exclusion logic on Windows, and add assertions in
test/js/node/process/process.test.js lines 1101-1112 verifying reports omit
environment and network data after each setting is enabled.
- Around line 2538-2559: Update Process_functionWriteReport in
src/jsc/bindings/BunProcess.cpp:2538-2559 to generate and write the process
report after argument normalization, then return the resulting output filename
instead of echoing file. Add a temp-directory test in
test/js/node/process/process.test.js:1052-1061 that verifies writeReport creates
the file and that it contains report data.
- Around line 2643-2647: Check for and propagate any pending exception
immediately after JSC::constructEmptyObject in the process report setup, before
mutating report with putDirectCustomAccessor or subsequent properties. Reuse the
surrounding DECLARE_TOP_EXCEPTION_SCOPE(vm) handling and preserve the existing
return behavior.
- Around line 2112-2116: Update the javascriptStack construction around
errorObject stack access to use a local catch scope, clearing and ignoring
exceptions from both the stack getter and getOwnPropertyNames() so
process.report.getReport() returns its fallback or partial report. Preserve
successfully retrieved stack data, and add coverage for throwing getters and
proxies.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7be850e7-e876-4a52-866d-ac31b895ab93

📥 Commits

Reviewing files that changed from the base of the PR and between 2f5e810 and 5faf248.

📒 Files selected for processing (4)
  • src/jsc/bindings/BunProcess.cpp
  • src/jsc/bindings/BunProcess.h
  • src/jsc/bindings/BunProcessReportObjectWindows.cpp
  • test/js/node/process/process.test.js

Comment thread src/jsc/bindings/BunProcess.cpp Outdated
Comment thread src/jsc/bindings/BunProcess.cpp
Comment thread src/jsc/bindings/BunProcess.cpp
Comment thread src/jsc/bindings/BunProcess.cpp
…and properties

getReport(err) now uses a TopExceptionScope around the .stack getter,
property-name enumeration, and per-property reads so a throwing getter,
throwing toString, or Proxy trap falls back to the "No stack." shape or
skips the offending property instead of aborting the whole report. This
matches Node's TryCatch-wrapped PrintJavaScriptErrorStack.

Also adds the missing RETURN_IF_EXCEPTION after constructEmptyObject in
constructProcessReportObject.
Comment thread test/js/node/process/process.test.js Outdated
Comment thread src/jsc/bindings/BunProcess.cpp
Custom getters/setters and getReport() receive the lexical global, which
is a NodeVMGlobalObject (sibling of Zig::GlobalObject) when invoked from
a node:vm context. Route through defaultGlobalObject() like the rest of
this file instead of uncheckedDowncast.
Comment thread src/jsc/bindings/BunProcess.cpp
Comment thread src/jsc/bindings/BunProcess.cpp Outdated
…ctions for err

errorProperties enumeration now skips integer-index own keys, matching
Node and avoiding the putDirect ASSERT(!parseIndex(propertyName)) on
inputs like {stack: '...', 0: 'v'}.

getReport(err)/writeReport(err) now go through V::validateObject so
arrays and functions are rejected with ERR_INVALID_ARG_TYPE like Node.
writeReport's first-arg overload shuffle excludes callables so a
function argument fails validateString('file') rather than being
treated as err.

@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.

Caution

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

⚠️ Outside diff range comments (1)
src/jsc/bindings/BunProcess.cpp (1)

2503-2503: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Check for exceptions before storing the generated stack.

constructReportJavaScriptStack can enter JS and return an empty value with a termination exception pending. Line 2503 immediately passes that result to putDirect.

Proposed fix
-        report->putDirect(vm, JSC::Identifier::fromString(vm, "javascriptStack"_s), constructReportJavaScriptStack(vm, globalObject, errorValue), 0);
+        auto javascriptStack = constructReportJavaScriptStack(vm, globalObject, errorValue);
+        RETURN_IF_EXCEPTION(scope, {});
+        report->putDirect(vm, JSC::Identifier::fromString(vm, "javascriptStack"_s), javascriptStack, 0);

As per coding guidelines, check for exceptions after every call that can throw or run user code before using its result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jsc/bindings/BunProcess.cpp` at line 2503, Update the report construction
flow around constructReportJavaScriptStack to check vm.exception() immediately
after the call and before passing its result to report->putDirect. Return or
propagate the pending exception using the surrounding function’s established
error-handling path, while preserving the existing property assignment when no
exception is pending.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/jsc/bindings/BunProcess.cpp`:
- Line 2503: Update the report construction flow around
constructReportJavaScriptStack to check vm.exception() immediately after the
call and before passing its result to report->putDirect. Return or propagate the
pending exception using the surrounding function’s established error-handling
path, while preserving the existing property assignment when no exception is
pending.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c934786-31d3-4f41-9eed-3a750118c2e1

📥 Commits

Reviewing files that changed from the base of the PR and between 5faf248 and 0363762.

📒 Files selected for processing (2)
  • src/jsc/bindings/BunProcess.cpp
  • test/js/node/process/process.test.js

…tDirect

constructReportJavaScriptStack can return an empty JSValue when a
termination exception is pending; check for that before passing the
result to putDirect (which asserts on empty values).
Comment thread src/jsc/bindings/BunProcess.cpp Outdated
…ll platforms

isSignalName() on Windows only covers the seven libuv-registerable
console signals, so the setter rejected its own "SIGUSR2" default.
Validate against the full getSignalNames() table instead so names like
SIGUSR1/SIGUSR2/SIGABRT are accepted on Windows, matching Node's
os.constants.signals-based validateSignalName.
Comment thread src/jsc/bindings/BunProcess.cpp

@claude claude 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.

No new issues found on this pass, but this is ~350 lines of JSC binding C++ (custom accessors, catch scopes over user-controlled property enumeration, new Process fields) that took several fix rounds to settle — worth a human look, especially on the declined SIGPWR/os.constants.signals parity question the author left to maintainer preference.

What was reviewed:

  • constructReportJavaScriptStack: exception-scope discipline around user .stack reads, getOwnPropertyNames, and toString; parseIndex skip avoids the putDirect assert; the no-newline .stack case correctly omits the stack array.
  • Accessor macros / setSignal: all sites go through defaultGlobalObject() (node:vm safe); getSignalNames() scan accepts SIGUSR2 on Windows.
  • getReport/writeReport arg validation via Bun::V::validateObject — arrays/functions rejected; writeReport overload shuffle excludes callables so functions route to the file error.
Extended reasoning...

Overview

This PR reworks process.report in src/jsc/bindings/BunProcess.cpp (+ the Windows twin and BunProcess.h): getReport(err) now embeds the passed error's stack into javascriptStack, writeReport validates its (file, err) overloads, and the nine configuration properties become native CustomAccessors backed by new Process member fields with validateBoolean/validateString/signal-name validation. The duplicated POSIX/Windows javascriptStack builders are unified into a shared constructReportJavaScriptStack. ~230 lines of tests added to process.test.js.

Security risks

Low but nonzero. constructReportJavaScriptStack enumerates and reads properties off a user-supplied object under a catch scope — the classic "user code can throw / re-enter" surface. The catch-scope handling looks correct (each get/toString failure is cleared and skipped, termination is propagated), and the earlier-found node:vm type-confusion and putDirect index-key assert have been fixed. No auth/crypto/filesystem writes are touched (writeReport remains a validation-only no-op).

Level of scrutiny

Medium-high. This is hand-written JSC binding C++ with ThrowScope/TopExceptionScope interleaving, macro-generated custom getters/setters, and new state on the Process object. The PR's own history — five fix commits addressing a node:vm type-confusion crash, a debug-build assert on integer-index keys, array/function validation gaps, and a Windows signal-table mismatch — demonstrates the surface is subtle enough to warrant human eyes even after automated review is clean.

Other factors

  • All my prior blocking findings are resolved and covered by new tests (node:vm subprocess test, integer-index test, SIGUSR2 round-trip row).
  • One nit was intentionally declined: the signal setter validates against the fixed 32-entry getSignalNames() table rather than the platform-derived os.constants.signals, so SIGPWR/SIGSTKFLT/SIGPOLL are rejected on Linux and SIGINFO/SIGBREAK are over-accepted. The author left this to maintainer preference.
  • m_reportOnUncaughtException (pre-existing, aliases the capture-callback flag) and the new m_reportReportOnUncaughtException now coexist on Process — intentional per the PR description, but a maintainer may want to weigh in on the naming/duplication.
  • Candidate issues examined and ruled out this run: the "no newline in err.stack → javascriptStack.stack omitted" case matches Node's shape (Node also omits the stack array when the split yields a single line).

@robobun

robobun commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

CI status: the process.report tests (24 cases in test/js/node/process/process.test.js) pass on every lane across builds #74141 and #74208. The remaining red in #74208 is three pre-existing breaks on main (test-worker-message-port-transfer-terminate.js, test-http2-reset-flood.js, no-orphans.test.ts) plus a handful of known flakes, none of which touch process.report, BunProcess.cpp, or worker/http2 code this diff changes. Ready for review.

@robobun

robobun commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-07-17, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main.

@robobun robobun closed this Sep 13, 2026
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.

1 participant