JIT: (bug) Loop cloning removes bounds checks from an unsigned countdown loop whose induction variable underflows
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
Loop cloning treats an unsigned decrementing loop as monotone, so the IV can wrap below the limit (`0 - k` → huge `uint`) and the bounds-check-free fast clone reads far out of bounds.
### Minimal Repro
```csharp
using System;
using System.Runtime.CompilerServices;
public static class P
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static int Test(int[] a, uint n, uint lo)
{
int sum = 0;
for (uint i = n; i > lo; i -= 3)
{
sum += a[i];
}
return sum;
}
public static void Main()
{
int[] a = new int[8];
try
{
Console.WriteLine(Test(a, 7, 0));
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("IndexOutOfRangeException");
}
}
}
```
`i` walks `7, 4, 1`, then `1 - 3` wraps to `0xFFFFFFFE`, which is still `> lo` under the unsigned comparison, so `a[i]` must throw.
### Expected
```
IndexOutOfRangeException
```
### Actual
```
Fatal error.
```
Exit code `0xC0000005`. The fast clone indexes `a` with `0xFFFFFFFE` (~16 GB past the array base) — an out-of-bounds read from plain safe C#.
### Notes
- `NaturalLoopIterInfo::IsDecreasingLoop()` (`src/coreclr/jit/flowgraph.cpp`) does not consider `TestTree->IsUnsigned()`, so an unsigned `GT_GT` is accepted as a plain decreasing test even though it is not monotone under wraparound.
- The derived cloning conditions `a != null && n >= 0 && lo >= 0 && n < a.Length` only constrain the initial IV; nothing proves the IV cannot step past the limit.
- Any `n`/`lo` with `(n - lo) % step != 0` hits it. Fix direction: bail out (or add conditions, e.g. `step` divides `init - limit`) when the test is unsigned.
- Not a regression: also reproduces on released .NET 10.0.12.
Contributor guide
Assessment
This issue has not been assessed yet.