File: Metadata\WinMdDumpTest.cs
Web Access
Project: src\src\Compilers\CSharp\Test\WinRT\Microsoft.CodeAnalysis.CSharp.WinRT.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.WinRT.UnitTests)
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols.Metadata
{
    public class WinMdDumpTest : CSharpTestBase
    {
        private readonly MetadataReference _windowsRef = MetadataReference.CreateFromImage(TestResources.WinRt.Windows.AsImmutableOrNull());
        private readonly MetadataReference _systemRuntimeRef = MetadataReference.CreateFromImage(Net461.Resources.SystemRuntime.AsImmutableOrNull());
        private readonly MetadataReference _systemObjectModelRef = MetadataReference.CreateFromImage(Net461.Resources.SystemObjectModel.AsImmutableOrNull());
        private readonly MetadataReference _windowsRuntimeUIXamlRef = MetadataReference.CreateFromImage(TestResources.NetFX.WinRt.SystemRuntimeWindowsRuntimeUIXaml.AsImmutableOrNull());
        private readonly MetadataReference _interopServicesWindowsRuntimeRef = MetadataReference.CreateFromImage(Net461.Resources.SystemRuntimeInteropServicesWindowsRuntime.AsImmutableOrNull());
 
        private void AppendMembers(StringBuilder result, NamespaceOrTypeSymbol container, string indent)
        {
            string memberIndent;
            if (container is NamedTypeSymbol)
            {
                memberIndent = indent + "  ";
 
                result.Append(indent);
                result.AppendLine("{");
 
                AppendCustomAttributes(result, container, indent, inBlock: true);
 
                if (container.GetAttributes().Length > 0)
                {
                    result.AppendLine();
                }
            }
            else
            {
                memberIndent = indent;
            }
 
            foreach (var member in container.GetMembers().OrderBy(m => m.Name, System.StringComparer.InvariantCulture))
            {
                switch (member.Kind)
                {
                    case SymbolKind.NamedType:
                        var namedType = (PENamedTypeSymbol)member;
                        result.Append(memberIndent);
                        result.Append(".class ");
                        MetadataSignatureHelper.AppendTypeAttributes(result, namedType.Flags);
                        result.Append(' ');
                        result.Append(member);
 
                        if ((object)namedType.BaseType() != null)
                        {
                            result.AppendLine();
                            result.Append(memberIndent);
                            result.Append("       extends ");
                            result.Append(namedType.BaseType());
                        }
 
                        if (namedType.Interfaces().Length > 0)
                        {
                            result.AppendLine();
                            result.Append(memberIndent);
                            result.Append("       implements ");
                            result.Append(string.Join(", ", namedType.Interfaces()));
                        }
 
                        result.AppendLine();
 
                        AppendMembers(result, namedType, memberIndent);
                        break;
 
                    case SymbolKind.Namespace:
                        var ns = member as PENamespaceSymbol;
                        if ((object)ns != null)
                        {
                            AppendMembers(result, ns, indent);
                        }
                        break;
 
                    case SymbolKind.Method:
                        var method = member as PEMethodSymbol;
                        if ((object)method != null && method.AssociatedSymbol == null)
                        {
                            result.Append(memberIndent);
                            result.Append(".method ");
                            AppendMethod(result, method, memberIndent);
 
                            AppendCustomAttributes(result, member, memberIndent, inBlock: false);
                        }
                        break;
 
                    case SymbolKind.Field:
                        var field = (PEFieldSymbol)member;
                        result.Append(memberIndent);
                        result.Append(".field ");
 
                        MetadataSignatureHelper.AppendFieldAttributes(result, field.Flags);
                        result.Append(' ');
 
                        result.Append(field.TypeWithAnnotations);
                        result.Append(' ');
                        result.Append(member.Name);
                        result.AppendLine();
 
                        AppendCustomAttributes(result, member, memberIndent, inBlock: false);
                        break;
 
                    case SymbolKind.Property:
                        var property = (PEPropertySymbol)member;
                        string propertyName;
 
                        result.Append(memberIndent);
                        result.Append(".property ");
 
                        PropertyAttributes propertyAttrs;
                        ((PEModuleSymbol)container.ContainingModule).Module.GetPropertyDefPropsOrThrow(property.Handle, out propertyName, out propertyAttrs);
                        if (MetadataSignatureHelper.AppendPropertyAttributes(result, propertyAttrs))
                        {
                            result.Append(' ');
                        }
 
                        result.Append(property.TypeWithAnnotations);
                        result.Append(' ');
                        result.Append(property.Name);
                        result.AppendLine();
 
                        result.Append(memberIndent);
                        result.AppendLine("{");
 
                        AppendCustomAttributes(result, member, memberIndent, inBlock: true);
 
                        if (property.GetMethod != null)
                        {
                            result.Append(memberIndent);
                            result.Append("  .get ");
                            AppendMethod(result, (PEMethodSymbol)property.GetMethod, memberIndent);
                        }
 
                        if (property.SetMethod != null)
                        {
                            result.Append(memberIndent);
                            result.Append("  .set ");
                            AppendMethod(result, (PEMethodSymbol)property.SetMethod, memberIndent);
                        }
 
                        result.Append(memberIndent);
                        result.AppendLine("}");
                        break;
 
                    case SymbolKind.Event:
                        var evnt = (PEEventSymbol)member;
 
                        result.Append(memberIndent);
                        result.Append(".event ");
 
                        string eventName;
                        EventAttributes eventAttrs;
                        EntityHandle type;
                        ((PEModuleSymbol)container.ContainingModule).Module.GetEventDefPropsOrThrow(evnt.Handle, out eventName, out eventAttrs, out type);
 
                        if (MetadataSignatureHelper.AppendEventAttributes(result, eventAttrs))
                        {
                            result.Append(' ');
                        }
 
                        result.Append(evnt.TypeWithAnnotations);
                        result.Append(' ');
                        result.Append(evnt.Name);
                        result.AppendLine();
 
                        result.Append(memberIndent);
                        result.Append('{');
                        result.AppendLine();
 
                        AppendCustomAttributes(result, member, memberIndent, inBlock: true);
 
                        if (evnt.RemoveMethod != null)
                        {
                            result.Append(memberIndent);
                            result.Append("  .removeon ");
                            AppendMethod(result, (PEMethodSymbol)evnt.RemoveMethod, memberIndent);
                        }
 
                        if (evnt.AddMethod != null)
                        {
                            result.Append(memberIndent);
                            result.Append("  .addon ");
                            AppendMethod(result, (PEMethodSymbol)evnt.AddMethod, memberIndent);
                        }
 
                        result.Append(memberIndent);
                        result.AppendLine("}");
                        break;
                }
            }
 
            if (container is NamedTypeSymbol)
            {
                result.Append(indent);
                result.AppendLine("}");
            }
        }
 
        private static void AppendCustomAttributes(StringBuilder result, Symbol symbol, string indent, bool inBlock)
        {
            var attributes = symbol.GetAttributes();
            if (attributes.Length == 0)
            {
                return;
            }
 
            if (!inBlock)
            {
                result.Append(indent);
                result.AppendLine("{");
            }
 
            string memberIndent = indent + "  ";
 
            foreach (var attribute in attributes)
            {
                result.Append(memberIndent);
                result.Append(".custom ");
                if (attribute.AttributeConstructor == null)
                {
                    result.Append("[Missing: ");
                    result.Append(attribute.AttributeClass);
                    result.Append(']');
                }
                else
                {
                    AppendMethod(result, (PEMethodSymbol)attribute.AttributeConstructor, indent: null, includeTypeName: true);
                }
 
                result.Append(" = (");
 
                int i = 0;
                foreach (var arg in attribute.ConstructorArguments)
                {
                    if (i > 0)
                    {
                        result.Append(", ");
                    }
 
                    AppendConstant(result, arg);
                    i++;
                }
 
                foreach (var arg in attribute.NamedArguments)
                {
                    if (i > 0)
                    {
                        result.Append(", ");
                    }
 
                    result.Append(arg.Key);
                    result.Append(" = ");
                    AppendConstant(result, arg.Value);
                    i++;
                }
 
                result.Append(')');
                result.AppendLine();
            }
 
            if (!inBlock)
            {
                result.Append(indent);
                result.AppendLine("}");
            }
        }
 
        private static void AppendConstant(StringBuilder result, TypedConstant constant)
        {
            switch (constant.Kind)
            {
                case TypedConstantKind.Array:
                    result.Append('{');
                    int i = 0;
 
                    foreach (var item in constant.Values)
                    {
                        if (i > 0)
                        {
                            result.Append(", ");
                        }
 
                        AppendConstant(result, item);
                    }
 
                    result.Append('}');
                    break;
 
                case TypedConstantKind.Type:
                    result.Append("typeof(");
                    result.Append(constant.Value);
                    result.Append(')');
                    break;
 
                case TypedConstantKind.Enum:
                case TypedConstantKind.Primitive:
                    var value = constant.Value;
                    if (value.GetType() == typeof(string))
                    {
                        result.Append('"');
                        result.Append(value);
                        result.Append('"');
                    }
                    else if (value.GetType() == typeof(bool))
                    {
                        result.Append(value.ToString().ToLower());
                    }
                    else
                    {
                        result.Append(value);
                    }
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(constant.Kind);
            }
        }
 
        private static void AppendMethod(StringBuilder result, PEMethodSymbol method, string indent, bool includeTypeName = false)
        {
            MetadataSignatureHelper.AppendMethodAttributes(result, method.Flags);
            result.Append(' ');
            AppendSignatureType(result, method.ReturnType, RefKind.None);
            result.Append(' ');
 
            if (includeTypeName)
            {
                result.Append(method.ContainingType);
                result.Append("::");
            }
 
            result.Append(method.Name);
 
            result.Append('(');
 
            bool hasParameterAttributes = false;
            int i = 0;
            foreach (PEParameterSymbol parameter in method.Parameters)
            {
                if (i > 0)
                {
                    result.Append(", ");
                }
 
                if (parameter.GetAttributes().Length > 0)
                {
                    hasParameterAttributes = true;
                }
 
                if (MetadataSignatureHelper.AppendParameterAttributes(result, parameter.Flags, all: true))
                {
                    result.Append(' ');
                }
 
                AppendSignatureType(result, parameter.Type, parameter.RefKind);
                result.Append(' ');
                result.Append(parameter.Name);
                i++;
            }
 
            result.Append(") ");
            MetadataSignatureHelper.AppendMethodImplAttributes(result, method.ImplementationAttributes);
 
            if (indent != null)
            {
                result.AppendLine();
 
                if (hasParameterAttributes)
                {
                    result.Append(indent);
                    result.AppendLine("{");
 
                    string memberIndent = indent + "  ";
 
                    i = 1;
                    foreach (PEParameterSymbol parameter in method.Parameters)
                    {
                        if (parameter.GetAttributes().Length > 0)
                        {
                            result.Append(memberIndent);
                            result.AppendFormat(".param [{0}]", i);
                            result.AppendLine();
 
                            AppendCustomAttributes(result, parameter, indent, inBlock: true);
                        }
 
                        i++;
                    }
 
                    result.Append(indent);
                    result.AppendLine("}");
                }
            }
        }
 
        private static void AppendSignatureType(StringBuilder result, TypeSymbol type, RefKind refKind)
        {
            result.Append(type);
 
            if (refKind != RefKind.None)
            {
                result.Append('&');
            }
        }
 
        private void AppendAssemblyRefs(StringBuilder result, PEAssemblySymbol assembly)
        {
            foreach (var a in assembly.PrimaryModule.GetReferencedAssemblies())
            {
                result.Append(".assembly extern ");
                result.AppendLine(a.GetDisplayName(fullKey: true));
            }
        }
 
        private string Dump(MetadataReference winmd, MetadataReference[] additionalRefs = null)
        {
            IEnumerable<MetadataReference> references = new[] { MscorlibRef_v4_0_30316_17626, _systemRuntimeRef, _systemObjectModelRef, _windowsRuntimeUIXamlRef, _interopServicesWindowsRuntimeRef, winmd };
 
            if (additionalRefs != null)
            {
                references = references.Concat(additionalRefs);
            }
 
            var comp = CreateEmptyCompilation("", references, options: TestOptions.ReleaseDll.WithMetadataImportOptions(MetadataImportOptions.All));
 
            var writer = new StringBuilder();
            AppendAssemblyRefs(writer, (PEAssemblySymbol)comp.GetReferencedAssemblySymbol(winmd));
 
            AppendMembers(writer, comp.GetReferencedAssemblySymbol(winmd).GlobalNamespace, "");
            return writer.ToString();
        }
 
        private void AssertDumpsEqual(string expected, string actual)
        {
            if (expected != actual)
            {
                string fileExpected = Path.Combine(Path.GetTempPath(), "roslyn_winmd_dump.expected.txt");
                string fileActual = Path.Combine(Path.GetTempPath(), "roslyn_winmd_dump.actual.txt");
                File.WriteAllText(fileExpected, expected);
                File.WriteAllText(fileActual, actual);
                Assert.True(false, "Dump is different. To investigate compare files:\r\n\"" + fileExpected + "\" \"" + fileActual + "\"");
            }
        }
 
        [Fact]
        public void DumpWindowsWinMD()
        {
            var expected = Encoding.UTF8.GetString(TestResources.WinRt.Windows_dump);
            var actual = Dump(_windowsRef);
            AssertDumpsEqual(expected, actual);
        }
 
        [Fact]
        public void DumpWinMDPrefixing()
        {
            var winmd = MetadataReference.CreateFromImage(TestResources.WinRt.WinMDPrefixing.AsImmutableOrNull());
            var actual = Dump(winmd, new[] { _windowsRef });
            var expected = Encoding.UTF8.GetString(TestResources.WinRt.WinMDPrefixing_dump);
            AssertDumpsEqual(expected, actual);
        }
    }
}