File: System\Delegate.NativeAot.cs
Web Access
Project: 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;

using Internal.Reflection.Augments;
using Internal.Runtime;
using Internal.Runtime.Augments;
using Internal.Runtime.CompilerServices;

namespace System
{
    public abstract partial class Delegate : ICloneable, ISerializable
    {
        private object _helperObject;
        private object _target; // Keep _target and _methodPtr next to each other for optimal delegate invoke performance
        private IntPtr _methodPtr;
        private nint _extraFunctionPointerOrData;

        public object? Target
        {
            get
            {
                // Multi-cast delegates return the Target of the last delegate in the list
                if (TryGetInvocations(out ReadOnlySpan<Wrapper> invocations))
                {
                    return invocations[^1].Value!.Target;
                }

                // Closed static delegates place a value in _helperObject that they pass to the target method.
                if (_methodPtr == GetThunk(ClosedStaticThunk) ||
                    _methodPtr == GetThunk(ClosedInstanceThunkOverGenericMethod) ||
                    _methodPtr == GetThunk(ObjectArrayThunk))
                    return _helperObject;

                // Other non-closed thunks can be identified as the _target field points at this.
                if (ReferenceEquals(_target, this))
                {
                    return null;
                }

                // NativeFunctionPointerWrapper used by marshalled function pointers is not returned as a public target
                if (_target is NativeFunctionPointerWrapper)
                {
                    return null;
                }

                // Closed instance delegates place a value in _target, and we've ruled out all other types of delegates
                return _target;
            }
        }

        [RequiresUnreferencedCode("The target method might be removed")]
        protected Delegate(object target, string method)
        {
            // This constructor cannot be used by application code. To create a delegate by specifying the name of a method, an
            // overload of the public static CreateDelegate method is used. This will eventually end up calling into the internal
            // implementation of CreateDelegate below, and does not invoke this constructor.
            // The constructor is just for API compatibility with the public contract of the Delegate class.
            throw new PlatformNotSupportedException();
        }

        protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type target, string method)
        {
            // This constructor cannot be used by application code. To create a delegate by specifying the name of a method, an
            // overload of the public static CreateDelegate method is used. This will eventually end up calling into the internal
            // implementation of CreateDelegate below, and does not invoke this constructor.
            // The constructor is just for API compatibility with the public contract of the Delegate class.
            throw new PlatformNotSupportedException();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool TryGetInvocations(out ReadOnlySpan<Wrapper> invocations)
        {
            if (HasSingleTarget)
            {
                invocations = default;
                return false;
            }

            Debug.Assert(_helperObject is Wrapper[]);
            Wrapper[] invocationList = (Wrapper[])_helperObject;

            Debug.Assert(invocationList.Length > 1);
            Debug.Assert((uint)invocationList.Length >= (nuint)_extraFunctionPointerOrData);
            Debug.Assert(invocationList[0].Value is not null);

            invocations = new ReadOnlySpan<Wrapper>(invocationList, 0, (int)_extraFunctionPointerOrData);
            return true;
        }

        // WARNING: These constants are also declared in src/coreclr/tools/Common/TypeSystem/IL/DelegateInfo.cs
        // Do not change their values without updating the values in the calling convention converter component
        private const int MulticastThunk = 0;
        private const int ClosedStaticThunk = 1;
        private const int OpenStaticThunk = 2;
        private const int ClosedInstanceThunkOverGenericMethod = 3; // This may not exist
        private const int OpenInstanceThunk = 4;        // This may not exist
        private const int ObjectArrayThunk = 5;         // This may not exist

        // If the thunk does not exist, the function will return IntPtr.Zero.
        private protected virtual IntPtr GetThunk(int whichThunk)
        {
            // NativeAOT doesn't support Universal Shared Code, so let's make this method return null.
            return IntPtr.Zero;
        }

        /// <summary>
        /// The reflection apis use this api to figure out what MethodInfo is related
        /// to a delegate.
        /// </summary>
        /// <param name="typeOfFirstParameterIfInstanceDelegate">
        ///   This value indicates which type an delegate's function pointer is associated with
        ///   This value is ONLY set for delegates where the function pointer points at an instance method
        /// </param>
        /// <param name="isOpenResolver">
        ///   This value indicates if the returned pointer is an open resolver structure.
        /// </param>
        internal unsafe IntPtr GetDelegateLdFtnResult(out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver)
        {
            typeOfFirstParameterIfInstanceDelegate = default;
            isOpenResolver = false;

            if (_extraFunctionPointerOrData != 0)
            {
                if (GetThunk(OpenInstanceThunk) != _methodPtr)
                {
                    return _extraFunctionPointerOrData;
                }

                typeOfFirstParameterIfInstanceDelegate = ((OpenMethodResolver*)_extraFunctionPointerOrData)->DeclaringType;
                isOpenResolver = true;
                return _extraFunctionPointerOrData;
            }

            if (_target != null)
                typeOfFirstParameterIfInstanceDelegate = new RuntimeTypeHandle(_target.GetMethodTable());

            return _methodPtr;
        }

        // This function is known to the compiler.
        private void InitializeClosedInstance(object firstParameter, IntPtr functionPointer)
        {
            if (firstParameter is null)
                throw new ArgumentException(SR.Arg_DlgtNullInst);

            _methodPtr = functionPointer;
            _target = firstParameter;
        }

        // This function is known to the compiler.
        private void InitializeClosedInstanceSlow(object firstParameter, IntPtr functionPointer)
        {
            // This method is like InitializeClosedInstance, but it handles ALL cases. In particular, it handles generic method with fun function pointers.

            if (firstParameter is null)
                throw new ArgumentException(SR.Arg_DlgtNullInst);

            if (!FunctionPointerOps.IsGenericMethodPointer(functionPointer))
            {
                _methodPtr = functionPointer;
                _target = firstParameter;
            }
            else
            {
                _target = this;
                _methodPtr = GetThunk(ClosedInstanceThunkOverGenericMethod);
                _extraFunctionPointerOrData = functionPointer;
                _helperObject = firstParameter;
            }
        }

        // This function is known to the compiler.
        private void InitializeClosedInstanceWithGVMResolution(object firstParameter, IntPtr dispatchCell)
        {
            if (firstParameter is null)
                throw new NullReferenceException();

            IntPtr functionResolution = RuntimeImports.RhpResolveInterfaceMethod(firstParameter, dispatchCell);

            if (functionResolution == IntPtr.Zero)
            {
                // TODO! What to do when GVM resolution fails. Should never happen
                throw new InvalidOperationException();
            }
            if (!FunctionPointerOps.IsGenericMethodPointer(functionResolution))
            {
                _methodPtr = functionResolution;
                _target = firstParameter;
            }
            else
            {
                _target = this;
                _methodPtr = GetThunk(ClosedInstanceThunkOverGenericMethod);
                _extraFunctionPointerOrData = functionResolution;
                _helperObject = firstParameter;
            }
        }

        // This function is known to the compiler.
        private void InitializeClosedInstanceToInterface(object firstParameter, IntPtr dispatchCell)
        {
            if (firstParameter is null)
                throw new NullReferenceException();

            _methodPtr = RuntimeImports.RhpResolveInterfaceMethod(firstParameter, dispatchCell);
            _target = firstParameter;
        }

        // This is used to implement MethodInfo.CreateDelegate() in a desktop-compatible way. Yes, the desktop really
        // let you use that api to invoke an instance method with a null 'this'.
        private void InitializeClosedInstanceWithoutNullCheck(object firstParameter, IntPtr functionPointer)
        {
            if (!FunctionPointerOps.IsGenericMethodPointer(functionPointer))
            {
                _methodPtr = functionPointer;
                _target = firstParameter;
            }
            else
            {
                _target = this;
                _methodPtr = GetThunk(ClosedInstanceThunkOverGenericMethod);
                _extraFunctionPointerOrData = functionPointer;
                _helperObject = firstParameter;
            }
        }

        // This function is known to the compiler.
        private void InitializeClosedStaticThunk(object firstParameter, IntPtr functionPointer, IntPtr functionPointerThunk)
        {
            _extraFunctionPointerOrData = functionPointer;
            _helperObject = firstParameter;
            _methodPtr = functionPointerThunk;
            _target = this;
        }

        // This function is known to the compiler.
        private void InitializeOpenStaticThunk(object _ /*firstParameter*/, IntPtr functionPointer, IntPtr functionPointerThunk)
        {
            // This sort of delegate is invoked by calling the thunk function pointer with the arguments to the delegate + a reference to the delegate object itself.
            _target = this;
            _methodPtr = functionPointerThunk;
            _extraFunctionPointerOrData = functionPointer;
        }

        private void InitializeOpenInstanceThunkDynamic(IntPtr functionPointer, IntPtr functionPointerThunk)
        {
            // This sort of delegate is invoked by calling the thunk function pointer with the arguments to the delegate + a reference to the delegate object itself.
            _target = this;
            _methodPtr = functionPointerThunk;
            _extraFunctionPointerOrData = functionPointer;
        }

        // This function is only ever called by the open instance method thunk, and in that case,
        // _extraFunctionPointerOrData always points to an OpenMethodResolver
        [MethodImpl(MethodImplOptions.NoInlining)]
        private IntPtr GetActualTargetFunctionPointer(object thisObject)
        {
            return OpenMethodResolver.ResolveMethod(_extraFunctionPointerOrData, thisObject);
        }

        [DebuggerGuidedStepThrough]
        protected virtual object? DynamicInvokeImpl(object?[]? args)
        {
            DynamicInvokeInfo dynamicInvokeInfo = ReflectionAugments.GetDelegateDynamicInvokeInfo(GetType());

            object? result = dynamicInvokeInfo.Invoke(_target, _methodPtr,
                args, binderBundle: null, wrapInTargetInvocationException: true);
            DebugAnnotations.PreviousCallContainsDebuggerStepInCode();
            return result;
        }

        protected virtual MethodInfo GetMethodImpl()
        {
            // NOTE: this implementation is mirrored in GetDiagnosticMethodInfo below

            // Multi-cast delegates return the Method of the last delegate in the list
            if (TryGetInvocations(out ReadOnlySpan<Wrapper> invocations))
            {
                return invocations[^1].Value!.GetMethodImpl();
            }

            // Return the delegate Invoke method for marshalled function pointers and LINQ expressions
            if (_target is NativeFunctionPointerWrapper || _methodPtr == GetThunk(ObjectArrayThunk))
            {
                return GetInvokeMethod(GetType());
            }

            return ReflectionAugments.GetDelegateMethod(this);
        }

        internal DiagnosticMethodInfo GetDiagnosticMethodInfo()
        {
            // NOTE: this implementation is mirrored in GetMethodImpl above

            // Multi-cast delegates return the diagnostic method info of the last delegate in the list
            if (TryGetInvocations(out ReadOnlySpan<Wrapper> invocations))
            {
                return invocations[^1].Value!.GetDiagnosticMethodInfo();
            }

            // Return the delegate Invoke method for marshalled function pointers and LINQ expressions
            if (_target is NativeFunctionPointerWrapper || _methodPtr == GetThunk(ObjectArrayThunk))
            {
                Type t = GetType();
                return new DiagnosticMethodInfo("Invoke", t.FullName, t.Module.Assembly.FullName);
            }

            IntPtr ldftnResult = GetDelegateLdFtnResult(out RuntimeTypeHandle _, out bool isOpenResolver);
            if (isOpenResolver)
            {
                MethodInfo mi = ReflectionAugments.GetDelegateMethod(this);
                Type? declaringType = mi.DeclaringType;
                if (declaringType.IsConstructedGenericType)
                    declaringType = declaringType.GetGenericTypeDefinition();
                return new DiagnosticMethodInfo(mi.Name, declaringType.FullName, mi.Module.Assembly.FullName);
            }

            IntPtr functionPointer;
            if (FunctionPointerOps.IsGenericMethodPointer(ldftnResult))
            {
                unsafe
                {
                    GenericMethodDescriptor* realTargetData = FunctionPointerOps.ConvertToGenericDescriptor(ldftnResult);
                    functionPointer = RuntimeAugments.GetCodeTarget(realTargetData->MethodFunctionPointer);
                }
            }
            else
            {
                nint unboxedPointer = RuntimeAugments.GetCodeTarget(ldftnResult);
                if (unboxedPointer == ldftnResult)
                    unboxedPointer = RuntimeAugments.GetTargetOfUnboxingAndInstantiatingStub(ldftnResult);

                functionPointer = unboxedPointer != 0 ? unboxedPointer : ldftnResult;
            }
            return RuntimeAugments.StackTraceCallbacksIfAvailable?.TryGetDiagnosticMethodInfoFromStartAddress(functionPointer);
        }

        public static Delegate CreateDelegate(Type type, object? firstArgument, MethodInfo method, bool throwOnBindFailure) =>
            ReflectionAugments.CreateDelegate(type, firstArgument, method, throwOnBindFailure);

        public static Delegate CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure) =>
            ReflectionAugments.CreateDelegate(type, method, throwOnBindFailure);

        [RequiresUnreferencedCode("The target method might be removed")]
        public static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure) =>
            ReflectionAugments.CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure);

        public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type target, string method, bool ignoreCase, bool throwOnBindFailure) =>
            ReflectionAugments.CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure);

        internal IntPtr TryGetOpenStaticFunctionPointer() => GetThunk(OpenStaticThunk) == _methodPtr ? _extraFunctionPointerOrData : 0;

        internal NativeFunctionPointerWrapper? TryGetNativeFunctionPointerWrapper() => _target as NativeFunctionPointerWrapper;

        // Returns a new delegate of the specified type whose implementation is provided by the
        // provided delegate.
        internal static unsafe Delegate CreateObjectArrayDelegate(Type t, Func<object?[], object?> handler)
        {
            RuntimeTypeHandle typeHandle = t.TypeHandle;

            MethodTable* delegateEEType = typeHandle.ToMethodTable();
            Debug.Assert(delegateEEType != null);
            Debug.Assert(delegateEEType->IsCanonical);

            RuntimeAugments.EnsureMethodTableSafeToAllocate(delegateEEType);

            Delegate del = (Delegate)RuntimeImports.RhNewObject(delegateEEType);

            IntPtr objArrayThunk = del.GetThunk(ObjectArrayThunk);
            if (objArrayThunk == IntPtr.Zero)
            {
                throw new InvalidOperationException();
            }

            del._helperObject = handler;
            del._methodPtr = objArrayThunk;
            del._target = del;
            return del;
        }

        //
        // Internal (and quite unsafe) helper to create delegates of an arbitrary type. This is used to support Reflection invoke.
        //
        // Note that delegates constructed the normal way do not come through here. The IL transformer generates the equivalent of
        // this code customized for each delegate type.
        //
        internal static unsafe Delegate CreateDelegate(MethodTable* delegateEEType, IntPtr ldftnResult, object thisObject, bool isStatic, bool isOpen)
        {
            RuntimeAugments.EnsureMethodTableSafeToAllocate(delegateEEType);

            Delegate del = (Delegate)RuntimeImports.RhNewObject(delegateEEType);

            // What? No constructor call? That's right, and it's not an oversight. All "construction" work happens in
            // the Initialize() methods. This helper has a hard dependency on this invariant.

            if (isStatic)
            {
                if (isOpen)
                {
                    IntPtr thunk = del.GetThunk(OpenStaticThunk);
                    del.InitializeOpenStaticThunk(null, ldftnResult, thunk);
                }
                else
                {
                    IntPtr thunk = del.GetThunk(ClosedStaticThunk);
                    del.InitializeClosedStaticThunk(thisObject, ldftnResult, thunk);
                }
            }
            else
            {
                if (isOpen)
                {
                    IntPtr thunk = del.GetThunk(OpenInstanceThunk);
                    del.InitializeOpenInstanceThunkDynamic(ldftnResult, thunk);
                }
                else
                {
                    del.InitializeClosedInstanceWithoutNullCheck(thisObject, ldftnResult);
                }
            }
            return del;
        }

        private unsafe Delegate NewMulticastDelegate(Wrapper[] invocationList, int invocationCount, bool thisIsMultiCastAlready = false)
        {
            // First, allocate a new delegate just like this one, i.e. same type as the this object
            Delegate result = Unsafe.As<Delegate>(RuntimeImports.RhNewObject(this.GetMethodTable()));

            // Performance optimization - if this already points to a true multicast delegate,
            // copy _methodPtr field rather than calling GetThunk to get it
            result._methodPtr = thisIsMultiCastAlready ? _methodPtr : GetThunk(MulticastThunk);
            result._target = result;
            result._helperObject = invocationList;
            result._extraFunctionPointerOrData = invocationCount;

            return result;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool SlotEquals(Delegate previous, Delegate o) =>
            ReferenceEquals(previous._target, o._target) &&
            ReferenceEquals(previous._helperObject, o._helperObject) &&
            previous._extraFunctionPointerOrData == o._extraFunctionPointerOrData &&
            previous._methodPtr == o._methodPtr;

        private bool EqualsCore(Delegate other)
        {
            Debug.Assert(RuntimeHelpers.AreTypesEquivalent(this, other));

            if (TryGetInvocations(out ReadOnlySpan<Wrapper> invocations))
                return other.TryGetInvocations(out ReadOnlySpan<Wrapper> otherInvocations) && invocations.SequenceEqual(otherInvocations);

            if (_target is NativeFunctionPointerWrapper nativeFunctionPointerWrapper)
            {
                if (other._target is not NativeFunctionPointerWrapper dnativeFunctionPointerWrapper)
                    return false;

                return nativeFunctionPointerWrapper.NativeFunctionPointer == dnativeFunctionPointerWrapper.NativeFunctionPointer;
            }

            if (!ReferenceEquals(_helperObject, other._helperObject) ||
                !FunctionPointerOps.Compare(_extraFunctionPointerOrData, other._extraFunctionPointerOrData) ||
                !FunctionPointerOps.Compare(_methodPtr, other._methodPtr))
            {
                return false;
            }

            // Those delegate kinds with thunks put themselves into the _target, so we can't
            // blindly compare the _target fields for equality.
            // Check if both delegates have themselves there then.
            object target = ReferenceEquals(_target, this) ? other : _target;
            return ReferenceEquals(target, other._target);
        }

        public sealed override unsafe int GetHashCode()
        {
            if (TryGetInvocations(out ReadOnlySpan<Wrapper> invocations))
            {
                int hash = 0;
                foreach (ref readonly Wrapper wrapper in invocations)
                {
                    hash = hash * 33 + wrapper.GetHashCode();
                }
                return hash;
            }

            MethodTable* methodTable = this.GetMethodTable();
            if (_target is NativeFunctionPointerWrapper nativeFunctionPointerWrapper)
            {
                return HashCode.Combine((nuint)methodTable, nativeFunctionPointerWrapper.NativeFunctionPointer);
            }

            return HashCode.Combine((nuint)methodTable,
                RuntimeHelpers.GetHashCode(_helperObject),
                FunctionPointerOps.GetHashCode(_extraFunctionPointerOrData),
                FunctionPointerOps.GetHashCode(_methodPtr),
                ReferenceEquals(_target, this) ? 0 : RuntimeHelpers.GetHashCode(_target));
        }
    }
}