Description
I'm back with another assert that I've hit while running checked builds. This time it looks like the assert itself might be checking the wrong thing.
CastHelpers.IsNullableForType asserts when typeMT is an open Nullable<T> (the generic type definition). This is reachable from ordinary managed code, for example any IsAssignableFrom where the destination type is typeof(Nullable<>), so a checked/Debug runtime trips on input that the release runtime handles fine.
The offending line is CastHelpers.cs#L598:
internal static bool IsNullableForType(MethodTable* typeMT, MethodTable* boxedMT)
{
if (!typeMT->IsNullable)
return false;
Debug.Assert(typeMT->InstantiationArg0() == **typeMT->PerInstInfo); // <-- here
MethodTable* pMTNullableArg = **typeMT->PerInstInfo;
...
}
MethodTable.InstantiationArg0() is an FCall to MethodTableNative::InstantiationArg0, which does:
return mt->GetInstantiation()[0].AsMethodTable();
and TypeHandle::AsMethodTable() asserts !IsTypeDesc(). For an open Nullable<T>, instantiation argument 0 is a TypeVarTypeDesc, not a MethodTable, so the assert fires.
The assert is the only thing on this path that calls InstantiationArg0(). The line below it reads **typeMT->PerInstInfo directly, and that stays valid for an open generic: it gives back a pointer that never matches boxedMT, so the method returns false like it should. As far as I can tell the problem is only in the diagnostic and release builds are unaffected.
Reproduction Steps
On a checked/Debug CoreCLR:
Console.WriteLine(typeof(int?).IsAssignableFrom(typeof(int))); // control
Console.WriteLine(typeof(Nullable<>).IsAssignableFrom(typeof(int))); // asserts
Path in: RuntimeType.IsAssignableFrom → TypeHandle.CanCastToForReflection → CanCastToWorker → CastHelpers.IsNullableForType.
Expected behavior
Prints True then False without asserting. typeof(Nullable<>).IsAssignableFrom(x) is a legal call that works fine in release.
Actual behavior
The first line prints True. The second one asserts:
ASSERT FAILED
Expression: !IsTypeDesc()
Location: line 74 in .../src/coreclr/vm/typehandle.inl
Function: AsMethodTable
Regression?
Not sure. Claude thinks it was #109135 but I've been lied to before.
Known Workarounds
Use release instead of a checked or debug build.
Configuration
- .NET 10 (observed on
10.0.10-dev)
- Reproduced on Windows x64, macOS arm64 and Linux x64, so it doesn't look architecture or OS specific
Other information
No response
Description
I'm back with another assert that I've hit while running checked builds. This time it looks like the assert itself might be checking the wrong thing.
CastHelpers.IsNullableForTypeasserts whentypeMTis an openNullable<T>(the generic type definition). This is reachable from ordinary managed code, for example anyIsAssignableFromwhere the destination type istypeof(Nullable<>), so a checked/Debug runtime trips on input that the release runtime handles fine.The offending line is
CastHelpers.cs#L598:MethodTable.InstantiationArg0()is an FCall toMethodTableNative::InstantiationArg0, which does:and
TypeHandle::AsMethodTable()asserts!IsTypeDesc(). For an openNullable<T>, instantiation argument 0 is aTypeVarTypeDesc, not aMethodTable, so the assert fires.The assert is the only thing on this path that calls
InstantiationArg0(). The line below it reads**typeMT->PerInstInfodirectly, and that stays valid for an open generic: it gives back a pointer that never matchesboxedMT, so the method returnsfalselike it should. As far as I can tell the problem is only in the diagnostic and release builds are unaffected.Reproduction Steps
On a checked/Debug CoreCLR:
Path in:
RuntimeType.IsAssignableFrom→TypeHandle.CanCastToForReflection→CanCastToWorker→CastHelpers.IsNullableForType.Expected behavior
Prints
TruethenFalsewithout asserting.typeof(Nullable<>).IsAssignableFrom(x)is a legal call that works fine in release.Actual behavior
The first line prints
True. The second one asserts:Regression?
Not sure. Claude thinks it was #109135 but I've been lied to before.
Known Workarounds
Use release instead of a checked or debug build.
Configuration
10.0.10-dev)Other information
No response