File: src\runtime\src\coreclr\tools\Common\TypeSystem\Ecma\MetadataExtensions.cs
Web Access
Project: src\runtime\src\coreclr\tools\aot\ILCompiler.TypeSystem\ILCompiler.TypeSystem.csproj (ILCompiler.TypeSystem)
// 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.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
using Internal.Text;

namespace Internal.TypeSystem.Ecma
{
    public static class MetadataExtensions
    {
        public static CustomAttributeValue<TypeDesc>? GetDecodedCustomAttribute(this EcmaType This,
            string attributeNamespace, string attributeName)
        {
            var metadataReader = This.MetadataReader;

            var attributeHandle = metadataReader.GetCustomAttributeHandle(metadataReader.GetTypeDefinition(This.Handle).GetCustomAttributes(),
                attributeNamespace, attributeName);

            if (attributeHandle.IsNil)
                return null;

            return metadataReader.GetCustomAttribute(attributeHandle).DecodeValue(new CustomAttributeTypeProvider(This.Module));
        }

        public static IEnumerable<CustomAttributeValue<TypeDesc>> GetDecodedCustomAttributes(this EcmaType This,
            string attributeNamespace, string attributeName)
        {
            var metadataReader = This.MetadataReader;
            var attributeHandles = metadataReader.GetTypeDefinition(This.Handle).GetCustomAttributes();
            foreach (var attributeHandle in attributeHandles)
            {
                if (IsEqualCustomAttributeName(attributeHandle, metadataReader, attributeNamespace, attributeName))
                {
                    yield return metadataReader.GetCustomAttribute(attributeHandle).DecodeValue(new CustomAttributeTypeProvider(This.Module));
                }
            }
        }

        public static CustomAttributeValue<TypeDesc>? GetDecodedCustomAttribute(this EcmaMethod This,
            string attributeNamespace, string attributeName)
        {
            var metadataReader = This.MetadataReader;

            var attributeHandle = metadataReader.GetCustomAttributeHandle(metadataReader.GetMethodDefinition(This.Handle).GetCustomAttributes(),
                attributeNamespace, attributeName);

            if (attributeHandle.IsNil)
                return null;

            return metadataReader.GetCustomAttribute(attributeHandle).DecodeValue(new CustomAttributeTypeProvider(This.Module));
        }

        public static IEnumerable<CustomAttributeValue<TypeDesc>> GetDecodedCustomAttributes(this EcmaMethod This,
            string attributeNamespace, string attributeName)
        {
            var metadataReader = This.MetadataReader;
            var attributeHandles = metadataReader.GetMethodDefinition(This.Handle).GetCustomAttributes();
            foreach (var attributeHandle in attributeHandles)
            {
                if (IsEqualCustomAttributeName(attributeHandle, metadataReader, attributeNamespace, attributeName))
                {
                    yield return metadataReader.GetCustomAttribute(attributeHandle).DecodeValue(new CustomAttributeTypeProvider(This.Module));
                }
            }
        }

        public static CustomAttributeValue<TypeDesc>? GetDecodedCustomAttribute(this EcmaField This,
            string attributeNamespace, string attributeName)
        {
            var metadataReader = This.MetadataReader;

            var attributeHandle = metadataReader.GetCustomAttributeHandle(metadataReader.GetFieldDefinition(This.Handle).GetCustomAttributes(),
                attributeNamespace, attributeName);

            if (attributeHandle.IsNil)
                return null;

            return metadataReader.GetCustomAttribute(attributeHandle).DecodeValue(new CustomAttributeTypeProvider(This.Module));
        }

        public static IEnumerable<CustomAttributeValue<TypeDesc>> GetDecodedCustomAttributes(this EcmaField This,
            string attributeNamespace, string attributeName)
        {
            var metadataReader = This.MetadataReader;
            var attributeHandles = metadataReader.GetFieldDefinition(This.Handle).GetCustomAttributes();
            foreach (var attributeHandle in attributeHandles)
            {
                if (IsEqualCustomAttributeName(attributeHandle, metadataReader, attributeNamespace, attributeName))
                {
                    yield return metadataReader.GetCustomAttribute(attributeHandle).DecodeValue(new CustomAttributeTypeProvider(This.Module));
                }
            }
        }

        public static IEnumerable<CustomAttributeValue<TypeDesc>> GetDecodedCustomAttributes(this EcmaAssembly This,
            string attributeNamespace, string attributeName)
        {
            var metadataReader = This.MetadataReader;
            var attributeHandles = metadataReader.GetAssemblyDefinition().GetCustomAttributes();
            foreach (var attributeHandle in attributeHandles)
            {
                if (IsEqualCustomAttributeName(attributeHandle, metadataReader, attributeNamespace, attributeName))
                {
                    yield return metadataReader.GetCustomAttribute(attributeHandle).DecodeValue(new CustomAttributeTypeProvider(This));
                }
            }
        }

        public static CustomAttributeHandle GetCustomAttributeHandle(this MetadataReader metadataReader, CustomAttributeHandleCollection customAttributes,
            string attributeNamespace, string attributeName)
        {
            foreach (var attributeHandle in customAttributes)
            {
                if (IsEqualCustomAttributeName(attributeHandle, metadataReader, attributeNamespace, attributeName))
                {
                    return attributeHandle;
                }
            }

            return default(CustomAttributeHandle);
        }

        public static bool IsEqualCustomAttributeName(CustomAttributeHandle attributeHandle, MetadataReader metadataReader,
            string attributeNamespace, string attributeName)
        {
            StringHandle namespaceHandle, nameHandle;
            if (!metadataReader.GetAttributeNamespaceAndName(attributeHandle, out namespaceHandle, out nameHandle))
                return false;

            return metadataReader.StringComparer.Equals(namespaceHandle, attributeNamespace)
                && metadataReader.StringComparer.Equals(nameHandle, attributeName);
        }

        public static bool GetAttributeNamespaceAndName(this MetadataReader metadataReader, CustomAttributeHandle attributeHandle,
            out StringHandle namespaceHandle, out StringHandle nameHandle)
        {
            EntityHandle attributeType;
            if (!GetAttributeTypeAndConstructor(metadataReader, attributeHandle, out attributeType, out _))
            {
                namespaceHandle = default(StringHandle);
                nameHandle = default(StringHandle);
                return false;
            }

            return GetAttributeTypeNamespaceAndName(metadataReader, attributeType, out namespaceHandle, out nameHandle);
        }

        public static bool GetAttributeTypeAndConstructor(this MetadataReader metadataReader, CustomAttributeHandle attributeHandle,
            out EntityHandle attributeType, out EntityHandle attributeCtor)
        {
            attributeCtor = metadataReader.GetCustomAttribute(attributeHandle).Constructor;

            if (attributeCtor.Kind == HandleKind.MemberReference)
            {
                attributeType = metadataReader.GetMemberReference((MemberReferenceHandle)attributeCtor).Parent;
                return true;
            }
            else if (attributeCtor.Kind == HandleKind.MethodDefinition)
            {
                attributeType = metadataReader.GetMethodDefinition((MethodDefinitionHandle)attributeCtor).GetDeclaringType();
                return true;
            }
            else
            {
                // invalid metadata
                attributeType = default(EntityHandle);
                return false;
            }
        }

        public static bool GetAttributeTypeNamespaceAndName(this MetadataReader metadataReader, EntityHandle attributeType,
            out StringHandle namespaceHandle, out StringHandle nameHandle)
        {
            namespaceHandle = default(StringHandle);
            nameHandle = default(StringHandle);

            if (attributeType.Kind == HandleKind.TypeReference)
            {
                TypeReference typeRefRow = metadataReader.GetTypeReference((TypeReferenceHandle)attributeType);
                HandleKind handleType = typeRefRow.ResolutionScope.Kind;

                // Nested type?
                if (handleType == HandleKind.TypeReference || handleType == HandleKind.TypeDefinition)
                    return false;

                nameHandle = typeRefRow.Name;
                namespaceHandle = typeRefRow.Namespace;
                return true;
            }
            else if (attributeType.Kind == HandleKind.TypeDefinition)
            {
                var def = metadataReader.GetTypeDefinition((TypeDefinitionHandle)attributeType);

                // Nested type?
                if (IsNested(def.Attributes))
                    return false;

                nameHandle = def.Name;
                namespaceHandle = def.Namespace;
                return true;
            }
            else
            {
                // unsupported metadata
                return false;
            }
        }

        public static PInvokeFlags GetDelegatePInvokeFlags(this EcmaType type)
        {
            PInvokeFlags flags = new PInvokeFlags(PInvokeAttributes.PreserveSig);

            if (!type.IsDelegate)
            {
                return flags;
            }

            var customAttributeValue = type.GetDecodedCustomAttribute(
                               "System.Runtime.InteropServices", "UnmanagedFunctionPointerAttribute");

            if (customAttributeValue == null)
            {
                return flags;
            }

            if (!customAttributeValue.HasValue)
            {
                return flags;
            }

            if (customAttributeValue.Value.FixedArguments.Length == 1)
            {
                CallingConvention callingConvention = (CallingConvention)customAttributeValue.Value.FixedArguments[0].Value;

                switch (callingConvention)
                {
                    case CallingConvention.StdCall:
                        flags.UnmanagedCallingConvention = MethodSignatureFlags.UnmanagedCallingConventionStdCall;
                        break;
                    case CallingConvention.Cdecl:
                        flags.UnmanagedCallingConvention = MethodSignatureFlags.UnmanagedCallingConventionCdecl;
                        break;
                    case CallingConvention.ThisCall:
                        flags.UnmanagedCallingConvention = MethodSignatureFlags.UnmanagedCallingConventionThisCall;
                        break;
                    case CallingConvention.Winapi:
                        // Platform default
                        break;
                }
            }

            foreach (var namedArgument in customAttributeValue.Value.NamedArguments)
            {
                if (namedArgument.Name == "CharSet")
                {
                    flags.CharSet = (CharSet)namedArgument.Value;
                }
                else if (namedArgument.Name == "BestFitMapping")
                {
                    flags.BestFitMapping = (bool)namedArgument.Value;
                }
                else if (namedArgument.Name == "SetLastError")
                {
                    flags.SetLastError = (bool)namedArgument.Value;
                }
                else if (namedArgument.Name == "ThrowOnUnmappableChar")
                {
                    flags.ThrowOnUnmappableChar = (bool)namedArgument.Value;
                }
            }
            return flags;
        }

        // This mask is the fastest way to check if a type is nested from its flags,
        // but it should not be added to the BCL enum as its semantics can be misleading.
        // Consider, for example, that (NestedFamANDAssem & NestedMask) == NestedFamORAssem.
        // Only comparison of the masked value to 0 is meaningful, which is different from
        // the other masks in the enum.
        private const TypeAttributes NestedMask = (TypeAttributes)0x00000006;

        public static bool IsNested(this TypeAttributes flags)
        {
            return (flags & NestedMask) != 0;
        }

        public static bool IsRuntimeSpecialName(this MethodAttributes flags)
        {
            return (flags & (MethodAttributes.SpecialName | MethodAttributes.RTSpecialName))
                == (MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
        }

        public static bool IsPublic(this MethodAttributes flags)
        {
            return (flags & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
        }

        public static unsafe byte* GetTypeNamePointer(this MetadataReader reader, TypeDefinitionHandle handle)
        {
            return reader.GetBlobReader(reader.GetTypeDefinition(handle).Name).CurrentPointer;
        }

        public static unsafe byte* GetTypeNamespacePointer(this MetadataReader reader, TypeDefinitionHandle handle)
        {
            return reader.GetBlobReader(reader.GetTypeDefinition(handle).Namespace).CurrentPointer;
        }

        public static unsafe byte* GetMethodNamePointer(this MetadataReader reader, MethodDefinitionHandle handle)
        {
            return reader.GetBlobReader(reader.GetMethodDefinition(handle).Name).CurrentPointer;
        }

        public static unsafe ReadOnlySpan<byte> GetStringBytes(this MetadataReader reader, StringHandle handle)
        {
            BlobReader blobReader = reader.GetBlobReader(handle);
            return new ReadOnlySpan<byte>(blobReader.CurrentPointer, blobReader.Length);
        }

        public static unsafe bool StringEquals(this MetadataReader reader, StringHandle handle, Utf8Span otherString)
        {
            int stringOffset = reader.GetHeapOffset(handle);

            // Could the heap hold a string this big?
            if (stringOffset + otherString.Length >= reader.GetHeapSize(HeapIndex.String))
                return false;

            // Check for null terminator at the expected location
            byte* currentChar = reader.MetadataPointer + reader.GetHeapMetadataOffset(HeapIndex.String) + stringOffset;
            if (*(currentChar + otherString.Length) != 0)
                return false;

            // Compare characters
            ReadOnlySpan<byte> otherSpan = otherString.AsSpan();
            for (int i = 0; i < otherString.Length; i++)
            {
                if (otherSpan[i] != *(currentChar++))
                    return false;
            }

            return true;
        }
    }
}