The result of an isinst helper call inherits the isNonNull property of its input, but a failed isinst returns null. The optimizer then drops the null check and constant-folds a type comparison, producing a silently wrong answer.
Minimal Repro
using System;
using System.Runtime.CompilerServices;
public class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool Test<T>()
{
object o = "hi";
IComparable<T>? c = o as IComparable<T>; // null when T is not string
return c!.GetType() == typeof(string);
}
public static void Main()
{
Console.WriteLine("Test<string>() = " + Test<string>());
try { Console.WriteLine("Test<object>() = " + Test<object>()); }
catch (NullReferenceException) { Console.WriteLine("Test<object>(): NullReferenceException (expected)"); }
}
}
Expected
Test<string>() = True
Test<object>(): NullReferenceException (expected)
Actual
Test<string>() = True
Test<object>() = True
Notes
Correct under DOTNET_JITMinOpts=1, so this is an optimizer bug. Also repros on released .NET 10.0.12.
The shared-generic (__Canon) instantiation folds to mov eax, 1 / ret: the isinst helper call is dead-coded, the implicit null check is gone and the comparison is constant-folded.
Compiler::gtGetHelperCallClassHandle falls back to the value argument for CORINFO_HELP_ISINSTANCEOF* with an interface/unknown target and copies its isNonNull; gtFoldTypeCompare then trusts it.
The result of an
isinsthelper call inherits theisNonNullproperty of its input, but a failedisinstreturns null. The optimizer then drops the null check and constant-folds a type comparison, producing a silently wrong answer.Minimal Repro
Expected
Actual
Notes
Correct under
DOTNET_JITMinOpts=1, so this is an optimizer bug. Also repros on released .NET 10.0.12.The shared-generic (
__Canon) instantiation folds tomov eax, 1/ret: theisinsthelper call is dead-coded, the implicit null check is gone and the comparison is constant-folded.Compiler::gtGetHelperCallClassHandlefalls back to the value argument forCORINFO_HELP_ISINSTANCEOF*with an interface/unknown target and copies itsisNonNull;gtFoldTypeComparethen trusts it.