Skip to content

Commit

Permalink
Port .NET Native type name parser (dotnet#1472)
Browse files Browse the repository at this point in the history
* Add type parser

* PR feedback

* Fix mono build

* Move corert code to external

* Match filepaths
  • Loading branch information
mateoatr authored and Mateo Torres Ruiz committed Nov 13, 2020
1 parent afb5dc1 commit 3fdf588
Show file tree
Hide file tree
Showing 22 changed files with 1,601 additions and 250 deletions.
1 change: 1 addition & 0 deletions external/corert/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The code in this folder was adapted from dotnet/corert commit c8bfca5f4554badfb89b80d2319769f83512bf62
Original file line number Diff line number Diff line change
@@ -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"),
};
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 3fdf588

Please sign in to comment.