Skip to content

Compare prototypes in deepStrictEqual - #29037

Open
robobun wants to merge 13 commits into
mainfrom
farm/b90f9159/deepstrictequal-prototype
Open

Compare prototypes in deepStrictEqual#29037
robobun wants to merge 13 commits into
mainfrom
farm/b90f9159/deepstrictequal-prototype

Conversation

@robobun

@robobun robobun commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #29030.

Repro

import assert from "node:assert";
assert.deepStrictEqual({}, Object.create(null));
console.log("ASSERTION PASSED (this should NOT happen)");

Node throws ERR_ASSERTION because the two objects' prototypes differ
(Object.prototype vs null). Bun prints ASSERTION PASSED.

Cause

Bun__deepEquals in src/bun.js/bindings/bindings.cpp only compared
JSObject::calculatedClassName in the strict branch. For both {} and
Object.create(null) that's "Object", so the check passed and neither
object had any own properties for the subsequent enumeration step to
differentiate.

Fix

Compare prototypes with === next to the class name check in the strict
branch — this mirrors Node's util.isDeepStrictEqual:

The prototypes of each object are compared using ===.

Non-strict deepEqual / toEqual are untouched.

Verification

bun bd test test/regression/issue/29030.test.ts — 9/9 pass.
USE_SYSTEM_BUN=1 bun test test/regression/issue/29030.test.ts — 2/9 fail,
the two {} vs Object.create(null) cases the bug actually affects.

Full test/js/node/assert/ suite (92 tests) and
test/js/bun/bun-object/deep-equals.spec.ts (36 tests) still pass.


[review] gate passed · iteration 20 · 8 files touched

fails on main (without fix)
ASAN without fix: 16 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/assert/deep-equal.test.ts "test/regression/issue/29030.test.ts"
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (1b2cbddd5)

test/regression/issue/29030.test.ts:
 7 | import assert from "node:assert";
 8 | import { isDeepStrictEqual } from "node:util";
 9 | 
10 | describe("issue #29030 - deepStrictEqual prototype check", () => {
11 |   test("{} vs Object.create(null) is not strict-deep-equal", () => {
12 |     expect(() => assert.deepStrictEqual({}, Object.create(null))).toThrow();
                                                                       ^
error: expect(received).toThrow()

Received function did not throw
Received value: undefined

      at <anonymous> (/workspace/bun/test/regression/issue/29030.test.ts:12:67)
(fail) issue #29030 - deepStrictEqual prototype check > {} vs Object.create(nu
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (085c74d0c)

test/regression/issue/29030.test.ts:
(pass) issue #29030 - deepStrictEqual prototype check > {} vs Object.create(null) is not strict-deep-equal [2.84ms]
(pass) issue #29030 - deepStrictEqual prototype check > Object.create(null) vs Object.create(null) is still strict-deep-equal [0.08ms]
(pass) issue #29030 - deepStrictEqual prototype check > {} vs {} is still strict-deep-equal [0.05ms]
(pass) issue #29030 - deepStrictEqual prototype check > two instances of the same class are strict-deep-equal [0.08ms]
(pass) issue #29030 - deepStrictEqual prototype check > instances of different classes are not strict-deep-equal [0.17ms]
(pass) issue #29030 - deepStrictEqual prototype check > objects with non-null properties still compare by prototype [0.58ms]
(pass) issue #29030 - deepStrictEqual prototype check > two null-proto objects with same own properties are strict-deep-equal [0.10ms]
(pass) issue #29030 - deepStrictEqual prototype check > a subclass instance and a base-class instance are not strict-deep-equal [0.17ms]
(pass) issue #29030 - deepStrictEqual prototype check > non-strict deepEqual still ignores prototype differences [0.08m
... (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/mechgate.xml" test/js/node/assert/deep-equal.test.ts "test/regression/issue/29030.test.ts"
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (1b2cbddd5)

test/regression/issue/29030.test.ts:
(pass) issue #29030 - deepStrictEqual prototype check > {} vs Object.create(null) is not strict-deep-equal [102.01ms]
(pass) issue #29030 - deepStrictEqual prototype check > Object.create(null) vs Object.create(null) is still strict-deep-equal [3.75ms]
(pass) issue #29030 - deepStrictEqual prototype check > {} vs {} is still strict-deep-equal [3.01ms]
(pass) issue #29030 - deepStrictEqual prototype check > two instances of the same class are strict-deep-equal [5.98ms]
(pass) issue #29030 - deepStrictEqual prototype check > instances of different classes are not strict-deep-equal [11.66ms]
(pass) issue #29030 - deepStrictEqual prototype check 
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 697ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/120] gen cpp.rs (cppbind)
[2/120] gen BunObject.lut.h
Generating /workspace/bun/build/release/codegen/BunObject.lut.h from /workspace/bun/src/jsc/bindings/BunObject.cpp
[3/120] gen JS modules (bundle-modules)
Preprocess modules (6609ms)
Bundle modules (48ms)
Postprocesss modules (31ms)
Bundle Functions (727ms)
Generate Code (94ms)

[7.52s] Bundled "src/js" for production
  1912 kb
  162 internal modules
  12 native modules
  90 internal functions across 19 files
[3/120] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: component 
... (truncated)
diff hotspot
src/js/node/assert.ts                      |  19 ++--
 src/jsc/bindings/BunObject.cpp             |  15 +++-
 src/jsc/bindings/bindings.cpp              |  78 +++++++++++++----
 src/jsc/bindings/headers-handwritten.h     |   2 +-
 test/expectations.txt                      |   6 ++
 test/js/bun/bun-object/deep-equals.spec.ts |   2 +-
 test/js/node/assert/deep-equal.test.ts     |   5 --
 test/regression/issue/29030.test.ts        | 134 +++++++++++++++++++++++++++++
 8 files changed, 227 insertions(+), 34 deletions(-)

gate history · 2 passed · 0 rejected · iteration 20

evidence per changed file
file                                        reads  edits  tests
src/js/node/assert.ts                           3      6     47
src/jsc/bindings/BunObject.cpp                  1      1     46
src/jsc/bindings/bindings.cpp                   7      6     47
src/jsc/bindings/headers-handwritten.h          1      1     46
test/expectations.txt                           3      4     57
test/js/bun/bun-object/deep-equals.spec.ts      1      1     50
test/js/node/assert/deep-equal.test.ts          2      3      0
test/regression/issue/29030.test.ts             7      8     46

root cause · written by the author bot

The bug was that Bun's deep strict equality comparison did not compare the prototypes of the objects being checked, so values with different prototypes, such as a plain object literal and an object created with Object.create(null), were incorrectly reported as strictly deep equal. Node's assert.deepStrictEqual and util.isDeepStrictEqual require prototype identity as part of strict comparison, so this divergence caused assertions that should throw to pass silently. The fix adds a prototype identity check to the strict-mode deepEquals path, which also resolves several previously documented st…

@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Added an early prototype-identity check to the strict branch of Bun__deepEquals so objects with different prototypes are treated as not deeply strict-equal; also added regression tests covering strict and non-strict deep-equality behavior across prototype scenarios.

Changes

Cohort / File(s) Summary
Strict Prototype Checking
src/bun.js/bindings/bindings.cpp
Moved temporary object bindings earlier and inserted an early prototype identity check in the isStrict path of Bun__deepEquals, returning false when prototypes differ before special-object handling and array/type dispatch.
Regression Tests
test/regression/issue/29030.test.ts
Added tests asserting assert.deepStrictEqual and node:util.isDeepStrictEqual fail for differing prototypes (e.g., {} vs Object.create(null), different classes, subclass vs base, built-ins) and that assert.deepEqual remains permissive.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding prototype comparison to deepStrictEqual, which is the core fix in this PR.
Linked Issues check ✅ Passed The PR successfully implements the required fix: comparing object prototypes with === in the strict deep-equality path, addressing the core requirement from issue #29030.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objective: prototype comparison in deepStrictEqual strict path and a regression test, with non-strict equality behavior intentionally unchanged.
Description check ✅ Passed The PR description clearly explains the bug, fix, and verification, even though it uses different headings than the repository template.

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

@robobun

robobun commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 7:24 AM PT - Jul 12th, 2026

@robobun, your commit 1b2cbdd has 3 failures in Build #72227 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 29037

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

bun-29037 --bun

Comment thread src/bun.js/bindings/bindings.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

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

Inline comments:
In `@test/regression/issue/29030.test.ts`:
- Around line 12-13: Replace the overly specific matcher usage that asserts a
particular error class with the preferred bare matcher: change expectation calls
that use expect(() =>
assert.deepStrictEqual(...)).toThrow(assert.AssertionError) to use plain
expect(() => assert.deepStrictEqual(...)).toThrow() so the tests only assert
that the call throws; update the two occurrences shown (calls to
assert.deepStrictEqual) and apply the same change to the other listed assertion
checks that currently pass assert.AssertionError to .toThrow().
🪄 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: 5fad3456-0bfa-438c-a19c-f038a1a1f1d6

📥 Commits

Reviewing files that changed from the base of the PR and between 1afabdd and e79ea8e.

📒 Files selected for processing (2)
  • src/bun.js/bindings/bindings.cpp
  • test/regression/issue/29030.test.ts

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

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the prototype check is now correctly placed before and the array branch, addressing all the gaps I flagged earlier.

Extended reasoning...

Overview

The PR adds a prototype () comparison to in strict mode, fixing incorrectly passing. The final commit (e79ea8e) moves the check to before and the array branch dispatch, so it applies to plain objects, array subclasses, Map/Set/Error/Date/RegExp subclasses, and every other object type. Two new test files cover the original bug and the subclass regressions.

Security risks

None. This is a correctness fix for an equality comparison utility with no auth, crypto, or permission implications.

Level of scrutiny

Low. The change is mechanically simple: add a guard block with two calls (both properly wrapped in ) and move two variable declarations. The logic precisely mirrors Node.js behavior and is validated by comprehensive regression tests.

Other factors

The only bug flagged by the hunting system — missing after the second call — is explicitly pre-existing and not introduced or worsened by this PR. The author responded promptly to my earlier review and the follow-up commit fully addresses the concerns raised.

Comment thread src/jsc/bindings/bindings.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

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

Inline comments:
In `@test/regression/issue/29030.test.ts`:
- Around line 65-66: Update the test comment to remove the incorrect reference
to util.isDeepEqual and mention only assert.deepEqual: replace the existing
comment that references "util.isDeepEqual" with a concise line such as "The
Node.js loose `assert.deepEqual` does NOT check prototypes. Verify we didn't
regress the loose path." so the comment references only assert.deepEqual and, if
needed, the correct API name util.isDeepStrictEqual when discussing strict
equality.
🪄 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: dada59be-fbfb-4005-b317-ef0789031e97

📥 Commits

Reviewing files that changed from the base of the PR and between e79ea8e and 3c80170.

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

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

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior feedback addressed; the prototype check is correctly positioned before and the array branch, covering all object types.

Extended reasoning...

Overview

This PR adds a prototype identity check () to in the strict comparison path, fixing incorrectly passing. Two files changed: (the fix) and (regression tests). The change is small and focused.

What Was Addressed Since My Previous Reviews

My first comment flagged that the prototype check was placed after and the array branch, leaving subclasses of Array/Map/Set/Error/Date/RegExp unguarded. This was fixed in e79ea8e: the check is now placed before the calls and before the array-branch dispatch, so it applies to every object type. My second comment noted a pre-existing missing after the second call; that is pre-existing and outside the scope of this fix.

Security Risks

None. The change is a comparison-semantics fix with no auth, crypto, or permissions involvement.

Level of Scrutiny

The fix is a targeted, well-understood change to a non-security-critical path. The regression test suite covers plain objects, null-prototype objects, class instances, and subclasses of Array/Map/Set/Error — all the edge cases that previously bypassed the check.

Other Factors

The remaining reported bug (specialObjectsDequal skipping own-property checks for Date/RegExp/Map/Set etc.) is explicitly pre-existing and not introduced or worsened by this PR. All reviewer feedback (prototype check placement, bare style) has been addressed.

Comment thread src/jsc/bindings/bindings.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.

LGTM — all prior concerns addressed; the prototype check is correctly placed before all type dispatch.

Extended reasoning...

Overview

This PR fixes issue #29030 by adding a prototype-identity check to the strict branch of Bun__deepEquals in src/bun.js/bindings/bindings.cpp. It also adds a comprehensive regression test suite in test/regression/issue/29030.test.ts covering plain objects, class instances, array subclasses, and Map/Set/Error subclasses.

Correctness of the fix

The prototype check is placed at the top of the object-comparison block, before specialObjectsDequal and before the array-branch dispatch — exactly where Node.js performs its prototype check. This ensures the guard applies uniformly to all object types: plain objects, arrays, Maps, Sets, Dates, RegExps, Errors, and their subclasses. Exception handling uses the correct RETURN_IF_EXCEPTION(scope, false) guards.

Security risks

None. This is a behavioral correctness fix to a deep-equality utility. No auth, crypto, or permission code is touched.

Level of scrutiny

The change is small (19 lines of C++), well-scoped, and follows JSC conventions exactly. The pre-existing issues noted in prior inline comments (missing guard after the second specialObjectsDequal call, and content-only short-circuiting for Date/RegExp/etc.) predate this PR and are separate concerns for separate issues.

Other factors

All prior review comments — including moving the check above the array/special-type branches, switching to bare .toThrow(), and fixing the util.isDeepEqual comment — have been addressed in subsequent commits. All inline comments are resolved.

@robobun
robobun force-pushed the farm/b90f9159/deepstrictequal-prototype branch from 89d83d6 to 0b6cd64 Compare May 6, 2026 05:06

@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 fix looks correct and all earlier feedback is addressed. Deferring to a human since this changes strict-equality semantics in Bun__deepEquals, which also backs expect().toStrictEqual() in bun:test (not just node:assert), so it's worth a maintainer sanity-checking the behavior change.

Extended reasoning...

Overview

Adds a prototype-identity check (getPrototype + ===) to the isStrict branch of Bun__deepEquals in src/jsc/bindings/bindings.cpp, placed before specialObjectsDequal and the array branch so it covers plain objects, arrays, Map/Set/Error/Date/RegExp/ArrayBuffer and their subclasses. Adds a 14-case regression test at test/regression/issue/29030.test.ts. Non-strict (deepEqual/toEqual) is untouched via if constexpr (isStrict).

Security risks

None. No auth, crypto, I/O, or untrusted input handling. getPrototype can invoke a Proxy getPrototypeOf trap, but both calls are guarded with RETURN_IF_EXCEPTION.

Level of scrutiny

Moderate-to-high. Bun__deepEquals is a core, performance-sensitive path that backs assert.deepStrictEqual, util.isDeepStrictEqual, and Bun's own expect().toStrictEqual(). The change is semantically correct (matches both Node's documented behavior and Jest's toStrictEqual), but it tightens strict-equality and could surface as new failures in downstream user test suites that previously passed. That's a user-visible behavior change in the test runner, which I'd rather a maintainer sign off on than auto-approve.

Other factors

  • All three rounds of my earlier inline feedback were addressed: the prototype check was moved above specialObjectsDequal/the array branch, and the two remaining items (missing RETURN_IF_EXCEPTION after the second specialObjectsDequal call; own-property enumeration bypass for Date/RegExp/Map/Set/etc.) were correctly identified as pre-existing and deferred to a follow-up.
  • CodeRabbit nits (bare .toThrow(), stale util.isDeepEqual comment) are resolved.
  • Asymmetric matchers are handled at lines 660–682, before the new check, so expect.anything() etc. are unaffected.
  • Regression tests cover both directions, same-prototype positive cases, the loose path, and Array/Map/Set/Error subclasses.

Comment thread src/jsc/bindings/bindings.cpp Outdated
@robobun
robobun force-pushed the farm/b90f9159/deepstrictequal-prototype branch from 247bbeb to 17c277c Compare May 6, 2026 07:43
Comment thread src/jsc/bindings/bindings.cpp Outdated
@robobun
robobun requested a review from Jarred-Sumner as a code owner May 6, 2026 08:30
Comment thread test/expectations.txt Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread test/regression/issue/29030.test.ts
@robobun
robobun force-pushed the farm/b90f9159/deepstrictequal-prototype branch from 022e569 to 2b43fa9 Compare June 16, 2026 22:53
Comment thread src/js/node/_http_outgoing.ts Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/js/node/_http_outgoing.ts Outdated
@robobun

robobun commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

Diff is green locally but CI cannot run: every build on this branch for the last several hours (62986, 62989, 63012, 63014, 63016) fails at agent provisioning with `Image not found: linux-*-2023-amazonlinux-with-docker-v37` (robobun.ts:2594). No test lane ever starts — build 63016 shows 1 passed / 28 waiting_failed / 0 real test failures, the single annotation being the image-not-found error. This is a fleet-wide infra issue unrelated to this diff; a ci: retrigger would just hit the same missing-image error.

Local verification on the latest commit (e0cd2f58f8):

  • test/regression/issue/29030.test.ts — 17/17 pass (ASAN debug)
  • test/js/node/assert/ — 93 pass
  • test-assert-typedarray-deepequal.js — 40 pass
  • node-http.test.ts getHeaders/headers/set-cookie — 8 pass
  • partialDeepStrictEqual regression tests (24338, 28522) pass
  • Behaviors cross-checked against Node v26.

All review threads resolved. Ready for a maintainer to re-run once the CI images are available.

Comment thread src/js/node/assert.ts
Comment thread test/regression/issue/29030.test.ts
Comment thread test/regression/issue/29030.test.ts Outdated
Comment thread src/js/node/assert.ts
@robobun
robobun force-pushed the farm/b90f9159/deepstrictequal-prototype branch from 38591ce to d3e1daf Compare June 18, 2026 16:43
@robobun

robobun commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (d3e1daf7ea). One non-trivial conflict in src/js/node/_http_outgoing.ts: main refactored OutgoingMessage from a prototype object-literal (headersSymbol + FetchHeaders.toJSON()) to OutgoingMessage.prototype.* assignments backed by kOutHeaders, and that refactor already makes getHeaders() return a null-prototype object ({ __proto__: null }) with the headers/_headers accessors delegating to it. That fully supersedes this PR's getHeaders()/get headers() null-proto edits and the cached-Object.assign/Object.create tweak, so I dropped those edits and took main's version of the file verbatim (verified Object.getPrototypeOf(res.getHeaders()) === null still holds).

The deep-equality fix itself (prototype check in Bun__deepEquals gated to assert.deepStrictEqual/util.isDeepStrictEqual/Bun.deepEquals(_,_,true), the checkPrototypes opt-out for partialDeepStrictEqual, and the regression tests) rebased cleanly. Post-rebase: 17/17 regression tests and the full test/js/node/assert/ + partial-regression suites (105) pass.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues found after the rebase and all prior threads are resolved, but this changes core deep-equality semantics (new checkPrototypes template axis threaded through Bun__deepEquals/specialObjectsDequal, an undocumented 4th Bun.deepEquals arg, and a node-parallel test moved to expected-fail), so it's worth a maintainer pass before merge.

Extended reasoning...

Overview

This PR adds prototype-identity comparison to the strict path of Bun__deepEquals so assert.deepStrictEqual({}, Object.create(null)) throws like Node. To get there it:

  • Adds a third checkPrototypes template parameter to Bun__deepEquals and specialObjectsDequal in src/jsc/bindings/bindings.cpp, threads it through every recursive call site (~15), and emits three explicit instantiations.
  • Adds an undocumented 4th argument to Bun.deepEquals in BunObject.cpp so assert.partialDeepStrictEqual can opt out of the prototype gate while keeping strict content comparison.
  • Updates the template signature in headers-handwritten.h.
  • Adds isDeepStrictEqualWithoutPrototype to src/js/node/assert.ts and switches six compareBranch call sites to it.
  • Adds test-child-process-advanced-serialization.js to test/expectations.txt as [ FAIL ] on all platforms (the new check correctly surfaces a pre-existing IPC Buffer→Uint8Array prototype-fidelity gap).
  • Adds a 17-case regression test.

The _http_outgoing.ts edits from earlier revisions were dropped during rebase since main's refactor already returns null-proto from getHeaders().

Security risks

None identified. No auth, crypto, filesystem, or network surface is touched; the prototype check only tightens an equality comparison and cannot be used to bypass anything.

Level of scrutiny

Medium-high. Bun__deepEquals is the engine behind assert.deepStrictEqual, util.isDeepStrictEqual, the public Bun.deepEquals API, and (via a different instantiation) expect().toStrictEqual(). The change is a user-visible behavior change to a public API, adds a new template axis to hot-path C++ comparison code, introduces an internal-but-publicly-reachable 4th argument to Bun.deepEquals, and quarantines a previously-passing node-parallel test. None of that is mechanical.

Other factors

  • The PR has been through ~10 rounds of bot review; every thread is resolved and the current bug-hunter pass found nothing.
  • The !enableAsymmetricMatchers gate keeps expect().toStrictEqual() unchanged, which was the right call given the cross-realm/Jest-compat fallout seen in earlier CI runs — but that design choice (Node-strict semantics for Bun.deepEquals(_,_,true) vs. Jest-loose for toStrictEqual) is worth a human ack.
  • Marking test-child-process-advanced-serialization.js as expected-fail everywhere is reasonable (the gap it exposes is pre-existing), but a maintainer should confirm that's the preferred trade-off vs. fixing IPC Buffer fidelity here.
  • CI was infra-blocked on the last several builds; the latest build (#63342) is in progress per robobun.

@robobun

robobun commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the pass. Flagging the two intentional design choices for a maintainer, since both came out of earlier CI fallout:

  1. Node-strict for Bun.deepEquals(_,_,true) / assert.deepStrictEqual / util.isDeepStrictEqual, unchanged for expect().toStrictEqual(). The prototype check is gated on !enableAsymmetricMatchers, so toStrictEqual keeps its existing calculatedClassName type guard. Removing that gate broke cross-realm objects (vm runInContext Sets) and Buffer-vs-Uint8Array content comparisons in the bun:test suite, so scoping the stricter semantics to the node:assert/util surface (which is what assert.deepStrictEqual ignores prototype differences #29030 is about) was deliberate.

  2. test-child-process-advanced-serialization.js marked expected-fail. The new check correctly surfaces a pre-existing gap: advanced-serialization IPC round-trips a Buffer back as a plain Uint8Array, so deepStrictEqual(received, {buffer: Buffer}) now fails. That is a separate structured-clone-fidelity bug; I quarantined the test rather than widen this PR into the IPC layer. Happy to file/fix it as a follow-up if preferred.

The checkPrototypes template axis + internal 4th Bun.deepEquals arg exist solely so assert.partialDeepStrictEqual keeps strict-content-without-prototype semantics (Node parity verified for top-level, object-property, and array/set-element positions). Behavior cross-checked against Node v26.

@robobun

robobun commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

CI status: the only hard failure is windows-x64-baseline-verify-baseline — the Nehalem baseline instruction scan rejects strcspn [RTM] (Restricted Transactional Memory / Intel TSX instructions). It reproduced identically on both the rebased build (#63342) and an empty ci: retrigger (#63344), so it is deterministic, not flake.

This is not reachable from this PR's diff:

  • The diff touches src/jsc/bindings/{bindings.cpp,BunObject.cpp}, headers-handwritten.h, src/js/node/assert.ts, test/expectations.txt, and the regression test. None reference strcspn or any string-scan primitive, and none change build/link/target config.
  • strcspn appears only in vendored src/jsc/bindings/sqlite/sqlite3.c; the RTM instructions come from the baseline CRT strcspn it links, determined by the Windows baseline toolchain, not by source.
  • Recent main builds (#63224–#63291) pass this check, so it looks like either a just-deployed scanner/CRT change now catching a latent baseline-build issue, or LTO binary-layout perturbation surfacing it. Either way the resolution is on the CI side (baseline instruction allowlist or the Windows baseline CRT/SQLite build), not in this change.

The deep-equality fix is green everywhere I can verify locally: 17/17 regression tests, the full test/js/node/assert/ suite (93) + partial-regression suites, all under ASAN debug, with behavior cross-checked against Node v26. All review threads resolved. Ready for a maintainer to take it from here (the baseline-verify check needs a CI-side fix before this can go green).

robobun added 12 commits July 12, 2026 12:25
Fixes #29030. Node's assert.deepStrictEqual differentiates {} from
Object.create(null) because their prototypes differ (Object.prototype
vs null). Bun's Bun__deepEquals only compared the calculated class
name, which is "Object" for both, so the mismatch slipped through.

Add a prototype === prototype check alongside the class name check
in the strict branch.
The prototype check only ran inside the generic JSObject branch, which
was unreachable for arrays (their own branch returns first) and for
types handled by specialObjectsDequal (Map, Set, Date, RegExp, Error,
ArrayBuffer). Move it up so every object path sees it.

class Sub extends Array {}        // Sub.prototype !== Array.prototype
assert.deepStrictEqual(new Sub(), []);       // now throws

class MyMap extends Map {}        // MyMap.prototype !== Map.prototype
assert.deepStrictEqual(new MyMap(), new Map()); // now throws
The fix in 074d466 added a prototype-identity check to
Bun__deepEquals' isStrict path. Bun__deepEquals is instantiated with
isStrict=true by two callers:

  - node:assert.deepStrictEqual / util.isDeepStrictEqual — where this
    matches Node's documented behavior (see issue #29030).
  - bun:test's expect().toStrictEqual() via jestStrictDeepEquals →
    Bun__deepEquals<true, true> — where the stricter check broke
    existing tests that compared Buffer against Uint8Array by content,
    cross-realm Sets from vm.runInContext, null-prototype vs plain
    objects returned by various Bun APIs, etc.

Issue #29030 is scoped to node:assert; tighten only that path by
gating the new check on !enableAsymmetricMatchers. bun:test's
toStrictEqual keeps its existing calculatedClassName-based type guard,
matching prior Bun behavior, and the earlier valkey/zstd test patches
are no longer needed (they've been reverted in this commit).
The `<true, false>` template instantiation covers three user-facing
APIs, not two: `node:assert.deepStrictEqual`,
`node:util.isDeepStrictEqual`, and the public `Bun.deepEquals(a, b,
true)` (see functionBunDeepEquals in BunObject.cpp). Spell out all
three in the comment so it doesn't read as if `Bun.deepEquals` is
unaffected.
…ent)

The prototype-identity check added for assert.deepStrictEqual also
fired for assert.partialDeepStrictEqual, because compareBranch
(src/js/node/assert.ts) delegates ArrayBuffer/ArrayBufferView, Buffer,
Error/RegExp/Date comparisons straight to Bun.deepEquals(_, _, true).
Node's partial mode does not enforce prototype identity for these, so
partialDeepStrictEqual(Buffer.from([1,2]), new Uint8Array([1,2])) and
partialDeepStrictEqual(new (class extends Error{})('x'), new Error('x'))
regressed from pass to throw.

Add a checkPrototypes template parameter to Bun__deepEquals (default
true, threaded through recursion and specialObjectsDequal). Bun.deepEquals
accepts an optional internal 4th argument to disable it. compareBranch's
leaf-type delegations now call it with the check off, restoring Node's
partial semantics while deepStrictEqual keeps the prototype check.
…ent loops

05c8f54 switched compareBranch's three direct-return delegations to
isDeepStrictEqualWithoutPrototype, but the Set-element loop and the two
Array-multiset loops still called plain isDeepStrictEqual, which
enforces the new prototype gate. So partialDeepStrictEqual with a Buffer
vs Uint8Array (or any content-equal, prototype-distinct value) as an
array/set *element* still threw, diverging from Node.

Switch those three element-matching comparisons to the prototype-skipping
variant and cover arrays-of-buffers and sets-of-buffers in the regression
test. Verified parity with Node v26.
Match the file's convention (bare .toThrow() for node:assert regression
tests) instead of pinning assert.AssertionError.
The deepStrictEqual prototype check (issue #29030) correctly rejects
the Buffer message round-tripped through advanced-serialization IPC,
which comes back as a plain Uint8Array. The script is an unmodifiable
upstream Node test, so a whole-file expectations entry is the only
available mechanism (same shape as the test-stream-wrap entries).
Unquarantine once IPC preserves the Buffer prototype.
main's deep-equal.test.ts documents Bun's strict-mode gaps as
test.failing. The prototype check in this PR fixes five of them
(x2 surfaces: assert.deepStrictEqual and util.isDeepStrictEqual):

- a null-prototype object and {}
- instances of two distinct anonymous classes
- instances of two distinct identically named classes
- an Array subclass instance and an array
- a Buffer and a Uint8Array with the same bytes

Drop their strictBug markers so they run as regular tests. The
remaining strictBug entries (extra-own-property on Date/RegExp/Map/
typed arrays/arrays, WeakMap/WeakSet) are the pre-existing
specialObjectsDequal content-only gaps this PR does not address.
@robobun
robobun force-pushed the farm/b90f9159/deepstrictequal-prototype branch from b112417 to 085c74d Compare July 12, 2026 12:35
@robobun

robobun commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (085c74d0c3). Two notable resolutions:

  1. test/expectations.txt — main restructured the file (test: stop quarantining whole files for one broken case #33952) with a policy header discouraging whole-file quarantines for single broken cases. The test-child-process-advanced-serialization.js script is an unmodifiable upstream Node test (per test/js/node/test/parallel/CLAUDE.md), so a whole-file entry remains the only available mechanism — same shape as main's existing test-stream-wrap*.js entries. Re-added it under the vendored-node-tests section with a comment explaining the IPC Buffer→Uint8Array fidelity gap and when to unquarantine.

  2. test/js/node/assert/deep-equal.test.ts — main added a deep-equality matrix that documents Bun's strict-mode gaps as test.failing. This PR's prototype check fixes five of them (null-proto vs {}, two distinct anonymous classes, two distinct identically-named classes, Array subclass vs array, Buffer vs Uint8Array — each on both assert.deepStrictEqual and util.isDeepStrictEqual), so those ten tests flipped to failing-but-passing. Removed their strictBug markers; the remaining markers (extra-own-property on Date/RegExp/Map/typed arrays, WeakMap/WeakSet) are the pre-existing specialObjectsDequal content-only gaps this PR doesn't address.

Also dropped the stale ci: retrigger empty commit. Post-rebase: 17/17 regression tests and the full assert matrix (356 tests, including the five newly-unmarked cases) pass under ASAN debug.

Same as the deep-equal.test.ts markers: main documents the
null-prototype-vs-literal gap as it.failing in deep-equals.spec.ts,
and the prototype check in this PR makes it pass, which the runner
reports as a failure. Drop the marker so it runs as a regular test.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues on the latest rebase. Deferring to a maintainer for the two design decisions you already flagged: (1) gating the prototype check on !enableAsymmetricMatchers so Bun.deepEquals(_,_,true) and expect().toStrictEqual() now diverge on plain objects, and (2) whole-file quarantine of test-child-process-advanced-serialization.js. Also worth a maintainer look: the new internal 4th arg to Bun.deepEquals and the checkPrototypes template axis threaded through recursion.

What was reviewed:

  • Prototype-check placement (runs before specialObjectsDequal and the array branch, so Array/Map/Set/Error subclasses are covered) and its if constexpr gating across all four template instantiations.
  • getPrototype() exception handling — both calls have RETURN_IF_EXCEPTION guards; Proxy getPrototypeOf traps that throw propagate correctly.
  • compareBranch call sites in assert.ts — all six isDeepStrictEqual delegations now use the without-prototype variant, so partialDeepStrictEqual keeps pre-PR content-only semantics for Buffer/Uint8Array in top-level, nested, array-element, and set-element positions.
  • The five strictBug markers removed from deep-equal.test.ts each correspond to a case the new check fixes; remaining markers are the unrelated own-property/WeakMap gaps.
Extended reasoning...

Overview

This PR adds prototype-identity comparison to Bun__deepEquals's strict path, fixing #29030 (assert.deepStrictEqual({}, Object.create(null)) incorrectly passing). It touches:

  • src/jsc/bindings/bindings.cpp — new prototype check gated on isStrict && !enableAsymmetricMatchers && checkPrototypes, placed before specialObjectsDequal and the array branch; new checkPrototypes template parameter threaded through ~15 recursive call sites and specialObjectsDequal.
  • src/jsc/bindings/BunObject.cppBun.deepEquals gains an internal 4th boolean arg to select checkPrototypes=false.
  • src/jsc/bindings/headers-handwritten.h — template signature update.
  • src/js/node/assert.tscompareBranch (backing partialDeepStrictEqual) switched to isDeepStrictEqualWithoutPrototype at 6 call sites.
  • test/expectations.txt — quarantines test-child-process-advanced-serialization.js (IPC Buffer→Uint8Array fidelity gap correctly surfaced by the new check).
  • Tests: new 134-line regression suite, five strictBug markers removed from the deep-equal matrix, one it.failing flipped in deep-equals.spec.ts.

Security risks

None identified. This is a comparison-semantics change; no auth, crypto, permissions, or untrusted-input parsing. getPrototype() can invoke Proxy traps, but both calls are guarded with RETURN_IF_EXCEPTION and the check runs before any raw pointers into growable state are held.

Level of scrutiny

High. Bun__deepEquals is the core comparison engine backing assert.deepStrictEqual, util.isDeepStrictEqual, Bun.deepEquals, and bun:test's toStrictEqual/toEqual. A behavior change here fans out to every user of those APIs, and this PR already went through 19 iterations precisely because early revisions broke unrelated test suites (vm cross-realm, jsonl, http headers, IPC). The final shape — a third template axis plus an internal API argument — is not mechanical.

Other factors

  • Author explicitly requested maintainer sign-off (comment 2026-06-18) on the !enableAsymmetricMatchers gate and the test quarantine. That alone rules out bot approval.
  • Behavior divergence introduced: Bun.deepEquals({}, Object.create(null), true) now returns false while expect({}).toStrictEqual(Object.create(null)) still passes. Docs claiming they're equivalent are now stale (author acknowledged, chose to leave for follow-up).
  • New internal API surface: 4th arg to Bun.deepEquals is undocumented/untyped. Reasonable choice, but a maintainer should confirm they're OK with it vs. a separate internal binding.
  • Whole-file test quarantine: expectations.txt policy header discourages this; author's justification (unmodifiable upstream script) is sound and matches existing test-stream-wrap* entries, but it's the kind of trade-off a maintainer should ratify.
  • All 12+ prior inline review threads (mine and CodeRabbit's) are resolved. Test coverage on the new paths is good — the regression file exercises deepStrictEqual, isDeepStrictEqual, deepEqual, and partialDeepStrictEqual across plain objects, subclasses of Array/Map/Set/Error, and container-element positions.
  • No bugs found by the bug-hunting system on the current revision.

@robobun

robobun commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author

Consolidating the maintainer decision points in one place, since review is otherwise clean (all threads resolved, no open findings):

  1. Scope of the prototype check. It applies to assert.deepStrictEqual, util.isDeepStrictEqual, and Bun.deepEquals(a, b, true) (the <true, false> instantiation). expect().toStrictEqual() keeps its existing calculatedClassName guard — removing the gate broke cross-realm objects (vm runInContext) and Buffer-vs-Uint8Array content comparisons across the bun:test suite. Consequence: Bun.deepEquals({}, Object.create(null), true) is false while expect({}).toStrictEqual(Object.create(null)) still passes, so the docs equating the two are now approximate.

  2. Internal 4th argument to Bun.deepEquals + checkPrototypes template axis. Exists solely so assert.partialDeepStrictEqual keeps strict-content-without-prototype semantics (Node parity verified for top-level, object-property, and array/set-element positions against Node v26). Undocumented and untyped on purpose; happy to move it to a separate internal binding if preferred.

  3. Whole-file quarantine of test-child-process-advanced-serialization.js. The check correctly surfaces that advanced-serialization IPC returns Buffer as plain Uint8Array. The script is unmodifiable upstream (per test/js/node/test/parallel/CLAUDE.md), so an expectations entry is the only mechanism — same shape as the existing test-stream-wrap* entries. Unquarantine once IPC preserves the Buffer prototype.

Current CI run on 1b2cbddd55 has zero failures so far. Main's own deep-equal.test.ts matrix documents the five strict-mode gaps this PR fixes (previously test.failing, now unmarked and passing).

@robobun

robobun commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author

Re: test-stream-iter-readable-interop.js failing in build 72227 (flagged as a main break).

It is not broken on main; it passes on current HEAD. The failure is caused by this PR's (correct) prototype comparison in deepStrictEqual.

Root cause: the test does assert.deepStrictEqual(await bytes(from(readable)), new Uint8Array(data)). Both Node and Bun's concatBytes have an identical single-chunk fast path that returns the chunk as-is when byteOffset === 0 && byteLength === buffer.byteLength. In Node, Buffer.from('binary data here') is pool-backed (65 KB backing buffer), so the fast path does not trigger and concatBytes copies into a plain Uint8Array. In Bun, Buffer.from is not pooled, so the fast path returns the Buffer directly, and with prototype comparison deepStrictEqual(Buffer, Uint8Array) now rejects.

Node shows the same behavior when the input is unpooled:

$ node --experimental-stream-iter -e '...Buffer.allocUnsafeSlow(4)...'
result constructor: Buffer
deepStrictEqual -> AssertionError (Buffer vs Uint8Array)

So stream/iter is byte-for-byte matching Node here; the upstream test passes in Node only because of Buffer pooling. This PR will need to add test-stream-iter-readable-interop.js to test/expectations.txt (it already edits that file) with a note pointing at the Buffer-pooling difference.

dylan-conway added a commit that referenced this pull request Jul 24, 2026
…s, regexp highlighting, vm namespace, tty.WriteStream + deepEquals fixes (test-util 70% → 83%) (#34434)

## What

Brings `node:util` closer to **node v26.3.0** and fixes several
`Bun.deepEquals` bugs the newly ported tests exposed.

Fixes #33074
Fixes #25736
Fixes #20129

### `test-util-*` compatibility vs node v26.3.0

| | before | after |
|---|---|---|
| passing | 21 / 30 (70%) | **25 / 30 (83%)** |

**+3 tests added** (verbatim from v26.3.0), **+1 updated**, plus new
coverage in bun's own deep-equality matrix and util tests.

Beyond `node:util`, this also fixes two bugs the porting turned up:
`tty.WriteStream` throwing on a read-only fd, and vm module namespaces
having a non-null prototype.

> Overlaps four open PRs (#30985, #33080, #32872, #29037) — see [this
comment](#34434 (comment)).
Happy to drop the overlapping areas or close in favour of them.

## Fixes

**`util.styleText` was still on the pre-v26 API.** Ported node v26.3.0's
version: hex/RGB colors (`#RGB`/`#RRGGBB`), the `none` format, nested
close-code handling, and `{ validateStream, stream }` — colour is
suppressed when the target stream isn't a TTY.

**New APIs**
- `util.getCallSites(frameCount, { sourceMap })` — captures through a
private `prepareStackTrace`, so a user-installed
`Error.prepareStackTrace` is never invoked and `Error.stackTraceLimit`
doesn't bound the result. `CallSite` gains `getScriptId`. Column numbers
and the `column` alias verified against the node v26.3.0 binary.
- `util.convertProcessSignalToExitCode(signal)`
- `util.isDeepStrictEqual(a, b, skipPrototype)` — node v26's third
argument, threaded through `Bun__deepEquals` as a template parameter.

**`util.inspect`**
- **Regexes are syntax-highlighted** by group depth (ported v26's
`highlightRegExp`), replacing the flat `red` style — removing the
`TODO(BridgeAR): Highlight regular expressions properly` bun inherited
from node.
- **`hasBuiltInToString`** rejected *any* `Symbol.toPrimitive`, but
`Date.prototype[Symbol.toPrimitive]` is built-in, so `util.format('%s',
date)` printed the `toString` form where node prints inspect's ISO form.
Ported v26's version, which distinguishes own from inherited.
- **`extraKeys`** — ported v26's mechanism replacing `unshift(keys, …)`.
Those entries are getters, so node brackets them: `ArrayBuffer {
[Uint8Contents]: <..>, [byteLength]: 4 }`, and `showHidden` on a typed
array reports `[BYTES_PER_ELEMENT]`, `[length]`, `[byteLength]`,
`[byteOffset]`, `[buffer]`.
- **vm module namespaces get a null prototype.** A Module Namespace
Exotic Object is specified to have `[[Prototype]] = null`; bun points
the shared structure at an object carrying an `__esModule` accessor for
CJS interop. Nulled in `NodeVMModule::namespaceObject` only, so real ESM
namespaces keep the accessor.

**`Bun.deepEquals` correctness (strict mode)**
- boxed **Strings** ignored extra own properties
- boxed **Symbols/BigInts** are plain `ObjectType` in JSC and never
reached the type switch, so two *different* boxed Symbols compared equal
- the fast path fetched the right-hand property without checking
**enumerability**
- typed arrays skipped own **non-index properties** (e.g. symbols)

**Loose mode** is *not* untouched (an earlier revision of this
description wrongly said so). Three loose-visible cases moved onto
node's behaviour, and one regression was caught in review and gated
back:

| loose case | node | bun before | bun now |
|---|---|---|---|
| `Object(1n)` vs `Object(2n)` | false | true | **false** |
| `Object(Symbol())` vs `{}` | false | true | **false** |
| `Object(Symbol('a'))` vs `Object(Symbol('a'))` | false | true |
**false** |
| enumerable sym vs non-enumerable | true | true | **true** |

## Perf

Both strict fall-throughs are gated on the structure actually carrying
named properties. Elements and characters are synthesized by
`getOwnPropertySlot` rather than stored in the structure, so an
unguarded fall-through lands in the index-enumerating slow path.
Measured before the guard: `new String('a'.repeat(100000))` took
**2,004,127µs/op** and a 1KB `Uint8Array` **4264µs/op**, both linear.
With the guard both are flat (~5µs).

## Verification

- A/B against a clean build of the same base commit, using the runner's
own dispatch (`bun test` for files containing `node:test`, `bun run`
otherwise).
- Every expectation checked against a **real node v26.3.0 binary**, not
read off the source. Two review findings (a `TypeError` from a primed
style cache, and an empty regexp palette) **reproduce identically on
node** and are deliberately left as faithful ports.
- Regression sweep: `test-assert-*` / `test-buffer-*` /
`test-console-*`, `buffer.test.js` (617), the 271-case deep-equality
matrix, bun's `expect` suites, and 97/97 node vm tests. `oxlint` clean.

## Notes for reviewers

**`test-util-styletext{,-hex}.js` need the real environment.** They
assert styleText's own colour decisions against a TTY. Deleting the
runner's forced `FORCE_COLOR=0`/`NO_COLOR=1` wasn't enough — `spawnBun`
sets `FORCE_COLOR=1` in its own base env, which reached the child. The
three colour variables are now set to `undefined` (which `child_process`
drops) for those two files. With no colour variables set, bun matches
node on all eight TTY cases.

## Known gaps (deliberate)

- **`test-util-inspect.js`** — `--expose-internals`; needs
`internalBinding('js_stream').JSStream`, whose `_externalStream` must be
a real napi external. It gets ~145 assertions in before that line.
- **`test-util-format.js`** — one assertion: `[Foo: null prototype]`. V8
keeps the constructor on the object's map; JSC has no equivalent. I
tried recovering it from JSC's structure transition chain —
`previousID()` yields nothing usable after a prototype change (verified
across `setPrototypeOf`, `__proto__ =`, and `Reflect.setPrototypeOf`),
so this needs a WebKit-side change. Everything else in the file passes.
- **`test-util-isDeepStrictEqual.js`** — needs `Buffer` ≠ `Uint8Array`
in strict mode. That's correct (node rejects them, and the matrix pins
bun's answer as a known bug), but enabling it turns
`test-child-process-advanced-serialization.js` red: bun's advanced IPC
is backed by `SerializedScriptValue`, which downgrades `Buffer` to
`Uint8Array`, so that test currently passes only because two bugs cancel
out:
  ```
  bun:  received.buffer → Uint8Array, isBuffer: false
  node: received.buffer → Buffer,     isBuffer: true
  ```
Preserving `Buffer` through structured clone belongs in its own change.
- **`test-util-getcallsites.js`** — asserts `getCallSites().length > 1`
at module scope, which holds in node only because node wraps modules in
JS functions (8 frames vs bun's 1; bun's loader is native). Not
portable. `getCallSites` keeps real coverage in
`test-util-getcallsites-preparestacktrace.js`.
- `getCallSites`'s `sourceMap` option is validated but not applied.

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

---

**no test proof** · iteration 11 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js

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

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Status check against current main (165dc9f), after #34434 and #34660 merged.

Covered by main:

Not covered by main:

So the deepStrictEqual change in the title has landed; what remains here is only the Bun.deepEquals strict-mode behavior change, which overlaps #37776. Leaving this open for a maintainer to decide whether that part is wanted; otherwise it can be closed in favor of #34660 and #37776.

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.

assert.deepStrictEqual ignores prototype differences

1 participant