node:http2: populate internal/http2/util for --expose-internals tests [1tpjlb]#29825
node:http2: populate internal/http2/util for --expose-internals tests [1tpjlb]#29825robobun wants to merge 4 commits into
Conversation
WalkthroughAdds three new internal module specifiers for HTTP/2 and test bindings, extracts and centralizes HTTP/2 utilities, adjusts ERR_INVALID_ARG_TYPE formatting to prefer "instance of" for certain types, and introduces many HTTP/2-focused tests plus minor test message updates. Changes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
Updated 8:31 PM PT - Jun 18th, 2026
❌ @robobun, your commit 2161ddc has 5 failures in
🧪 To try this PR locally: bunx bun-pr 29825That installs a local version of the PR into your bun-29825 --bun |
StatusRebased onto main after #31584 (the node:http2 rewrite) and #31830 (fs). 10/14 tests from node-compat group
All 10 ✅ removed from CI (63432, 2161ddc): build finished with all per-context statuses green (
None touch any file in this PR. claude[bot] review: no issues found, deferring to a maintainer for final sign-off on the cross-cutting |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
Re duplicate detection: #29823 (also mine, group
The http2 util module, the |
There was a problem hiding this comment.
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
📒 Files selected for processing (25)
src/bun.js/HardcodedModule.zigsrc/bun.js/ModuleLoader.zigsrc/bun.js/bindings/ErrorCode.cppsrc/bun.js/bindings/ErrorCode.tssrc/js/internal/http2/core.tssrc/js/internal/http2/util.tssrc/js/internal/test/binding.tssrc/js/node/http2.tstest/js/node/child_process/child_process-node.test.jstest/js/node/http2/node-http2.test.jstest/js/node/test/parallel/test-assert.jstest/js/node/test/parallel/test-buffer-concat.jstest/js/node/test/parallel/test-child-process-constructor.jstest/js/node/test/parallel/test-dns-setservers-type-check.jstest/js/node/test/parallel/test-http2-createserver-options.jstest/js/node/test/parallel/test-http2-misc-util.jstest/js/node/test/parallel/test-http2-server-socket-destroy.jstest/js/node/test/parallel/test-http2-util-assert-valid-pseudoheader.jstest/js/node/test/parallel/test-http2-util-asserts.jstest/js/node/test/parallel/test-http2-util-headers-list.jstest/js/node/test/parallel/test-http2-util-nghttp2error.jstest/js/node/test/parallel/test-http2-util-update-options-buffer.jstest/js/node/test/parallel/test-process-hrtime.jstest/js/node/test/parallel/test-process-setgroups.jstest/js/web/workers/message-event.test.ts
7e457a8 to
3c643a5
Compare
3c643a5 to
1969eee
Compare
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🔴
src/jsc/bindings/ErrorCode.ts:102— InsertingERR_HTTP2_INVALID_CONNECTION_HEADERSmid-array (alphabetical position) violates the trailing comment in this file — discriminants are index-aligned with the checked-insrc/jsc/ErrorCode.rs, which this PR doesn't touch. Every RustErrorCodeconstant ≥86 is now off-by-one against the regenerated C++errors[]table andErrorCode::COUNT(324) no longer matches; this is what's failingbuild-ruston all 8 platforms. Either append the entry afterERR_PROXY_TUNNELat the end of the array, or regeneratesrc/jsc/ErrorCode.rsin this PR.Extended reasoning...
What the bug is
ERR_HTTP2_INVALID_CONNECTION_HEADERSis inserted at line 102 ofsrc/jsc/bindings/ErrorCode.ts, betweenERR_HTTP2_INFO_STATUS_NOT_ALLOWEDandERR_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:
- C++ side (
ErrorCode+List.h/ theerrors[]table): regenerated at build time fromErrorCode.tsbysrc/codegen/generate-node-errors.ts. After this PR it will have 325 entries withERR_HTTP2_INVALID_CONNECTION_HEADERSat index 86 and emitNODE_ERROR_COUNT = 325. - 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 byHTTP2_INVALID_HEADER_VALUE = ErrorCode(86)— no entry for the new codepub const COUNT: u16 = 324CODE_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, andgit log -- src/jsc/ErrorCode.rsshows 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.tstelling contributors to append, which was overlooked here. The robobun CI comment showsbuild-rustfailing 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
- Before this PR,
ErrorCode.tsindex 85 =ERR_HTTP2_INFO_STATUS_NOT_ALLOWED, index 86 =ERR_HTTP2_INVALID_HEADER_VALUE.ErrorCode.rsmirrors this:ErrorCode(85)/ErrorCode(86). Both sides have 324 entries. - 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. generate-node-errors.tsregenerates the C++errors[]table to match: C++ index 86 →ERR_HTTP2_INVALID_CONNECTION_HEADERS,NODE_ERROR_COUNT = 325.ErrorCode.rsis unchanged: Rust still saysHTTP2_INVALID_HEADER_VALUE = ErrorCode(86)andCOUNT = 324.- Any Rust caller passing
ErrorCode::HTTP2_INVALID_HEADER_VALUE(value 86) intoBun__createErrorWithCodewould now get anERR_HTTP2_INVALID_CONNECTION_HEADERSinstance — and likewise every one of the ~238 codes from index 86 onward is off by one. TheCOUNTmismatch (324 vs 325) trips the build-time check, which is whybuild-rustis red across the board.
Impact
The PR cannot build on any platform (all 8
build-rustjobs 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.tsand include the updatedsrc/jsc/ErrorCode.rs(with the new entry at 86, all subsequent discriminants +1,COUNT = 325, and the newCODE_STRrow) in this PR.
The first option is the minimal change and keeps the file's stated invariant.
- C++ side (
|
Already fixed in 5353e3e (the review landed against the older diff). |
…ls tests
Fills the Symbol.for('::bunhttp2internals::').util slot with the helpers
Node's --expose-internals tests need (assertWithinRange, sessionName,
buildNgHeaderString, NghttpError, nghttp2ErrorString, updateOptionsBuffer,
optionsBuffer, getAuthority, toHeaderObject, isIllegalConnectionSpecificHeader,
isPayloadMeaningless, kSocket, kSensitiveHeaders, ...) and adds a
.binding slot surfaced as internalBinding('http2') via the test-common
Bun.plugin shim.
ERR_INVALID_ARG_TYPE now matches Node.js when given a single expected
type: 'Object'/'Function' lowercase to 'of type object', and a single
CamelCase identifier (Array, RegExp, AbortSignal, ...) formats as
'an instance of X'. Multi-element arrays and pre-formatted strings keep
their existing wording. Updated the handful of test expectations that
were checking Bun's old (incorrect) wording.
Adds NGHTTP2_NV_FLAG_NONE / NGHTTP2_NV_FLAG_NO_INDEX to http2.constants
and the ERR_HTTP2_INVALID_CONNECTION_HEADERS code.
Moves 10 tests from group 1tpjlb off the [ FAIL ] expectations list:
test-http2-misc-util
test-http2-server-socket-destroy
test-http2-util-assert-valid-pseudoheader
test-http2-util-asserts
test-http2-util-headers-list
test-http2-util-nghttp2error
test-http2-util-update-options-buffer
test-http2-client-socket-destroy
test-http2-create-client-secure-session
test-http2-server-sessionerror
…erting Inserting in the alphabetical section of ErrorCode.ts shifted every subsequent discriminant by one, but src/jsc/ErrorCode.rs (checked-in, not regenerated) still held the old indices. Rust callers ended up creating the wrong error class/code for everything after the insertion point (ERR_INVALID_ARG_TYPE reported as ERR_INVALID_ADDRESS_FAMILY, ERR_IPC_CHANNEL_CLOSED as ERR_IP_BLOCKED, etc.). Moved the new entry to the append-only tail and mirrored it in ErrorCode.rs so the discriminants stay aligned.
5353e3e to
a4d4ba5
Compare
The table was keyed incorrectly: NGHTTP2_ERR_FATAL=-900 is just a threshold marker, so -901..-904 were all shifted by one (e.g. -903 BAD_CLIENT_MAGIC reported 'SETTINGS frame cannot be flooded'). The -504..-521 range also had messages assigned to the wrong codes. Re-transcribed directly from nghttp2_helper.c / nghttp2.h.
There was a problem hiding this comment.
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.
What
Populates the
Symbol.for("::bunhttp2internals::")].utilslot that #31584 added (previously{}) with the helpers Node's--expose-internalstests need fromrequire('internal/http2/util'):assertWithinRange,sessionName,buildNgHeaderString,NghttpError,nghttp2ErrorString,updateOptionsBuffer/optionsBuffer,getAuthority,toHeaderObject,isIllegalConnectionSpecificHeader,isPayloadMeaningless,assertValidPseudoHeader{,Response,Trailer},assertIsObject,assertIsArray,kSocket,kSensitiveHeaders,kProxySocket,kRequest,kAuthority,kProtocol,MAX_ADDITIONAL_SETTINGS.Adds a
.bindingslot alongside it ({ constants, nghttp2ErrorString, optionsBuffer }) and aninternal/test/bindingentry in the test-commonBun.pluginshim that returns it forinternalBinding('http2')and falls through toprocess.binding(name)otherwise.While here:
ERR_INVALID_ARG_TYPEnow matches Node.js when given a single expected type."Object"/"Function"lowercase to"of type object", and a single CamelCase identifier (Array,RegExp,AbortSignal,Date, …) formats as"an instance of X". Multi-element arrays and pre-formatted strings with spaces/punctuation keep their existing wording so no unrelated callers change. A handful of existing test expectations are updated to the correct Node wording (verified againstnode -e).NGHTTP2_NV_FLAG_NONE/NGHTTP2_NV_FLAG_NO_INDEXtohttp2.constants(present in Node).ERR_HTTP2_INVALID_CONNECTION_HEADERScode.ArgListoverload ofERR_INVALID_ARG_TYPEagainstlength == 0(previously underflowed).Rebase
Rebased onto main after #31584 (node:http2 rewrite) landed. The original approach here created standalone
src/js/internal/http2/{util,core}.tsmodules wired intoHardcodedModule.zig; that's dropped in favour of #31584'sBun.pluginshim intest/js/node/test/common/index.jsbacked by theSymbol.for("::bunhttp2internals::")export. The helpers now live directly inhttp2.tsnext to the existing ones.Tests
Moves 10 tests from node-compat group 1tpjlb off the
[ FAIL ]list intest/expectations.txt:The remaining 4 (
test-http2-client-destroy,test-http2-client-http1-server,test-http2-misbehaving-multiplex,test-http2-server-http1-client) resolveinternal/http2/utilcorrectly now but still fail on runtime behaviour (abort-signal listener count,HPE_PAUSED_H2_UPGRADEparser code, reused-stream-id error class) and stay on the expectations list.