From fa3d1ec707ec67cb6cdf99a32f2c0564d87725ca Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 27 Feb 2024 11:11:17 -0800 Subject: [PATCH] [NativeAOT] Delegate bug fixes - Fix Delegate.Method and Delegate.Target for marshalled delegates - Add tests and fixes for corner various delegate corner case behaviors - Delete runtime test for GetInvocationList since there is a better test coverage for this API under libraries Fixes https://github.com/dotnet/runtimelab/issues/164 --- .../Runtime/Augments/RuntimeAugments.cs | 6 +- .../src/System/Delegate.cs | 310 ++++++++---------- .../Runtime/InteropServices/PInvokeMarshal.cs | 92 +++--- .../DelegateMethodInfoRetriever.cs | 21 +- .../src/Resources/Strings.resx | 3 - .../System/MulticastDelegateTests.cs | 25 +- .../delegate/delegate/DelegateCombine1.csproj | 4 +- .../delegate/DelegateCombineImpl.csproj | 4 +- .../delegate/delegate/DelegateEquals1.csproj | 3 - .../delegate/DelegateGetHashCode1.csproj | 3 - .../DelegateGetInvocationList1.csproj | 14 - .../delegate/delegate/DelegateRemove.csproj | 3 - .../delegate/delegateRemoveImpl.csproj | 3 - .../delegate/delegategetinvocationlist1.cs | 230 ------------- .../FunctionPointer/FunctionPointer.cs | 10 +- 15 files changed, 211 insertions(+), 520 deletions(-) delete mode 100644 src/tests/CoreMangLib/system/delegate/delegate/DelegateGetInvocationList1.csproj delete mode 100644 src/tests/CoreMangLib/system/delegate/delegate/delegategetinvocationlist1.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index 441036959931d..8421eea29887e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -138,11 +138,11 @@ public static Delegate CreateDelegate(RuntimeTypeHandle typeHandleForDelegate, I } // - // Helper to extract the artifact that uniquely identifies a method in the runtime mapping tables. + // Helper to extract the artifact that identifies a reflectable delegate target in the runtime mapping tables. // - public static IntPtr GetDelegateLdFtnResult(Delegate d, out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver, out bool isInterpreterEntrypoint) + public static IntPtr GetDelegateLdFtnResult(Delegate d, out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver) { - return d.GetFunctionPointer(out typeOfFirstParameterIfInstanceDelegate, out isOpenResolver, out isInterpreterEntrypoint); + return d.GetDelegateLdFtnResult(out typeOfFirstParameterIfInstanceDelegate, out isOpenResolver); } // Low level method that returns the loaded modules as array. ReadOnlySpan returning overload diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs index 1c565a045b826..9de1b4587081a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Delegate.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Runtime; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using Internal.Reflection.Augments; @@ -45,6 +46,15 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al private nint m_extraFunctionPointerOrData; private IntPtr m_functionPointer; + // m_helperObject may point to an array of delegates if this is a multicast delegate. We use this wrapper to distinguish between + // our own array of delegates and user provided Wrapper[]. As a added benefit, this wrapper also eliminates array co-variance + // overhead for our own array of delegates. + private struct Wrapper + { + public Wrapper(Delegate value) => Value = value; + public Delegate Value; + } + // WARNING: These constants are also declared in System.Private.TypeLoader\Internal\Runtime\TypeLoader\CallConverterThunk.cs // Do not change their values without updating the values in the calling convention converter component private protected const int MulticastThunk = 0; @@ -63,14 +73,8 @@ private protected virtual IntPtr GetThunk(int whichThunk) } /// - /// Used by various parts of the runtime as a replacement for Delegate.Method - /// - /// The Interop layer uses this to distinguish between different methods on a - /// single type, and to get the function pointer for delegates to static functions - /// /// The reflection apis use this api to figure out what MethodInfo is related /// to a delegate. - /// /// /// /// This value indicates which type an delegate's function pointer is associated with @@ -79,27 +83,12 @@ private protected virtual IntPtr GetThunk(int whichThunk) /// /// This value indicates if the returned pointer is an open resolver structure. /// - /// - /// Delegate points to an object array thunk (the delegate wraps a Func<object[], object> delegate). This - /// is typically a delegate pointing to the LINQ expression interpreter. - /// - /// - internal unsafe IntPtr GetFunctionPointer(out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver, out bool isInterpreterEntrypoint) + internal unsafe IntPtr GetDelegateLdFtnResult(out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver) { typeOfFirstParameterIfInstanceDelegate = default(RuntimeTypeHandle); isOpenResolver = false; - isInterpreterEntrypoint = false; - if (GetThunk(MulticastThunk) == m_functionPointer) - { - return IntPtr.Zero; - } - else if (GetThunk(ObjectArrayThunk) == m_functionPointer) - { - isInterpreterEntrypoint = true; - return IntPtr.Zero; - } - else if (m_extraFunctionPointerOrData != 0) + if (m_extraFunctionPointerOrData != 0) { if (GetThunk(OpenInstanceThunk) == m_functionPointer) { @@ -113,13 +102,11 @@ internal unsafe IntPtr GetFunctionPointer(out RuntimeTypeHandle typeOfFirstParam if (m_firstParameter != null) typeOfFirstParameterIfInstanceDelegate = new RuntimeTypeHandle(m_firstParameter.GetMethodTable()); - // TODO! Implementation issue for generic invokes here ... we need another IntPtr for uniqueness. - return m_functionPointer; } } - // This function is known to the IL Transformer. + // This function is known to the compiler. private void InitializeClosedInstance(object firstParameter, IntPtr functionPointer) { if (firstParameter is null) @@ -129,7 +116,7 @@ private void InitializeClosedInstance(object firstParameter, IntPtr functionPoin m_firstParameter = firstParameter; } - // This function is known to the IL Transformer. + // 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. @@ -180,6 +167,7 @@ private void InitializeClosedInstanceWithGVMResolution(object firstParameter, Ru return; } + // This function is known to the compiler. private void InitializeClosedInstanceToInterface(object firstParameter, IntPtr dispatchCell) { if (firstParameter is null) @@ -207,7 +195,7 @@ private void InitializeClosedInstanceWithoutNullCheck(object firstParameter, Int } } - // This function is known to the compiler backend. + // This function is known to the compiler. private void InitializeClosedStaticThunk(object firstParameter, IntPtr functionPointer, IntPtr functionPointerThunk) { m_extraFunctionPointerOrData = functionPointer; @@ -216,7 +204,7 @@ private void InitializeClosedStaticThunk(object firstParameter, IntPtr functionP m_firstParameter = this; } - // This function is known to the compiler backend. + // 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. @@ -241,15 +229,7 @@ private IntPtr GetActualTargetFunctionPointer(object thisObject) return OpenMethodResolver.ResolveMethod(m_extraFunctionPointerOrData, thisObject); } - internal bool IsDynamicDelegate() - { - if (this.GetThunk(MulticastThunk) == IntPtr.Zero) - { - return true; - } - - return false; - } + internal bool IsDynamicDelegate() => GetThunk(MulticastThunk) == IntPtr.Zero; [DebuggerGuidedStepThroughAttribute] protected virtual object? DynamicInvokeImpl(object?[]? args) @@ -274,19 +254,31 @@ internal bool IsDynamicDelegate() protected virtual MethodInfo GetMethodImpl() { + // Multi-cast delegates return the Method of the last delegate in the list + if (m_helperObject is Wrapper[] invocationList) + { + int invocationCount = (int)m_extraFunctionPointerOrData; + return invocationList[invocationCount - 1].Value.GetMethodImpl(); + } + + // Return the delegate Invoke method for marshalled function pointers and LINQ expressions + if ((m_firstParameter is NativeFunctionPointerWrapper) || (m_functionPointer == GetThunk(ObjectArrayThunk))) + { + return GetType().GetMethod("Invoke"); + } + return ReflectionAugments.ReflectionCoreCallbacks.GetDelegateMethod(this); } - public object Target + public object? Target { get { // Multi-cast delegates return the Target of the last delegate in the list - if (m_functionPointer == GetThunk(MulticastThunk)) + if (m_helperObject is Wrapper[] invocationList) { - Delegate[] invocationList = (Delegate[])m_helperObject; int invocationCount = (int)m_extraFunctionPointerOrData; - return invocationList[invocationCount - 1].Target; + return invocationList[invocationCount - 1].Value.Target; } // Closed static delegates place a value in m_helperObject that they pass to the target method. @@ -301,6 +293,12 @@ public object Target return null; } + // NativeFunctionPointerWrapper used by marshalled function pointers is not returned as a public target + if (m_firstParameter is NativeFunctionPointerWrapper) + { + return null; + } + // Closed instance delegates place a value in m_firstParameter, and we've ruled out all other types of delegates return m_firstParameter; } @@ -319,13 +317,9 @@ public object Target // V1 api: Creates open delegates to static methods only, relaxed signature checking disallowed. public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method, bool ignoreCase, bool throwOnBindFailure) => ReflectionAugments.ReflectionCoreCallbacks.CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure); - internal bool IsOpenStatic - { - get - { - return GetThunk(OpenStaticThunk) == m_functionPointer; - } - } + internal IntPtr TryGetOpenStaticFunctionPointer() => (GetThunk(OpenStaticThunk) == m_functionPointer) ? m_extraFunctionPointerOrData : 0; + + internal NativeFunctionPointerWrapper? TryGetNativeFunctionPointerWrapper() => m_firstParameter as NativeFunctionPointerWrapper; internal static unsafe bool InternalEqualTypes(object a, object b) { @@ -397,21 +391,14 @@ internal static unsafe Delegate CreateDelegate(MethodTable* delegateEEType, IntP return del; } - private unsafe MulticastDelegate NewMulticastDelegate(Delegate[] invocationList, int invocationCount, bool thisIsMultiCastAlready = false) + private unsafe Delegate NewMulticastDelegate(Wrapper[] invocationList, int invocationCount, bool thisIsMultiCastAlready = false) { - // First, allocate a new multicast delegate just like this one, i.e. same type as the this object - MulticastDelegate result = (MulticastDelegate)RuntimeImports.RhNewObject(this.GetMethodTable()); + // First, allocate a new delegate just like this one, i.e. same type as the this object + Delegate result = Unsafe.As(RuntimeImports.RhNewObject(this.GetMethodTable())); // Performance optimization - if this already points to a true multicast delegate, - // copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them - if (thisIsMultiCastAlready) - { - result.m_functionPointer = this.m_functionPointer; - } - else - { - result.m_functionPointer = GetThunk(MulticastThunk); - } + // copy m_functionPointer field rather than calling GetThunk to get it + result.m_functionPointer = thisIsMultiCastAlready ? m_functionPointer : GetThunk(MulticastThunk); result.m_firstParameter = result; result.m_helperObject = invocationList; result.m_extraFunctionPointerOrData = (IntPtr)invocationCount; @@ -419,22 +406,19 @@ private unsafe MulticastDelegate NewMulticastDelegate(Delegate[] invocationList, return result; } - private static bool TrySetSlot(Delegate[] a, int index, Delegate o) + private static bool TrySetSlot(Wrapper[] a, int index, Delegate o) { - if (a[index] == null && System.Threading.Interlocked.CompareExchange(ref a[index], o, null) == null) + if (a[index].Value == null && System.Threading.Interlocked.CompareExchange(ref a[index].Value, o, null) == null) return true; // The slot may be already set because we have added and removed the same method before. // Optimize this case, because it's cheaper than copying the array. - if (a[index] != null) + if (a[index].Value is Delegate dd) { - MulticastDelegate d = (MulticastDelegate)o; - MulticastDelegate dd = (MulticastDelegate)a[index]; - - if (object.ReferenceEquals(dd.m_firstParameter, d.m_firstParameter) && - object.ReferenceEquals(dd.m_helperObject, d.m_helperObject) && - dd.m_extraFunctionPointerOrData == d.m_extraFunctionPointerOrData && - dd.m_functionPointer == d.m_functionPointer) + if (object.ReferenceEquals(dd.m_firstParameter, o.m_firstParameter) && + object.ReferenceEquals(dd.m_helperObject, o.m_helperObject) && + dd.m_extraFunctionPointerOrData == o.m_extraFunctionPointerOrData && + dd.m_functionPointer == o.m_functionPointer) { return true; } @@ -446,35 +430,31 @@ private static bool TrySetSlot(Delegate[] a, int index, Delegate o) // to form a new delegate. protected virtual Delegate CombineImpl(Delegate? d) { - if (d is null) // cast to object for a more efficient test + if (d is null) return this; // Verify that the types are the same... if (!InternalEqualTypes(this, d)) throw new ArgumentException(SR.Arg_DlgtTypeMis); - if (IsDynamicDelegate() && d.IsDynamicDelegate()) - { + if (IsDynamicDelegate()) throw new InvalidOperationException(); - } - MulticastDelegate dFollow = (MulticastDelegate)d; - Delegate[]? resultList; int followCount = 1; - Delegate[]? followList = dFollow.m_helperObject as Delegate[]; + Wrapper[]? followList = d.m_helperObject as Wrapper[]; if (followList != null) - followCount = (int)dFollow.m_extraFunctionPointerOrData; + followCount = (int)d.m_extraFunctionPointerOrData; int resultCount; - Delegate[]? invocationList = m_helperObject as Delegate[]; - if (invocationList == null) + Wrapper[]? resultList; + if (m_helperObject is not Wrapper[] invocationList) { resultCount = 1 + followCount; - resultList = new Delegate[resultCount]; - resultList[0] = this; + resultList = new Wrapper[resultCount]; + resultList[0] = new Wrapper(this); if (followList == null) { - resultList[1] = dFollow; + resultList[1] = new Wrapper(d); } else { @@ -493,14 +473,14 @@ protected virtual Delegate CombineImpl(Delegate? d) resultList = invocationList; if (followList == null) { - if (!TrySetSlot(resultList, invocationCount, dFollow)) + if (!TrySetSlot(resultList, invocationCount, d)) resultList = null; } else { for (int i = 0; i < followCount; i++) { - if (!TrySetSlot(resultList, invocationCount + i, followList[i])) + if (!TrySetSlot(resultList, invocationCount + i, followList[i].Value)) { resultList = null; break; @@ -515,14 +495,14 @@ protected virtual Delegate CombineImpl(Delegate? d) while (allocCount < resultCount) allocCount *= 2; - resultList = new Delegate[allocCount]; + resultList = new Wrapper[allocCount]; for (int i = 0; i < invocationCount; i++) resultList[i] = invocationList[i]; if (followList == null) { - resultList[invocationCount] = dFollow; + resultList[invocationCount] = new Wrapper(d); } else { @@ -534,14 +514,13 @@ protected virtual Delegate CombineImpl(Delegate? d) } } - private Delegate[] DeleteFromInvocationList(Delegate[] invocationList, int invocationCount, int deleteIndex, int deleteCount) + private static Wrapper[] DeleteFromInvocationList(Wrapper[] invocationList, int invocationCount, int deleteIndex, int deleteCount) { - Delegate[] thisInvocationList = (Delegate[])m_helperObject; - int allocCount = thisInvocationList.Length; + int allocCount = invocationList.Length; while (allocCount / 2 >= invocationCount - deleteCount) allocCount /= 2; - Delegate[] newInvocationList = new Delegate[allocCount]; + Wrapper[] newInvocationList = new Wrapper[allocCount]; for (int i = 0; i < deleteIndex; i++) newInvocationList[i] = invocationList[i]; @@ -552,11 +531,11 @@ private Delegate[] DeleteFromInvocationList(Delegate[] invocationList, int invoc return newInvocationList; } - private static bool EqualInvocationLists(Delegate[] a, Delegate[] b, int start, int count) + private static bool EqualInvocationLists(Wrapper[] a, Wrapper[] b, int start, int count) { for (int i = 0; i < count; i++) { - if (!(a[start + i].Equals(b[i]))) + if (!(a[start + i].Value.Equals(b[i].Value))) return false; } return true; @@ -572,17 +551,14 @@ private static bool EqualInvocationLists(Delegate[] a, Delegate[] b, int start, // There is a special case were we are removing using a delegate as // the value we need to check for this case // - MulticastDelegate? v = d as MulticastDelegate; - - if (v is null) + if (d is null) return this; - if (v.m_helperObject as Delegate[] == null) + if (d.m_helperObject is not Wrapper[] dInvocationList) { - Delegate[]? invocationList = m_helperObject as Delegate[]; - if (invocationList == null) + if (m_helperObject is not Wrapper[] invocationList) { // they are both not real Multicast - if (this.Equals(v)) + if (this.Equals(d)) return null; } else @@ -590,16 +566,16 @@ private static bool EqualInvocationLists(Delegate[] a, Delegate[] b, int start, int invocationCount = (int)m_extraFunctionPointerOrData; for (int i = invocationCount; --i >= 0;) { - if (v.Equals(invocationList[i])) + if (d.Equals(invocationList[i])) { if (invocationCount == 2) { // Special case - only one value left, either at the beginning or the end - return invocationList[1 - i]; + return invocationList[1 - i].Value; } else { - Delegate[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1); + Wrapper[] list = DeleteFromInvocationList(invocationList, invocationCount, i, 1); return NewMulticastDelegate(list, invocationCount - 1, true); } } @@ -608,29 +584,28 @@ private static bool EqualInvocationLists(Delegate[] a, Delegate[] b, int start, } else { - Delegate[]? invocationList = m_helperObject as Delegate[]; - if (invocationList != null) + if (m_helperObject is Wrapper[] invocationList) { int invocationCount = (int)m_extraFunctionPointerOrData; - int vInvocationCount = (int)v.m_extraFunctionPointerOrData; - for (int i = invocationCount - vInvocationCount; i >= 0; i--) + int dInvocationCount = (int)d.m_extraFunctionPointerOrData; + for (int i = invocationCount - dInvocationCount; i >= 0; i--) { - if (EqualInvocationLists(invocationList, v.m_helperObject as Delegate[], i, vInvocationCount)) + if (EqualInvocationLists(invocationList, dInvocationList, i, dInvocationCount)) { - if (invocationCount - vInvocationCount == 0) + if (invocationCount - dInvocationCount == 0) { // Special case - no values left return null; } - else if (invocationCount - vInvocationCount == 1) + else if (invocationCount - dInvocationCount == 1) { // Special case - only one value left, either at the beginning or the end - return invocationList[i != 0 ? 0 : invocationCount - 1]; + return invocationList[i != 0 ? 0 : invocationCount - 1].Value; } else { - Delegate[] list = DeleteFromInvocationList(invocationList, invocationCount, i, vInvocationCount); - return NewMulticastDelegate(list, invocationCount - vInvocationCount, true); + Wrapper[] list = DeleteFromInvocationList(invocationList, invocationCount, i, dInvocationCount); + return NewMulticastDelegate(list, invocationCount - dInvocationCount, true); } } } @@ -642,41 +617,19 @@ private static bool EqualInvocationLists(Delegate[] a, Delegate[] b, int start, public virtual Delegate[] GetInvocationList() { - Delegate[] del; - Delegate[]? invocationList = m_helperObject as Delegate[]; - if (invocationList == null) - { - del = new Delegate[1]; - del[0] = this; - } - else + if (m_helperObject is Wrapper[] invocationList) { // Create an array of delegate copies and each // element into the array int invocationCount = (int)m_extraFunctionPointerOrData; - del = new Delegate[invocationCount]; + var del = new Delegate[invocationCount]; for (int i = 0; i < del.Length; i++) - del[i] = invocationList[i]; + del[i] = invocationList[i].Value; + return del; } - return del; - } - - private bool InvocationListEquals(MulticastDelegate d) - { - Delegate[] invocationList = (Delegate[])m_helperObject; - if (d.m_extraFunctionPointerOrData != m_extraFunctionPointerOrData) - return false; - int invocationCount = (int)m_extraFunctionPointerOrData; - for (int i = 0; i < invocationCount; i++) - { - Delegate dd = invocationList[i]; - Delegate[] dInvocationList = (Delegate[])d.m_helperObject; - if (!dd.Equals(dInvocationList[i])) - return false; - } - return true; + return new Delegate[] { this }; } public override bool Equals([NotNullWhen(true)] object? obj) @@ -688,73 +641,74 @@ public override bool Equals([NotNullWhen(true)] object? obj) if (!InternalEqualTypes(this, obj)) return false; - // Since this is a MulticastDelegate and we know - // the types are the same, obj should also be a - // MulticastDelegate - Debug.Assert(obj is MulticastDelegate, "Shouldn't have failed here since we already checked the types are the same!"); - var d = Unsafe.As(obj); + // Since this is a Delegate and we know the types are the same, obj should also be a Delegate + Debug.Assert(obj is Delegate, "Shouldn't have failed here since we already checked the types are the same!"); + var d = Unsafe.As(obj); // there are 2 kind of delegate kinds for comparison - // 1- Multicast (m_helperObject is Delegate[]) + // 1- Multicast (m_helperObject is Wrapper[]) // 2- Single-cast delegate, which can be compared with a structural comparison - IntPtr multicastThunk = GetThunk(MulticastThunk); - if (m_functionPointer == multicastThunk) + if (m_helperObject is Wrapper[] invocationList) { - return d.m_functionPointer == multicastThunk && InvocationListEquals(d); - } - else - { - if (!object.ReferenceEquals(m_helperObject, d.m_helperObject) || - (!FunctionPointerOps.Compare(m_extraFunctionPointerOrData, d.m_extraFunctionPointerOrData)) || - (!FunctionPointerOps.Compare(m_functionPointer, d.m_functionPointer))) - { + if (d.m_extraFunctionPointerOrData != m_extraFunctionPointerOrData) return false; - } - // Those delegate kinds with thunks put themselves into the m_firstParameter, so we can't - // blindly compare the m_firstParameter fields for equality. - if (object.ReferenceEquals(m_firstParameter, this)) + if (d.m_helperObject is not Wrapper[] dInvocationList) + return false; + + int invocationCount = (int)m_extraFunctionPointerOrData; + for (int i = 0; i < invocationCount; i++) { - return object.ReferenceEquals(d.m_firstParameter, d); + if (!invocationList[i].Value.Equals(dInvocationList[i].Value)) + return false; } + return true; + } - return object.ReferenceEquals(m_firstParameter, d.m_firstParameter); + if (!object.ReferenceEquals(m_helperObject, d.m_helperObject) || + (!FunctionPointerOps.Compare(m_extraFunctionPointerOrData, d.m_extraFunctionPointerOrData)) || + (!FunctionPointerOps.Compare(m_functionPointer, d.m_functionPointer))) + { + return false; + } + + // Those delegate kinds with thunks put themselves into the m_firstParameter, so we can't + // blindly compare the m_firstParameter fields for equality. + if (object.ReferenceEquals(m_firstParameter, this)) + { + return object.ReferenceEquals(d.m_firstParameter, d); } + + return object.ReferenceEquals(m_firstParameter, d.m_firstParameter); } public override int GetHashCode() { - Delegate[]? invocationList = m_helperObject as Delegate[]; - if (invocationList == null) - { - return base.GetHashCode(); - } - else + if (m_helperObject is Wrapper[] invocationList) { int hash = 0; for (int i = 0; i < (int)m_extraFunctionPointerOrData; i++) { - hash = hash * 33 + invocationList[i].GetHashCode(); + hash = hash * 33 + invocationList[i].Value.GetHashCode(); } - return hash; } + + return base.GetHashCode(); } - public bool HasSingleTarget => !(m_helperObject is Delegate[]); + public bool HasSingleTarget => m_helperObject is not Wrapper[]; // Used by delegate invocation list enumerator internal Delegate? TryGetAt(int index) { - if (!(m_helperObject is Delegate[] invocationList)) + if (m_helperObject is Wrapper[] invocationList) { - return (index == 0) ? this : null; - } - else - { - return ((uint)index < (uint)m_extraFunctionPointerOrData) ? invocationList[index] : null; + return ((uint)index < (uint)m_extraFunctionPointerOrData) ? invocationList[index].Value : null; } + + return (index == 0) ? this : null; } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs index bf7fa122af6c5..87e3303b6f154 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs @@ -56,7 +56,7 @@ public static unsafe IntPtr GetFunctionPointerForDelegate(Delegate del) throw new ArgumentException(SR.Argument_NeedNonGenericType, "delegate"); #pragma warning restore CA2208 - NativeFunctionPointerWrapper? fpWrapper = del.Target as NativeFunctionPointerWrapper; + NativeFunctionPointerWrapper? fpWrapper = del.TryGetNativeFunctionPointerWrapper(); if (fpWrapper != null) { // @@ -104,64 +104,64 @@ internal unsafe struct ThunkContextData public IntPtr FunctionPtr; // Function pointer for open static delegates } - internal sealed class PInvokeDelegateThunk + internal sealed unsafe class PInvokeDelegateThunk { - public IntPtr Thunk; // Thunk pointer - public IntPtr ContextData; // ThunkContextData pointer which will be stored in the context slot of the thunk + public readonly IntPtr Thunk; // Thunk pointer + public readonly IntPtr ContextData; // ThunkContextData pointer which will be stored in the context slot of the thunk public PInvokeDelegateThunk(Delegate del) { - Thunk = RuntimeAugments.AllocateThunk(s_thunkPoolHeap); - Debug.Assert(Thunk != IntPtr.Zero); - if (Thunk == IntPtr.Zero) { - // We've either run out of memory, or failed to allocate a new thunk due to some other bug. Now we should fail fast - Environment.FailFast("Insufficient number of thunks."); + throw new OutOfMemoryException(); } - else - { - // - // Allocate unmanaged memory for GCHandle of delegate and function pointer of open static delegate - // We will store this pointer on the context slot of thunk data - // - unsafe - { - ContextData = (IntPtr)NativeMemory.Alloc((nuint)(2 * IntPtr.Size)); - ThunkContextData* thunkData = (ThunkContextData*)ContextData; + // + // For open static delegates set target to ReverseOpenStaticDelegateStub which calls the static function pointer directly + // + IntPtr openStaticFunctionPointer = del.TryGetOpenStaticFunctionPointer(); - // allocate a weak GChandle for the delegate - thunkData->Handle = GCHandle.Alloc(del, GCHandleType.Weak); + // + // Allocate unmanaged memory for GCHandle of delegate and function pointer of open static delegate + // We will store this pointer on the context slot of thunk data + // + unsafe + { + ContextData = (IntPtr)NativeMemory.Alloc((nuint)(2 * IntPtr.Size)); - // if it is an open static delegate get the function pointer - if (del.IsOpenStatic) - thunkData->FunctionPtr = del.GetFunctionPointer(out RuntimeTypeHandle _, out bool _, out bool _); - else - thunkData->FunctionPtr = default; - } + ThunkContextData* thunkData = (ThunkContextData*)ContextData; + + // allocate a weak GChandle for the delegate + thunkData->Handle = GCHandle.Alloc(del, GCHandleType.Weak); + thunkData->FunctionPtr = openStaticFunctionPointer; } + + IntPtr pTarget = RuntimeInteropData.GetDelegateMarshallingStub(new RuntimeTypeHandle(del.GetMethodTable()), openStaticFunctionPointer != IntPtr.Zero); + Debug.Assert(pTarget != IntPtr.Zero); + + RuntimeAugments.SetThunkData(s_thunkPoolHeap, Thunk, ContextData, pTarget); } ~PInvokeDelegateThunk() { // Free the thunk - RuntimeAugments.FreeThunk(s_thunkPoolHeap, Thunk); - unsafe + if (Thunk != IntPtr.Zero) + { + RuntimeAugments.FreeThunk(s_thunkPoolHeap, Thunk); + } + + if (ContextData != IntPtr.Zero) { - if (ContextData != IntPtr.Zero) + // free the GCHandle + GCHandle handle = ((ThunkContextData*)ContextData)->Handle; + if (handle.IsAllocated) { - // free the GCHandle - GCHandle handle = ((ThunkContextData*)ContextData)->Handle; - if (handle.IsAllocated) - { - handle.Free(); - } - - // Free the allocated context data memory - NativeMemory.Free((void*)ContextData); + handle.Free(); } + + // Free the allocated context data memory + NativeMemory.Free((void*)ContextData); } } } @@ -179,19 +179,7 @@ private static unsafe PInvokeDelegateThunk AllocateThunk(Delegate del) Debug.Assert(s_thunkPoolHeap != null); } - var delegateThunk = new PInvokeDelegateThunk(del); - - // - // For open static delegates set target to ReverseOpenStaticDelegateStub which calls the static function pointer directly - // - bool openStaticDelegate = del.IsOpenStatic; - - IntPtr pTarget = RuntimeInteropData.GetDelegateMarshallingStub(new RuntimeTypeHandle(del.GetMethodTable()), openStaticDelegate); - Debug.Assert(pTarget != IntPtr.Zero); - - RuntimeAugments.SetThunkData(s_thunkPoolHeap, delegateThunk.Thunk, delegateThunk.ContextData, pTarget); - - return delegateThunk; + return new PInvokeDelegateThunk(del); } /// diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs index 5ae75d1d55eb9..5669203a6f39d 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs @@ -17,20 +17,7 @@ public static class DelegateMethodInfoRetriever { public static MethodInfo GetDelegateMethodInfo(Delegate del) { - Delegate[] invokeList = del.GetInvocationList(); - del = invokeList[invokeList.Length - 1]; - IntPtr originalLdFtnResult = RuntimeAugments.GetDelegateLdFtnResult(del, out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver, out bool isInterpreterEntrypoint); - - if (isInterpreterEntrypoint) - { - // This is a special kind of delegate where the invoke method is "ObjectArrayThunk". Typically, - // this will be a delegate that points the LINQ Expression interpreter. We could manufacture - // a MethodInfo based on the delegate's Invoke signature, but let's just throw for now. - throw new NotSupportedException(SR.DelegateGetMethodInfo_ObjectArrayDelegate); - } - - if (originalLdFtnResult == (IntPtr)0) - return null; + IntPtr originalLdFtnResult = RuntimeAugments.GetDelegateLdFtnResult(del, out RuntimeTypeHandle typeOfFirstParameterIfInstanceDelegate, out bool isOpenResolver); QMethodDefinition methodHandle = default(QMethodDefinition); RuntimeTypeHandle[] genericMethodTypeArgumentHandles = null; @@ -79,11 +66,7 @@ public static MethodInfo GetDelegateMethodInfo(Delegate del) throw new NotSupportedException(SR.Format(SR.DelegateGetMethodInfo_NoDynamic_WithDisplayString, methodDisplayString)); } } - MethodBase methodBase = ExecutionDomain.GetMethod(typeOfFirstParameterIfInstanceDelegate, methodHandle, genericMethodTypeArgumentHandles); - MethodInfo methodInfo = methodBase as MethodInfo; - if (methodInfo != null) - return methodInfo; - return null; // GetMethod() returned a ConstructorInfo. + return (MethodInfo)ExecutionDomain.GetMethod(typeOfFirstParameterIfInstanceDelegate, methodHandle, genericMethodTypeArgumentHandles); } } } diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx index 80023f62d226a..e5432845a5206 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx @@ -204,9 +204,6 @@ Cannot retrieve a MethodInfo for this delegate because the necessary generic instantiation was not metadata-enabled. - - Cannot retrieve a MethodInfo for this delegate because the delegate target is an interpreted LINQ expression. - Could not retrieve the mapping of the interface '{0}' on type '{1}' because the type implements the interface abstractly. diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/MulticastDelegateTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/MulticastDelegateTests.cs index 354f997ed5c7b..9994fa02f10fa 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/MulticastDelegateTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/MulticastDelegateTests.cs @@ -66,6 +66,21 @@ public static void EqualsTest() Assert.Equal(d1.GetHashCode(), d1.GetHashCode()); } + [Fact] + public static void ArrayDelegates() + { + // Delegate implementation may use Delegate[] arrays as sentinels. Validate that + // the sentinels are not confused with user provided targets. + + Action da = new Delegate[5].MyExtension; + Assert.True(da.HasSingleTarget); + Assert.Equal(1, da.GetInvocationList().Length); + + Func dd = new Delegate[10].GetLength; + Assert.True(dd.HasSingleTarget); + Assert.Equal(1, dd.GetInvocationList().Length); + } + [Fact] public static void CombineReturn() { @@ -199,7 +214,7 @@ private static void CheckInvokeList(D[] expected, D combo, Tracker target) { CheckIsSingletonDelegate((D)(expected[i]), (D)(invokeList[i]), target); } - Assert.Same(combo.Target, expected[expected.Length - 1].Target); + Assert.Same(combo.Target, expected[^1].Target); Assert.Same(combo.Target, target); Assert.Equal(combo.HasSingleTarget, invokeList.Length == 1); int count = 0; @@ -209,6 +224,7 @@ private static void CheckInvokeList(D[] expected, D combo, Tracker target) count++; } Assert.Equal(count, invokeList.Length); + Assert.Equal(combo.Method, invokeList[^1].Method); } private static void CheckIsSingletonDelegate(D expected, D actual, Tracker target) @@ -283,4 +299,11 @@ private class C public string Goo(int x) => new string('A', x); } } + + static class MulticastDelegateTestsExtensions + { + public static void MyExtension(this Delegate[] delegates) + { + } + } } diff --git a/src/tests/CoreMangLib/system/delegate/delegate/DelegateCombine1.csproj b/src/tests/CoreMangLib/system/delegate/delegate/DelegateCombine1.csproj index 62c497a099222..3ca1a050735ef 100644 --- a/src/tests/CoreMangLib/system/delegate/delegate/DelegateCombine1.csproj +++ b/src/tests/CoreMangLib/system/delegate/delegate/DelegateCombine1.csproj @@ -1,9 +1,7 @@ - - true true - 1 + 0 diff --git a/src/tests/CoreMangLib/system/delegate/delegate/DelegateCombineImpl.csproj b/src/tests/CoreMangLib/system/delegate/delegate/DelegateCombineImpl.csproj index df8e80338fad0..7c4ca6e8d532b 100644 --- a/src/tests/CoreMangLib/system/delegate/delegate/DelegateCombineImpl.csproj +++ b/src/tests/CoreMangLib/system/delegate/delegate/DelegateCombineImpl.csproj @@ -1,9 +1,7 @@ - - true true - 1 + 0 diff --git a/src/tests/CoreMangLib/system/delegate/delegate/DelegateEquals1.csproj b/src/tests/CoreMangLib/system/delegate/delegate/DelegateEquals1.csproj index 2d8fbf191cbb5..07251f373e3ed 100644 --- a/src/tests/CoreMangLib/system/delegate/delegate/DelegateEquals1.csproj +++ b/src/tests/CoreMangLib/system/delegate/delegate/DelegateEquals1.csproj @@ -1,9 +1,6 @@ - - true true - 1 diff --git a/src/tests/CoreMangLib/system/delegate/delegate/DelegateGetHashCode1.csproj b/src/tests/CoreMangLib/system/delegate/delegate/DelegateGetHashCode1.csproj index a597f02daeda1..6a33e215d0fc5 100644 --- a/src/tests/CoreMangLib/system/delegate/delegate/DelegateGetHashCode1.csproj +++ b/src/tests/CoreMangLib/system/delegate/delegate/DelegateGetHashCode1.csproj @@ -1,9 +1,6 @@ - - true true - 1 diff --git a/src/tests/CoreMangLib/system/delegate/delegate/DelegateGetInvocationList1.csproj b/src/tests/CoreMangLib/system/delegate/delegate/DelegateGetInvocationList1.csproj deleted file mode 100644 index e82724aaace7f..0000000000000 --- a/src/tests/CoreMangLib/system/delegate/delegate/DelegateGetInvocationList1.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - true - true - 1 - - - - - - - - diff --git a/src/tests/CoreMangLib/system/delegate/delegate/DelegateRemove.csproj b/src/tests/CoreMangLib/system/delegate/delegate/DelegateRemove.csproj index 0217c4ad8d879..87fdce6e85994 100644 --- a/src/tests/CoreMangLib/system/delegate/delegate/DelegateRemove.csproj +++ b/src/tests/CoreMangLib/system/delegate/delegate/DelegateRemove.csproj @@ -1,9 +1,6 @@ - - true true - 1 diff --git a/src/tests/CoreMangLib/system/delegate/delegate/delegateRemoveImpl.csproj b/src/tests/CoreMangLib/system/delegate/delegate/delegateRemoveImpl.csproj index ab802b989cda7..6d02178792036 100644 --- a/src/tests/CoreMangLib/system/delegate/delegate/delegateRemoveImpl.csproj +++ b/src/tests/CoreMangLib/system/delegate/delegate/delegateRemoveImpl.csproj @@ -1,9 +1,6 @@ - - true true - 1 diff --git a/src/tests/CoreMangLib/system/delegate/delegate/delegategetinvocationlist1.cs b/src/tests/CoreMangLib/system/delegate/delegate/delegategetinvocationlist1.cs deleted file mode 100644 index d0c8f34327362..0000000000000 --- a/src/tests/CoreMangLib/system/delegate/delegate/delegategetinvocationlist1.cs +++ /dev/null @@ -1,230 +0,0 @@ -// 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.Globalization; -using Xunit; -//test case for delegate GetInvocationList method. -namespace DelegateTest -{ - delegate bool booldelegate(); - public class DelegateGetInvocationList - { - - booldelegate starkWork; - - [Fact] - public static int TestEntryPoint() - { - DelegateGetInvocationList delegateGetInvocationList = new DelegateGetInvocationList(); - - TestLibrary.TestFramework.BeginTestCase("DelegateGetInvocationList"); - - if (delegateGetInvocationList.RunTests()) - { - TestLibrary.TestFramework.EndTestCase(); - TestLibrary.TestFramework.LogInformation("PASS"); - return 100; - - } - else - { - TestLibrary.TestFramework.EndTestCase(); - TestLibrary.TestFramework.LogInformation("FAIL"); - return 0; - } - } - - public bool RunTests() - { - bool retVal = true; - - TestLibrary.TestFramework.LogInformation("[Positive]"); - retVal = PosTest1() && retVal; - retVal = PosTest2() && retVal; - retVal = PosTest3() && retVal; - retVal = PosTest4() && retVal; - return retVal; - } - - // Returns true if the expected result is right - // Returns false if the expected result is wrong - public bool PosTest1() - { - bool retVal = true; - - TestLibrary.TestFramework.BeginScenario("PosTest1: Call GetInvocationList against a delegate with one function"); - try - { - DelegateGetInvocationList delctor = new DelegateGetInvocationList(); - booldelegate dStartWork_Bool = new booldelegate(new TestClass().StartWork_Bool); - delctor.starkWork = dStartWork_Bool; - Delegate[] invocationList = delctor.starkWork.GetInvocationList(); - if (invocationList.Length != 1) - { - TestLibrary.TestFramework.LogError("001", "Call GetInvocationList against a delegate with one function returns wrong result: " + invocationList.Length); - retVal = false; - } - if (!delctor.starkWork.GetInvocationList()[0].Equals(dStartWork_Bool)) - { - TestLibrary.TestFramework.LogError("002", " GetInvocationList return error method "); - retVal = false; - } - delctor.starkWork(); - } - catch (Exception e) - { - TestLibrary.TestFramework.LogError("003", "Unexpected exception: " + e); - retVal = false; - } - - return retVal; - } - // Returns true if the expected result is right - // Returns false if the expected result is wrong - public bool PosTest2() - { - bool retVal = true; - - TestLibrary.TestFramework.BeginScenario("PosTest2: Call GetInvocationList against a delegate with muti different functions "); - try - { - DelegateGetInvocationList delctor = new DelegateGetInvocationList(); - booldelegate bStartWork_Bool = new booldelegate(new TestClass().StartWork_Bool); - booldelegate bWorking_Bool = new booldelegate(new TestClass().Working_Bool); - booldelegate bCompleted_Bool = new booldelegate(new TestClass().Completed_Bool); - - delctor.starkWork += bStartWork_Bool; - delctor.starkWork += bWorking_Bool; - delctor.starkWork += bCompleted_Bool; - Delegate[] invocationList = delctor.starkWork.GetInvocationList(); - if (invocationList.Length != 3) - { - TestLibrary.TestFramework.LogError("004", "Call GetInvocationList against a delegate with one function returns wrong result: " + invocationList.Length); - retVal = false; - } - if (!delctor.starkWork.GetInvocationList()[0].Equals(bStartWork_Bool) - || !delctor.starkWork.GetInvocationList()[1].Equals(bWorking_Bool) - || !delctor.starkWork.GetInvocationList()[2].Equals(bCompleted_Bool)) - { - TestLibrary.TestFramework.LogError("005", " GetInvocationList return error method "); - retVal = false; - } - delctor.starkWork(); - } - catch (Exception e) - { - TestLibrary.TestFramework.LogError("006", "Unexpected exception: " + e); - retVal = false; - } - - return retVal; - } - // Returns true if the expected result is right - // Returns false if the expected result is wrong - public bool PosTest3() - { - bool retVal = true; - - TestLibrary.TestFramework.BeginScenario("PosTest3: Call GetInvocationList against a delegate with muti functions ,some is null"); - try - { - DelegateGetInvocationList delctor = new DelegateGetInvocationList(); - booldelegate bStartWork_Bool = new booldelegate(new TestClass().StartWork_Bool); - booldelegate bWorking_Bool = new booldelegate(new TestClass().Working_Bool); - booldelegate bCompleted_Bool = new booldelegate(new TestClass().Completed_Bool); - - delctor.starkWork += bStartWork_Bool; - delctor.starkWork += null; - delctor.starkWork += bWorking_Bool; - delctor.starkWork += bCompleted_Bool; - Delegate[] invocationList = delctor.starkWork.GetInvocationList(); - if (invocationList.Length != 3) - { - TestLibrary.TestFramework.LogError("007", "Call GetInvocationList against a delegate with one function returns wrong result: " + invocationList.Length); - retVal = false; - } - if (!delctor.starkWork.GetInvocationList()[0].Equals(bStartWork_Bool) - || !delctor.starkWork.GetInvocationList()[1].Equals(bWorking_Bool) - || !delctor.starkWork.GetInvocationList()[2].Equals(bCompleted_Bool)) - { - TestLibrary.TestFramework.LogError("008", " GetInvocationList return error method "); - retVal = false; - } - delctor.starkWork(); - } - catch (Exception e) - { - TestLibrary.TestFramework.LogError("009", "Unexpected exception: " + e); - retVal = false; - } - - return retVal; - } - - // Returns true if the expected result is right - // Returns false if the expected result is wrong - public bool PosTest4() - { - bool retVal = true; - - TestLibrary.TestFramework.BeginScenario("PosTest4: Call GetInvocationList against a delegate with muti functions ,some of these are the same"); - try - { - DelegateGetInvocationList delctor = new DelegateGetInvocationList(); - booldelegate bStartWork_Bool = new booldelegate(new TestClass().StartWork_Bool); - booldelegate bWorking_Bool = new booldelegate(new TestClass().Working_Bool); - booldelegate bCompleted_Bool = new booldelegate(new TestClass().Completed_Bool); - - delctor.starkWork += bStartWork_Bool; - delctor.starkWork += bStartWork_Bool; - delctor.starkWork += bWorking_Bool; - delctor.starkWork += bCompleted_Bool; - Delegate[] invocationList = delctor.starkWork.GetInvocationList(); - if (invocationList.Length != 4) - { - TestLibrary.TestFramework.LogError("010", "Call GetInvocationList against a delegate with one function returns wrong result: " + invocationList.Length); - retVal = false; - } - if (!delctor.starkWork.GetInvocationList()[0].Equals(bStartWork_Bool) - || !delctor.starkWork.GetInvocationList()[1].Equals(bStartWork_Bool) - || !delctor.starkWork.GetInvocationList()[2].Equals(bWorking_Bool) - || !delctor.starkWork.GetInvocationList()[3].Equals(bCompleted_Bool)) - { - TestLibrary.TestFramework.LogError("011", " GetInvocationList return error method "); - retVal = false; - } - delctor.starkWork(); - } - catch (Exception e) - { - TestLibrary.TestFramework.LogError("012", "Unexpected exception: " + e); - retVal = false; - } - - return retVal; - } - - } - //create testclass for providing test method and test target. - class TestClass - { - public bool StartWork_Bool() - { - TestLibrary.TestFramework.LogInformation("StartWork_Bool method is running ."); - return true; - } - public bool Working_Bool() - { - TestLibrary.TestFramework.LogInformation("Working_Bool method is running ."); - return true; - } - public bool Completed_Bool() - { - TestLibrary.TestFramework.LogInformation("Completed_Bool method is running ."); - return true; - } - } - - -} diff --git a/src/tests/Interop/MarshalAPI/FunctionPointer/FunctionPointer.cs b/src/tests/Interop/MarshalAPI/FunctionPointer/FunctionPointer.cs index 3dbcb6aaf3980..7b0fd902a7bdf 100644 --- a/src/tests/Interop/MarshalAPI/FunctionPointer/FunctionPointer.cs +++ b/src/tests/Interop/MarshalAPI/FunctionPointer/FunctionPointer.cs @@ -28,8 +28,6 @@ static class FunctionPointerNative delegate void VoidDelegate(); [Fact] - - [ActiveIssue("https://github.com/dotnet/runtimelab/issues/164", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] public static void RunGetDelForFcnPtrTest() { Console.WriteLine($"Running {nameof(RunGetDelForFcnPtrTest)}..."); @@ -42,6 +40,10 @@ public static void RunGetDelForFcnPtrTest() VoidDelegate del = (VoidDelegate)Marshal.GetDelegateForFunctionPointer(fcnptr, typeof(VoidDelegate)); Assert.Equal(md.Target, del.Target); Assert.Equal(md.Method, del.Method); + + VoidDelegate del2 = (VoidDelegate)Marshal.GetDelegateForFunctionPointer(fcnptr, typeof(VoidDelegate)); + Assert.Equal(del, del2); + Assert.Equal(del.GetHashCode(), del2.GetHashCode()); } // Native FcnPtr -> Delegate @@ -51,6 +53,10 @@ public static void RunGetDelForFcnPtrTest() Assert.Null(del.Target); Assert.Equal("Invoke", del.Method.Name); + VoidDelegate del2 = (VoidDelegate)Marshal.GetDelegateForFunctionPointer(fcnptr, typeof(VoidDelegate));; + Assert.Equal(del, del2); + Assert.Equal(del.GetHashCode(), del2.GetHashCode()); + // Round trip of a native function pointer is never legal for a non-concrete Delegate type Assert.Throws(() => {