diff --git a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Validation.cs b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Validation.cs index 018f576aa1b331..9a9bc97f32722c 100644 --- a/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Validation.cs +++ b/src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Validation.cs @@ -1,6 +1,8 @@ // 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.Collections.Generic; using Internal.TypeSystem; using Debug = System.Diagnostics.Debug; @@ -11,13 +13,180 @@ namespace ILCompiler // in unpredictable spots. public partial class CompilerTypeSystemContext { + [ThreadStatic] + private static List t_typeLoadCheckInProgressStack; + private static List EmptyList = new List(); + private readonly ValidTypeHashTable _validTypes = new ValidTypeHashTable(); + /// /// Ensures that the type can be fully loaded. The method will throw one of the type system /// exceptions if the type is not loadable. /// public void EnsureLoadableType(TypeDesc type) { - _validTypes.GetOrCreateValue(type); + if (type == null) + return; + + if (_validTypes.Contains(type)) + return; + + // Use a scheme where we push a stack of types in the process of loading + // When the stack pops, without throwing an exception, the type will be marked as being detected as successfully loadable. + // We need this complex scheme, as types can have circular dependencies. In addition, due to circular references, we can + // be forced to move when a type is successfully marked as loaded up the stack to an earlier call to EnsureLoadableType + // + // For example, consider the following case: + // interface IInterface {} + // interface IPassThruInterface : IInterface {} + // interface ISimpleInterface {} + // class A : IInterface>, IPassThruInterface>, ISimpleInterface {} + // class B : A {} + // + // We call EnsureLoadableType on B + // + // This will generate the following interesting stacks of calls to EnsureLoadableType + // + // B -> A -> B + // This stack indicates that A can only be considered loadable if B is considered loadable, so we must defer marking + // A as loadable until we finish processing B. + // + // B -> A -> ISimpleInterface + // Since examining ISimpleInterface does not have any dependency on B or A, it can be marked as loadable as soon + // as we finish processing it. + // + // B -> A -> IInterface> -> A + // This stack indicates that IInterface> can be considered loadable if A is considered loadable. We must defer + // marking IInterface> as loadable until we are able to mark A as loadable. Based on the stack above, that can + // only happen once B is considered loadable. + // + // B -> A -> IPassthruInterface> -> IInterface> + // This stack indicates that IPassthruInterface> can be considered loadable if IInterface> is considered + // loadable. If this happens after the IInterface> is marked as being loadable once B is considered loadable + // then we will push the loadibility marking to the B level at this point. OR we will continue to recurse and the logic + // will note that IInterface> needs A needs B which will move the marking up at that point. + + if (PushTypeLoadInProgress(type)) + return; + + bool threwException = true; + try + { + EnsureLoadableTypeUncached(type); + threwException = false; + } + finally + { + PopTypeLoadabilityCheckInProgress(threwException); + } + } + + private sealed class TypeLoadabilityCheckInProgress + { + public TypeDesc TypeInLoadabilityCheck; + public bool MarkTypeAsSuccessfullyLoadedIfNoExceptionThrown; + public List OtherTypesToMarkAsSuccessfullyLoaded; + + public void AddToOtherTypesToMarkAsSuccessfullyLoaded(TypeDesc type) + { + if (OtherTypesToMarkAsSuccessfullyLoaded == EmptyList) + { + OtherTypesToMarkAsSuccessfullyLoaded = new List(); + } + + Debug.Assert(!OtherTypesToMarkAsSuccessfullyLoaded.Contains(type)); + OtherTypesToMarkAsSuccessfullyLoaded.Add(type); + } + } + + // Returns true to indicate the type should be considered to be loadable (although it might not be, actually safety may require more code to execute) + private static bool PushTypeLoadInProgress(TypeDesc type) + { + t_typeLoadCheckInProgressStack ??= new List(); + + // Walk stack to see if the specified type is already in the process of being type checked. + int typeLoadCheckInProgressStackOffset = -1; + bool checkingMode = false; // Checking for match on TypeLoadabilityCheck field or in OtherTypesToMarkAsSuccessfullyLoaded. (true for OtherTypesToMarkAsSuccessfullyLoaded) + for (int typeCheckDepth = t_typeLoadCheckInProgressStack.Count - 1; typeCheckDepth >= 0; typeCheckDepth--) + { + if (t_typeLoadCheckInProgressStack[typeCheckDepth].TypeInLoadabilityCheck == type) + { + // The stack contains the interesting type. + if (t_typeLoadCheckInProgressStack[typeCheckDepth].MarkTypeAsSuccessfullyLoadedIfNoExceptionThrown) + { + // And this is the level where the type is known to be successfully loaded. + typeLoadCheckInProgressStackOffset = typeCheckDepth; + break; + } + } + else if (t_typeLoadCheckInProgressStack[typeCheckDepth].OtherTypesToMarkAsSuccessfullyLoaded.Contains(type)) + { + // We've found where the type will be marked as successfully loaded. + typeLoadCheckInProgressStackOffset = typeCheckDepth; + break; + } + } + + if (checkingMode) + { + // If we enabled checkingMode we should always have found the type + Debug.Assert(typeLoadCheckInProgressStackOffset != -1); + } + + if (typeLoadCheckInProgressStackOffset == -1) + { + // The type is not already in the process of being checked for loadability, so return false to indicate that normal load checking should begin + TypeLoadabilityCheckInProgress typeCheckInProgress = new TypeLoadabilityCheckInProgress(); + typeCheckInProgress.TypeInLoadabilityCheck = type; + typeCheckInProgress.OtherTypesToMarkAsSuccessfullyLoaded = EmptyList; + typeCheckInProgress.MarkTypeAsSuccessfullyLoadedIfNoExceptionThrown = true; + t_typeLoadCheckInProgressStack.Add(typeCheckInProgress); + return false; + } + + // Move timing of when types are considered loaded back to the point at which we mark this type as loaded + var typeLoadCheckToAddTo = t_typeLoadCheckInProgressStack[typeLoadCheckInProgressStackOffset]; + for (int typeCheckDepth = t_typeLoadCheckInProgressStack.Count - 1; typeCheckDepth > typeLoadCheckInProgressStackOffset; typeCheckDepth--) + { + if (t_typeLoadCheckInProgressStack[typeCheckDepth].MarkTypeAsSuccessfullyLoadedIfNoExceptionThrown) + { + typeLoadCheckToAddTo.AddToOtherTypesToMarkAsSuccessfullyLoaded(t_typeLoadCheckInProgressStack[typeCheckDepth].TypeInLoadabilityCheck); + t_typeLoadCheckInProgressStack[typeCheckDepth].MarkTypeAsSuccessfullyLoadedIfNoExceptionThrown = false; + } + + foreach (var typeToMove in t_typeLoadCheckInProgressStack[typeCheckDepth].OtherTypesToMarkAsSuccessfullyLoaded) + { + typeLoadCheckToAddTo.AddToOtherTypesToMarkAsSuccessfullyLoaded(typeToMove); + } + + t_typeLoadCheckInProgressStack[typeCheckDepth].OtherTypesToMarkAsSuccessfullyLoaded = EmptyList; + } + + // We are going to report that the type should be considered to be loadable at this stage + return true; + } + + private void PopTypeLoadabilityCheckInProgress(bool exceptionThrown) + { + Debug.Assert(EmptyList.Count == 0); + var typeLoadabilityCheck = t_typeLoadCheckInProgressStack[t_typeLoadCheckInProgressStack.Count - 1]; + t_typeLoadCheckInProgressStack.RemoveAt(t_typeLoadCheckInProgressStack.Count - 1); + + if (!exceptionThrown) + { + if (!typeLoadabilityCheck.MarkTypeAsSuccessfullyLoadedIfNoExceptionThrown) + { + Debug.Assert(typeLoadabilityCheck.OtherTypesToMarkAsSuccessfullyLoaded.Count == 0); + } + + if (typeLoadabilityCheck.MarkTypeAsSuccessfullyLoadedIfNoExceptionThrown) + { + _validTypes.GetOrCreateValue(typeLoadabilityCheck.TypeInLoadabilityCheck); + foreach (var type in typeLoadabilityCheck.OtherTypesToMarkAsSuccessfullyLoaded) + { + _validTypes.GetOrCreateValue(type); + } + } + } } public void EnsureLoadableMethod(MethodDesc method) @@ -37,11 +206,10 @@ private sealed class ValidTypeHashTable : LockFreeReaderHashtable key == value; protected override bool CompareValueToValue(TypeDesc value1, TypeDesc value2) => value1 == value2; - protected override TypeDesc CreateValueFromKey(TypeDesc key) => EnsureLoadableTypeUncached(key); + protected override TypeDesc CreateValueFromKey(TypeDesc key) => key; protected override int GetKeyHashCode(TypeDesc key) => key.GetHashCode(); protected override int GetValueHashCode(TypeDesc value) => value.GetHashCode(); } - private readonly ValidTypeHashTable _validTypes = new ValidTypeHashTable(); private static TypeDesc EnsureLoadableTypeUncached(TypeDesc type) { @@ -53,7 +221,11 @@ private static TypeDesc EnsureLoadableTypeUncached(TypeDesc type) } } - if (type.IsParameterizedType) + if (type.IsGenericParameter) + { + // Generic parameters don't need validation + } + else if (type.IsParameterizedType) { // Validate parameterized types var parameterizedType = (ParameterizedType)type; @@ -161,16 +333,14 @@ private static TypeDesc EnsureLoadableTypeUncached(TypeDesc type) { ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadGeneral, type); } + + ((CompilerTypeSystemContext)type.Context).EnsureLoadableType(typeArg); } - // Don't validate constraints with crossgen2 - the type system is not set up correctly - // and doesn't see generic interfaces on arrays. -#if !READYTORUN if (!defType.IsCanonicalSubtype(CanonicalFormKind.Any) && !defType.CheckConstraints()) { ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadGeneral, type); } -#endif // Check the type doesn't have bogus MethodImpls or overrides and we can get the finalizer. defType.GetFinalizer(); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunLibraryRootProvider.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunLibraryRootProvider.cs index dd977c2e437bd7..ac79e7ea759eca 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunLibraryRootProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunLibraryRootProvider.cs @@ -119,6 +119,8 @@ private static void CheckTypeCanBeUsedInSignature(TypeDesc type) ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadGeneral, type); } } + + ((CompilerTypeSystemContext)type.Context).EnsureLoadableType(type); } private static Instantiation GetInstantiationThatMeetsConstraints(Instantiation definition) diff --git a/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.cs b/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.cs new file mode 100644 index 00000000000000..73cf62d086b99b --- /dev/null +++ b/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// This is the C# file used to generate GitHub_89337.il + +class GitHub_89337 +{ + class Generic + { + } + + class Derived : System.Xml.NameTable { } + static Generic? Passthru(Generic? param) + { + return param; + } + + static int Main() + { + return 100; + } +} diff --git a/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.il b/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.il new file mode 100644 index 00000000000000..9b671612d08eb0 --- /dev/null +++ b/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.il @@ -0,0 +1,206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// This test checks that R2R binaries with non-findable dependencies can still build correctly. + +// The only difference between this file and GitHub_89337.cs, is that references to the System.Xml.ReaderWriter +// assembly have been renamed to references to NonexistentAssembly + +// .NET IL Disassembler. Version 8.0.0-dev + + + +// Metadata version: v4.0.30319 +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 6:0:0:0 +} +.assembly extern NonexistentAssembly +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 6:0:0:0 +} +.assembly GitHub_89337 +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) + .custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx + 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. + + // --- The following custom attribute is added automatically, do not uncomment ------- + // .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) + + .custom instance void [System.Runtime]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 18 2E 4E 45 54 43 6F 72 65 41 70 70 2C 56 // ....NETCoreApp,V + 65 72 73 69 6F 6E 3D 76 36 2E 30 01 00 54 0E 14 // ersion=v6.0..T.. + 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79 // FrameworkDisplay + 4E 61 6D 65 08 2E 4E 45 54 20 36 2E 30 ) // Name..NET 6.0 + .custom instance void [System.Runtime]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 0C 47 69 74 48 75 62 5F 38 39 33 33 37 00 // ...GitHub_89337. + 00 ) + .custom instance void [System.Runtime]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 05 44 65 62 75 67 00 00 ) // ...Debug.. + .custom instance void [System.Runtime]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0.. + .custom instance void [System.Runtime]System.Reflection.AssemblyInformationalVersionAttribute::.ctor(string) = ( 01 00 05 31 2E 30 2E 30 00 00 ) // ...1.0.0.. + .custom instance void [System.Runtime]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 0C 47 69 74 48 75 62 5F 38 39 33 33 37 00 // ...GitHub_89337. + 00 ) + .custom instance void [System.Runtime]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 0C 47 69 74 48 75 62 5F 38 39 33 33 37 00 // ...GitHub_89337. + 00 ) + .hash algorithm 0x00008004 + .ver 1:0:0:0 +} +.module GitHub_89337.dll +// MVID: {9cd3bf31-7da9-45e9-a188-4b1492ff96cd} +.imagebase 0x00400000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY +// Image base: 0x00000271F1C10000 + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class private auto ansi sealed beforefieldinit Microsoft.CodeAnalysis.EmbeddedAttribute + extends [System.Runtime]System.Attribute +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = ( 01 00 00 00 ) + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Attribute::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method EmbeddedAttribute::.ctor + +} // end of class Microsoft.CodeAnalysis.EmbeddedAttribute + +.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableAttribute + extends [System.Runtime]System.Attribute +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = ( 01 00 84 6B 00 00 02 00 54 02 0D 41 6C 6C 6F 77 // ...k....T..Allow + 4D 75 6C 74 69 70 6C 65 00 54 02 09 49 6E 68 65 // Multiple.T..Inhe + 72 69 74 65 64 00 ) // rited. + .field public initonly uint8[] NullableFlags + .method public hidebysig specialname rtspecialname + instance void .ctor(uint8 A_1) cil managed + { + // Code size 24 (0x18) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Attribute::.ctor() + IL_0006: nop + IL_0007: ldarg.0 + IL_0008: ldc.i4.1 + IL_0009: newarr [System.Runtime]System.Byte + IL_000e: dup + IL_000f: ldc.i4.0 + IL_0010: ldarg.1 + IL_0011: stelem.i1 + IL_0012: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags + IL_0017: ret + } // end of method NullableAttribute::.ctor + + .method public hidebysig specialname rtspecialname + instance void .ctor(uint8[] A_1) cil managed + { + // Code size 15 (0xf) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Attribute::.ctor() + IL_0006: nop + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags + IL_000e: ret + } // end of method NullableAttribute::.ctor + +} // end of class System.Runtime.CompilerServices.NullableAttribute + +.class private auto ansi beforefieldinit GitHub_89337 + extends [System.Runtime]System.Object +{ + .class auto ansi nested private beforefieldinit Generic`1 + extends [System.Runtime]System.Object + { + .param type T + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( 01 00 02 00 00 ) + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Generic`1::.ctor + + } // end of class Generic`1 + + .class auto ansi nested private beforefieldinit Derived + extends [NonexistentAssembly]System.Xml.NameTable + { + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [NonexistentAssembly]System.Xml.NameTable::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Derived::.ctor + + } // end of class Derived + + .method private hidebysig static class GitHub_89337/Generic`1 + Passthru(class GitHub_89337/Generic`1 param) cil managed + { + .param [0] + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) + .param [1] + .custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = ( 01 00 02 00 00 00 02 01 00 00 ) + // Code size 7 (0x7) + .maxstack 1 + .locals init (class GitHub_89337/Generic`1 V_0) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.0 + IL_0003: br.s IL_0005 + + IL_0005: ldloc.0 + IL_0006: ret + } // end of method GitHub_89337::Passthru + + .method private hidebysig static int32 + Main() cil managed + { + .entrypoint + // Code size 8 (0x8) + .maxstack 1 + .locals init (int32 V_0) + IL_0000: nop + IL_0001: ldc.i4.s 100 + IL_0003: stloc.0 + IL_0004: br.s IL_0006 + + IL_0006: ldloc.0 + IL_0007: ret + } // end of method GitHub_89337::Main + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method GitHub_89337::.ctor + +} // end of class GitHub_89337 \ No newline at end of file diff --git a/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.ilproj b/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.ilproj new file mode 100644 index 00000000000000..f51eb88fcda4c2 --- /dev/null +++ b/src/tests/readytorun/regressions/GitHub_89337/GitHub_89337.ilproj @@ -0,0 +1,9 @@ + + + Exe + 1 + + + + +