602 lines
18 KiB
C#
Raw Permalink Normal View History

2021-09-20 18:20:01 +02:00
/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text;
namespace dnSpy.Debugger.DotNet.Metadata {
struct DmdMemberFormatter : IDisposable {
readonly GlobalFlags globalFlags;
StringBuilder writer;
const int MAX_RECURSION_COUNT = 100;
int recursionCounter;
[Flags]
enum GlobalFlags {
None = 0,
Serializable = 0x00000001,
}
DmdMemberFormatter(GlobalFlags flags) {
globalFlags = flags;
writer = ObjectPools.AllocStringBuilder();
recursionCounter = 0;
}
public void Dispose() => ObjectPools.FreeNoToString(ref writer!);
bool IncrementRecursionCounter() {
if (recursionCounter >= MAX_RECURSION_COUNT)
return false;
recursionCounter++;
return true;
}
void DecrementRecursionCounter() => recursionCounter--;
static bool IsGenericTypeDefinition(DmdType type) {
if (!type.IsMetadataReference)
return type.IsGenericTypeDefinition;
// It's a TypeRef, make sure it won't throw if it can't resolve the type
var resolvedType = type.ResolveNoThrow();
if (resolvedType is not null)
return resolvedType.IsGenericTypeDefinition;
// Guess based on name
return type is Impl.DmdTypeRef && type.MetadataName!.LastIndexOf('`') >= 0;
}
static bool ContainsGenericParameters(DmdType type) {
if (!type.IsMetadataReference)
return type.ContainsGenericParameters;
// It's a TypeRef, make sure it won't throw if it can't resolve the type
var resolvedType = type.ResolveNoThrow();
if (resolvedType is not null)
return resolvedType.ContainsGenericParameters;
if (type is Impl.DmdTypeRef)
return type.MetadataName!.LastIndexOf('`') >= 0;
return type.ContainsGenericParameters;
}
static void WriteAssemblyFullName(StringBuilder sb, DmdType type) {
if (!type.IsMetadataReference) {
if (type.TypeSignatureKind != DmdTypeSignatureKind.GenericInstance) {
type.Assembly.GetName().FormatFullNameTo(sb);
return;
}
// Won't throw
type = type.GetGenericTypeDefinition();
}
var nonNested = Impl.DmdTypeUtilities.GetNonNestedType(type);
if (nonNested is not null) {
var typeScope = nonNested.TypeScope;
switch (typeScope.Kind) {
default:
case DmdTypeScopeKind.Invalid:
sb.Append("???");
return;
case DmdTypeScopeKind.Module:
((DmdModule)typeScope.Data!).Assembly.GetName().FormatFullNameTo(sb);
return;
case DmdTypeScopeKind.ModuleRef:
((IDmdAssemblyName)typeScope.Data2!).FormatFullNameTo(sb);
return;
case DmdTypeScopeKind.AssemblyRef:
((IDmdAssemblyName)typeScope.Data!).FormatFullNameTo(sb);
return;
}
}
}
static DmdType? GetGenericTypeDefinition(DmdType type) {
if (!type.IsMetadataReference)
return type.GetGenericTypeDefinition();
var resolvedType = type.ResolveNoThrow();
if (resolvedType is not null)
return resolvedType.GetGenericTypeDefinition();
if (type is Impl.DmdGenericInstanceTypeRef)
return type.GetGenericTypeDefinition();
if (type.MetadataName!.LastIndexOf('`') >= 0)
return type;
return null;
}
static ReadOnlyCollection<DmdType> GetGenericArguments(DmdType type) {
if (!type.IsMetadataReference)
return type.GetGenericArguments();
var resolvedType = type.ResolveNoThrow();
if (resolvedType is not null)
return resolvedType.GetGenericArguments();
if (type is Impl.DmdGenericInstanceTypeRef)
return type.GetGenericArguments();
return ReadOnlyCollectionHelpers.Empty<DmdType>();
}
static IList<DmdType> GetGenericArguments(DmdMethodBase method) {
if (method.GetMethodSignature().GenericParameterCount == 0)
return Array.Empty<DmdType>();
if (!method.IsMetadataReference)
return method.GetGenericArguments();
var resolvedMethod = method.ResolveMethodBaseNoThrow();
if (resolvedMethod is not null)
return resolvedMethod.GetGenericArguments();
return Array.Empty<DmdType>();
}
public static string? FormatFullName(DmdType type) => Format(type, serializable: true);
public static string? FormatAssemblyQualifiedName(DmdType type) {
var t = type;
while (t.GetElementType() is DmdType elementType)
t = elementType;
if (!IsGenericTypeDefinition(t) && ContainsGenericParameters(t))
return null;
using (var formatter = new DmdMemberFormatter(GlobalFlags.Serializable)) {
formatter.Write(type);
formatter.writer.Append(", ");
WriteAssemblyFullName(formatter.writer, type);
return formatter.writer.ToString();
}
}
public static string Format(DmdMemberInfo member, bool serializable = false) {
using (var formatter = new DmdMemberFormatter(serializable ? GlobalFlags.Serializable : GlobalFlags.None))
return formatter.FormatCore(member);
}
public static string? Format(DmdType type, bool serializable = false) {
if (serializable) {
var t = type;
while (t.GetElementType() is DmdType elementType)
t = elementType;
if (!IsGenericTypeDefinition(t) && ContainsGenericParameters(t))
return null;
}
using (var formatter = new DmdMemberFormatter(serializable ? GlobalFlags.Serializable : GlobalFlags.None))
return formatter.FormatCore(type);
}
public static string Format(DmdFieldInfo field, bool serializable = false) {
using (var formatter = new DmdMemberFormatter(serializable ? GlobalFlags.Serializable : GlobalFlags.None))
return formatter.FormatCore(field);
}
public static string Format(DmdMethodBase method, bool serializable = false) {
using (var formatter = new DmdMemberFormatter(serializable ? GlobalFlags.Serializable : GlobalFlags.None))
return formatter.FormatCore(method);
}
public static string Format(DmdPropertyInfo property, bool serializable = false) {
using (var formatter = new DmdMemberFormatter(serializable ? GlobalFlags.Serializable : GlobalFlags.None))
return formatter.FormatCore(property);
}
public static string Format(DmdEventInfo @event, bool serializable = false) {
using (var formatter = new DmdMemberFormatter(serializable ? GlobalFlags.Serializable : GlobalFlags.None))
return formatter.FormatCore(@event);
}
public static string Format(DmdParameterInfo parameter, bool serializable = false) {
using (var formatter = new DmdMemberFormatter(serializable ? GlobalFlags.Serializable : GlobalFlags.None))
return formatter.FormatCore(parameter);
}
public static string Format(DmdMethodSignature methodSignature, bool serializable = false) {
using (var formatter = new DmdMemberFormatter(serializable ? GlobalFlags.Serializable : GlobalFlags.None))
return formatter.FormatCore(methodSignature);
}
public static string FormatName(DmdType type) {
if (type.MetadataName is string name && name.IndexOfAny(escapeChars) < 0)
return name;
using (var formatter = new DmdMemberFormatter(GlobalFlags.None))
return formatter.FormatNameCore(type);
}
string FormatCore(DmdMemberInfo member) {
Write(member);
return writer.ToString();
}
string FormatCore(DmdType type) {
Write(type);
return writer.ToString();
}
string FormatCore(DmdFieldInfo field) {
Write(field);
return writer.ToString();
}
string FormatCore(DmdMethodBase method) {
Write(method);
return writer.ToString();
}
string FormatCore(DmdPropertyInfo property) {
Write(property);
return writer.ToString();
}
string FormatCore(DmdEventInfo @event) {
Write(@event);
return writer.ToString();
}
string FormatCore(DmdParameterInfo parameter) {
Write(parameter);
return writer.ToString();
}
string FormatCore(DmdMethodSignature methodSignature) {
Write(methodSignature);
return writer.ToString();
}
string FormatNameCore(DmdType type) {
WriteName(type, GetTypeFlags(shortTypeNames: false) | TypeFlags.FnPtrIsIntPtr);
return writer.ToString();
}
void Write(DmdMemberInfo member) {
switch (member.MemberType) {
case DmdMemberTypes.TypeInfo:
case DmdMemberTypes.NestedType:
Write((DmdType)member);
break;
case DmdMemberTypes.Field:
Write((DmdFieldInfo)member);
break;
case DmdMemberTypes.Constructor:
case DmdMemberTypes.Method:
Write((DmdMethodBase)member);
break;
case DmdMemberTypes.Property:
Write((DmdPropertyInfo)member);
break;
case DmdMemberTypes.Event:
Write((DmdEventInfo)member);
break;
default:
Debug.Fail($"Unknown member: {member.GetType()}");
break;
}
}
TypeFlags GetTypeFlags(bool shortTypeNames) {
var flags = TypeFlags.None;
if (shortTypeNames)
flags |= TypeFlags.ShortSpecialNames | TypeFlags.NoDeclaringTypeNames;
return flags;
}
void Write(DmdType type) => Write(type, GetTypeFlags(false) | TypeFlags.FnPtrIsIntPtr);
[Flags]
enum TypeFlags {
None = 0,
ShortSpecialNames = 0x00000001,
NoDeclaringTypeNames = 0x00000002,
NoGenericDefParams = 0x00000004,
MethodGenericArgumentType = 0x00000008,
FnPtrIsIntPtr = 0x00000010,
}
void WriteIdentifier(string? id) {
if (id is null)
id = string.Empty;
if (id.IndexOfAny(escapeChars) < 0)
writer.Append(id);
else {
int start = 0;
for (int i = 0; i < id.Length; i++) {
var c = id[i];
switch (c) {
// coreclr: IsTypeNameReservedChar()
case ',':
case '[':
case ']':
case '&':
case '*':
case '+':
case '\\':
writer.Append(id, start, i - start);
writer.Append('\\');
writer.Append(c);
start = i + 1;
break;
}
}
if (start != id.Length) {
if (start == 0)
writer.Append(id);
else
writer.Append(id, start, id.Length - start);
}
}
}
// coreclr: IsTypeNameReservedChar()
static readonly char[] escapeChars = new[] { ',', '[', ']', '&', '*', '+', '\\' };
static bool IsShortNameType(DmdType type) => type.IsPrimitive || type == type.AppDomain.System_Void || type == type.AppDomain.System_TypedReference;
void Write(DmdType? type, TypeFlags flags) {
if (type is null) {
writer.Append("???");
return;
}
if (!IncrementRecursionCounter()) {
writer.Append("???");
return;
}
switch (type.TypeSignatureKind) {
case DmdTypeSignatureKind.Type:
if ((flags & TypeFlags.NoDeclaringTypeNames) == 0 && type.DeclaringType is DmdType declType && !type.IsGenericParameter) {
Write(declType, flags | (IsGenericTypeDefinition(type) ? TypeFlags.NoGenericDefParams : 0));
writer.Append('+');
}
if (!type.IsNested && type.MetadataNamespace is string ns && ns.Length > 0) {
if ((globalFlags & GlobalFlags.Serializable) != 0 ||
((flags & TypeFlags.MethodGenericArgumentType) == 0 && ((flags & TypeFlags.ShortSpecialNames) == 0 || !IsShortNameType(type)))) {
WriteIdentifier(ns);
writer.Append('.');
}
}
WriteIdentifier(type.MetadataName);
if ((flags & TypeFlags.NoGenericDefParams) == 0 && (globalFlags & GlobalFlags.Serializable) == 0)
WriteTypeGenericArguments(GetGenericArguments(type), flags & ~TypeFlags.NoGenericDefParams);
break;
case DmdTypeSignatureKind.Pointer:
Write(type.GetElementType(), flags);
writer.Append('*');
break;
case DmdTypeSignatureKind.ByRef:
Write(type.GetElementType(), flags);
writer.Append('&');
break;
case DmdTypeSignatureKind.TypeGenericParameter:
case DmdTypeSignatureKind.MethodGenericParameter:
WriteIdentifier(type.MetadataName);
break;
case DmdTypeSignatureKind.SZArray:
Write(type.GetElementType(), flags);
writer.Append("[]");
break;
case DmdTypeSignatureKind.MDArray:
Write(type.GetElementType(), flags);
writer.Append('[');
var rank = type.GetArrayRank();
if (rank <= 0)
writer.Append("???");
else if (rank == 1)
writer.Append('*');
else
writer.Append(',', rank - 1);
writer.Append(']');
break;
case DmdTypeSignatureKind.GenericInstance:
Write(GetGenericTypeDefinition(type), flags | TypeFlags.NoGenericDefParams);
if ((flags & TypeFlags.MethodGenericArgumentType) == 0)
WriteTypeGenericArguments(GetGenericArguments(type), flags);
break;
case DmdTypeSignatureKind.FunctionPointer:
if ((flags & TypeFlags.FnPtrIsIntPtr) != 0)
Write(type.AppDomain.System_IntPtr, flags);
else
writer.Append("(fnptr)");
break;
default: throw new InvalidOperationException();
}
DecrementRecursionCounter();
}
void WriteTypeGenericArguments(IList<DmdType> genericArguments, TypeFlags flags) => WriteGenericArguments(genericArguments, flags & ~(TypeFlags.ShortSpecialNames | TypeFlags.NoDeclaringTypeNames));
void WriteMethodGenericArguments(IList<DmdType> genericArguments, TypeFlags flags) => WriteGenericArguments(genericArguments, flags | TypeFlags.MethodGenericArgumentType);
void WriteGenericArguments(IList<DmdType> genericArguments, TypeFlags flags) {
if (genericArguments.Count == 0)
return;
writer.Append('[');
for (int i = 0; i < genericArguments.Count; i++) {
if (i > 0) {
// No whitespace is added
writer.Append(',');
}
if ((globalFlags & GlobalFlags.Serializable) != 0)
writer.Append('[');
var gaType = genericArguments[i];
Write(gaType, flags);
if ((globalFlags & GlobalFlags.Serializable) != 0) {
writer.Append(", ");
WriteAssemblyFullName(writer, gaType);
writer.Append(']');
}
}
writer.Append(']');
}
void WriteParameters(IList<DmdType> parameters, TypeFlags flags) {
for (int i = 0; i < parameters.Count; i++) {
if (i > 0)
writer.Append(", ");
int origLen = writer.Length;
var type = parameters[i];
FormatTypeName(type, flags);
if (type.IsByRef && (globalFlags & GlobalFlags.Serializable) == 0) {
while (writer.Length > origLen && writer[writer.Length - 1] == '&')
writer.Length--;
writer.Append(" ByRef");
}
}
}
void Write(DmdFieldInfo field) {
FormatTypeName(field.FieldType, GetTypeFlags(true) | TypeFlags.FnPtrIsIntPtr);
writer.Append(' ');
writer.Append(field.Name);
}
void Write(DmdMethodBase method) => WriteMethod(method.Name, method.GetMethodSignature(), GetGenericArguments(method), isMethod: true);
void Write(DmdPropertyInfo property) => WriteMethod(property.Name, property.GetMethodSignature(), genericArguments: null, isMethod: false);
void Write(DmdMethodSignature methodSignature) => WriteMethod(null, methodSignature, genericArguments: null, isMethod: true);
void WriteMethod(string? name, DmdMethodSignature sig, IList<DmdType>? genericArguments, bool isMethod) {
var flags = GetTypeFlags(true) | TypeFlags.FnPtrIsIntPtr;
FormatTypeName(sig.ReturnType, flags);
writer.Append(' ');
writer.Append(name);
if (genericArguments is not null)
WriteMethodGenericArguments(genericArguments, flags);
if (isMethod || sig.GetParameterTypes().Count != 0 || sig.GetVarArgsParameterTypes().Count != 0) {
if (!isMethod)
writer.Append(' ');
writer.Append(isMethod ? '(' : '[');
WriteParameters(sig.GetParameterTypes(), flags);
if ((sig.Flags & DmdSignatureCallingConvention.Mask) == DmdSignatureCallingConvention.VarArg) {
if (sig.GetParameterTypes().Count > 0)
writer.Append(", ");
writer.Append("...");
}
writer.Append(isMethod ? ')' : ']');
}
}
void FormatTypeName(DmdType type, TypeFlags flags) {
if ((globalFlags & GlobalFlags.Serializable) != 0)
Write(type, flags);
else {
var rootType = type;
while (rootType.GetElementType() is DmdType elementType)
rootType = elementType;
if (rootType.IsNested)
WriteName(type, flags);
else
Write(type, flags | TypeFlags.ShortSpecialNames);
}
}
void Write(DmdEventInfo @event) {
FormatTypeName(@event.EventHandlerType, GetTypeFlags(true) | TypeFlags.FnPtrIsIntPtr);
writer.Append(' ');
writer.Append(@event.Name);
}
void Write(DmdParameterInfo parameter) {
FormatTypeName(parameter.ParameterType, GetTypeFlags(true) | TypeFlags.FnPtrIsIntPtr);
writer.Append(' ');
writer.Append(parameter.Name);
}
void WriteName(DmdType? type, TypeFlags flags) {
if (type is null) {
writer.Append("???");
return;
}
if (!IncrementRecursionCounter()) {
writer.Append("???");
return;
}
switch (type.TypeSignatureKind) {
case DmdTypeSignatureKind.Type:
WriteIdentifier(type.MetadataName);
break;
case DmdTypeSignatureKind.Pointer:
WriteName(type.GetElementType(), flags);
writer.Append('*');
break;
case DmdTypeSignatureKind.ByRef:
WriteName(type.GetElementType(), flags);
writer.Append('&');
break;
case DmdTypeSignatureKind.TypeGenericParameter:
case DmdTypeSignatureKind.MethodGenericParameter:
WriteIdentifier(type.MetadataName);
break;
case DmdTypeSignatureKind.SZArray:
WriteName(type.GetElementType(), flags);
writer.Append("[]");
break;
case DmdTypeSignatureKind.MDArray:
WriteName(type.GetElementType(), flags);
writer.Append('[');
var rank = type.GetArrayRank();
if (rank <= 0)
writer.Append("???");
else if (rank == 1)
writer.Append('*');
else
writer.Append(',', rank - 1);
writer.Append(']');
break;
case DmdTypeSignatureKind.GenericInstance:
WriteName(GetGenericTypeDefinition(type), flags);
break;
case DmdTypeSignatureKind.FunctionPointer:
if ((flags & TypeFlags.FnPtrIsIntPtr) != 0)
WriteName(type.AppDomain.System_IntPtr, flags);
else
writer.Append("(fnptr)");
break;
default: throw new InvalidOperationException();
}
DecrementRecursionCounter();
}
}
}