From 6062e7d01eb42f07edcef6c6e06bb14807ce4e7f Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 15 Jan 2026 02:10:26 +0100 Subject: [PATCH 1/2] Fix possible overflow in optAssertionPropGlobal_RelOp --- src/coreclr/jit/assertionprop.cpp | 6 ++- src/coreclr/jit/rangecheck.h | 15 ++++++++ .../JitBlue/Runtime_122288/Runtime_122288.cs | 37 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_122288/Runtime_122288.cs diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 61cfb57382a612..b3a8efb4781e70 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -4475,9 +4475,11 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, { Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset)); Range peeledOp1Rng = Range(Limit(Limit::keUnknown)); - if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng)) + if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng) && + !RangeOps::DoesAddOverflow(peeledOp1Rng, peeledOffsetRng)) { - // Subtract handles overflow internally. + // We don't have to check overflow for Subtract because rng2 will + // have keUnknown for its limits if overflow happens. rng2 = RangeOps::Subtract(rng2, peeledOffsetRng); RangeOps::RelationKind kind = diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index 4c65a87865628b..c903d3e34bfa04 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -420,6 +420,21 @@ struct RangeOps }); } + static bool DoesAddOverflow(Range& r1, Range& r2) + { + bool r1loConst = r1.LowerLimit().IsConstant() || r1.LowerLimit().IsBinOpArray(); + bool r1hiConst = r1.UpperLimit().IsConstant() || r1.UpperLimit().IsBinOpArray(); + bool r2loConst = r2.LowerLimit().IsConstant() || r2.LowerLimit().IsBinOpArray(); + bool r2hiConst = r2.UpperLimit().IsConstant() || r2.UpperLimit().IsBinOpArray(); + + if ((r1loConst && r2loConst && IntAddOverflows(r1.LowerLimit().GetConstant(), r2.LowerLimit().GetConstant())) || + (r1hiConst && r2hiConst && IntAddOverflows(r1.UpperLimit().GetConstant(), r2.UpperLimit().GetConstant()))) + { + return true; + } + return false; + } + static Range Subtract(Range& r1, Range& r2) { return ApplyRangeOp(r1, r2, [](Limit& a, Limit& b) { diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_122288/Runtime_122288.cs b/src/tests/JIT/Regression/JitBlue/Runtime_122288/Runtime_122288.cs new file mode 100644 index 00000000000000..a87ee5d2ab9ad4 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_122288/Runtime_122288.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Generated by Fuzzlyn v3.3 on 2025-12-07 15:16:42 +// Run on X64 Windows +// Seed: 1178100487102642388-vectort,vector128,vector256,x86aes,x86avx,x86avx2,x86avx512bw,x86avx512bwvl,x86avx512cd,x86avx512cdvl,x86avx512dq,x86avx512dqvl,x86avx512f,x86avx512fvl,x86avx512fx64,x86bmi1,x86bmi1x64,x86bmi2,x86bmi2x64,x86fma,x86lzcnt,x86lzcntx64,x86pclmulqdq,x86popcnt,x86popcntx64,x86sse,x86ssex64,x86sse2,x86sse2x64,x86sse3,x86sse41,x86sse41x64,x86sse42,x86sse42x64,x86ssse3,x86x86base +// Reduced from 246.6 KiB to 0.4 KiB in 00:07:26 +// Debug: Prints 1 line(s) +// Release: Prints 0 line(s) + +using System; +using Xunit; + +public class Runtime_122288 +{ + private class C0 + { + public byte F0; + public uint F2; + } + + [Fact] + public static void TestEntryPoint() + { + var vr7 = new C0[] + { + new C0() + }; + if (1 >= (2147483646 + (ushort)(vr7[0].F2 - 1))) + { + } + else + { + throw new InvalidOperationException(); + } + } +} From 88d4fc596193d3133ba773ec8be26479befab38f Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 15 Jan 2026 17:43:40 +0100 Subject: [PATCH 2/2] simpler fix --- src/coreclr/jit/assertionprop.cpp | 36 ++++++++++++++++++------------- src/coreclr/jit/rangecheck.h | 25 ++++++++------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index b3a8efb4781e70..1435e47cceee8a 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -3982,8 +3982,12 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, int cns = static_cast(tree->gtGetOp2()->AsIntCon()->IconValue()); - if ((rng.LowerLimit().IsConstant() && !rng.LowerLimit().AddConstant(cns)) || - (rng.UpperLimit().IsConstant() && !rng.UpperLimit().AddConstant(cns))) + bool loOverflows = false; + bool hiOverflows = false; + rng.LowerLimit().AddConstant(cns, &loOverflows); + rng.UpperLimit().AddConstant(cns, &hiOverflows); + + if (loOverflows || hiOverflows) { // Add cns to both bounds if they are constants. Make sure the addition doesn't overflow. return; @@ -4473,22 +4477,24 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, if (peeledOffset != 0) { - Range peeledOffsetRng = Range(Limit(Limit::keConstant, peeledOffset)); - Range peeledOp1Rng = Range(Limit(Limit::keUnknown)); - if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng) && - !RangeOps::DoesAddOverflow(peeledOp1Rng, peeledOffsetRng)) + Range peeledOp1Rng = Range(Limit(Limit::keUnknown)); + if (RangeCheck::TryGetRangeFromAssertions(this, peeledOp1VN, assertions, &peeledOp1Rng)) { - // We don't have to check overflow for Subtract because rng2 will - // have keUnknown for its limits if overflow happens. - rng2 = RangeOps::Subtract(rng2, peeledOffsetRng); + bool loOverflows = false; + bool hiOverflows = false; + peeledOp1Rng.LowerLimit().AddConstant(peeledOffset, &loOverflows); + peeledOp1Rng.UpperLimit().AddConstant(peeledOffset, &hiOverflows); - RangeOps::RelationKind kind = - RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), peeledOp1Rng, rng2); - if ((kind != RangeOps::RelationKind::Unknown)) + if (!loOverflows && !hiOverflows) { - newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse(); - newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT); - return optAssertionProp_Update(newTree, tree, stmt); + RangeOps::RelationKind kind = + RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), peeledOp1Rng, rng2); + if ((kind != RangeOps::RelationKind::Unknown)) + { + newTree = kind == RangeOps::RelationKind::AlwaysTrue ? gtNewTrue() : gtNewFalse(); + newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT); + return optAssertionProp_Update(newTree, tree, stmt); + } } } } diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index c903d3e34bfa04..0e714b655874ce 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -133,8 +133,12 @@ struct Limit { return type == keBinOpArray; } - bool AddConstant(int i) + bool AddConstant(int i, bool* overflows = nullptr) { + if (overflows != nullptr) + { + *overflows = false; + } switch (type) { case keDependent: @@ -143,6 +147,10 @@ struct Limit case keConstant: if (IntAddOverflows(cns, i)) { + if (overflows != nullptr) + { + *overflows = true; + } return false; } cns += i; @@ -420,21 +428,6 @@ struct RangeOps }); } - static bool DoesAddOverflow(Range& r1, Range& r2) - { - bool r1loConst = r1.LowerLimit().IsConstant() || r1.LowerLimit().IsBinOpArray(); - bool r1hiConst = r1.UpperLimit().IsConstant() || r1.UpperLimit().IsBinOpArray(); - bool r2loConst = r2.LowerLimit().IsConstant() || r2.LowerLimit().IsBinOpArray(); - bool r2hiConst = r2.UpperLimit().IsConstant() || r2.UpperLimit().IsBinOpArray(); - - if ((r1loConst && r2loConst && IntAddOverflows(r1.LowerLimit().GetConstant(), r2.LowerLimit().GetConstant())) || - (r1hiConst && r2hiConst && IntAddOverflows(r1.UpperLimit().GetConstant(), r2.UpperLimit().GetConstant()))) - { - return true; - } - return false; - } - static Range Subtract(Range& r1, Range& r2) { return ApplyRangeOp(r1, r2, [](Limit& a, Limit& b) {