JIT: (bug) Wrong exception order for `stelem.ref`: the array range check runs before the value expression
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
For `stelem.ref` the value operand must be evaluated before the array store's range check. When morph
demotes the array-store helper call into a plain `STOREIND`, the range check ends up first and the wrong
exception is reported.
### Minimal Repro
```csharp
using System;
using System.Runtime.CompilerServices;
class C
{
public object F;
}
class Program
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static object[] Make(int len) => new object[len];
[MethodImpl(MethodImplOptions.NoInlining)]
static void Use(object[] a) { }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static void Test(int len, int index, C c)
{
object[] arr = Make(len);
arr[index] = c.F; // 'c.F' must be evaluated before the store's range check
Use(arr);
}
static void Main()
{
try
{
Test(1, 5, null);
Console.WriteLine("no exception");
}
catch (Exception e)
{
Console.WriteLine(e.GetType().Name);
}
}
}
```
### Expected
```
NullReferenceException
```
### Actual
```
IndexOutOfRangeException
```
The bounds check precedes the faulting load of `c.F`:
```asm
cmp rcx, rdx
jae SHORT G_M62848_IG04 ; <-- range check first
lea rcx, bword ptr [rdi+8*rcx+0x10]
mov rdx, gword ptr [rbx+0x08] ; <-- 'c.F' (must fault first) second
call CORINFO_HELP_ASSIGN_REF
```
### Notes
The `CEE_STELEM_REF` helper-call path in `importer.cpp` does not apply the `ARR_ST` ("strict ordering of
exceptions for array store") spill that the inline `INDEX_ADDR` path applies. When `gtCanSkipCovariantStoreCheck`
later succeeds in `fgMorphCall` and rewrites the helper call into `STOREIND(INDEX_ADDR(arr, index), value)`,
that ordering guarantee is lost. Writing `new object[len]` directly (no inlined `Make`) takes the inline path
and behaves correctly. Also reproduces on .NET 10.0.12, so not a `main`-only regression.
Contributor guide
Research direction
Reproduce the exception-ordering difference with the minimal C# program, then inspect importer.cpp around the CEE_STELEM_REF helper-call path and the ARR_ST spill used by INDEX_ADDR. Follow fgMorphCall and gtCanSkipCovariantStoreCheck to understand where the ordering is lost. Done means the repro reports NullReferenceException and the behavior is covered by a regression test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, csharp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 65/100