File: Debugger\Engine\DkmClrType.cs
Web Access
Project: src\src\ExpressionEvaluator\Core\Test\ResultProvider\Microsoft.CodeAnalysis.ResultProvider.Utilities.csproj (Microsoft.CodeAnalysis.ExpressionEvaluator.ResultProvider.Utilities)
// 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
 
#region Assembly Microsoft.VisualStudio.Debugger.Engine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// References\Debugger\v2.0\Microsoft.VisualStudio.Debugger.Engine.dll
 
#endregion
 
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Microsoft.VisualStudio.Debugger.Evaluation;
using Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation;
using Microsoft.VisualStudio.Debugger.Metadata;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.Debugger.Clr
{
    [DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
    public class DkmClrType
    {
        /// <summary>
        /// We would accept inherited members for tests purposes comparing to <see cref="TypeHelpers.MemberBindingFlags"/> 
        /// because an actual VS <see cref="GetEvalAttributes(DkmClrAppDomain, Type)"/> may return attributes from base types.
        /// Therefore, we do not check here for <see cref="BindingFlags.DeclaredOnly"/>.
        /// </summary>
        private const BindingFlags MemberBindingFlags = BindingFlags.Public |
                                                         BindingFlags.NonPublic |
                                                         BindingFlags.Instance |
                                                         BindingFlags.Static;
        private readonly Type _lmrType;
        private readonly System.Lazy<ReadOnlyCollection<DkmClrEvalAttribute>> _lazyEvalAttributes;
        private readonly DkmClrObjectFavoritesInfo _favorites;
        private ReadOnlyCollection<DkmClrType> _lazyGenericArguments;
 
        internal DkmClrType(DkmClrModuleInstance module, DkmClrAppDomain appDomain, Type lmrType, DkmClrObjectFavoritesInfo favorites = null)
        {
            ModuleInstance = module;
            AppDomain = appDomain;
            _lmrType = lmrType;
            _lazyEvalAttributes = new System.Lazy<ReadOnlyCollection<DkmClrEvalAttribute>>(
                () => GetEvalAttributes(appDomain, lmrType),
                LazyThreadSafetyMode.PublicationOnly);
            _favorites = favorites;
        }
 
        internal DkmClrType(Type lmrType) :
            this(DkmClrRuntimeInstance.DefaultRuntime, lmrType)
        {
        }
 
        internal DkmClrType(DkmClrRuntimeInstance runtime, Type lmrType) :
            this(runtime.DefaultModule, runtime.DefaultAppDomain, lmrType)
        {
        }
 
        public DkmClrAppDomain AppDomain { get; }
 
        public DkmClrType ElementType
        {
            get
            {
                var elementType = _lmrType.GetElementType();
                return (elementType == null) ? null : Create(AppDomain, elementType);
            }
        }
 
        internal System.Type UnderlyingType
        {
            get { return ((TypeImpl)_lmrType).Type; }
        }
 
        internal DkmClrType MakeGenericType(params DkmClrType[] genericArguments)
        {
            var type = new DkmClrType(
                ModuleInstance,
                AppDomain,
                _lmrType.MakeGenericType(genericArguments.Select(t => t._lmrType).ToArray()),
                _favorites);
            type._lazyGenericArguments = new ReadOnlyCollection<DkmClrType>(genericArguments);
            return type;
        }
 
        internal DkmClrType MakeArrayType()
        {
            return new DkmClrType(
                ModuleInstance,
                AppDomain,
                _lmrType.MakeArrayType());
        }
 
        internal DkmClrValue Instantiate(params object[] args)
        {
            return Instantiate(args, null, DkmEvaluationResultFlags.None);
        }
 
        internal DkmClrValue Instantiate(
            object[] args,
            string alias,
            DkmEvaluationResultFlags evalFlags)
        {
            object value = UnderlyingType.Instantiate(args);
            return new DkmClrValue(
                value,
                DkmClrValue.GetHostObjectValue(_lmrType, value),
                this,
                alias: alias,
                evalFlags: evalFlags,
                valueFlags: DkmClrValueFlags.None,
                nativeComPointer: 0);
        }
 
        private static readonly ReadOnlyCollection<DkmClrType> s_emptyTypes = new ReadOnlyCollection<DkmClrType>(new DkmClrType[0]);
 
        public ReadOnlyCollection<DkmClrType> GenericArguments
        {
            get
            {
                if (_lazyGenericArguments == null)
                {
                    var typeArgs = _lmrType.GetGenericArguments();
                    var genericArgs = (typeArgs.Length == 0)
                        ? s_emptyTypes
                        : new ReadOnlyCollection<DkmClrType>(typeArgs.Select(t => DkmClrType.Create(AppDomain, t)).ToArray());
                    Interlocked.CompareExchange(ref _lazyGenericArguments, genericArgs, null);
                }
                return _lazyGenericArguments;
            }
        }
 
        public virtual Type GetLmrType()
        {
            return _lmrType;
        }
 
        public ReadOnlyCollection<DkmClrEvalAttribute> GetEvalAttributes()
        {
            return _lazyEvalAttributes.Value;
        }
 
        public DkmClrModuleInstance ModuleInstance { get; }
 
        public DkmClrRuntimeInstance RuntimeInstance
        {
            get { return ModuleInstance.RuntimeInstance; }
        }
 
        private string GetDebuggerDisplay()
        {
            var result = _lmrType.ToString();
            var proxyAttribute = _lazyEvalAttributes.Value.OfType<DkmClrDebuggerTypeProxyAttribute>().FirstOrDefault();
            result = proxyAttribute != null
                ? string.Format("{0} (Proxy = {1})", result, proxyAttribute.ProxyType.GetLmrType().ToString())
                : result;
            return result;
        }
 
        public static DkmClrType Create(DkmClrAppDomain appDomain, Type type)
        {
            return new DkmClrType(appDomain.RuntimeInstance.DefaultModule, appDomain, type);
        }
 
        private static System.Type GetProxyType(System.Type type)
        {
            var attribute = (DebuggerTypeProxyAttribute)type.GetCustomAttributes(typeof(DebuggerTypeProxyAttribute), inherit: false).FirstOrDefault();
            if (attribute == null)
            {
                return null;
            }
 
            // Assume the proxy type is from the same assembly
            // and strip off the assembly qualifier since that won't
            // resolve to explicitly loaded assemblies.
            var proxyName = attribute.ProxyTypeName;
            int separator = proxyName.IndexOf(',');
            if (separator >= 0)
            {
                proxyName = proxyName.Substring(0, separator);
            }
 
            var assembly = type.Assembly;
            return assembly.GetType(proxyName);
        }
 
        private static ReadOnlyCollection<DkmClrEvalAttribute> GetEvalAttributes(DkmClrAppDomain appDomain, Type type)
        {
            var reflectionType = ((TypeImpl)type).Type;
            return appDomain.TypeToEvalAttributesMap.GetOrAdd(
                reflectionType,
                static (k, a) => getEvalAttributesCore(k, a),
                type);
 
            static ReadOnlyCollection<DkmClrEvalAttribute> getEvalAttributesCore(System.Type reflectionType, Type type)
            {
                var attributes = ArrayBuilder<DkmClrEvalAttribute>.GetInstance();
 
                var proxyType = GetProxyType(reflectionType);
                if (proxyType != null)
                {
                    attributes.Add(new DkmClrDebuggerTypeProxyAttribute(new DkmClrType((TypeImpl)proxyType)));
                }
 
                var members = type.GetMembers(MemberBindingFlags).Where(TypeHelpers.IsVisibleMember);
                foreach (var member in members)
                {
                    foreach (var attribute in GetBrowsableAttributes(type, member))
                    {
                        attributes.Add(attribute);
                    }
                }
 
                var debuggerDisplay = GetDebuggerDisplayAttribute(reflectionType);
                if (debuggerDisplay != null)
                {
                    attributes.Add(debuggerDisplay);
                }
 
                var debuggerVisualizers = GetDebuggerVisualizerAttributes(reflectionType);
                if (debuggerVisualizers != null)
                {
                    attributes.AddRange(debuggerVisualizers);
                }
 
                return attributes.ToImmutableAndFree();
            }
        }
 
        private static ReadOnlyCollection<DkmClrDebuggerBrowsableAttribute> GetBrowsableAttributes(Type type, MemberInfo member)
        {
            var attributes = ArrayBuilder<DkmClrDebuggerBrowsableAttribute>.GetInstance();
            foreach (var attribute in member.GetCustomAttributesData())
            {
                var data = ((CustomAttributeDataImpl)attribute).CustomAttributeData;
                if (data.AttributeType == typeof(DebuggerBrowsableAttribute))
                {
                    var state = (DebuggerBrowsableState)data.ConstructorArguments[0].Value;
                    attributes.Add(new DkmClrDebuggerBrowsableAttribute(member.Name, ConvertBrowsableState(state)));
                }
            }
 
            return attributes.ToImmutableAndFree();
        }
 
        private static DkmClrDebuggerBrowsableAttributeState ConvertBrowsableState(DebuggerBrowsableState state)
        {
            switch (state)
            {
                case DebuggerBrowsableState.Never:
                    return DkmClrDebuggerBrowsableAttributeState.Never;
                case DebuggerBrowsableState.Collapsed:
                    return DkmClrDebuggerBrowsableAttributeState.Collapsed;
                case DebuggerBrowsableState.RootHidden:
                    return DkmClrDebuggerBrowsableAttributeState.RootHidden;
                default:
                    throw ExceptionUtilities.UnexpectedValue(state);
            }
        }
 
        private static DkmClrDebuggerDisplayAttribute GetDebuggerDisplayAttribute(System.Type type)
        {
            var attributeData = type.GetCustomAttributesData().FirstOrDefault(data => data.AttributeType == typeof(DebuggerDisplayAttribute));
            if (attributeData == null)
            {
                return null;
            }
 
            return new DkmClrDebuggerDisplayAttribute(type.AssemblyQualifiedName)
            {
                Name = (string)attributeData.NamedArguments.SingleOrDefault(arg => arg.MemberName == "Name").TypedValue.Value,
                Value = (string)attributeData.ConstructorArguments.Single().Value,
                TypeName = (string)attributeData.NamedArguments.SingleOrDefault(arg => arg.MemberName == "Type").TypedValue.Value,
            };
        }
 
        private static DkmClrDebuggerVisualizerAttribute[] GetDebuggerVisualizerAttributes(System.Type type)
        {
            var attributesData = type.GetCustomAttributesData().Where(data => data.AttributeType == typeof(DebuggerVisualizerAttribute));
            if (attributesData.Count() == 0)
            {
                return null;
            }
 
            var builder = ArrayBuilder<DkmClrDebuggerVisualizerAttribute>.GetInstance();
 
            foreach (var attributeData in attributesData)
            {
                var argValueTypeBuilder = ArrayBuilder<System.Type>.GetInstance();
                foreach (var typedArg in attributeData.ConstructorArguments)
                {
                    var argumentType = typedArg.ArgumentType.FullName;
 
                    System.Type argValueType = null;
 
                    if (string.Equals(argumentType, "System.String", System.StringComparison.Ordinal))
                    {
                        var typeName = (string)typedArg.Value;
                        var assembly = type.Assembly;
                        argValueType = assembly.GetType(typeName);
                    }
                    else if (string.Equals(argumentType, "System.Type", System.StringComparison.Ordinal))
                    {
                        argValueType = typedArg.Value as System.Type;
                    }
 
                    if (argValueType != null)
                    {
                        argValueTypeBuilder.Add(argValueType);
                    }
                    else
                    {
                        Debug.Fail("Failed to resolve the type of the arguments for DebuggerVisualizer attribute.");
                        return null;
                    }
                }
 
                // Attribute not recognized.
                if (argValueTypeBuilder.Count == 0)
                {
                    Debug.Fail("Failed to retrieve the visualizer types from a [DebuggerVisualizer] attribute");
                    return null;
                }
 
                string uiSideVisualizerTypeName = argValueTypeBuilder[0].FullName;
                string uiSideVisualizerAssemblyName = argValueTypeBuilder[0].Assembly.FullName;
                string debuggeeSideVisualizerTypeName;
                string debuggeeSideVisualizerAssemblyName;
 
                if (argValueTypeBuilder.Count > 1)
                {
                    System.Type debuggeeSideType = argValueTypeBuilder[1];
                    debuggeeSideVisualizerTypeName = debuggeeSideType.FullName;
                    debuggeeSideVisualizerAssemblyName = debuggeeSideType.Assembly.FullName;
                }
                else
                {
                    debuggeeSideVisualizerTypeName = "Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource";
                    var vsVersion = System.Environment.GetEnvironmentVariable("VisualStudioVersion") ?? "14.0";
                    debuggeeSideVisualizerAssemblyName = $"Microsoft.VisualStudio.DebuggerVisualizers, Version={vsVersion}.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
                }
 
                string visualizerDescription = uiSideVisualizerTypeName;
 
                argValueTypeBuilder.Free();
 
                // Try to get the Visualizer Description. If no description is specified, we will fall back to the Visualizer Type Name.
                foreach (var namedArg in attributeData.NamedArguments)
                {
                    if (namedArg.MemberInfo.Name == "Description")
                    {
                        visualizerDescription = (string)namedArg.TypedValue.Value;
                        break;
                    }
                }
 
                builder.Add(new DkmClrDebuggerVisualizerAttribute(
                    targetMember: null,
                    uiSideVisualizerTypeName: uiSideVisualizerTypeName,
                    uiSideVisualizerAssemblyName: uiSideVisualizerAssemblyName,
                    uiSideVisualizerAssemblyLocation: Evaluation.DkmClrCustomVisualizerAssemblyLocation.Unknown,
                    debuggeeSideVisualizerTypeName: debuggeeSideVisualizerTypeName,
                    debuggeeSideVisualizerAssemblyName: debuggeeSideVisualizerAssemblyName,
                    visualizerDescription: visualizerDescription,
                    extensionPartId: System.Guid.Empty));
            }
 
            return builder.ToArrayAndFree();
        }
 
        public DkmClrObjectFavoritesInfo GetFavorites() => _favorites;
    }
}