JIT: (bug) Loop cloning treats a jagged-array row `a[k]` as loop-invariant, dropping bounds checks when the row is replaced inside the loop
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Minimal repro
```csharp
using System;
using System.Runtime.CompilerServices;
public class Program
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static int Test(int[][] rows, int k, int n)
{
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += rows[k][i];
if (i == 0) rows[k] = new int[1] { 999 }; // rows[k][1] must now throw
}
return sum;
}
public static int Main()
{
int[][] rows = new int[1][];
rows[0] = new int[16];
for (int j = 0; j < 16; j++) rows[0][j] = j + 1;
try { Console.WriteLine($"Actual: sum={Test(rows, 0, 8)}"); return 1; }
catch (IndexOutOfRangeException) { Console.WriteLine("Actual: IndexOutOfRangeException"); return 100; }
}
}
```
Run: `corerun repro.dll` (Checked or Release JIT, .NET 11 main @ b44cd904110)
### Expected
`Actual: IndexOutOfRangeException`, exit code 100.
### Actual
`Actual: sum=1`, exit code 1 — no exception; the loop reads 7 words past the 1-element row.
### Notes
`DOTNET_JitCloneLoops=0` restores the exception. `optCanOptimizeByLoopCloning` checks only the `rows`/`k` locals for invariance, never the heap cell `rows[k]`, so the preheader `rows[k].Length` guard is stale.
The store variant (`rows[k][i] = v` with `new int[1]`, `n = 32`) writes 31 words past the row and corrupts the GC heap: `Assert failure: m_alignpad == 0` in `ObjHeader::Validate` (`src/coreclr/vm/syncblk.cpp:1840`).
Contributor guide
Assessment
This issue has not been assessed yet.