File: ComHost\ClsidMap.cs
Web Access
Project: src\src\runtime\src\installer\managed\Microsoft.NET.HostModel\Microsoft.NET.HostModel.csproj (Microsoft.NET.HostModel)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Microsoft.NET.HostModel.ComHost
{
    public static class ClsidMap
    {
        private static readonly JsonSerializerOptions s_jsonOptions = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };

        private struct ClsidEntry
        {
            [JsonPropertyName("type")]
            public string Type { get; set; }
            [JsonPropertyName("assembly")]
            public string Assembly { get; set; }
            [JsonPropertyName("progid")]
            public string ProgId { get; set; }
        }

        public static void Create(MetadataReader metadataReader, string clsidMapPath)
        {
            Dictionary<string, ClsidEntry> clsidMap = new Dictionary<string, ClsidEntry>();

            string assemblyName = GetAssemblyName(metadataReader).FullName;

            bool isAssemblyComVisible = IsComVisible(metadataReader, metadataReader.GetAssemblyDefinition());

            foreach (TypeDefinitionHandle type in metadataReader.TypeDefinitions)
            {
                TypeDefinition definition = metadataReader.GetTypeDefinition(type);

                // Only public COM-visible classes can be exposed via the COM host.
                if (TypeIsPublic(metadataReader, definition) && TypeIsClass(metadataReader, definition) && IsComVisible(metadataReader, definition, isAssemblyComVisible))
                {
                    Guid guid = GetTypeGuid(metadataReader, definition);
                    string guidString = GetTypeGuid(metadataReader, definition).ToString("B");

                    if (clsidMap.TryGetValue(guidString, out ClsidEntry value))
                    {
                        throw new ConflictingGuidException(value.Type, GetTypeName(metadataReader, definition), guid);
                    }

                    string progId = GetProgId(metadataReader, definition);

                    clsidMap.Add(guidString,
                        new ClsidEntry
                        {
                            Type = GetTypeName(metadataReader, definition),
                            Assembly = assemblyName,
                            ProgId = !string.IsNullOrWhiteSpace(progId) ? progId : null
                        });
                }
            }

            using (StreamWriter writer = File.CreateText(clsidMapPath))
            {
                writer.Write(JsonSerializer.Serialize(clsidMap, s_jsonOptions));
            }
        }

        private static bool TypeIsClass(MetadataReader metadataReader, TypeDefinition definition)
        {
            if ((definition.Attributes & TypeAttributes.Interface) != 0)
            {
                return false;
            }

            EntityHandle baseTypeEntity = definition.BaseType;
            if (baseTypeEntity.Kind == HandleKind.TypeReference)
            {
                TypeReference baseClass = metadataReader.GetTypeReference((TypeReferenceHandle)baseTypeEntity);
                if (baseClass.ResolutionScope.Kind == HandleKind.AssemblyReference)
                {
                    if (HasTypeName(metadataReader, baseClass, "System", "ValueType") || HasTypeName(metadataReader, baseClass, "System", "Enum"))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        private static bool TypeIsPublic(MetadataReader reader, TypeDefinition type)
        {
            switch (type.Attributes & TypeAttributes.VisibilityMask)
            {
                case TypeAttributes.Public:
                    return true;
                case TypeAttributes.NestedPublic:
                    return TypeIsPublic(reader, reader.GetTypeDefinition(type.GetDeclaringType()));
                default:
                    return false;
            }
        }

        private static string GetTypeName(MetadataReader metadataReader, TypeDefinition type)
        {
            if (!type.GetDeclaringType().IsNil)
            {
                return $"{GetTypeName(metadataReader, metadataReader.GetTypeDefinition(type.GetDeclaringType()))}+{metadataReader.GetString(type.Name)}";
            }
            return $"{metadataReader.GetString(type.Namespace)}{Type.Delimiter}{metadataReader.GetString(type.Name)}";
        }

        private static bool HasTypeName(MetadataReader metadataReader, TypeReference type, string ns, string name)
        {
            return metadataReader.StringComparer.Equals(type.Namespace, ns) && metadataReader.StringComparer.Equals(type.Name, name);
        }

        private static AssemblyName GetAssemblyName(MetadataReader metadataReader)
        {
            AssemblyName name = new AssemblyName();

            AssemblyDefinition definition = metadataReader.GetAssemblyDefinition();
            name.Name = metadataReader.GetString(definition.Name);
            name.Version = definition.Version;
            name.CultureInfo = CultureInfo.GetCultureInfo(metadataReader.GetString(definition.Culture));
            name.SetPublicKey(metadataReader.GetBlobBytes(definition.PublicKey));

            return name;
        }

        private static bool IsComVisible(MetadataReader reader, AssemblyDefinition assembly)
        {
            CustomAttributeHandle handle = GetComVisibleAttribute(reader, assembly.GetCustomAttributes());

            if (handle.IsNil)
            {
                return false;
            }

            CustomAttribute comVisibleAttribute = reader.GetCustomAttribute(handle);
            CustomAttributeValue<KnownType> data = comVisibleAttribute.DecodeValue(new TypeResolver());
            return (bool)data.FixedArguments[0].Value;
        }

        private static bool IsComVisible(MetadataReader metadataReader, TypeDefinition definition, bool assemblyComVisible)
        {
            // We need to ensure that all parent scopes of the given type are not explicitly non-ComVisible.
            bool? IsComVisibleCore(TypeDefinition typeDefinition)
            {
                CustomAttributeHandle handle = GetComVisibleAttribute(metadataReader, typeDefinition.GetCustomAttributes());
                if (handle.IsNil)
                {
                    return null;
                }

                CustomAttribute comVisibleAttribute = metadataReader.GetCustomAttribute(handle);
                CustomAttributeValue<KnownType> data = comVisibleAttribute.DecodeValue(new TypeResolver());
                return (bool)data.FixedArguments[0].Value;
            }

            if (!definition.GetDeclaringType().IsNil)
            {
                return IsComVisible(metadataReader, metadataReader.GetTypeDefinition(definition.GetDeclaringType()), assemblyComVisible) && (IsComVisibleCore(definition) ?? assemblyComVisible);
            }

            return IsComVisibleCore(definition) ?? assemblyComVisible;
        }

        private static CustomAttributeHandle GetComVisibleAttribute(MetadataReader reader, CustomAttributeHandleCollection customAttributes)
        {
            foreach (CustomAttributeHandle attr in customAttributes)
            {
                CustomAttribute attribute = reader.GetCustomAttribute(attr);
                if (IsTargetAttribute(reader, attribute, "System.Runtime.InteropServices", "ComVisibleAttribute"))
                {
                    return attr;
                }
            }
            return default;
        }

        private static Guid GetTypeGuid(MetadataReader reader, TypeDefinition type)
        {
            // Find the class' GUID by reading the GuidAttribute value.
            // We do not support implicit runtime-generated GUIDs for the .NET Core COM host.
            foreach (CustomAttributeHandle attr in type.GetCustomAttributes())
            {
                CustomAttribute attribute = reader.GetCustomAttribute(attr);
                if (IsTargetAttribute(reader, attribute, "System.Runtime.InteropServices", "GuidAttribute"))
                {
                    CustomAttributeValue<KnownType> data = attribute.DecodeValue(new TypeResolver());
                    return Guid.Parse((string)data.FixedArguments[0].Value);
                }
            }
            throw new MissingGuidException(GetTypeName(reader, type));
        }

        private static string GetProgId(MetadataReader reader, TypeDefinition type)
        {
            foreach (CustomAttributeHandle attr in type.GetCustomAttributes())
            {
                CustomAttribute attribute = reader.GetCustomAttribute(attr);
                if (IsTargetAttribute(reader, attribute, "System.Runtime.InteropServices", "ProgIdAttribute"))
                {
                    CustomAttributeValue<KnownType> data = attribute.DecodeValue(new TypeResolver());
                    return (string)data.FixedArguments[0].Value;
                }
            }
            return GetTypeName(reader, type);
        }

        private static bool IsTargetAttribute(MetadataReader reader, CustomAttribute attribute, string targetNamespace, string targetName)
        {
            StringHandle namespaceMaybe;
            StringHandle nameMaybe;
            switch (attribute.Constructor.Kind)
            {
                case HandleKind.MemberReference:
                    MemberReference refConstructor = reader.GetMemberReference((MemberReferenceHandle)attribute.Constructor);
                    TypeReference refType = reader.GetTypeReference((TypeReferenceHandle)refConstructor.Parent);
                    namespaceMaybe = refType.Namespace;
                    nameMaybe = refType.Name;
                    break;

                case HandleKind.MethodDefinition:
                    MethodDefinition defConstructor = reader.GetMethodDefinition((MethodDefinitionHandle)attribute.Constructor);
                    TypeDefinition defType = reader.GetTypeDefinition(defConstructor.GetDeclaringType());
                    namespaceMaybe = defType.Namespace;
                    nameMaybe = defType.Name;
                    break;

                default:
                    Debug.Fail("Unknown attribute constructor kind");
                    return false;
            }

            return reader.StringComparer.Equals(namespaceMaybe, targetNamespace) && reader.StringComparer.Equals(nameMaybe, targetName);
        }

        private enum KnownType
        {
            Bool,
            String,
            SystemType,
            Unknown
        }

        private sealed class TypeResolver : ICustomAttributeTypeProvider<KnownType>
        {
            public KnownType GetPrimitiveType(PrimitiveTypeCode typeCode)
            {
                switch (typeCode)
                {
                    case PrimitiveTypeCode.Boolean:
                        return KnownType.Bool;
                    case PrimitiveTypeCode.String:
                        return KnownType.String;
                    default:
                        return KnownType.Unknown;
                }
            }

            public KnownType GetSystemType()
            {
                return KnownType.SystemType;
            }

            public KnownType GetSZArrayType(KnownType elementType)
            {
                return KnownType.Unknown;
            }

            public KnownType GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
            {
                return KnownType.Unknown;
            }

            public KnownType GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
            {
                return KnownType.Unknown;
            }

            public KnownType GetTypeFromSerializedName(string name)
            {
                return KnownType.Unknown;
            }

            public PrimitiveTypeCode GetUnderlyingEnumType(KnownType type)
            {
                throw new BadImageFormatException("Unexpectedly got an enum parameter for an attribute.");
            }

            public bool IsSystemType(KnownType type)
            {
                return type == KnownType.SystemType;
            }
        }

    }
}