Skip to content
6 changes: 6 additions & 0 deletions src/coreclr/jit/compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3319,6 +3319,12 @@ inline bool Compiler::IsPotentialGCSafePoint(GenTree* tree) const
}
}

// A tree that may throw can call a throw helper, which is a gc safe point.
if ((tree->gtFlags & GTF_EXCEPT) != 0)
{
return true;
}

// TYP_STRUCT-typed stores might be converted into calls (with gc safe points) in Lower.
// This is quite a conservative fix as it's hard to prove Lower won't do it at this point.
if (tree->OperIsLocalStore())
Expand Down
107 changes: 107 additions & 0 deletions src/tests/JIT/Regression_ro_2/Runtime_135966.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.CompilerServices;
using Xunit;

// Each victim throws before its GC ref local is first stored, so a GC while the throw
// helper runs or the exception is dispatched must not see DirtyStack's garbage in it.
public class Runtime_135966
{
private class Holder
{
public object Value = new object();
}

[InlineArray(32)]
private struct Garbage
{
private nint _element;
}

[Fact]
public static void TestEntryPoint()
{
int[] array = new int[1];

for (int i = 0; i < 10; i++)
{
DirtyStack();

bool caught = false;
try
{
NullCheckVictim(null);
}
catch (NullReferenceException) when (Collect())
{
caught = true;
}

Assert.True(caught);

DirtyStack();

caught = false;
try
{
BoundsCheckVictim(array, 5);
}
catch (IndexOutOfRangeException) when (Collect())
{
caught = true;
}

Assert.True(caught);
}
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void DirtyStack()
{
Garbage garbage = default;
Span<nint> span = garbage;
span.Fill(0x1000);
Consume(span);
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void Consume(Span<nint> span)
{
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static object NullCheckVictim(Holder holder)
{
object value = holder.Value;
KeepAlive(value);
return value;
}

// The local is address-exposed, so it is an untracked GC slot on every target.
[MethodImpl(MethodImplOptions.NoInlining)]
private static object BoundsCheckVictim(int[] array, int index)
{
int element = array[index];
object value = array;
KeepAlive(ref value);
return element == 0 ? value : null;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void KeepAlive(object value)
{
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void KeepAlive(ref object value)
{
}

private static bool Collect()
{
GC.Collect();
return true;
}
}
Loading