dotnet / dotnet/runtime

JIT: (bug) Loop hoisting moves a throwing division above a bounds check, so the wrong exception is raised

Open
#133,585 2 comments 0 reactions 1 assignee Claimed by @AndyAyersMS View on GitHub
area-CodeGen-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

## Repro

```csharp
using System;
using System.Runtime.CompilerServices;

internal static class Program
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
private static int Test(int[] arr, int idx, int a, int b, int n)
{
int r = 0;
for (int i = 0; i < n; i++)
{
r += arr[idx];
r += a / b;
}
return r;
}

private static void Main()
{
try
{
Console.WriteLine(Test(new int[1], 5, 1, 0, 5));
}
catch (Exception e)
{
Console.WriteLine(e.GetType().Name);
}
}
}
```

`arr` has length 1 and `idx` is 5, so the very first statement of the first iteration must fault
before `a / b` is ever evaluated.

## Expected

```
IndexOutOfRangeException
```

## Actual

```
DivideByZeroException
```

Also reproduces on the **Release** runner with no environment variables set at all.
`DOTNET_JitDoLoopHoisting=0` and `DOTNET_JITMinOpts=1` both print the correct
`IndexOutOfRangeException`.

The invariant `arr[idx]` bounds check is not hoistable, but because it is loop-invariant it does
not close loop hoisting's exception-ordering barrier, so the invariant `a / b` is hoisted into the
preheader and executes ahead of it.

## Platform

Windows x64, .NET 11 (dotnet/runtime main @ a0b86b1e2a, Checked and Release builds). No special
env vars or CPU features required.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.