| File: DebuggerAttributes.cs | Web Access |
| Project: src\winforms\src\Common\tests\TestUtilities\System.Private.Windows.Core.TestUtilities.csproj (System.Private.Windows.Core.TestUtilities) |
// 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; } }