CVE-2026-11075
Overview
Files Changed
src/builtins/builtins-number-tsa.ccsrc/codegen/code-stub-assembler.ccsrc/common/globals.hsrc/compiler/operation-typer.ccsrc/compiler/representation-change.cc
Patch
From 330a25ad001ee1ac3b7a2176a1f175cee2592a5f Mon Sep 17 00:00:00 2001
From: Victor Gomes <victorgomes@chromium.org>
Date: Wed, 15 Apr 2026 10:29:16 +0200
Subject: [PATCH] [turbofan] Fix and simplify additive safe integer optimization
This CL simplifies the speculative optimization for additive safe
integers and introduces a safer margin for feedback ranges to prevent
overflow.
* Simplify optimization logic: Always perform the operation when the feedback is `AdditiveSafeInteger`, rather than relying on statically proving one side. This simplifies the operation typer and avoids range mistakes.
* Prevent bounds overflow: Introduce tighter feedback bounds (`kMaxAdditiveSafeIntegerFeedback` and `kMinAdditiveSafeIntegerFeedback` at ±2^51). This safer margin ensures that adding two minimum values together will no longer exceed the safe integer range.
* Update tests: Adjust `test/mjsunit/additive-safe-int-feedback.js` to reflect the newly introduced ranges.
Fixed: 499659070
Change-Id: Ic16dbb644b310bed24b169fed90c6637cac10daa
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7748026
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#106492}
---
diff --git a/src/builtins/builtins-number-tsa.cc b/src/builtins/builtins-number-tsa.cc
index b9c5daf..8bf79e8 100644
--- a/src/builtins/builtins-number-tsa.cc
+++ b/src/builtins/builtins-number-tsa.cc
@@ -94,11 +94,11 @@
BIND(if_int);
{
- // Check if AdditiveSafeInteger: (value - kMinAdditiveSafeInteger) >> 53
- // == 0
- V<Word64> shifted_value =
- Word64ShiftRightLogical(Word64Sub(value_i64, kMinAdditiveSafeInteger),
- kAdditiveSafeIntegerBitLength);
+ // Check if AdditiveSafeIntegerFeedback: (value -
+ // kMinAdditiveSafeIntegerFeedback) >> 51 == 0
+ V<Word64> shifted_value = Word64ShiftRightLogical(
+ Word64Sub(value_i64, kMinAdditiveSafeIntegerFeedback),
+ kAdditiveSafeIntegerFeedbackBitLength);
GOTO_IF_NOT(Word64Equal(shifted_value, 0), if_fail);
return value_i64;
}
diff --git a/src/codegen/code-stub-assembler.cc b/src/codegen/code-stub-assembler.cc
index 30f47be..e95cd98 100644
--- a/src/codegen/code-stub-assembler.cc
+++ b/src/codegen/code-stub-assembler.cc
@@ -7186,10 +7186,11 @@
if_failed);
BIND(&if_int64);
- // Check if AdditiveSafeInteger: (value - kMinAdditiveSafeInteger) >> 53 == 0
- TNode<Int64T> shifted_value =
- Word64Shr(Int64Sub(value_int64, Int64Constant(kMinAdditiveSafeInteger)),
- Uint64Constant(kAdditiveSafeIntegerBitLength));
+ // Check if AdditiveSafeIntegerFeedback: (value -
+ // kMinAdditiveSafeIntegerFeedback) >> 51 == 0
+ TNode<Int64T> shifted_value = Word64Shr(
+ Int64Sub(value_int64, Int64Constant(kMinAdditiveSafeIntegerFeedback)),
+ Uint64Constant(kAdditiveSafeIntegerFeedbackBitLength));
GotoIfNot(Word64Equal(shifted_value, Int64Constant(0)), if_failed);
return UncheckedCast<AdditiveSafeIntegerT>(value_int64);
}
diff --git a/src/common/globals.h b/src/common/globals.h
index a795221..e3ded06 100644
--- a/src/common/globals.h
+++ b/src/common/globals.h
@@ -2133,29 +2133,30 @@
#endif // V8_ENABLE_UNDEFINED_DOUBLE
// ES6 section 20.1.2.6 Number.MAX_SAFE_INTEGER
-constexpr uint64_t kMaxSafeIntegerUint64 = 9007199254740991; // 2^53-1
-static_assert(kMaxSafeIntegerUint64 == (uint64_t{1} << 53) - 1);
+constexpr uint64_t kMaxSafeIntegerUint64 = (uint64_t{1} << 53) - 1; // 2^53-1
constexpr double kMaxSafeInteger = static_cast<double>(kMaxSafeIntegerUint64);
// ES6 section 21.1.2.8 Number.MIN_SAFE_INTEGER
constexpr double kMinSafeInteger = -kMaxSafeInteger;
constexpr double kMaxUInt32Double = double{kMaxUInt32};
-constexpr int64_t kMaxAdditiveSafeInteger = 4503599627370495; // 2^52 - 1
-static_assert(kMaxAdditiveSafeInteger == (int64_t{1} << 52) - 1);
-constexpr int64_t kMinAdditiveSafeInteger = -4503599627370496; // - 2^52
-static_assert(kMinAdditiveSafeInteger == -(int64_t{1} << 52));
-constexpr int kAdditiveSafeIntegerBitLength = 53;
-// Number of bits to shift left before addition to detect potential overflow.
-constexpr int kAdditiveSafeIntegerShift = 64 - kAdditiveSafeIntegerBitLength;
-
+constexpr int64_t kMaxAdditiveSafeInteger = (int64_t{1} << 52) - 1; // 2^52 - 1
+constexpr int64_t kMinAdditiveSafeInteger =
+ -(int64_t{1} << 52) + 1; // - (2^52 - 1)
static_assert(kMaxAdditiveSafeInteger + kMaxAdditiveSafeInteger <=
kMaxSafeInteger);
-// kMinAdditiveSafeInteger + kMinAdditiveSafeInteger would overflow the integer
-// safe addition.
-static_assert(kMinAdditiveSafeInteger + (kMinAdditiveSafeInteger + 1) >=
+static_assert(kMinAdditiveSafeInteger + kMinAdditiveSafeInteger >=
kMinSafeInteger);
+constexpr int64_t kMaxAdditiveSafeIntegerFeedback =
+ (int64_t{1} << 51) - 1; // 2^51 - 1
+constexpr int64_t kMinAdditiveSafeIntegerFeedback =
+ -(int64_t{1} << 51); // - 2^51
+constexpr int kAdditiveSafeIntegerFeedbackBitLength = 52;
+// Number of bits to shift left before addition to detect potential overflow.
+constexpr int kAdditiveSafeIntegerFeedbackShift =
+ 64 - kAdditiveSafeIntegerFeedbackBitLength;
+
// The order of this enum has to be kept in sync with the predicates below.
enum class VariableMode : uint8_t {
// User declared variables:
diff --git a/src/compiler/operation-typer.cc b/src/compiler/operation-typer.cc
index d28d90c..d297650 100644
--- a/src/compiler/operation-typer.cc
+++ b/src/compiler/operation-typer.cc
@@ -719,22 +719,12 @@
}
Type OperationTyper::SpeculativeAdditiveSafeIntegerAdd(Type lhs, Type rhs) {
- Type result = SpeculativeNumberAdd(lhs, rhs);
- if (lhs.Is(cache_->kAdditiveSafeInteger) ||
- rhs.Is(cache_->kAdditiveSafeInteger)) {
- return Type::Intersect(result, cache_->kAdditiveSafeInteger, zone());
- }
- return result;
+ return SpeculativeNumberAdd(lhs, rhs);
}
Type OperationTyper::SpeculativeAdditiveSafeIntegerSubtract(Type lhs,
Type rhs) {
- Type result = SpeculativeNumberSubtract(lhs, rhs);
- if (lhs.Is(cache_->kAdditiveSafeInteger) ||
- rhs.Is(cache_->kAdditiveSafeInteger)) {
- return Type::Intersect(result, cache_->kAdditiveSafeInteger, zone());
- }
- return result;
+ return SpeculativeNumberSubtract(lhs, rhs);
}
Type OperationTyper::SpeculativeSmallIntegerAdd(Type lhs, Type rhs) {
diff --git a/src/compiler/representation-change.cc b/src/compiler/representation-change.cc
index 23ad745..cfc1cf3 100644
--- a/src/compiler/representation-change.cc
+++ b/src/compiler/representation-change.cc
@@ -1011,7 +1011,7 @@
op = machine()->ChangeFloat64ToUint32();
} else if (use_info.truncation().IsUsedAsWord32()) {
if (use_info.type_check() == TypeCheckKind::kAdditiveSafeInteger) {
- if (output_type.Is(cache_->kAdditiveSafeInteger)) {
+ if (output_type.Is(cache_->kAdditiveSafeIntegerFeedback)) {
op = machine()->TruncateFloat64ToWord32();
} else {
op = simplified()->CheckedFloat64ToAdditiveSafeInteger(
@@ -1045,7 +1045,7 @@
op = machine()->ChangeFloat64ToUint32();
} else if (use_info.truncation().IsUsedAsWord32()) {
if (use_info.type_check() == TypeCheckKind::kAdditiveSafeInteger) {
- if (output_type.Is(cache_->kAdditiveSafeInteger)) {
+ if (output_type.Is(cache_->kAdditiveSafeIntegerFeedback)) {
op = machine()->TruncateFloat64ToWord32();
} else {
op = simplified()->CheckedFloat64ToAdditiveSafeInteger(
@@ -1086,7 +1086,7 @@
CheckTaggedInputMode::kAdditiveSafeInteger, use_info.feedback());
} else if (use_info.truncation().IsUsedAsWord32()) {
if (use_info.type_check() == TypeCheckKind::kAdditiveSafeInteger) {
- if (output_type.Is(cache_->kAdditiveSafeInteger)) {
+ if (output_type.Is(cache_->kAdditiveSafeIntegerFeedback)) {
op = simplified()->TruncateNumberOrOddballToWord32();
} else {
op = simplified()->CheckedTruncateTaggedToWord32(
@@ -1281,8 +1281,8 @@
int64_t const iv = static_cast<int64_t>(fv);
if (static_cast<double>(iv) == fv) {
if (use_info.type_check() == TypeCheckKind::kAdditiveSafeInteger) {
- if (iv < kMinAdditiveSafeInteger ||
- kMaxAdditiveSafeInteger < iv ||
+ if (iv < kMinAdditiveSafeIntegerFeedback ||
+ kMaxAdditiveSafeIntegerFeedback < iv ||
(iv == 0 && std::signbit(fv))) {
Node* unreachable = InsertUnconditionalDeopt(
use_node, DeoptimizeReason::kNotAdditiveSafeInteger,
@@ -1372,7 +1372,7 @@
if (use_info.type_check() == TypeCheckKind::kAdditiveSafeInteger) {
// float32 -> float64 -> int64
node = InsertChangeFloat32ToFloat64(node);
- if (output_type.Is(cache_->kAdditiveSafeInteger)) {
+ if (output_type.Is(cache_->kAdditiveSafeIntegerFeedback)) {
op = machine()->ChangeFloat64ToInt64();
} else {
op = simplified()->CheckedFloat64ToAdditiveSafeInteger(
@@ -1406,7 +1406,7 @@
}
} else if (output_rep == MachineRepresentation::kFloat64) {
if (use_info.type_check() == TypeCheckKind::kAdditiveSafeInteger) {
- if (output_type.Is(cache_->kAdditiveSafeInteger)) {
+ if (output_type.Is(cache_->kAdditiveSafeIntegerFeedback)) {
op = machine()->ChangeFloat64ToInt64();
} else {
op = simplified()->CheckedFloat64ToAdditiveSafeInteger(
@@ -1454,7 +1454,7 @@
Regression Test / PoC
diff --git a/test/mjsunit/additive-safe-int-feedback.js b/test/mjsunit/additive-safe-int-feedback.js
index 7530b9f..aabdb83 100644
--- a/test/mjsunit/additive-safe-int-feedback.js
+++ b/test/mjsunit/additive-safe-int-feedback.js
@@ -5,8 +5,8 @@
// Flags: --allow-natives-syntax --additive-safe-int-feedback
// Flags: --turbofan
-const maxAdditiveSafeInteger = 4503599627370495; // 2^52 - 1
-const minAdditiveSafeInteger = - 4503599627370496; // - 2^52
+const maxAdditiveSafeInteger = 2251799813685247; // 2^52 - 1
+const minAdditiveSafeInteger = - 2251799813685248; // - 2^51
// If one of the inputs is a constant in the additive safe range,
// we can use AdditiveSafeInteger.
@@ -89,8 +89,15 @@
assertEquals(1231234567891, foo(1231234567890, 1));
assertOptimized(foo);
- // We don't deopt in overflow.
+ // We deopt in overflow.
assertEquals(maxAdditiveSafeInteger + 1, foo(maxAdditiveSafeInteger, 1));
+ assertUnoptimized(foo);
+
+ // Re-optimize to continue test.
+ %PrepareFunctionForOptimization(foo);
+ assertEquals(1231234567891, foo(1231234567890, 1));
+ %OptimizeFunctionOnNextCall(foo);
+ assertEquals(1231234567891, foo(1231234567890, 1));
assertOptimized(foo);
// Don't deopt with doubles.
@@ -300,6 +307,13 @@
// And we cannot deopt by overflowing the first one.
assertEquals(maxAdditiveSafeInteger + 2, foo(maxAdditiveSafeInteger, 1));
+ assertUnoptimized(foo);
+
+ // Re-optimize to continue test.
+ %PrepareFunctionForOptimization(foo);
+ assertEquals(1231234567892, foo(1231234567890, 1));
+ %OptimizeFunctionOnNextCall(foo);
+ assertEquals(1231234567892, foo(1231234567890, 1));
assertOptimized(foo);
// Don't deopt with doubles.
Original Bug Report
V8: JIT Miscompilation via Incorrect Type Narrowing in TurboFan SpeculativeAdditiveSafeIntegerAdd
VULNERABILITY DETAILS
Summary
A type narrowing bug in TurboFan’s OperationTyper::SpeculativeAdditiveSafeIntegerAdd causes the compiler to compute an incorrectly narrow result type when the mathematical sum can exceed the kAdditiveSafeInteger range [-2^52, 2^52-1]. The function intersects the result type with kAdditiveSafeInteger when either input is in range (OR condition), but when both inputs are in range and their sum exceeds the range, the intersection clips the result to just 2 values instead of 2^32. After algebraic simplification of (x + C) - C -> x, the incorrect type Range(0, 1) persists on the result node, causing TypeNarrowingReducer to fold comparisons and ConstantFoldingReducer to eliminate reachable branches, producing wrong JavaScript results. The bug is reproducible on all 64-bit architectures with default V8 flags and no special command-line options.
Overview
The root cause is in OperationTyper::SpeculativeAdditiveSafeIntegerAdd (src/compiler/operation-typer.cc:721-728). The function intersects the addition result type with kAdditiveSafeInteger (range [-2^52, 2^52-1]) when either input type is a subset of kAdditiveSafeInteger (OR condition on line 723). This creates an incorrectly narrow result type when the mathematical sum can exceed the kAdditiveSafeInteger bounds, even though both inputs individually are within range.
When a subsequent subtraction of the same constant creates an algebraic identity (x + C) - C, the MachineOperatorReducer eliminates both operations and their runtime overflow checks. The incorrect type Range(0, 1) – computed from the clipped intermediate – persists on the result node, causing TypeNarrowingReducer to fold comparisons and ConstantFoldingReducer to eliminate branches, producing wrong JavaScript results.
Detail
The vulnerable code in OperationTyper::SpeculativeAdditiveSafeIntegerAdd:
// src/compiler/operation-typer.cc:721-728
Type OperationTyper::SpeculativeAdditiveSafeIntegerAdd(Type lhs, Type rhs) {
Type result = SpeculativeNumberAdd(lhs, rhs);
if (lhs.Is(cache_->kAdditiveSafeInteger) || // <-- OR condition: only ONE input needs to be in range
rhs.Is(cache_->kAdditiveSafeInteger)) {
return Type::Intersect(result, cache_->kAdditiveSafeInteger, zone()); // <-- clips result to [-2^52, 2^52-1]
}
return result;
}
And the corresponding subtraction:
// src/compiler/operation-typer.cc:730-738
Type OperationTyper::SpeculativeAdditiveSafeIntegerSubtract(Type lhs,
Type rhs) {
Type result = SpeculativeNumberSubtract(lhs, rhs);
if (lhs.Is(cache_->kAdditiveSafeInteger) ||
rhs.Is(cache_->kAdditiveSafeInteger)) {
return Type::Intersect(result, cache_->kAdditiveSafeInteger, zone());
}
return result;
}
How the miscompilation occurs (in pipeline execution order):
-
Incorrect Type Computation (Typer phase): For
ix(x >>> 0, Uint32 range[0, 2^32-1]) – which is a subset ofkAdditiveSafeInteger– adding constantBASE = 2^52 - 2producesSpeculativeNumberAddresultRange(4503599627370494, 4503603922337789). The OR condition triggers (both inputs are individually withinkAdditiveSafeInteger), and intersection withkAdditiveSafeIntegerclips this toRange(4503599627370494, 4503599627370495)– just 2 possible values instead of 2^32. -
Type Propagation (Typer phase): The subtraction
sum - BASEis typed asRange(0, 1)bySpeculativeAdditiveSafeIntegerSubtract, because the subtraction ranger computesRange(4503599627370494, 4503599627370495) - Range(4503599627370494, 4503599627370494) = Range(0, 1). The actual mathematical range should be[0, 2^32-1]. -
Algebraic Simplification (LateOptimization phase): The
MachineOperatorReducer(src/compiler/machine-operator-reducer.cc:1179-1196) converts(x + C) - Cintox + C + (-C), then constant-foldsC + (-C) = 0viaReduceInt64Add(src/compiler/machine-operator-reducer.cc:1147-1155), resulting inx + 0 = x. This eliminates both the addition and subtraction operations along with theirCheckedAdditiveSafeIntegerAdd/Subruntime overflow checks. The incorrect typeRange(0, 1)from step 2 persists on the result node even though the actual runtime value is the unmodifiedx >>> 0. -
Comparison Folding (TypedOptimizations phase):
TypeNarrowingReducer(src/compiler/type-narrowing-reducer.cc:33-34) foldsidx < 2tosingleton_truebecauseRange(0, 1).Max() = 1 < 2:// src/compiler/type-narrowing-reducer.cc:33-34 if (left_type.Max() < right_type.Min()) { new_type = op_typer_.singleton_true(); } -
Branch Elimination (TypedOptimizations phase):
ConstantFoldingReducer(src/compiler/constant-folding-reducer.cc:31-32) detects the singleton type and replaces the comparison with a constant, causing branch elimination to remove the reachable false branch:// src/compiler/constant-folding-reducer.cc:31-32 } else if (type.Is(Type::PlainNumber()) && type.Min() == type.Max()) { result = jsgraph->ConstantNoHole(type.Min());
Trigger Conditions
- Target must be a 64-bit architecture (
additive_safe_int_feedbackflag istrueby default only on 64-bit;src/flags/flag-definitions.h:860-877) - The addition operand must be within
kAdditiveSafeIntegerrange (e.g.,x >>> 0produces Uint32, a subset) - The constant added must be close to
kMaxAdditiveSafeInteger(2^52 - 1) so that the sum can exceed the range - The addition must be paired with a subtraction of the same constant, enabling algebraic simplification
- The result of the add-sub pair must be used in a comparison that can be folded by TypeNarrowingReducer
- The function must be compiled by TurboFan (requires sufficient warmup or
%OptimizeFunctionOnNextCall)
Version
Reproduced Version
mainbranch latest commit (2026/04/06):01333f9ce95- V8 14.9.0
Bisect
The commit 0a1fae9e77c6d8e85d8197b4f4396815ec9194b9 introduces this bug.
commit 0a1fae9e77c6d8e85d8197b4f4396815ec9194b9
Author: Victor Gomes <victorgomes@chromium.org>
Date: Tue Feb 11 14:13:21 2025 +0100
[turbofan] Use AdditiveSafeInt feedback for faster int add/sub
Add Int53 addition feedback and use it during simplified lowering
to optimize integer addition and subtraction. When one of the inputs
is known to be within the Int53 range (since the check is relatively
expensive), we can use a shifted integer addition instead of floating
addition.
AdditiveSafeInteger (Int53) is used instead of SafeInteger because
the safe integer range (-2^53 + 1 to 2^53 -1) excludes -2^53.
Using SafeInteger directly would require an additional check after
the operation to ensure the result remains within the valid bounds.
This change does *not* implement Maglev-specific optimizations.
Bug: 384959125
Change-Id: I7b54d075c55ab7828e407cec03933a34be6247d5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6038008
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#98643}
The first V8 milestone branch that contains this commit is branch-heads/13.5 (Chrome M135). V8 branch-heads/13.4 does NOT contain the commit.
Reproduction Case
Release Build
out/x64.release/d8 --allow-natives-syntax poc.js
Result:
=== Wrong Result ===
[Interpreter] f(5) = 5
[TurboFan] f(5) = -1
Optimized: true
Debug Build
out/x64.debug/d8 --allow-natives-syntax poc.js
Result:
=== Wrong Result ===
[Interpreter] f(5) = 5
[TurboFan] f(5) = -1
Optimized: true
Also confirmed on ASAN release build (out/x64.release_asan/d8 --allow-natives-syntax poc.js) with identical results.
PoC Code
// Run: out/x64.release/d8 --allow-natives-syntax poc.js
const BASE = 4503599627370494; // 2^52 - 2
function f_interp(x) {
let ix = x >>> 0;
let idx = (ix + BASE) - BASE;
if (idx < 2) return -1;
return idx;
}
// Separate function to avoid feedback pollution from f_interp(5),
// which would record idx=5 and prevent AdditiveSafeInteger speculation.
function f_jit(x) {
let ix = x >>> 0;
let idx = (ix + BASE) - BASE;
if (idx < 2) return -1;
return idx;
}
// --- Interpreter ---
const interp = f_interp(5);
// --- TurboFan (train only with 0,1 so feedback stays AdditiveSafeInteger) ---
%PrepareFunctionForOptimization(f_jit);
for (let i = 0; i < 10000; i++) { f_jit(0); f_jit(1); }
%OptimizeFunctionOnNextCall(f_jit);
f_jit(0);
const opt = f_jit(5);
print("=== Wrong Result ===");
print("[Interpreter] f(5) = " + interp); // 5 (correct)
print("[TurboFan] f(5) = " + opt); // -1 (wrong)
print("Optimized: " + ((%GetOptimizationStatus(f_jit) & 32) !== 0));
Suggested Patch
src/compiler/operation-typer.cc
--- a/src/compiler/operation-typer.cc
+++ b/src/compiler/operation-typer.cc
@@ -721,7 +721,11 @@
Type OperationTyper::SpeculativeAdditiveSafeIntegerAdd(Type lhs, Type rhs) {
Type result = SpeculativeNumberAdd(lhs, rhs);
- if (lhs.Is(cache_->kAdditiveSafeInteger) ||
- rhs.Is(cache_->kAdditiveSafeInteger)) {
+ // Only narrow to kAdditiveSafeInteger when BOTH inputs are within range
+ // AND the computed result is already within range. With the OR condition,
+ // one input near the boundary plus any in-range value can produce a sum
+ // that exceeds the bounds, and the intersection clips the result type to
+ // a range that doesn't contain all possible values.
+ if (lhs.Is(cache_->kAdditiveSafeInteger) &&
+ rhs.Is(cache_->kAdditiveSafeInteger) &&
+ result.Is(cache_->kAdditiveSafeInteger)) {
return Type::Intersect(result, cache_->kAdditiveSafeInteger, zone());
}
return result;
@@ -730,8 +734,9 @@
Type OperationTyper::SpeculativeAdditiveSafeIntegerSubtract(Type lhs,
Type rhs) {
Type result = SpeculativeNumberSubtract(lhs, rhs);
- if (lhs.Is(cache_->kAdditiveSafeInteger) ||
- rhs.Is(cache_->kAdditiveSafeInteger)) {
+ if (lhs.Is(cache_->kAdditiveSafeInteger) &&
+ rhs.Is(cache_->kAdditiveSafeInteger) &&
+ result.Is(cache_->kAdditiveSafeInteger)) {
return Type::Intersect(result, cache_->kAdditiveSafeInteger, zone());
}
return result;
Explanation
The fix changes the OR condition (||) to AND (&&) with an additional guard that the computed result is already within kAdditiveSafeInteger. The intersection is now only applied when it is provably a no-op (i.e., when both inputs AND the result are within bounds), so it can never incorrectly clip the result type.
The original OR condition was wrong because two values that are individually within kAdditiveSafeInteger (e.g., Uint32 [0, 2^32-1] and constant 2^52 - 2) can sum to a value that exceeds the range. The intersection then clips the result to a range that excludes actually-reachable values, creating incorrect type information that persists through algebraic simplification and causes downstream miscompilation.
Note: CanSpeculateAdditiveSafeInteger in simplified-lowering.cc:1839 does not need fixing. Its OR condition only controls whether CheckedAdditiveSafeIntegerAdd/Sub operations (with runtime overflow checks) are emitted – a lowering decision. The bug is solely in the typing of the result.
Credit Information
Reporter credit: Junyoung Park(@candymate) of KAIST Hacking Lab