From a0b27913b88a56ffecf20ac0e319507c7c877eba Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Fri, 4 Sep 2020 21:32:26 -0700 Subject: [PATCH 1/5] Add type parser --- .../ReflectionMethodBodyScanner.cs | 7 +- src/linker/Linker.Steps/LinkAttributesStep.cs | 3 +- src/linker/Linker.Steps/MarkStep.cs | 25 +- src/linker/Linker/AssemblyNameHelpers.cs | 160 ++++++++++++ src/linker/Linker/AssemblyNameLexer.cs | 129 ++++++++++ src/linker/Linker/AssemblyNameParser.cs | 204 +++++++++++++++ src/linker/Linker/AssemblyUtilities.cs | 27 -- src/linker/Linker/LinkContext.cs | 6 + src/linker/Linker/RuntimeAssemblyName.cs | 112 +++++++++ src/linker/Linker/TypeLexer.cs | 234 ++++++++++++++++++ src/linker/Linker/TypeName.cs | 220 ++++++++++++++++ src/linker/Linker/TypeNameParser.cs | 51 ---- src/linker/Linker/TypeParser.cs | 185 ++++++++++++++ src/linker/Linker/TypeParsing.cs | 77 ++++++ .../Tests/TypeNameParserTests.cs | 125 ---------- 15 files changed, 1340 insertions(+), 225 deletions(-) create mode 100644 src/linker/Linker/AssemblyNameHelpers.cs create mode 100644 src/linker/Linker/AssemblyNameLexer.cs create mode 100644 src/linker/Linker/AssemblyNameParser.cs create mode 100644 src/linker/Linker/RuntimeAssemblyName.cs create mode 100644 src/linker/Linker/TypeLexer.cs create mode 100644 src/linker/Linker/TypeName.cs delete mode 100644 src/linker/Linker/TypeNameParser.cs create mode 100644 src/linker/Linker/TypeParser.cs create mode 100644 src/linker/Linker/TypeParsing.cs delete mode 100644 test/Mono.Linker.Tests/Tests/TypeNameParserTests.cs diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index 463202058087..0fa12430260c 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -797,7 +797,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } foreach (var typeNameValue in methodParams[0].UniqueValues ()) { if (typeNameValue is KnownStringValue knownStringValue) { - TypeDefinition foundType = AssemblyUtilities.ResolveFullyQualifiedTypeName (_context, knownStringValue.Contents); + TypeDefinition foundType = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. reflectionContext.RecordHandledPattern (); @@ -1335,7 +1335,8 @@ void ProcessCreateInstanceByName (ref ReflectionPatternContext reflectionContext continue; } - var resolvedType = resolvedAssembly.FindType (typeNameStringValue.Contents); + var typeName = System.Reflection.Runtime.TypeParsing.TypeParser.ParseAssemblyQualifiedTypeName (typeNameStringValue.Contents).TypeName; + var resolvedType = _context.TypeNameResolver.ResolveTypeName (resolvedAssembly, typeName); if (resolvedType == null) { // It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case // Note that we did find the assembly, so it's not a linker config problem, it's either intentional, or wrong versions of assemblies @@ -1567,7 +1568,7 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC } else if (uniqueValue is SystemTypeValue systemTypeValue) { MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, systemTypeValue.TypeRepresented, requiredMemberTypes); } else if (uniqueValue is KnownStringValue knownStringValue) { - TypeDefinition foundType = AssemblyUtilities.ResolveFullyQualifiedTypeName (_context, knownStringValue.Contents); + TypeDefinition foundType = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. reflectionContext.RecordHandledPattern (); diff --git a/src/linker/Linker.Steps/LinkAttributesStep.cs b/src/linker/Linker.Steps/LinkAttributesStep.cs index d694ef3e927d..a37eaed361db 100644 --- a/src/linker/Linker.Steps/LinkAttributesStep.cs +++ b/src/linker/Linker.Steps/LinkAttributesStep.cs @@ -172,7 +172,8 @@ bool GetAttributeType (XPathNodeIterator iterator, string attributeFullName, out return false; } - attributeType = assembly.FindType (attributeFullName); + var typeName = System.Reflection.Runtime.TypeParsing.TypeParser.ParseAssemblyQualifiedTypeName (attributeFullName).TypeName; + attributeType = Context.TypeNameResolver.ResolveTypeName (assembly, typeName); } if (attributeType == null) { diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index c10e249c06dd..618ef7721878 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -733,8 +733,7 @@ protected virtual void MarkUserDependency (MemberReference context, CustomAttrib TypeDefinition td; if (args.Count >= 2 && args[1].Value is string typeName) { - td = (assembly ?? context.Module.Assembly).FindType (typeName); - + td = _context.TypeNameResolver.ResolveTypeName (typeName + "," + (assembly ?? context.Module.Assembly)); if (td == null) { _context.LogWarning ( $"Could not resolve dependency type '{typeName}' specified in a `PreserveDependency` attribute", 2004, context.Resolve ()); @@ -1548,25 +1547,15 @@ protected virtual void DoAdditionalInstantiatedTypeProcessing (TypeDefinition ty TypeDefinition GetDebuggerAttributeTargetType (CustomAttribute ca, AssemblyDefinition asm) { - TypeReference targetTypeReference = null; foreach (var property in ca.Properties) { - if (property.Name == "Target") { - targetTypeReference = (TypeReference) property.Argument.Value; - break; - } + if (property.Name == "Target") + return ((TypeReference) property.Argument.Value)?.Resolve (); - if (property.Name == "TargetTypeName") { - if (TypeNameParser.TryParseTypeAssemblyQualifiedName ((string) property.Argument.Value, out string typeName, out string assemblyName)) { - if (string.IsNullOrEmpty (assemblyName)) - targetTypeReference = asm.MainModule.GetType (typeName); - else - targetTypeReference = _context.GetAssemblies ().FirstOrDefault (a => a.Name.Name == assemblyName)?.MainModule.GetType (typeName); - } - break; - } + if (property.Name == "TargetTypeName") + return _context.TypeNameResolver.ResolveTypeName ((string) property.Argument.Value); } - return targetTypeReference?.Resolve (); + return null; } void MarkTypeSpecialCustomAttributes (TypeDefinition type) @@ -1650,7 +1639,7 @@ protected virtual void MarkTypeConverterLikeDependency (CustomAttribute attribut TypeDefinition tdef = null; switch (attribute.ConstructorArguments[0].Value) { case string s: - tdef = AssemblyUtilities.ResolveFullyQualifiedTypeName (_context, s); + tdef = _context.TypeNameResolver.ResolveTypeName (s); break; case TypeReference type: tdef = type.Resolve (); diff --git a/src/linker/Linker/AssemblyNameHelpers.cs b/src/linker/Linker/AssemblyNameHelpers.cs new file mode 100644 index 000000000000..1565a81da999 --- /dev/null +++ b/src/linker/Linker/AssemblyNameHelpers.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// Copyright (c) Microsoft Corporation. All rights reserved. + +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Globalization; + +namespace System.Reflection.Runtime.Assemblies +{ + internal static partial class AssemblyNameHelpers + { + internal static string ComputeDisplayName (RuntimeAssemblyName a) + { + const int PUBLIC_KEY_TOKEN_LEN = 8; + + if (a.Name == string.Empty) + throw new FileLoadException (); + + StringBuilder sb = new StringBuilder (); + if (a.Name != null) { + sb.AppendQuoted (a.Name); + } + + if (a.Version != null) { + Version canonicalizedVersion = a.Version.CanonicalizeVersion (); + if (canonicalizedVersion.Major != ushort.MaxValue) { + sb.Append (", Version="); + sb.Append (canonicalizedVersion.Major); + + if (canonicalizedVersion.Minor != ushort.MaxValue) { + sb.Append ('.'); + sb.Append (canonicalizedVersion.Minor); + + if (canonicalizedVersion.Build != ushort.MaxValue) { + sb.Append ('.'); + sb.Append (canonicalizedVersion.Build); + + if (canonicalizedVersion.Revision != ushort.MaxValue) { + sb.Append ('.'); + sb.Append (canonicalizedVersion.Revision); + } + } + } + } + } + + string cultureName = a.CultureName; + if (cultureName != null) { + if (cultureName == string.Empty) + cultureName = "neutral"; + sb.Append (", Culture="); + sb.AppendQuoted (cultureName); + } + + byte[] pkt = a.PublicKeyOrToken; + if (pkt != null) { + if (pkt.Length > PUBLIC_KEY_TOKEN_LEN) + throw new ArgumentException (); + + sb.Append (", PublicKeyToken="); + if (pkt.Length == 0) + sb.Append ("null"); + else { + foreach (byte b in pkt) { + sb.Append (b.ToString ("x2", CultureInfo.InvariantCulture)); + } + } + } + + if (0 != (a.Flags & AssemblyNameFlags.Retargetable)) + sb.Append (", Retargetable=Yes"); + + AssemblyContentType contentType = ExtractAssemblyContentType (a.Flags); + if (contentType == AssemblyContentType.WindowsRuntime) + sb.Append (", ContentType=WindowsRuntime"); + + // NOTE: By design (desktop compat) AssemblyName.FullName and ToString() do not include ProcessorArchitecture. + + return sb.ToString (); + } + + private static void AppendQuoted (this StringBuilder sb, string s) + { + bool needsQuoting = false; + const char quoteChar = '\"'; + + // App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one + // by some algorithm. Rather than guess at it, I'll just use double-quote consistently. + if (s != s.Trim () || s.Contains ("\"") || s.Contains ("\'")) + needsQuoting = true; + + if (needsQuoting) + sb.Append (quoteChar); + + for (int i = 0; i < s.Length; i++) { + bool addedEscape = false; + foreach (KeyValuePair kv in AssemblyNameLexer.EscapeSequences) { + string escapeReplacement = kv.Value; + if (!(s[i] == escapeReplacement[0])) + continue; + if ((s.Length - i) < escapeReplacement.Length) + continue; + if (s.AsSpan (i, escapeReplacement.Length).SequenceEqual (escapeReplacement)) { + sb.Append ('\\'); + sb.Append (kv.Key); + addedEscape = true; + } + } + + if (!addedEscape) + sb.Append (s[i]); + } + + if (needsQuoting) + sb.Append (quoteChar); + } + + private static Version CanonicalizeVersion (this Version version) + { + ushort major = (ushort) version.Major; + ushort minor = (ushort) version.Minor; + ushort build = (ushort) version.Build; + ushort revision = (ushort) version.Revision; + + if (major == version.Major && minor == version.Minor && build == version.Build && revision == version.Revision) + return version; + + return new Version (major, minor, build, revision); + } + + // + // These helpers convert between the combined flags+contentType+processorArchitecture value and the separated parts. + // + // Since these are only for trusted callers, they do NOT check for out of bound bits. + // + + internal static AssemblyContentType ExtractAssemblyContentType (AssemblyNameFlags flags) + { + return (AssemblyContentType) ((((int) flags) >> 9) & 0x7); + } + + internal static ProcessorArchitecture ExtractProcessorArchitecture (AssemblyNameFlags flags) + { + return (ProcessorArchitecture) ((((int) flags) >> 4) & 0x7); + } + + internal static AssemblyNameFlags ExtractAssemblyNameFlags (AssemblyNameFlags combinedFlags) + { + return combinedFlags & unchecked((AssemblyNameFlags) 0xFFFFF10F); + } + + internal static AssemblyNameFlags CombineAssemblyNameFlags (AssemblyNameFlags flags, AssemblyContentType contentType, ProcessorArchitecture processorArchitecture) + { + return (AssemblyNameFlags) (((int) flags) | (((int) contentType) << 9) | ((int) processorArchitecture << 4)); + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/AssemblyNameLexer.cs b/src/linker/Linker/AssemblyNameLexer.cs new file mode 100644 index 000000000000..37d3b0dc3062 --- /dev/null +++ b/src/linker/Linker/AssemblyNameLexer.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Text; +using System.Collections.Generic; + +namespace System.Reflection.Runtime.Assemblies +{ + // + // A simple lexer for assembly display names. + // + internal struct AssemblyNameLexer + { + internal AssemblyNameLexer (string s) + { + // Convert string to char[] with NUL terminator. (An actual NUL terminator in the input string will be treated + // as an actual end of string: this is compatible with desktop behavior.) + char[] chars = new char[s.Length + 1]; + s.CopyTo (0, chars, 0, s.Length); + _chars = chars; + _index = 0; + } + + // + // Return the next token in assembly name. If you expect the result to be DisplayNameToken.String, + // use GetNext(out String) instead. + // + internal Token GetNext () + { + string ignore; + return GetNext (out ignore); + } + + // + // Return the next token in assembly name. If the result is DisplayNameToken.String, + // sets "tokenString" to the tokenized string. + // + internal Token GetNext (out string tokenString) + { + tokenString = null; + while (char.IsWhiteSpace (_chars[_index])) + _index++; + + char c = _chars[_index++]; + if (c == 0) + return Token.End; + if (c == ',') + return Token.Comma; + if (c == '=') + return Token.Equals; + + StringBuilder sb = new StringBuilder (); + char quoteChar = (char) 0; + if (c == '\'' || c == '\"') { + quoteChar = c; + c = _chars[_index++]; + } + + for (; ; ) + { + if (c == 0) { + _index--; + break; // Terminate: End of string (desktop compat: if string was quoted, permitted to terminate without end-quote.) + } + + if (quoteChar != 0 && c == quoteChar) + break; // Terminate: Found closing quote of quoted string. + + if (quoteChar == 0 && (c == ',' || c == '=')) { + _index--; + break; // Terminate: Found start of a new ',' or '=' token. + } + + if (quoteChar == 0 && (c == '\'' || c == '\"')) + throw new FileLoadException (); // Desktop compat: Unescaped quote illegal unless entire string is quoted. + + if (c == '\\') { + c = _chars[_index++]; + bool matched = false; + foreach (KeyValuePair kv in EscapeSequences) { + if (c == kv.Key) { + matched = true; + sb.Append (kv.Value); + break; + } + } + if (!matched) + throw new FileLoadException (); // Unrecognized escape + } else { + sb.Append (c); + } + + c = _chars[_index++]; + } + + tokenString = sb.ToString (); + if (quoteChar == 0) + tokenString = tokenString.Trim (); // Unless quoted, whitespace at beginning or end doesn't count. + return Token.String; + + } + + internal static KeyValuePair[] EscapeSequences = + { + new KeyValuePair('\\', "\\"), + new KeyValuePair(',', ","), + new KeyValuePair('=', "="), + new KeyValuePair('\'', "'"), + new KeyValuePair('\"', "\""), + new KeyValuePair('n', Environment.NewLine), + new KeyValuePair('t', "\t"), + }; + + // Token categories for display name lexer. + internal enum Token + { + Equals = 1, + Comma = 2, + String = 3, + End = 4, + } + + private readonly char[] _chars; + private int _index; + } + +} \ No newline at end of file diff --git a/src/linker/Linker/AssemblyNameParser.cs b/src/linker/Linker/AssemblyNameParser.cs new file mode 100644 index 000000000000..6ea3cb01e93b --- /dev/null +++ b/src/linker/Linker/AssemblyNameParser.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Diagnostics; +using System.Globalization; +using System.Collections.Generic; + +namespace System.Reflection.Runtime.Assemblies +{ + // + // Parses an assembly name. + // + internal static class AssemblyNameParser + { + internal static RuntimeAssemblyName Parse (string s) + { + Debug.Assert (s != null); + AssemblyNameLexer lexer = new AssemblyNameLexer (s); + + int indexOfNul = s.IndexOf ((char) 0); + if (indexOfNul != -1) + s = s.Substring (0, indexOfNul); + if (s.Length == 0) + throw new ArgumentException (); + + // Name must come first. + string name; + AssemblyNameLexer.Token token = lexer.GetNext (out name); + if (token != AssemblyNameLexer.Token.String) { + throw new FileLoadException (); + } + + if (name == string.Empty || name.IndexOfAny (s_illegalCharactersInSimpleName) != -1) + throw new FileLoadException (); + + Version version = null; + string cultureName = null; + byte[] pkt = null; + AssemblyNameFlags flags = 0; + + List alreadySeen = new List (); + token = lexer.GetNext (); + while (token != AssemblyNameLexer.Token.End) { + if (token != AssemblyNameLexer.Token.Comma) + throw new FileLoadException (); + + string attributeName; + token = lexer.GetNext (out attributeName); + if (token != AssemblyNameLexer.Token.String) + throw new FileLoadException (); + token = lexer.GetNext (); + + // Compat note: Inside AppX apps, the desktop CLR's AssemblyName parser skips past any elements that don't follow the "=" pattern. + // (when running classic Windows apps, such an illegal construction throws an exception as expected.) + // Naturally, at least one app unwittingly takes advantage of this. + if (token == AssemblyNameLexer.Token.Comma || token == AssemblyNameLexer.Token.End) + continue; + + if (token != AssemblyNameLexer.Token.Equals) + throw new FileLoadException (); + + string attributeValue; + token = lexer.GetNext (out attributeValue); + if (token != AssemblyNameLexer.Token.String) + throw new FileLoadException (); + + if (attributeName == string.Empty) + throw new FileLoadException (); + + for (int i = 0; i < alreadySeen.Count; i++) { + if (alreadySeen[i].Equals (attributeName, StringComparison.OrdinalIgnoreCase)) + throw new FileLoadException (); // Cannot specify the same attribute twice. + } + + alreadySeen.Add (attributeName); + if (attributeName.Equals ("Version", StringComparison.OrdinalIgnoreCase)) { + version = ParseVersion (attributeValue); + } + + if (attributeName.Equals ("Culture", StringComparison.OrdinalIgnoreCase)) { + cultureName = ParseCulture (attributeValue); + } + + if (attributeName.Equals ("PublicKeyToken", StringComparison.OrdinalIgnoreCase)) { + pkt = ParsePKT (attributeValue); + } + + if (attributeName.Equals ("ProcessorArchitecture", StringComparison.OrdinalIgnoreCase)) { + flags |= (AssemblyNameFlags) (((int) ParseProcessorArchitecture (attributeValue)) << 4); + } + + if (attributeName.Equals ("Retargetable", StringComparison.OrdinalIgnoreCase)) { + if (attributeValue.Equals ("Yes", StringComparison.OrdinalIgnoreCase)) + flags |= AssemblyNameFlags.Retargetable; + else if (attributeValue.Equals ("No", StringComparison.OrdinalIgnoreCase)) { + // nothing to do + } else + throw new FileLoadException (); + } + + if (attributeName.Equals ("ContentType", StringComparison.OrdinalIgnoreCase)) { + if (attributeValue.Equals ("WindowsRuntime", StringComparison.OrdinalIgnoreCase)) + flags |= (AssemblyNameFlags) (((int) AssemblyContentType.WindowsRuntime) << 9); + else + throw new FileLoadException (); + } + + // Desktop compat: If we got here, the attribute name is unknown to us. Ignore it (as long it's not duplicated.) + token = lexer.GetNext (); + } + return new RuntimeAssemblyName (name, version, cultureName, flags, pkt); + } + + private static Version ParseVersion (string attributeValue) + { + string[] parts = attributeValue.Split ('.'); + if (parts.Length > 4) + throw new FileLoadException (); + ushort[] versionNumbers = new ushort[4]; + for (int i = 0; i < versionNumbers.Length; i++) { + if (i >= parts.Length) + versionNumbers[i] = ushort.MaxValue; + else { + // Desktop compat: TryParse is a little more forgiving than Fusion. + for (int j = 0; j < parts[i].Length; j++) { + if (!Char.IsDigit (parts[i][j])) + throw new FileLoadException (); + } + + if (!(ushort.TryParse (parts[i], out versionNumbers[i]))) + throw new FileLoadException (); + } + } + + if (versionNumbers[0] == ushort.MaxValue || versionNumbers[1] == ushort.MaxValue) + throw new FileLoadException (); + if (versionNumbers[2] == ushort.MaxValue) + return new Version (versionNumbers[0], versionNumbers[1]); + if (versionNumbers[3] == ushort.MaxValue) + return new Version (versionNumbers[0], versionNumbers[1], versionNumbers[2]); + + return new Version (versionNumbers[0], versionNumbers[1], versionNumbers[2], versionNumbers[3]); + } + + private static string ParseCulture (string attributeValue) + { + if (attributeValue.Equals ("Neutral", StringComparison.OrdinalIgnoreCase)) { + return ""; + } else { + CultureInfo culture = new CultureInfo (attributeValue); // Force a CultureNotFoundException if not a valid culture. + return culture.Name; + } + } + + private static byte[] ParsePKT (string attributeValue) + { + if (attributeValue.Equals ("null", StringComparison.OrdinalIgnoreCase) || attributeValue == string.Empty) + return new byte[0]; + + if (attributeValue.Length != 8 * 2) + throw new FileLoadException (); + + byte[] pkt = new byte[8]; + int srcIndex = 0; + for (int i = 0; i < 8; i++) { + char hi = attributeValue[srcIndex++]; + char lo = attributeValue[srcIndex++]; + pkt[i] = (byte) ((ParseHexNybble (hi) << 4) | ParseHexNybble (lo)); + } + + return pkt; + } + + private static ProcessorArchitecture ParseProcessorArchitecture (String attributeValue) + { + if (attributeValue.Equals ("msil", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.MSIL; + if (attributeValue.Equals ("x86", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.X86; + if (attributeValue.Equals ("ia64", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.IA64; + if (attributeValue.Equals ("amd64", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.Amd64; + if (attributeValue.Equals ("arm", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.Arm; + throw new FileLoadException (); + } + + private static byte ParseHexNybble (char c) + { + if (c >= '0' && c <= '9') + return (byte) (c - '0'); + if (c >= 'a' && c <= 'f') + return (byte) (c - 'a' + 10); + if (c >= 'A' && c <= 'F') + return (byte) (c - 'A' + 10); + throw new FileLoadException (); + } + + private static readonly char[] s_illegalCharactersInSimpleName = { '/', '\\', ':' }; + } +} \ No newline at end of file diff --git a/src/linker/Linker/AssemblyUtilities.cs b/src/linker/Linker/AssemblyUtilities.cs index 6e1749b2dc19..4917e382d4f7 100644 --- a/src/linker/Linker/AssemblyUtilities.cs +++ b/src/linker/Linker/AssemblyUtilities.cs @@ -12,33 +12,6 @@ public static bool IsCrossgened (this ModuleDefinition module) (module.Attributes & ModuleAttributes.ILLibrary) != 0; } - public static TypeDefinition ResolveFullyQualifiedTypeName (LinkContext context, string name) - { - if (!TypeNameParser.TryParseTypeAssemblyQualifiedName (name, out string typeName, out string assemblyName)) - return null; - - foreach (var assemblyDefinition in context.GetAssemblies ()) { - if (assemblyName != null && assemblyDefinition.Name.Name != assemblyName) - continue; - - var foundType = assemblyDefinition.MainModule.GetType (typeName); - if (foundType == null) - continue; - - return foundType; - } - - return null; - } - - public static TypeDefinition FindType (this AssemblyDefinition assembly, string fullName) - { - fullName = fullName.ToCecilName (); - - var type = assembly.MainModule.GetType (fullName); - return type?.Resolve (); - } - public static EmbeddedResource FindEmbeddedResource (this AssemblyDefinition assembly, string name) { foreach (var resource in assembly.MainModule.Resources) { diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index 55f2e901a81a..22ce2ea1a87c 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -57,6 +57,7 @@ public class LinkContext : IDisposable bool _ignoreUnresolved; readonly AssemblyResolver _resolver; + readonly ReflectionTypeNameResolver _typeNameResolver; readonly ReaderParameters _readerParameters; ISymbolReaderProvider _symbolReaderProvider; @@ -150,6 +151,10 @@ public AssemblyResolver Resolver { get { return _resolver; } } + internal ReflectionTypeNameResolver TypeNameResolver { + get { return _typeNameResolver; } + } + public ReaderParameters ReaderParameters { get { return _readerParameters; } } @@ -216,6 +221,7 @@ public LinkContext (Pipeline pipeline, AssemblyResolver resolver, ReaderParamete _pipeline = pipeline; _resolver = resolver; _resolver.Context = this; + _typeNameResolver = new ReflectionTypeNameResolver (this); _actions = new Dictionary (); _parameters = new Dictionary (StringComparer.Ordinal); _readerParameters = readerParameters; diff --git a/src/linker/Linker/RuntimeAssemblyName.cs b/src/linker/Linker/RuntimeAssemblyName.cs new file mode 100644 index 000000000000..d6dcc24ac5d3 --- /dev/null +++ b/src/linker/Linker/RuntimeAssemblyName.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace System.Reflection.Runtime.Assemblies +{ + // + // This is a private assembly name abstraction that's more suitable for use as keys in our caches. + // + // - Immutable, unlike the public AssemblyName + // - Has a useful Equals() override, unlike the public AssemblyName. + // + // We use this as our internal interchange type and only convert to and from the public AssemblyName class at public boundaries. + // + internal sealed class RuntimeAssemblyName : IEquatable + { + public RuntimeAssemblyName (string name, Version version, string cultureName, AssemblyNameFlags flags, byte[] publicKeyOrToken) + { + Debug.Assert (name != null); + this.Name = name; + + // Optional version. + this.Version = version; + + // Optional culture name. + this.CultureName = cultureName; + + // Optional flags (this is actually an OR of the classic flags and the ContentType.) + this.Flags = flags; + + // Optional public key (if Flags.PublicKey == true) or public key token. + this.PublicKeyOrToken = publicKeyOrToken; + } + + // Simple name. + public string Name { get; private set; } + + // Optional version. + public Version Version { get; private set; } + + // Optional culture name. + public string CultureName { get; private set; } + + // Optional flags (this is actually an OR of the classic flags and the ContentType.) + public AssemblyNameFlags Flags { get; private set; } + + // Optional public key (if Flags.PublicKey == true) or public key token. + public byte[] PublicKeyOrToken { get; private set; } + + // Equality - this compares every bit of data in the RuntimeAssemblyName which is acceptable for use as keys in a cache + // where semantic duplication is permissible. This method is *not* meant to define ref->def binding rules or + // assembly binding unification rules. + public bool Equals (RuntimeAssemblyName other) + { + if (other == null) + return false; + if (!this.Name.Equals (other.Name)) + return false; + if (this.Version == null) { + if (other.Version != null) + return false; + } else { + if (!this.Version.Equals (other.Version)) + return false; + } + if (!string.Equals (this.CultureName, other.CultureName)) + return false; + if (this.Flags != other.Flags) + return false; + + byte[] thisPK = this.PublicKeyOrToken; + byte[] otherPK = other.PublicKeyOrToken; + if (thisPK == null) { + if (otherPK != null) + return false; + } else if (otherPK == null) { + return false; + } else if (thisPK.Length != otherPK.Length) { + return false; + } else { + for (int i = 0; i < thisPK.Length; i++) { + if (thisPK[i] != otherPK[i]) + return false; + } + } + + return true; + } + + public sealed override bool Equals (Object obj) + { + RuntimeAssemblyName other = obj as RuntimeAssemblyName; + if (other == null) + return false; + + return Equals (other); + } + + public sealed override int GetHashCode () + { + return this.Name.GetHashCode (); + } + + public string FullName { + get { + return AssemblyNameHelpers.ComputeDisplayName (this); + } + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/TypeLexer.cs b/src/linker/Linker/TypeLexer.cs new file mode 100644 index 000000000000..31e70d9e91a1 --- /dev/null +++ b/src/linker/Linker/TypeLexer.cs @@ -0,0 +1,234 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Reflection.Runtime.Assemblies; + + +namespace System.Reflection.Runtime.TypeParsing +{ + // + // String tokenizer for typenames passed to the GetType() api's. + // + internal sealed class TypeLexer + { + public TypeLexer (string s) + { + // Turn the string into a char array with a NUL terminator. + char[] chars = new char[s.Length + 1]; + s.CopyTo (0, chars, 0, s.Length); + _chars = chars; + _index = 0; + } + + public TokenType Peek { + get { + SkipWhiteSpace (); + char c = _chars[_index]; + return CharToToken (c); + } + } + + public TokenType PeekSecond { + get { + SkipWhiteSpace (); + int index = _index + 1; + while (Char.IsWhiteSpace (_chars[index])) + index++; + char c = _chars[index]; + return CharToToken (c); + } + } + + + public void Skip () + { + Debug.Assert (_index != _chars.Length); + SkipWhiteSpace (); + _index++; + } + + // Return the next token and skip index past it unless already at end of string + // or the token is not a reserved token. + public TokenType GetNextToken () + { + TokenType tokenType = Peek; + if (tokenType == TokenType.End || tokenType == TokenType.Other) + return tokenType; + Skip (); + return tokenType; + } + + // + // Lex the next segment as part of a type name. (Do not use for assembly names.) + // + // Note that unescaped "."'s do NOT terminate the identifier, but unescaped "+"'s do. + // + // Terminated by the first non-escaped reserved character ('[', ']', '+', '&', '*' or ',') + // + public string GetNextIdentifier () + { + SkipWhiteSpace (); + + int src = _index; + char[] buffer = new char[_chars.Length]; + int dst = 0; + for (; ; ) + { + char c = _chars[src]; + TokenType token = CharToToken (c); + if (token != TokenType.Other) + break; + src++; + if (c == '\\') { + c = _chars[src]; + if (c != NUL) + src++; + if (c == NUL || CharToToken (c) == TokenType.Other) { + // If we got here, a backslash was used to escape a character that is not legal to escape inside a type name. + // + // Common sense would dictate throwing an ArgumentException but that's not what the desktop CLR does. + // The desktop CLR treats this case by returning FALSE from TypeName::TypeNameParser::GetIdentifier(). + // Unfortunately, no one checks this return result. Instead, the CLR keeps parsing (unfortunately, the lexer + // was left in some strange state by the previous failure but typically, this goes unnoticed) and eventually, tries to resolve + // a Type whose name is the empty string. When it can't resolve that type, the CLR throws a TypeLoadException() + // complaining about be unable to find a type with the empty name. + // + // To emulate this accidental behavior, we'll throw a special exception that's caught by the TypeParser. + // + throw new IllegalEscapeSequenceException (); + } + + } + buffer[dst++] = c; + } + + _index = src; + return new string (buffer, 0, dst); + } + + // + // Lex the next segment as the assembly name at the end of an assembly-qualified type name. (Do not use for + // assembly names embedded inside generic type arguments.) + // + // Terminated by NUL. There are no escape characters defined by the typename lexer (however, AssemblyName + // does have its own escape rules.) + // + public RuntimeAssemblyName GetNextAssemblyName () + { + SkipWhiteSpace (); + + int src = _index; + char[] buffer = new char[_chars.Length]; + int dst = 0; + for (; ; ) + { + char c = _chars[src]; + if (c == NUL) + break; + src++; + buffer[dst++] = c; + } + _index = src; + string fullName = new string (buffer, 0, dst); + return AssemblyNameParser.Parse (fullName); + } + + // + // Lex the next segment as an assembly name embedded inside a generic argument type. + // + // Terminated by an unescaped ']'. + // + public RuntimeAssemblyName GetNextEmbeddedAssemblyName () + { + SkipWhiteSpace (); + + int src = _index; + char[] buffer = new char[_chars.Length]; + int dst = 0; + for (; ; ) + { + char c = _chars[src]; + if (c == NUL) + throw new ArgumentException (); + if (c == ']') + break; + src++; + + // Backslash can be used to escape a ']' - any other backslash character is left alone (along with the backslash) + // for the AssemblyName parser to handle. + if (c == '\\' && _chars[src] == ']') { + c = _chars[src++]; + } + buffer[dst++] = c; + } + _index = src; + string fullName = new string (buffer, 0, dst); + return AssemblyNameParser.Parse (fullName); + } + + // + // Classify a character as a TokenType. (Fortunately, all tokens in typename strings other than identifiers are single-character tokens.) + // + private static TokenType CharToToken (char c) + { + switch (c) { + case NUL: + return TokenType.End; + case '[': + return TokenType.OpenSqBracket; + case ']': + return TokenType.CloseSqBracket; + case ',': + return TokenType.Comma; + case '+': + return TokenType.Plus; + case '*': + return TokenType.Asterisk; + case '&': + return TokenType.Ampersand; + default: + return TokenType.Other; + } + } + + // + // The desktop typename parser has a strange attitude towards whitespace. It throws away whitespace between punctuation tokens and whitespace + // preceeding identifiers or assembly names (and this cannot be escaped away). But whitespace between the end of an identifier + // and the punctuation that ends it is *not* ignored. + // + // In other words, GetType(" Foo") searches for "Foo" but GetType("Foo ") searches for "Foo ". + // + // Whitespace between the end of an assembly name and the punction mark that ends it is also not ignored by this parser, + // but this is irrelevant since the assembly name is then turned over to AssemblyName for parsing, which *does* ignore trailing whitespace. + // + private void SkipWhiteSpace () + { + while (Char.IsWhiteSpace (_chars[_index])) + _index++; + } + + + private int _index; + private readonly char[] _chars; + private const char NUL = (char) 0; + + + public sealed class IllegalEscapeSequenceException : Exception + { + } + } + + internal enum TokenType + { + End = 0, //At end of string + OpenSqBracket = 1, //'[' + CloseSqBracket = 2, //']' + Comma = 3, //',' + Plus = 4, //'+' + Asterisk = 5, //'*' + Ampersand = 6, //'&' + Other = 7, //Type identifier, AssemblyName or embedded AssemblyName. + } +} \ No newline at end of file diff --git a/src/linker/Linker/TypeName.cs b/src/linker/Linker/TypeName.cs new file mode 100644 index 000000000000..dc95e3497522 --- /dev/null +++ b/src/linker/Linker/TypeName.cs @@ -0,0 +1,220 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Collections.Generic; +using System.Reflection.Runtime.Assemblies; + +namespace System.Reflection.Runtime.TypeParsing +{ + // + // The TypeName class is the base class for a family of types that represent the nodes in a parse tree for + // assembly-qualified type names. + // + internal abstract class TypeName + { + public abstract override string ToString (); + } + + + // + // Represents a parse of a type name optionally qualified by an assembly name. If present, the assembly name follows + // a comma following the type name. + // + internal sealed class AssemblyQualifiedTypeName : TypeName + { + public AssemblyQualifiedTypeName (NonQualifiedTypeName typeName, RuntimeAssemblyName assemblyName) + { + Debug.Assert (typeName != null); + TypeName = typeName; + AssemblyName = assemblyName; + } + + public NonQualifiedTypeName TypeName { get; private set; } + public RuntimeAssemblyName AssemblyName { get; private set; } + + public sealed override string ToString () + { + return TypeName.ToString () + ((AssemblyName == null) ? "" : ", " + AssemblyName.FullName); + } + } + + // + // Base class for all non-assembly-qualified type names. + // + internal abstract class NonQualifiedTypeName : TypeName + { + } + + // + // Base class for namespace or nested type. + // + internal abstract class NamedTypeName : NonQualifiedTypeName + { + } + + // + // Non-nested named type. The full name is the namespace-qualified name. For example, the FullName for + // System.Collections.Generic.IList<> is "System.Collections.Generic.IList`1". + // + internal sealed partial class NamespaceTypeName : NamedTypeName + { + public NamespaceTypeName (string[] namespaceParts, string name) + { + Debug.Assert (namespaceParts != null); + Debug.Assert (name != null); + + _name = name; + _namespaceParts = namespaceParts; + } + + public sealed override string ToString () + { + string fullName = ""; + for (int i = 0; i < _namespaceParts.Length; i++) { + fullName += _namespaceParts[_namespaceParts.Length - i - 1]; + fullName += "."; + } + fullName += _name; + return fullName; + } + + private string _name; + private string[] _namespaceParts; + } + + // + // A nested type. The Name is the simple name of the type (not including any portion of its declaring type name.) + // + internal sealed class NestedTypeName : NamedTypeName + { + public NestedTypeName (string name, NamedTypeName declaringType) + { + Name = name; + DeclaringType = declaringType; + } + + public string Name { get; private set; } + public NamedTypeName DeclaringType { get; private set; } + + public sealed override string ToString () + { + // Cecil's format uses '/' instead of '+' for nested types. + return DeclaringType + "/" + Name; + } + } + + // + // Abstract base for array, byref and pointer type names. + // + internal abstract class HasElementTypeName : NonQualifiedTypeName + { + public HasElementTypeName (TypeName elementTypeName) + { + ElementTypeName = elementTypeName; + } + + public TypeName ElementTypeName { get; private set; } + } + + // + // A single-dimensional zero-lower-bound array type name. + // + internal sealed class ArrayTypeName : HasElementTypeName + { + public ArrayTypeName (TypeName elementTypeName) + : base (elementTypeName) + { + } + + public sealed override string ToString () + { + return ElementTypeName + "[]"; + } + } + + // + // A multidim array type name. + // + internal sealed class MultiDimArrayTypeName : HasElementTypeName + { + public MultiDimArrayTypeName (TypeName elementTypeName, int rank) + : base (elementTypeName) + { + _rank = rank; + } + + public sealed override string ToString () + { + return ElementTypeName + "[" + (_rank == 1 ? "*" : new string (',', _rank - 1)) + "]"; + } + + private int _rank; + } + + // + // A byref type. + // + internal sealed class ByRefTypeName : HasElementTypeName + { + public ByRefTypeName (TypeName elementTypeName) + : base (elementTypeName) + { + } + + public sealed override string ToString () + { + return ElementTypeName + "&"; + } + } + + // + // A pointer type. + // + internal sealed class PointerTypeName : HasElementTypeName + { + public PointerTypeName (TypeName elementTypeName) + : base (elementTypeName) + { + } + + public sealed override string ToString () + { + return ElementTypeName + "*"; + } + } + + // + // A constructed generic type. + // + internal sealed class ConstructedGenericTypeName : NonQualifiedTypeName + { + public ConstructedGenericTypeName (NamedTypeName genericType, IEnumerable genericArguments) + { + GenericType = genericType; + GenericArguments = genericArguments; + } + + public NamedTypeName GenericType { get; private set; } + public IEnumerable GenericArguments { get; private set; } + + public sealed override string ToString () + { + string s = GenericType.ToString (); + s += "["; + string sep = ""; + foreach (TypeName genericTypeArgument in GenericArguments) { + s += sep; + sep = ","; + AssemblyQualifiedTypeName assemblyQualifiedTypeArgument = genericTypeArgument as AssemblyQualifiedTypeName; + if (assemblyQualifiedTypeArgument == null || assemblyQualifiedTypeArgument.AssemblyName == null) + s += genericTypeArgument.ToString (); + else + s += "[" + genericTypeArgument.ToString () + "]"; + } + s += "]"; + return s; + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/TypeNameParser.cs b/src/linker/Linker/TypeNameParser.cs deleted file mode 100644 index b04b2c607584..000000000000 --- a/src/linker/Linker/TypeNameParser.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; - -namespace Mono.Linker -{ - public static class TypeNameParser - { - public static bool TryParseTypeAssemblyQualifiedName (string value, out string typeName, out string assemblyName) - { - if (string.IsNullOrEmpty (value)) { - typeName = null; - assemblyName = null; - return false; - } - - //Filter the assembly qualified name down to the basic type by removing pointer, reference, and array markers on the type - //We must also convert nested types from + to / to match cecil's formatting - value = value - .Replace ('+', '/') - .Replace ("*", string.Empty) - .Replace ("&", string.Empty); - - while (value.IndexOf ('[') > 0) { - var openidx = value.IndexOf ('['); - var closeidx = value.IndexOf (']'); - - // No matching close ] or out of order - if (closeidx < 0 || closeidx < openidx) { - typeName = null; - assemblyName = null; - return false; - } - - value = value.Remove (openidx, closeidx + 1 - openidx); - } - - var tokens = value.Split (','); - typeName = tokens[0].Trim (); - assemblyName = null; - if (tokens.Length > 1) - assemblyName = tokens[1].Trim (); - - if (string.IsNullOrWhiteSpace (typeName)) { - typeName = null; - assemblyName = null; - return false; - } - - return true; - } - } -} \ No newline at end of file diff --git a/src/linker/Linker/TypeParser.cs b/src/linker/Linker/TypeParser.cs new file mode 100644 index 000000000000..d68f690967f7 --- /dev/null +++ b/src/linker/Linker/TypeParser.cs @@ -0,0 +1,185 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Reflection.Runtime.Assemblies; + +namespace System.Reflection.Runtime.TypeParsing +{ + // + // Parser for type names passed to GetType() apis. + // + internal sealed class TypeParser + { + // + // Parses a typename. The typename may be optionally postpended with a "," followed by a legal assembly name. + // + public static AssemblyQualifiedTypeName ParseAssemblyQualifiedTypeName (string s) + { + // Desktop compat: a whitespace-only "typename" qualified by an assembly name throws an ArgumentException rather than + // a TypeLoadException. + int idx = 0; + while (idx < s.Length && Char.IsWhiteSpace (s[idx])) { + idx++; + } + if (idx < s.Length && s[idx] == ',') + throw new ArgumentException (); + + try { + TypeParser parser = new TypeParser (s); + NonQualifiedTypeName typeName = parser.ParseNonQualifiedTypeName (); + TokenType token = parser._lexer.GetNextToken (); + if (token == TokenType.End) + return new AssemblyQualifiedTypeName (typeName, null); + if (token == TokenType.Comma) { + RuntimeAssemblyName assemblyName = parser._lexer.GetNextAssemblyName (); + token = parser._lexer.Peek; + if (token != TokenType.End) + throw new ArgumentException (); + return new AssemblyQualifiedTypeName (typeName, assemblyName); + } + throw new ArgumentException (); + } catch (TypeLexer.IllegalEscapeSequenceException) { + // Emulates a CLR4.5 bug that causes any string that contains an illegal escape sequence to be parsed as the empty string. + return ParseAssemblyQualifiedTypeName (string.Empty); + } + } + + private TypeParser (string s) + { + _lexer = new TypeLexer (s); + } + + // + // Parses a type name without any assembly name qualification. + // + private NonQualifiedTypeName ParseNonQualifiedTypeName () + { + // Parse the named type or constructed generic type part first. + NonQualifiedTypeName typeName = ParseNamedOrConstructedGenericTypeName (); + + // Iterate through any "has-element" qualifiers ([], &, *). + for (; ; ) + { + TokenType token = _lexer.Peek; + if (token == TokenType.End) + break; + if (token == TokenType.Asterisk) { + _lexer.Skip (); + typeName = new PointerTypeName (typeName); + } else if (token == TokenType.Ampersand) { + _lexer.Skip (); + typeName = new ByRefTypeName (typeName); + } else if (token == TokenType.OpenSqBracket) { + _lexer.Skip (); + token = _lexer.GetNextToken (); + if (token == TokenType.Asterisk) { + typeName = new MultiDimArrayTypeName (typeName, 1); + token = _lexer.GetNextToken (); + } else { + int rank = 1; + while (token == TokenType.Comma) { + token = _lexer.GetNextToken (); + rank++; + } + if (rank == 1) + typeName = new ArrayTypeName (typeName); + else + typeName = new MultiDimArrayTypeName (typeName, rank); + } + if (token != TokenType.CloseSqBracket) + throw new ArgumentException (); + } else { + break; + } + } + return typeName; + } + + // + // Foo or Foo+Inner or Foo[String] or Foo+Inner[String] + // + private NonQualifiedTypeName ParseNamedOrConstructedGenericTypeName () + { + NamedTypeName namedType = ParseNamedTypeName (); + // Because "[" is used both for generic arguments and array indexes, we must peek two characters deep. + if (!(_lexer.Peek == TokenType.OpenSqBracket && (_lexer.PeekSecond == TokenType.Other || _lexer.PeekSecond == TokenType.OpenSqBracket))) + return namedType; + else { + _lexer.Skip (); + List genericTypeArguments = new List (); + for (; ; ) + { + TypeName genericTypeArgument = ParseGenericTypeArgument (); + genericTypeArguments.Add (genericTypeArgument); + TokenType token = _lexer.GetNextToken (); + if (token == TokenType.CloseSqBracket) + break; + if (token != TokenType.Comma) + throw new ArgumentException (); + } + + return new ConstructedGenericTypeName (namedType, genericTypeArguments); + } + } + + // + // Foo or Foo+Inner + // + private NamedTypeName ParseNamedTypeName () + { + NamedTypeName namedType = ParseNamespaceTypeName (); + while (_lexer.Peek == TokenType.Plus) { + _lexer.Skip (); + string nestedTypeName = _lexer.GetNextIdentifier (); + namedType = new NestedTypeName (nestedTypeName, namedType); + } + + return namedType; + } + + // + // Non-nested named type. + // + private NamespaceTypeName ParseNamespaceTypeName () + { + string fullName = _lexer.GetNextIdentifier (); + string[] parts = fullName.Split ('.'); + int numNamespaceParts = parts.Length - 1; + string[] namespaceParts = new string[numNamespaceParts]; + for (int i = 0; i < numNamespaceParts; i++) + namespaceParts[numNamespaceParts - i - 1] = parts[i]; + string name = parts[numNamespaceParts]; + return new NamespaceTypeName (namespaceParts, name); + } + + // + // Parse a generic argument. In particular, generic arguments can take the special form [,]. + // + private TypeName ParseGenericTypeArgument () + { + TokenType token = _lexer.GetNextToken (); + if (token == TokenType.Other) { + NonQualifiedTypeName nonQualifiedTypeName = ParseNonQualifiedTypeName (); + return new AssemblyQualifiedTypeName (nonQualifiedTypeName, null); + } else if (token == TokenType.OpenSqBracket) { + RuntimeAssemblyName assemblyName = null; + NonQualifiedTypeName typeName = ParseNonQualifiedTypeName (); + token = _lexer.GetNextToken (); + if (token == TokenType.Comma) { + assemblyName = _lexer.GetNextEmbeddedAssemblyName (); + token = _lexer.GetNextToken (); + } + + if (token != TokenType.CloseSqBracket) + throw new ArgumentException (); + + return new AssemblyQualifiedTypeName (typeName, assemblyName); + } else + throw new ArgumentException (); + } + + private TypeLexer _lexer; + } +} \ No newline at end of file diff --git a/src/linker/Linker/TypeParsing.cs b/src/linker/Linker/TypeParsing.cs new file mode 100644 index 000000000000..824e5b2fc1fa --- /dev/null +++ b/src/linker/Linker/TypeParsing.cs @@ -0,0 +1,77 @@ +using System; +using System.Reflection.Runtime.TypeParsing; +using System.Reflection.Runtime.Assemblies; +using Mono.Cecil; + +namespace Mono.Linker +{ + internal class ReflectionTypeNameResolver + { + private readonly LinkContext _context; + + public ReflectionTypeNameResolver (LinkContext context) + { + _context = context; + } + + public TypeDefinition ResolveTypeName (string typeNameString) + { + if (string.IsNullOrEmpty (typeNameString)) + return null; + + AssemblyQualifiedTypeName parsedTypeName; + try { + parsedTypeName = TypeParser.ParseAssemblyQualifiedTypeName (typeNameString); + } catch (ArgumentException) { + return null; + } catch (System.IO.FileLoadException) { + return null; + } + + return ResolveTypeName (parsedTypeName); + } + + private TypeDefinition ResolveTypeName (TypeName typeName) + { + if (typeName is AssemblyQualifiedTypeName) { + AssemblyQualifiedTypeName assemblyQualifiedTypeName = (AssemblyQualifiedTypeName) typeName; + RuntimeAssemblyName assemblyName = assemblyQualifiedTypeName.AssemblyName; + foreach (var assemblyDefinition in _context.GetAssemblies ()) { + if (assemblyName != null && assemblyDefinition.Name.Name != assemblyName.Name) + continue; + + var foundType = ResolveTypeName (assemblyDefinition, assemblyQualifiedTypeName.TypeName); + if (foundType == null) + continue; + + return foundType; + } + + return null; + } else if (typeName is NonQualifiedTypeName) { + return ResolveTypeName (null, (NonQualifiedTypeName) typeName); + } + + // This is unreachable + throw new NotImplementedException (); + } + + public TypeDefinition ResolveTypeName (AssemblyDefinition assembly, NonQualifiedTypeName typeName) + { + if (typeName is ConstructedGenericTypeName) { + ConstructedGenericTypeName genericTypeName = (ConstructedGenericTypeName) typeName; + return assembly.MainModule.GetType (genericTypeName.GenericType.ToString ()); + } else if (typeName is HasElementTypeName) { + HasElementTypeName elementTypeName = (HasElementTypeName) typeName; + TypeDefinition elementType = ResolveTypeName (assembly, elementTypeName.ElementTypeName as NonQualifiedTypeName); + if (elementType == null) + return null; + + return assembly.MainModule.GetType (elementType.ToString ()); + } + + return assembly.MainModule.GetType (typeName.ToString ()); + } + + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests/Tests/TypeNameParserTests.cs b/test/Mono.Linker.Tests/Tests/TypeNameParserTests.cs deleted file mode 100644 index 758d5ef17ad4..000000000000 --- a/test/Mono.Linker.Tests/Tests/TypeNameParserTests.cs +++ /dev/null @@ -1,125 +0,0 @@ -using NUnit.Framework; - -namespace Mono.Linker.Tests -{ - [TestFixture] - public class TypeNameParserTests - { - [Test] - public void TryParseTypeAssemblyQualifiedName_Null () - { - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (null, out _, out _), Is.False); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_FullyQualified () - { - var value = typeof (TypeNameParserTests).AssemblyQualifiedName; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo (typeof (TypeNameParserTests).FullName)); - Assert.That (assemblyName, Is.EqualTo (typeof (TypeNameParserTests).Assembly.GetName ().Name)); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_NameAndAssemblyOnly () - { - var value = $"{typeof (TypeNameParserTests).FullName}, {typeof (TypeNameParserTests).Assembly.GetName ().Name}"; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo (typeof (TypeNameParserTests).FullName)); - Assert.That (assemblyName, Is.EqualTo (typeof (TypeNameParserTests).Assembly.GetName ().Name)); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_NameOnly () - { - var value = typeof (TypeNameParserTests).FullName; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo (typeof (TypeNameParserTests).FullName)); - Assert.That (assemblyName, Is.Null); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_GenericType_FullyQualified () - { - var value = typeof (SampleGenericType<,>).AssemblyQualifiedName; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo ($"{typeof (TypeNameParserTests).FullName}/SampleGenericType`2")); - Assert.That (assemblyName, Is.EqualTo (typeof (TypeNameParserTests).Assembly.GetName ().Name)); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_GenericType_NameAndAssemblyOnly () - { - var value = $"{typeof (SampleGenericType<,>).FullName}, {typeof (TypeNameParserTests).Assembly.GetName ().Name}"; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo ($"{typeof (TypeNameParserTests).FullName}/SampleGenericType`2")); - Assert.That (assemblyName, Is.EqualTo (typeof (TypeNameParserTests).Assembly.GetName ().Name)); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_GenericType_NameOnly () - { - var value = typeof (SampleGenericType<,>).FullName; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo ($"{typeof (TypeNameParserTests).FullName}/SampleGenericType`2")); - Assert.That (assemblyName, Is.Null); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_NestedType_FullyQualified () - { - var value = typeof (SampleNestedType).AssemblyQualifiedName; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo ($"{typeof (TypeNameParserTests).FullName}/{nameof (SampleNestedType)}")); - Assert.That (assemblyName, Is.EqualTo (typeof (TypeNameParserTests).Assembly.GetName ().Name)); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_NestedType_NameAndAssemblyOnly () - { - var value = $"{typeof (SampleNestedType).FullName}, {typeof (TypeNameParserTests).Assembly.GetName ().Name}"; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo ($"{typeof (TypeNameParserTests).FullName}/{nameof (SampleNestedType)}")); - Assert.That (assemblyName, Is.EqualTo (typeof (TypeNameParserTests).Assembly.GetName ().Name)); - } - - [Test] - public void TryParseTypeAssemblyQualifiedName_NestedType_NameOnly () - { - var value = typeof (SampleNestedType).FullName; - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (value, out string typeName, out string assemblyName), Is.True); - Assert.That (typeName, Is.EqualTo ($"{typeof (TypeNameParserTests).FullName}/{nameof (SampleNestedType)}")); - Assert.That (assemblyName, Is.Null); - } - - [Test] - public void MissingTypeName () - { - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (", System", out string typeName, out string assemblyName), Is.False); - Assert.That (typeName, Is.Null); - Assert.That (assemblyName, Is.Null); - } - - - [TestCase ("A[]][")] - [TestCase ("A][")] - [TestCase ("A[")] - [TestCase (", , ")] - [TestCase (", , , ")] - [TestCase (", , , , ")] - public void InvalidValues (string name) - { - Assert.That (TypeNameParser.TryParseTypeAssemblyQualifiedName (name, out string typeName, out string assemblyName), Is.False); - Assert.That (typeName, Is.Null); - Assert.That (assemblyName, Is.Null); - } - - class SampleNestedType - { - } - - class SampleGenericType - { - } - } -} \ No newline at end of file From 075accdfaaa9c0308c3ee258a995dd8c66893883 Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Thu, 10 Sep 2020 00:22:57 -0700 Subject: [PATCH 2/5] PR feedback --- .../ReflectionMethodBodyScanner.cs | 14 +-- src/linker/Linker.Steps/LinkAttributesStep.cs | 3 +- src/linker/Linker.Steps/MarkStep.cs | 17 +++- ...ies.cs => AssemblyDefinitionExtensions.cs} | 12 +-- src/linker/Linker/LinkContext.cs | 8 +- .../Linker/ModuleDefinitionExtensions.cs | 14 +++ src/linker/Linker/RuntimeAssemblyName.cs | 2 +- src/linker/Linker/TypeName.cs | 6 +- src/linker/Linker/TypeNameResolver.cs | 71 +++++++++++++++ src/linker/Linker/TypeParser.cs | 19 ++-- src/linker/Linker/TypeParsing.cs | 77 ----------------- .../Reflection/TypeUsedViaReflection.cs | 30 +++++++ .../Tests/TypeNameResolverTests.cs | 86 +++++++++++++++++++ 13 files changed, 243 insertions(+), 116 deletions(-) rename src/linker/Linker/{AssemblyUtilities.cs => AssemblyDefinitionExtensions.cs} (57%) create mode 100644 src/linker/Linker/ModuleDefinitionExtensions.cs create mode 100644 src/linker/Linker/TypeNameResolver.cs delete mode 100644 src/linker/Linker/TypeParsing.cs create mode 100644 test/Mono.Linker.Tests/Tests/TypeNameResolverTests.cs diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index 0fa12430260c..1c39907582c7 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -797,13 +797,14 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } foreach (var typeNameValue in methodParams[0].UniqueValues ()) { if (typeNameValue is KnownStringValue knownStringValue) { - TypeDefinition foundType = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); + TypeReference foundType = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. reflectionContext.RecordHandledPattern (); } else { - reflectionContext.RecordRecognizedPattern (foundType, () => _markStep.MarkTypeVisibleToReflection (foundType, new DependencyInfo (DependencyKind.AccessedViaReflection, callingMethodDefinition), callingMethodDefinition)); - methodReturnValue = MergePointValue.MergeValues (methodReturnValue, new SystemTypeValue (foundType)); + var typeDef = foundType?.Resolve (); + reflectionContext.RecordRecognizedPattern (typeDef, () => _markStep.MarkTypeVisibleToReflection (foundType, new DependencyInfo (DependencyKind.AccessedViaReflection, callingMethodDefinition), callingMethodDefinition)); + methodReturnValue = MergePointValue.MergeValues (methodReturnValue, new SystemTypeValue (typeDef)); } } else if (typeNameValue == NullValue.Instance) { reflectionContext.RecordHandledPattern (); @@ -1335,8 +1336,7 @@ void ProcessCreateInstanceByName (ref ReflectionPatternContext reflectionContext continue; } - var typeName = System.Reflection.Runtime.TypeParsing.TypeParser.ParseAssemblyQualifiedTypeName (typeNameStringValue.Contents).TypeName; - var resolvedType = _context.TypeNameResolver.ResolveTypeName (resolvedAssembly, typeName); + var resolvedType = TypeNameResolver.ResolveTypeName (resolvedAssembly, typeNameStringValue.Contents); if (resolvedType == null) { // It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case // Note that we did find the assembly, so it's not a linker config problem, it's either intentional, or wrong versions of assemblies @@ -1345,7 +1345,7 @@ void ProcessCreateInstanceByName (ref ReflectionPatternContext reflectionContext continue; } - MarkConstructorsOnType (ref reflectionContext, resolvedType, + MarkConstructorsOnType (ref reflectionContext, resolvedType?.Resolve (), parameterlessConstructor ? m => m.Parameters.Count == 0 : (Func) null, bindingFlags); } else { reflectionContext.RecordUnrecognizedPattern (2032, $"Unrecognized value passed to the parameter '{calledMethod.Parameters[1].Name}' of method '{calledMethod.GetDisplayName ()}'. It's not possible to guarantee the availability of the target type."); @@ -1568,7 +1568,7 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC } else if (uniqueValue is SystemTypeValue systemTypeValue) { MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, systemTypeValue.TypeRepresented, requiredMemberTypes); } else if (uniqueValue is KnownStringValue knownStringValue) { - TypeDefinition foundType = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); + TypeDefinition foundType = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents)?.Resolve (); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. reflectionContext.RecordHandledPattern (); diff --git a/src/linker/Linker.Steps/LinkAttributesStep.cs b/src/linker/Linker.Steps/LinkAttributesStep.cs index a37eaed361db..3fb141a8d65e 100644 --- a/src/linker/Linker.Steps/LinkAttributesStep.cs +++ b/src/linker/Linker.Steps/LinkAttributesStep.cs @@ -172,8 +172,7 @@ bool GetAttributeType (XPathNodeIterator iterator, string attributeFullName, out return false; } - var typeName = System.Reflection.Runtime.TypeParsing.TypeParser.ParseAssemblyQualifiedTypeName (attributeFullName).TypeName; - attributeType = Context.TypeNameResolver.ResolveTypeName (assembly, typeName); + attributeType = TypeNameResolver.ResolveTypeName (assembly, attributeFullName)?.Resolve (); } if (attributeType == null) { diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 618ef7721878..3e5853814db4 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -32,6 +32,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection.Runtime.TypeParsing; using System.Text.RegularExpressions; using Mono.Cecil; using Mono.Cecil.Cil; @@ -733,7 +734,7 @@ protected virtual void MarkUserDependency (MemberReference context, CustomAttrib TypeDefinition td; if (args.Count >= 2 && args[1].Value is string typeName) { - td = _context.TypeNameResolver.ResolveTypeName (typeName + "," + (assembly ?? context.Module.Assembly)); + td = TypeNameResolver.ResolveTypeName (assembly ?? context.Module.Assembly, typeName)?.Resolve (); if (td == null) { _context.LogWarning ( $"Could not resolve dependency type '{typeName}' specified in a `PreserveDependency` attribute", 2004, context.Resolve ()); @@ -1551,8 +1552,16 @@ TypeDefinition GetDebuggerAttributeTargetType (CustomAttribute ca, AssemblyDefin if (property.Name == "Target") return ((TypeReference) property.Argument.Value)?.Resolve (); - if (property.Name == "TargetTypeName") - return _context.TypeNameResolver.ResolveTypeName ((string) property.Argument.Value); + if (property.Name == "TargetTypeName") { + string targetTypeName = (string) property.Argument.Value; + TypeName typeName = TypeParser.ParseTypeName (targetTypeName); + if (typeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) { + AssemblyDefinition assembly = _context.GetLoadedAssembly (assemblyQualifiedTypeName.AssemblyName.Name); + return TypeNameResolver.ResolveTypeName (assembly, targetTypeName)?.Resolve (); + } + + return TypeNameResolver.ResolveTypeName (asm, targetTypeName)?.Resolve (); + } } return null; @@ -1639,7 +1648,7 @@ protected virtual void MarkTypeConverterLikeDependency (CustomAttribute attribut TypeDefinition tdef = null; switch (attribute.ConstructorArguments[0].Value) { case string s: - tdef = _context.TypeNameResolver.ResolveTypeName (s); + tdef = _context.TypeNameResolver.ResolveTypeName (s)?.Resolve (); break; case TypeReference type: tdef = type.Resolve (); diff --git a/src/linker/Linker/AssemblyUtilities.cs b/src/linker/Linker/AssemblyDefinitionExtensions.cs similarity index 57% rename from src/linker/Linker/AssemblyUtilities.cs rename to src/linker/Linker/AssemblyDefinitionExtensions.cs index 4917e382d4f7..d5aced51a199 100644 --- a/src/linker/Linker/AssemblyUtilities.cs +++ b/src/linker/Linker/AssemblyDefinitionExtensions.cs @@ -1,17 +1,9 @@ -using Mono.Cecil; +using Mono.Cecil; namespace Mono.Linker { - - public static class AssemblyUtilities + public static class AssemblyDefinitionExtensions { - - public static bool IsCrossgened (this ModuleDefinition module) - { - return (module.Attributes & ModuleAttributes.ILOnly) == 0 && - (module.Attributes & ModuleAttributes.ILLibrary) != 0; - } - public static EmbeddedResource FindEmbeddedResource (this AssemblyDefinition assembly, string name) { foreach (var resource in assembly.MainModule.Resources) { diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index 22ce2ea1a87c..77ff231c0db8 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -57,7 +57,7 @@ public class LinkContext : IDisposable bool _ignoreUnresolved; readonly AssemblyResolver _resolver; - readonly ReflectionTypeNameResolver _typeNameResolver; + readonly TypeNameResolver _typeNameResolver; readonly ReaderParameters _readerParameters; ISymbolReaderProvider _symbolReaderProvider; @@ -151,7 +151,7 @@ public AssemblyResolver Resolver { get { return _resolver; } } - internal ReflectionTypeNameResolver TypeNameResolver { + internal TypeNameResolver TypeNameResolver { get { return _typeNameResolver; } } @@ -221,7 +221,7 @@ public LinkContext (Pipeline pipeline, AssemblyResolver resolver, ReaderParamete _pipeline = pipeline; _resolver = resolver; _resolver.Context = this; - _typeNameResolver = new ReflectionTypeNameResolver (this); + _typeNameResolver = new TypeNameResolver (this); _actions = new Dictionary (); _parameters = new Dictionary (StringComparer.Ordinal); _readerParameters = readerParameters; @@ -456,7 +456,7 @@ public virtual AssemblyDefinition[] GetAssemblies () public AssemblyDefinition GetLoadedAssembly (string name) { - if (_resolver.AssemblyCache.TryGetValue (name, out var ad)) + if (!string.IsNullOrEmpty(name) && _resolver.AssemblyCache.TryGetValue (name, out var ad)) return ad; return null; diff --git a/src/linker/Linker/ModuleDefinitionExtensions.cs b/src/linker/Linker/ModuleDefinitionExtensions.cs new file mode 100644 index 000000000000..08bcbb06deb7 --- /dev/null +++ b/src/linker/Linker/ModuleDefinitionExtensions.cs @@ -0,0 +1,14 @@ +using Mono.Cecil; + +namespace Mono.Linker +{ + public static class ModuleDefinitionExtensions + { + + public static bool IsCrossgened (this ModuleDefinition module) + { + return (module.Attributes & ModuleAttributes.ILOnly) == 0 && + (module.Attributes & ModuleAttributes.ILLibrary) != 0; + } + } +} diff --git a/src/linker/Linker/RuntimeAssemblyName.cs b/src/linker/Linker/RuntimeAssemblyName.cs index d6dcc24ac5d3..a7444f155064 100644 --- a/src/linker/Linker/RuntimeAssemblyName.cs +++ b/src/linker/Linker/RuntimeAssemblyName.cs @@ -14,7 +14,7 @@ namespace System.Reflection.Runtime.Assemblies // // We use this as our internal interchange type and only convert to and from the public AssemblyName class at public boundaries. // - internal sealed class RuntimeAssemblyName : IEquatable + public sealed class RuntimeAssemblyName : IEquatable { public RuntimeAssemblyName (string name, Version version, string cultureName, AssemblyNameFlags flags, byte[] publicKeyOrToken) { diff --git a/src/linker/Linker/TypeName.cs b/src/linker/Linker/TypeName.cs index dc95e3497522..461f2fe4faec 100644 --- a/src/linker/Linker/TypeName.cs +++ b/src/linker/Linker/TypeName.cs @@ -12,7 +12,7 @@ namespace System.Reflection.Runtime.TypeParsing // The TypeName class is the base class for a family of types that represent the nodes in a parse tree for // assembly-qualified type names. // - internal abstract class TypeName + public abstract class TypeName { public abstract override string ToString (); } @@ -22,7 +22,7 @@ internal abstract class TypeName // Represents a parse of a type name optionally qualified by an assembly name. If present, the assembly name follows // a comma following the type name. // - internal sealed class AssemblyQualifiedTypeName : TypeName + public sealed class AssemblyQualifiedTypeName : TypeName { public AssemblyQualifiedTypeName (NonQualifiedTypeName typeName, RuntimeAssemblyName assemblyName) { @@ -43,7 +43,7 @@ public sealed override string ToString () // // Base class for all non-assembly-qualified type names. // - internal abstract class NonQualifiedTypeName : TypeName + public abstract class NonQualifiedTypeName : TypeName { } diff --git a/src/linker/Linker/TypeNameResolver.cs b/src/linker/Linker/TypeNameResolver.cs new file mode 100644 index 000000000000..b8e877ae5b47 --- /dev/null +++ b/src/linker/Linker/TypeNameResolver.cs @@ -0,0 +1,71 @@ +using System; +using System.Reflection.Runtime.TypeParsing; +using Mono.Cecil; + +namespace Mono.Linker +{ + internal class TypeNameResolver + { + readonly LinkContext _context; + + public TypeNameResolver (LinkContext context) + { + _context = context; + } + + public TypeReference ResolveTypeName (string typeNameString) + { + if (string.IsNullOrEmpty (typeNameString)) + return null; + + TypeName parsedTypeName; + try { + parsedTypeName = TypeParser.ParseTypeName (typeNameString); + } catch (ArgumentException) { + return null; + } catch (System.IO.FileLoadException) { + return null; + } + + if (parsedTypeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) { + AssemblyDefinition assembly = _context.GetLoadedAssembly (assemblyQualifiedTypeName.AssemblyName.Name); + return ResolveTypeName (assembly, assemblyQualifiedTypeName.TypeName); + } + + foreach (var assemblyDefiniton in _context.GetAssemblies ()) { + var foundType = ResolveTypeName (assemblyDefiniton, parsedTypeName); + if (foundType != null) + return foundType; + } + + return null; + } + + public static TypeReference ResolveTypeName (AssemblyDefinition assembly, string typeNameString) + { + return ResolveTypeName (assembly, TypeParser.ParseTypeName (typeNameString)); + } + + static TypeReference ResolveTypeName (AssemblyDefinition assembly, TypeName typeName) + { + if (assembly == null) + return null; + + if (typeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) { + return ResolveTypeName (assembly, assemblyQualifiedTypeName.TypeName); + } else if (typeName is ConstructedGenericTypeName constructedGenericTypeName) { + var genericTypeDef = ResolveTypeName (assembly, constructedGenericTypeName.GenericType)?.Resolve (); + var genericInstanceType = new GenericInstanceType (genericTypeDef); + foreach (var arg in constructedGenericTypeName.GenericArguments) { + genericInstanceType.GenericArguments.Add (ResolveTypeName (assembly, arg)); + } + + return genericInstanceType; + } else if (typeName is HasElementTypeName elementTypeName) { + return ResolveTypeName (assembly, elementTypeName.ElementTypeName); + } + + return assembly.MainModule.GetType (typeName.ToString ()); + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/TypeParser.cs b/src/linker/Linker/TypeParser.cs index d68f690967f7..bec0e97a068e 100644 --- a/src/linker/Linker/TypeParser.cs +++ b/src/linker/Linker/TypeParser.cs @@ -10,28 +10,31 @@ namespace System.Reflection.Runtime.TypeParsing // // Parser for type names passed to GetType() apis. // - internal sealed class TypeParser + public sealed class TypeParser { // // Parses a typename. The typename may be optionally postpended with a "," followed by a legal assembly name. // - public static AssemblyQualifiedTypeName ParseAssemblyQualifiedTypeName (string s) + public static TypeName ParseTypeName (string s) { - // Desktop compat: a whitespace-only "typename" qualified by an assembly name throws an ArgumentException rather than - // a TypeLoadException. + if (string.IsNullOrEmpty (s)) + return null; + + // Linker specific: to keep the existing behavior, we return null whenever we have a whitespace type name, insteead + // of throwing any exception. int idx = 0; - while (idx < s.Length && Char.IsWhiteSpace (s[idx])) { + while (idx < s.Length && char.IsWhiteSpace (s[idx])) { idx++; } if (idx < s.Length && s[idx] == ',') - throw new ArgumentException (); + return null; try { TypeParser parser = new TypeParser (s); NonQualifiedTypeName typeName = parser.ParseNonQualifiedTypeName (); TokenType token = parser._lexer.GetNextToken (); if (token == TokenType.End) - return new AssemblyQualifiedTypeName (typeName, null); + return typeName; if (token == TokenType.Comma) { RuntimeAssemblyName assemblyName = parser._lexer.GetNextAssemblyName (); token = parser._lexer.Peek; @@ -42,7 +45,7 @@ public static AssemblyQualifiedTypeName ParseAssemblyQualifiedTypeName (string s throw new ArgumentException (); } catch (TypeLexer.IllegalEscapeSequenceException) { // Emulates a CLR4.5 bug that causes any string that contains an illegal escape sequence to be parsed as the empty string. - return ParseAssemblyQualifiedTypeName (string.Empty); + return ParseTypeName (string.Empty); } } diff --git a/src/linker/Linker/TypeParsing.cs b/src/linker/Linker/TypeParsing.cs deleted file mode 100644 index 824e5b2fc1fa..000000000000 --- a/src/linker/Linker/TypeParsing.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Reflection.Runtime.TypeParsing; -using System.Reflection.Runtime.Assemblies; -using Mono.Cecil; - -namespace Mono.Linker -{ - internal class ReflectionTypeNameResolver - { - private readonly LinkContext _context; - - public ReflectionTypeNameResolver (LinkContext context) - { - _context = context; - } - - public TypeDefinition ResolveTypeName (string typeNameString) - { - if (string.IsNullOrEmpty (typeNameString)) - return null; - - AssemblyQualifiedTypeName parsedTypeName; - try { - parsedTypeName = TypeParser.ParseAssemblyQualifiedTypeName (typeNameString); - } catch (ArgumentException) { - return null; - } catch (System.IO.FileLoadException) { - return null; - } - - return ResolveTypeName (parsedTypeName); - } - - private TypeDefinition ResolveTypeName (TypeName typeName) - { - if (typeName is AssemblyQualifiedTypeName) { - AssemblyQualifiedTypeName assemblyQualifiedTypeName = (AssemblyQualifiedTypeName) typeName; - RuntimeAssemblyName assemblyName = assemblyQualifiedTypeName.AssemblyName; - foreach (var assemblyDefinition in _context.GetAssemblies ()) { - if (assemblyName != null && assemblyDefinition.Name.Name != assemblyName.Name) - continue; - - var foundType = ResolveTypeName (assemblyDefinition, assemblyQualifiedTypeName.TypeName); - if (foundType == null) - continue; - - return foundType; - } - - return null; - } else if (typeName is NonQualifiedTypeName) { - return ResolveTypeName (null, (NonQualifiedTypeName) typeName); - } - - // This is unreachable - throw new NotImplementedException (); - } - - public TypeDefinition ResolveTypeName (AssemblyDefinition assembly, NonQualifiedTypeName typeName) - { - if (typeName is ConstructedGenericTypeName) { - ConstructedGenericTypeName genericTypeName = (ConstructedGenericTypeName) typeName; - return assembly.MainModule.GetType (genericTypeName.GenericType.ToString ()); - } else if (typeName is HasElementTypeName) { - HasElementTypeName elementTypeName = (HasElementTypeName) typeName; - TypeDefinition elementType = ResolveTypeName (assembly, elementTypeName.ElementTypeName as NonQualifiedTypeName); - if (elementType == null) - return null; - - return assembly.MainModule.GetType (elementType.ToString ()); - } - - return assembly.MainModule.GetType (typeName.ToString ()); - } - - } -} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs b/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs index ded113e92739..abdc02c8228d 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/TypeUsedViaReflection.cs @@ -22,6 +22,8 @@ public static void Main () TestReference (); TestArray (); TestArrayOfArray (); + TestGenericArray (); + TestGenericArrayFullString (); TestMultiDimensionalArray (); TestMultiDimensionalArrayFullString (); TestMultiDimensionalArrayAsmName (); @@ -72,6 +74,34 @@ public static void TestGenericString () var typeKept = Type.GetType (reflectionTypeKeptString, false); } + [Kept] + public class GenericArray { } + + [Kept] + public class GenericArgument { } + + [Kept] + public static void TestGenericArray () + { + const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericArray`1[[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericArgument]]"; + var typeKept = Type.GetType (reflectionTypeKeptString, false); + } + + [Kept] + public class GenericArrayFullString { } + + [Kept] + public class GenericArgumentFullString { } + + [Kept] + public static void TestGenericArrayFullString () + { + const string reflectionTypeKeptString = "Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericArrayFullString`1" + + "[[Mono.Linker.Tests.Cases.Reflection.TypeUsedViaReflection+GenericArgumentFullString, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]," + + " test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; + var typeKept = Type.GetType (reflectionTypeKeptString, false); + } + [Kept] public class FullConst { } diff --git a/test/Mono.Linker.Tests/Tests/TypeNameResolverTests.cs b/test/Mono.Linker.Tests/Tests/TypeNameResolverTests.cs new file mode 100644 index 000000000000..97d33b89cc77 --- /dev/null +++ b/test/Mono.Linker.Tests/Tests/TypeNameResolverTests.cs @@ -0,0 +1,86 @@ +using System.Reflection.Runtime.TypeParsing; +using NUnit.Framework; + +namespace Mono.Linker.Tests +{ + [TestFixture] + [Parallelizable] + public class TypeNameResolverTests + { + [Test] + public void TryParseAssemblyQualifiedTypeName_Null () + { + Assert.IsNull (TypeParser.ParseTypeName (null)); + } + + [Test] + public void TryParseAssemblyQualifiedTypeName_FullyQualified () + { + var value = typeof (TypeNameResolverTests).AssemblyQualifiedName; + var typeName = TypeParser.ParseTypeName (value); + Assert.IsTrue (typeName is AssemblyQualifiedTypeName); + AssemblyQualifiedTypeName assemblyQualifiedTypeName = (AssemblyQualifiedTypeName) typeName; + Assert.AreEqual (assemblyQualifiedTypeName.AssemblyName.FullName, typeof (TypeNameResolverTests).Assembly.FullName); + Assert.AreEqual (assemblyQualifiedTypeName.TypeName.ToString (), typeof (TypeNameResolverTests).FullName); + } + + [Test] + public void TryParseAssemblyQualifiedTypeName_NameAndAssemblyOnly () + { + var value = $"{typeof (TypeNameResolverTests).FullName}, {typeof (TypeNameResolverTests).Assembly.GetName ().Name}"; + var typeName = TypeParser.ParseTypeName (value); + Assert.IsTrue (typeName is AssemblyQualifiedTypeName); + AssemblyQualifiedTypeName assemblyQualifiedTypeName = (AssemblyQualifiedTypeName) typeName; + Assert.AreEqual (assemblyQualifiedTypeName.AssemblyName.Name, typeof (TypeNameResolverTests).Assembly.GetName ().Name); + Assert.AreEqual (assemblyQualifiedTypeName.TypeName.ToString (), typeof (TypeNameResolverTests).FullName); + } + + [Test] + public void TryParseAssemblyQualifiedTypeName_NameOnly () + { + var value = typeof (TypeNameResolverTests).FullName; + var typeName = TypeParser.ParseTypeName (value); + Assert.IsFalse (typeName is AssemblyQualifiedTypeName); + Assert.AreEqual (typeName.ToString (), value); + } + + [Test] + public void TryParseAssemblyQualifiedTypeName_GenericType_FullyQualified () + { + var value = typeof (GenericType<,>).AssemblyQualifiedName; + var typeName = TypeParser.ParseTypeName (value); + Assert.IsTrue (typeName is AssemblyQualifiedTypeName); + AssemblyQualifiedTypeName assemblyQualifiedTypeName = (AssemblyQualifiedTypeName) typeName; + Assert.AreEqual (assemblyQualifiedTypeName.AssemblyName.Name, typeof (TypeNameResolverTests).Assembly.GetName ().Name); + Assert.AreEqual (assemblyQualifiedTypeName.TypeName.ToString (), $"{typeof (TypeNameResolverTests).FullName}/GenericType`2"); + } + + [Test] + public void TryParseAssemblyQualifiedTypeName_GenericType_NameAndAssemblyOnly () + { + var value = $"{typeof (GenericType<,>).FullName}, {typeof (TypeNameResolverTests).Assembly.GetName ().Name}"; + var typeName = TypeParser.ParseTypeName (value); + Assert.IsTrue (typeName is AssemblyQualifiedTypeName); + AssemblyQualifiedTypeName assemblyQualifiedTypeName = (AssemblyQualifiedTypeName) typeName; + Assert.AreEqual (assemblyQualifiedTypeName.AssemblyName.Name, typeof (TypeNameResolverTests).Assembly.GetName ().Name); + Assert.AreEqual (assemblyQualifiedTypeName.TypeName.ToString (), $"{typeof (TypeNameResolverTests).FullName}/GenericType`2"); + } + + [Test] + public void TryParseAssemblyQualifiedTypeName_GenericType_NameOnly () + { + var value = typeof (GenericType<,>).FullName; + var typeName = TypeParser.ParseTypeName (value); + Assert.IsFalse (typeName is AssemblyQualifiedTypeName); + Assert.AreEqual (typeName.ToString (), $"{typeof (TypeNameResolverTests).FullName}/GenericType`2"); + } + + [Test] + public void MissingTypeName () + { + Assert.IsNull (TypeParser.ParseTypeName (", System")); + } + + class GenericType { } + } +} From b1ef30dd30ab0d7a6b79e9e61d2f782e45ea5e02 Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Thu, 10 Sep 2020 14:13:27 -0700 Subject: [PATCH 3/5] Fix mono build --- src/linker/Linker/AssemblyNameHelpers.cs | 2 +- src/linker/Linker/LinkContext.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linker/Linker/AssemblyNameHelpers.cs b/src/linker/Linker/AssemblyNameHelpers.cs index 1565a81da999..d211e4a483cb 100644 --- a/src/linker/Linker/AssemblyNameHelpers.cs +++ b/src/linker/Linker/AssemblyNameHelpers.cs @@ -103,7 +103,7 @@ private static void AppendQuoted (this StringBuilder sb, string s) continue; if ((s.Length - i) < escapeReplacement.Length) continue; - if (s.AsSpan (i, escapeReplacement.Length).SequenceEqual (escapeReplacement)) { + if (s.Substring (i, escapeReplacement.Length).Equals (escapeReplacement)) { sb.Append ('\\'); sb.Append (kv.Key); addedEscape = true; diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index 77ff231c0db8..013dcb7c8e92 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -456,7 +456,7 @@ public virtual AssemblyDefinition[] GetAssemblies () public AssemblyDefinition GetLoadedAssembly (string name) { - if (!string.IsNullOrEmpty(name) && _resolver.AssemblyCache.TryGetValue (name, out var ad)) + if (!string.IsNullOrEmpty (name) && _resolver.AssemblyCache.TryGetValue (name, out var ad)) return ad; return null; From 9514e51dba70e6b7026480647b577eea0f982dda Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Fri, 11 Sep 2020 14:21:39 -0700 Subject: [PATCH 4/5] Move corert code to external --- external/TypeParsing/README.md | 1 + .../Reflection/AssemblyNameFormatter.cs | 156 ++++++++++++ .../System/Reflection/AssemblyNameHelpers.cs | 42 ++++ .../System/Reflection}/AssemblyNameLexer.cs | 69 +++--- .../System/Reflection/AssemblyNameParser.cs | 216 +++++++++++++++++ .../Runtime/TypeParsing}/TypeLexer.cs | 122 +++++----- .../Runtime/TypeParsing}/TypeName.cs | 83 ++++--- .../Runtime/TypeParsing/TypeParser.cs | 225 ++++++++++++++++++ .../System/Reflection}/RuntimeAssemblyName.cs | 69 +++--- .../ReflectionMethodBodyScanner.cs | 13 +- src/linker/Linker/AssemblyNameHelpers.cs | 160 ------------- src/linker/Linker/AssemblyNameParser.cs | 204 ---------------- src/linker/Linker/TypeNameResolver.cs | 20 +- src/linker/Linker/TypeParser.cs | 188 --------------- src/linker/Mono.Linker.csproj | 1 + 15 files changed, 839 insertions(+), 730 deletions(-) create mode 100644 external/TypeParsing/README.md create mode 100644 external/TypeParsing/System/Reflection/AssemblyNameFormatter.cs create mode 100644 external/TypeParsing/System/Reflection/AssemblyNameHelpers.cs rename {src/linker/Linker => external/TypeParsing/System/Reflection}/AssemblyNameLexer.cs (56%) create mode 100644 external/TypeParsing/System/Reflection/AssemblyNameParser.cs rename {src/linker/Linker => external/TypeParsing/System/Reflection/Runtime/TypeParsing}/TypeLexer.cs (72%) rename {src/linker/Linker => external/TypeParsing/System/Reflection/Runtime/TypeParsing}/TypeName.cs (61%) create mode 100644 external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeParser.cs rename {src/linker/Linker => external/TypeParsing/System/Reflection}/RuntimeAssemblyName.cs (61%) delete mode 100644 src/linker/Linker/AssemblyNameHelpers.cs delete mode 100644 src/linker/Linker/AssemblyNameParser.cs delete mode 100644 src/linker/Linker/TypeParser.cs diff --git a/external/TypeParsing/README.md b/external/TypeParsing/README.md new file mode 100644 index 000000000000..babf7d8e26fa --- /dev/null +++ b/external/TypeParsing/README.md @@ -0,0 +1 @@ +The code in this folder was adapted from dotnet/corert commit c8bfca5f4554badfb89b80d2319769f83512bf62 \ No newline at end of file diff --git a/external/TypeParsing/System/Reflection/AssemblyNameFormatter.cs b/external/TypeParsing/System/Reflection/AssemblyNameFormatter.cs new file mode 100644 index 000000000000..4471c15bee06 --- /dev/null +++ b/external/TypeParsing/System/Reflection/AssemblyNameFormatter.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Text; +using System.Globalization; +using System.Collections.Generic; + +namespace System.Reflection +{ + internal static class AssemblyNameFormatter + { + public static string ComputeDisplayName(RuntimeAssemblyName a) + { + const int PUBLIC_KEY_TOKEN_LEN = 8; + + if (a.Name == string.Empty) + throw new FileLoadException(); + + StringBuilder sb = new StringBuilder(); + if (a.Name != null) + { + sb.AppendQuoted(a.Name); + } + + if (a.Version != null) + { + Version canonicalizedVersion = a.Version.CanonicalizeVersion(); + if (canonicalizedVersion.Major != ushort.MaxValue) + { + sb.Append(", Version="); + sb.Append(canonicalizedVersion.Major); + + if (canonicalizedVersion.Minor != ushort.MaxValue) + { + sb.Append('.'); + sb.Append(canonicalizedVersion.Minor); + + if (canonicalizedVersion.Build != ushort.MaxValue) + { + sb.Append('.'); + sb.Append(canonicalizedVersion.Build); + + if (canonicalizedVersion.Revision != ushort.MaxValue) + { + sb.Append('.'); + sb.Append(canonicalizedVersion.Revision); + } + } + } + } + } + + string cultureName = a.CultureName; + if (cultureName != null) + { + if (cultureName == string.Empty) + cultureName = "neutral"; + sb.Append(", Culture="); + sb.AppendQuoted(cultureName); + } + + byte[] pkt = a.PublicKeyOrToken; + if (pkt != null) + { + if (pkt.Length > PUBLIC_KEY_TOKEN_LEN) + throw new ArgumentException(); + + sb.Append(", PublicKeyToken="); + if (pkt.Length == 0) + sb.Append("null"); + else + { + foreach (byte b in pkt) + { + sb.Append(b.ToString("x2", CultureInfo.InvariantCulture)); + } + } + } + + if (0 != (a.Flags & AssemblyNameFlags.Retargetable)) + sb.Append(", Retargetable=Yes"); + + AssemblyContentType contentType = a.Flags.ExtractAssemblyContentType(); + if (contentType == AssemblyContentType.WindowsRuntime) + sb.Append(", ContentType=WindowsRuntime"); + + // NOTE: By design (desktop compat) AssemblyName.FullName and ToString() do not include ProcessorArchitecture. + + return sb.ToString(); + } + + private static void AppendQuoted(this StringBuilder sb, string s) + { + bool needsQuoting = false; + const char quoteChar = '\"'; + + // App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one + // by some algorithm. Rather than guess at it, we use double quotes consistently. + if (s != s.Trim() || s.Contains('\"') || s.Contains('\'')) + needsQuoting = true; + + if (needsQuoting) + sb.Append(quoteChar); + + for (int i = 0; i < s.Length; i++) + { + bool addedEscape = false; + foreach (KeyValuePair kv in EscapeSequences) + { + string escapeReplacement = kv.Value; + if (!(s[i] == escapeReplacement[0])) + continue; + if ((s.Length - i) < escapeReplacement.Length) + continue; + if (s.Substring(i, escapeReplacement.Length).Equals(escapeReplacement)) + { + sb.Append('\\'); + sb.Append(kv.Key); + addedEscape = true; + } + } + + if (!addedEscape) + sb.Append(s[i]); + } + + if (needsQuoting) + sb.Append(quoteChar); + } + + private static Version CanonicalizeVersion(this Version version) + { + ushort major = (ushort)version.Major; + ushort minor = (ushort)version.Minor; + ushort build = (ushort)version.Build; + ushort revision = (ushort)version.Revision; + + if (major == version.Major && minor == version.Minor && build == version.Build && revision == version.Revision) + return version; + + return new Version(major, minor, build, revision); + } + + public static KeyValuePair[] EscapeSequences = + { + new KeyValuePair('\\', "\\"), + new KeyValuePair(',', ","), + new KeyValuePair('=', "="), + new KeyValuePair('\'', "'"), + new KeyValuePair('\"', "\""), + new KeyValuePair('n', Environment.NewLine), + new KeyValuePair('t', "\t"), + }; + } +} \ No newline at end of file diff --git a/external/TypeParsing/System/Reflection/AssemblyNameHelpers.cs b/external/TypeParsing/System/Reflection/AssemblyNameHelpers.cs new file mode 100644 index 000000000000..9b9c1a702b29 --- /dev/null +++ b/external/TypeParsing/System/Reflection/AssemblyNameHelpers.cs @@ -0,0 +1,42 @@ +// 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.Globalization; +using System.IO; +using System.Text; +using System.Collections.Generic; + +namespace System.Reflection +{ + public static partial class AssemblyNameHelpers + { + + + // + // These helpers convert between the combined flags+contentType+processorArchitecture value and the separated parts. + // + // Since these are only for trusted callers, they do NOT check for out of bound bits. + // + + internal static AssemblyContentType ExtractAssemblyContentType(this AssemblyNameFlags flags) + { + return (AssemblyContentType)((((int)flags) >> 9) & 0x7); + } + + internal static ProcessorArchitecture ExtractProcessorArchitecture(this AssemblyNameFlags flags) + { + return (ProcessorArchitecture)((((int)flags) >> 4) & 0x7); + } + + public static AssemblyNameFlags ExtractAssemblyNameFlags(this AssemblyNameFlags combinedFlags) + { + return combinedFlags & unchecked((AssemblyNameFlags)0xFFFFF10F); + } + + internal static AssemblyNameFlags CombineAssemblyNameFlags(AssemblyNameFlags flags, AssemblyContentType contentType, ProcessorArchitecture processorArchitecture) + { + return (AssemblyNameFlags)(((int)flags) | (((int)contentType) << 9) | ((int)processorArchitecture << 4)); + } + } +} \ No newline at end of file diff --git a/src/linker/Linker/AssemblyNameLexer.cs b/external/TypeParsing/System/Reflection/AssemblyNameLexer.cs similarity index 56% rename from src/linker/Linker/AssemblyNameLexer.cs rename to external/TypeParsing/System/Reflection/AssemblyNameLexer.cs index 37d3b0dc3062..5ef3d595ab48 100644 --- a/src/linker/Linker/AssemblyNameLexer.cs +++ b/external/TypeParsing/System/Reflection/AssemblyNameLexer.cs @@ -1,24 +1,23 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.IO; using System.Text; using System.Collections.Generic; -namespace System.Reflection.Runtime.Assemblies +namespace System.Reflection { // // A simple lexer for assembly display names. // internal struct AssemblyNameLexer { - internal AssemblyNameLexer (string s) + internal AssemblyNameLexer(string s) { // Convert string to char[] with NUL terminator. (An actual NUL terminator in the input string will be treated // as an actual end of string: this is compatible with desktop behavior.) char[] chars = new char[s.Length + 1]; - s.CopyTo (0, chars, 0, s.Length); + s.CopyTo(0, chars, 0, s.Length); _chars = chars; _index = 0; } @@ -27,20 +26,20 @@ internal AssemblyNameLexer (string s) // Return the next token in assembly name. If you expect the result to be DisplayNameToken.String, // use GetNext(out String) instead. // - internal Token GetNext () + internal Token GetNext() { string ignore; - return GetNext (out ignore); + return GetNext(out ignore); } // // Return the next token in assembly name. If the result is DisplayNameToken.String, // sets "tokenString" to the tokenized string. // - internal Token GetNext (out string tokenString) + internal Token GetNext(out string tokenString) { tokenString = null; - while (char.IsWhiteSpace (_chars[_index])) + while (char.IsWhiteSpace(_chars[_index])) _index++; char c = _chars[_index++]; @@ -51,16 +50,19 @@ internal Token GetNext (out string tokenString) if (c == '=') return Token.Equals; - StringBuilder sb = new StringBuilder (); - char quoteChar = (char) 0; - if (c == '\'' || c == '\"') { + StringBuilder sb = new StringBuilder(); + + char quoteChar = (char)0; + if (c == '\'' || c == '\"') + { quoteChar = c; c = _chars[_index++]; } for (; ; ) { - if (c == 0) { + if (c == 0) + { _index--; break; // Terminate: End of string (desktop compat: if string was quoted, permitted to terminate without end-quote.) } @@ -68,51 +70,45 @@ internal Token GetNext (out string tokenString) if (quoteChar != 0 && c == quoteChar) break; // Terminate: Found closing quote of quoted string. - if (quoteChar == 0 && (c == ',' || c == '=')) { + if (quoteChar == 0 && (c == ',' || c == '=')) + { _index--; break; // Terminate: Found start of a new ',' or '=' token. } if (quoteChar == 0 && (c == '\'' || c == '\"')) - throw new FileLoadException (); // Desktop compat: Unescaped quote illegal unless entire string is quoted. + throw new FileLoadException(); // Desktop compat: Unescaped quote illegal unless entire string is quoted. - if (c == '\\') { + if (c == '\\') + { c = _chars[_index++]; bool matched = false; - foreach (KeyValuePair kv in EscapeSequences) { - if (c == kv.Key) { + foreach (KeyValuePair kv in AssemblyNameFormatter.EscapeSequences) + { + if (c == kv.Key) + { matched = true; - sb.Append (kv.Value); + sb.Append(kv.Value); break; } } if (!matched) - throw new FileLoadException (); // Unrecognized escape - } else { - sb.Append (c); + throw new FileLoadException(); // Unrecognized escape + } + else + { + sb.Append(c); } c = _chars[_index++]; } - tokenString = sb.ToString (); + tokenString = sb.ToString(); if (quoteChar == 0) - tokenString = tokenString.Trim (); // Unless quoted, whitespace at beginning or end doesn't count. + tokenString = tokenString.Trim(); // Unless quoted, whitespace at beginning or end doesn't count. return Token.String; - } - internal static KeyValuePair[] EscapeSequences = - { - new KeyValuePair('\\', "\\"), - new KeyValuePair(',', ","), - new KeyValuePair('=', "="), - new KeyValuePair('\'', "'"), - new KeyValuePair('\"', "\""), - new KeyValuePair('n', Environment.NewLine), - new KeyValuePair('t', "\t"), - }; - // Token categories for display name lexer. internal enum Token { @@ -125,5 +121,4 @@ internal enum Token private readonly char[] _chars; private int _index; } - } \ No newline at end of file diff --git a/external/TypeParsing/System/Reflection/AssemblyNameParser.cs b/external/TypeParsing/System/Reflection/AssemblyNameParser.cs new file mode 100644 index 000000000000..fdf5b8f064ab --- /dev/null +++ b/external/TypeParsing/System/Reflection/AssemblyNameParser.cs @@ -0,0 +1,216 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Diagnostics; +using System.Globalization; +using System.Collections.Generic; + +namespace System.Reflection +{ + // + // Parses an assembly name. + // + internal static class AssemblyNameParser + { + internal static RuntimeAssemblyName Parse(string s) + { + Debug.Assert(s != null); + + int indexOfNul = s.IndexOf((char)0); + if (indexOfNul != -1) + s = s.Substring(0, indexOfNul); + if (s.Length == 0) + throw new ArgumentException(); + + AssemblyNameLexer lexer = new AssemblyNameLexer(s); + + // Name must come first. + string name; + AssemblyNameLexer.Token token = lexer.GetNext(out name); + if (token != AssemblyNameLexer.Token.String) + throw new FileLoadException(); + + if (name == string.Empty || name.IndexOfAny(s_illegalCharactersInSimpleName) != -1) + throw new FileLoadException(); + + Version version = null; + string cultureName = null; + byte[] pkt = null; + AssemblyNameFlags flags = 0; + + List alreadySeen = new List(); + token = lexer.GetNext(); + while (token != AssemblyNameLexer.Token.End) + { + if (token != AssemblyNameLexer.Token.Comma) + throw new FileLoadException(); + string attributeName; + token = lexer.GetNext(out attributeName); + if (token != AssemblyNameLexer.Token.String) + throw new FileLoadException(); + token = lexer.GetNext(); + + // Compat note: Inside AppX apps, the desktop CLR's AssemblyName parser skips past any elements that don't follow the "=" pattern. + // (when running classic Windows apps, such an illegal construction throws an exception as expected.) + // Naturally, at least one app unwittingly takes advantage of this. + if (token == AssemblyNameLexer.Token.Comma || token == AssemblyNameLexer.Token.End) + continue; + + if (token != AssemblyNameLexer.Token.Equals) + throw new FileLoadException(); + string attributeValue; + token = lexer.GetNext(out attributeValue); + if (token != AssemblyNameLexer.Token.String) + throw new FileLoadException(); + + if (attributeName == string.Empty) + throw new FileLoadException(); + + for (int i = 0; i < alreadySeen.Count; i++) + { + if (alreadySeen[i].Equals(attributeName, StringComparison.OrdinalIgnoreCase)) + throw new FileLoadException(); // Cannot specify the same attribute twice. + } + alreadySeen.Add(attributeName); + if (attributeName.Equals("Version", StringComparison.OrdinalIgnoreCase)) + { + version = ParseVersion(attributeValue); + } + + if (attributeName.Equals("Culture", StringComparison.OrdinalIgnoreCase)) + { + cultureName = ParseCulture(attributeValue); + } + + if (attributeName.Equals("PublicKeyToken", StringComparison.OrdinalIgnoreCase)) + { + pkt = ParsePKT(attributeValue); + } + + if (attributeName.Equals("ProcessorArchitecture", StringComparison.OrdinalIgnoreCase)) + { + flags |= (AssemblyNameFlags)(((int)ParseProcessorArchitecture(attributeValue)) << 4); + } + + if (attributeName.Equals("Retargetable", StringComparison.OrdinalIgnoreCase)) + { + if (attributeValue.Equals("Yes", StringComparison.OrdinalIgnoreCase)) + flags |= AssemblyNameFlags.Retargetable; + else if (attributeValue.Equals("No", StringComparison.OrdinalIgnoreCase)) + { + // nothing to do + } + else + throw new FileLoadException(); + } + + if (attributeName.Equals("ContentType", StringComparison.OrdinalIgnoreCase)) + { + if (attributeValue.Equals("WindowsRuntime", StringComparison.OrdinalIgnoreCase)) + flags |= (AssemblyNameFlags)(((int)AssemblyContentType.WindowsRuntime) << 9); + else + throw new FileLoadException(); + } + + // Desktop compat: If we got here, the attribute name is unknown to us. Ignore it (as long it's not duplicated.) + token = lexer.GetNext(); + } + return new RuntimeAssemblyName(name, version, cultureName, flags, pkt); + } + + private static Version ParseVersion(string attributeValue) + { + string[] parts = attributeValue.Split('.'); + if (parts.Length > 4) + throw new FileLoadException(); + ushort[] versionNumbers = new ushort[4]; + for (int i = 0; i < versionNumbers.Length; i++) + { + if (i >= parts.Length) + versionNumbers[i] = ushort.MaxValue; + else + { + // Desktop compat: TryParse is a little more forgiving than Fusion. + for (int j = 0; j < parts[i].Length; j++) + { + if (!char.IsDigit(parts[i][j])) + throw new FileLoadException(); + } + if (!(ushort.TryParse(parts[i], out versionNumbers[i]))) + { + throw new FileLoadException(); + } + } + } + + if (versionNumbers[0] == ushort.MaxValue || versionNumbers[1] == ushort.MaxValue) + throw new FileLoadException(); + if (versionNumbers[2] == ushort.MaxValue) + return new Version(versionNumbers[0], versionNumbers[1]); + if (versionNumbers[3] == ushort.MaxValue) + return new Version(versionNumbers[0], versionNumbers[1], versionNumbers[2]); + return new Version(versionNumbers[0], versionNumbers[1], versionNumbers[2], versionNumbers[3]); + } + + private static string ParseCulture(string attributeValue) + { + if (attributeValue.Equals("Neutral", StringComparison.OrdinalIgnoreCase)) + { + return ""; + } + else + { + CultureInfo culture = CultureInfo.GetCultureInfo(attributeValue); // Force a CultureNotFoundException if not a valid culture. + return culture.Name; + } + } + + private static byte[] ParsePKT(string attributeValue) + { + if (attributeValue.Equals("null", StringComparison.OrdinalIgnoreCase) || attributeValue == string.Empty) + return Array.Empty(); + + if (attributeValue.Length != 8 * 2) + throw new FileLoadException(); + + byte[] pkt = new byte[8]; + int srcIndex = 0; + for (int i = 0; i < 8; i++) + { + char hi = attributeValue[srcIndex++]; + char lo = attributeValue[srcIndex++]; + pkt[i] = (byte)((ParseHexNybble(hi) << 4) | ParseHexNybble(lo)); + } + return pkt; + } + + private static ProcessorArchitecture ParseProcessorArchitecture(string attributeValue) + { + if (attributeValue.Equals("msil", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.MSIL; + if (attributeValue.Equals("x86", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.X86; + if (attributeValue.Equals("ia64", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.IA64; + if (attributeValue.Equals("amd64", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.Amd64; + if (attributeValue.Equals("arm", StringComparison.OrdinalIgnoreCase)) + return ProcessorArchitecture.Arm; + throw new FileLoadException(); + } + + private static byte ParseHexNybble(char c) + { + if (c >= '0' && c <= '9') + return (byte)(c - '0'); + if (c >= 'a' && c <= 'f') + return (byte)(c - 'a' + 10); + if (c >= 'A' && c <= 'F') + return (byte)(c - 'A' + 10); + throw new FileLoadException(); + } + + private static readonly char[] s_illegalCharactersInSimpleName = { '/', '\\', ':' }; + } +} \ No newline at end of file diff --git a/src/linker/Linker/TypeLexer.cs b/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeLexer.cs similarity index 72% rename from src/linker/Linker/TypeLexer.cs rename to external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeLexer.cs index 31e70d9e91a1..4a2429721dce 100644 --- a/src/linker/Linker/TypeLexer.cs +++ b/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeLexer.cs @@ -1,10 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Diagnostics; -using System.Reflection.Runtime.Assemblies; - namespace System.Reflection.Runtime.TypeParsing { @@ -13,50 +10,54 @@ namespace System.Reflection.Runtime.TypeParsing // internal sealed class TypeLexer { - public TypeLexer (string s) + public TypeLexer(String s) { // Turn the string into a char array with a NUL terminator. char[] chars = new char[s.Length + 1]; - s.CopyTo (0, chars, 0, s.Length); + s.CopyTo(0, chars, 0, s.Length); _chars = chars; _index = 0; } - public TokenType Peek { - get { - SkipWhiteSpace (); + public TokenType Peek + { + get + { + SkipWhiteSpace(); char c = _chars[_index]; - return CharToToken (c); + return CharToToken(c); } } - public TokenType PeekSecond { - get { - SkipWhiteSpace (); + public TokenType PeekSecond + { + get + { + SkipWhiteSpace(); int index = _index + 1; - while (Char.IsWhiteSpace (_chars[index])) + while (Char.IsWhiteSpace(_chars[index])) index++; char c = _chars[index]; - return CharToToken (c); + return CharToToken(c); } } - public void Skip () + public void Skip() { - Debug.Assert (_index != _chars.Length); - SkipWhiteSpace (); + Debug.Assert(_index != _chars.Length); + SkipWhiteSpace(); _index++; } // Return the next token and skip index past it unless already at end of string // or the token is not a reserved token. - public TokenType GetNextToken () + public TokenType GetNextToken() { TokenType tokenType = Peek; if (tokenType == TokenType.End || tokenType == TokenType.Other) return tokenType; - Skip (); + Skip(); return tokenType; } @@ -67,9 +68,9 @@ public TokenType GetNextToken () // // Terminated by the first non-escaped reserved character ('[', ']', '+', '&', '*' or ',') // - public string GetNextIdentifier () + public String GetNextIdentifier() { - SkipWhiteSpace (); + SkipWhiteSpace(); int src = _index; char[] buffer = new char[_chars.Length]; @@ -77,15 +78,17 @@ public string GetNextIdentifier () for (; ; ) { char c = _chars[src]; - TokenType token = CharToToken (c); + TokenType token = CharToToken(c); if (token != TokenType.Other) break; src++; - if (c == '\\') { + if (c == '\\') + { c = _chars[src]; if (c != NUL) src++; - if (c == NUL || CharToToken (c) == TokenType.Other) { + if (c == NUL || CharToToken(c) == TokenType.Other) + { // If we got here, a backslash was used to escape a character that is not legal to escape inside a type name. // // Common sense would dictate throwing an ArgumentException but that's not what the desktop CLR does. @@ -97,15 +100,14 @@ public string GetNextIdentifier () // // To emulate this accidental behavior, we'll throw a special exception that's caught by the TypeParser. // - throw new IllegalEscapeSequenceException (); + throw new IllegalEscapeSequenceException(); } - } buffer[dst++] = c; } _index = src; - return new string (buffer, 0, dst); + return new String(buffer, 0, dst); } // @@ -115,9 +117,9 @@ public string GetNextIdentifier () // Terminated by NUL. There are no escape characters defined by the typename lexer (however, AssemblyName // does have its own escape rules.) // - public RuntimeAssemblyName GetNextAssemblyName () + public RuntimeAssemblyName GetNextAssemblyName() { - SkipWhiteSpace (); + SkipWhiteSpace(); int src = _index; char[] buffer = new char[_chars.Length]; @@ -131,8 +133,8 @@ public RuntimeAssemblyName GetNextAssemblyName () buffer[dst++] = c; } _index = src; - string fullName = new string (buffer, 0, dst); - return AssemblyNameParser.Parse (fullName); + String fullName = new String(buffer, 0, dst); + return AssemblyNameParser.Parse(fullName); } // @@ -140,9 +142,9 @@ public RuntimeAssemblyName GetNextAssemblyName () // // Terminated by an unescaped ']'. // - public RuntimeAssemblyName GetNextEmbeddedAssemblyName () + public RuntimeAssemblyName GetNextEmbeddedAssemblyName() { - SkipWhiteSpace (); + SkipWhiteSpace(); int src = _index; char[] buffer = new char[_chars.Length]; @@ -151,45 +153,47 @@ public RuntimeAssemblyName GetNextEmbeddedAssemblyName () { char c = _chars[src]; if (c == NUL) - throw new ArgumentException (); + throw new ArgumentException(); if (c == ']') break; src++; // Backslash can be used to escape a ']' - any other backslash character is left alone (along with the backslash) // for the AssemblyName parser to handle. - if (c == '\\' && _chars[src] == ']') { + if (c == '\\' && _chars[src] == ']') + { c = _chars[src++]; } buffer[dst++] = c; } _index = src; - string fullName = new string (buffer, 0, dst); - return AssemblyNameParser.Parse (fullName); + String fullName = new String(buffer, 0, dst); + return AssemblyNameParser.Parse(fullName); } // // Classify a character as a TokenType. (Fortunately, all tokens in typename strings other than identifiers are single-character tokens.) // - private static TokenType CharToToken (char c) + private static TokenType CharToToken(char c) { - switch (c) { - case NUL: - return TokenType.End; - case '[': - return TokenType.OpenSqBracket; - case ']': - return TokenType.CloseSqBracket; - case ',': - return TokenType.Comma; - case '+': - return TokenType.Plus; - case '*': - return TokenType.Asterisk; - case '&': - return TokenType.Ampersand; - default: - return TokenType.Other; + switch (c) + { + case NUL: + return TokenType.End; + case '[': + return TokenType.OpenSqBracket; + case ']': + return TokenType.CloseSqBracket; + case ',': + return TokenType.Comma; + case '+': + return TokenType.Plus; + case '*': + return TokenType.Asterisk; + case '&': + return TokenType.Ampersand; + default: + return TokenType.Other; } } @@ -203,16 +207,16 @@ private static TokenType CharToToken (char c) // Whitespace between the end of an assembly name and the punction mark that ends it is also not ignored by this parser, // but this is irrelevant since the assembly name is then turned over to AssemblyName for parsing, which *does* ignore trailing whitespace. // - private void SkipWhiteSpace () + private void SkipWhiteSpace() { - while (Char.IsWhiteSpace (_chars[_index])) + while (Char.IsWhiteSpace(_chars[_index])) _index++; } private int _index; private readonly char[] _chars; - private const char NUL = (char) 0; + private const char NUL = (char)0; public sealed class IllegalEscapeSequenceException : Exception diff --git a/src/linker/Linker/TypeName.cs b/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeName.cs similarity index 61% rename from src/linker/Linker/TypeName.cs rename to external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeName.cs index 461f2fe4faec..81403cc4fc43 100644 --- a/src/linker/Linker/TypeName.cs +++ b/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeName.cs @@ -1,10 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Diagnostics; using System.Collections.Generic; -using System.Reflection.Runtime.Assemblies; namespace System.Reflection.Runtime.TypeParsing { @@ -14,30 +12,29 @@ namespace System.Reflection.Runtime.TypeParsing // public abstract class TypeName { - public abstract override string ToString (); + public abstract override string ToString(); } - // // Represents a parse of a type name optionally qualified by an assembly name. If present, the assembly name follows // a comma following the type name. // public sealed class AssemblyQualifiedTypeName : TypeName { - public AssemblyQualifiedTypeName (NonQualifiedTypeName typeName, RuntimeAssemblyName assemblyName) + public AssemblyQualifiedTypeName(NonQualifiedTypeName typeName, RuntimeAssemblyName assemblyName) { - Debug.Assert (typeName != null); + Debug.Assert(typeName != null); TypeName = typeName; AssemblyName = assemblyName; } - public NonQualifiedTypeName TypeName { get; private set; } - public RuntimeAssemblyName AssemblyName { get; private set; } - - public sealed override string ToString () + public sealed override string ToString() { - return TypeName.ToString () + ((AssemblyName == null) ? "" : ", " + AssemblyName.FullName); + return TypeName.ToString() + ((AssemblyName == null) ? "" : ", " + AssemblyName.FullName); } + + public RuntimeAssemblyName AssemblyName { get; } + public NonQualifiedTypeName TypeName { get; } } // @@ -60,19 +57,20 @@ internal abstract class NamedTypeName : NonQualifiedTypeName // internal sealed partial class NamespaceTypeName : NamedTypeName { - public NamespaceTypeName (string[] namespaceParts, string name) + public NamespaceTypeName(string[] namespaceParts, string name) { - Debug.Assert (namespaceParts != null); - Debug.Assert (name != null); + Debug.Assert(namespaceParts != null); + Debug.Assert(name != null); _name = name; _namespaceParts = namespaceParts; } - public sealed override string ToString () + public sealed override string ToString() { string fullName = ""; - for (int i = 0; i < _namespaceParts.Length; i++) { + for (int i = 0; i < _namespaceParts.Length; i++) + { fullName += _namespaceParts[_namespaceParts.Length - i - 1]; fullName += "."; } @@ -89,7 +87,7 @@ public sealed override string ToString () // internal sealed class NestedTypeName : NamedTypeName { - public NestedTypeName (string name, NamedTypeName declaringType) + public NestedTypeName(string name, NamedTypeName declaringType) { Name = name; DeclaringType = declaringType; @@ -98,7 +96,7 @@ public NestedTypeName (string name, NamedTypeName declaringType) public string Name { get; private set; } public NamedTypeName DeclaringType { get; private set; } - public sealed override string ToString () + public sealed override string ToString() { // Cecil's format uses '/' instead of '+' for nested types. return DeclaringType + "/" + Name; @@ -110,12 +108,12 @@ public sealed override string ToString () // internal abstract class HasElementTypeName : NonQualifiedTypeName { - public HasElementTypeName (TypeName elementTypeName) + public HasElementTypeName(TypeName elementTypeName) { ElementTypeName = elementTypeName; } - public TypeName ElementTypeName { get; private set; } + public TypeName ElementTypeName { get; } } // @@ -123,12 +121,12 @@ public HasElementTypeName (TypeName elementTypeName) // internal sealed class ArrayTypeName : HasElementTypeName { - public ArrayTypeName (TypeName elementTypeName) - : base (elementTypeName) + public ArrayTypeName(TypeName elementTypeName) + : base(elementTypeName) { } - public sealed override string ToString () + public sealed override string ToString() { return ElementTypeName + "[]"; } @@ -139,15 +137,15 @@ public sealed override string ToString () // internal sealed class MultiDimArrayTypeName : HasElementTypeName { - public MultiDimArrayTypeName (TypeName elementTypeName, int rank) - : base (elementTypeName) + public MultiDimArrayTypeName(TypeName elementTypeName, int rank) + : base(elementTypeName) { _rank = rank; } - public sealed override string ToString () + public sealed override string ToString() { - return ElementTypeName + "[" + (_rank == 1 ? "*" : new string (',', _rank - 1)) + "]"; + return ElementTypeName + "[" + (_rank == 1 ? "*" : new string(',', _rank - 1)) + "]"; } private int _rank; @@ -158,12 +156,12 @@ public sealed override string ToString () // internal sealed class ByRefTypeName : HasElementTypeName { - public ByRefTypeName (TypeName elementTypeName) - : base (elementTypeName) + public ByRefTypeName(TypeName elementTypeName) + : base(elementTypeName) { } - public sealed override string ToString () + public sealed override string ToString() { return ElementTypeName + "&"; } @@ -174,12 +172,12 @@ public sealed override string ToString () // internal sealed class PointerTypeName : HasElementTypeName { - public PointerTypeName (TypeName elementTypeName) - : base (elementTypeName) + public PointerTypeName(TypeName elementTypeName) + : base(elementTypeName) { } - public sealed override string ToString () + public sealed override string ToString() { return ElementTypeName + "*"; } @@ -190,28 +188,29 @@ public sealed override string ToString () // internal sealed class ConstructedGenericTypeName : NonQualifiedTypeName { - public ConstructedGenericTypeName (NamedTypeName genericType, IEnumerable genericArguments) + public ConstructedGenericTypeName(NamedTypeName genericType, IEnumerable genericArguments) { GenericType = genericType; GenericArguments = genericArguments; } - public NamedTypeName GenericType { get; private set; } - public IEnumerable GenericArguments { get; private set; } + public NamedTypeName GenericType { get; } + public IEnumerable GenericArguments { get; } - public sealed override string ToString () + public sealed override string ToString() { - string s = GenericType.ToString (); + string s = GenericType.ToString(); s += "["; string sep = ""; - foreach (TypeName genericTypeArgument in GenericArguments) { + foreach (TypeName genericTypeArgument in GenericArguments) + { s += sep; sep = ","; AssemblyQualifiedTypeName assemblyQualifiedTypeArgument = genericTypeArgument as AssemblyQualifiedTypeName; if (assemblyQualifiedTypeArgument == null || assemblyQualifiedTypeArgument.AssemblyName == null) - s += genericTypeArgument.ToString (); + s += genericTypeArgument.ToString(); else - s += "[" + genericTypeArgument.ToString () + "]"; + s += "[" + genericTypeArgument.ToString() + "]"; } s += "]"; return s; diff --git a/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeParser.cs b/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeParser.cs new file mode 100644 index 000000000000..c06cf7667ab0 --- /dev/null +++ b/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeParser.cs @@ -0,0 +1,225 @@ +// 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; + +namespace System.Reflection.Runtime.TypeParsing +{ + // + // Parser for type names passed to GetType() apis. + // + public sealed class TypeParser + { + // + // Parses a typename. The typename may be optionally postpended with a "," followed by a legal assembly name. + // + public static TypeName ParseTypeName(string s) + { + try + { + return ParseAssemblyQualifiedTypeName(s); + } + catch (ArgumentException) + { + return null; + } + } + + // + // Parses a typename. The typename may be optionally postpended with a "," followed by a legal assembly name. + // + private static TypeName ParseAssemblyQualifiedTypeName(String s) + { + if (string.IsNullOrEmpty(s)) + return null; + + // Desktop compat: a whitespace-only "typename" qualified by an assembly name throws an ArgumentException rather than + // a TypeLoadException. + int idx = 0; + while (idx < s.Length && Char.IsWhiteSpace(s[idx])) + { + idx++; + } + if (idx < s.Length && s[idx] == ',') + throw new ArgumentException(); + + try + { + TypeParser parser = new TypeParser(s); + NonQualifiedTypeName typeName = parser.ParseNonQualifiedTypeName(); + TokenType token = parser._lexer.GetNextToken(); + if (token == TokenType.End) + return typeName; + if (token == TokenType.Comma) + { + RuntimeAssemblyName assemblyName = parser._lexer.GetNextAssemblyName(); + token = parser._lexer.Peek; + if (token != TokenType.End) + throw new ArgumentException(); + return new AssemblyQualifiedTypeName(typeName, assemblyName); + } + throw new ArgumentException(); + } + catch (TypeLexer.IllegalEscapeSequenceException) + { + // Emulates a CLR4.5 bug that causes any string that contains an illegal escape sequence to be parsed as the empty string. + return ParseAssemblyQualifiedTypeName(String.Empty); + } + } + + private TypeParser(String s) + { + _lexer = new TypeLexer(s); + } + + + // + // Parses a type name without any assembly name qualification. + // + private NonQualifiedTypeName ParseNonQualifiedTypeName() + { + // Parse the named type or constructed generic type part first. + NonQualifiedTypeName typeName = ParseNamedOrConstructedGenericTypeName(); + + // Iterate through any "has-element" qualifiers ([], &, *). + for (;;) + { + TokenType token = _lexer.Peek; + if (token == TokenType.End) + break; + if (token == TokenType.Asterisk) + { + _lexer.Skip(); + typeName = new PointerTypeName(typeName); + } + else if (token == TokenType.Ampersand) + { + _lexer.Skip(); + typeName = new ByRefTypeName(typeName); + } + else if (token == TokenType.OpenSqBracket) + { + _lexer.Skip(); + token = _lexer.GetNextToken(); + if (token == TokenType.Asterisk) + { + typeName = new MultiDimArrayTypeName(typeName, 1); + token = _lexer.GetNextToken(); + } + else + { + int rank = 1; + while (token == TokenType.Comma) + { + token = _lexer.GetNextToken(); + rank++; + } + if (rank == 1) + typeName = new ArrayTypeName(typeName); + else + typeName = new MultiDimArrayTypeName(typeName, rank); + } + if (token != TokenType.CloseSqBracket) + throw new ArgumentException(); + } + else + { + break; + } + } + return typeName; + } + + // + // Foo or Foo+Inner or Foo[String] or Foo+Inner[String] + // + private NonQualifiedTypeName ParseNamedOrConstructedGenericTypeName() + { + NamedTypeName namedType = ParseNamedTypeName(); + // Because "[" is used both for generic arguments and array indexes, we must peek two characters deep. + if (!(_lexer.Peek == TokenType.OpenSqBracket && (_lexer.PeekSecond == TokenType.Other || _lexer.PeekSecond == TokenType.OpenSqBracket))) + return namedType; + else + { + _lexer.Skip(); + List genericTypeArguments = new List(); + for (;;) + { + TypeName genericTypeArgument = ParseGenericTypeArgument(); + genericTypeArguments.Add(genericTypeArgument); + TokenType token = _lexer.GetNextToken(); + if (token == TokenType.CloseSqBracket) + break; + if (token != TokenType.Comma) + throw new ArgumentException(); + } + + return new ConstructedGenericTypeName(namedType, genericTypeArguments); + } + } + + // + // Foo or Foo+Inner + // + private NamedTypeName ParseNamedTypeName() + { + NamedTypeName namedType = ParseNamespaceTypeName(); + while (_lexer.Peek == TokenType.Plus) + { + _lexer.Skip(); + String nestedTypeName = _lexer.GetNextIdentifier(); + namedType = new NestedTypeName(nestedTypeName, namedType); + } + return namedType; + } + + // + // Non-nested named type. + // + private NamespaceTypeName ParseNamespaceTypeName() + { + string fullName = _lexer.GetNextIdentifier(); + string[] parts = fullName.Split('.'); + int numNamespaceParts = parts.Length - 1; + string[] namespaceParts = new string[numNamespaceParts]; + for (int i = 0; i < numNamespaceParts; i++) + namespaceParts[numNamespaceParts - i - 1] = parts[i]; + string name = parts[numNamespaceParts]; + return new NamespaceTypeName(namespaceParts, name); + } + + // + // Parse a generic argument. In particular, generic arguments can take the special form [,]. + // + private TypeName ParseGenericTypeArgument() + { + TokenType token = _lexer.GetNextToken(); + if (token == TokenType.Other) + { + NonQualifiedTypeName nonQualifiedTypeName = ParseNonQualifiedTypeName(); + return nonQualifiedTypeName; + } + else if (token == TokenType.OpenSqBracket) + { + RuntimeAssemblyName assemblyName = null; + NonQualifiedTypeName typeName = ParseNonQualifiedTypeName(); + token = _lexer.GetNextToken(); + if (token == TokenType.Comma) + { + assemblyName = _lexer.GetNextEmbeddedAssemblyName(); + token = _lexer.GetNextToken(); + } + if (token != TokenType.CloseSqBracket) + throw new ArgumentException(); + if (assemblyName == null) + return typeName; + else + return new AssemblyQualifiedTypeName(typeName, assemblyName); + } + else + throw new ArgumentException(); + } + + private readonly TypeLexer _lexer; + } +} \ No newline at end of file diff --git a/src/linker/Linker/RuntimeAssemblyName.cs b/external/TypeParsing/System/Reflection/RuntimeAssemblyName.cs similarity index 61% rename from src/linker/Linker/RuntimeAssemblyName.cs rename to external/TypeParsing/System/Reflection/RuntimeAssemblyName.cs index a7444f155064..561e9382f379 100644 --- a/src/linker/Linker/RuntimeAssemblyName.cs +++ b/external/TypeParsing/System/Reflection/RuntimeAssemblyName.cs @@ -1,10 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. using System.Diagnostics; -namespace System.Reflection.Runtime.Assemblies +namespace System.Reflection { // // This is a private assembly name abstraction that's more suitable for use as keys in our caches. @@ -16,9 +15,9 @@ namespace System.Reflection.Runtime.Assemblies // public sealed class RuntimeAssemblyName : IEquatable { - public RuntimeAssemblyName (string name, Version version, string cultureName, AssemblyNameFlags flags, byte[] publicKeyOrToken) + public RuntimeAssemblyName(string name, Version version, string cultureName, AssemblyNameFlags flags, byte[] publicKeyOrToken) { - Debug.Assert (name != null); + Debug.Assert(name != null); this.Name = name; // Optional version. @@ -35,52 +34,63 @@ public RuntimeAssemblyName (string name, Version version, string cultureName, As } // Simple name. - public string Name { get; private set; } + public string Name { get; } // Optional version. - public Version Version { get; private set; } + public Version Version { get; } // Optional culture name. - public string CultureName { get; private set; } + public string CultureName { get; } // Optional flags (this is actually an OR of the classic flags and the ContentType.) - public AssemblyNameFlags Flags { get; private set; } + public AssemblyNameFlags Flags { get; } // Optional public key (if Flags.PublicKey == true) or public key token. - public byte[] PublicKeyOrToken { get; private set; } + public byte[] PublicKeyOrToken { get; } // Equality - this compares every bit of data in the RuntimeAssemblyName which is acceptable for use as keys in a cache // where semantic duplication is permissible. This method is *not* meant to define ref->def binding rules or // assembly binding unification rules. - public bool Equals (RuntimeAssemblyName other) + public bool Equals(RuntimeAssemblyName other) { if (other == null) return false; - if (!this.Name.Equals (other.Name)) + if (!this.Name.Equals(other.Name)) return false; - if (this.Version == null) { + if (this.Version == null) + { if (other.Version != null) return false; - } else { - if (!this.Version.Equals (other.Version)) + } + else + { + if (!this.Version.Equals(other.Version)) return false; } - if (!string.Equals (this.CultureName, other.CultureName)) + if (!string.Equals(this.CultureName, other.CultureName)) return false; if (this.Flags != other.Flags) return false; byte[] thisPK = this.PublicKeyOrToken; byte[] otherPK = other.PublicKeyOrToken; - if (thisPK == null) { + if (thisPK == null) + { if (otherPK != null) return false; - } else if (otherPK == null) { + } + else if (otherPK == null) + { return false; - } else if (thisPK.Length != otherPK.Length) { + } + else if (thisPK.Length != otherPK.Length) + { return false; - } else { - for (int i = 0; i < thisPK.Length; i++) { + } + else + { + for (int i = 0; i < thisPK.Length; i++) + { if (thisPK[i] != otherPK[i]) return false; } @@ -89,23 +99,24 @@ public bool Equals (RuntimeAssemblyName other) return true; } - public sealed override bool Equals (Object obj) + public sealed override bool Equals(object obj) { RuntimeAssemblyName other = obj as RuntimeAssemblyName; if (other == null) return false; - - return Equals (other); + return Equals(other); } - public sealed override int GetHashCode () + public sealed override int GetHashCode() { - return this.Name.GetHashCode (); + return this.Name.GetHashCode(); } - public string FullName { - get { - return AssemblyNameHelpers.ComputeDisplayName (this); + public string FullName + { + get + { + return AssemblyNameFormatter.ComputeDisplayName(this); } } } diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index 1c39907582c7..808d37d9ecc9 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -797,14 +797,14 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } foreach (var typeNameValue in methodParams[0].UniqueValues ()) { if (typeNameValue is KnownStringValue knownStringValue) { - TypeReference foundType = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); + TypeReference foundTypeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents); + TypeDefinition foundType = foundTypeRef?.Resolve (); if (foundType == null) { // Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back. reflectionContext.RecordHandledPattern (); } else { - var typeDef = foundType?.Resolve (); - reflectionContext.RecordRecognizedPattern (typeDef, () => _markStep.MarkTypeVisibleToReflection (foundType, new DependencyInfo (DependencyKind.AccessedViaReflection, callingMethodDefinition), callingMethodDefinition)); - methodReturnValue = MergePointValue.MergeValues (methodReturnValue, new SystemTypeValue (typeDef)); + reflectionContext.RecordRecognizedPattern (foundType, () => _markStep.MarkTypeVisibleToReflection (foundTypeRef, new DependencyInfo (DependencyKind.AccessedViaReflection, callingMethodDefinition), callingMethodDefinition)); + methodReturnValue = MergePointValue.MergeValues (methodReturnValue, new SystemTypeValue (foundType)); } } else if (typeNameValue == NullValue.Instance) { reflectionContext.RecordHandledPattern (); @@ -1336,7 +1336,7 @@ void ProcessCreateInstanceByName (ref ReflectionPatternContext reflectionContext continue; } - var resolvedType = TypeNameResolver.ResolveTypeName (resolvedAssembly, typeNameStringValue.Contents); + var resolvedType = (TypeNameResolver.ResolveTypeName (resolvedAssembly, typeNameStringValue.Contents))?.Resolve (); if (resolvedType == null) { // It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case // Note that we did find the assembly, so it's not a linker config problem, it's either intentional, or wrong versions of assemblies @@ -1345,8 +1345,7 @@ void ProcessCreateInstanceByName (ref ReflectionPatternContext reflectionContext continue; } - MarkConstructorsOnType (ref reflectionContext, resolvedType?.Resolve (), - parameterlessConstructor ? m => m.Parameters.Count == 0 : (Func) null, bindingFlags); + MarkConstructorsOnType (ref reflectionContext, resolvedType, parameterlessConstructor ? m => m.Parameters.Count == 0 : (Func) null, bindingFlags); } else { reflectionContext.RecordUnrecognizedPattern (2032, $"Unrecognized value passed to the parameter '{calledMethod.Parameters[1].Name}' of method '{calledMethod.GetDisplayName ()}'. It's not possible to guarantee the availability of the target type."); } diff --git a/src/linker/Linker/AssemblyNameHelpers.cs b/src/linker/Linker/AssemblyNameHelpers.cs deleted file mode 100644 index d211e4a483cb..000000000000 --- a/src/linker/Linker/AssemblyNameHelpers.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -// Copyright (c) Microsoft Corporation. All rights reserved. - -using System.IO; -using System.Text; -using System.Collections.Generic; -using System.Globalization; - -namespace System.Reflection.Runtime.Assemblies -{ - internal static partial class AssemblyNameHelpers - { - internal static string ComputeDisplayName (RuntimeAssemblyName a) - { - const int PUBLIC_KEY_TOKEN_LEN = 8; - - if (a.Name == string.Empty) - throw new FileLoadException (); - - StringBuilder sb = new StringBuilder (); - if (a.Name != null) { - sb.AppendQuoted (a.Name); - } - - if (a.Version != null) { - Version canonicalizedVersion = a.Version.CanonicalizeVersion (); - if (canonicalizedVersion.Major != ushort.MaxValue) { - sb.Append (", Version="); - sb.Append (canonicalizedVersion.Major); - - if (canonicalizedVersion.Minor != ushort.MaxValue) { - sb.Append ('.'); - sb.Append (canonicalizedVersion.Minor); - - if (canonicalizedVersion.Build != ushort.MaxValue) { - sb.Append ('.'); - sb.Append (canonicalizedVersion.Build); - - if (canonicalizedVersion.Revision != ushort.MaxValue) { - sb.Append ('.'); - sb.Append (canonicalizedVersion.Revision); - } - } - } - } - } - - string cultureName = a.CultureName; - if (cultureName != null) { - if (cultureName == string.Empty) - cultureName = "neutral"; - sb.Append (", Culture="); - sb.AppendQuoted (cultureName); - } - - byte[] pkt = a.PublicKeyOrToken; - if (pkt != null) { - if (pkt.Length > PUBLIC_KEY_TOKEN_LEN) - throw new ArgumentException (); - - sb.Append (", PublicKeyToken="); - if (pkt.Length == 0) - sb.Append ("null"); - else { - foreach (byte b in pkt) { - sb.Append (b.ToString ("x2", CultureInfo.InvariantCulture)); - } - } - } - - if (0 != (a.Flags & AssemblyNameFlags.Retargetable)) - sb.Append (", Retargetable=Yes"); - - AssemblyContentType contentType = ExtractAssemblyContentType (a.Flags); - if (contentType == AssemblyContentType.WindowsRuntime) - sb.Append (", ContentType=WindowsRuntime"); - - // NOTE: By design (desktop compat) AssemblyName.FullName and ToString() do not include ProcessorArchitecture. - - return sb.ToString (); - } - - private static void AppendQuoted (this StringBuilder sb, string s) - { - bool needsQuoting = false; - const char quoteChar = '\"'; - - // App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one - // by some algorithm. Rather than guess at it, I'll just use double-quote consistently. - if (s != s.Trim () || s.Contains ("\"") || s.Contains ("\'")) - needsQuoting = true; - - if (needsQuoting) - sb.Append (quoteChar); - - for (int i = 0; i < s.Length; i++) { - bool addedEscape = false; - foreach (KeyValuePair kv in AssemblyNameLexer.EscapeSequences) { - string escapeReplacement = kv.Value; - if (!(s[i] == escapeReplacement[0])) - continue; - if ((s.Length - i) < escapeReplacement.Length) - continue; - if (s.Substring (i, escapeReplacement.Length).Equals (escapeReplacement)) { - sb.Append ('\\'); - sb.Append (kv.Key); - addedEscape = true; - } - } - - if (!addedEscape) - sb.Append (s[i]); - } - - if (needsQuoting) - sb.Append (quoteChar); - } - - private static Version CanonicalizeVersion (this Version version) - { - ushort major = (ushort) version.Major; - ushort minor = (ushort) version.Minor; - ushort build = (ushort) version.Build; - ushort revision = (ushort) version.Revision; - - if (major == version.Major && minor == version.Minor && build == version.Build && revision == version.Revision) - return version; - - return new Version (major, minor, build, revision); - } - - // - // These helpers convert between the combined flags+contentType+processorArchitecture value and the separated parts. - // - // Since these are only for trusted callers, they do NOT check for out of bound bits. - // - - internal static AssemblyContentType ExtractAssemblyContentType (AssemblyNameFlags flags) - { - return (AssemblyContentType) ((((int) flags) >> 9) & 0x7); - } - - internal static ProcessorArchitecture ExtractProcessorArchitecture (AssemblyNameFlags flags) - { - return (ProcessorArchitecture) ((((int) flags) >> 4) & 0x7); - } - - internal static AssemblyNameFlags ExtractAssemblyNameFlags (AssemblyNameFlags combinedFlags) - { - return combinedFlags & unchecked((AssemblyNameFlags) 0xFFFFF10F); - } - - internal static AssemblyNameFlags CombineAssemblyNameFlags (AssemblyNameFlags flags, AssemblyContentType contentType, ProcessorArchitecture processorArchitecture) - { - return (AssemblyNameFlags) (((int) flags) | (((int) contentType) << 9) | ((int) processorArchitecture << 4)); - } - } -} \ No newline at end of file diff --git a/src/linker/Linker/AssemblyNameParser.cs b/src/linker/Linker/AssemblyNameParser.cs deleted file mode 100644 index 6ea3cb01e93b..000000000000 --- a/src/linker/Linker/AssemblyNameParser.cs +++ /dev/null @@ -1,204 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.IO; -using System.Diagnostics; -using System.Globalization; -using System.Collections.Generic; - -namespace System.Reflection.Runtime.Assemblies -{ - // - // Parses an assembly name. - // - internal static class AssemblyNameParser - { - internal static RuntimeAssemblyName Parse (string s) - { - Debug.Assert (s != null); - AssemblyNameLexer lexer = new AssemblyNameLexer (s); - - int indexOfNul = s.IndexOf ((char) 0); - if (indexOfNul != -1) - s = s.Substring (0, indexOfNul); - if (s.Length == 0) - throw new ArgumentException (); - - // Name must come first. - string name; - AssemblyNameLexer.Token token = lexer.GetNext (out name); - if (token != AssemblyNameLexer.Token.String) { - throw new FileLoadException (); - } - - if (name == string.Empty || name.IndexOfAny (s_illegalCharactersInSimpleName) != -1) - throw new FileLoadException (); - - Version version = null; - string cultureName = null; - byte[] pkt = null; - AssemblyNameFlags flags = 0; - - List alreadySeen = new List (); - token = lexer.GetNext (); - while (token != AssemblyNameLexer.Token.End) { - if (token != AssemblyNameLexer.Token.Comma) - throw new FileLoadException (); - - string attributeName; - token = lexer.GetNext (out attributeName); - if (token != AssemblyNameLexer.Token.String) - throw new FileLoadException (); - token = lexer.GetNext (); - - // Compat note: Inside AppX apps, the desktop CLR's AssemblyName parser skips past any elements that don't follow the "=" pattern. - // (when running classic Windows apps, such an illegal construction throws an exception as expected.) - // Naturally, at least one app unwittingly takes advantage of this. - if (token == AssemblyNameLexer.Token.Comma || token == AssemblyNameLexer.Token.End) - continue; - - if (token != AssemblyNameLexer.Token.Equals) - throw new FileLoadException (); - - string attributeValue; - token = lexer.GetNext (out attributeValue); - if (token != AssemblyNameLexer.Token.String) - throw new FileLoadException (); - - if (attributeName == string.Empty) - throw new FileLoadException (); - - for (int i = 0; i < alreadySeen.Count; i++) { - if (alreadySeen[i].Equals (attributeName, StringComparison.OrdinalIgnoreCase)) - throw new FileLoadException (); // Cannot specify the same attribute twice. - } - - alreadySeen.Add (attributeName); - if (attributeName.Equals ("Version", StringComparison.OrdinalIgnoreCase)) { - version = ParseVersion (attributeValue); - } - - if (attributeName.Equals ("Culture", StringComparison.OrdinalIgnoreCase)) { - cultureName = ParseCulture (attributeValue); - } - - if (attributeName.Equals ("PublicKeyToken", StringComparison.OrdinalIgnoreCase)) { - pkt = ParsePKT (attributeValue); - } - - if (attributeName.Equals ("ProcessorArchitecture", StringComparison.OrdinalIgnoreCase)) { - flags |= (AssemblyNameFlags) (((int) ParseProcessorArchitecture (attributeValue)) << 4); - } - - if (attributeName.Equals ("Retargetable", StringComparison.OrdinalIgnoreCase)) { - if (attributeValue.Equals ("Yes", StringComparison.OrdinalIgnoreCase)) - flags |= AssemblyNameFlags.Retargetable; - else if (attributeValue.Equals ("No", StringComparison.OrdinalIgnoreCase)) { - // nothing to do - } else - throw new FileLoadException (); - } - - if (attributeName.Equals ("ContentType", StringComparison.OrdinalIgnoreCase)) { - if (attributeValue.Equals ("WindowsRuntime", StringComparison.OrdinalIgnoreCase)) - flags |= (AssemblyNameFlags) (((int) AssemblyContentType.WindowsRuntime) << 9); - else - throw new FileLoadException (); - } - - // Desktop compat: If we got here, the attribute name is unknown to us. Ignore it (as long it's not duplicated.) - token = lexer.GetNext (); - } - return new RuntimeAssemblyName (name, version, cultureName, flags, pkt); - } - - private static Version ParseVersion (string attributeValue) - { - string[] parts = attributeValue.Split ('.'); - if (parts.Length > 4) - throw new FileLoadException (); - ushort[] versionNumbers = new ushort[4]; - for (int i = 0; i < versionNumbers.Length; i++) { - if (i >= parts.Length) - versionNumbers[i] = ushort.MaxValue; - else { - // Desktop compat: TryParse is a little more forgiving than Fusion. - for (int j = 0; j < parts[i].Length; j++) { - if (!Char.IsDigit (parts[i][j])) - throw new FileLoadException (); - } - - if (!(ushort.TryParse (parts[i], out versionNumbers[i]))) - throw new FileLoadException (); - } - } - - if (versionNumbers[0] == ushort.MaxValue || versionNumbers[1] == ushort.MaxValue) - throw new FileLoadException (); - if (versionNumbers[2] == ushort.MaxValue) - return new Version (versionNumbers[0], versionNumbers[1]); - if (versionNumbers[3] == ushort.MaxValue) - return new Version (versionNumbers[0], versionNumbers[1], versionNumbers[2]); - - return new Version (versionNumbers[0], versionNumbers[1], versionNumbers[2], versionNumbers[3]); - } - - private static string ParseCulture (string attributeValue) - { - if (attributeValue.Equals ("Neutral", StringComparison.OrdinalIgnoreCase)) { - return ""; - } else { - CultureInfo culture = new CultureInfo (attributeValue); // Force a CultureNotFoundException if not a valid culture. - return culture.Name; - } - } - - private static byte[] ParsePKT (string attributeValue) - { - if (attributeValue.Equals ("null", StringComparison.OrdinalIgnoreCase) || attributeValue == string.Empty) - return new byte[0]; - - if (attributeValue.Length != 8 * 2) - throw new FileLoadException (); - - byte[] pkt = new byte[8]; - int srcIndex = 0; - for (int i = 0; i < 8; i++) { - char hi = attributeValue[srcIndex++]; - char lo = attributeValue[srcIndex++]; - pkt[i] = (byte) ((ParseHexNybble (hi) << 4) | ParseHexNybble (lo)); - } - - return pkt; - } - - private static ProcessorArchitecture ParseProcessorArchitecture (String attributeValue) - { - if (attributeValue.Equals ("msil", StringComparison.OrdinalIgnoreCase)) - return ProcessorArchitecture.MSIL; - if (attributeValue.Equals ("x86", StringComparison.OrdinalIgnoreCase)) - return ProcessorArchitecture.X86; - if (attributeValue.Equals ("ia64", StringComparison.OrdinalIgnoreCase)) - return ProcessorArchitecture.IA64; - if (attributeValue.Equals ("amd64", StringComparison.OrdinalIgnoreCase)) - return ProcessorArchitecture.Amd64; - if (attributeValue.Equals ("arm", StringComparison.OrdinalIgnoreCase)) - return ProcessorArchitecture.Arm; - throw new FileLoadException (); - } - - private static byte ParseHexNybble (char c) - { - if (c >= '0' && c <= '9') - return (byte) (c - '0'); - if (c >= 'a' && c <= 'f') - return (byte) (c - 'a' + 10); - if (c >= 'A' && c <= 'F') - return (byte) (c - 'A' + 10); - throw new FileLoadException (); - } - - private static readonly char[] s_illegalCharactersInSimpleName = { '/', '\\', ':' }; - } -} \ No newline at end of file diff --git a/src/linker/Linker/TypeNameResolver.cs b/src/linker/Linker/TypeNameResolver.cs index b8e877ae5b47..e2d9e0b84f77 100644 --- a/src/linker/Linker/TypeNameResolver.cs +++ b/src/linker/Linker/TypeNameResolver.cs @@ -54,15 +54,27 @@ static TypeReference ResolveTypeName (AssemblyDefinition assembly, TypeName type if (typeName is AssemblyQualifiedTypeName assemblyQualifiedTypeName) { return ResolveTypeName (assembly, assemblyQualifiedTypeName.TypeName); } else if (typeName is ConstructedGenericTypeName constructedGenericTypeName) { - var genericTypeDef = ResolveTypeName (assembly, constructedGenericTypeName.GenericType)?.Resolve (); - var genericInstanceType = new GenericInstanceType (genericTypeDef); + var genericTypeRef = ResolveTypeName (assembly, constructedGenericTypeName.GenericType); + if (genericTypeRef == null) + return null; + + TypeDefinition genericType = genericTypeRef.Resolve (); + var genericInstanceType = new GenericInstanceType (genericType); foreach (var arg in constructedGenericTypeName.GenericArguments) { - genericInstanceType.GenericArguments.Add (ResolveTypeName (assembly, arg)); + var genericArgument = ResolveTypeName (assembly, arg); + if (genericArgument == null) + return null; + + genericInstanceType.GenericArguments.Add (genericArgument); } return genericInstanceType; } else if (typeName is HasElementTypeName elementTypeName) { - return ResolveTypeName (assembly, elementTypeName.ElementTypeName); + var elementType = ResolveTypeName (assembly, elementTypeName.ElementTypeName); + if (elementType == null) + return null; + + return elementType; } return assembly.MainModule.GetType (typeName.ToString ()); diff --git a/src/linker/Linker/TypeParser.cs b/src/linker/Linker/TypeParser.cs deleted file mode 100644 index bec0e97a068e..000000000000 --- a/src/linker/Linker/TypeParser.cs +++ /dev/null @@ -1,188 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Reflection.Runtime.Assemblies; - -namespace System.Reflection.Runtime.TypeParsing -{ - // - // Parser for type names passed to GetType() apis. - // - public sealed class TypeParser - { - // - // Parses a typename. The typename may be optionally postpended with a "," followed by a legal assembly name. - // - public static TypeName ParseTypeName (string s) - { - if (string.IsNullOrEmpty (s)) - return null; - - // Linker specific: to keep the existing behavior, we return null whenever we have a whitespace type name, insteead - // of throwing any exception. - int idx = 0; - while (idx < s.Length && char.IsWhiteSpace (s[idx])) { - idx++; - } - if (idx < s.Length && s[idx] == ',') - return null; - - try { - TypeParser parser = new TypeParser (s); - NonQualifiedTypeName typeName = parser.ParseNonQualifiedTypeName (); - TokenType token = parser._lexer.GetNextToken (); - if (token == TokenType.End) - return typeName; - if (token == TokenType.Comma) { - RuntimeAssemblyName assemblyName = parser._lexer.GetNextAssemblyName (); - token = parser._lexer.Peek; - if (token != TokenType.End) - throw new ArgumentException (); - return new AssemblyQualifiedTypeName (typeName, assemblyName); - } - throw new ArgumentException (); - } catch (TypeLexer.IllegalEscapeSequenceException) { - // Emulates a CLR4.5 bug that causes any string that contains an illegal escape sequence to be parsed as the empty string. - return ParseTypeName (string.Empty); - } - } - - private TypeParser (string s) - { - _lexer = new TypeLexer (s); - } - - // - // Parses a type name without any assembly name qualification. - // - private NonQualifiedTypeName ParseNonQualifiedTypeName () - { - // Parse the named type or constructed generic type part first. - NonQualifiedTypeName typeName = ParseNamedOrConstructedGenericTypeName (); - - // Iterate through any "has-element" qualifiers ([], &, *). - for (; ; ) - { - TokenType token = _lexer.Peek; - if (token == TokenType.End) - break; - if (token == TokenType.Asterisk) { - _lexer.Skip (); - typeName = new PointerTypeName (typeName); - } else if (token == TokenType.Ampersand) { - _lexer.Skip (); - typeName = new ByRefTypeName (typeName); - } else if (token == TokenType.OpenSqBracket) { - _lexer.Skip (); - token = _lexer.GetNextToken (); - if (token == TokenType.Asterisk) { - typeName = new MultiDimArrayTypeName (typeName, 1); - token = _lexer.GetNextToken (); - } else { - int rank = 1; - while (token == TokenType.Comma) { - token = _lexer.GetNextToken (); - rank++; - } - if (rank == 1) - typeName = new ArrayTypeName (typeName); - else - typeName = new MultiDimArrayTypeName (typeName, rank); - } - if (token != TokenType.CloseSqBracket) - throw new ArgumentException (); - } else { - break; - } - } - return typeName; - } - - // - // Foo or Foo+Inner or Foo[String] or Foo+Inner[String] - // - private NonQualifiedTypeName ParseNamedOrConstructedGenericTypeName () - { - NamedTypeName namedType = ParseNamedTypeName (); - // Because "[" is used both for generic arguments and array indexes, we must peek two characters deep. - if (!(_lexer.Peek == TokenType.OpenSqBracket && (_lexer.PeekSecond == TokenType.Other || _lexer.PeekSecond == TokenType.OpenSqBracket))) - return namedType; - else { - _lexer.Skip (); - List genericTypeArguments = new List (); - for (; ; ) - { - TypeName genericTypeArgument = ParseGenericTypeArgument (); - genericTypeArguments.Add (genericTypeArgument); - TokenType token = _lexer.GetNextToken (); - if (token == TokenType.CloseSqBracket) - break; - if (token != TokenType.Comma) - throw new ArgumentException (); - } - - return new ConstructedGenericTypeName (namedType, genericTypeArguments); - } - } - - // - // Foo or Foo+Inner - // - private NamedTypeName ParseNamedTypeName () - { - NamedTypeName namedType = ParseNamespaceTypeName (); - while (_lexer.Peek == TokenType.Plus) { - _lexer.Skip (); - string nestedTypeName = _lexer.GetNextIdentifier (); - namedType = new NestedTypeName (nestedTypeName, namedType); - } - - return namedType; - } - - // - // Non-nested named type. - // - private NamespaceTypeName ParseNamespaceTypeName () - { - string fullName = _lexer.GetNextIdentifier (); - string[] parts = fullName.Split ('.'); - int numNamespaceParts = parts.Length - 1; - string[] namespaceParts = new string[numNamespaceParts]; - for (int i = 0; i < numNamespaceParts; i++) - namespaceParts[numNamespaceParts - i - 1] = parts[i]; - string name = parts[numNamespaceParts]; - return new NamespaceTypeName (namespaceParts, name); - } - - // - // Parse a generic argument. In particular, generic arguments can take the special form [,]. - // - private TypeName ParseGenericTypeArgument () - { - TokenType token = _lexer.GetNextToken (); - if (token == TokenType.Other) { - NonQualifiedTypeName nonQualifiedTypeName = ParseNonQualifiedTypeName (); - return new AssemblyQualifiedTypeName (nonQualifiedTypeName, null); - } else if (token == TokenType.OpenSqBracket) { - RuntimeAssemblyName assemblyName = null; - NonQualifiedTypeName typeName = ParseNonQualifiedTypeName (); - token = _lexer.GetNextToken (); - if (token == TokenType.Comma) { - assemblyName = _lexer.GetNextEmbeddedAssemblyName (); - token = _lexer.GetNextToken (); - } - - if (token != TokenType.CloseSqBracket) - throw new ArgumentException (); - - return new AssemblyQualifiedTypeName (typeName, assemblyName); - } else - throw new ArgumentException (); - } - - private TypeLexer _lexer; - } -} \ No newline at end of file diff --git a/src/linker/Mono.Linker.csproj b/src/linker/Mono.Linker.csproj index af2921676b7b..31275b8b2a6d 100644 --- a/src/linker/Mono.Linker.csproj +++ b/src/linker/Mono.Linker.csproj @@ -22,6 +22,7 @@ + From cc1cd8fd2445dede2d55c63dc917dc86f7be607c Mon Sep 17 00:00:00 2001 From: Mateo Torres Ruiz Date: Mon, 14 Sep 2020 10:53:47 -0700 Subject: [PATCH 5/5] Match filepaths --- external/{TypeParsing => corert}/README.md | 0 .../shared}/System/Reflection/AssemblyNameFormatter.cs | 2 +- .../shared}/System/Reflection/AssemblyNameHelpers.cs | 0 .../shared}/System/Reflection/AssemblyNameLexer.cs | 0 .../shared}/System/Reflection/AssemblyNameParser.cs | 0 .../shared}/System/Reflection/RuntimeAssemblyName.cs | 0 .../src}/System/Reflection/Runtime/TypeParsing/TypeLexer.cs | 0 .../src}/System/Reflection/Runtime/TypeParsing/TypeName.cs | 0 .../src}/System/Reflection/Runtime/TypeParsing/TypeParser.cs | 0 src/linker/Mono.Linker.csproj | 2 +- 10 files changed, 2 insertions(+), 2 deletions(-) rename external/{TypeParsing => corert}/README.md (100%) rename external/{TypeParsing => corert/src/System.Private.CoreLib/shared}/System/Reflection/AssemblyNameFormatter.cs (98%) rename external/{TypeParsing => corert/src/System.Private.CoreLib/shared}/System/Reflection/AssemblyNameHelpers.cs (100%) rename external/{TypeParsing => corert/src/System.Private.CoreLib/shared}/System/Reflection/AssemblyNameLexer.cs (100%) rename external/{TypeParsing => corert/src/System.Private.CoreLib/shared}/System/Reflection/AssemblyNameParser.cs (100%) rename external/{TypeParsing => corert/src/System.Private.CoreLib/shared}/System/Reflection/RuntimeAssemblyName.cs (100%) rename external/{TypeParsing => corert/src/System.Private.CoreLib/src}/System/Reflection/Runtime/TypeParsing/TypeLexer.cs (100%) rename external/{TypeParsing => corert/src/System.Private.CoreLib/src}/System/Reflection/Runtime/TypeParsing/TypeName.cs (100%) rename external/{TypeParsing => corert/src/System.Private.CoreLib/src}/System/Reflection/Runtime/TypeParsing/TypeParser.cs (100%) diff --git a/external/TypeParsing/README.md b/external/corert/README.md similarity index 100% rename from external/TypeParsing/README.md rename to external/corert/README.md diff --git a/external/TypeParsing/System/Reflection/AssemblyNameFormatter.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs similarity index 98% rename from external/TypeParsing/System/Reflection/AssemblyNameFormatter.cs rename to external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs index 4471c15bee06..821a8d3c8ec5 100644 --- a/external/TypeParsing/System/Reflection/AssemblyNameFormatter.cs +++ b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs @@ -97,7 +97,7 @@ private static void AppendQuoted(this StringBuilder sb, string s) // App-compat: You can use double or single quotes to quote a name, and Fusion (or rather the IdentityAuthority) picks one // by some algorithm. Rather than guess at it, we use double quotes consistently. - if (s != s.Trim() || s.Contains('\"') || s.Contains('\'')) + if (s != s.Trim() || s.Contains("\"") || s.Contains("\'")) needsQuoting = true; if (needsQuoting) diff --git a/external/TypeParsing/System/Reflection/AssemblyNameHelpers.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameHelpers.cs similarity index 100% rename from external/TypeParsing/System/Reflection/AssemblyNameHelpers.cs rename to external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameHelpers.cs diff --git a/external/TypeParsing/System/Reflection/AssemblyNameLexer.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameLexer.cs similarity index 100% rename from external/TypeParsing/System/Reflection/AssemblyNameLexer.cs rename to external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameLexer.cs diff --git a/external/TypeParsing/System/Reflection/AssemblyNameParser.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameParser.cs similarity index 100% rename from external/TypeParsing/System/Reflection/AssemblyNameParser.cs rename to external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameParser.cs diff --git a/external/TypeParsing/System/Reflection/RuntimeAssemblyName.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/RuntimeAssemblyName.cs similarity index 100% rename from external/TypeParsing/System/Reflection/RuntimeAssemblyName.cs rename to external/corert/src/System.Private.CoreLib/shared/System/Reflection/RuntimeAssemblyName.cs diff --git a/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeLexer.cs b/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeLexer.cs similarity index 100% rename from external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeLexer.cs rename to external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeLexer.cs diff --git a/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeName.cs b/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeName.cs similarity index 100% rename from external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeName.cs rename to external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeName.cs diff --git a/external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeParser.cs b/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeParser.cs similarity index 100% rename from external/TypeParsing/System/Reflection/Runtime/TypeParsing/TypeParser.cs rename to external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeParser.cs diff --git a/src/linker/Mono.Linker.csproj b/src/linker/Mono.Linker.csproj index 31275b8b2a6d..ed3595ce6d89 100644 --- a/src/linker/Mono.Linker.csproj +++ b/src/linker/Mono.Linker.csproj @@ -22,7 +22,7 @@ - +