JIT: (bug) Bounds check incorrectly eliminated — range analysis ignores signed underflow of `x + negativeConstant`
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
RangeCheck removes an array bounds check for an index computed as `x + ` without accounting for signed underflow of the addition. The result is a silent out-of-bounds read instead of `IndexOutOfRangeException`.
### Minimal Repro
```csharp
using System;
using System.Runtime.CompilerServices;
public static class Program
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static int Test(int x)
{
int[] arr = new int[8];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = i + 1;
}
if (x < int.MaxValue)
{
int y = x + (-2147483640);
if (y >= 0)
{
return arr[y];
}
}
return -1;
}
public static void Main()
{
Console.WriteLine(Test(int.MinValue));
}
}
```
### Expected
`int.MinValue + (-2147483640)` wraps to `8`, and `arr.Length == 8`, so the indexer must throw:
```
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Program.Test(Int32 x)
at Program.Main()
```
### Actual
```
UB
```
No `CORINFO_HELP_RNGCHKFAIL` is emitted. `DOTNET_JITMinOpts=1` throws correctly.
### Notes
- x64, `main` @ `6ed2ba318dcaa936da60f7d6bc9eb30cad54fc0c`; also reproduces on .NET 10.0.12 Release, so not a regression (not bisected further).
- Also happens with a heap array (store `arr` into a static to force escape), turning it into an out-of-bounds *heap* read.
- Root cause: from `x <= int.MaxValue - 1` the JIT derives `y <= 6`, and with the dominating `y >= 0` concludes `y in [0, 6]`. `RangeCheck::AddOverflows` / `GetLimitMax` (`src/coreclr/jit/rangecheck.cpp`) only test the operands' *upper* limits, so underflow at the lower end — one operand a negative constant, the other's lower limit unconstrained at `int.MinValue` — is never detected.
Contributor guide
Research direction
Start in src/coreclr/jit/rangecheck.cpp, focusing on RangeCheck::AddOverflows and GetLimitMax, and reproduce the issue with the provided C# program using the stated runtime configuration. Done means signed underflow is accounted for so the array access retains its bounds check and the repro throws IndexOutOfRangeException rather than producing an out-of-bounds read.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100