Skip to content

fetch: throw TypeError when init is a non-nullish primitive - #29198

Open
robobun wants to merge 1 commit into
mainfrom
farm/814397a5/fetch-init-typeerror
Open

robobun wants to merge 1 commit into
mainfrom
farm/814397a5/fetch-init-typeerror

Conversation

@robobun

@robobun robobun commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator

What / why

fetch(url, 0), fetch(url, ""), fetch(url, false), fetch(url, 0n) and fetch(url, Symbol()) silently resolved in Bun instead of rejecting with a TypeError.

Per the WHATWG Fetch spec, the init argument is a Web IDL dictionary, and dictionary conversion throws TypeError for primitives other than undefined/null. Node.js honours this — Bun did not.

Root cause

src/bun.js/webcore/fetch.zig fetchImpl pulled the second argument with args.nextEat() and kept it only if it was an object / DOMWrapper. For every other value type it fell through to null and treated the call as if init had been omitted.

Fix

Explicit three-way check on the second argument:

  • undefined / null → treated as missing (spec-compliant).
  • object or DOMWrapper → used as options.
  • anything else (number, bigint, string, boolean, symbol) → reject with TypeError.

Repro

await fetch('https://example.com', 0); // now rejects with TypeError
await fetch('https://example.com', ''); // TypeError
await fetch('https://example.com', false); // TypeError
await fetch('https://example.com', Symbol('x')); // TypeError

Verification

test/regression/issue/29195.test.ts covers number, bigint, string, boolean, and symbol init (all must reject with TypeError) plus undefined, null, and object init (must not reject with TypeError).

  • Against the baked 1.3.11 bun: 8 fail / 3 pass (reproduces the bug).
  • Against this build: 11 pass.

Fixes #29195

@coderabbitai

coderabbitai Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Added WebIDL-style validation for the optional init parameter in fetch() and Request construction: non-object primitives (excluding undefined/null) now cause a synchronous TypeError/rejection; control flow preserves URL-related errors occurring before init validation.

Changes

Cohort / File(s) Summary
Fetch API init validation
src/bun.js/webcore/fetch.zig
Defer consumption of the second argument; validate init per WebIDL (allow undefined/null or object/DOMWrapper). If init is an invalid primitive, return a rejected promise with a TypeError specifying "init" must be object, undefined, or null.
Request constructor init validation
src/bun.js/webcore/Request.zig
Added an early type check in constructInto that throws INVALID_ARG_TYPE TypeError when the optional init is neither undefined/null nor an object. This check runs after URL handling to preserve error ordering.
Tests
test/js/web/fetch/fetch.test.ts
Added tests (init argument validation (#29195)) asserting fetch(url, init) and new Request(url, init) reject/throw for invalid primitive init values and accept undefined, null, or objects; includes assertion ensuring URL errors surface before init type errors.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fetch: throw TypeError when init is a non-nullish primitive' accurately and concisely describes the main change—adding TypeError rejection for invalid init argument types.
Description check ✅ Passed The description comprehensively covers the issue, root cause, fix, repro steps, and verification results. All template sections are filled with detailed, relevant information.
Linked Issues check ✅ Passed The PR fully addresses issue #29195's requirements: fetch now rejects with TypeError for non-nullish primitives and preserves correct behavior for undefined, null, and objects.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the three-way init validation in fetch.zig and Request.zig, plus comprehensive tests. No unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@robobun

robobun commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 6:05 AM PT - Aug 14th, 2026

@robobun, your commit 3019519 has some failures in Build #95750 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 29198

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

bun-29198 --bun

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

LGTM — clean, spec-compliant fix with good test coverage. One inline nit about the error message omitting null, but the runtime behavior is correct.

Extended reasoning...

Overview

This PR fixes a spec compliance bug in fetchImpl (src/bun.js/webcore/fetch.zig). The change adds ~9 lines to explicitly handle the init argument as a Web IDL dictionary: undefined/null → treated as omitted, object/DOMWrapper → used as options, any other primitive → TypeError. A new regression test file covers all the relevant cases (number, bigint, string, boolean, symbol must reject; undefined, null, plain object must not reject).

Security risks

None. This is a validation tightening with no auth, crypto, or permission implications.

Level of scrutiny

Low. The change is small, isolated to a single brk: block in fetchImpl, follows the existing pattern of returning JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM, and aligns the implementation with the WHATWG Fetch + Web IDL spec.

Other factors

The one bug found (error message says "must be of type object or undefined" without mentioning null) is a pure developer-experience nit — the runtime behavior of accepting null is already correct and is verified by the test. This doesn't warrant blocking the PR. Test coverage is thorough and well-structured.

Comment thread src/bun.js/webcore/fetch.zig Outdated

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

LGTM — my prior feedback on the error message was addressed in c9de504.

Extended reasoning...

Overview

This PR modifies () to throw a when the argument is a non-nullish primitive (number, bigint, string, boolean, symbol), matching WHATWG Fetch spec / Web IDL dictionary conversion semantics. A regression test file () is added covering all relevant cases. The diff is 9 lines of logic and 44 lines of tests.

Security risks

None. This is a pure input-validation tightening — it rejects more inputs than before (previously silently ignored, now rejected with TypeError). No auth, crypto, or permission code is touched.

Level of scrutiny

Low. The change is a targeted three-way branch (undefined/null → pass through, object/DOMWrapper → use as options, anything else → TypeError), directly following the spec. It follows the existing error-handling pattern used elsewhere in the same function and is fully covered by the new regression tests.

Other factors

My earlier inline comment about the error message omitting was resolved in the follow-up commit (c9de504). The bug hunter flagged a pre-existing gap in () that is out of scope for this PR and should be tracked as a separate issue. No outstanding reviewer comments remain unaddressed.

Comment thread src/bun.js/webcore/fetch.zig Outdated

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/regression/issue/29195.test.ts`:
- Around line 28-34: The tests use Jest-style .rejects assertions but Bun
expects the async throw form; update both cases to use the Bun pattern by
wrapping the async call in a function passed to expect and calling
toThrow(TypeError) — e.g. replace await expect(fetch(url, value as
any)).rejects.toBeInstanceOf(TypeError) with expect(async () => { await
fetch(url, value as any); }).toThrow(TypeError), and similarly replace await
expect(fetch(url, Symbol("test") as any)).rejects.toBeInstanceOf(TypeError) with
expect(async () => { await fetch(url, Symbol("test") as any);
}).toThrow(TypeError); keep the same test names and use the same variables
(bad_init, url, fetch) so only the assertion form changes.
- Around line 36-47: The test using test.each(good_init) relies on a network/DNS
failure to avoid TypeError coupling; change the fetch call to use a
deterministic always-success URL (e.g., a data: URL) instead of url so the
outcome is not tied to network error shapes, then assert that calling fetch with
each value does not reject with a TypeError (or simply does not throw); update
the block around the fetch(...) in the test callback (the async (_label, value)
=> { ... } function) to fetch a guaranteed-success resource and adjust the
expectation to assert no TypeError is thrown.
🪄 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: 14acac2c-f49b-4d3f-8fd3-f5ebd458b8f6

📥 Commits

Reviewing files that changed from the base of the PR and between c9de504 and 39f4a7a.

📒 Files selected for processing (2)
  • src/bun.js/webcore/Request.zig
  • test/regression/issue/29195.test.ts

Comment thread test/regression/issue/29195.test.ts Outdated
Comment thread test/regression/issue/29195.test.ts Outdated

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

LGTM — both prior issues fixed, remaining nits are no-ops with no behavioral impact.

Extended reasoning...

Overview

The PR adds spec-compliant TypeError rejection in both fetchImpl (fetch.zig) and constructInto (Request.zig) when the init argument is a non-nullish primitive. A regression test file covers numbers, bigint, strings, booleans, symbols, and valid values (undefined, null, object).

Security risks

None. This is a validation change that makes Bun throw sooner on invalid input, matching browser and Node.js behavior.

Level of scrutiny

Low. The changes are small, self-contained, and follow the established pattern in the codebase. Both code paths are early-exit guards before any resource allocation occurs.

Other factors

Both issues flagged in prior reviews have been resolved: the error message now correctly lists 'undefined, or null', and Request.zig received the same guard. The two remaining bug reports are harmless nits: a vestigial dead-code is_error = true assignment (pre-existing pattern, zero behavioral impact) and a slightly inaccurate test comment. Test coverage is thorough.

Comment thread src/bun.js/webcore/fetch.zig Outdated
Comment thread test/regression/issue/29195.test.ts Outdated

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

♻️ Duplicate comments (1)
test/regression/issue/29195.test.ts (1)

30-35: 🧹 Nitpick | 🔵 Trivial

Prefer Bun’s async rejection assertion form (toThrow) over .rejects.

Line 31 and Line 35 still use Jest-style .rejects. In Bun tests, the preferred pattern is wrapping the async call and asserting with .toThrow(TypeError).

Suggested diff
   test.each(bad_init)("rejects with TypeError when init is %s", async (_label, value) => {
-    await expect(fetch(url, value as any)).rejects.toBeInstanceOf(TypeError);
+    await expect(async () => {
+      await fetch(url, value as any);
+    }).toThrow(TypeError);
   });

   test("rejects with TypeError when init is a symbol", async () => {
-    await expect(fetch(url, Symbol("test") as any)).rejects.toBeInstanceOf(TypeError);
+    await expect(async () => {
+      await fetch(url, Symbol("test") as any);
+    }).toThrow(TypeError);
   });
#!/bin/bash
set -euo pipefail

# Verify current assertion form in this file
rg -nP --type=ts '\.rejects\.' test/regression/issue/29195.test.ts

# Sample nearby regression-test conventions using async wrapper + toThrow
rg -nP --type=ts 'expect\s*\(\s*async\s*\(\)\s*=>\s*\{' test/regression/issue -g '*.test.ts' -m 20
rg -nP --type=ts '\)\.toThrow\(' test/regression/issue -g '*.test.ts' -m 20

Based on learnings, in Bun tests async rejection assertions should use expect(async () => { await ... }).toThrow().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/regression/issue/29195.test.ts` around lines 30 - 35, Replace the
Jest-style `.rejects.toBeInstanceOf(TypeError)` assertions with Bun's async
wrapper + `.toThrow(TypeError)` form: wrap the async call to `fetch(url, value
as any)` used in the `test.each(bad_init)` case and the standalone symbol case
in an `async () => { await fetch(...) }` arrow function and assert `expect(async
() => { await fetch(...) }).toThrow(TypeError)` so both the `test.each` callback
and the `"rejects with TypeError when init is a symbol"` test use Bun's
preferred `toThrow` pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@test/regression/issue/29195.test.ts`:
- Around line 30-35: Replace the Jest-style `.rejects.toBeInstanceOf(TypeError)`
assertions with Bun's async wrapper + `.toThrow(TypeError)` form: wrap the async
call to `fetch(url, value as any)` used in the `test.each(bad_init)` case and
the standalone symbol case in an `async () => { await fetch(...) }` arrow
function and assert `expect(async () => { await fetch(...)
}).toThrow(TypeError)` so both the `test.each` callback and the `"rejects with
TypeError when init is a symbol"` test use Bun's preferred `toThrow` pattern.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f776276c-8a81-4f53-8ddf-b2e8f9fc3912

📥 Commits

Reviewing files that changed from the base of the PR and between 39f4a7a and f383d5c.

📒 Files selected for processing (1)
  • test/regression/issue/29195.test.ts

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

LGTM — all substantive issues from prior reviews have been addressed.

Extended reasoning...

Overview

This PR adds a three-way check on the fetch() and new Request() init argument: undefined/null are treated as absent, objects (including DOMWrapper) are used as options, and any other primitive immediately throws a TypeError. Changes touch fetch.zig (~10 lines), Request.zig (~7 lines), and a new regression test file.

Security risks

None. This is a validation tightening that throws earlier on invalid input — no auth, crypto, or permission paths are touched.

Level of scrutiny

Low. The fix is mechanically straightforward and closely follows the pattern already used throughout fetchImpl. The spec reference (Web IDL dictionary conversion) is accurate, and the test coverage is comprehensive and hermetic (uses data: URLs).

Other factors

Both issues I raised in previous review rounds were fixed. The residual is_error = true assignment is a pre-existing dead-code pattern spanning 40+ call sites across the function — out of scope here. The CodeRabbit test-style suggestion was a labeled nitpick. CI failures in fetch.upgrade.test.ts on aarch64 appear pre-existing and unrelated to these changes.

Comment thread src/bun.js/webcore/Request.zig Outdated
Comment thread test/regression/issue/29195.test.ts Outdated

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/bun.js/webcore/Request.zig`:
- Around line 646-651: The init-type guard in Request.zig is currently
validating arguments[1] too early and can mask exceptions thrown during
first-argument stringification; remove or relocate the existing if-block that
checks arguments[1].isUndefinedOrNull() / isObject() and instead re-insert that
exact validation immediately after the first-argument URL/stringifier conversion
(the constructInto path that reads .url/toString()) completes, but before any
subsequent reads from arguments[1]; keep the same error message and behavior so
the TypeError still fires for invalid init values but only after left-to-right
conversion of the first argument.
🪄 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: a1d9338c-9e16-43ac-b9a8-77ffddbd4e0d

📥 Commits

Reviewing files that changed from the base of the PR and between f383d5c and de185fd.

📒 Files selected for processing (2)
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/fetch.zig

Comment thread src/runtime/webcore/Request.zig Outdated
Comment thread src/bun.js/webcore/fetch.zig Outdated

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

♻️ Duplicate comments (1)
src/bun.js/webcore/Request.zig (1)

646-650: ⚠️ Potential issue | 🟠 Major

init validation is still ordered too early and can mask first-argument conversion errors.

On Line 648, this check runs before the object-input URL/toString conversion path (Lines 759-773).
new Request({ toString() { throw new Error("boom"); } }, 0 as any) will throw the "init" TypeError instead of "boom".

Suggested fix (move validation after first-arg URL/string conversion path)
-    // https://fetch.spec.whatwg.org/#dom-request — init is a Web IDL dictionary.
-    // Checked after the url-string branch so an invalid-URL error surfaces first.
-    if (arguments.len > 1 and !arguments[1].isUndefinedOrNull() and !arguments[1].isObject()) {
-        return globalThis.ERR(.INVALID_ARG_TYPE, "Failed to construct 'Request': The \"init\" argument must be of type object, undefined, or null.", .{}).throw();
-    }

     const values_to_try_ = [_]JSValue{
         if (arguments.len > 1 and arguments[1].isObject())
             arguments[1]
@@
         if (!fields.contains(.url)) {
             if (try value.fastGet(globalThis, .url)) |url| {
                 req.url = try bun.String.fromJS(url, globalThis);
@@
             if (globalThis.hasException()) return error.JSError;
         }
+
+        // Validate `init` only after first-argument URL/toString conversion has run.
+        if (`@intFromEnum`(value) == `@intFromEnum`(values_to_try[values_to_try.len - 1]) and
+            arguments.len > 1 and
+            !arguments[1].isUndefinedOrNull() and
+            !arguments[1].isObject())
+        {
+            return globalThis.ERR(.INVALID_ARG_TYPE, "Failed to construct 'Request': The \"init\" argument must be of type object, undefined, or null.", .{}).throw();
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bun.js/webcore/Request.zig` around lines 646 - 650, The init-argument
type check runs too early in the Request constructor and can mask errors from
converting the first argument (e.g., when the first arg is an object with
toString that throws); move the block that validates arguments[1] (the "init"
object-type check that returns ERR(.INVALID_ARG_TYPE, ...)) to after the
first-argument URL/string conversion path (the code handling object-input
URL/toString conversion in Request.zig), so the first-arg conversion executes
and can propagate its errors before validating the second argument; update any
comments accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/bun.js/webcore/Request.zig`:
- Around line 646-650: The init-argument type check runs too early in the
Request constructor and can mask errors from converting the first argument
(e.g., when the first arg is an object with toString that throws); move the
block that validates arguments[1] (the "init" object-type check that returns
ERR(.INVALID_ARG_TYPE, ...)) to after the first-argument URL/string conversion
path (the code handling object-input URL/toString conversion in Request.zig), so
the first-arg conversion executes and can propagate its errors before validating
the second argument; update any comments accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4368bcfc-e7c8-4e33-83f9-ae8067b3fb23

📥 Commits

Reviewing files that changed from the base of the PR and between de185fd and 7b46a7e.

📒 Files selected for processing (3)
  • src/bun.js/webcore/Request.zig
  • src/bun.js/webcore/fetch.zig
  • test/js/web/fetch/fetch.test.ts

@Jarred-Sumner
Jarred-Sumner force-pushed the farm/814397a5/fetch-init-typeerror branch from 3df2c51 to 0445170 Compare May 4, 2026 10:37
Comment thread src/runtime/webcore/Request.zig Outdated
Comment thread test/regression/issue/29195.test.ts Outdated
@robobun
robobun force-pushed the farm/814397a5/fetch-init-typeerror branch from 8830f06 to ee0efe7 Compare May 25, 2026 14:07
Comment thread src/runtime/webcore/fetch.zig Outdated
Comment thread src/runtime/webcore/fetch.rs Outdated
Comment thread src/runtime/webcore/Request.rs Outdated
@robobun

robobun commented May 25, 2026

Copy link
Copy Markdown
Collaborator Author

The diff is green where it matters: cargo clippy + all build-rust lanes pass, and every Linux test lane including debian-13-x64-asan-test-bun passes (the #29195 tests run there). The one red lane — windows-2019-x64 shard 6/8 — failed on Bun.Transpiler.transform stack overflows and spawn-stdin-readable-stream, both unrelated to this fetch/Request change and both known Windows flakes (a sibling shard of the same Windows lane passed). Needs a maintainer to re-run the flaky shard / merge.

Comment thread test/js/web/fetch/fetch.test.ts Outdated
@robobun

robobun commented May 25, 2026

Copy link
Copy Markdown
Collaborator Author

CI status: cargo clippy + all build-rust lanes green, all Linux test lanes green including debian-13-x64-asan-test-bun (where the #29195 tests run and pass). The only persistent red is windows-2019-x64 shard 6/8, failing on Bun.Transpiler.transform stack overflows / transformSync stack overflows — a transpiler stack-depth test unrelated to this fetch/Request change (not in the diff). It reproduces deterministically on that Windows shard across builds independent of my commits, so it is not something this PR can fix. The fetch/Request init validation itself is complete and verified. Ready for a maintainer to merge (the Windows transpiler flake needs addressing separately).

@robobun
robobun force-pushed the farm/814397a5/fetch-init-typeerror branch 2 times, most recently from 1a84215 to ebcba4f Compare May 28, 2026 23:13
Comment thread test/js/web/fetch/fetch.test.ts

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

The parse error from db39877's predecessor is fixed and I found no new issues, but this is a user-visible behavioral change to fetch()/Request with manual ref-counting in the live Rust path — worth a maintainer's eyes before merge.

Extended reasoning...

Overview

This PR adds WebIDL-compliant validation so that fetch(url, init) and new Request(url, init) reject/throw TypeError when init is a non-nullish primitive (number, string, boolean, bigint, symbol). It touches the live Rust implementations in src/runtime/webcore/fetch.rs (fetch_impl) and src/runtime/webcore/Request.rs (construct_into), keeps the non-compiled .zig siblings in sync as porting references, and adds ~50 lines of tests to test/js/web/fetch/fetch.test.ts.

Since my last review, commit db398775 restored the }); that closed the preceding it() before the new describe block — fetch.test.ts now parses cleanly (verified with esbuild). The bug-hunting pass on the current head found nothing.

Security risks

None identified. The change tightens input validation on a public API; it does not touch auth, crypto, networking transport, or filesystem access. The only resource concern was the url_str_optional WTFStringImpl ref, and the explicit .deref() before the early return in fetch.rs handles it.

Level of scrutiny

Moderate-to-high. fetch() and the Request constructor are among the most heavily exercised code paths in Bun, and this is a user-visible behavioral change — code that previously passed e.g. fetch(url, 0) and silently succeeded will now reject. The Rust change also threads through manual ref-counting (bun_core::String is Copy with no Drop) and the Promise-vs-sync-throw distinction, both of which were gotten wrong earlier in this PR's history and fixed via review.

Other factors

The PR has been through roughly a dozen review iterations (error-message wording, sibling Request path, argument-ordering, test placement, the .zig.rs port, a string leak, sync-throw vs. reject, and the test-file parse error), each of which was addressed. Test coverage for the new behavior is solid (8 bad-init × 2 APIs + 3 good-init × 2 APIs + an ordering test). The robobun CI status still shows unrelated -no-pie linker warnings on android/freebsd build-rust lanes; the test lanes for db398775 aren't yet reflected in the status comment. Given the behavioral surface area and the history, a maintainer sign-off is the right call rather than a bot approval.

@robobun

robobun commented May 29, 2026

Copy link
Copy Markdown
Collaborator Author

CI on db39877: 71 lanes pass, cargo clippy passes, and every test lane that got a runner is green. The 2 red lanes are darwin-14-aarch64 and darwin-26-aarch64 test-bun, both in Expired state (buildkite reports jobs_failed: 0 — no test ran and failed; the macOS-ARM runner pool was exhausted so the jobs never got an agent). This is infrastructure, not a code failure, and nothing in this diff can affect it. The fetch/Request init-validation fix is complete and all 17 review threads are resolved. Needs a maintainer to re-run the expired darwin shards / merge.

@robobun
robobun force-pushed the farm/814397a5/fetch-init-typeerror branch from db39877 to d768e8f Compare June 29, 2026 16:31
Comment thread src/runtime/webcore/fetch.rs Outdated
@mintlify

mintlify Bot commented Jun 29, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
bun 🟢 Ready View Preview Jun 29, 2026, 5:06 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

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

All prior feedback is addressed and I found nothing new this round; the change looks correct and well-tested, but since it adds an early-return with manual BunString deref inside fetch_impl (and a guard in Request::construct_into), a maintainer should give it a final look before merge.

Extended reasoning...

Overview

This PR adds WebIDL-compliant init argument validation to fetch() and new Request(): non-nullish primitives now reject/throw TypeError instead of being silently ignored. The final diff touches src/runtime/webcore/fetch.rs (~35 lines: defer init_arg capture, then a new validation block after StringOrURL::from_js that derefs url_str_optional and returns a rejected promise), src/runtime/webcore/Request.rs (~17 lines: a bail! guard after the url-string branch), and test/js/web/fetch/fetch.test.ts (a new describe block with 8 bad-init + 3 good-init cases for both fetch and Request, plus an argument-ordering test). The two docs/*.mdx changes are pure autofix.ci whitespace/table-padding reflow with no content change.

Security risks

None. The change strictly adds an input-validation error path; the happy path (object/undefined/null init) is byte-for-byte unchanged. No auth, crypto, or permission surfaces are involved.

Level of scrutiny

Moderate-to-high. fetch_impl is one of Bun's hottest production code paths and uses manual bun_core::String refcounting (Copy + no Drop). The new early return correctly calls s.deref() on url_str_optional before bailing and returns a rejected promise (not a sync throw) — both of which were earlier review findings that have been fixed and verified — but this is exactly the kind of lifetime-sensitive edit a Bun maintainer should sign off on rather than a bot.

Other factors

This PR has been through ~15 review iterations; every prior inline finding (error-message wording, sibling Request path, WebIDL ordering, url_str leak, sync-throw vs reject, test placement, parse error from rebase, stale .zig comment) is resolved in the current diff. The bug-hunting system found nothing new this run. The one CI failure (test-net-connect-memleak.js on two Linux x64 lanes) is unrelated to fetch/Request. No CODEOWNERS entry covers src/runtime/webcore/. Given the critical path + manual refcount handling, I'm deferring rather than approving.

@robobun

robobun commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

The fix is complete and verified, and all review threads are resolved. The diff is green on the lanes that exercise this change: on build 66849 every Linux test lane passed, including debian-13-x64-asan-test-bun, where the issue #29195 tests run.

The remaining red lanes are unrelated to this fetch/Request change:

  • darwin-26-aarch64 test-bun: fails before running any test on a Buildkite artifact-download timeout (buildkite-agent artifact download timed out after 120s for step 'darwin-aarch64-build-bun'). This has recurred on three consecutive builds (66837, 66849, 66877), so it is persistent infra rather than something this diff or a re-run can fix.
  • alpine-3.23 x64 / x64-baseline: test/js/node/test/parallel/test-net-connect-memleak.js asserts a node:net socket is GC-collected after globalThis.gc(). It passes on main and is unrelated to init validation.
  • Windows update_interactive_install and dev-and-prod: CI flagged both as flaky (auto-retried).

Since the only blocker on the relevant platforms is a recurring infra timeout a re-run will not reliably clear, this needs a maintainer to merge. Happy to rebase if it goes stale.

@robobun
robobun force-pushed the farm/814397a5/fetch-init-typeerror branch 2 times, most recently from 5558731 to 2767d18 Compare July 8, 2026 08:43
@robobun

robobun commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto latest main and force-pushed. Squashed the iteration history into a single commit and dropped two unrelated autofix.ci docs reflow files (docs/guides/util/base64.mdx, docs/runtime/web-apis.mdx) so the diff is scoped to the fetch/Request change and its tests. The only conflict was in test/js/web/fetch/fetch.test.ts, where main's new #16682 idle-timeout test landed at the same spot as the #29195 block; both are kept. All 23 #29195 tests pass locally.

CI on the rebased commit (build 70438): green on all Linux lanes including debian-13-x64-asan-test-bun, where the #29195 tests run. The one red lane is darwin-14-x64 test-bun, which timed out on unrelated S3 large-file upload tests (should be able to upload large files ...) plus a BuildKite internal error; neither touches this fetch/Request change. The diff is green where it matters and ready for a maintainer to merge.

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

LGTM — the last nit (stale .zig reference in the comment) was addressed in the rebase, and the squashed diff is small, focused, and well-tested.

Extended reasoning...

Overview

This PR makes fetch(url, init) and new Request(url, init) reject/throw a TypeError when init is a non-nullish primitive, per the Web IDL dictionary conversion rules. It touches three files: src/runtime/webcore/fetch.rs (~40 net lines in fetch_impl — defer reading init, validate after first-arg conversion, reject with a promise, explicitly deref() the borrowed URL string on the early return), src/runtime/webcore/Request.rs (~17 lines in construct_into — same three-way check after the url-string branch, routed through the existing bail! cleanup), and test/js/web/fetch/fetch.test.ts (~50 lines — a hermetic describe block with 8 bad-init and 3 good-init cases for each of fetch() and new Request(), plus a left-to-right ordering assertion).

Security risks

None. This tightens input validation on a public API by rejecting inputs that were previously silently ignored. No new parsing, no auth/crypto/permissions surface, no untrusted data flowing into new sinks.

Level of scrutiny

Moderate — fetch_impl and Request::construct_into are core runtime paths with manual refcount management, so the leak/ordering concerns raised earlier were real. But the final diff is small and follows the exact patterns already used by adjacent validation errors in the same function (ctx.to_type_error + dangerously_create_rejected_promise_value_without_notifying_vm, and bail!(Err(global_this.err(...).throw())) respectively). The explicit url_str_optional.deref() before the early return mirrors the OwnedString wrap comment immediately below it.

Other factors

This PR has been through ~15 review rounds; every prior finding (missing null in the message, sibling Request path, url_str leak, sync-throw vs. rejected-promise, test placement, brace mismatch, stale .zig comment reference) is resolved in the current squashed diff. The bug-hunting pass on the current HEAD found nothing. Tests are hermetic (data: URL, no network), use it.each over the primitive matrix, and live in the correct module test file. Prior CI runs on the same code (pre-squash) were green on all Linux lanes including ASAN; the only red was unrelated Windows/macOS infra.

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

LGTM — small spec-compliance fix, all prior review feedback addressed and no issues found in the current revision.

Extended reasoning...

Overview

This PR adds WebIDL-compliant validation for the init argument to fetch() and new Request(): when init is a non-nullish primitive (number, string, boolean, bigint, symbol), Bun now rejects/throws TypeError instead of silently ignoring it, matching the WHATWG Fetch spec, browsers, and Node.js. The change touches three files: src/runtime/webcore/fetch.rs (~40 lines net: defers init capture until after first-arg conversion, adds a three-way type check that returns a rejected promise with explicit url_str_optional.deref() on the error path), src/runtime/webcore/Request.rs (~17 lines: adds the same check after first-arg ToString via the existing bail! cleanup macro), and test/js/web/fetch/fetch.test.ts (~50 lines: 23 hermetic test cases using a data: URL).

Security risks

None. This tightens input validation on a user-facing API — previously-accepted garbage input now errors early. No new parsing, no untrusted-data handling, no auth/crypto/permission surface. The only resource-management concern (the +1 WTFStringImpl ref from StringOrURL::from_js) is explicitly released on the new early-return path, and the Request.rs path routes through the existing bail!finalize_without_deinit cleanup which I verified derefs req.url via OwnedStringCell::set.

Level of scrutiny

Medium. fetch() and Request() are critical user-facing hot paths, but the actual change is a small, mechanical three-way type check that follows the exact patterns already used throughout both functions (dangerously_create_rejected_promise_value_without_notifying_vm for fetch validation errors; bail! for Request constructor errors). The behavioral change — rejecting on primitive init — is spec-mandated and matches every other runtime, so breakage risk for real code is minimal.

Other factors

This PR has been through an unusually thorough review cycle: 17 inline review threads over ~3 months, all resolved, covering error-message wording, sibling-path coverage (Request constructor), WebIDL argument-conversion ordering, a memory leak on the early-return path, sync-throw vs. rejected-promise semantics, test-file placement, a syntax error from a bad splice, and stale comment references — every one of which is fixed in the current squashed diff. I verified the final state against each: the .zig reference is gone, the comment in Request.rs carries the corrected wording, test/regression/issue/29195.test.ts is deleted, tests live in fetch.test.ts and use a hermetic data: URL, and the ordering test is delivery-mode agnostic. The bug-hunting system found nothing in the current revision. Test coverage is comprehensive (8 bad-init × 2 APIs, 3 good-init × 2 APIs, plus an ordering test). The author reports all 23 tests pass locally after the rebase; CI on prior revisions was green on all lanes that exercise this code (including ASAN).

@robobun
robobun force-pushed the farm/814397a5/fetch-init-typeerror branch from 2767d18 to 849cc76 Compare August 14, 2026 08:04
Comment thread src/runtime/webcore/Request.rs Outdated
Comment thread src/runtime/webcore/fetch.rs Outdated
Comment thread src/runtime/webcore/fetch.rs Outdated
Comment thread src/runtime/webcore/fetch.rs Outdated
Comment thread src/runtime/webcore/Request.rs
Comment thread src/runtime/webcore/fetch.rs
Comment thread src/runtime/webcore/fetch.rs
Comment thread src/runtime/webcore/fetch.rs Outdated
Comment thread src/runtime/webcore/fetch.rs Outdated
Comment thread test/js/web/fetch/fetch.test.ts Outdated
fetch() and new Request() silently ignored a non-nullish primitive init
argument (number, bigint, string, boolean, symbol) instead of failing.
Per the WHATWG Fetch spec (Web IDL dictionary conversion), a non-object
init must produce a TypeError: fetch() rejects and new Request() throws,
matching Node. undefined, null, and objects are unchanged.

Closes #29195

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

LGTM — both nits from the last round are addressed (RAII OwnedString wrap at acquisition + .into_inner() at consumption in fetch.rs, and the stale test-comment parenthetical dropped).

What was reviewed

  • fetch_impl: init validated after first-arg StringOrURL::from_js (WebIDL left-to-right); url_str_optional now Option<OwnedString> so the +1 releases via Drop on the new early return — no manual deref(), no leak.
  • Request::construct_into: same dictionary check via the existing bail! cleanup path; is_object() covers DOMWrapper so no divergence from the fetch.rs guard.
  • Tests: hermetic data: URL, full primitive matrix × both entry points, plus the toString-ordering case; fetch.test.ts parses at HEAD.
Extended reasoning...

Overview

Tightens WebIDL dictionary conversion for the init argument on both fetch() and new Request(): a non-nullish primitive now rejects/throws TypeError instead of being silently ignored. Three files: src/runtime/webcore/fetch.rs (~35 net lines — capture init_arg, validate after first-arg conversion, reject via the same rejected-promise shape every other validation error in fetch_impl uses; plus the OwnedString RAII wrap of url_str_optional), src/runtime/webcore/Request.rs (14 lines — same check via the established bail! cleanup macro), and test/js/web/fetch/fetch.test.ts (47 lines — it.each matrix over 8 primitives × {fetch, Request} + 3 accepted values + a WebIDL argument-ordering test).

Security risks

None. This adds an argument-type guard that rejects more inputs; no new parsing, no new I/O, no auth/crypto/permissions. The good-path tests use a data: URL so no network is touched.

Level of scrutiny

Medium — fetch_impl is a hot, ownership-sensitive path — but the change is narrowly a validation guard plus a small RAII refactor that this review specifically requested. The one memory-safety-relevant edit (wrapping url_str_optional in Option<bun_core::OwnedString> at acquisition and disarming with .into_inner() at consumption) matches the idiom already used in bun_fetch_preconnect and the proxy-href sites in the same file, and OwnedString::into_inner (src/bun_core/string/mod.rs:1182) hands the +1 back without deref'ing, so the outer OwnedString::new(...) wrap doesn't double-count. The rejected-promise construction on the new early return matches every sibling validation error in the function.

Other factors

This PR has been through ~17 review threads over four months, all resolved. My two remaining nits from the 08:24 round are now addressed exactly as suggested; the comment-cop flags on the long rationale comments are cleared (comments trimmed to 1–2 lines each). The bug-hunting system found nothing this run. I re-verified fetch.test.ts parses at HEAD (a prior iteration had a brace-splice regression). The Request.rs guard uses !is_object(), which covers DOMWrapper (JSType ≥ ObjectType), so it accepts the same set as the fetch.rs is_object() || js_type() == DOMWrapper guard.

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.

fetch() does not throw when invalid values are passed

1 participant