Skip to content

Commit

Permalink
Backport of Handle open types to appear in interface maps (dotnet#97733
Browse files Browse the repository at this point in the history
…) to release/8.0-staging

- Reflection IsAssignableFrom api
- As well as constraint checking
  • Loading branch information
davidwrighton committed Feb 8, 2024
1 parent c2415d7 commit 3ceaa63
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 14 deletions.
13 changes: 7 additions & 6 deletions src/coreclr/vm/methodtable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1433,7 +1433,7 @@ BOOL MethodTable::CanCastToInterface(MethodTable *pTargetMT, TypeHandlePairList
if (CanCastByVarianceToInterfaceOrDelegate(pTargetMT, pVisited))
return TRUE;

if (pTargetMT->IsSpecialMarkerTypeForGenericCasting())
if (pTargetMT->IsSpecialMarkerTypeForGenericCasting() && !GetWriteableData()->MayHaveOpenInterfacesInInterfaceMap())
return FALSE; // The special marker types cannot be cast to (at this time, they are the open generic types, so they are however, valid input to this method).

InterfaceMapIterator it = IterateInterfaceMap();
Expand Down Expand Up @@ -1470,7 +1470,7 @@ BOOL MethodTable::CanCastByVarianceToInterfaceOrDelegate(MethodTable *pTargetMT,

// Shortcut for generic approx type scenario
if (pMTInterfaceMapOwner != NULL &&
!pMTInterfaceMapOwner->ContainsGenericVariables() &&
!pMTInterfaceMapOwner->GetWriteableData()->MayHaveOpenInterfacesInInterfaceMap() &&
IsSpecialMarkerTypeForGenericCasting() &&
GetTypeDefRid() == pTargetMT->GetTypeDefRid() &&
GetModule() == pTargetMT->GetModule() &&
Expand All @@ -1497,7 +1497,7 @@ BOOL MethodTable::CanCastByVarianceToInterfaceOrDelegate(MethodTable *pTargetMT,
for (DWORD i = 0; i < inst.GetNumArgs(); i++)
{
TypeHandle thArg = inst[i];
if (IsSpecialMarkerTypeForGenericCasting() && pMTInterfaceMapOwner && !pMTInterfaceMapOwner->ContainsGenericVariables())
if (IsSpecialMarkerTypeForGenericCasting() && pMTInterfaceMapOwner && !pMTInterfaceMapOwner->GetWriteableData()->MayHaveOpenInterfacesInInterfaceMap())
{
thArg = pMTInterfaceMapOwner;
}
Expand Down Expand Up @@ -1957,7 +1957,7 @@ NOINLINE BOOL MethodTable::ImplementsInterface(MethodTable *pInterface)
{
WRAPPER_NO_CONTRACT;

if (pInterface->IsSpecialMarkerTypeForGenericCasting())
if (pInterface->IsSpecialMarkerTypeForGenericCasting() && !GetWriteableData()->MayHaveOpenInterfacesInInterfaceMap())
return FALSE; // The special marker types cannot be cast to (at this time, they are the open generic types, so they are however, valid input to this method).

return ImplementsInterfaceInline(pInterface);
Expand All @@ -1974,7 +1974,7 @@ BOOL MethodTable::ImplementsEquivalentInterface(MethodTable *pInterface)
}
CONTRACTL_END;

if (pInterface->IsSpecialMarkerTypeForGenericCasting())
if (pInterface->IsSpecialMarkerTypeForGenericCasting() && !GetWriteableData()->MayHaveOpenInterfacesInInterfaceMap())
return FALSE; // The special marker types cannot be cast to (at this time, they are the open generic types, so they are however, valid input to this method).

// look for exact match first (optimize for success)
Expand Down Expand Up @@ -9568,12 +9568,13 @@ PTR_MethodTable MethodTable::InterfaceMapIterator::GetInterface(MethodTable* pMT
GC_TRIGGERS;
THROWS;
PRECONDITION(m_i != (DWORD) -1 && m_i < m_count);
PRECONDITION(CheckPointer(pMTOwner));
POSTCONDITION(CheckPointer(RETVAL));
}
CONTRACT_END;

MethodTable *pResult = m_pMap->GetMethodTable();
if (pResult->IsSpecialMarkerTypeForGenericCasting() && !pMTOwner->ContainsGenericVariables())
if (pResult->IsSpecialMarkerTypeForGenericCasting() && !pMTOwner->GetWriteableData()->MayHaveOpenInterfacesInInterfaceMap())
{
TypeHandle ownerAsInst[MaxGenericParametersForSpecialMarkerType];
for (DWORD i = 0; i < MaxGenericParametersForSpecialMarkerType; i++)
Expand Down
15 changes: 13 additions & 2 deletions src/coreclr/vm/methodtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ struct MethodTableWriteableData
enum_flag_IsNotFullyLoaded = 0x00000040,
enum_flag_DependenciesLoaded = 0x00000080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED

// enum_unused = 0x00000100,
enum_flag_MayHaveOpenInterfaceInInterfaceMap = 0x00000100,

enum_flag_CanCompareBitsOrUseFastGetHashCode = 0x00000200, // Is any field type or sub field type overrode Equals or GetHashCode
enum_flag_HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x00000400, // Whether we have checked the overridden Equals or GetHashCode
Expand Down Expand Up @@ -393,6 +393,17 @@ struct MethodTableWriteableData
m_dwFlags &= ~(MethodTableWriteableData::enum_flag_HasApproxParent);

}

inline void SetMayHaveOpenInterfacesInInterfaceMap()
{
LIMITED_METHOD_CONTRACT;
InterlockedOr((LONG*)&m_dwFlags, MethodTableWriteableData::enum_flag_MayHaveOpenInterfaceInInterfaceMap);
}

inline bool MayHaveOpenInterfacesInInterfaceMap() const
{
return !!(m_dwFlags & MethodTableWriteableData::enum_flag_MayHaveOpenInterfaceInInterfaceMap);
}
}; // struct MethodTableWriteableData

typedef DPTR(MethodTableWriteableData) PTR_MethodTableWriteableData;
Expand Down Expand Up @@ -1970,7 +1981,7 @@ class MethodTable
if (pCurrentMethodTable->HasSameTypeDefAs(pMT) &&
pMT->HasInstantiation() &&
pCurrentMethodTable->IsSpecialMarkerTypeForGenericCasting() &&
!pMTOwner->ContainsGenericVariables() &&
!pMTOwner->GetWriteableData()->MayHaveOpenInterfacesInInterfaceMap() &&
pMT->GetInstantiation().ContainsAllOneType(pMTOwner))
{
exactMatch = true;
Expand Down
3 changes: 1 addition & 2 deletions src/coreclr/vm/methodtable.inl
Original file line number Diff line number Diff line change
Expand Up @@ -1395,7 +1395,6 @@ FORCEINLINE BOOL MethodTable::ImplementsInterfaceInline(MethodTable *pInterface)
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(pInterface->IsInterface()); // class we are looking up should be an interface
PRECONDITION(!pInterface->IsSpecialMarkerTypeForGenericCasting());
}
CONTRACTL_END;

Expand Down Expand Up @@ -1428,7 +1427,7 @@ FORCEINLINE BOOL MethodTable::ImplementsInterfaceInline(MethodTable *pInterface)
while (--numInterfaces);

// Second scan, looking for the curiously recurring generic scenario
if (pInterface->HasInstantiation() && !ContainsGenericVariables() && pInterface->GetInstantiation().ContainsAllOneType(this))
if (pInterface->HasInstantiation() && !GetWriteableData()->MayHaveOpenInterfacesInInterfaceMap() && pInterface->GetInstantiation().ContainsAllOneType(this))
{
numInterfaces = GetNumInterfaces();
pInfo = GetInterfaceMap();
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/vm/methodtablebuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9341,6 +9341,10 @@ MethodTableBuilder::LoadExactInterfaceMap(MethodTable *pMT)
// to represent a type instantiated over its own generic variables, and the special marker type is currently the open type
// and we make this case distinguishable by simply disallowing the optimization in those cases.
bool retryWithExactInterfaces = !pMT->IsValueType() || pMT->IsSharedByGenericInstantiations() || pMT->ContainsGenericVariables();
if (retryWithExactInterfaces)
{
pMT->GetWriteableDataForWrite()->SetMayHaveOpenInterfacesInInterfaceMap();
}

DWORD nAssigned = 0;
do
Expand Down Expand Up @@ -9407,6 +9411,7 @@ MethodTableBuilder::LoadExactInterfaceMap(MethodTable *pMT)

if (retry)
{
pMT->GetWriteableDataForWrite()->SetMayHaveOpenInterfacesInInterfaceMap();
retryWithExactInterfaces = true;
}
} while (retry);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// 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;

namespace CuriouslyRecurringPatternThroughInterface
{
interface IGeneric<T_IGeneric>
Expand All @@ -10,14 +16,85 @@ class CuriouslyRecurringThroughInterface<T_CuriouslyRecurringThroughInterface> :
{
}

class Program
class BaseClassWhereAImplementsB<A,B> where A : B {}
interface ICuriouslyRecurring2<T_ICuriouslyRecurring> : IGeneric<DerivedCuriouslyRecurringThroughInterface<T_ICuriouslyRecurring>>
{
}
class DerivedCuriouslyRecurringThroughInterface<T_CuriouslyRecurringThroughInterface> : BaseClassWhereAImplementsB<DerivedCuriouslyRecurringThroughInterface<T_CuriouslyRecurringThroughInterface>,ICuriouslyRecurring2<T_CuriouslyRecurringThroughInterface>>, ICuriouslyRecurring2<T_CuriouslyRecurringThroughInterface>
{
}

public class Program
{
static object _o;
static int Main()

public static int Main()
{
TestIfCuriouslyRecurringInterfaceCanBeLoaded();
TestIfCuriouslyRecurringInterfaceCanCast();
TestIfCuriouslyRecurringInterfaceCanBeUsedAsConstraint();
TestIfCuriouslyRecurringInterfaceCanBeUsedAsConstraintWorker();
return 100; // Failures cause exceptions
}

public static void TestIfCuriouslyRecurringInterfaceCanBeLoaded()
{
Console.WriteLine("TestIfCuriouslyRecurringInterfaceCanBeLoaded");

// Test that the a generic using a variant of the curiously recurring pattern involving an interface can be loaded.
_o = typeof(CuriouslyRecurringThroughInterface<int>);
return 100;
Console.WriteLine("Found type: {0}", _o);
Console.WriteLine("Curiously recurring interface: {0}", typeof(ICuriouslyRecurring<>));
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]: {0}", typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]);
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces().Length: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces().Length);
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0]: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0]);
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments().Length: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments().Length);
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0]: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0]);
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetGenericArguments()[0]: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetGenericArguments()[0]);
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetGenericArguments()[0]==typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetGenericArguments()[0]==typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]);
if (!(typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetGenericArguments()[0]==typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]))
{
throw new Exception("Fail checking for condition that should be true - 2");
}

Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces().Length: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces().Length);

// On CoreCLR this gets the Open type, which isn't really correct, but it has been that way for a very long time
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0]: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0]);
Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0]==typeof(ICuriouslyRecurring<>): {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0]==typeof(ICuriouslyRecurring<>));

Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0].GetGenericTypeDefinition()==typeof(ICuriouslyRecurring<>): {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0].GetGenericTypeDefinition()==typeof(ICuriouslyRecurring<>));

Console.WriteLine("typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0].GetGenericArguments()[0]==typeof(ICuriouslyRecurring<>)GetGenericArguments()[0]: {0}", typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0].GetGenericArguments()[0]==typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]);
if (!(typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0].GetInterfaces()[0].GetGenericArguments()[0]==typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]))
{
throw new Exception("Fail checking for condition that should be true - 2");
}
}

public static void TestIfCuriouslyRecurringInterfaceCanCast()
{
Console.WriteLine("TestIfCuriouslyRecurringInterfaceCanCast");
Console.WriteLine("typeof(ICuriouslyRecurring<>).MakeGenericType(typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]).IsAssignableFrom(typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0]): {0}", typeof(ICuriouslyRecurring<>).MakeGenericType(typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]).IsAssignableFrom(typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0]));
if (!(typeof(ICuriouslyRecurring<>).MakeGenericType(typeof(ICuriouslyRecurring<>).GetGenericArguments()[0]).IsAssignableFrom(typeof(ICuriouslyRecurring<>).GetInterfaces()[0].GetGenericArguments()[0])))
{
throw new Exception("Fail");
}
}

public static void TestIfCuriouslyRecurringInterfaceCanBeUsedAsConstraint()
{
Console.WriteLine("TestIfCuriouslyRecurringInterfaceCanBeUsedAsConstraint");
TestIfCuriouslyRecurringInterfaceCanBeUsedAsConstraintWorker();
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void TestIfCuriouslyRecurringInterfaceCanBeUsedAsConstraintWorker()
{
// Test that the a generic using a variant of the curiously recurring pattern involving an interface and constraint can be loaded.
// This test is just like TestIfCuriouslyRecurringInterfaceCanBeLoaded, except that it is structured so that we perform a cast via a constraint at type load time
_o = typeof(DerivedCuriouslyRecurringThroughInterface<int>);
Console.WriteLine("Found type: {0}", _o);
}
}
}
}

0 comments on commit 3ceaa63

Please sign in to comment.