|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System.Reflection;
using System.Text;
namespace System.Diagnostics;
internal sealed class DebuggerAttributeInfo
{
public object Instance { get; set; }
public IEnumerable<PropertyInfo> Properties { get; set; }
}
internal static class DebuggerAttributes
{
internal static object GetFieldValue(object obj, string fieldName)
{
return GetField(obj, fieldName).GetValue(obj);
}
internal static void InvokeDebuggerTypeProxyProperties(object obj)
{
DebuggerAttributeInfo info = ValidateDebuggerTypeProxyProperties(obj.GetType(), obj);
foreach (PropertyInfo pi in info.Properties)
{
pi.GetValue(info.Instance, null);
}
}
internal static DebuggerAttributeInfo ValidateDebuggerTypeProxyProperties(object obj)
{
return ValidateDebuggerTypeProxyProperties(obj.GetType(), obj);
}
internal static DebuggerAttributeInfo ValidateDebuggerTypeProxyProperties(Type type, object obj)
{
return ValidateDebuggerTypeProxyProperties(type, type.GenericTypeArguments, obj);
}
internal static DebuggerAttributeInfo ValidateDebuggerTypeProxyProperties(Type type, Type[] genericTypeArguments, object obj)
{
Type proxyType = GetProxyType(type, genericTypeArguments);
// Create an instance of the proxy type, and make sure we can access all of the instance properties
// on the type without exception
object proxyInstance = Activator.CreateInstance(proxyType, obj);
IEnumerable<PropertyInfo> properties = GetDebuggerVisibleProperties(proxyType);
return new DebuggerAttributeInfo
{
Instance = proxyInstance,
Properties = properties
};
}
public static DebuggerBrowsableState? GetDebuggerBrowsableState(MemberInfo info)
{
CustomAttributeData debuggerBrowsableAttribute = info.CustomAttributes
.SingleOrDefault(a => a.AttributeType == typeof(DebuggerBrowsableAttribute));
// Enums in attribute constructors are boxed as ints, so cast to int? first.
return (DebuggerBrowsableState?)(int?)debuggerBrowsableAttribute?.ConstructorArguments.Single().Value;
}
public static IEnumerable<FieldInfo> GetDebuggerVisibleFields(Type debuggerAttributeType)
{
// The debugger doesn't evaluate non-public members of type proxies.
IEnumerable<FieldInfo> visibleFields = debuggerAttributeType.GetFields()
.Where(fi => fi.IsPublic && GetDebuggerBrowsableState(fi) != DebuggerBrowsableState.Never);
return visibleFields;
}
public static IEnumerable<PropertyInfo> GetDebuggerVisibleProperties(Type debuggerAttributeType)
{
// The debugger doesn't evaluate non-public members of type proxies. GetGetMethod returns null if the getter is non-public.
IEnumerable<PropertyInfo> visibleProperties = debuggerAttributeType.GetProperties()
.Where(pi => pi.GetGetMethod() is not null && GetDebuggerBrowsableState(pi) != DebuggerBrowsableState.Never);
return visibleProperties;
}
public static object GetProxyObject(object obj) => Activator.CreateInstance(GetProxyType(obj), obj);
public static Type GetProxyType(object obj) => GetProxyType(obj.GetType());
public static Type GetProxyType(Type type) => GetProxyType(type, type.GenericTypeArguments);
private static Type GetProxyType(Type type, Type[] genericTypeArguments)
{
// Get the DebuggerTypeProxyAttribute for obj
CustomAttributeData[] attrs =
type.GetTypeInfo().CustomAttributes
.Where(a => a.AttributeType == typeof(DebuggerTypeProxyAttribute))
.ToArray();
if (attrs.Length != 1)
{
throw new InvalidOperationException($"Expected one DebuggerTypeProxyAttribute on {type}.");
}
CustomAttributeData cad = attrs[0];
Type proxyType = cad.ConstructorArguments[0].ArgumentType == typeof(Type) ?
(Type)cad.ConstructorArguments[0].Value :
Type.GetType((string)cad.ConstructorArguments[0].Value);
if (genericTypeArguments.Length > 0)
{
proxyType = proxyType.MakeGenericType(genericTypeArguments);
}
return proxyType;
}
internal static string ValidateDebuggerDisplayReferences(object obj)
{
// Get the DebuggerDisplayAttribute for obj
Type objType = obj.GetType();
CustomAttributeData[] attrs =
objType.GetTypeInfo().CustomAttributes
.Where(a => a.AttributeType == typeof(DebuggerDisplayAttribute))
.ToArray();
if (attrs.Length != 1)
{
throw new InvalidOperationException($"Expected one DebuggerDisplayAttribute on {objType}.");
}
CustomAttributeData cad = attrs[0];
// Get the text of the DebuggerDisplayAttribute
string attrText = (string)cad.ConstructorArguments[0].Value;
string[] segments = attrText.Split(['{', '}']);
if (segments.Length % 2 == 0)
{
throw new InvalidOperationException($"The DebuggerDisplayAttribute for {objType} lacks a closing brace.");
}
if (segments.Length == 1)
{
throw new InvalidOperationException($"The DebuggerDisplayAttribute for {objType} doesn't reference any expressions.");
}
StringBuilder sb = new();
for (int i = 0; i < segments.Length; i += 2)
{
string literal = segments[i];
sb.Append(literal);
if (i + 1 < segments.Length)
{
string reference = segments[i + 1];
bool noQuotes = reference.EndsWith(",nq", StringComparison.Ordinal);
reference = reference.Replace(",nq", string.Empty);
// Evaluate the reference.
if (!TryEvaluateReference(obj, reference, out object member))
{
throw new InvalidOperationException($"The DebuggerDisplayAttribute for {objType} contains the expression \"{reference}\".");
}
string memberString = GetDebuggerMemberString(member, noQuotes);
sb.Append(memberString);
}
}
return sb.ToString();
}
private static string GetDebuggerMemberString(object member, bool noQuotes)
{
return member switch
{
null => "null",
byte or sbyte or short or ushort or int or uint or long or ulong or float or double => member.ToString(),
string when noQuotes => member.ToString(),
string => $"\"{member}\"",
_ => $"{{{member}}}"
};
}
private static bool TryEvaluateReference(object obj, string reference, out object member)
{
if (GetProperty(obj, reference) is { } property)
{
member = property.GetValue(obj);
return true;
}
if (GetField(obj, reference) is { } field)
{
member = field.GetValue(obj);
return true;
}
member = null;
return false;
}
private static FieldInfo GetField(object obj, string fieldName)
{
for (Type t = obj.GetType(); t is not null; t = t.GetTypeInfo().BaseType)
{
if (t.GetTypeInfo().GetDeclaredField(fieldName) is { } field)
{
return field;
}
}
return null;
}
private static PropertyInfo GetProperty(object obj, string propertyName)
{
for (Type t = obj.GetType(); t is not null; t = t.GetTypeInfo().BaseType)
{
if (t.GetTypeInfo().GetDeclaredProperty(propertyName) is { } property)
{
return property;
}
}
return null;
}
}
|