From e33895c60a1eb19fa8f0a7ea41860ca0dac06ed9 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Sun, 19 Mar 2023 16:57:29 -0700 Subject: [PATCH] Proper type name parser for native AOT compiler --- .../Compiler/Dataflow/ReflectionMarker.cs | 16 +- .../ReflectionMethodBodyScanner.cs | 99 ------------ .../DependencyAnalysis/TypeNameParser.cs | 141 ++++++++++++++++++ .../ILCompiler.Compiler.csproj | 8 +- .../src/System/Net/Http/X509ResourceClient.cs | 25 ++-- .../src/System/Reflection/TypeNameParser.cs | 4 + .../src/System/Text/ValueStringBuilder.cs | 2 + .../DataFlow/ApplyTypeAnnotations.cs | 24 +-- 8 files changed, 178 insertions(+), 141 deletions(-) delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionMethodBodyScanner.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeNameParser.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs index 11fff9e657a232..1b195c09d36ee9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -78,9 +79,9 @@ internal bool TryResolveTypeNameAndMark(string typeName, in DiagnosticContext di { ModuleDesc? callingModule = ((diagnosticContext.Origin.MemberDefinition as MethodDesc)?.OwningType as MetadataType)?.Module; - // NativeAOT doesn't have a fully capable type name resolver yet - // Once this is implemented don't forget to wire up marking of type forwards which are used in generic parameters - if (!DependencyAnalysis.ReflectionMethodBodyScanner.ResolveType(typeName, callingModule, diagnosticContext.Origin.MemberDefinition!.Context, out TypeDesc foundType, out ModuleDesc referenceModule)) + List referencedModules = new(); + TypeDesc foundType = System.Reflection.TypeNameParser.ResolveType(typeName, callingModule, diagnosticContext.Origin.MemberDefinition!.Context, referencedModules); + if (foundType == null) { type = default; return false; @@ -88,9 +89,12 @@ internal bool TryResolveTypeNameAndMark(string typeName, in DiagnosticContext di if (_enabled) { - // Also add module metadata in case this reference was through a type forward - if (Factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) - _dependencies.Add(Factory.ModuleMetadata(referenceModule), reason); + foreach (ModuleDesc referencedModule in referencedModules) + { + // Also add module metadata in case this reference was through a type forward + if (Factory.MetadataManager.CanGenerateMetadata(referencedModule.GetGlobalModuleType())) + _dependencies.Add(Factory.ModuleMetadata(referencedModule), reason); + } MarkType(diagnosticContext.Origin, foundType, reason); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionMethodBodyScanner.cs deleted file mode 100644 index 44277568a5fec8..00000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReflectionMethodBodyScanner.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Internal.TypeSystem; - -using AssemblyName = System.Reflection.AssemblyName; -using StringBuilder = System.Text.StringBuilder; - -namespace ILCompiler.DependencyAnalysis -{ - internal static class ReflectionMethodBodyScanner - { - public static bool ResolveType(string name, ModuleDesc callingModule, TypeSystemContext context, out TypeDesc type, out ModuleDesc referenceModule) - { - // This can do enough resolution to resolve "Foo" or "Foo, Assembly, PublicKeyToken=...". - // The reflection resolution rules are complicated. This is only needed for a heuristic, - // not for correctness, so this shortcut is okay. - - type = null; - int i = 0; - - // Consume type name part - StringBuilder typeName = new StringBuilder(); - StringBuilder typeNamespace = new StringBuilder(); - string containingTypeName = null; - while (i < name.Length && (char.IsLetterOrDigit(name[i]) || name[i] == '.' || name[i] == '_' || name[i] == '`' || name[i] == '+')) - { - if (name[i] == '.') - { - if (typeNamespace.Length > 0) - typeNamespace.Append('.'); - typeNamespace.Append(typeName); - typeName.Clear(); - } - else if (name[i] == '+') - { - containingTypeName = typeName.ToString(); - typeName.Clear(); - } - else - { - typeName.Append(name[i]); - } - i++; - } - - string nestedTypeName = null; - if (containingTypeName != null) - { - nestedTypeName = typeName.ToString(); - typeName = new StringBuilder(containingTypeName); - } - - // Consume any comma or white space - while (i < name.Length && (name[i] == ' ' || name[i] == ',')) - { - i++; - } - - // Consume assembly name - StringBuilder assemblyName = new StringBuilder(); - while (i < name.Length && (char.IsLetterOrDigit(name[i]) || name[i] == '.' || name[i] == '_')) - { - assemblyName.Append(name[i]); - i++; - } - - // If the name was assembly-qualified, resolve the assembly - // If it wasn't qualified, we resolve in the calling assembly - - referenceModule = callingModule; - if (assemblyName.Length > 0) - { - referenceModule = context.ResolveAssembly(new AssemblyName(assemblyName.ToString()), false); - } - - if (referenceModule == null) - return false; - - // Resolve type in the assembly - MetadataType mdType = referenceModule.GetType(typeNamespace.ToString(), typeName.ToString(), throwIfNotFound: false); - if (mdType != null && nestedTypeName != null) - mdType = mdType.GetNestedType(nestedTypeName); - - // If it didn't resolve and wasn't assembly-qualified, we also try core library - if (mdType == null && assemblyName.Length == 0) - { - referenceModule = context.SystemModule; - mdType = referenceModule.GetType(typeNamespace.ToString(), typeName.ToString(), throwIfNotFound: false); - if (mdType != null && nestedTypeName != null) - mdType = mdType.GetNestedType(nestedTypeName); - } - - type = mdType; - - return type != null; - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeNameParser.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeNameParser.cs new file mode 100644 index 00000000000000..25aa2f33dc1a3e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/TypeNameParser.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +using Internal.TypeSystem; + +namespace System.Reflection +{ + internal unsafe ref partial struct TypeNameParser + { + private TypeSystemContext _context; + private ModuleDesc _callingModule; + private List _referencedModules; + + public static TypeDesc ResolveType(string name, ModuleDesc callingModule, TypeSystemContext context, List referencedModules) + { + return new System.Reflection.TypeNameParser(name) + { + _context = context, + _callingModule = callingModule, + _referencedModules = referencedModules + }.Parse()?.Value; + } + + private sealed class Type + { + public Type(TypeDesc type) => Value = type; + public TypeDesc Value { get; } + + public Type MakeArrayType() => new Type(Value.MakeArrayType()); + public Type MakeArrayType(int rank) => new Type(Value.MakeArrayType(rank)); + public Type MakePointerType() => new Type(Value.MakePointerType()); + public Type MakeByRefType() => new Type(Value.MakeByRefType()); + + public Type MakeGenericType(Type[] typeArguments) + { + TypeDesc[] instantiation = new TypeDesc[typeArguments.Length]; + for (int i = 0; i < typeArguments.Length; i++) + instantiation[i] = typeArguments[i].Value; + return new Type(((MetadataType)Value).MakeInstantiatedType(instantiation)); + } + } + + private static bool CheckTopLevelAssemblyQualifiedName() => true; + + private Type GetType(string typeName, ReadOnlySpan nestedTypeNames, string assemblyNameIfAny) + { + ModuleDesc module = null; + + if (assemblyNameIfAny != null) + { + AssemblyName an = TryParseAssemblyName(assemblyNameIfAny); + if (an != null) + module = _context.ResolveAssembly(an, throwIfNotFound: false); + } + else + { + module = _callingModule; + } + + if (module == null) + return null; + + Type type = GetTypeCore(module, typeName, nestedTypeNames); + if (type != null) + { + _referencedModules?.Add(module); + return type; + } + + // If it didn't resolve and wasn't assembly-qualified, we also try core library + if (assemblyNameIfAny == null) + { + type = GetTypeCore(_context.SystemModule, typeName, nestedTypeNames); + if (type != null) + { + _referencedModules?.Add(_context.SystemModule); + return type; + } + } + + return null; + } + + private static AssemblyName TryParseAssemblyName(string assemblyName) + { + try + { + return new AssemblyName(assemblyName); + } + catch (FileLoadException) + { + return null; + } + catch (ArgumentException) + { + return null; + } + } + + private static Type GetTypeCore(ModuleDesc module, string typeName, ReadOnlySpan nestedTypeNames) + { + string typeNamespace, name; + + int separator = typeName.LastIndexOf('.'); + if (separator <= 0) + { + typeNamespace = ""; + name = typeName; + } + else + { + if (typeName[separator - 1] == '.') + separator--; + typeNamespace = typeName.Substring(0, separator); + name = typeName.Substring(separator + 1); + } + + MetadataType type = module.GetType(typeNamespace, name, throwIfNotFound: false); + if (type == null) + return null; + + for (int i = 0; i < nestedTypeNames.Length; i++) + { + type = type.GetNestedType(nestedTypeNames[i]); + if (type == null) + return null; + } + + return new Type(type); + } + + private static void ParseError() + { + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 56390d65ecbe33..4578500923ef8e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -30,6 +30,12 @@ + + TypeNameParser.cs + + + ValueStringBuilder.cs + IL\DelegateInfo.cs @@ -406,7 +412,6 @@ - @@ -416,6 +421,7 @@ + diff --git a/src/libraries/Common/src/System/Net/Http/X509ResourceClient.cs b/src/libraries/Common/src/System/Net/Http/X509ResourceClient.cs index ff5a237c3f3ae3..5fe2b8ce402382 100644 --- a/src/libraries/Common/src/System/Net/Http/X509ResourceClient.cs +++ b/src/libraries/Common/src/System/Net/Http/X509ResourceClient.cs @@ -83,29 +83,22 @@ internal static partial class X509ResourceClient // the latter can't in turn have an explicit dependency on the former. // Get the relevant types needed. - Type? socketsHttpHandlerType = Type.GetType("System.Net.Http.SocketsHttpHandler, System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); - Type? httpMessageHandlerType = Type.GetType("System.Net.Http.HttpMessageHandler, System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); - Type? httpClientType = Type.GetType("System.Net.Http.HttpClient, System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); - Type? httpRequestMessageType = Type.GetType("System.Net.Http.HttpRequestMessage, System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); - Type? httpResponseMessageType = Type.GetType("System.Net.Http.HttpResponseMessage, System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); - Type? httpResponseHeadersType = Type.GetType("System.Net.Http.Headers.HttpResponseHeaders, System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); - Type? httpContentType = Type.GetType("System.Net.Http.HttpContent, System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", throwOnError: false); + Type? socketsHttpHandlerType = Type.GetType("System.Net.Http.SocketsHttpHandler, System.Net.Http", throwOnError: false); + Type? httpMessageHandlerType = Type.GetType("System.Net.Http.HttpMessageHandler, System.Net.Http", throwOnError: false); + Type? httpClientType = Type.GetType("System.Net.Http.HttpClient, System.Net.Http", throwOnError: false); + Type? httpRequestMessageType = Type.GetType("System.Net.Http.HttpRequestMessage, System.Net.Http", throwOnError: false); + Type? httpResponseMessageType = Type.GetType("System.Net.Http.HttpResponseMessage, System.Net.Http", throwOnError: false); + Type? httpResponseHeadersType = Type.GetType("System.Net.Http.Headers.HttpResponseHeaders, System.Net.Http", throwOnError: false); + Type? httpContentType = Type.GetType("System.Net.Http.HttpContent, System.Net.Http", throwOnError: false); + Type? taskOfHttpResponseMessageType = Type.GetType("System.Threading.Tasks.Task`1[[System.Net.Http.HttpResponseMessage, System.Net.Http]], System.Runtime", throwOnError: false); if (socketsHttpHandlerType == null || httpMessageHandlerType == null || httpClientType == null || httpRequestMessageType == null || - httpResponseMessageType == null || httpResponseHeadersType == null || httpContentType == null) + httpResponseMessageType == null || httpResponseHeadersType == null || httpContentType == null || taskOfHttpResponseMessageType == null) { Debug.Fail("Unable to load required type."); return null; } - // Workaround until https://github.com/dotnet/runtime/issues/72833 is fixed - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "The type HttpResponseMessage is a reference type")] - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - static Type GetTaskOfHttpResponseMessageType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? httpResponseMessageType) => typeof(Task<>).MakeGenericType(httpResponseMessageType!); - - Type taskOfHttpResponseMessageType = GetTaskOfHttpResponseMessageType(httpResponseMessageType); - // Get the methods on those types. ConstructorInfo? socketsHttpHandlerCtor = socketsHttpHandlerType.GetConstructor(Type.EmptyTypes); PropertyInfo? pooledConnectionIdleTimeoutProp = socketsHttpHandlerType.GetProperty("PooledConnectionIdleTimeout"); diff --git a/src/libraries/Common/src/System/Reflection/TypeNameParser.cs b/src/libraries/Common/src/System/Reflection/TypeNameParser.cs index 9dcdf11079d8d9..d7bb0e8fb2b0b3 100644 --- a/src/libraries/Common/src/System/Reflection/TypeNameParser.cs +++ b/src/libraries/Common/src/System/Reflection/TypeNameParser.cs @@ -6,6 +6,8 @@ using System.Runtime.InteropServices; using System.Text; +#nullable enable + namespace System.Reflection { // @@ -622,10 +624,12 @@ private static string EscapeTypeName(string typeName, ReadOnlySpan neste return fullName; } +#if SYSTEM_PRIVATE_CORELIB private void ParseError() { if (_throwOnError) throw new ArgumentException(SR.Arg_ArgumentException, $"typeName@{_errorIndex}"); } +#endif } } diff --git a/src/libraries/Common/src/System/Text/ValueStringBuilder.cs b/src/libraries/Common/src/System/Text/ValueStringBuilder.cs index b89d7e01caf22e..966f1c8cfc5edc 100644 --- a/src/libraries/Common/src/System/Text/ValueStringBuilder.cs +++ b/src/libraries/Common/src/System/Text/ValueStringBuilder.cs @@ -6,6 +6,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#nullable enable + namespace System.Text { internal ref partial struct ValueStringBuilder diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs index b7fcb3d7b0f761..d4a1c66d57da6e 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs @@ -128,9 +128,7 @@ private static void RequireCombinationOnString ( { } - // https://github.com/dotnet/runtime/issues/72833 - // NativeAOT doesn't implement full type name parser yet - [Kept (By = Tool.Trimmer)] + [Kept] class FromStringConstantWithGenericInner { } @@ -143,9 +141,7 @@ class FromStringConstantWithGeneric public T GetValue () { return default (T); } } - // https://github.com/dotnet/runtime/issues/72833 - // NativeAOT doesn't implement full type name parser yet - [Kept (By = Tool.Trimmer)] + [Kept] class FromStringConstantWithGenericInnerInner { [Kept (By = Tool.Trimmer)] @@ -156,7 +152,7 @@ public void Method () int unusedField; } - [Kept (By = Tool.Trimmer)] + [Kept] class FromStringConstantWithGenericInnerOne< [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] [KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute), By = Tool.Trimmer)] @@ -164,9 +160,7 @@ class FromStringConstantWithGenericInnerOne< { } - // https://github.com/dotnet/runtime/issues/72833 - // NativeAOT doesn't implement full type name parser yet - [Kept (By = Tool.Trimmer)] + [Kept] class FromStringConstantWithGenericInnerTwo { void UnusedMethod () @@ -174,22 +168,14 @@ void UnusedMethod () } } - // https://github.com/dotnet/runtime/issues/72833 - // NativeAOT doesn't implement full type name parser yet - [Kept (By = Tool.Trimmer)] + [Kept] class FromStringConstantWitGenericInnerMultiDimArray { } - // https://github.com/dotnet/runtime/issues/72833 - // NativeAOT actually preserves this, but for a slightly wrong reason - it completely ignores the array notations [Kept] - [KeptMember (".ctor()", By = Tool.NativeAot)] class FromStringConstantWithMultiDimArray { - // https://github.com/dotnet/runtime/issues/72833 - // NativeAOT actually preserves this, but for a slightly wrong reason - it completely ignores the array notations - [Kept (By = Tool.NativeAot)] public void UnusedMethod () { } }