File: src\runtime\src\coreclr\tools\Common\TypeSystem\Ecma\EcmaMethod.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.Diagnostics;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Threading;

using Internal.Text;

namespace Internal.TypeSystem.Ecma
{
    public sealed partial class EcmaMethod : MethodDesc, EcmaModule.IEntityHandleObject
    {
        private static class MethodFlags
        {
#pragma warning disable IDE0055 // Disable formatting to keep aligned
            public const int BasicMetadataCache     = 0x00001;
            public const int Virtual                = 0x00002;
            public const int NewSlot                = 0x00004;
            public const int Abstract               = 0x00008;
            public const int Final                  = 0x00010;
            public const int NoInlining             = 0x00020;
            public const int AggressiveInlining     = 0x00040;
            public const int RuntimeImplemented     = 0x00080;
            public const int InternalCall           = 0x00100;
            public const int Synchronized           = 0x00200;
            public const int AggressiveOptimization = 0x00400;
            public const int NoOptimization         = 0x00800;
            public const int RequireSecObject       = 0x01000;
            public const int Async                  = 0x02000;

            public const int AttributeMetadataCache = 0x04000;
            public const int Intrinsic              = 0x08000;
            public const int UnmanagedCallersOnly   = 0x10000;
#pragma warning restore IDE0055
        };

        private EcmaType _type;
        private MethodDefinitionHandle _handle;

        // Cached values
        private unsafe volatile byte* _namePointer;
        private int _nameLength;
        private ThreadSafeFlags _methodFlags;
        private MethodSignature _signature;
        private TypeDesc[] _genericParameters; // TODO: Optional field?

        internal EcmaMethod(EcmaType type, MethodDefinitionHandle handle)
        {
            _type = type;
            _handle = handle;
        }

        EntityHandle EcmaModule.IEntityHandleObject.Handle
        {
            get
            {
                return _handle;
            }
        }

        public override TypeSystemContext Context
        {
            get
            {
                return _type.Module.Context;
            }
        }

        public override EcmaType OwningType
        {
            get
            {
                return _type;
            }
        }

        private MethodSignature InitializeSignature()
        {
            var metadataReader = MetadataReader;
            BlobReader signatureReader = metadataReader.GetBlobReader(metadataReader.GetMethodDefinition(_handle).Signature);

            EcmaSignatureParser parser = new EcmaSignatureParser(Module, signatureReader, NotFoundBehavior.Throw);
            var signature = parser.ParseMethodSignature();
            return (_signature = signature);
        }

        public override MethodSignature Signature
        {
            get
            {
                if (_signature == null)
                    return InitializeSignature();
                return _signature;
            }
        }

        public EcmaModule Module
        {
            get
            {
                return _type.Module;
            }
        }

        public MetadataReader MetadataReader
        {
            get
            {
                return _type.MetadataReader;
            }
        }

        public MethodDefinitionHandle Handle
        {
            get
            {
                return _handle;
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private int InitializeMethodFlags(int mask)
        {
            int flags = 0;

            if ((mask & MethodFlags.BasicMetadataCache) != 0)
            {
                var methodAttributes = Attributes;
                var methodImplAttributes = ImplAttributes;

                if ((methodAttributes & MethodAttributes.Virtual) != 0)
                    flags |= MethodFlags.Virtual;

                if ((methodAttributes & MethodAttributes.NewSlot) != 0)
                    flags |= MethodFlags.NewSlot;

                if ((methodAttributes & MethodAttributes.Abstract) != 0)
                    flags |= MethodFlags.Abstract;

                if ((methodAttributes & MethodAttributes.Final) != 0)
                    flags |= MethodFlags.Final;

                if ((methodAttributes & MethodAttributes.RequireSecObject) != 0)
                    flags |= MethodFlags.RequireSecObject;

                if ((methodImplAttributes & MethodImplAttributes.NoInlining) != 0)
                    flags |= MethodFlags.NoInlining;

                // System.Reflection.Primitives we build against doesn't define AggressiveOptimization
                const MethodImplAttributes MethodImplAttributes_AggressiveOptimization = (MethodImplAttributes)0x0200;

                // No optimization bit beats aggressive optimization bit (CLR compatible behavior)
                if ((methodImplAttributes & MethodImplAttributes.NoOptimization) != 0)
                    flags |= MethodFlags.NoOptimization;
                else if ((methodImplAttributes & MethodImplAttributes_AggressiveOptimization) != 0)
                    flags |= MethodFlags.AggressiveOptimization;

                if ((methodImplAttributes & MethodImplAttributes.AggressiveInlining) != 0)
                    flags |= MethodFlags.AggressiveInlining;

                if ((methodImplAttributes & MethodImplAttributes.Runtime) != 0)
                    flags |= MethodFlags.RuntimeImplemented;

                if ((methodImplAttributes & MethodImplAttributes.InternalCall) != 0)
                    flags |= MethodFlags.InternalCall;

                if ((methodImplAttributes & MethodImplAttributes.Synchronized) != 0)
                    flags |= MethodFlags.Synchronized;

                if ((methodImplAttributes & MethodImplAttributes.Async) != 0)
                    flags |= MethodFlags.Async;

                flags |= MethodFlags.BasicMetadataCache;
            }

            // Fetching custom attribute based properties is more expensive, so keep that under
            // a separate cache that might not be accessed very frequently.
            if ((mask & MethodFlags.AttributeMetadataCache) != 0)
            {
                var metadataReader = this.MetadataReader;
                var methodDefinition = metadataReader.GetMethodDefinition(_handle);

                foreach (var attributeHandle in methodDefinition.GetCustomAttributes())
                {
                    StringHandle namespaceHandle, nameHandle;
                    if (!metadataReader.GetAttributeNamespaceAndName(attributeHandle, out namespaceHandle, out nameHandle))
                        continue;

                    if (metadataReader.StringComparer.Equals(namespaceHandle, "System.Runtime.CompilerServices"))
                    {
                        if (metadataReader.StringComparer.Equals(nameHandle, "IntrinsicAttribute"))
                        {
                            flags |= MethodFlags.Intrinsic;
                        }
                    }
                    else
                    if (metadataReader.StringComparer.Equals(namespaceHandle, "System.Runtime.InteropServices"))
                    {
                        if (metadataReader.StringComparer.Equals(nameHandle, "UnmanagedCallersOnlyAttribute"))
                        {
                            flags |= MethodFlags.UnmanagedCallersOnly;
                        }
                    }
                }

                flags |= MethodFlags.AttributeMetadataCache;
            }

            Debug.Assert((flags & mask) != 0);
            _methodFlags.AddFlags(flags);

            return flags & mask;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int GetMethodFlags(int mask)
        {
            int flags = _methodFlags.Value & mask;
            if (flags != 0)
                return flags;
            return InitializeMethodFlags(mask);
        }

        public override bool IsVirtual
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.Virtual) & MethodFlags.Virtual) != 0;
            }
        }

        public override bool IsNewSlot
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.NewSlot) & MethodFlags.NewSlot) != 0;
            }
        }

        public override bool IsAbstract
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.Abstract) & MethodFlags.Abstract) != 0;
            }
        }

        public override bool IsFinal
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.Final) & MethodFlags.Final) != 0;
            }
        }

        public override bool IsNoInlining
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.NoInlining) & MethodFlags.NoInlining) != 0;
            }
        }

        public override bool RequireSecObject
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.RequireSecObject) & MethodFlags.RequireSecObject) != 0;
            }
        }

        public override bool IsAggressiveOptimization
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.AggressiveOptimization) & MethodFlags.AggressiveOptimization) != 0;
            }
        }

        public override bool IsNoOptimization
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.NoOptimization) & MethodFlags.NoOptimization) != 0;
            }
        }

        public override bool IsAggressiveInlining
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.AggressiveInlining) & MethodFlags.AggressiveInlining) != 0;
            }
        }

        public override bool IsRuntimeImplemented
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.RuntimeImplemented) & MethodFlags.RuntimeImplemented) != 0;
            }
        }

        public override bool IsIntrinsic
        {
            get
            {
                return (GetMethodFlags(MethodFlags.AttributeMetadataCache | MethodFlags.Intrinsic) & MethodFlags.Intrinsic) != 0;
            }
        }

        public override bool IsInternalCall
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.InternalCall) & MethodFlags.InternalCall) != 0;
            }
        }

        public override bool IsSynchronized
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.Synchronized) & MethodFlags.Synchronized) != 0;
            }
        }

        public override bool IsUnmanagedCallersOnly
        {
            get
            {
                return (GetMethodFlags(MethodFlags.AttributeMetadataCache | MethodFlags.UnmanagedCallersOnly) & MethodFlags.UnmanagedCallersOnly) != 0;
            }
        }

        public override bool IsSpecialName
        {
            get
            {
                return (Attributes & MethodAttributes.SpecialName) != 0;
            }
        }

        public override bool IsDefaultConstructor
        {
            get
            {
                MethodAttributes attributes = Attributes;
                return attributes.IsRuntimeSpecialName()
                    && attributes.IsPublic()
                    && Signature.Length == 0
                    && Name == ".ctor"u8
                    && !_type.IsAbstract;
            }
        }

        public override bool IsPublic
        {
            get
            {
                return Attributes.IsPublic();
            }
        }

        public override bool IsStaticConstructor
        {
            get
            {
                return Attributes.IsRuntimeSpecialName() && Name == ".cctor"u8;
            }
        }

        public override bool IsAsync
        {
            get
            {
                return (GetMethodFlags(MethodFlags.BasicMetadataCache | MethodFlags.Async) & MethodFlags.Async) != 0;
            }
        }

        public MethodAttributes Attributes
        {
            get
            {
                return MetadataReader.GetMethodDefinition(_handle).Attributes;
            }
        }

        public MethodImplAttributes ImplAttributes
        {
            get
            {
                return MetadataReader.GetMethodDefinition(_handle).ImplAttributes;
            }
        }

        public unsafe ReadOnlySpan<byte> InitializeName()
        {
            StringHandle handle = MetadataReader.GetMethodDefinition(_handle).Name;
            _nameLength = MetadataReader.GetStringBytes(handle).Length;
            _namePointer = MetadataReader.MetadataPointer + MetadataReader.GetHeapMetadataOffset(HeapIndex.String) + MetadataReader.GetHeapOffset(handle);
            return new ReadOnlySpan<byte>(_namePointer, _nameLength);
        }

        public override unsafe Utf8Span Name
        {
            get
            {
                byte* namePointer = _namePointer;
                if (namePointer != null)
                {
                    return new ReadOnlySpan<byte>(namePointer, _nameLength);
                }
                return InitializeName();
            }
        }

        private void ComputeGenericParameters()
        {
            var genericParameterHandles = MetadataReader.GetMethodDefinition(_handle).GetGenericParameters();
            int count = genericParameterHandles.Count;
            if (count > 0)
            {
                TypeDesc[] genericParameters = new TypeDesc[count];
                int i = 0;
                foreach (var genericParameterHandle in genericParameterHandles)
                {
                    genericParameters[i++] = new EcmaGenericParameter(Module, genericParameterHandle);
                }
                Interlocked.CompareExchange(ref _genericParameters, genericParameters, null);
            }
            else
            {
                _genericParameters = TypeDesc.EmptyTypes;
            }
        }

        public override Instantiation Instantiation
        {
            get
            {
                if (_genericParameters == null)
                    ComputeGenericParameters();
                return new Instantiation(_genericParameters);
            }
        }

        public override bool HasCustomAttribute(string attributeNamespace, string attributeName)
        {
            return !MetadataReader.GetCustomAttributeHandle(MetadataReader.GetMethodDefinition(_handle).GetCustomAttributes(),
                attributeNamespace, attributeName).IsNil;
        }

        public override bool IsPInvoke
        {
            get
            {
                return (((int)Attributes & (int)MethodAttributes.PinvokeImpl) != 0);
            }
        }

        public override PInvokeMetadata GetPInvokeMethodMetadata()
        {
            if (!IsPInvoke)
                return default(PInvokeMetadata);

            MetadataReader metadataReader = MetadataReader;
            MethodDefinition methodDef = metadataReader.GetMethodDefinition(_handle);
            MethodImport import = methodDef.GetImport();
            string name = metadataReader.GetString(import.Name);

            ModuleReference moduleRef = metadataReader.GetModuleReference(import.Module);
            string moduleName = metadataReader.GetString(moduleRef.Name);

            MethodImportAttributes importAttributes = import.Attributes;

            // If either BestFitMapping or ThrowOnUnmappable wasn't set on the p/invoke,
            // look for the value in the owning type or assembly.
            if ((importAttributes & MethodImportAttributes.BestFitMappingMask) == 0 ||
                (importAttributes & MethodImportAttributes.ThrowOnUnmappableCharMask) == 0)
            {
                TypeDefinition declaringType = metadataReader.GetTypeDefinition(methodDef.GetDeclaringType());

                // Start with owning type
                MethodImportAttributes fromCA = GetImportAttributesFromBestFitMappingAttribute(declaringType.GetCustomAttributes());
                if ((importAttributes & MethodImportAttributes.BestFitMappingMask) == 0)
                    importAttributes |= fromCA & MethodImportAttributes.BestFitMappingMask;
                if ((importAttributes & MethodImportAttributes.ThrowOnUnmappableCharMask) == 0)
                    importAttributes |= fromCA & MethodImportAttributes.ThrowOnUnmappableCharMask;

                // If we still don't know, check the assembly
                if ((importAttributes & MethodImportAttributes.BestFitMappingMask) == 0 ||
                    (importAttributes & MethodImportAttributes.ThrowOnUnmappableCharMask) == 0)
                {
                    fromCA = GetImportAttributesFromBestFitMappingAttribute(metadataReader.GetAssemblyDefinition().GetCustomAttributes());
                    if ((importAttributes & MethodImportAttributes.BestFitMappingMask) == 0)
                        importAttributes |= fromCA & MethodImportAttributes.BestFitMappingMask;
                    if ((importAttributes & MethodImportAttributes.ThrowOnUnmappableCharMask) == 0)
                        importAttributes |= fromCA & MethodImportAttributes.ThrowOnUnmappableCharMask;
                }
            }

            // Spot check the enums match
            Debug.Assert((int)MethodImportAttributes.CallingConventionStdCall == (int)PInvokeAttributes.CallingConventionStdCall);
            Debug.Assert((int)MethodImportAttributes.CharSetAuto == (int)PInvokeAttributes.CharSetAuto);
            Debug.Assert((int)MethodImportAttributes.CharSetUnicode == (int)PInvokeAttributes.CharSetUnicode);
            Debug.Assert((int)MethodImportAttributes.SetLastError == (int)PInvokeAttributes.SetLastError);

            PInvokeAttributes attributes = (PInvokeAttributes)importAttributes;

            if ((ImplAttributes & MethodImplAttributes.PreserveSig) != 0)
                attributes |= PInvokeAttributes.PreserveSig;

            return new PInvokeMetadata(moduleName, name, attributes);
        }

        private MethodImportAttributes GetImportAttributesFromBestFitMappingAttribute(CustomAttributeHandleCollection attributeHandles)
        {
            // Look for the [BestFitMapping(BestFitMapping: x, ThrowOnUnmappableChar = y)] attribute and
            // translate that to MethodImportAttributes

            MethodImportAttributes result = 0;
            MetadataReader reader = MetadataReader;

            CustomAttributeHandle attributeHandle = reader.GetCustomAttributeHandle(
                attributeHandles, "System.Runtime.InteropServices", "BestFitMappingAttribute");
            if (!attributeHandle.IsNil)
            {
                CustomAttribute attribute = reader.GetCustomAttribute(attributeHandle);
                CustomAttributeValue<TypeDesc> decoded = attribute.DecodeValue(
                    new CustomAttributeTypeProvider(_type.Module));

                if (decoded.FixedArguments.Length != 1 || !(decoded.FixedArguments[0].Value is bool))
                    ThrowHelper.ThrowBadImageFormatException();
                if ((bool)decoded.FixedArguments[0].Value)
                    result |= MethodImportAttributes.BestFitMappingEnable;
                else
                    result |= MethodImportAttributes.BestFitMappingDisable;

                foreach (CustomAttributeNamedArgument<TypeDesc> namedArg in decoded.NamedArguments)
                {
                    if (namedArg.Name == "ThrowOnUnmappableChar")
                    {
                        if (!(namedArg.Value is bool))
                            ThrowHelper.ThrowBadImageFormatException();
                        if ((bool)namedArg.Value)
                            result |= MethodImportAttributes.ThrowOnUnmappableCharEnable;
                        else
                            result |= MethodImportAttributes.ThrowOnUnmappableCharDisable;
                        break;
                    }
                }
            }

            return result;
        }

        public override ParameterMetadata[] GetParameterMetadata()
        {
            MetadataReader metadataReader = MetadataReader;

            // Spot check the enums match
            Debug.Assert((int)ParameterAttributes.In == (int)ParameterMetadataAttributes.In);
            Debug.Assert((int)ParameterAttributes.Out == (int)ParameterMetadataAttributes.Out);
            Debug.Assert((int)ParameterAttributes.Optional == (int)ParameterMetadataAttributes.Optional);
            Debug.Assert((int)ParameterAttributes.HasDefault == (int)ParameterMetadataAttributes.HasDefault);
            Debug.Assert((int)ParameterAttributes.HasFieldMarshal == (int)ParameterMetadataAttributes.HasFieldMarshal);

            ParameterHandleCollection parameterHandles = metadataReader.GetMethodDefinition(_handle).GetParameters();
            ParameterMetadata[] parameterMetadataArray = new ParameterMetadata[parameterHandles.Count];
            int index = 0;
            foreach (ParameterHandle parameterHandle in parameterHandles)
            {
                Parameter parameter = metadataReader.GetParameter(parameterHandle);
                MarshalAsDescriptor marshalAsDescriptor = GetMarshalAsDescriptor(parameter);
                ParameterMetadata data = new ParameterMetadata(parameter.SequenceNumber, (ParameterMetadataAttributes)parameter.Attributes, marshalAsDescriptor);
                parameterMetadataArray[index++] = data;
            }
            return parameterMetadataArray;
        }

        private MarshalAsDescriptor GetMarshalAsDescriptor(Parameter parameter)
        {
            if ((parameter.Attributes & ParameterAttributes.HasFieldMarshal) == ParameterAttributes.HasFieldMarshal)
            {
                MetadataReader metadataReader = MetadataReader;
                BlobReader marshalAsReader = metadataReader.GetBlobReader(parameter.GetMarshallingDescriptor());
                EcmaSignatureParser parser = new EcmaSignatureParser(Module, marshalAsReader, NotFoundBehavior.Throw);
                MarshalAsDescriptor marshalAs = parser.ParseMarshalAsDescriptor();
                Debug.Assert(marshalAs != null);
                return marshalAs;
            }
            return null;
        }

        public override EcmaMethod GetMethodDefinition() => this;

        public override EcmaMethod GetTypicalMethodDefinition() => this;

        public override MethodDesc InstantiateSignature(Instantiation typeInstantiation, Instantiation methodInstantiation) => this;
    }
}