diff options
author | Mateo Torres-Ruiz <mateoatr@users.noreply.github.com> | 2020-09-16 16:23:06 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-16 16:23:06 +0300 |
commit | ff542781fd221ff5d029d4a189dfbfeebb74cffe (patch) | |
tree | d78f79260c48bb4d48fe70d0ac5fe1fad6259042 /external | |
parent | 78e35797f5b1b0b1df0aa25111f6574291d8deb0 (diff) |
Port .NET Native type name parser (#1472)
* Add type parser
* PR feedback
* Fix mono build
* Move corert code to external
* Match filepaths
Diffstat (limited to 'external')
9 files changed, 1344 insertions, 0 deletions
diff --git a/external/corert/README.md b/external/corert/README.md new file mode 100644 index 000000000..babf7d8e2 --- /dev/null +++ b/external/corert/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/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameFormatter.cs new file mode 100644 index 000000000..821a8d3c8 --- /dev/null +++ b/external/corert/src/System.Private.CoreLib/shared/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<char, string> 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<char, string>[] EscapeSequences = + { + new KeyValuePair<char, string>('\\', "\\"), + new KeyValuePair<char, string>(',', ","), + new KeyValuePair<char, string>('=', "="), + new KeyValuePair<char, string>('\'', "'"), + new KeyValuePair<char, string>('\"', "\""), + new KeyValuePair<char, string>('n', Environment.NewLine), + new KeyValuePair<char, string>('t', "\t"), + }; + } +}
\ No newline at end of file diff --git a/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameHelpers.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameHelpers.cs new file mode 100644 index 000000000..9b9c1a702 --- /dev/null +++ b/external/corert/src/System.Private.CoreLib/shared/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/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameLexer.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameLexer.cs new file mode 100644 index 000000000..5ef3d595a --- /dev/null +++ b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameLexer.cs @@ -0,0 +1,124 @@ +// 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.Collections.Generic; + +namespace System.Reflection +{ + // + // 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<char, string> kv in AssemblyNameFormatter.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; + } + + // 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/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameParser.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/AssemblyNameParser.cs new file mode 100644 index 000000000..fdf5b8f06 --- /dev/null +++ b/external/corert/src/System.Private.CoreLib/shared/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<string> alreadySeen = new List<string>(); + 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 "<Something>=<Something>" 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<byte>(); + + 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/external/corert/src/System.Private.CoreLib/shared/System/Reflection/RuntimeAssemblyName.cs b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/RuntimeAssemblyName.cs new file mode 100644 index 000000000..561e9382f --- /dev/null +++ b/external/corert/src/System.Private.CoreLib/shared/System/Reflection/RuntimeAssemblyName.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Reflection +{ + // + // 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. + // + public sealed class RuntimeAssemblyName : IEquatable<RuntimeAssemblyName> + { + 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; } + + // Optional version. + public Version Version { get; } + + // Optional culture name. + public string CultureName { get; } + + // Optional flags (this is actually an OR of the classic flags and the ContentType.) + public AssemblyNameFlags Flags { get; } + + // Optional public key (if Flags.PublicKey == true) or public key token. + 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) + { + 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 AssemblyNameFormatter.ComputeDisplayName(this); + } + } + } +}
\ No newline at end of file diff --git a/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeLexer.cs b/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeLexer.cs new file mode 100644 index 000000000..4a2429721 --- /dev/null +++ b/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeLexer.cs @@ -0,0 +1,238 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +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/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeName.cs b/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeName.cs new file mode 100644 index 000000000..81403cc4f --- /dev/null +++ b/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeName.cs @@ -0,0 +1,219 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Collections.Generic; + +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. + // + public 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. + // + public sealed class AssemblyQualifiedTypeName : TypeName + { + public AssemblyQualifiedTypeName(NonQualifiedTypeName typeName, RuntimeAssemblyName assemblyName) + { + Debug.Assert(typeName != null); + TypeName = typeName; + AssemblyName = assemblyName; + } + + public sealed override string ToString() + { + return TypeName.ToString() + ((AssemblyName == null) ? "" : ", " + AssemblyName.FullName); + } + + public RuntimeAssemblyName AssemblyName { get; } + public NonQualifiedTypeName TypeName { get; } + } + + // + // Base class for all non-assembly-qualified type names. + // + public 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; } + } + + // + // 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<TypeName> genericArguments) + { + GenericType = genericType; + GenericArguments = genericArguments; + } + + public NamedTypeName GenericType { get; } + public IEnumerable<TypeName> GenericArguments { get; } + + 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/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeParser.cs b/external/corert/src/System.Private.CoreLib/src/System/Reflection/Runtime/TypeParsing/TypeParser.cs new file mode 100644 index 000000000..c06cf7667 --- /dev/null +++ b/external/corert/src/System.Private.CoreLib/src/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<TypeName> genericTypeArguments = new List<TypeName>(); + 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 [<typename>,<assemblyname>]. + // + 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 |