Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port .NET Native type name parser #1472

Merged
merged 6 commits into from
Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions external/TypeParsing/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
156 changes: 156 additions & 0 deletions external/TypeParsing/System/Reflection/AssemblyNameFormatter.cs
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"),
};
}
}
42 changes: 42 additions & 0 deletions external/TypeParsing/System/Reflection/AssemblyNameHelpers.cs
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));
}
}
}
124 changes: 124 additions & 0 deletions external/TypeParsing/System/Reflection/AssemblyNameLexer.cs
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