Skip to content

WebKit: fix Set iteration corrupting large-integer keys to INT32_MIN (#30757) - #30760

Closed
robobun wants to merge 1 commit into
mainfrom
farm/178d285e/fix-set-iteration-int32-corruption
Closed

WebKit: fix Set iteration corrupting large-integer keys to INT32_MIN (#30757)#30760
robobun wants to merge 1 commit into
mainfrom
farm/178d285e/fix-set-iteration-int32-corruption

Conversation

@robobun

@robobun robobun commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Fixes #30757.

Root cause

WebKit HashMapHelper.h::normalizeMapKey did int i = static_cast<int>(d) before its i == d round-trip check. Out-of-range static_cast<int>(double) is UB (LLVM fptosi → poison); under LTO the optimizer folded the branch and the integer path was always taken, normalizing every oversized integer-valued double key to jsNumber(INT32_MIN). Non-LTO builds got away with it because hardware cvttsd2si returns INT32_MIN on overflow and the compare filtered the bad case.

[...new Set([1751241600000])][0]       // got: -2147483648, want: 1751241600000
new Set([1751241600000]).has(-1 << 31) // true (spurious)

.has(originalValue) still worked because lookup hashed the stored (corrupted) normalized key against itself. Tagged Int32s, fractional doubles, strings — all unaffected.

Fix

Fixed in WebKit by replacing the UB cast with truncateDoubleToInt32 (Source/WTF/wtf/MathExtras.h — hardware cvttsd2si on x86_64, fcvtzs on arm64), which returns INT32_MIN on overflow so the subsequent i == d filter works as intended. Picked up in bun when main bumped WebKit to 0d85951a.

Test

test/regression/issue/30757.test.ts — three concurrent tests spawning subprocesses to exercise the real iteration path:

  • Set round-trip preserves every value in the failure table (timestamp, ±(INT32_MAX+1), 2^32, MAX_SAFE_INTEGER, ±Infinity (identity-checked in-subprocess), NaN)
  • s.size stays 4 with mixed small + oversized keys and s.has(-2147483648) no longer spuriously matches oversized keys
  • Same shape for Map (same normalizer)

Coverage note: the Set/Map corruption only manifests in LTO release builds (UB-under-LTO), so bun bd passes with or without the WebKit pin. Real coverage lands in the Linux release-LTO CI lane, which exercised the failing path against the pre-fix WebKit and now passes against 0d85951a.

@robobun

robobun commented May 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 3:19 AM PT - May 23rd, 2026

@robobun, your commit 3314ce3 has 1 failures in Build #57190 (All Failures):

  • 📦 Binary size — 6 over 0.50 MB
  • targetthis build canary: main #57196
    sizeΔ
    bun-darwin-aarch6454.84 MB54.84 MB-0.0 KB
    bun-darwin-x6458.87 MB58.88 MB-8.0 KB
    bun-linux-aarch6480.86 MB69.74 MB+11.12 MB
    bun-linux-x6481.81 MB70.67 MB+11.14 MB
    bun-linux-x64-baseline80.89 MB69.75 MB+11.14 MB
    bun-linux-aarch64-musl75.67 MB64.55 MB+11.12 MB
    bun-linux-x64-musl76.88 MB65.77 MB+11.11 MB
    bun-linux-x64-musl-baseline76.20 MB65.11 MB+11.09 MB
    bun-linux-aarch64-android77.82 MB77.82 MB+0.0 KB
    bun-linux-x64-android79.74 MB79.75 MB-16.0 KB
    bun-freebsd-x6482.29 MB82.29 MB+0.0 KB
    bun-freebsd-aarch6484.30 MB84.30 MB+0.0 KB
    bun-windows-x6490.31 MB90.32 MB-6.5 KB
    bun-windows-x64-baseline89.36 MB89.37 MB-8.0 KB
    bun-windows-aarch6487.22 MB87.23 MB-3.5 KB

    Add [skip size check] to the commit message if this increase is intentional.


🧪   To try this PR locally:

bunx bun-pr 30760

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

bun-30760 --bun

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds three regression tests validating Set/Map behavior for numeric values outside the Int32 range; wraps OVERLAY_CSS with JSON.stringify in the build define for bundler JSON parsing; and adds an explicit include for assert() usage.

Changes

Set/Map numeric key regression tests

Layer / File(s) Summary
Regression tests for Set/Map Int32 overflow behavior
test/regression/issue/30757.test.ts
Three Bun test cases spawn a child Bun process and validate Set and Map iteration and lookups for oversized integer-valued doubles, NaN preservation, Infinity JSON serialization, and correct Set.has/Map.get behavior against INT32_MIN corruption targets.

CSS overlay build definition fix

Layer / File(s) Summary
OVERLAY_CSS JSON.stringify wrapping
src/codegen/bake-codegen.ts
The OVERLAY_CSS define entry is updated from css(...) to JSON.stringify(css(...)) with comments explaining that define expects JS expressions and that the Rust-rewrite bundler requires JSON-wrapped CSS strings.

C++ assert include

Layer / File(s) Summary
Include <cassert> for assert()
src/jsc/bindings/wtf-bindings.cpp
Add an explicit #include <cassert> (with comment) so assert() used in uv__tty_make_raw is not dependent on transitive includes.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix—addressing Set iteration corruption of large-integer keys to INT32_MIN—which is the primary objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description thoroughly explains the root cause, the fix applied, test coverage, and implementation details.

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


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

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/regression/issue/30757.test.ts`:
- Around line 1-24: Replace the multi-line header comment at the top of the test
(the long prose block describing bug history and root cause) with the two-line
regression-test header pattern: first the GitHub issue URL
(https://github.com/oven-sh/bun/issues/30757) and on the next line a single
brief description like "Fix normalization of large integer-valued doubles that
collapsed to INT32_MIN"; remove the extra paragraphs about normalizeMapKey,
INT32_MIN, WebKit history, and the long root-cause explanation so only the URL
and one-line summary remain.
🪄 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: 78518986-fa75-4612-a9e3-6a575458b789

📥 Commits

Reviewing files that changed from the base of the PR and between bbd3e62 and 224c1d8.

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

Comment thread test/regression/issue/30757.test.ts
Comment thread test/regression/issue/30757.test.ts Outdated
Comment thread test/regression/issue/30757.test.ts Outdated
Comment thread scripts/build/deps/webkit.ts Outdated
@robobun

robobun commented May 15, 2026

Copy link
Copy Markdown
Collaborator Author

Gate check can't mechanically verify this PR because the bug is LTO-specific: static_cast<int>(double) for an out-of-range double is UB, and the optimizer only exploits the resulting LLVM fptosi poison under -flto=full. The hardware cvttsd2si returns INT32_MIN on overflow and the i == d round-trip compare filters the bad case on every build configuration that doesn't LTO — so the test passes on debug+ASAN and on plain release, even against the unpatched WebKit. Local verification against the LTO release profile (what CI ships):

$ /workspace/bun/build/release-lto/bun --revision
1.3.14-canary.1+e4a69f11b  (WEBKIT_VERSION = preview-pr-232-b0366015)
$ /workspace/bun/build/release-lto/bun test test/regression/issue/30757.test.ts
(pass) Set iteration preserves numeric values outside Int32 range [10.47ms]
(pass) Set.has does not match the INT32_MIN corruption target after normalization [9.62ms]
(pass) Map iteration preserves numeric keys outside Int32 range [9.52ms]
 3 pass

vs. the shipped canary (same source, same WebKit pin as this PR's parent commit, same LTO, no fix):

$ /tmp/bun-canary/bun-linux-x64/bun --revision
1.3.14-canary.1+bbd3e624a
$ /tmp/bun-canary/bun-linux-x64/bun test test/regression/issue/30757.test.ts
3 fail  (all three tests see -2147483648 instead of the original values)

The Linux release-LTO Buildkite lane on this PR will exercise the same assertion and gate the fix — needs a maintainer to override the mechanical gate and merge.

@robobun
robobun force-pushed the farm/178d285e/fix-set-iteration-int32-corruption branch from e4a69f1 to 2901f25 Compare May 18, 2026 09:18
@robobun

robobun commented May 18, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased on main (WebKit bumped to 3167a44f there in the meantime — trivial conflict in scripts/build/deps/webkit.ts). Dropped the earlier preview-pr-232-b0366015 commit; CodeRabbit correctly flagged that a preview-PR tag isn't durable for sync-webkit-source.ts / process.versions.webkit.

oven-sh/WebKit#232 has been rebased onto the new WebKit main (commit b37ec334 on 3167a44f) and the preview build is running. Once it produces a fresh autobuild-preview-pr-232-* tag, or #232 merges and a regular autobuild-<sha> tag is cut, I'll push the version bump.

Comment thread src/codegen/bake-codegen.ts Outdated
@robobun
robobun force-pushed the farm/178d285e/fix-set-iteration-int32-corruption branch from 15a0aae to 785bcc3 Compare May 18, 2026 09:46
Comment thread test/regression/issue/30757.test.ts Outdated
Comment thread src/jsc/bindings/wtf-bindings.cpp Outdated
@robobun
robobun force-pushed the farm/178d285e/fix-set-iteration-int32-corruption branch from 785bcc3 to 274434d Compare May 18, 2026 10:24
Comment thread scripts/build/deps/webkit.ts Outdated
@robobun
robobun force-pushed the farm/178d285e/fix-set-iteration-int32-corruption branch from a7fdaed to 1e43bb2 Compare May 19, 2026 05:39
Comment thread test/regression/issue/30757.test.ts Outdated
@robobun
robobun force-pushed the farm/178d285e/fix-set-iteration-int32-corruption branch from 1e43bb2 to a806eb8 Compare May 19, 2026 05:56
@robobun

robobun commented May 19, 2026

Copy link
Copy Markdown
Collaborator Author

Picks up #31080 too (same normalizeMapKey UB — 2**31, ±Infinity, oversized-double Map/Set keys all collapse to INT32_MIN under LTO). The regression tests here already cover those cases via the oversized-key set and the hasCorruptionTarget check.

@robobun

robobun commented May 21, 2026

Copy link
Copy Markdown
Collaborator Author

Picks up #31171 as well — same normalizeMapKey UB reported fresh with a production timestamp-keyed Map repro (Date.now()-range keys all collapsing into one bucket). The existing regression test here (test/regression/issue/30757.test.ts) already covers the timestamp / 2**32 / 2**40 + has(2**41) cases from that report, so no new coverage needed.

@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 — test-only addition, all prior feedback addressed, and the WebKit fix already landed on main via the 0d85951a bump.

Extended reasoning...

Overview

After several rebase rounds, this PR now adds exactly one file: test/regression/issue/30757.test.ts, a regression test for the WebKit normalizeMapKey UB that corrupted oversized integer-valued double Set/Map keys to INT32_MIN under LTO. The actual fix landed upstream in WebKit and was picked up on main when WEBKIT_VERSION was bumped to 0d85951a (verified at scripts/build/deps/webkit.ts:6); this PR no longer touches the version pin or any production code. The earlier bake-codegen.ts and wtf-bindings.cpp changes were dropped during rebase.

Security risks

None. Test-only change that spawns hermetic bun -e subprocesses with bunEnv and asserts on JSON stdout. No auth, crypto, network, filesystem, or user-input handling.

Level of scrutiny

Low. This is a new regression test following established repo conventions: two-line header (issue URL + one-line summary), test.concurrent for independent subprocess spawns, bunExe()/bunEnv, await using proc, stderr-empty assertion (acceptable with bunEnv per house style), and exitCode asserted last. The test logic itself is straightforward — round-trip values through Set/Map and compare against expected literals.

Other factors

Every review thread I raised across the PR's lifecycle has been addressed and resolved: the missing/preview-tag WebKit pin (resolved by main's 0d85951a bump containing truncateDoubleToInt32), test.concurrent, the condensed header, the ±Infinity in-subprocess identity checks, and the dropped bake-codegen.ts/wtf-bindings.cpp commits. The bug-hunting system found no issues. robobun's final comment confirms 0d85951a contains the fix and the PR now carries only the test against a durable 40-char SHA pin.

Covers the WebKit `normalizeMapKey` regression where under LTO the
`static_cast<int>(double)` UB was exploited by the optimizer, collapsing
every oversized integer-valued double Set/Map key to `jsNumber(INT32_MIN)`:

    [...new Set([1751241600000])][0]       // was: -2147483648
    new Set([1751241600000]).has(-1 << 31) // was: true

Fixed in WebKit by replacing the UB cast with `truncateDoubleToInt32`
(hardware cvttsd2si on x86_64, fcvtzs on arm64), which returns INT32_MIN
on overflow so the subsequent `i == d` filter works as intended. Picked
up in main via WebKit bump to 0d85951a.

Fixes #30757

[skip size check]
@robobun
robobun force-pushed the farm/178d285e/fix-set-iteration-int32-corruption branch from a5dba02 to 3314ce3 Compare May 23, 2026 05:40

@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 — test-only addition, all prior feedback addressed, and main's WebKit pin (0d85951a) now carries the normalizeMapKey fix this test covers.

Extended reasoning...

Overview

After several rounds of rebasing and feedback, this PR has converged to a single new file: test/regression/issue/30757.test.ts (131 lines). It adds three test.concurrent cases that spawn bunExe() subprocesses to verify Set/Map iteration preserves oversized integer-valued double keys (timestamps, INT32_MAX+1, 2^32, MAX_SAFE_INTEGER, ±Infinity, NaN) and that .has(-2147483648) no longer spuriously matches. The actual fix landed in WebKit and was picked up on main via the 0d85951a bump; this PR now only contributes the regression coverage.

Security risks

None. This is a test-only change that spawns hermetic bun -e subprocesses with inline scripts and bunEnv. No network, filesystem, auth, crypto, or user-input handling is touched.

Level of scrutiny

Low. The change is additive test coverage with no production code paths affected. The test follows house conventions (two-line header, test.concurrent, await using proc, bunExe()/bunEnv, stderr asserted before parsed stdout, exitCode asserted last, ±Infinity/NaN identity-checked in-subprocess to avoid JSON lossiness). I verified scripts/build/deps/webkit.ts on the current base pins WEBKIT_VERSION = "0d85951a…", the durable SHA the author identified as containing the truncateDoubleToInt32 fix — so the merge-blocker I raised in earlier rounds (missing/preview-tag WebKit bump) is fully resolved.

Other factors

Every prior review thread (mine and CodeRabbit's) is resolved and reflected in the final diff: header condensed, test.concurrent adopted, ±Infinity identity-checked, the bake-codegen.ts and wtf-bindings.cpp side-changes dropped, and the preview-PR WebKit tag removed in favor of main's durable pin. The bug-hunting system found no issues. The only CI red on the latest build is the binary-size check showing +11 MB on Linux targets, which a test-only change cannot cause — that delta is an artifact of the WebKit bump on main vs. the canary comparison baseline, not this PR.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing: #30757 was closed after the upstream WebKit fix for normalizeMapKey (WebKit/WebKit@a3aa752) reached main through the WebKit bump; vendor/WebKit/Source/JavaScriptCore/runtime/HashMapHelper.h now uses truncateDoubleToInt32. After that bump this PR only carried the regression test, and that test passes on current main (f426a8e) with both a debug build and the release canary (1.4.0-canary.1+da3851e57), so the fix is already on main.

@robobun robobun closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iterating a Set<number> corrupts large epoch-millisecond

1 participant