HashMapHelper: range-check before double→int cast in normalizeMapKey#232
HashMapHelper: range-check before double→int cast in normalizeMapKey#232robobun wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📥 CommitsReviewing files that changed from the base of the PR and between 3167a44 and b0366015ea1c36e94ba3fc4010d9f672a7809556. 📒 Files selected for processing (1)
WalkthroughThe ChangesMap Key Bounds Check
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Preview Builds
|
Picks up oven-sh/WebKit#232 (HashMapHelper: range-check before static_cast<int>(double) in normalizeMapKey). This is the actual fix for the Set iteration corruption covered by the regression test two commits ago. Using the preview-pr-232 autobuild for now; swap to the merged commit tag once oven-sh/WebKit#232 is in main.
b036601 to
b37ec33
Compare
…_MIN Values that are tagged as doubles in JSC (any integer-valued number outside `[INT32_MIN, INT32_MAX]`) round-tripped through `new Set([v])` came back as `-2147483648`. `new Date(1751241600000)` → `new Date(1751241600000)` through a Set collapsed all oversized integers to `INT32_MIN`. Root cause in WebKit: `normalizeMapKey` in `HashMapHelper.h` called `int i = static_cast<int>(d)` before its `i == d` round-trip check. Casting an out-of-range double to int is UB (LLVM IR `fptosi` produces poison). Under LTO the optimizer exploited the UB and folded the branch so the integer path was always taken, normalizing every oversized integer-valued double key to `jsNumber(INT32_MIN)`. `has(originalValue)` still worked because the stored (corrupted) normalized key round-trips with itself; iteration paths returned the corrupted value to JS. WebKit fix: range-check `d` against `[INT32_MIN, INT32_MAX]` before the cast (oven-sh/WebKit#232). Fixes #30757
…_MIN Values that are tagged as doubles in JSC (any integer-valued number outside `[INT32_MIN, INT32_MAX]`) round-tripped through `new Set([v])` came back as `-2147483648`. `new Date(1751241600000)` → `new Date(1751241600000)` through a Set collapsed all oversized integers to `INT32_MIN`. Root cause in WebKit: `normalizeMapKey` in `HashMapHelper.h` called `int i = static_cast<int>(d)` before its `i == d` round-trip check. Casting an out-of-range double to int is UB (LLVM IR `fptosi` produces poison). Under LTO the optimizer exploited the UB and folded the branch so the integer path was always taken, normalizing every oversized integer-valued double key to `jsNumber(INT32_MIN)`. `has(originalValue)` still worked because the stored (corrupted) normalized key round-trips with itself; iteration paths returned the corrupted value to JS. WebKit fix: range-check `d` against `[INT32_MIN, INT32_MAX]` before the cast (oven-sh/WebKit#232). Fixes #30757
`static_cast<int>(double)` is undefined behavior for out-of-range values (in LLVM IR `fptosi` produces poison). Under LTO, the subsequent `i == d` round-trip check gets folded and the integer path is taken even for doubles like `1751241600000`, collapsing every oversized integer-valued double Map/Set key to `jsNumber(INT32_MIN)`. Range-check `d` against `[INT32_MIN, INT32_MAX]` before the cast so the fast path stays integer-only and the fallthrough preserves the original double key. Symptoms without this patch (1.3.14-canary LTO builds): ```js [...new Set([1751241600000])][0] // -2147483648, want 1751241600000 new Set([1751241600000]).has(-1 << 31) // true ``` has() still works on the original value because lookup compares against the stored (corrupted) normalized key; iteration/reads return the corrupted value to JS. Fractional doubles, tagged Int32s, strings, etc. are unaffected because they never enter the `static_cast<int>` branch. Fixes oven-sh/bun#30757
b37ec33 to
1cf55aa
Compare
Picks up oven-sh/WebKit#232 (HashMapHelper: range-check before `static_cast<int>(double)` in `normalizeMapKey`) rebased onto current WebKit main (`1cf55aaf71` on `3167a44fb9`). Preview tag rather than a merged `autobuild-<sha>` because #232 is still open; swap once it lands.
…_MIN Values that are tagged as doubles in JSC (any integer-valued number outside `[INT32_MIN, INT32_MAX]`) round-tripped through `new Set([v])` came back as `-2147483648`. `new Date(1751241600000)` → `new Date(1751241600000)` through a Set collapsed all oversized integers to `INT32_MIN`. Root cause in WebKit: `normalizeMapKey` in `HashMapHelper.h` called `int i = static_cast<int>(d)` before its `i == d` round-trip check. Casting an out-of-range double to int is UB (LLVM IR `fptosi` produces poison). Under LTO the optimizer exploited the UB and folded the branch so the integer path was always taken, normalizing every oversized integer-valued double key to `jsNumber(INT32_MIN)`. `has(originalValue)` still worked because the stored (corrupted) normalized key round-trips with itself; iteration paths returned the corrupted value to JS. WebKit fix: range-check `d` against `[INT32_MIN, INT32_MAX]` before the cast (oven-sh/WebKit#232). Fixes #30757
Picks up oven-sh/WebKit#232 (HashMapHelper: range-check before `static_cast<int>(double)` in `normalizeMapKey`) rebased onto current WebKit main (`1cf55aaf71` on `3167a44fb9`). Preview tag rather than a merged `autobuild-<sha>` because #232 is still open; swap once it lands.
…_MIN Values that are tagged as doubles in JSC (any integer-valued number outside `[INT32_MIN, INT32_MAX]`) round-tripped through `new Set([v])` came back as `-2147483648`. `new Date(1751241600000)` → `new Date(1751241600000)` through a Set collapsed all oversized integers to `INT32_MIN`. Root cause in WebKit: `normalizeMapKey` in `HashMapHelper.h` called `int i = static_cast<int>(d)` before its `i == d` round-trip check. Casting an out-of-range double to int is UB (LLVM IR `fptosi` produces poison). Under LTO the optimizer exploited the UB and folded the branch so the integer path was always taken, normalizing every oversized integer-valued double key to `jsNumber(INT32_MIN)`. `has(originalValue)` still worked because the stored (corrupted) normalized key round-trips with itself; iteration paths returned the corrupted value to JS. WebKit fix: range-check `d` against `[INT32_MIN, INT32_MAX]` before the cast (oven-sh/WebKit#232). Fixes #30757
Picks up oven-sh/WebKit#232 (HashMapHelper: range-check before `static_cast<int>(double)` in `normalizeMapKey`) rebased onto current WebKit main (`1cf55aaf71` on `3167a44fb9`). Preview tag rather than a merged `autobuild-<sha>` because #232 is still open; swap once it lands.
Jarred-Sumner
left a comment
There was a problem hiding this comment.
@robobun could this use oen of those specialized int conversion methods for avoiding UB in C++ that WebKit uses in several places?
Fixes oven-sh/bun#30757.
Problem
static_cast<int>(double)is undefined behavior for out-of-range values (LLVM IRfptosion such inputs produces poison). Under LTO the optimizer exploits the UB and folds the subsequenti == dround-trip check to true, so the integer path is taken even for doubles that do not fit in an int32. Every oversized integer-valued double Map/Set key is then silently normalized tojsNumber(INT32_MIN)..has(originalValue)still worked because the stored (corrupted) normalized key round-trips with itself; iteration paths surfaced the corrupted value. Tagged Int32s, fractional doubles, strings, etc. are unaffected because they never enter thestatic_cast<int>branch.Fix
Range-check
dagainst[INT32_MIN, INT32_MAX]before the cast. Defined-behavior gate keeps the integer fast path correct and preserves the original double for out-of-range keys.The DFG/FTL
NormalizeMapKeylowering is already safe because it usesbranchConvertDoubleToInt32(hardware overflow branch); the C++ helper just needed to match that behavior.Testing
Verified against an LTO release build of bun at the affected WebKit pin:
1751241600000-2147483648❌1751241600000✅2147483648(INT32_MAX+1)-2147483648❌2147483648✅-2147483649(INT32_MIN−1)-2147483648❌-2147483649✅Number.MAX_SAFE_INTEGER-2147483648❌9007199254740991✅Infinity/-Infinity-2147483648❌Infinity/-Infinity✅new Set([a, b, c])with distinct large doublesRegression test going up in oven-sh/bun (see WebKit#30757).