Skip to content

node:http2: populate internal/http2/util for --expose-internals tests [1tpjlb] - #29825

Closed
robobun wants to merge 1 commit into
mainfrom
farm/4b07618d/internal-http2-util
Closed

node:http2: populate internal/http2/util for --expose-internals tests [1tpjlb]#29825
robobun wants to merge 1 commit into
mainfrom
farm/4b07618d/internal-http2-util

Conversation

@robobun

@robobun robobun commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes 10 of the 14 tests in node-compat group 1tpjlb: the Node --expose-internals tests that require('internal/http2/util').

Problem

These tests import Node's private internal/http2/util module for its helpers (buildNgHeaderString, NghttpError, updateOptionsBuffer, the pseudo-header validators, ...). #31584 added a Symbol.for("::bunhttp2internals::") slot on the node:http2 default export and a test-common Bun.plugin shim that serves internal/http2/util from it, but landed the util sub-object empty, so every test fails on the import.

Fix

  • src/js/node/http2.ts: add the internal/http2/util helpers, ported from Node's lib/internal/http2/util.js, and populate the Symbol.for("::bunhttp2internals::")].util / .binding slots from them. The error table in nghttp2ErrorString is transcribed directly from nghttp2_strerror() (nghttp2_helper.c) keyed by the codes in nghttp2.h.
  • src/js/internal/test/binding.ts: add a case "http2" so internalBinding('http2') returns the .binding slot (constants, nghttp2ErrorString, optionsBuffer). This is the real built-in trace_events: implement Node's trace-events subsystem and port the v26.3.0 trace tests — JSON trace writer, --trace-event-* / --trace-env / --trace-exit flags, per-subsystem events, worker tids, NodeTracing inspector domain (+34 tests, 100% passing) #31824 added; it wins over a Bun.plugin virtual module, so the shim version this PR originally carried was dead and is deleted from the test-common plugin.
  • assertIsObject / assertIsArray now pass their expected type as a one-element list. Node's ERR_INVALID_ARG_TYPE normalizes a string expected to a list internally and classifies each element (kTypes lowercase, classRegExp to "an instance of X"). Bun exposes that logic as the ArgList overload of $ERR_INVALID_ARG_TYPE, while the single-string overload is a phrase-passthrough for pre-formatted messages. One call site already passed an array; the list form is simply how a caller selects Node's classification. No C++ changes.
  • ERR_HTTP2_INVALID_CONNECTION_HEADERS is added to ErrorCode.ts (appended, so the discriminants in the checked-in ErrorCode.rs mirror stay index-aligned: 331, COUNT 332). buildNgHeaderString throws it and test-http2-util-headers-list asserts it.
  • NGHTTP2_NV_FLAG_NONE / NGHTTP2_NV_FLAG_NO_INDEX added to http2.constants (asserted by test-http2-util-headers-list and the existing node-http2.test.js constants test).

Ten tests move off test/expectations.txt:

test-http2-util-assert-valid-pseudoheader   test-http2-misc-util
test-http2-util-asserts                     test-http2-server-socket-destroy
test-http2-util-headers-list                test-http2-client-socket-destroy
test-http2-util-nghttp2error                test-http2-create-client-secure-session
test-http2-util-update-options-buffer       test-http2-server-sessionerror

The remaining 4 (test-http2-client-destroy, test-http2-client-http1-server, test-http2-misbehaving-multiplex, test-http2-server-http1-client) resolve internal/http2/util correctly now but still fail on runtime behaviour (abort-signal listener count, HPE_PAUSED_H2_UPGRADE parser code, reused-stream-id error class) and stay on the expectations list.

Not in this PR

No changes to src/jsc/bindings/ErrorCode.cpp and nothing under test/js/node/test/parallel/ beyond test/expectations.txt. An earlier revision taught the single-string ERR_INVALID_ARG_TYPE overload Node's type classification, which reworded ~35 production call sites and required updating two test expectations. The review thread (correctly) asked for a human to sign off on that cross-cutting change; it turned out to be unnecessary once the http2 helpers use the list form, so it was dropped. Teaching the single-string callers Node's wording is a separate compat cleanup.

Verification

All 10 newly-enabled tests pass, the full node-http2.test.js runtime suite passes (287/287, 0 fail), test-http2-createserver-options / test-http2-createsecureserver-options / test-http2-update-settings (which assert on the production assertIsObject message) pass unchanged, test-assert.js and message-event.test.ts pass unmodified, internalBinding('trace_events') / ('constants') consumers (#31824's own tests) are unaffected, and Buffer.alloc(3).swap32() / buildNgHeaderString report their own error codes, proving the ErrorCode.rs/ErrorCode.ts discriminants stay aligned across the append.

@coderabbitai

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR refactors src/js/node/http2.ts to import shared constants, symbol keys, validation helpers, and utility functions from internal/http2/util instead of defining them locally, removing ~374 lines of inline code. Related tests update error message wording and add two new nghttp2 flag constants. An x64 allowlist entry is added for a JSC LLInt false positive.

HTTP/2 Refactor and Test Alignment

Layer / File(s) Summary
HTTP/2 shared logic centralization
src/js/node/http2.ts
Replaces inline constants table, pseudo-header validation sets, NoPayloadMethods, symbol keys, and utility functions with imports from internal/http2/util. Adds a $data namespace on the default export exposing internal session/stream classes for internal/http2/core.
Test updates for new constants and error messages
test/js/node/http2/node-http2.test.js, test/js/node/test/parallel/test-assert.js, test/js/web/workers/message-event.test.ts
Adds assertions for NGHTTP2_NV_FLAG_NONE and NGHTTP2_NV_FLAG_NO_INDEX constants; updates expected error messages from "must be of type X" to "must be an instance of X" and from capitalized Object to lowercase object across HTTP/2, assert, and MessageEvent tests.

x64 Baseline Static Verification

Layer / File(s) Summary
llint_op_enter_wide32 allowlist entry
scripts/verify-baseline-static/allowlist-x64.txt
Adds an annotated entry for llint_op_enter_wide32 as a data-in-text false positive with an [AVX] feature ceiling, preventing false failures in the x64 baseline static verification script.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: populating internal HTTP/2 utilities for Node compatibility tests.
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.
Description check ✅ Passed The description is detailed and covers the change and verification, though it uses different headings than the repo template.

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

@robobun

robobun commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 5:11 AM PT - Jul 2nd, 2026

@robobun, your commit 64f1420 has 1 failures in Build #67905 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 29825

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

bun-29825 --bun

@robobun

robobun commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author

Status: closed, superseded on main

Everything this PR set out to do for node-compat group 1tpjlb has since landed or been decided upstream. Verified against a bare build of main 18059646 with none of this PR's code: 12 of the group's 14 tests pass as-is, and the other 2 (test-http2-util-headers-list, test-http2-util-update-options-buffer) were deliberately deleted by #32488 as out of scope (they test internalBinding('http2') shape). The per-file breakdown is in the closing comment.

Nothing needs to be carried forward from this branch.

Previous status (historical)

Before being superseded, this PR reached 10/14 passing for the group with a 7-file diff (http2 util/binding helpers, an internalBinding('http2') case, an appended error code, and expectations/constants updates), no C++ changes, all review threads resolved, and CI at 284/286 lanes with the remaining 2 being a broken darwin runner and a MySQL container health-check timeout. The earlier cross-cutting ErrorCode.cpp formatter change was removed in favour of having assertIsObject / assertIsArray pass a one-element list; that is also the approach that ended up on main.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. node: expose internal/* under --expose-internals, implement internal/errors helpers [1v1cy7] #29823 - Both PRs implement --expose-internals infrastructure for internal/* modules, add internal/test/binding, and modify ModuleLoader.zig to gate internal module access

🤖 Generated with Claude Code

@robobun

robobun commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author

Re duplicate detection: #29823 (also mine, group 1v1cy7) adds a generic internal/*ResolvedSource.Tag mechanism. This PR adds the http2-specific internal modules on top of the same gate. Whichever lands second gets a straightforward rebase:

The http2 util module, the ERR_INVALID_ARG_TYPE formatter fix, and the 7 node tests are independent of which mechanism lands.

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

🤖 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/bindings/ErrorCode.cpp`:
- Around line 590-600: The code does not guard for expected_types.size() == 0
which causes unsigned underflow when the later code uses length - 1; add an
early check for length == 0 after computing unsigned length and handle it by
calling ERR_INVALID_ARG_TYPE with a safe empty/placeholder expected-type (e.g.,
an empty string or "[]") so no index math or toString() is performed; modify the
branch that currently handles length == 1 / length - 1 to first return when
length == 0 using the same ERR_INVALID_ARG_TYPE path so expected_types and any
subsequent indexing are never accessed when empty.

In `@src/js/internal/http2/util.ts`:
- Around line 559-562: The function assertWithinRange allows NaN because typeof
NaN === "number" and comparisons with NaN are false; update assertWithinRange to
explicitly reject NaN by adding a check (e.g., Number.isNaN(value)) to the
existing conditional so that any NaN value triggers the throw of
$ERR_HTTP2_INVALID_SETTING_VALUE_RangeError with the same message; locate the
function assertWithinRange and augment its if condition to include the NaN test.

In `@src/js/internal/test/binding.ts`:
- Around line 17-20: internalBinding currently returns an empty object when a
binding is missing which masks real missing/native failures; change the fallback
so that when bindings[name] is undefined the function calls and returns
process.binding(name) (preserving the existing behavior of omitting argument
validation), i.e., locate the internalBinding function and replace the final
"return {}" with "return process.binding(name)" so unknown bindings fall through
to Node's native process.binding.

In `@test/js/node/child_process/child_process-node.test.js`:
- Around line 674-678: The test currently asserts the full error.message string
using arg?.toString(), but Node formats the received value with its own
determineSpecificType rendering so the exact text can differ; update the
assertion for fork(...) to instead check error.code === "ERR_INVALID_ARG_TYPE"
and error.name === "TypeError" and assert the message matches a looser pattern
(e.g. use a RegExp like /^The "args" argument must be an instance of Array\.
Received .+/) rather than comparing to arg?.toString() so the test no longer
depends on determineSpecificType formatting.

In `@test/js/node/test/parallel/test-http2-misc-util.js`:
- Around line 19-21: The test's forEach loop is calling sessionName(2) instead
of using the loop variable i, so only the value 2 is actually tested; update the
assertion to call sessionName(i) and keep the rest of the test unchanged (the
loop over [2, '', 'test', {}, [], true] should assert
strictEqual(sessionName(i), '<invalid>') to validate all invalid inputs);
reference: sessionName and the forEach callback variable i in
test-http2-misc-util.js.
🪄 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: 6ee828b5-00c6-42d7-bad8-370f930c25ff

📥 Commits

Reviewing files that changed from the base of the PR and between 4d615e8 and cd046c4.

📒 Files selected for processing (25)
  • src/bun.js/HardcodedModule.zig
  • src/bun.js/ModuleLoader.zig
  • src/bun.js/bindings/ErrorCode.cpp
  • src/bun.js/bindings/ErrorCode.ts
  • src/js/internal/http2/core.ts
  • src/js/internal/http2/util.ts
  • src/js/internal/test/binding.ts
  • src/js/node/http2.ts
  • test/js/node/child_process/child_process-node.test.js
  • test/js/node/http2/node-http2.test.js
  • test/js/node/test/parallel/test-assert.js
  • test/js/node/test/parallel/test-buffer-concat.js
  • test/js/node/test/parallel/test-child-process-constructor.js
  • test/js/node/test/parallel/test-dns-setservers-type-check.js
  • test/js/node/test/parallel/test-http2-createserver-options.js
  • test/js/node/test/parallel/test-http2-misc-util.js
  • test/js/node/test/parallel/test-http2-server-socket-destroy.js
  • test/js/node/test/parallel/test-http2-util-assert-valid-pseudoheader.js
  • test/js/node/test/parallel/test-http2-util-asserts.js
  • test/js/node/test/parallel/test-http2-util-headers-list.js
  • test/js/node/test/parallel/test-http2-util-nghttp2error.js
  • test/js/node/test/parallel/test-http2-util-update-options-buffer.js
  • test/js/node/test/parallel/test-process-hrtime.js
  • test/js/node/test/parallel/test-process-setgroups.js
  • test/js/web/workers/message-event.test.ts

Comment thread src/jsc/bindings/ErrorCode.cpp Outdated
Comment thread src/js/internal/http2/util.ts Outdated
Comment thread src/js/internal/test/binding.ts Outdated
Comment thread test/js/node/child_process/child_process-node.test.js
Comment thread test/js/node/test/parallel/test-http2-misc-util.js
Comment thread src/js/node/http2.ts Outdated
Comment thread src/js/internal/http2/util.ts Outdated
@Jarred-Sumner
Jarred-Sumner force-pushed the farm/4b07618d/internal-http2-util branch from 7e457a8 to 3c643a5 Compare May 4, 2026 10:29
@robobun
robobun force-pushed the farm/4b07618d/internal-http2-util branch from 3c643a5 to 1969eee Compare June 18, 2026 02:54
@robobun
robobun requested a review from Jarred-Sumner as a code owner June 18, 2026 02:54
@robobun robobun changed the title node:http2: expose internal/http2/util for --expose-internals tests [1tpjlb] node:http2: populate internal/http2/util for --expose-internals tests [1tpjlb] Jun 18, 2026

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

Additional findings (outside current diff — PR may have been updated during review):

  • 🔴 src/jsc/bindings/ErrorCode.ts:102 — Inserting ERR_HTTP2_INVALID_CONNECTION_HEADERS mid-array (alphabetical position) violates the trailing comment in this file — discriminants are index-aligned with the checked-in src/jsc/ErrorCode.rs, which this PR doesn't touch. Every Rust ErrorCode constant ≥86 is now off-by-one against the regenerated C++ errors[] table and ErrorCode::COUNT (324) no longer matches; this is what's failing build-rust on all 8 platforms. Either append the entry after ERR_PROXY_TUNNEL at the end of the array, or regenerate src/jsc/ErrorCode.rs in this PR.

    Extended reasoning...

    What the bug is

    ERR_HTTP2_INVALID_CONNECTION_HEADERS is inserted at line 102 of src/jsc/bindings/ErrorCode.ts, between ERR_HTTP2_INFO_STATUS_NOT_ALLOWED and ERR_HTTP2_INVALID_HEADER_VALUE (the alphabetical position). However, the trailing comment in this same file (lines 334-335) explicitly states:

    // Appended (not alphabetical): discriminants are index-aligned with the checked-in Rust mirror (src/jsc/ErrorCode.rs) — only ever append here.

    The mid-array insertion shifts the index of every subsequent entry by +1, breaking the index alignment with src/jsc/ErrorCode.rs, which is not regenerated in this PR.

    The specific code path that triggers it

    There are two consumers of this array, with different update mechanisms:

    1. C++ side (ErrorCode+List.h / the errors[] table): regenerated at build time from ErrorCode.ts by src/codegen/generate-node-errors.ts. After this PR it will have 325 entries with ERR_HTTP2_INVALID_CONNECTION_HEADERS at index 86 and emit NODE_ERROR_COUNT = 325.
    2. Rust side (src/jsc/ErrorCode.rs): checked-in, not build-generated. Its header (lines 3-8) says "Discriminants MUST stay index-aligned with the C++ errors[] table so Bun__createErrorWithCode picks the correct ctor / name / code triple. Regenerate by re-running the inline extractor against ErrorCode.ts; do not hand-edit individual entries." At HEAD it still has:
      • HTTP2_INFO_STATUS_NOT_ALLOWED = ErrorCode(85) immediately followed by HTTP2_INVALID_HEADER_VALUE = ErrorCode(86) — no entry for the new code
      • pub const COUNT: u16 = 324
      • CODE_STR: [&str; ErrorCode::COUNT as usize] going straight from "ERR_HTTP2_INFO_STATUS_NOT_ALLOWED" to "ERR_HTTP2_INVALID_HEADER_VALUE"

    The PR's changed-files list does not include src/jsc/ErrorCode.rs, and git log -- src/jsc/ErrorCode.rs shows it was last touched before this PR.

    Why existing code doesn't prevent it

    The C++ generator runs every build, so it picks up the new entry automatically. The Rust mirror does not — it must be regenerated manually via the inline extractor mentioned in its header. The only guard is the in-source comment in ErrorCode.ts telling contributors to append, which was overlooked here. The robobun CI comment shows build-rust failing on all 8 platforms (linux x64/aarch64/musl/asan/baseline/android, macOS x64/aarch64) for commit 1969eee, consistent with a count/alignment static assertion firing during the Rust build.

    Step-by-step proof

    1. Before this PR, ErrorCode.ts index 85 = ERR_HTTP2_INFO_STATUS_NOT_ALLOWED, index 86 = ERR_HTTP2_INVALID_HEADER_VALUE. ErrorCode.rs mirrors this: ErrorCode(85) / ErrorCode(86). Both sides have 324 entries.
    2. This PR inserts ["ERR_HTTP2_INVALID_CONNECTION_HEADERS", TypeError] at TS index 86. Now TS index 86 = ERR_HTTP2_INVALID_CONNECTION_HEADERS, index 87 = ERR_HTTP2_INVALID_HEADER_VALUE, …, and the array has 325 entries.
    3. generate-node-errors.ts regenerates the C++ errors[] table to match: C++ index 86 → ERR_HTTP2_INVALID_CONNECTION_HEADERS, NODE_ERROR_COUNT = 325.
    4. ErrorCode.rs is unchanged: Rust still says HTTP2_INVALID_HEADER_VALUE = ErrorCode(86) and COUNT = 324.
    5. Any Rust caller passing ErrorCode::HTTP2_INVALID_HEADER_VALUE (value 86) into Bun__createErrorWithCode would now get an ERR_HTTP2_INVALID_CONNECTION_HEADERS instance — and likewise every one of the ~238 codes from index 86 onward is off by one. The COUNT mismatch (324 vs 325) trips the build-time check, which is why build-rust is red across the board.

    Impact

    The PR cannot build on any platform (all 8 build-rust jobs fail). If the count check were somehow bypassed, every Rust-originated error from index 86 onward would surface with the wrong .code / constructor / name at runtime.

    Fix

    Either of:

    • Append instead of insert: move the new entry to the end of the array, after ["ERR_PROXY_TUNNEL", Error] (in the section already marked "Appended (not alphabetical)"). This requires no Rust change.
    • Regenerate the Rust mirror: re-run the inline extractor against ErrorCode.ts and include the updated src/jsc/ErrorCode.rs (with the new entry at 86, all subsequent discriminants +1, COUNT = 325, and the new CODE_STR row) in this PR.

    The first option is the minimal change and keeps the file's stated invariant.

@robobun

robobun commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Already fixed in 5353e3e (the review landed against the older diff). ERR_HTTP2_INVALID_CONNECTION_HEADERS is now appended after ERR_PROXY_TUNNEL in ErrorCode.ts, and src/jsc/ErrorCode.rs is updated with the new entry at index 324 and COUNT = 325. Verified locally that the 29 regressed tests (ERR_INVALID_ARG_TYPEERR_INVALID_ADDRESS_FAMILY etc.) pass again.

Comment thread src/js/node/http2.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.

No issues found and CI is green; deferring to a human since the ERR_INVALID_ARG_TYPE wording change in ErrorCode.cpp is a cross-cutting behaviour change worth a second pair of eyes.

Extended reasoning...

Overview

This PR populates the internal/http2/util and internal/test/binding shims that Node's --expose-internals tests need, moving 10 http2 tests off the expectations FAIL list. It touches: src/js/node/http2.ts (~290 added lines of test-only helpers — buildNgHeaderString, NghttpError, nghttp2ErrorString, updateOptionsBuffer/optionsBuffer, etc., exposed via the Symbol.for('::bunhttp2internals::') slot); src/jsc/bindings/ErrorCode.cpp (reworks single-type ERR_INVALID_ARG_TYPE formatting to emit "an instance of X" for CamelCase class names and lowercased "of type object/function" for Object/Function, plus a length==0 underflow guard); ErrorCode.ts/ErrorCode.rs (append ERR_HTTP2_INVALID_CONNECTION_HEADERS and bump COUNT); the test-common Bun.plugin shim; and ~8 test files updating expected error wording.

Security risks

None identified. The new helpers are only reachable via the --expose-internals test shim (a Bun.plugin in test/js/node/test/common/index.js), not via the public node:http2 surface. The C++ change is purely error-message formatting; the new length==0 guard removes a latent unsigned-underflow OOB read in the ArgList overload.

Level of scrutiny

Moderate. The http2 additions are low-risk test infrastructure, but the ErrorCode.cpp change is a cross-cutting behaviour change to a globally-used error formatter — every caller that passes a single CamelCase identifier ("Array", "RegExp", "MessagePort", "Date", …) now produces different message text. The isSingleClassName heuristic looks correct and matches Node's /^(?:[A-Z][a-z0-9]*)+$/ classifier, and CI (build 63274) is fully green with the updated test expectations, but the blast radius is wide enough that a maintainer should sign off on the wording policy. The index-aligned ErrorCode.ts/ErrorCode.rs append also deserves a quick eyeball since misalignment there silently shifts every subsequent error code.

Other factors

All prior review threads (CodeRabbit's underflow guard, process.binding fallthrough, my dead-binding nits, and the nghttp2_strerror table mismatch) are resolved. The most recent commit (2161ddc) re-transcribed the nghttp2 error table directly from nghttp2_helper.c per my last comment. The bug-hunting system found nothing on this revision. The PR description is thorough and the remaining 4 still-failing tests are clearly scoped out as runtime-behaviour follow-ups.

@robobun
robobun force-pushed the farm/4b07618d/internal-http2-util branch 3 times, most recently from de86af6 to 7462c34 Compare June 27, 2026 01:08

@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 issues found in this pass; deferring because the ERR_INVALID_ARG_TYPE single-type formatter rewrite in ErrorCode.cpp is a production-path change (affects error wording for every caller passing a bare class name), so it's worth a human sanity-check alongside the http2 internals additions.

Extended reasoning...

Overview

This PR populates the Symbol.for("::bunhttp2internals::").util / .binding slots in src/js/node/http2.ts (~300 new lines of helpers ported from Node's internal/http2/util: buildNgHeaderString, NghttpError, nghttp2ErrorString, updateOptionsBuffer, pseudo-header validators, etc.), adds the ERR_HTTP2_INVALID_CONNECTION_HEADERS error code (ErrorCode.ts/.rs), rewrites the single-type branch of ERR_INVALID_ARG_TYPE in ErrorCode.cpp to match Node's wording ("an instance of X" for CamelCase identifiers, "of type object/function" for Object/Function), wires an internal/test/binding shim into the test-common Bun.plugin, and removes 10 http2 tests from test/expectations.txt. Test expectation strings in test-assert.js and message-event.test.ts are updated for the new wording.

Security risks

None identified. The new http2 helpers are reachable only via the --expose-internals symbol slot and the test-harness plugin shim; they don't alter request handling, auth, or socket paths. The ErrorCode.cpp change is pure message formatting. The internalBinding shim falls through to process.binding(name), which is already a privileged surface gated the same way.

Level of scrutiny

Medium. Most of the diff is test-only infrastructure, but two pieces run in production: (1) the ERR_INVALID_ARG_TYPE formatter in ErrorCode.cpp — every caller that passes a single CamelCase type name (e.g. "Array", "RegExp", "MessagePort", "Date") now gets "an instance of X" instead of "of type X", which is a user-visible wording change across the runtime; and (2) the two new http2.constants entries. The C++ change is small and well-scoped (the isSingleClassName predicate plus a 4-way branch), but it's the kind of cross-cutting wording change a maintainer should sign off on rather than a bot.

Other factors

All prior review threads (CodeRabbit and my three earlier nits — unused destructures, dead kIsNodeError getter, mis-keyed kNghttp2ErrorMessages table) are resolved. The author reports 10/14 target tests passing with the rest blocked on unrelated runtime gaps, and CI is green modulo known flakes. The bug-hunting pass on the current revision found nothing. I'm deferring rather than approving solely because of the production-path ErrorCode.cpp change and overall diff size, not because of any open concern.

@robobun

robobun commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

Adding scope detail on the ERR_INVALID_ARG_TYPE single-type formatter change, since that is the one production-path piece here and the thing a reviewer would want to bound.

Why it is needed. Two of the ten target tests assert on it directly:

  • test-http2-util-asserts.js: assertIsObject(1, 'foo', 'Object') must produce must be of type object. (lowercase object). Node lowercases Object/Function via its kTypes list in lib/internal/errors.js.
  • test-http2-misc-util.js: assertIsArray(x, 'test') must produce must be an instance of Array., and assertIsObject(x, 'test', 'Date') must produce must be an instance of Date.. Node routes any bare /^[A-Z][a-zA-Z0-9]*$/ identifier through classRegExp to an instance of X.

Before this PR, Bun's single-StringView overload only had the legacy phrase heuristic, so it produced of type Object, of type Array, and of type Date for those. The array-of-types overload already had the correct Node categorization on main (isPrimitiveTypeName + the class regexp); this change applies the same two rules to the single-type path, reusing the same predicates rather than duplicating them.

Blast radius. Call sites in src/ that pass a bare class name as the single expected type, and therefore change wording from of type X to an instance of X (or to of type object for the one Object case):

  • JS builtins: ReadableStream (9), Array (5), AbortSignal (5), WritableStream (2), Writable (2), Readable (2), EventEmitter (2), Writer, RegExp, Object, Iterable, Error, Duplex, AsyncIterable (1 each)
  • C++: KeyObject (2), Buffer (2), AbortSignal (2), TypedArray, Histogram, Array (1 each)

Every one of these is a case where Node already says an instance of X (or of type object), so the wording moves toward Node, not away from it. Callers passing a pre-formatted phrase ("Array of unique strings"), a flattened list ("string, Buffer, or Uint8Array"), or a lowercase primitive are untouched; they hit the unchanged else branch.

In Bun's own test suite, exactly two files asserted the old wording and both are updated in this PR: test-assert.js (RegExp, two occurrences) and message-event.test.ts (MessagePort, two occurrences). A grep for must be of type [A-Z] across test/ (excluding node_modules) now comes back empty, so nothing else in the suite depended on the old rendering.

@robobun
robobun force-pushed the farm/4b07618d/internal-http2-util branch from 7462c34 to 17d5b44 Compare June 27, 2026 04:28

@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 issues found in the current revision. Deferring to a human reviewer because the ERR_INVALID_ARG_TYPE single-type formatter change in ErrorCode.cpp is a cross-cutting production-path change (the scope analysis is thorough and it moves toward Node, but error-message wording shifts across ~40 call sites are worth a human sign-off), and the ~290 new lines in http2.ts add non-trivial header/options logic alongside the test-only exports.

Extended reasoning...

Overview

This PR populates the previously-empty Symbol.for("::bunhttp2internals::")].util and adds a .binding slot in src/js/node/http2.ts so that ten Node --expose-internals http2 tests can resolve internal/http2/util and internalBinding('http2'). It adds ~290 lines of helpers to http2.ts (buildNgHeaderString, updateOptionsBuffer/optionsBuffer, NghttpError, the kNghttp2ErrorMessages table, sessionName, assertWithinRange, pseudo-header/connection-header validators, isPayloadMeaningless), wires internal/test/binding into the test-common Bun.plugin shim, adds two NGHTTP2_NV_FLAG_* constants, registers ERR_HTTP2_INVALID_CONNECTION_HEADERS in ErrorCode.{ts,rs}, and removes ten tests from test/expectations.txt.

The one production-path change is to the single-StringView overload of ERR_INVALID_ARG_TYPE in src/jsc/bindings/ErrorCode.cpp: "Object"/"Function" now lowercase to of type object/function, and a bare CamelCase identifier renders as an instance of X. Two test files (test-assert.js, message-event.test.ts) are updated for the new wording.

Security risks

None identified. The new http2 helpers are exposed only via the Symbol.for("::bunhttp2internals::") slot and the test-only Bun.plugin shim gated behind --expose-internals; none of them are wired into the runtime request path. buildNgHeaderString performs header validation but is not called by the http2 engine itself in this PR. The ErrorCode.cpp change is pure string formatting with no parsing of untrusted input.

Level of scrutiny

Medium-high. Most of the diff is test-infrastructure (the .util/.binding exports, the Bun.plugin shim entry, expectations removals), which is low-risk and well-covered by the ten newly-passing Node tests. However, the ERR_INVALID_ARG_TYPE formatter rewording is a user-observable behaviour change that fans out to ~40 call sites across JS builtins and C++ bindings (ReadableStream, AbortSignal, Array, KeyObject, etc.). The author's blast-radius comment is thorough — every affected site moves toward Node's wording, and a grep confirms no remaining test depends on the old rendering — but a wording shift this broad on a hot error path deserves a human sign-off rather than a bot approval.

Other factors

The PR has been through several review rounds with all threads resolved: an unsigned-underflow guard added to the ArgList overload, internalBinding falling through to process.binding, unused destructure cleanup, and a full re-transcription of the kNghttp2ErrorMessages table after I flagged off-by-one and mis-keyed entries. The bug-hunting system found nothing in the current revision. CI on the latest commit (17d5b44) is building; the previous red run was attributed to an unrelated main-branch Windows sourcemap issue since fixed by #32797. Given the breadth of new logic in http2.ts plus the cross-cutting error-formatter change, I'm deferring rather than approving.

@robobun

robobun commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator Author

Two points to close out the remaining review surface, both new relative to the earlier scope comment.

The test-assert.js edit restores upstream Node's message text; it does not introduce a Bun-specific one. test/js/node/test/parallel/ is the vendored Node suite, so a diff there deserves suspicion. Here is why this one is the exception: Bun's assert.match throws $ERR_INVALID_ARG_TYPE("regexp", "RegExp", regexp) (assert.ts:953), the same call shape as Node's lib/assert.js. Node renders a bare class name expected-type as an instance of RegExp, so the of type RegExp string that was in Bun's copy of test-assert.js was local drift away from upstream (a previous edit made to match Bun's then-wrong formatter), and this PR reverts that drift. message-event.test.ts is a Bun-authored test in test/js/web/, not a vendored one.

To make that concrete rather than inferred: Bun's vendored test-assert.js was last synced from Node v26.3.0 (#32626), and upstream's copy at that tag already has the new wording. nodejs/node@v26.3.0 test/parallel/test-assert.js:1499:

message: 'The "regexp" argument must be an instance of RegExp. ' +
        "Received type string ('string')"

so this edit makes Bun's copy match upstream byte for byte on that line (1549 is identical).

None of the ~290 new lines in http2.ts are dead. Checked every new top-level const/function/class (44 of them) for references beyond their own definition; none came back unused. The internal ones (kNghttp2ErrorMessages, kNeverIndexFlag, kNoHeaderFlags, emptyArray, the IDX_OPTIONS_* constants) are all consumed by buildNgHeaderString / updateOptionsBuffer / nghttp2ErrorString, and the rest are the util / binding exports the ten tests import. The Symbol.for("::bunhttp2internals::") slot and the test-common Bun.plugin shim that consumes it are both pre-existing from #31584; that PR landed util as an empty object, and this one fills it. Whether a future PR wires buildNgHeaderString into Bun's own request path is a separate decision; here it is intentionally test-surface only.

@robobun
robobun force-pushed the farm/4b07618d/internal-http2-util branch 2 times, most recently from 55d2a00 to b9bee93 Compare July 2, 2026 00:52
@robobun

robobun commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

For anyone orienting from the CodeRabbit walkthrough: its summary describes a revision that is several force-pushes stale (its incremental-review footer reported the range 7e457a88..0f50f655, and it has re-posted the same content since). Four concrete mismatches against the diff at the current head, 1788fb94:

  1. scripts/verify-baseline-static/allowlist-x64.txt is not in this PR. That fix landed on main independently and dropped out of this branch on an earlier rebase.
  2. src/js/node/http2.ts does not "import shared constants and helpers from internal/http2/util" or remove ~374 lines. It does the opposite: it adds ~330 lines of inline helpers and exposes them so the Node tests that require('internal/http2/util') can reach them.
  3. There is no $data namespace. The export slot is Symbol.for("::bunhttp2internals::"), added by node:http2: rewritten inbound engine, batched write path, server push, +290 node v26.3.0 tests (79% passing) #31584.
  4. test/js/node/test/parallel/test-assert.js and test/js/web/workers/message-event.test.ts are no longer touched. They were in an earlier revision alongside an ErrorCode.cpp formatter change that has since been removed entirely.

The actual file list at 1788fb94 is: src/js/internal/test/binding.ts, src/js/node/http2.ts, src/jsc/ErrorCode.rs, src/jsc/bindings/ErrorCode.ts, test/expectations.txt, test/js/node/http2/node-http2.test.js, test/js/node/test/common/index.js. No C++ files and nothing under test/js/node/test/parallel/. The PR description and the status comment are accurate for the current head.

@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 issues found. The bulk of this is test-only --expose-internals infrastructure filling the ::bunhttp2internals:: slots, but the ERR_INVALID_ARG_TYPE single-type formatter change in ErrorCode.cpp is a production-path C++ change that rewords ~40 call sites — the blast-radius analysis in the thread looks solid, but that's the piece worth a human sanity check.

Extended reasoning...

Overview

This PR populates the previously-empty Symbol.for("::bunhttp2internals::")].util and adds a .binding slot in src/js/node/http2.ts (~290 new lines: buildNgHeaderString, updateOptionsBuffer/optionsBuffer, NghttpError, nghttp2ErrorString, sessionName, pseudo-header validators, etc.), wires internalBinding('http2') in src/js/internal/test/binding.ts, appends ERR_HTTP2_INVALID_CONNECTION_HEADERS to ErrorCode.ts/ErrorCode.rs, and — the one production-path piece — refactors the single-StringView overload of ERR_INVALID_ARG_TYPE in src/jsc/bindings/ErrorCode.cpp so bare class names render as "an instance of X" and Object/Function lowercase to "of type object/function" (matching Node and the existing ArgList overload). Ten node-parallel http2 tests move off test/expectations.txt; two Bun-authored tests and one vendored-Node test are updated to the corrected wording.

Security risks

None identified. The new http2 helpers are only reachable via the ::bunhttp2internals:: symbol under --expose-internals (test-only). buildNgHeaderString validates header tokens and rejects connection-specific headers but is not wired into any request path yet. The ErrorCode.cpp change is pure string-formatting of an already-thrown error message; no new user-input parsing, no allocation-size arithmetic, no auth/crypto surface.

Level of scrutiny

Medium. Most of the diff is test-only infrastructure ported from Node's lib/internal/http2/util.js and is exercised by the 10 newly-enabled node-parallel tests. The part warranting human attention is the ERR_INVALID_ARG_TYPE formatter: it's shared C++ that changes user-visible error text at ~40 call sites (ReadableStream, Array, AbortSignal, KeyObject, etc.). The author's blast-radius comment enumerates every affected caller and confirms all move toward Node's wording, and a grep of test/ for the old rendering came back empty after the two in-PR test updates — but a cross-cutting error-message change in native code is exactly the sort of thing a human should skim rather than auto-approve.

Other factors

All prior review threads (CodeRabbit's empty-array underflow guard, process.binding fallthrough, my nits on unused destructures / kIsNodeError / the mis-keyed kNghttp2ErrorMessages table) are resolved with commits. CI is reported green (283/283 test lanes; remaining failures were a broken darwin runner). The ErrorCode.rs/ErrorCode.ts append is index-aligned at 331 with COUNT=332. The test-assert.js edit was verified against upstream Node v26.3.0 as a byte-for-byte revert to upstream wording. This is a solid, well-documented Node-compat PR — just larger and more cross-cutting than the auto-approve bar.

@robobun
robobun force-pushed the farm/4b07618d/internal-http2-util branch from b9bee93 to 1788fb9 Compare July 2, 2026 01:17
@robobun

robobun commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Closing out the one thing claude[bot] deferred on across its last three passes: the ERR_INVALID_ARG_TYPE single-type formatter change in ErrorCode.cpp is removed as of 1788fb94. The deferral was right; re-examining it turned up a cleaner design that makes the formatter change unnecessary.

Node's ERR_INVALID_ARG_TYPE normalizes a string expected to a one-element list internally and then classifies each element (kTypes lowercase, classRegExp to "an instance of X"). Bun already exposes exactly that logic as the ArgList overload of $ERR_INVALID_ARG_TYPE; the single-string overload is a phrase-passthrough for pre-formatted messages, and one call site in http2.ts was already passing an array. So the fix was never in the formatter. assertIsObject / assertIsArray now do the same one-line list normalization Node's error class does, which routes them to the already-correct overload. Every target test passes with no C++ changes, and the two test-expectation edits the formatter change forced (test-assert.js, message-event.test.ts) are gone with it.

The diff is now 7 files: the http2 helpers filling the ::bunhttp2internals:: slots, the internalBinding('http2') case in the real internal/test/binding built-in, the appended ERR_HTTP2_INVALID_CONNECTION_HEADERS code, and the expectations/constants updates. No native code, nothing under test/js/node/test/parallel/. Verified all 10 target tests, the full 287-test node-http2.test.js suite, the http2 option-validation tests that assert on the production assertIsObject message, the now-unmodified test-assert.js / message-event.test.ts, and the trace_events internalBinding consumers. Details in the updated description.

Teaching the ~35 existing single-string callers Node's wording is a real compat improvement but a separate cleanup, not something this PR should carry.

@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 blockers found, but this adds ~320 lines to src/js/node/http2.ts (including a production-path change to assertIsObject/assertIsArray error routing and two new public http2.constants), so it's worth a human sign-off from someone familiar with the http2 module.

Extended reasoning...

Overview

Populates the previously-empty Symbol.for("::bunhttp2internals::")].util and adds a .binding slot on node:http2 so that 10 upstream Node --expose-internals http2 tests can resolve internal/http2/util and internalBinding('http2'). Seven files touched: src/js/node/http2.ts (~320 new lines: buildNgHeaderString, updateOptionsBuffer/optionsBuffer, NghttpError, nghttp2ErrorString, sessionName, assertWithinRange, isIllegalConnectionSpecificHeader, pseudo-header validators, isPayloadMeaningless, plus the exported util/binding objects), src/js/internal/test/binding.ts (adds the http2 case), src/jsc/ErrorCode.{rs,ts} (appends ERR_HTTP2_INVALID_CONNECTION_HEADERS), test/expectations.txt (removes 10 FAIL entries), test/js/node/http2/node-http2.test.js (asserts two new constants), and test/js/node/test/common/index.js (drops the superseded NghttpError stub).

Two changes reach production paths rather than test-only surface: (1) assertIsObject/assertIsArray now wrap their types argument in a one-element array so the throw routes through the array overload of $ERR_INVALID_ARG_TYPE (which applies Node's type-vs-instance classification), altering error-message wording for existing callers; and (2) NGHTTP2_NV_FLAG_NONE/NGHTTP2_NV_FLAG_NO_INDEX are added to the public http2.constants object.

Security risks

None identified. All new code is pure JS with no I/O, crypto, auth, or filesystem access. buildNgHeaderString validates header names via the existing checkIsHttpToken and rejects connection-specific headers, but it is only reachable via the internal symbol and is not wired into Bun's own request path. optionsBuffer is a module-level Uint32Array that mirrors Node's layout for test parity only; nothing native reads it.

Level of scrutiny

Medium. The bulk of the addition is Node-compat test scaffolding gated behind a private symbol, which is low-risk. However, the file is a production node:* module, the assertIsObject/assertIsArray reroute changes user-observable error text on real API paths, and buildNgHeaderString is ~70 lines of non-trivial header-processing logic ported from Node — enough surface that a maintainer familiar with the http2 subsystem should confirm it matches upstream semantics and doesn't regress any Bun-specific behaviour.

Other factors

The bug-hunting system found nothing on the current head. All prior review threads (CodeRabbit's empty-array/underflow, NaN-in-range, fallthrough; my three earlier nits on unused destructures, the unreachable kIsNodeError getter, and the mis-keyed kNghttp2ErrorMessages table) are resolved — though several of those were against files/revisions that no longer exist in the current 7-file diff. CI on the previous head was 283/283 green with only unrelated darwin infra timeouts. I verified that every identifier referenced by the new buildNgHeaderString (ArrayIsArray, ObjectKeys, SafeSet, checkIsHttpToken, kSingleValueHeaders, sensitiveHeaders) is already defined in the file. Note the PR description and the most recent status comment still reference ErrorCode.cpp, test-assert.js, and message-event.test.ts, which are not in the current diff — the assertIsObject/assertIsArray array-wrap approach appears to have superseded the single-string formatter change, making those edits unnecessary.

@robobun

robobun commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Making the one thing this review hands to a human quick to verify. The phrase "production-path change to assertIsObject / assertIsArray error routing" sounds larger than it is: the complete production-observable delta is a single error string.

assertIsObject has four production call sites in http2.ts. Three use the default and one already passed an array. For the defaulting ones, main renders the string "object" through the single-string $ERR_INVALID_ARG_TYPE overload as of type object; this PR renders ["Object"] through the ArgList overload, where isPrimitiveTypeName lowercases it back to of type object. Byte-identical at every site, and the tests that pin that exact string (test-http2-createserver-options, test-http2-createsecureserver-options, test-http2-update-settings, and node-http2.test.js's two option-validation assertions) all pass unchanged.

assertIsArray has two production call sites, both options.remoteCustomSettings, both defaulting. Those are the only two whose message changes: must be of type Array. becomes must be an instance of Array., which is Node's exact wording (Node routes a bare 'Array' through its classRegExp to an instance of). A grep of test/ for of type Array outside node_modules matches only a comment in test-http2-ping.js, never an assertion, and the full 287-test node-http2.test.js suite passes.

So the entire production-visible effect of the "error routing" change is one message, on one option, moved to Node's exact text. Everything else renders byte-for-byte the same.

One small correction to the review's "Other factors" note: the PR description and status comment do mention ErrorCode.cpp, test-assert.js, and message-event.test.ts, but inside a "Not in this PR" section and a "this change is gone" section respectively. Both say those files were removed from the diff and are accurate for the current head (1788fb94, 7 files, no C++, nothing under test/js/node/test/parallel/).

… [1tpjlb]

Fills the Symbol.for('::bunhttp2internals::').util and .binding slots (landed
empty by #31584) with the helpers Node's internal/http2/util exports, so the
ported node tests that require('internal/http2/util') exercise Bun's real
implementation: assertValidPseudoHeader{,Response,Trailer}, assertWithinRange,
buildNgHeaderString, isIllegalConnectionSpecificHeader, NghttpError +
nghttp2ErrorString, sessionName, updateOptionsBuffer + optionsBuffer,
isPayloadMeaningless.

internalBinding('http2') is served by the real internal/test/binding built-in
(added by #31824), not by a test-common plugin shim; the shim's superseded
virtual module is deleted.

assertIsObject/assertIsArray now pass their expected type as a one-element
list. Node's ERR_INVALID_ARG_TYPE normalizes a string to a list internally;
Bun's single-string overload renders its input literally (for pre-formatted
phrases), so the list form is what selects Node's type classification. One
call site already used it. No C++ changes.

Also: ERR_HTTP2_INVALID_CONNECTION_HEADERS (appended, so the checked-in
ErrorCode.rs mirror stays index-aligned), and NGHTTP2_NV_FLAG_NONE /
NGHTTP2_NV_FLAG_NO_INDEX in http2.constants.

Enables 10 node-compat parallel tests previously marked [ FAIL ].
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing: this PR has been superseded on main.

While resolving the latest merge conflicts (main is ~1074 commits ahead of this branch), I found that everything this PR set out to do for node-compat group 1tpjlb has since landed or been decided upstream, so a rebase would produce either nothing or code main has deliberately removed.

Verified against a bare build of main 18059646, with none of this PR's code:

  • 12 of the group's 14 tests pass as-is, including the 4 I had previously marked as blocked on runtime gaps (test-http2-client-destroy, test-http2-client-http1-server, test-http2-misbehaving-multiplex, test-http2-server-http1-client), and test-http2-misc-util, which asserts the exact ERR_INVALID_ARG_TYPE wording this PR worked on.
  • The remaining 2 (test-http2-util-headers-list, test-http2-util-update-options-buffer) were deleted, not quarantined, by node:http/https/http2: raise Node v26.3.0 compat to ~94%, sync the upstream suites, and fix the Windows/macOS transport-layer teardown bugs they exposed #32488, which states that tests exercising internalBinding('http2') shape are out of scope for Bun. Roughly 60% of this PR's http2.ts additions (buildNgHeaderString, updateOptionsBuffer / optionsBuffer, the binding slot, the internalBinding('http2') case, the NV_FLAG constants) existed only to serve those two tests, so re-adding them would contradict that decision.

Per file:

This PR's change Status on main
http2.ts util slot helpers #32488 populated the slot itself; its assertIsObject / assertIsArray use the same one-element-list approach this PR arrived at
http2.ts binding slot and binding.ts http2 case Serve only the two policy-deleted tests
ErrorCode.ts ERR_HTTP2_INVALID_CONNECTION_HEADERS Already landed via #32602
ErrorCode.rs discriminant mirror File is now generated from ErrorCode.ts; the edit is obsolete
expectations.txt (10 removals) All 10 already removed
node-http2.test.js NV_FLAG assertions Serve the deleted headers-list test
common/index.js shim edit The shim module it edited was also removed upstream

Nothing here needs to be carried forward. Thanks to the reviewers who pushed back on the cross-cutting ErrorCode.cpp change earlier in this PR's life; removing it in favour of the list-normalisation approach is the version that ended up on main.

@robobun robobun closed this Aug 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