dotnet / dotnet/runtime

JIT: (bug) inlined callee's write to a caller local runs before the receiver null check throws

Open
#133,714 1 comment 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

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

struct S { public int A, B; }

class Node
{
public int F = 9;
public int Get(ref S s) { s = default; return F; }
}

class P
{
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static void Foo(Node n)
{
S s;
s.A = 1;
s.B = 2;
try { Console.WriteLine("no throw: " + n.Get(ref s)); }
catch (NullReferenceException) { Console.WriteLine("caught, s = " + s.A + "," + s.B); }
}

static void Main() => Foo(null);
}
```

**Expected:** `caught, s = 1,2` — the `callvirt` null check must throw before any of the callee body runs, so `s` is untouched.
**Actual:** `caught, s = 0,0` — the callee's `s = default` executed before the NullReferenceException.

Checked corerun, `-optimize+`: `0,0` with tiering on and with `DOTNET_TieredCompilation=0`; the same program compiled `-optimize-` prints `1,2`.
Matches the report: the substituted `initobj` becomes a plain `GT_STORE_LCL_VAR` of the caller local carrying only `GTF_ASG`, so the `GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS` scan in `impInlineIsGuaranteedThisDerefBeforeAnySideEffects` (src\coreclr\jit\importer.cpp) treats it as harmless, `thisDereferencedFirst` is set, and `fgInlinePrependStatements` drops the call-site null check.

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.