Skip to content

node:tls: list supported ciphers in getCiphers() and export the SecureContext class - #41696

Open
robobun wants to merge 6 commits into
mainfrom
robobun/34881250/tls-getciphers-securecontext
Open

robobun wants to merge 6 commits into
mainfrom
robobun/34881250/tls-getciphers-securecontext

Conversation

@robobun

@robobun robobun commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • tls.getCiphers() returns the tokens of the DEFAULT_CIPHERS policy string split on :. Node documents it as the supported cipher names, lower-cased and sorted. On bun it returns 19 entries such as !MD5 and !aNULL, it changes when tls.DEFAULT_CIPHERS is assigned, and tls.getCiphers().includes("ecdhe-rsa-aes128-gcm-sha256") is false.
  • tls.SecureContext is a plain builtin function with prototype === undefined. ctx instanceof tls.SecureContext throws TypeError: instanceof called on an object with an invalid prototype property, so option-normalising code that branches on it throws instead.
  • Giving SecureContext a prototype with $toClass(fn, name) (no base class) exposed a bug in jsFunctionToClass (src/jsc/bindings/ZigGlobalObject.cpp): it set mayBePrototype in place on the shared cached empty-object structure. Every {} created afterwards inherited the bit and debug builds hit ASSERTION FAILED: !newStructure->mayBePrototype() in LiteralParser.cpp on the next repeated JSON.parse shape.

Fix

  • New host function getSSLCiphers in src/jsc/bindings/NodeTLS.cpp: a fresh SSL_CTX, SSL_CTX_get_ciphers, plus the three TLS 1.3 suites that BoringSSL keeps out of the cipher list, as Node does. tls.getCiphers() lower-cases, de-duplicates, sorts and caches that list and returns a copy per call.
  • The internal SecureContext constructor is now the export. Like Node it returns an instance when called without new. The "share the memoised SSL_CTX" flag became a module-private symbol, so a user-constructed context always owns its handle.
  • jsFunctionToClass calls prototype->didBecomePrototype(vm), which transitions only the prototype object, instead of flagging its shared structure. All existing $toClass callers pass a base class, where the polluted structure was the per-prototype cache entry and nothing asserted, which is why this stayed hidden.
  • Verified: test/js/node/tls/node-tls-internals.test.ts and test/js/node/tls/node-tls-create-secure-context-args.test.ts (new tests fail on 1.4.3, pass here, including a subprocess that reproduced the assertion). Also test/js/node/tls/, test/js/bun/net/socket.test.ts, fetch.tls.test.ts, express.json.test.ts, net/readline/tty suites, and the vendored test-tls-getcipher, test-tls-set-ciphers*, test-tls-secure-context-usage-order, test-tls-sni-* scripts.

Background

  • SSL_CTX_get_ciphers returns the TLS 1.2 and older suites a context can negotiate. BoringSSL has a fixed TLS 1.3 set (TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256) that the cipher-list API neither configures nor reports.
  • Functions declared in builtin modules get no lazy prototype property from JSC, so $toClass(fn, name, base?) installs prototype, constructor and name by hand.
  • A JSC Structure carries a mayBePrototype bit. The supported way to set it is JSObject::didBecomePrototype, a structure transition on that one object. constructEmptyObject(globalObject) hands out the one cached structure that every empty object starts from, so mutating it in place changes all of them.
Notes
  • The x64-asan lane on the previous push failed ~28 suites with the LiteralParser.cpp(1597) assertion in the test runner process. Local repro: require("node:tls"); const o = {}; o.zz1 = 1; for (let i = 0; i < 3; i++) JSON.parse('{"zz1":1}') aborts a debug build before the ZigGlobalObject.cpp change and prints after it. The first parse creates the transition, the second follows it and asserts.
  • The new cipher list on linux has 22 entries. DES-CBC3-SHA is absent because BoringSSL's ALL excludes 3DES unless named. Node's list also has CCM suites that BoringSSL does not implement.
  • A first version used a class, which made tls.SecureContext(opts) without new throw. Node's constructor has an instanceof guard and returns an instance. The function form keeps that and the test covers it.
  • getCiphers() de-duplicates by sorting in place and skipping adjacent equal names with captured primordials, so a replaced Array.from or Set.prototype.add cannot poison the cached list.
  • Two tests in test/js/node/tls/node-tls-server.test.ts (SNICallback bind hostname, destroySoon) fail in this container on main as well. socket.test.ts "should not call drain before handshake" needs DNS for www.example.com and fails here for that reason only.

[human-review] gate passed · iteration 1 · 6 files touched

fails on main (without fix)
ASAN without fix: 2 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/node/tls/node-tls-create-secure-context-args.test.ts test/js/node/tls/node-tls-internals.test.ts
bun test v1.4.3 (f42e98025)

test/js/node/tls/node-tls-internals.test.ts:
(pass) NodeTLS.cpp > canonicalizeIP [14.62ms]
(pass) NodeTLS.cpp > rootCertificates [43.69ms]
(pass) NodeTLS.cpp > rootCertificates are the DER roots re-encoded in the historical format [566.78ms]
(pass) NodeTLS.cpp > rootCertificates are this checkout's root_certs.der, in order [139.58ms]
85 | 
86 |   // Node documents tls.getCiphers() as the supported cipher names, lower-cased
87 |   // and sorted. It is not the DEFAULT_CIPHERS policy string split on ":".
88 |   test("getCiphers lists the supported ciphers, lower-cased and sorted", () => {
89 |     const ciphers = tls.getCiphers();
90 |     expect(ciphers).toEqual([...ciphers].sort());
                         ^
error: expect(received).toEqual(expected)

  [
-   "!CAMELLIA",
-   "!DES",
-   "!EXPORT",
-   "!MD5",
-   "!PSK",
-   "!RC4",
-   "!SRP",
-   "!aNULL",
-   "!eNULL",
+   "TLS_AES_256_GCM_SHA384",
+  
... (truncated)

release without fix: 2 FAILED
bun test v1.4.3-canary.1 (f42e98025)

test/js/node/tls/node-tls-internals.test.ts:
(pass) NodeTLS.cpp > canonicalizeIP [0.15ms]
(pass) NodeTLS.cpp > rootCertificates [0.28ms]
(pass) NodeTLS.cpp > rootCertificates are the DER roots re-encoded in the historical format [4.58ms]
(pass) NodeTLS.cpp > rootCertificates are this checkout's root_certs.der, in order [4.89ms]
85 | 
86 |   // Node documents tls.getCiphers() as the supported cipher names, lower-cased
87 |   // and sorted. It is not the DEFAULT_CIPHERS policy string split on ":".
88 |   test("getCiphers lists the supported ciphers, lower-cased and sorted", () => {
89 |     const ciphers = tls.getCiphers();
90 |     expect(ciphers).toEqual([...ciphers].sort());
                         ^
error: expect(received).toEqual(expected)

  [
-   "!CAMELLIA",
-   "!DES",
-   "!EXPORT",
-   "!MD5",
-   "!PSK",
-   "!RC4",
-   "!SRP",
-   "!aNULL",
-   "!eNULL",
+   "TLS_AES_256_GCM_SHA384",
+   "TLS_CHACHA20_POLY1305_SHA256",
+   "TLS_AES_128_GCM_SHA256",
+   "ECDHE-RSA-AES128-GCM-SHA256",
    "ECDHE-ECDSA-AES128-GCM-SHA256",
+   "ECDHE-RSA-AES256-GCM-SHA384",
    "ECDHE-ECDSA-AES256-GCM-SHA384",
-   "ECDHE-RSA-AES128-GCM-S
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/node/tls/node-tls-create-secure-context-args.test.ts test/js/node/tls/node-tls-internals.test.ts
bun test v1.4.3 (f42e98025)

test/js/node/tls/node-tls-internals.test.ts:
(pass) NodeTLS.cpp > canonicalizeIP [22.89ms]
(pass) NodeTLS.cpp > rootCertificates [76.25ms]
(pass) NodeTLS.cpp > rootCertificates are the DER roots re-encoded in the historical format [783.63ms]
(pass) NodeTLS.cpp > rootCertificates are this checkout's root_certs.der, in order [188.82ms]
(pass) NodeTLS.cpp > getCiphers lists the supported ciphers, lower-cased and sorted [61.13ms]

test/js/node/tls/node-tls-create-secure-context-args.test.ts:
(pass) tls.createSecureContext extra arguments test > should throw an error if the privateKeyEngine is not a string [50.55ms]
(pass) tls.createSecureContext extra arguments test > should throw an error if the privateKeyIdentifier is not a string [14.78ms]
(pass) tls.createSecureContext extra arguments test > should throw with a valid privateKeyIdentifier but missing privateKeyEngine [5.94ms]
(pass) tls.createSecureContext
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 2620ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/127] gen cpp.rs (cppbind)
[2/127] gen JS modules (bundle-modules)
Preprocess modules (10067ms)
Bundle modules (75ms)
Postprocesss modules (172ms)
Bundle Functions (522ms)
Generate Code (40ms)

[10.88s] Bundled "src/js" for production
  2595 kb
  197 internal modules
  13 native modules
  50 internal functions across 16 files
[2/13] cargo bun_runtime → libbun_runtime.a
�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_base64 v0.0.0 (/workspace/bun/src/base64)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspac
... (truncated)
diff hotspot
src/js/node/tls.ts                                 | 70 ++++++++++++++--------
 src/jsc/bindings/NodeTLS.cpp                       | 36 +++++++++++
 src/jsc/bindings/NodeTLS.h                         |  1 +
 src/jsc/bindings/ZigGlobalObject.cpp               |  3 +-
 .../node-tls-create-secure-context-args.test.ts    | 55 +++++++++++++++++
 test/js/node/tls/node-tls-internals.test.ts        | 36 ++++++++++-
 6 files changed, 174 insertions(+), 27 deletions(-)

gate history · 1 passed · 0 rejected · iteration 1

evidence per changed file
file                                                      reads  edits  tests
src/js/node/tls.ts                                            5      3     17
src/jsc/bindings/NodeTLS.cpp                                  2      1     15
src/jsc/bindings/NodeTLS.h                                    1      2     15
src/jsc/bindings/ZigGlobalObject.cpp                          2      2     15
…js/node/tls/node-tls-create-secure-context-args.test.ts      1      1     12
test/js/node/tls/node-tls-internals.test.ts                   0      0     13

@coderabbitai

coderabbitai Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 14 days. After that, they cost $0.25 per reviewed file.

Or wait 6 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 6a9a5105-2659-46a2-9b8f-be09a69d36ef

📥 Commits

Reviewing files that changed from the base of the PR and between d316760 and 35e9cbb.

📒 Files selected for processing (6)
  • src/js/node/tls.ts
  • src/jsc/bindings/NodeTLS.cpp
  • src/jsc/bindings/NodeTLS.h
  • src/jsc/bindings/ZigGlobalObject.cpp
  • test/js/node/tls/node-tls-create-secure-context-args.test.ts
  • test/js/node/tls/node-tls-internals.test.ts

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

@github-actions github-actions Bot added the claude label Sep 6, 2026
@robobun

robobun commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 11:46 AM PT - Sep 6th, 2026

⏳ @robobun, your commit 35e9cbb is still building in Build #111643, but has 2 failures so far (All Failures):

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

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread src/js/node/tls.ts Outdated
Comment thread test/js/node/tls/node-tls-internals.test.ts Outdated
Comment thread src/js/node/tls.ts Outdated
Comment thread src/js/node/tls.ts Outdated
Comment thread src/js/node/tls.ts Outdated
Comment thread src/js/node/tls.ts Outdated
Comment thread src/jsc/bindings/NodeTLS.cpp Outdated
@robobun

robobun commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status

  • Reproduced on bun 1.4.3 with USE_SYSTEM_BUN=1 bun test test/js/node/tls/node-tls-internals.test.ts test/js/node/tls/node-tls-create-secure-context-args.test.ts: tls.getCiphers() returned the 19 DEFAULT_CIPHERS tokens, and ctx instanceof tls.SecureContext threw instanceof called on an object with an invalid prototype property.
  • The x64-asan aborts on 0615160 (ASSERTION FAILED: !newStructure->mayBePrototype() in LiteralParser.cpp) were real: $toClass without a base class flagged the shared empty-object structure. Fixed in ea0eb61 in jsFunctionToClass, with a subprocess test that reproduced the abort. That lane is clean on 35e9cbb.
  • CI on 35e9cbb (build 111643) is red on two tests this diff does not touch: test/js/node/test/parallel/test-crypto-dh-leak.js on x64-asan (fails on main too) and test/js/web/fetch/fetch-backpressure.test.ts "stalled no consumer drains the full body" on Windows aarch64 (listed in test/flaky-tests.txt, test: lower the stall mark in fetch-backpressure.test.ts to the client's high-water mark #41014 addresses it). Every other lane passed. The diff is ready for review.

@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 three points from my earlier review are addressed: SecureContext is now a $toClass-backed function with the if (!(this instanceof SecureContext)) guard (and a test for the no-new call), getCiphers() dedupes via sorted-adjacent comparison using only captured primordials (no Set/Array.from), and the fresh-array test now asserts .not.toBe against a spread copy. I have nothing further to flag — deferring only because this is node:tls, so a human sign-off is still appropriate.

What was reviewed:

  • getSSLCiphers in NodeTLS.cpp — throw scope, MarkPopErrorOnReturn, DeleteFnPtr<SSL_CTX> RAII, MarkedArgumentBuffer overflow check, RETURN_IF_EXCEPTION/RELEASE_AND_RETURN all in place.
  • kCachedContext symbol gating — the no-new recursive call passes only options, so a user cannot forge the cached path; every internal InternalSecureContext site was updated.
  • Test hygiene — tls.DEFAULT_CIPHERS restored in try/finally; assertions are non-vacuous.
Extended reasoning...

Overview

Two commits landed since my last review (91f59aa, 0615160). The diff now converts SecureContext from a class to a function-class via $toClass with an explicit instanceof self-guard, replaces the Set/Array.from dedup in getCiphers() with a sort-then-skip-adjacent loop over captured primordials, and tightens the fresh-array test to use both an identity check and a spread copy before mutation. All three of my prior inline comments map directly onto these edits.

Security risks

The change touches node:tls but does not alter handshake, verification, or cipher-selection behavior. getSSLCiphers is a read-only enumeration of a fresh throwaway SSL_CTX; the SecureContext refactor preserves the invariant that user-constructed contexts own their SSL_CTX exclusively (the shared-cache opt-in is now gated by a module-private Symbol, which is strictly tighter than the previous public boolean second argument). No security-relevant defaults changed.

Level of scrutiny

Medium. The code itself is small and mechanical, and the C++ follows the exception-scope/RAII conventions REVIEW.md requires. However src/js/node/tls.ts and src/jsc/bindings/NodeTLS.cpp are security-adjacent files, and the approval guidelines say not to auto-approve changes in crypto/TLS paths regardless of apparent simplicity — a human should confirm the SecureContext unification did not change any internal caller's semantics.

Other factors

Tests were added in the existing files for both behaviors, including the no-new construction path and the DEFAULT_CIPHERS-independence of getCiphers(). The github-actions inline comments (likely lint) were followed by a fixup commit and the current diff is clean. No third-party CHANGES_REQUESTED reviews are outstanding.

… shared empty-object structure

$toClass(fn, name) with no base class built the prototype from the cached
empty-object structure and set mayBePrototype on that structure in place.
Every {} created afterwards inherited the bit and JSON.parse's fast path
asserts !newStructure->mayBePrototype(). node:tls is the first caller
without a base, so loading it aborted debug builds on the next repeated
JSON.parse shape. Use didBecomePrototype(), which transitions only the
prototype object.
Comment thread src/jsc/bindings/ZigGlobalObject.cpp 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.

Code review found no issues

No high-confidence issues detected in this change.

Jarred-Sumner pushed a commit that referenced this pull request Sep 14, 2026
…fieHellman, dns.Resolver, $.Shell, HTTPParser and ConnectionsList (#42528)

### Problem
- `class X extends C {}; new X()` returns an instance of `C`, not of
`X`, for `Bun.Cookie`, `Bun.CookieMap`, `crypto.ECDH`,
`crypto.DiffieHellman`, `dns.Resolver`, `$.Shell`, and `HTTPParser` and
`ConnectionsList` of the `http_parser` binding. `new X() instanceof X`
is `false` and the methods of `X` are missing. Node v26.3.0 returns an
`X`.
- The native constructors ignore `newTarget` and always use the base
structure (`JSCookie.cpp`, `JSCookieMap.cpp`,
`JSECDHConstructor.cpp:75`, `JSDiffieHellmanConstructor.cpp:193`,
`JSHTTPParserConstructor.cpp:23`,
`JSConnectionsListConstructor.cpp:23`). `$.Shell` (`shell.ts:344`)
always sets `ShellPrototype.prototype`.
- `dns.Resolver` (`dns.ts:694`) returns `new InternalResolver(options)`.
So `new dns.Resolver() instanceof dns.Resolver` is `false` too, and a
method patched on `dns.Resolver.prototype` never runs.

### Fix
- `Bun.Cookie` and `Bun.CookieMap` call `setSubclassStructureIfNeeded`,
like the other 22 `JSDOMConstructor` bindings.
- `structureForNewTarget` (new, beside `defaultGlobalObject`) holds the
`newTarget` block that `crypto.DiffieHellmanGroup` had. `ECDH`,
`DiffieHellman`, `DiffieHellmanGroup`, `HTTPParser` and
`ConnectionsList` call it. `$.Shell` uses `new.target.prototype`.
- `dns.Resolver` is the class itself, like `dns.promises.Resolver`.
`dns.Resolver()` without `new` now throws a `TypeError`, as in Node. The
compat page drops its note about this class.
- Verified: tests in `cookie-map.test.ts`, `ecdh.test.ts`,
`node-crypto.test.js`, `node-http-parser.test.ts`,
`bunshell-instance.test.ts` and `dns-resolver-class.test.ts`.
1.4.3-canary.1 fails 17 of them.

### Background
- `newTarget` is the constructor that `new` was applied to. In `new
X()`, `C` runs with `newTarget === X`. The object must get
`X.prototype`.
- A JSC `Structure` holds the prototype of an object.
`InternalFunction::createSubclassStructure(newTarget, base)` returns the
base structure with `newTarget.prototype`.
- `getFunctionRealm(newTarget)` is the global of `newTarget`. For a
class from a `node:vm` context it is not a Bun global, so the code
passes it through `defaultGlobalObject`.

<details><summary>Notes</summary>

Repro:

```js
const crypto = require("node:crypto"), dns = require("node:dns"), { HTTPParser, ConnectionsList } = process.binding("http_parser");
const T = [["Bun.Cookie", Bun.Cookie, ["a", "b"]], ["Bun.CookieMap", Bun.CookieMap, ["a=b"]], ["crypto.ECDH", crypto.ECDH, ["prime256v1"]],
  ["crypto.DiffieHellman", crypto.DiffieHellman, [Buffer.from("17", "hex")]], ["dns.Resolver", dns.Resolver, []], ["$.Shell", Bun.$.Shell, []],
  ["HTTPParser", HTTPParser, []], ["ConnectionsList", ConnectionsList, []],
  ["control crypto.DiffieHellmanGroup", crypto.DiffieHellmanGroup, ["modp14"]], ["control dns.promises.Resolver", dns.promises.Resolver, []], ["control URL", URL, ["http://a/"]]];
for (const [n, C, a] of T) { class X extends C { extra() { return 1; } } const r = new X(...a);
  console.log(n.padEnd(34), "instanceof X:", r instanceof X, "| instanceof C:", r instanceof C, "| extra:", typeof r.extra); }
```

| | 1.4.3-canary.1 | this branch | node v26.3.0 |
| --- | --- | --- | --- |
| `Bun.Cookie`, `Bun.CookieMap`, `$.Shell` | `false`, `true`,
`undefined` | `true`, `true`, `function` | |
| `crypto.ECDH`, `crypto.DiffieHellman`, `_http_common.HTTPParser` |
`false`, `true`, `undefined` | `true`, `true`, `function` | `true`,
`true`, `function` |
| `ConnectionsList` | `false`, `true`, `undefined` | `true`, `true`,
`function` | not reachable |
| `dns.Resolver` | `false`, `false`, `undefined` | `true`, `true`,
`function` | `true`, `true`, `function` |
| the three controls | `true`, `true`, `function` | `true`, `true`,
`function` | `true`, `true`, `function` |

`dns.Resolver`:
- The wrapper function is from the first `node:dns` implementation
(2b1b897). `$toClass` gave it a prototype that inherits from
`InternalResolver.prototype`, but the returned object had
`InternalResolver.prototype` itself. No object ever had
`dns.Resolver.prototype`.
- dd-trace wraps `dns.Resolver.prototype.resolve`, `reverse` and the
shorthands. On 1.4.3-canary.1 a wrapper installed there is never called
on an instance. It is called on this branch and on Node.
- Node declares `class Resolver extends ResolverBase {}`, so
`dns.Resolver()` without `new` throws there too: `Class constructor
Resolver cannot be invoked without 'new'`. No test in the repo called it
without `new`.
- The test is in a new file because `node-dns.test.js` resolves public
hostnames. 29 of its tests fail in a sandbox with no outbound DNS,
before and after this change (the same 29).
- `docs/runtime/nodejs-compat.mdx` said "the callback-style `Resolver`
class cannot be subclassed (`dns.promises.Resolver` can)" since #38441.
This PR removes that clause.

`structureForNewTarget`:
- It takes the lexical global, `newTarget`, and a pointer to the
`LazyClassStructure` member of `Zig::GlobalObject`. It returns the base
structure when `newTarget` is the constructor, and the subclass
structure otherwise.
- `DiffieHellmanGroup` calls it at the place where its block was, after
the argument checks, so its behaviour does not change. Its `if
(!newTarget)` branch is gone. A host `construct` function always has a
`newTarget`. `callDiffieHellmanGroup` goes through `JSC::construct`,
which sets it to the constructor.
- In `ECDH` and `DiffieHellman` the call is at the top of the
constructor. Node's `function ECDH(curve)` gets its `this` before
`validateString(curve)` runs, so a `prototype` getter on `newTarget`
runs before the argument error. `Reflect.construct(ECDH, [{}],
proxyWithThrowingPrototypeGetter)` throws the getter's error on Node and
on this branch.
- The other constructors that have the same block by hand (`Hash`,
`Hmac`, `Sign`, `X509Certificate`, `MIMEType`, `MIMEParams`, `Dirent`,
`vm.Script`, `DatabaseSync`) already honor `newTarget`. This PR does not
move them to the helper. That is a refactor with no change in behaviour.
- One difference from Node remains, and `DiffieHellmanGroup`, `Hash`,
`Hmac` and `Sign` already have it. For `Reflect.construct(ECDH, args,
F)` where `F.prototype` does not inherit from `ECDH.prototype`, Node's
`if (!(this instanceof ECDH)) return new ECDH(curve)` returns a plain
`ECDH`. Bun returns an object with `F.prototype`. The tests only assert
what is the same on Node.

Other cases checked by hand on this branch, for all the classes. None
crashes. `BUN_JSC_validateExceptionChecks=1` is clean on the subclass,
revoked Proxy and throwing getter paths.
- `Reflect.construct(C, args, F)` with a plain function, a bound
function (no `prototype`, so the base prototype is used), a Proxy, and a
function whose `prototype` is not an object.
- A revoked Proxy as `newTarget` throws `TypeError: Cannot get function
realm from revoked Proxy` (a `TypeError` on Node too).
- A subclass declared in a `node:vm` context, and `Reflect.construct`
with a `newTarget` from a context. The crypto tests cover the first
case.
- `ECDH("prime256v1")` and `DiffieHellman(prime)` without `new` still
return a base instance.

Census: I walked every constructible function reachable from
`globalThis`, `Bun`, `node:*` and `bun:*` to depth 3 and constructed a
subclass of each with a list of guessed arguments. 772 functions are
constructible. 210 returned an object for the subclass. On this branch
the ones that are still not an instance of the subclass are:
- `string_decoder.StringDecoder`. It returns the subclass constructor
itself. It has a separate change.
- `Buffer` and `SlowBuffer`. Node also returns a plain `Buffer`.
- `diagnostics_channel.Channel`. It overrides `Symbol.hasInstance`. The
prototype is correct, and Node prints the same.
- `module.Module`, `ShadowRealm` and `WebAssembly.Suspending`. Known,
and the last two are in JavaScriptCore.

The census has blind spots:
- 506 of the 772 did not construct with any guessed argument list.
- It skipped modules whose name starts with `_` and everything behind
`process.binding`. A review found `HTTPParser` and `ConnectionsList`
there, and this PR fixes them.
- It did not report a constructor whose `extends` clause throws.
`tls.SecureContext`, `Bun.ArrayBufferSink` and `Bun.SQL` have no
`prototype` object, so `class X extends C` throws `The value of the
superclass's prototype property is not an object or null`, and `new C()
instanceof C` throws too. #41696 (SecureContext) and #39936 (the sinks)
are open for the first two. `Bun.SQL` is a separate follow-up.
- A class from a native addon that uses the V8 API directly is not
reachable from JS globals. `FunctionTemplate::functionConstruct`
(`src/jsc/bindings/v8/shim/FunctionTemplate.cpp`) takes the prototype
from the callee and not from `newTarget`. That is a separate follow-up.

`$.Shell` was not in the original report. The census found it.

Separate from this PR: `Object.getPrototypeOf(Bun.Cookie)` and
`Object.getPrototypeOf(Bun.CookieMap)` are `Object.prototype`, not
`Function.prototype` (`prototypeForStructure` in both files). So
`Bun.Cookie.bind` is `undefined`, also on a subclass. This PR does not
change that.

Suites run on the debug build: `test/js/bun/cookie/`,
`test/js/node/crypto/node-crypto.test.js`, `ecdh.test.ts`,
`crypto-invalid-this.test.ts`,
`test/js/node/dns/dns-resolver-class.test.ts`, `node-dns.test.js`,
`test/js/node/http/node-http-parser.test.ts`,
`test/js/bun/shell/bunshell-instance.test.ts`,
`bunshell-default.test.ts`, `env.positionals.test.ts`, and the Node
tests `test-crypto-dh*.js` (13 files), `test-crypto-ecdh*.js`,
`test-crypto-classes.js`, `test-dns*.js` (25 files),
`test-worker-dns-terminate*.js`, `test-http-parser*.js`,
`test-http-common.js`.

</details>

<!-- robobun:evidence:begin -->

---

**[human-review]** gate passed · iteration 0 · 18 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/cookie/cookie-map.test.ts test/js/bun/shell/bunshell-instance.test.ts test/js/node/crypto/ecdh.test.ts test/js/node/crypto/node-crypto.test.js test/js/node/dns/dns-resolver-class.test.ts test/js/node/http/node-http-parser.test.ts
ninja: Entering directory `/workspace/bun/build/debug'
[1/166] cc obj/src/jsc/bindings/sqlite/sqlite3.c.o
[2/166] gen generated_host_exports.rs
generated_host_exports.rs: 122 exports (host=5, lazy=10, generic=107, rust=0); 243 extern-C blocks audited
[3/166] gen ZigGeneratedClasses.{cpp,h,rs}
Found 2 classes from /workspace/bun/src/jsc/resolve_message.classes.ts
  - ResolveMessage (15 fields)
  - BuildMessage (10 fields)
Found 1 classes from /workspace/bun/src/runtime/api/Archive.classes.ts
  - Archive (4 fields, 1 class fields)
Found 2 classes from /workspace/bun/src/runtime/api/BunObject.classes.ts
  - ResourceUsage (8 fields)
  - Subprocess (20 fields)
Found 1 classes from /workspace/bun/src/runtime/api/cron.classes.ts
  - CronJob (5 fields)
Found 3 classes from /workspace/bun/src/runtime/api/fil
... (truncated)

release without fix: all passed
bun test v1.4.3-canary.1 (138f46d)

test/js/bun/cookie/cookie-map.test.ts:
(pass) Bun.Cookie and Bun.CookieMap > can create a basic Cookie [0.08ms]
(pass) Bun.Cookie and Bun.CookieMap > can create a Cookie with options [0.07ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.toString() formats properly [0.10ms]
(pass) Bun.Cookie and Bun.CookieMap > can set Cookie expires as Date [7.65ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.isExpired() returns correct value [0.15ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.isExpired() gives Max-Age precedence over Expires [0.11ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse works with all attributes [0.10ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse keeps Expires when Max-Age comes first [0.12ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse takes the last value of a repeated attribute [0.04ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse reads every attribute in any order [0.17ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.serialize creates cookie string [0.09ms]
(pass) Bun.Cookie and Bun.CookieMap > can create an empty CookieMap [0.06ms]
(pass) Bun.Cookie and Bun.CookieMap > can create Cooki
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/cookie/cookie-map.test.ts test/js/bun/shell/bunshell-instance.test.ts test/js/node/crypto/ecdh.test.ts test/js/node/crypto/node-crypto.test.js test/js/node/dns/dns-resolver-class.test.ts test/js/node/http/node-http-parser.test.ts
bun test v1.4.3 (6a92015)

test/js/bun/cookie/cookie-map.test.ts:
(pass) Bun.Cookie and Bun.CookieMap > can create a basic Cookie [4.56ms]
(pass) Bun.Cookie and Bun.CookieMap > can create a Cookie with options [5.05ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.toString() formats properly [5.87ms]
(pass) Bun.Cookie and Bun.CookieMap > can set Cookie expires as Date [160.37ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.isExpired() returns correct value [4.67ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.isExpired() gives Max-Age precedence over Expires [6.09ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse works with all attributes [6.01ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse keeps Expires when Max-Age comes first [8.01ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 726ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/127] cc obj/src/jsc/bindings/sqlite/sqlite3.c.o
[2/127] gen generated_host_exports.rs
generated_host_exports.rs: 122 exports (host=5, lazy=10, generic=107, rust=0); 243 extern-C blocks audited
[3/127] gen ZigGeneratedClasses.{cpp,h,rs}
Found 2 classes from /workspace/bun/src/jsc/resolve_message.classes.ts
  - ResolveMessage (15 fields)
  - BuildMessage (10 fields)
Found 1 classes from /workspace/bun/src/runtime/api/Archive.classes.ts
  - Archive (4 fields, 1 class fields)
Found 2 classes from /workspace/bun/src/runtime/api/BunObject.classes.ts
  - ResourceUsage (8 fields)
  - Subprocess (20 fields)
Found 1 classes from /workspace/bun/src/runtime/api/cron.classes.ts
  - CronJob (5 fields)
Found 3 classes from /workspace/bun/src/runtime/api/filesystem_router.classes.ts
  - FileSystemRouter (5 fields)
  - FrameworkFileSystemRouter (2 fields)
  - MatchedRoute (8 fields)
Found 1 classes from /workspace/bun/src/runtime/api/Glob.classes.ts
  - Glob (5 fields)
Found 1 classes from /workspace/bun/src/runtime/api
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
docs/runtime/nodejs-compat.mdx                     |  2 +-
 src/js/builtins/shell.ts                           |  3 +-
 src/js/node/dns.ts                                 |  9 +---
 src/jsc/bindings/ZigGlobalObject.cpp               | 14 +++++
 src/jsc/bindings/ZigGlobalObject.h                 |  3 ++
 .../node/crypto/JSDiffieHellmanConstructor.cpp     |  7 ++-
 .../crypto/JSDiffieHellmanGroupConstructor.cpp     | 18 +------
 src/jsc/bindings/node/crypto/JSECDHConstructor.cpp |  6 +--
 .../node/http/JSConnectionsListConstructor.cpp     |  3 +-
 .../bindings/node/http/JSHTTPParserConstructor.cpp |  3 +-
 src/jsc/bindings/webcore/JSCookie.cpp              | 23 ++++----
 src/jsc/bindings/webcore/JSCookieMap.cpp           |  5 +-
 test/js/bun/cookie/cookie-map.test.ts              | 61 ++++++++++++++++++++++
 test/js/bun/shell/bunshell-instance.test.ts        | 15 ++++++
 test/js/node/crypto/ecdh.test.ts                   | 33 ++++++++++++
 test/js/node/crypto/node-crypto.test.js            | 34 ++++++++++++
 test/js/node/dns/dns-resolver-class.test.ts        | 34 ++++++++++++
 test/js/node/http/node-http-parser.test.ts         | 27 ++++++++++
 18 files changed, 253 insertions(+), 47 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                                      reads  edits  tests
docs/runtime/nodejs-compat.mdx                                1      1     29
src/js/builtins/shell.ts                                      1      1     29
src/js/node/dns.ts                                            2      0     29
src/jsc/bindings/ZigGlobalObject.cpp                          0      0     29
src/jsc/bindings/ZigGlobalObject.h                            3      2     29
…jsc/bindings/node/crypto/JSDiffieHellmanConstructor.cpp      0      0     29
…indings/node/crypto/JSDiffieHellmanGroupConstructor.cpp      0      0     29
src/jsc/bindings/node/crypto/JSECDHConstructor.cpp            1      1     29
…jsc/bindings/node/http/JSConnectionsListConstructor.cpp      0      0     29
src/jsc/bindings/node/http/JSHTTPParserConstructor.cpp        0      0     29
src/jsc/bindings/webcore/JSCookie.cpp                         1      0     29
src/jsc/bindings/webcore/JSCookieMap.cpp                      1      1     29
test/js/bun/cookie/cookie-map.test.ts                         1      2      7
test/js/bun/shell/bunshell-instance.test.ts                   1      2      6
test/js/node/crypto/ecdh.test.ts                              0      0      9
test/js/node/crypto/node-crypto.test.js                       0      0      9
(+ 2 more files)
```

</details>

<!-- robobun:evidence:end -->
usrbinkat pushed a commit to usrbinkat/bun that referenced this pull request Sep 15, 2026
…fieHellman, dns.Resolver, $.Shell, HTTPParser and ConnectionsList (oven-sh#42528)

### Problem
- `class X extends C {}; new X()` returns an instance of `C`, not of
`X`, for `Bun.Cookie`, `Bun.CookieMap`, `crypto.ECDH`,
`crypto.DiffieHellman`, `dns.Resolver`, `$.Shell`, and `HTTPParser` and
`ConnectionsList` of the `http_parser` binding. `new X() instanceof X`
is `false` and the methods of `X` are missing. Node v26.3.0 returns an
`X`.
- The native constructors ignore `newTarget` and always use the base
structure (`JSCookie.cpp`, `JSCookieMap.cpp`,
`JSECDHConstructor.cpp:75`, `JSDiffieHellmanConstructor.cpp:193`,
`JSHTTPParserConstructor.cpp:23`,
`JSConnectionsListConstructor.cpp:23`). `$.Shell` (`shell.ts:344`)
always sets `ShellPrototype.prototype`.
- `dns.Resolver` (`dns.ts:694`) returns `new InternalResolver(options)`.
So `new dns.Resolver() instanceof dns.Resolver` is `false` too, and a
method patched on `dns.Resolver.prototype` never runs.

### Fix
- `Bun.Cookie` and `Bun.CookieMap` call `setSubclassStructureIfNeeded`,
like the other 22 `JSDOMConstructor` bindings.
- `structureForNewTarget` (new, beside `defaultGlobalObject`) holds the
`newTarget` block that `crypto.DiffieHellmanGroup` had. `ECDH`,
`DiffieHellman`, `DiffieHellmanGroup`, `HTTPParser` and
`ConnectionsList` call it. `$.Shell` uses `new.target.prototype`.
- `dns.Resolver` is the class itself, like `dns.promises.Resolver`.
`dns.Resolver()` without `new` now throws a `TypeError`, as in Node. The
compat page drops its note about this class.
- Verified: tests in `cookie-map.test.ts`, `ecdh.test.ts`,
`node-crypto.test.js`, `node-http-parser.test.ts`,
`bunshell-instance.test.ts` and `dns-resolver-class.test.ts`.
1.4.3-canary.1 fails 17 of them.

### Background
- `newTarget` is the constructor that `new` was applied to. In `new
X()`, `C` runs with `newTarget === X`. The object must get
`X.prototype`.
- A JSC `Structure` holds the prototype of an object.
`InternalFunction::createSubclassStructure(newTarget, base)` returns the
base structure with `newTarget.prototype`.
- `getFunctionRealm(newTarget)` is the global of `newTarget`. For a
class from a `node:vm` context it is not a Bun global, so the code
passes it through `defaultGlobalObject`.

<details><summary>Notes</summary>

Repro:

```js
const crypto = require("node:crypto"), dns = require("node:dns"), { HTTPParser, ConnectionsList } = process.binding("http_parser");
const T = [["Bun.Cookie", Bun.Cookie, ["a", "b"]], ["Bun.CookieMap", Bun.CookieMap, ["a=b"]], ["crypto.ECDH", crypto.ECDH, ["prime256v1"]],
  ["crypto.DiffieHellman", crypto.DiffieHellman, [Buffer.from("17", "hex")]], ["dns.Resolver", dns.Resolver, []], ["$.Shell", Bun.$.Shell, []],
  ["HTTPParser", HTTPParser, []], ["ConnectionsList", ConnectionsList, []],
  ["control crypto.DiffieHellmanGroup", crypto.DiffieHellmanGroup, ["modp14"]], ["control dns.promises.Resolver", dns.promises.Resolver, []], ["control URL", URL, ["http://a/"]]];
for (const [n, C, a] of T) { class X extends C { extra() { return 1; } } const r = new X(...a);
  console.log(n.padEnd(34), "instanceof X:", r instanceof X, "| instanceof C:", r instanceof C, "| extra:", typeof r.extra); }
```

| | 1.4.3-canary.1 | this branch | node v26.3.0 |
| --- | --- | --- | --- |
| `Bun.Cookie`, `Bun.CookieMap`, `$.Shell` | `false`, `true`,
`undefined` | `true`, `true`, `function` | |
| `crypto.ECDH`, `crypto.DiffieHellman`, `_http_common.HTTPParser` |
`false`, `true`, `undefined` | `true`, `true`, `function` | `true`,
`true`, `function` |
| `ConnectionsList` | `false`, `true`, `undefined` | `true`, `true`,
`function` | not reachable |
| `dns.Resolver` | `false`, `false`, `undefined` | `true`, `true`,
`function` | `true`, `true`, `function` |
| the three controls | `true`, `true`, `function` | `true`, `true`,
`function` | `true`, `true`, `function` |

`dns.Resolver`:
- The wrapper function is from the first `node:dns` implementation
(2b1b897). `$toClass` gave it a prototype that inherits from
`InternalResolver.prototype`, but the returned object had
`InternalResolver.prototype` itself. No object ever had
`dns.Resolver.prototype`.
- dd-trace wraps `dns.Resolver.prototype.resolve`, `reverse` and the
shorthands. On 1.4.3-canary.1 a wrapper installed there is never called
on an instance. It is called on this branch and on Node.
- Node declares `class Resolver extends ResolverBase {}`, so
`dns.Resolver()` without `new` throws there too: `Class constructor
Resolver cannot be invoked without 'new'`. No test in the repo called it
without `new`.
- The test is in a new file because `node-dns.test.js` resolves public
hostnames. 29 of its tests fail in a sandbox with no outbound DNS,
before and after this change (the same 29).
- `docs/runtime/nodejs-compat.mdx` said "the callback-style `Resolver`
class cannot be subclassed (`dns.promises.Resolver` can)" since oven-sh#38441.
This PR removes that clause.

`structureForNewTarget`:
- It takes the lexical global, `newTarget`, and a pointer to the
`LazyClassStructure` member of `Zig::GlobalObject`. It returns the base
structure when `newTarget` is the constructor, and the subclass
structure otherwise.
- `DiffieHellmanGroup` calls it at the place where its block was, after
the argument checks, so its behaviour does not change. Its `if
(!newTarget)` branch is gone. A host `construct` function always has a
`newTarget`. `callDiffieHellmanGroup` goes through `JSC::construct`,
which sets it to the constructor.
- In `ECDH` and `DiffieHellman` the call is at the top of the
constructor. Node's `function ECDH(curve)` gets its `this` before
`validateString(curve)` runs, so a `prototype` getter on `newTarget`
runs before the argument error. `Reflect.construct(ECDH, [{}],
proxyWithThrowingPrototypeGetter)` throws the getter's error on Node and
on this branch.
- The other constructors that have the same block by hand (`Hash`,
`Hmac`, `Sign`, `X509Certificate`, `MIMEType`, `MIMEParams`, `Dirent`,
`vm.Script`, `DatabaseSync`) already honor `newTarget`. This PR does not
move them to the helper. That is a refactor with no change in behaviour.
- One difference from Node remains, and `DiffieHellmanGroup`, `Hash`,
`Hmac` and `Sign` already have it. For `Reflect.construct(ECDH, args,
F)` where `F.prototype` does not inherit from `ECDH.prototype`, Node's
`if (!(this instanceof ECDH)) return new ECDH(curve)` returns a plain
`ECDH`. Bun returns an object with `F.prototype`. The tests only assert
what is the same on Node.

Other cases checked by hand on this branch, for all the classes. None
crashes. `BUN_JSC_validateExceptionChecks=1` is clean on the subclass,
revoked Proxy and throwing getter paths.
- `Reflect.construct(C, args, F)` with a plain function, a bound
function (no `prototype`, so the base prototype is used), a Proxy, and a
function whose `prototype` is not an object.
- A revoked Proxy as `newTarget` throws `TypeError: Cannot get function
realm from revoked Proxy` (a `TypeError` on Node too).
- A subclass declared in a `node:vm` context, and `Reflect.construct`
with a `newTarget` from a context. The crypto tests cover the first
case.
- `ECDH("prime256v1")` and `DiffieHellman(prime)` without `new` still
return a base instance.

Census: I walked every constructible function reachable from
`globalThis`, `Bun`, `node:*` and `bun:*` to depth 3 and constructed a
subclass of each with a list of guessed arguments. 772 functions are
constructible. 210 returned an object for the subclass. On this branch
the ones that are still not an instance of the subclass are:
- `string_decoder.StringDecoder`. It returns the subclass constructor
itself. It has a separate change.
- `Buffer` and `SlowBuffer`. Node also returns a plain `Buffer`.
- `diagnostics_channel.Channel`. It overrides `Symbol.hasInstance`. The
prototype is correct, and Node prints the same.
- `module.Module`, `ShadowRealm` and `WebAssembly.Suspending`. Known,
and the last two are in JavaScriptCore.

The census has blind spots:
- 506 of the 772 did not construct with any guessed argument list.
- It skipped modules whose name starts with `_` and everything behind
`process.binding`. A review found `HTTPParser` and `ConnectionsList`
there, and this PR fixes them.
- It did not report a constructor whose `extends` clause throws.
`tls.SecureContext`, `Bun.ArrayBufferSink` and `Bun.SQL` have no
`prototype` object, so `class X extends C` throws `The value of the
superclass's prototype property is not an object or null`, and `new C()
instanceof C` throws too. oven-sh#41696 (SecureContext) and oven-sh#39936 (the sinks)
are open for the first two. `Bun.SQL` is a separate follow-up.
- A class from a native addon that uses the V8 API directly is not
reachable from JS globals. `FunctionTemplate::functionConstruct`
(`src/jsc/bindings/v8/shim/FunctionTemplate.cpp`) takes the prototype
from the callee and not from `newTarget`. That is a separate follow-up.

`$.Shell` was not in the original report. The census found it.

Separate from this PR: `Object.getPrototypeOf(Bun.Cookie)` and
`Object.getPrototypeOf(Bun.CookieMap)` are `Object.prototype`, not
`Function.prototype` (`prototypeForStructure` in both files). So
`Bun.Cookie.bind` is `undefined`, also on a subclass. This PR does not
change that.

Suites run on the debug build: `test/js/bun/cookie/`,
`test/js/node/crypto/node-crypto.test.js`, `ecdh.test.ts`,
`crypto-invalid-this.test.ts`,
`test/js/node/dns/dns-resolver-class.test.ts`, `node-dns.test.js`,
`test/js/node/http/node-http-parser.test.ts`,
`test/js/bun/shell/bunshell-instance.test.ts`,
`bunshell-default.test.ts`, `env.positionals.test.ts`, and the Node
tests `test-crypto-dh*.js` (13 files), `test-crypto-ecdh*.js`,
`test-crypto-classes.js`, `test-dns*.js` (25 files),
`test-worker-dns-terminate*.js`, `test-http-parser*.js`,
`test-http-common.js`.

</details>

<!-- robobun:evidence:begin -->

---

**[human-review]** gate passed · iteration 0 · 18 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/cookie/cookie-map.test.ts test/js/bun/shell/bunshell-instance.test.ts test/js/node/crypto/ecdh.test.ts test/js/node/crypto/node-crypto.test.js test/js/node/dns/dns-resolver-class.test.ts test/js/node/http/node-http-parser.test.ts
ninja: Entering directory `/workspace/bun/build/debug'
[1/166] cc obj/src/jsc/bindings/sqlite/sqlite3.c.o
[2/166] gen generated_host_exports.rs
generated_host_exports.rs: 122 exports (host=5, lazy=10, generic=107, rust=0); 243 extern-C blocks audited
[3/166] gen ZigGeneratedClasses.{cpp,h,rs}
Found 2 classes from /workspace/bun/src/jsc/resolve_message.classes.ts
  - ResolveMessage (15 fields)
  - BuildMessage (10 fields)
Found 1 classes from /workspace/bun/src/runtime/api/Archive.classes.ts
  - Archive (4 fields, 1 class fields)
Found 2 classes from /workspace/bun/src/runtime/api/BunObject.classes.ts
  - ResourceUsage (8 fields)
  - Subprocess (20 fields)
Found 1 classes from /workspace/bun/src/runtime/api/cron.classes.ts
  - CronJob (5 fields)
Found 3 classes from /workspace/bun/src/runtime/api/fil
... (truncated)

release without fix: all passed
bun test v1.4.3-canary.1 (138f46d)

test/js/bun/cookie/cookie-map.test.ts:
(pass) Bun.Cookie and Bun.CookieMap > can create a basic Cookie [0.08ms]
(pass) Bun.Cookie and Bun.CookieMap > can create a Cookie with options [0.07ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.toString() formats properly [0.10ms]
(pass) Bun.Cookie and Bun.CookieMap > can set Cookie expires as Date [7.65ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.isExpired() returns correct value [0.15ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.isExpired() gives Max-Age precedence over Expires [0.11ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse works with all attributes [0.10ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse keeps Expires when Max-Age comes first [0.12ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse takes the last value of a repeated attribute [0.04ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse reads every attribute in any order [0.17ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.serialize creates cookie string [0.09ms]
(pass) Bun.Cookie and Bun.CookieMap > can create an empty CookieMap [0.06ms]
(pass) Bun.Cookie and Bun.CookieMap > can create Cooki
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/cookie/cookie-map.test.ts test/js/bun/shell/bunshell-instance.test.ts test/js/node/crypto/ecdh.test.ts test/js/node/crypto/node-crypto.test.js test/js/node/dns/dns-resolver-class.test.ts test/js/node/http/node-http-parser.test.ts
bun test v1.4.3 (6a92015)

test/js/bun/cookie/cookie-map.test.ts:
(pass) Bun.Cookie and Bun.CookieMap > can create a basic Cookie [4.56ms]
(pass) Bun.Cookie and Bun.CookieMap > can create a Cookie with options [5.05ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.toString() formats properly [5.87ms]
(pass) Bun.Cookie and Bun.CookieMap > can set Cookie expires as Date [160.37ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.isExpired() returns correct value [4.67ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.isExpired() gives Max-Age precedence over Expires [6.09ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse works with all attributes [6.01ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse keeps Expires when Max-Age comes first [8.01ms]
(pass) Bun.Cookie and Bun.CookieMap > Cookie.parse
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 726ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/127] cc obj/src/jsc/bindings/sqlite/sqlite3.c.o
[2/127] gen generated_host_exports.rs
generated_host_exports.rs: 122 exports (host=5, lazy=10, generic=107, rust=0); 243 extern-C blocks audited
[3/127] gen ZigGeneratedClasses.{cpp,h,rs}
Found 2 classes from /workspace/bun/src/jsc/resolve_message.classes.ts
  - ResolveMessage (15 fields)
  - BuildMessage (10 fields)
Found 1 classes from /workspace/bun/src/runtime/api/Archive.classes.ts
  - Archive (4 fields, 1 class fields)
Found 2 classes from /workspace/bun/src/runtime/api/BunObject.classes.ts
  - ResourceUsage (8 fields)
  - Subprocess (20 fields)
Found 1 classes from /workspace/bun/src/runtime/api/cron.classes.ts
  - CronJob (5 fields)
Found 3 classes from /workspace/bun/src/runtime/api/filesystem_router.classes.ts
  - FileSystemRouter (5 fields)
  - FrameworkFileSystemRouter (2 fields)
  - MatchedRoute (8 fields)
Found 1 classes from /workspace/bun/src/runtime/api/Glob.classes.ts
  - Glob (5 fields)
Found 1 classes from /workspace/bun/src/runtime/api
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
docs/runtime/nodejs-compat.mdx                     |  2 +-
 src/js/builtins/shell.ts                           |  3 +-
 src/js/node/dns.ts                                 |  9 +---
 src/jsc/bindings/ZigGlobalObject.cpp               | 14 +++++
 src/jsc/bindings/ZigGlobalObject.h                 |  3 ++
 .../node/crypto/JSDiffieHellmanConstructor.cpp     |  7 ++-
 .../crypto/JSDiffieHellmanGroupConstructor.cpp     | 18 +------
 src/jsc/bindings/node/crypto/JSECDHConstructor.cpp |  6 +--
 .../node/http/JSConnectionsListConstructor.cpp     |  3 +-
 .../bindings/node/http/JSHTTPParserConstructor.cpp |  3 +-
 src/jsc/bindings/webcore/JSCookie.cpp              | 23 ++++----
 src/jsc/bindings/webcore/JSCookieMap.cpp           |  5 +-
 test/js/bun/cookie/cookie-map.test.ts              | 61 ++++++++++++++++++++++
 test/js/bun/shell/bunshell-instance.test.ts        | 15 ++++++
 test/js/node/crypto/ecdh.test.ts                   | 33 ++++++++++++
 test/js/node/crypto/node-crypto.test.js            | 34 ++++++++++++
 test/js/node/dns/dns-resolver-class.test.ts        | 34 ++++++++++++
 test/js/node/http/node-http-parser.test.ts         | 27 ++++++++++
 18 files changed, 253 insertions(+), 47 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                                      reads  edits  tests
docs/runtime/nodejs-compat.mdx                                1      1     29
src/js/builtins/shell.ts                                      1      1     29
src/js/node/dns.ts                                            2      0     29
src/jsc/bindings/ZigGlobalObject.cpp                          0      0     29
src/jsc/bindings/ZigGlobalObject.h                            3      2     29
…jsc/bindings/node/crypto/JSDiffieHellmanConstructor.cpp      0      0     29
…indings/node/crypto/JSDiffieHellmanGroupConstructor.cpp      0      0     29
src/jsc/bindings/node/crypto/JSECDHConstructor.cpp            1      1     29
…jsc/bindings/node/http/JSConnectionsListConstructor.cpp      0      0     29
src/jsc/bindings/node/http/JSHTTPParserConstructor.cpp        0      0     29
src/jsc/bindings/webcore/JSCookie.cpp                         1      0     29
src/jsc/bindings/webcore/JSCookieMap.cpp                      1      1     29
test/js/bun/cookie/cookie-map.test.ts                         1      2      7
test/js/bun/shell/bunshell-instance.test.ts                   1      2      6
test/js/node/crypto/ecdh.test.ts                              0      0      9
test/js/node/crypto/node-crypto.test.js                       0      0      9
(+ 2 more files)
```

</details>

<!-- robobun:evidence:end -->

This branch has not been deployed

No deployments
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.

2 participants