diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs index 5d6e83341e94..37adf30ec264 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs @@ -224,7 +224,37 @@ internal static string ToDisplayStringIfAvailable(this Type type, List gene } else { - return null; + // First, see if Type.Name is available. If Type.Name is available, then we can be reasonably confident that it is safe to call Type.FullName. + // We'll still wrap the call in a try-catch as a failsafe. + string s = type.InternalNameIfAvailable; + if (s == null) + return null; + + try + { + s = type.FullName; + } + catch (MissingMetadataException) + { + } + + // Insert commas so that CreateConstructedGenericTypeStringIfAvailable can fill the blanks. + // This is not strictly correct for types nested under generic types, but at this point we're doing + // best effort within reason. + if (type.IsGenericTypeDefinition) + { + s += "["; + int genericArgCount = type.GetGenericArguments().Length; + while (genericArgCount-- > 0) + { + genericParameterOffsets.Add(s.Length); + if (genericArgCount > 0) + s = s + ","; + } + s += "]"; + } + + return s; } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs index f598c32df4f0..c842747740b0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/CustomAttributeBasedDependencyAlgorithm.cs @@ -252,12 +252,8 @@ private static bool AddDependenciesFromCustomAttributeArgument(DependencyList de if (factory.MetadataManager.IsReflectionBlocked(typeofType)) return false; - // We go for the entire EEType because the reflection stack at runtime might need an EEType anyway - // (e.g. if this is a `typeof(SomeType[,,])` just having the metadata for SomeType is not enough). - // Theoretically, we only need the EEType if this is a constructed type, but let's go for the full - // EEType for the sake of consistency. - dependencies.Add(factory.MaximallyConstructableType(typeofType), "Custom attribute blob"); - + // Grab the metadata nodes that will be necessary to represent the typeof in the metadata blob + TypeMetadataNode.GetMetadataDependencies(ref dependencies, factory, typeofType, "Custom attribute blob"); return true; } } diff --git a/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs b/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs index c156a7fe2922..3c96b619ed67 100644 --- a/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs +++ b/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs @@ -638,6 +638,8 @@ struct FirstNeverUsedType { } struct SecondNeverUsedType { } + struct ThirdNeverUsedType { } + class Gen { } class TypeAttribute : Attribute @@ -667,21 +669,60 @@ class Holder2 { } [EnumArray(EnumArray = new MyEnum[] { 0 })] class Holder3 { } + [Type(SomeType = typeof(ThirdNeverUsedType))] + class Holder4 { } + public static void Run() { Console.WriteLine(nameof(TestAttributeExpressions)); - TypeAttribute attr1 = typeof(Holder1).GetCustomAttribute(); - if (attr1.SomeType.ToString() != "ReflectionTest+TestAttributeExpressions+FirstNeverUsedType*[,]") - throw new Exception(); + // We don't create EETypes for types referenced from typeof which makes + // getting their constructed version to fail at runtime because + // we place restrictions on on the ability of getting System.Type for + // constructed types. + // + // The workaround is to put some dataflow annotations on the Type-typed + // parameter/property/fields. + // + // This is testing that we get an actionable exception message. + // + // The reason why we don't create EETypes for these is size - + // and one wouldn't be able to do much with just the type anyway. + // We would need at least some methods and at that point we have reflectable + // methods and therefore an EEType. - TypeAttribute attr2 = typeof(Holder2).GetCustomAttribute(); - if (attr2.SomeType.ToString() != "ReflectionTest+TestAttributeExpressions+Gen`1[ReflectionTest+TestAttributeExpressions+SecondNeverUsedType]") + try + { + typeof(Holder1).GetCustomAttribute(); throw new Exception(); + } + catch (Exception ex) + { + if (!ex.ToString().Contains("ReflectionTest+TestAttributeExpressions+FirstNeverUsedType*[,]")) + throw; + } + + try + { + typeof(Holder2).GetCustomAttribute(); + } + catch (Exception ex) + { + if (!ex.ToString().Contains("ReflectionTest+TestAttributeExpressions+Gen`1[ReflectionTest+TestAttributeExpressions+SecondNeverUsedType]")) + throw; + } + + // Make sure we created EEType for the enum array. EnumArrayAttribute attr3 = typeof(Holder3).GetCustomAttribute(); if (attr3.EnumArray[0] != 0) throw new Exception(); + + // Unconstructed types don't have the problem with missing EETypes described above + + TypeAttribute attr4 = typeof(Holder4).GetCustomAttribute(); + if (attr4.SomeType.ToString() != "ReflectionTest+TestAttributeExpressions+ThirdNeverUsedType") + throw new Exception(); } }