File: System\Reflection\Runtime\MethodInfos\RuntimeMethodInfo.cs
Web Access
Project: src\src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Reflection.Runtime.BindingFlagSupport;
using System.Reflection.Runtime.General;
using System.Reflection.Runtime.ParameterInfos;
using System.Reflection.Runtime.TypeInfos;
using System.Runtime.CompilerServices;

using Internal.Reflection.Core.Execution;
using Internal.Runtime.Augments;

namespace System.Reflection.Runtime.MethodInfos
{
    //
    // Abstract base class for RuntimeNamedMethodInfo, RuntimeConstructedGenericMethodInfo.
    //
    internal abstract partial class RuntimeMethodInfo : MethodInfo
    {
        public abstract override MethodAttributes Attributes
        {
            get;
        }

        public abstract override CallingConventions CallingConvention
        {
            get;
        }

        public sealed override bool ContainsGenericParameters
        {
            get
            {
                if (DeclaringType.ContainsGenericParameters)
                    return true;

                if (!IsGenericMethod)
                    return false;

                Type[] pis = GetGenericArguments();
                for (int i = 0; i < pis.Length; i++)
                {
                    if (pis[i].ContainsGenericParameters)
                        return true;
                }

                return false;
            }
        }

        // V4.5 api - Creates open delegates over static or instance methods.
        public sealed override Delegate CreateDelegate(Type delegateType)
        {
            return CreateDelegateWorker(delegateType, null, allowClosed: false);
        }

        // V4.5 api - Creates open or closed delegates over static or instance methods.
        public sealed override Delegate CreateDelegate(Type delegateType, object target)
        {
            return CreateDelegateWorker(delegateType, target, allowClosed: true);
        }

        private Delegate CreateDelegateWorker(Type delegateType, object target, bool allowClosed)
        {
            ArgumentNullException.ThrowIfNull(delegateType);

            if (!(delegateType is RuntimeType runtimeDelegateType))
                throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(delegateType));

            RuntimeTypeInfo runtimeDelegateTypeInfo = runtimeDelegateType.ToRuntimeTypeInfo();

            if (!runtimeDelegateTypeInfo.IsDelegate)
                throw new ArgumentException(SR.Arg_MustBeDelegate);

            Delegate result = CreateDelegateNoThrowOnBindFailure(runtimeDelegateTypeInfo, target, allowClosed);
            if (result == null)
                throw new ArgumentException(SR.Arg_DlgtTargMeth);
            return result;
        }

        public abstract override IEnumerable<CustomAttributeData> CustomAttributes
        {
            get;
        }

        public sealed override Type DeclaringType
        {
            get
            {
                return this.RuntimeDeclaringType.ToType();
            }
        }

        public abstract override bool Equals(object obj);

        public abstract override int GetHashCode();

        public sealed override MethodInfo GetBaseDefinition()
        {
            // This check is for compatibility. Yes, it happens before we normalize constructed generic methods back to their backing definition.
            Type declaringType = DeclaringType;
            if (!IsVirtual || IsStatic || declaringType == null || declaringType.IsInterface)
                return this;

            MethodInfo method = this;

            // For compat: Remove any instantiation on generic methods.
            if (method.IsConstructedGenericMethod)
                method = method.GetGenericMethodDefinition();

            while (true)
            {
                MethodInfo next = method.GetImplicitlyOverriddenBaseClassMember(MethodPolicies.Instance);
                if (next == null)
                    return ((RuntimeMethodInfo)method).WithReflectedTypeSetToDeclaringType;

                method = next;
            }
        }

        public sealed override Type[] GetGenericArguments()
        {
            return RuntimeGenericArgumentsOrParameters.ToTypeArray();
        }

        internal abstract override int GenericParameterCount { get; }

        public abstract override MethodInfo GetGenericMethodDefinition();

        [RequiresUnreferencedCode("Trimming may change method bodies. For example it can change some instructions, remove branches or local variables.")]
        public sealed override MethodBody GetMethodBody()
        {
            throw new PlatformNotSupportedException();
        }

        public sealed override ParameterInfo[] GetParameters()
        {
            RuntimeParameterInfo[] runtimeParameterInfos = RuntimeParameters;
            if (runtimeParameterInfos.Length == 0)
                return Array.Empty<ParameterInfo>();
            ParameterInfo[] result = new ParameterInfo[runtimeParameterInfos.Length];
            for (int i = 0; i < result.Length; i++)
                result[i] = runtimeParameterInfos[i];
            return result;
        }

        public sealed override ReadOnlySpan<ParameterInfo> GetParametersAsSpan()
        {
            return RuntimeParameters;
        }

        public abstract override bool HasSameMetadataDefinitionAs(MemberInfo other);

        [DebuggerGuidedStepThrough]
        public sealed override object? Invoke(object? obj, BindingFlags invokeAttr, Binder binder, object?[]? parameters, CultureInfo culture)
        {
            MethodBaseInvoker methodInvoker = this.MethodInvoker;
            object? result = methodInvoker.Invoke(obj, parameters, binder, invokeAttr, culture);
            DebugAnnotations.PreviousCallContainsDebuggerStepInCode();
            return result;
        }

        public abstract override bool IsConstructedGenericMethod
        {
            get;
        }

        public abstract override bool IsGenericMethod
        {
            get;
        }

        public abstract override bool IsGenericMethodDefinition
        {
            get;
        }

        [RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")]
        [RequiresUnreferencedCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")]
        public abstract override MethodInfo MakeGenericMethod(params Type[] typeArguments);

        internal abstract override MethodBase MetadataDefinitionMethod { get; }

        public abstract override int MetadataToken
        {
            get;
        }

        public abstract override MethodImplAttributes MethodImplementationFlags
        {
            get;
        }

        public abstract override Module Module
        {
            get;
        }

        public sealed override string Name
        {
            get
            {
                return this.RuntimeName;
            }
        }

        public abstract override Type ReflectedType { get; }

        public sealed override ParameterInfo ReturnParameter
        {
            get
            {
                return this.RuntimeReturnParameter;
            }
        }

        public sealed override Type ReturnType
        {
            get
            {
                return ReturnParameter.ParameterType;
            }
        }

        public abstract override string ToString();

        public abstract override RuntimeMethodHandle MethodHandle { get; }

        internal abstract RuntimeTypeInfo RuntimeDeclaringType
        {
            get;
        }

        internal abstract string RuntimeName
        {
            get;
        }

        internal abstract RuntimeMethodInfo WithReflectedTypeSetToDeclaringType { get; }

        protected abstract MethodBaseInvoker UncachedMethodInvoker { get; }

        //
        // The non-public version of MethodInfo.GetGenericArguments() (does not array-copy and has a more truthful name.)
        //
        internal abstract RuntimeTypeInfo[] RuntimeGenericArgumentsOrParameters { get; }

        internal abstract RuntimeParameterInfo[] GetRuntimeParameters(RuntimeMethodInfo contextMethod, out RuntimeParameterInfo returnParameter);

        //
        // The non-public version of MethodInfo.GetParameters() (does not array-copy.)
        //
        internal RuntimeParameterInfo[] RuntimeParameters
        {
            get
            {
                RuntimeParameterInfo[] parameters = _lazyParameters;
                if (parameters == null)
                {
                    RuntimeParameterInfo returnParameter;
                    parameters = _lazyParameters = GetRuntimeParameters(this, out returnParameter);
                    _lazyReturnParameter = returnParameter;  // Opportunistically initialize the _lazyReturnParameter latch as well.
                }
                return parameters;
            }
        }

        internal RuntimeParameterInfo RuntimeReturnParameter
        {
            get
            {
                RuntimeParameterInfo returnParameter = _lazyReturnParameter;
                if (returnParameter == null)
                {
                    // Though the returnParameter is our primary objective, we can opportunistically initialize the _lazyParameters latch too.
                    _lazyParameters = GetRuntimeParameters(this, out returnParameter);
                    _lazyReturnParameter = returnParameter;
                }
                return returnParameter;
            }
        }

        private volatile RuntimeParameterInfo[] _lazyParameters;
        private volatile RuntimeParameterInfo _lazyReturnParameter;

        internal MethodBaseInvoker MethodInvoker
        {
            get
            {
                return _lazyMethodInvoker ??= UncachedMethodInvoker;
            }
        }

        internal IntPtr LdFtnResult => MethodInvoker.LdFtnResult;

        private volatile MethodBaseInvoker _lazyMethodInvoker;

        /// <summary>
        /// Common CreateDelegate worker. NOTE: If the method signature is not compatible, this method returns null rather than throwing an ArgumentException.
        /// This is needed to support the api overloads that have a "throwOnBindFailure" parameter.
        /// </summary>
        internal Delegate CreateDelegateNoThrowOnBindFailure(RuntimeTypeInfo runtimeDelegateType, object target, bool allowClosed)
        {
            Debug.Assert(runtimeDelegateType.IsDelegate);

            RuntimeMethodInfo invokeMethod = runtimeDelegateType.GetInvokeMethod();

            // Make sure the return type is assignment-compatible.
            Type expectedReturnType = ReturnParameter.ParameterType;
            Type actualReturnType = invokeMethod.ReturnParameter.ParameterType;
            if (!IsAssignableFrom(actualReturnType, expectedReturnType))
                return null;
            if (expectedReturnType.IsValueType && !actualReturnType.IsValueType)
            {
                // For value type returning methods, conversions between enums and primitives are allowed (and screened by the above call to IsAssignableFrom)
                // but conversions to Object or interfaces implemented by the value type are not.
                return null;
            }

            ReadOnlySpan<ParameterInfo> delegateParameters = invokeMethod.GetParametersAsSpan();
            ReadOnlySpan<ParameterInfo> targetParameters = this.GetParametersAsSpan();
            ReadOnlySpan<ParameterInfo>.Enumerator delegateParameterEnumerator = delegateParameters.GetEnumerator();
            ReadOnlySpan<ParameterInfo>.Enumerator targetParameterEnumerator = targetParameters.GetEnumerator();

            bool isStatic = this.IsStatic;
            bool isOpen;
            if (isStatic)
            {
                if (delegateParameters.Length == targetParameters.Length)
                {
                    // Open static: This is the "typical" case of calling a static method.
                    isOpen = true;
                    if (target != null)
                        return null;
                }
                else
                {
                    // Closed static: This is the "weird" v2.0 case where the delegate is closed over the target method's first parameter.
                    //   (it make some kinda sense if you think of extension methods.)
                    if (!allowClosed)
                        return null;
                    isOpen = false;
                    if (!targetParameterEnumerator.MoveNext())
                        return null;
                    if (target != null && !IsAssignableFrom(targetParameterEnumerator.Current.ParameterType, target.GetType()))
                        return null;
                }
            }
            else
            {
                if (delegateParameters.Length == targetParameters.Length)
                {
                    // Closed instance: This is the "typical" case of invoking an instance method.
                    isOpen = false;
                    if (!allowClosed)
                        return null;
                    if (target != null && !IsAssignableFrom(this.DeclaringType, target.GetType()))
                        return null;
                }
                else
                {
                    // Open instance: This is the "weird" v2.0 case where the delegate has a leading extra parameter that's assignable to the target method's
                    // declaring type.
                    if (!delegateParameterEnumerator.MoveNext())
                        return null;
                    isOpen = true;
                    Type firstParameterOfMethodType = this.DeclaringType;
                    if (firstParameterOfMethodType.IsValueType)
                        firstParameterOfMethodType = firstParameterOfMethodType.MakeByRefType();

                    if (!IsAssignableFrom(firstParameterOfMethodType, delegateParameterEnumerator.Current.ParameterType))
                        return null;
                    if (target != null)
                        return null;
                }
            }

            // Verify that the parameters that the delegate and method have in common are assignment-compatible.
            while (delegateParameterEnumerator.MoveNext())
            {
                if (!targetParameterEnumerator.MoveNext())
                    return null;
                if (!IsAssignableFrom(targetParameterEnumerator.Current.ParameterType, delegateParameterEnumerator.Current.ParameterType))
                    return null;
            }
            if (targetParameterEnumerator.MoveNext())
                return null;

            return CreateDelegateWithoutSignatureValidation(runtimeDelegateType.ToType(), target, isStatic: isStatic, isOpen: isOpen);
        }

        internal Delegate CreateDelegateWithoutSignatureValidation(Type delegateType, object target, bool isStatic, bool isOpen)
        {
            return MethodInvoker.CreateDelegate(delegateType.TypeHandle, target, isStatic: isStatic, isVirtual: false, isOpen: isOpen);
        }

        private static bool IsAssignableFrom(Type dstType, Type srcType)
        {
            // byref types do not have a TypeHandle so we must treat these separately.
            if (dstType.IsByRef && srcType.IsByRef)
            {
                if (!dstType.Equals(srcType))
                    return false;
            }

            // Enable pointers (which don't necessarily have typehandles). todo:be able to handle intptr <-> pointer, check if we need to handle
            // casts via pointer where the pointer types aren't identical
            if (dstType.Equals(srcType))
            {
                return true;
            }

            // If assignment compatible in the normal way, allow
            if (RuntimeAugments.IsAssignableFrom(dstType.TypeHandle, srcType.TypeHandle))
            {
                return true;
            }

            // they are not compatible yet enums can go into each other if their underlying element type is the same
            // or into their equivalent integral type
            Type dstTypeUnderlying = dstType;
            if (dstType.IsEnum)
            {
                dstTypeUnderlying = Enum.GetUnderlyingType(dstType);
            }
            Type srcTypeUnderlying = srcType;
            if (srcType.IsEnum)
            {
                srcTypeUnderlying = Enum.GetUnderlyingType(srcType);
            }
            if (dstTypeUnderlying.Equals(srcTypeUnderlying))
            {
                return true;
            }

            return false;
        }

        protected RuntimeMethodInfo WithDebugName()
        {
#if DEBUG
            if (_debugName == null)
            {
                _debugName = "Constructing..."; // Protect against any inadvertent reentrancy.
                _debugName = RuntimeName;
            }
#endif
            return this;
        }

#if DEBUG
        private string _debugName;
#endif
    }
}