| File: Hosting\ObjectFormatter\ObjectFormatterHelpers.cs | Web Access |
| Project: src\roslyn\src\Scripting\Core\Microsoft.CodeAnalysis.Scripting.csproj (Microsoft.CodeAnalysis.Scripting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Scripting.Hosting { using TypeInfo = System.Reflection.TypeInfo; internal static class ObjectFormatterHelpers { internal static readonly object VoidValue = new object(); internal const int NumberRadixDecimal = 10; internal const int NumberRadixHexadecimal = 16; internal static bool HasOverriddenToString(TypeInfo type) { if (type.IsInterface) { return false; } while (type.AsType() != typeof(object)) { if (type.GetDeclaredMethod("ToString", Type.EmptyTypes) != null) { return true; } type = type.BaseType.GetTypeInfo(); } return false; } internal static DebuggerDisplayAttribute GetApplicableDebuggerDisplayAttribute(MemberInfo member) { // Includes inherited attributes. The debugger uses the first attribute if multiple are applied. var result = member.GetCustomAttributes<DebuggerDisplayAttribute>().FirstOrDefault(); if (result != null) { return result; } // TODO (tomat): which assembly should we look at for dd attributes? if (member is TypeInfo type) { foreach (DebuggerDisplayAttribute attr in type.Assembly.GetCustomAttributes<DebuggerDisplayAttribute>()) { if (IsApplicableAttribute(type, attr.Target.GetTypeInfo(), attr.TargetTypeName)) { return attr; } } } return null; } private static DebuggerTypeProxyAttribute GetApplicableDebuggerTypeProxyAttribute(TypeInfo type) { // includes inherited attributes. The debugger uses the first attribute if multiple are applied. var result = type.GetCustomAttributes<DebuggerTypeProxyAttribute>().FirstOrDefault(); if (result != null) { return result; } // TODO (tomat): which assembly should we look at for proxy attributes? foreach (DebuggerTypeProxyAttribute attr in type.Assembly.GetCustomAttributes<DebuggerTypeProxyAttribute>()) { if (IsApplicableAttribute(type, attr.Target.GetTypeInfo(), attr.TargetTypeName)) { return attr; } } return null; } private static bool IsApplicableAttribute(TypeInfo type, TypeInfo targetType, string targetTypeName) { return type != null && AreEquivalent(targetType, type) || targetTypeName != null && type.FullName == targetTypeName; } private static bool AreEquivalent(TypeInfo type, TypeInfo other) { // TODO: Unify NoPIA interfaces // https://github.com/dotnet/corefx/issues/2101 return type.Equals(other); } internal static object GetDebuggerTypeProxy(object obj) { // use proxy type if defined: var type = obj.GetType().GetTypeInfo(); var debuggerTypeProxy = GetApplicableDebuggerTypeProxyAttribute(type); if (debuggerTypeProxy != null) { try { var proxyType = Type.GetType(debuggerTypeProxy.ProxyTypeName, throwOnError: false, ignoreCase: false); if (proxyType != null) { if (proxyType.GetTypeInfo().IsGenericTypeDefinition) { proxyType = proxyType.MakeGenericType(type.GenericTypeArguments); } return Activator.CreateInstance(proxyType, [obj]); } } catch (Exception) { // no-op, ignore proxy if it is implemented incorrectly or can't be loaded } } return null; } internal static MemberInfo ResolveMember(object obj, string memberName, bool callableOnly) { TypeInfo type = obj.GetType().GetTypeInfo(); // case-sensitive: TypeInfo currentType = type; while (true) { if (!callableOnly) { var field = currentType.GetDeclaredField(memberName); if (field != null) { return field; } var property = currentType.GetDeclaredProperty(memberName); if (property != null) { var getter = property.GetMethod; if (getter != null) { return getter; } } } var method = currentType.GetDeclaredMethod(memberName, Type.EmptyTypes); if (method != null) { return method; } if (currentType.BaseType == null) { break; } currentType = currentType.BaseType.GetTypeInfo(); } // case-insensitive: currentType = type; while (true) { IEnumerable<MemberInfo> members; if (callableOnly) { members = type.DeclaredMethods; } else { members = ((IEnumerable<MemberInfo>)type.DeclaredFields).Concat(type.DeclaredProperties); } MemberInfo candidate = null; foreach (var member in members) { if (StringComparer.OrdinalIgnoreCase.Equals(memberName, member.Name)) { if (candidate != null) { return null; } MethodInfo method; if (member is FieldInfo) { candidate = member; } else if ((method = member as MethodInfo) != null) { if (method.GetParameters().Length == 0) { candidate = member; } } else { var getter = ((PropertyInfo)member).GetMethod; if (getter?.GetParameters().Length == 0) { candidate = member; } } } } if (candidate != null) { return candidate; } if (currentType.BaseType == null) { break; } currentType = currentType.BaseType.GetTypeInfo(); } return null; } internal static object GetMemberValue(MemberInfo member, object obj, out Exception exception) { exception = null; try { FieldInfo field; MethodInfo method; if ((field = member as FieldInfo) != null) { return field.GetValue(obj); } if ((method = member as MethodInfo) != null) { return (method.ReturnType == typeof(void)) ? VoidValue : method.Invoke(obj, Array.Empty<object>()); } var property = (PropertyInfo)member; if (property.GetMethod == null) { return null; } return property.GetValue(obj, Array.Empty<object>()); } catch (TargetInvocationException e) { exception = e.InnerException; } return null; } internal static SpecialType GetPrimitiveSpecialType(Type type) { Debug.Assert(type != null); if (type == typeof(int)) { return SpecialType.System_Int32; } if (type == typeof(string)) { return SpecialType.System_String; } if (type == typeof(bool)) { return SpecialType.System_Boolean; } if (type == typeof(char)) { return SpecialType.System_Char; } if (type == typeof(long)) { return SpecialType.System_Int64; } if (type == typeof(double)) { return SpecialType.System_Double; } if (type == typeof(byte)) { return SpecialType.System_Byte; } if (type == typeof(decimal)) { return SpecialType.System_Decimal; } if (type == typeof(uint)) { return SpecialType.System_UInt32; } if (type == typeof(ulong)) { return SpecialType.System_UInt64; } if (type == typeof(float)) { return SpecialType.System_Single; } if (type == typeof(short)) { return SpecialType.System_Int16; } if (type == typeof(ushort)) { return SpecialType.System_UInt16; } if (type == typeof(DateTime)) { return SpecialType.System_DateTime; } if (type == typeof(sbyte)) { return SpecialType.System_SByte; } if (type == typeof(object)) { return SpecialType.System_Object; } if (type == typeof(void)) { return SpecialType.System_Void; } return SpecialType.None; } internal static ObjectDisplayOptions GetObjectDisplayOptions(bool useQuotes = false, bool escapeNonPrintable = false, bool includeCodePoints = false, int numberRadix = NumberRadixDecimal) { var options = ObjectDisplayOptions.None; if (useQuotes) { options |= ObjectDisplayOptions.UseQuotes; } if (escapeNonPrintable) { options |= ObjectDisplayOptions.EscapeNonPrintableCharacters; } if (includeCodePoints) { options |= ObjectDisplayOptions.IncludeCodePoints; } switch (numberRadix) { case NumberRadixDecimal: break; case NumberRadixHexadecimal: options |= ObjectDisplayOptions.UseHexadecimalNumbers; break; default: // If we ever support a radix other than decimal or hex, we'll // need to propagate the numeric (vs boolean) option down to // ObjectDisplay. throw new ArgumentNullException(nameof(numberRadix)); } return options; } // Parses // <clr-member-name> // <clr-member-name> ',' 'nq' // <clr-member-name> '(' ')' // <clr-member-name> '(' ')' ',' 'nq' internal static string ParseSimpleMemberName(string str, int start, int end, out bool noQuotes, out bool isCallable) { Debug.Assert(str != null && start >= 0 && end >= start); isCallable = false; noQuotes = false; // no-quotes suffix: if (end - 3 >= start && str[end - 2] == 'n' && str[end - 1] == 'q') { int j = end - 3; while (j >= start && Char.IsWhiteSpace(str[j])) { j--; } if (j >= start && str[j] == ',') { noQuotes = true; end = j; } } int i = end - 1; EatTrailingWhiteSpace(str, start, ref i); if (i > start && str[i] == ')') { int closingParen = i; i--; EatTrailingWhiteSpace(str, start, ref i); if (str[i] != '(') { i = closingParen; } else { i--; EatTrailingWhiteSpace(str, start, ref i); isCallable = true; } } EatLeadingWhiteSpace(str, ref start, i); return str.Substring(start, i - start + 1); } private static void EatTrailingWhiteSpace(string str, int start, ref int i) { while (i >= start && Char.IsWhiteSpace(str[i])) { i--; } } private static void EatLeadingWhiteSpace(string str, ref int i, int end) { while (i < end && Char.IsWhiteSpace(str[i])) { i++; } } } }