diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 688bf29079e08..76aab1f8b2d31 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -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(); @@ -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() && @@ -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; } @@ -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); @@ -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) @@ -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++) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 69f16bec6ebdd..f30fd7190f93c 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -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 @@ -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; @@ -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; diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index 777080c759017..f8b62de3e35d4 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -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; @@ -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(); diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 47024106985ee..76ac3e7601768 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -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 @@ -9407,6 +9411,7 @@ MethodTableBuilder::LoadExactInterfaceMap(MethodTable *pMT) if (retry) { + pMT->GetWriteableDataForWrite()->SetMayHaveOpenInterfacesInInterfaceMap(); retryWithExactInterfaces = true; } } while (retry); diff --git a/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.cs b/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.cs index 0a091e138a876..e74a23632c7b9 100644 --- a/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.cs +++ b/src/tests/Loader/classloader/generics/Instantiation/Positive/CuriouslyRecurringThroughInterface.cs @@ -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 @@ -10,14 +16,85 @@ class CuriouslyRecurringThroughInterface : { } - class Program + class BaseClassWhereAImplementsB where A : B {} + interface ICuriouslyRecurring2 : IGeneric> + { + } + class DerivedCuriouslyRecurringThroughInterface : BaseClassWhereAImplementsB,ICuriouslyRecurring2>, ICuriouslyRecurring2 + { + } + + 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); - 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); + Console.WriteLine("Found type: {0}", _o); } } -} \ No newline at end of file +}