From cfa940d81704b522bdcfb95d61d8e5b941d99bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= Date: Sun, 21 Jun 2026 02:30:23 +0200 Subject: [PATCH 01/10] Cleanup dead delegate code --- .../src/System/Delegate.CoreCLR.cs | 3 +- .../src/System/MulticastDelegate.CoreCLR.cs | 2 +- src/coreclr/debug/daccess/dacdbiimpl.cpp | 1 - src/coreclr/vm/comdelegate.cpp | 97 +++---------------- src/coreclr/vm/comdelegate.h | 1 - src/coreclr/vm/stubmgr.cpp | 33 ------- 6 files changed, 15 insertions(+), 122 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index 94ed1f03b71ae7..6563f99cbae192 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -379,8 +379,7 @@ internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target // Initialize the method... Delegate d = InternalAlloc(rtType); - // This is a new internal API added in Whidbey. Currently it's only - // used by the dynamic method code to generate a wrapper delegate. + // This is a new internal API added in Whidbey. // Allow flexible binding options since the target method is // unambiguously provided to us. diff --git a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs index 38fd1f5189f6ec..f206471ec88268 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs @@ -166,7 +166,7 @@ internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int inv internal void StoreDynamicMethod(MethodInfo dynamicMethod) { - Debug.Assert(_invocationCount == 0); + Debug.Assert(HasSingleTarget); _methodBase = dynamicMethod; } diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index adb057fba3011a..98e237426966be 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3268,7 +3268,6 @@ DacDbiInterfaceImpl::DelegateType DacDbiInterfaceImpl::GetDelegateType(VMPTR_Obj { // If this delegate points to a static function or this is a open virtual delegate, this should be non-null // Special case: This might fail in a VSD delegate (instance open virtual)... - // TODO: There is the special signatures cases missing. TADDR targetMethodPtr = PCODEToPINSTR(pDelObj->GetMethodPtrAux()); if (targetMethodPtr == (TADDR)NULL) { diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 8f0d4a2438aace..4e974562af9407 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -1812,10 +1812,7 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) // If you modify this logic, please update cDAC IObject.GetDelegateInfo. - MethodDesc *pMethodHandle = NULL; - DELEGATEREF thisDel = (DELEGATEREF) orDelegate; - DELEGATEREF innerDel = NULL; INT_PTR count = thisDel->GetInvocationCount(); if (count != 0) @@ -1823,98 +1820,30 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) // this is one of the following: // - multicast - _invocationList is Array && _invocationCount != 0 // - unamanaged ftn ptr - _invocationList == NULL && _invocationCount == -1 - // - wrapper delegate - _invocationList is Delegate && _invocationCount != NULL // - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc) - // or _invocationList points to a LoaderAllocator/DynamicResolver (inner open virtual delegate of a Wrapper Delegate) - // in the wrapper delegate case we want to unwrap and return the method desc of the inner delegate - // in the other cases we return the method desc for the invoke - innerDel = (DELEGATEREF) thisDel->GetInvocationList(); - bool fOpenVirtualDelegate = false; + // or _invocationList points to a LoaderAllocator/DynamicResolver - if (innerDel != NULL) - { - MethodTable *pMT = innerDel->GetMethodTable(); - if (pMT->IsDelegate()) - return GetMethodDesc(innerDel); - if (!pMT->IsArray()) - { - // must be a virtual one - fOpenVirtualDelegate = true; - } - } - else - { - if (count != DELEGATE_MARKER_UNMANAGEDFPTR) - { - // must be a virtual one - fOpenVirtualDelegate = true; - } - } + // we return the method desc for the invoke + OBJECTREF invocationList = thisDel->GetInvocationList(); + if ((invocationList != NULL && invocationList->GetMethodTable()->IsArray()) || count == DELEGATE_MARKER_UNMANAGEDFPTR) + return FindDelegateInvokeMethod(thisDel->GetMethodTable()); - if (fOpenVirtualDelegate) - pMethodHandle = GetMethodDescForOpenVirtualDelegate(thisDel); - else - pMethodHandle = FindDelegateInvokeMethod(thisDel->GetMethodTable()); + return GetMethodDescForOpenVirtualDelegate(thisDel); } - else - { - // Next, check for an open delegate - PCODE code = thisDel->GetMethodPtrAux(); - - if (code == (PCODE)NULL) - { - // Must be a normal delegate - code = thisDel->GetMethodPtr(); - } - pMethodHandle = NonVirtualEntry2MethodDesc(code); + // Next, check for an open delegate + PCODE code = thisDel->GetMethodPtrAux(); + if (code == (PCODE)NULL) + { + // Must be a normal delegate + code = thisDel->GetMethodPtr(); } + MethodDesc *pMethodHandle = NonVirtualEntry2MethodDesc(code); _ASSERTE(pMethodHandle); return pMethodHandle; } -OBJECTREF COMDelegate::GetTargetObject(OBJECTREF obj) -{ - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_COOPERATIVE; - } - CONTRACTL_END; - - OBJECTREF targetObject = NULL; - - DELEGATEREF thisDel = (DELEGATEREF) obj; - OBJECTREF innerDel = NULL; - - if (thisDel->GetInvocationCount() != 0) - { - // this is one of the following: - // - multicast - // - unmanaged ftn ptr - // - wrapper delegate - // - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc) - // or _invocationList points to a LoaderAllocator/DynamicResolver (inner open virtual delegate of a Wrapper Delegate) - // in the wrapper delegate case we want to unwrap and return the object of the inner delegate - innerDel = (DELEGATEREF) thisDel->GetInvocationList(); - if (innerDel != NULL) - { - MethodTable *pMT = innerDel->GetMethodTable(); - if (pMT->IsDelegate()) - { - targetObject = GetTargetObject(innerDel); - } - } - } - - if (targetObject == NULL) - targetObject = thisDel->GetTarget(); - - return targetObject; -} - BOOL COMDelegate::IsTrueMulticastDelegate(OBJECTREF delegate) { CONTRACTL diff --git a/src/coreclr/vm/comdelegate.h b/src/coreclr/vm/comdelegate.h index 4982f93c418744..8e055849936bad 100644 --- a/src/coreclr/vm/comdelegate.h +++ b/src/coreclr/vm/comdelegate.h @@ -60,7 +60,6 @@ class COMDelegate static MethodDesc * __fastcall GetMethodDesc(OBJECTREF obj); static MethodDesc* GetMethodDescForOpenVirtualDelegate(OBJECTREF orDelegate); - static OBJECTREF GetTargetObject(OBJECTREF obj); static BOOL IsTrueMulticastDelegate(OBJECTREF delegate); diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index e8f55803ece74a..1e0811883df545 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1242,39 +1242,6 @@ BOOL StubLinkStubManager::TraceDelegateObject(BYTE* pbDel, TraceDestination *tra return res; } - // invocationList is not null, so it can be one of the following: - // Multicast, Static closed (special sig), Secure - - // rule out the static with special sig - BYTE *pbCount = *(BYTE **)(pbDel + DelegateObject::GetOffsetOfInvocationCount()); - if (pbCount == NULL) - { - // it's a static closed, the target lives in _methodAuxPtr - ppbDest = (BYTE **)(pbDel + DelegateObject::GetOffsetOfMethodPtrAux()); - - if (*ppbDest == NULL) - { - // it's not looking good, bail out - LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: can't trace into it\n")); - return FALSE; - } - - LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: ppbDest: %p *ppbDest:%p\n", ppbDest, *ppbDest)); - - BOOL res = StubManager::TraceStub((PCODE) (*ppbDest), trace); - - LOG((LF_CORDB,LL_INFO10000, "SLSM::TDO: res: %d, result type: %d\n", (res ? "true" : "false"), trace->GetTraceType())); - - return res; - } - - MethodTable *pType = *(MethodTable**)pbDelInvocationList; - if (pType->IsDelegate()) - { - // this is a secure delegate. The target is hidden inside this field, so recurse. - return TraceDelegateObject(pbDelInvocationList, trace); - } - // Otherwise, we're going for the first invoke of the multi case. // In order to go to the correct spot, we have just have to fish out // slot 0 of the invocation list, and figure out where that's going to, From be5894da50d7b2fc726bc2232fbe4ef3aa479ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= Date: Mon, 22 Jun 2026 16:21:32 +0200 Subject: [PATCH 02/10] Address feedback --- docs/design/datacontracts/Object.md | 11 ++++------- .../src/System/Delegate.CoreCLR.cs | 9 +++++---- .../src/System/MulticastDelegate.CoreCLR.cs | 6 ------ .../Reflection/Emit/DynamicMethod.CoreCLR.cs | 4 +--- src/coreclr/debug/daccess/dacdbiimpl.cpp | 18 +++++------------- src/coreclr/vm/comdelegate.cpp | 11 +++-------- src/coreclr/vm/object.h | 1 + .../Contracts/Object_1.cs | 11 ++--------- .../Data/Delegate.cs | 1 + 9 files changed, 22 insertions(+), 50 deletions(-) diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index 4eaa4254601595..e498b46beea0ce 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -208,14 +208,11 @@ DelegateInfo GetDelegateInfo(TargetPointer address) { Data.Delegate del = new Data.Delegate(target, address); - // Classify the delegate from its invocation count and auxiliary pointer. - DelegateType delegateType = target.ReadNInt(address + /* Delegate::InvocationCount offset */) switch + DelegateType delegateType = DelegateType.Unknown; + if (target.ReadPointer(address + /* Delegate::InvocationList offset */) == TargetPointer.Null && target.ReadNInt(address + /* Delegate::InvocationCount offset */) != -1) { - 0 => del.MethodPtrAux == TargetCodePointer.Null - ? DelegateType.Closed - : DelegateType.Open, - _ => DelegateType.Unknown, - }; + delegateType = del.MethodPtrAux == TargetCodePointer.Null ? DelegateType.Closed : DelegateType.Open; + } // Pick the bound object and primary entry point based on the classification. // For Closed delegates the target is the bound `this` and MethodPtr is invoked on it. diff --git a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index 7fbf0ffebbfa12..3f274df8d4c9dc 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; @@ -363,8 +364,8 @@ protected virtual MethodInfo GetMethodImpl() // internal implementation details (FCALLS and utilities) // - // V2 internal API. - internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target, RuntimeMethodHandle method) + internal static Delegate CreateDelegateForDynamicMethod(Type type, object? target, RuntimeMethodHandle method, + DynamicMethod dynamicMethod) { ArgumentNullException.ThrowIfNull(type); @@ -377,9 +378,7 @@ internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target if (!rtType.IsDelegate()) throw new ArgumentException(SR.Arg_MustBeDelegate, nameof(type)); - // Initialize the method... Delegate d = InternalAlloc(rtType); - // This is a new internal API added in Whidbey. // Allow flexible binding options since the target method is // unambiguously provided to us. @@ -388,6 +387,8 @@ internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target RuntimeMethodHandle.GetDeclaringType(method.GetMethodInfo()), DelegateBindingFlags.RelaxedSignature)) throw new ArgumentException(SR.Arg_DlgtTargMeth); + + d._helperObject = dynamicMethod; return d; } diff --git a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs index 84a4920be8e129..bbdb80dc4c129f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs @@ -164,12 +164,6 @@ internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int inv return NewMulticastDelegate(invocationList, invocationCount, false); } - internal void StoreDynamicMethod(MethodInfo dynamicMethod) - { - Debug.Assert(HasSingleTarget); - _helperObject = dynamicMethod; - } - // This method will combine this delegate with the passed delegate // to form a new delegate. protected sealed override Delegate CombineImpl(Delegate? follow) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs index abdb8be14b27a3..410806cc37d42d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs @@ -55,9 +55,7 @@ public sealed override Delegate CreateDelegate(Type delegateType, object? target GC.KeepAlive(methodHandle); } - MulticastDelegate d = (MulticastDelegate)Delegate.CreateDelegateNoSecurityCheck(delegateType, target, GetMethodDescriptor()); - // stash this MethodInfo by brute force. - d.StoreDynamicMethod(this); + MulticastDelegate d = (MulticastDelegate)Delegate.CreateDelegateForDynamicMethod(delegateType, target, GetMethodDescriptor(), this); return d; } diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 98e237426966be..01c86aa215b6bf 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3262,22 +3262,14 @@ DacDbiInterfaceImpl::DelegateType DacDbiInterfaceImpl::GetDelegateType(VMPTR_Obj DelegateType delegateType = DelegateType::kUnknownDelegateType; PTR_DelegateObject pDelObj = dac_cast(delegateObject.GetDacPtr()); - INT_PTR invocationCount = pDelObj->GetInvocationCount(); - if (invocationCount == 0) + INT_PTR invocationCount = pDelObj->GetInvocationCount(); + PTR_Object invocationList = OBJECTREFToObject(pDelObj->GetInvocationList()); + if (invocationList == NULL && invocationCount != DELEGATE_MARKER_UNMANAGEDFPTR) { - // If this delegate points to a static function or this is a open virtual delegate, this should be non-null - // Special case: This might fail in a VSD delegate (instance open virtual)... + // If this delegate points to an open delegate, this should be non-null TADDR targetMethodPtr = PCODEToPINSTR(pDelObj->GetMethodPtrAux()); - if (targetMethodPtr == (TADDR)NULL) - { - // Static extension methods, other closed static delegates, and instance delegates fall into this category. - delegateType = DelegateType::kClosedDelegate; - } - else - { - delegateType = DelegateType::kOpenDelegate; - } + delegateType = targetMethodPtr == (TADDR)NULL ? DelegateType::kClosedDelegate : DelegateType::kOpenDelegate; } return delegateType; } diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index cb0625b5bd7fcb..3e392cd632374c 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -26,9 +26,6 @@ #include "comcallablewrapper.h" #endif // FEATURE_COMINTEROP -#define DELEGATE_MARKER_UNMANAGEDFPTR -1 - - #ifndef DACCESS_COMPILE #if defined(TARGET_X86) @@ -1821,11 +1818,10 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) // - multicast - _invocationList is Array && _invocationCount != 0 // - unamanaged ftn ptr - _invocationList == NULL && _invocationCount == -1 // - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc) - // or _invocationList points to a LoaderAllocator/DynamicResolver - // we return the method desc for the invoke + // we return the method desc for the invoke for the first two cases OBJECTREF invocationList = thisDel->GetInvocationList(); - if ((invocationList != NULL && invocationList->GetMethodTable()->IsArray()) || count == DELEGATE_MARKER_UNMANAGEDFPTR) + if (invocationList != NULL || count == DELEGATE_MARKER_UNMANAGEDFPTR) return FindDelegateInvokeMethod(thisDel->GetMethodTable()); return GetMethodDescForOpenVirtualDelegate(thisDel); @@ -1862,8 +1858,7 @@ BOOL COMDelegate::IsTrueMulticastDelegate(OBJECTREF delegate) OBJECTREF invocationList = ((DELEGATEREF)delegate)->GetInvocationList(); if (invocationList != NULL) { - MethodTable *pMT = invocationList->GetMethodTable(); - isMulticast = pMT->IsArray(); + isMulticast = TRUE; } } diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 2bdc5b72325f56..b5fe0b3a350c16 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1743,6 +1743,7 @@ typedef BStrWrapper* BSTRWRAPPEROBJECTREF; #endif // FEATURE_COMINTEROP +#define DELEGATE_MARKER_UNMANAGEDFPTR (-1) // This class corresponds to System.MulticastDelegate on the managed side. class DelegateObject : public Object diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs index 6be137d7e28c2b..912dc3e961a1e1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs @@ -163,17 +163,10 @@ public DelegateInfo GetDelegateInfo(TargetPointer address) { Data.Delegate del = _target.ProcessedData.GetOrAdd(address); - // Classify by invocation count first: - // anything other than 0 indicates a multicast/wrapper/special-sig delegate - // that this API does not interpret further. Only when invocationCount==0 - // do MethodPtr/MethodPtrAux unambiguously identify a closed/open delegate. DelegateType delegateType = DelegateType.Unknown; - if (del.InvocationCount.Value == 0) + if (del.InvocationList == TargetPointer.Null && del.InvocationCount.Value != -1) { - if (del.MethodPtrAux == TargetCodePointer.Null) - delegateType = DelegateType.Closed; - else - delegateType = DelegateType.Open; + delegateType = del.MethodPtrAux == TargetCodePointer.Null ? DelegateType.Closed : DelegateType.Open; } (TargetPointer targetObject, TargetCodePointer targetMethodPtr) = delegateType switch diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs index 1b87c4beb4db0a..9fee608dba0389 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs @@ -9,5 +9,6 @@ internal sealed partial class Delegate : IData [Field] public TargetPointer Target { get; } [Field] public TargetCodePointer MethodPtr { get; } [Field] public TargetCodePointer MethodPtrAux { get; } + [Field] public TargetPointer InvocationList { get; } [Field] public TargetNInt InvocationCount { get; } } From b2109d01692b7ef95de544c25b81b315c3d96633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= <35800402+MichalPetryka@users.noreply.github.com> Date: Fri, 26 Jun 2026 13:11:03 +0200 Subject: [PATCH 03/10] Update src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs Co-authored-by: Jan Kotas --- .../src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs index 410806cc37d42d..77aa9fc4fc77b9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs @@ -55,8 +55,7 @@ public sealed override Delegate CreateDelegate(Type delegateType, object? target GC.KeepAlive(methodHandle); } - MulticastDelegate d = (MulticastDelegate)Delegate.CreateDelegateForDynamicMethod(delegateType, target, GetMethodDescriptor(), this); - return d; + return Delegate.CreateDelegateForDynamicMethod(delegateType, target, GetMethodDescriptor(), this); } // This is guaranteed to return a valid handle From befd3214be748956904c57a706a44929712acb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= <35800402+MichalPetryka@users.noreply.github.com> Date: Fri, 26 Jun 2026 13:12:17 +0200 Subject: [PATCH 04/10] Apply suggestions from code review Co-authored-by: Jan Kotas --- src/coreclr/vm/comdelegate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index 3e392cd632374c..ec6d9f4277c91e 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -1816,7 +1816,7 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) { // this is one of the following: // - multicast - _invocationList is Array && _invocationCount != 0 - // - unamanaged ftn ptr - _invocationList == NULL && _invocationCount == -1 + // - unamanaged ftn ptr - _invocationList == null && _invocationCount == -1 // - virtual delegate - _invocationList == null && _invocationCount == (target MethodDesc) // we return the method desc for the invoke for the first two cases @@ -1831,7 +1831,7 @@ MethodDesc *COMDelegate::GetMethodDesc(OBJECTREF orDelegate) PCODE code = thisDel->GetMethodPtrAux(); if (code == (PCODE)NULL) { - // Must be a normal delegate + // Must be a closed delegate code = thisDel->GetMethodPtr(); } From ac493905a145a8a3ee5b216ee8c46248fb303d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= Date: Sat, 27 Jun 2026 01:08:12 +0200 Subject: [PATCH 05/10] Address review --- .../src/System/Delegate.CoreCLR.cs | 7 +--- .../src/System/MulticastDelegate.CoreCLR.cs | 35 +++++-------------- .../src/System/Delegate.cs | 2 ++ .../src/System/Delegate.Mono.cs | 2 -- .../Contracts/Object_1.cs | 3 +- .../Data/Delegate.cs | 5 +++ .../DataType.cs | 1 + 7 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index 3f274df8d4c9dc..6c60f3a670e6e3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -219,7 +219,7 @@ protected virtual MethodInfo GetMethodImpl() return (MethodInfo)_helperObject; } - public object? Target => GetTarget(); + internal object? GetTarget() => Unsafe.As(this).GetTarget(); // V1 API. [RequiresUnreferencedCode("The target method might be removed")] @@ -549,11 +549,6 @@ internal void InitializeVirtualCallStub(IntPtr methodPtr) [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_InitializeVirtualCallStub")] private static partial void InitializeVirtualCallStub(ObjectHandleOnStack d, IntPtr methodPtr); - - internal virtual object? GetTarget() - { - return (_methodPtrAux == IntPtr.Zero) ? _target : null; - } } // These flags effect the way BindToMethodInfo and BindToMethodName are allowed to bind a delegate to a target method. Their diff --git a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs index 0e1ac2c60bf74b..7c059b54954bbf 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs @@ -29,11 +29,6 @@ internal bool IsUnmanagedFunctionPtr() return _invocationCount == -1; } - internal bool InvocationListLogicallyNull() - { - return (_invocationList == null) || (_invocationList is LoaderAllocator) || (_invocationList is DynamicResolver); - } - [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] public override void GetObjectData(SerializationInfo info, StreamingContext context) @@ -65,7 +60,7 @@ public sealed override bool Equals([NotNullWhen(true)] object? obj) // 2- Unmanaged FntPtr (_invocationList == null) // 3- Open virtual (_invocationCount == MethodDesc of target, _invocationList == null, LoaderAllocator, or DynamicResolver) - if (InvocationListLogicallyNull()) + if (HasSingleTarget) { if (IsUnmanagedFunctionPtr()) { @@ -376,7 +371,7 @@ private static bool EqualInvocationLists(object[] a, object[] b, int start, int return del; } - internal new bool HasSingleTarget => _invocationList is not object[]; + internal new bool HasSingleTarget => _invocationList is null; // Used by delegate invocation list enumerator internal object? /* Delegate? */ TryGetAt(int index) @@ -412,29 +407,15 @@ public sealed override int GetHashCode() } } - internal override object? GetTarget() + internal new object? GetTarget() { - if (_invocationCount != 0) + if (_invocationList is object[] invocationList) { - // _invocationCount != 0 we are in one of these cases: - // - Multicast -> return the target of the last delegate in the list - // - unmanaged function pointer - return null - // - virtual open delegate - return null - if (InvocationListLogicallyNull()) - { - // both open virtual and ftn pointer return null for the target - return null; - } - else - { - if (_invocationList is object[] invocationList) - { - int invocationCount = (int)_invocationCount; - return ((Delegate)invocationList[invocationCount - 1]).GetTarget(); - } - } + // Multicast -> return the target of the last delegate in the list + int invocationCount = (int)_invocationCount; + return ((Delegate)invocationList[invocationCount - 1]).GetTarget(); } - return base.GetTarget(); + return _methodPtrAux == 0 ? _target : null; } protected override MethodInfo GetMethodImpl() diff --git a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs index 0e4c4d28ec5122..a215bb7164a5e3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs @@ -79,6 +79,8 @@ public abstract partial class Delegate : ICloneable, ISerializable /// /// true if the has a single invocation target. public bool HasSingleTarget => Unsafe.As(this).HasSingleTarget; + + public object? Target => GetTarget(); #endif /// diff --git a/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs index de3120dac128f2..fa749f1e9a6b64 100644 --- a/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Delegate.Mono.cs @@ -105,8 +105,6 @@ protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Al }; } - public object? Target => GetTarget(); - internal virtual object? GetTarget() => _target; public static Delegate CreateDelegate(Type type, object? firstArgument, MethodInfo method, bool throwOnBindFailure) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs index 8750cd5eead5ef..8800e535d9c9f6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs @@ -162,9 +162,10 @@ public TargetPointer GetSyncBlockAddress(TargetPointer address) public DelegateInfo GetDelegateInfo(TargetPointer address) { Data.Delegate del = _target.ProcessedData.GetOrAdd(address); + Data.MulticastDelegate multicast = _target.ProcessedData.GetOrAdd(address); DelegateType delegateType = DelegateType.Unknown; - if (del.InvocationList == TargetPointer.Null && del.InvocationCount.Value != -1) + if (multicast.InvocationList == TargetPointer.Null && multicast.InvocationCount.Value != -1) { delegateType = del.MethodPtrAux == TargetCodePointer.Null ? DelegateType.Closed : DelegateType.Open; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs index 9fee608dba0389..37051deb63e97b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs @@ -9,6 +9,11 @@ internal sealed partial class Delegate : IData [Field] public TargetPointer Target { get; } [Field] public TargetCodePointer MethodPtr { get; } [Field] public TargetCodePointer MethodPtrAux { get; } +} + +[CdacType(nameof(DataType.MulticastDelegate))] +internal sealed partial class MulticastDelegate : IData +{ [Field] public TargetPointer InvocationList { get; } [Field] public TargetNInt InvocationCount { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs index 4abbf4ce732740..2f29139bd9611b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -100,6 +100,7 @@ public enum DataType InterpMethodContextFrame, Array, Delegate, + MulticastDelegate, TypedByRef, StackTraceArrayHeader, StackTraceElement, From d3ecd6207b33c15f9656445e600759a81c374eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= <35800402+MichalPetryka@users.noreply.github.com> Date: Sat, 27 Jun 2026 03:46:56 +0200 Subject: [PATCH 06/10] Update src/libraries/System.Private.CoreLib/src/System/Delegate.cs Co-authored-by: Jan Kotas --- src/libraries/System.Private.CoreLib/src/System/Delegate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs index a215bb7164a5e3..bd08fcd15e855a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs @@ -80,7 +80,7 @@ public abstract partial class Delegate : ICloneable, ISerializable /// true if the has a single invocation target. public bool HasSingleTarget => Unsafe.As(this).HasSingleTarget; - public object? Target => GetTarget(); + public object? Target => Unsafe.As(this).GetTarget(); #endif /// From 64ffd36e9bad947829d6b858012ab017db80316d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= <35800402+MichalPetryka@users.noreply.github.com> Date: Sat, 27 Jun 2026 04:06:07 +0200 Subject: [PATCH 07/10] Change invocationList and invocationCount to internal --- .../src/System/MulticastDelegate.CoreCLR.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs index 7c059b54954bbf..dda6a6c812e66c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs @@ -17,12 +17,13 @@ namespace System [ComVisible(true)] public abstract partial class MulticastDelegate : Delegate { + internal object? _invocationList; + // This is set under 3 circumstances // 1. Multicast delegate // 2. Unmanaged function pointer // 3. Open virtual delegate - private object? _invocationList; // Initialized by VM as needed - private nint _invocationCount; + internal nint _invocationCount; internal bool IsUnmanagedFunctionPtr() { From f595114e2ed2c3cd3d65ec32a756f21454133081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= Date: Sat, 27 Jun 2026 17:52:15 +0200 Subject: [PATCH 08/10] Revert DAC changes --- docs/design/datacontracts/Object.md | 11 +++++++---- src/coreclr/debug/daccess/dacdbiimpl.cpp | 19 ++++++++++++++----- .../Contracts/Object_1.cs | 12 +++++++++--- .../Data/Delegate.cs | 6 ------ .../DataType.cs | 1 - 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index d1a6bedf8ec487..b1deda0504f581 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -220,11 +220,14 @@ DelegateInfo GetDelegateInfo(TargetPointer address) { Data.Delegate del = new Data.Delegate(target, address); - DelegateType delegateType = DelegateType.Unknown; - if (target.ReadPointer(address + /* Delegate::InvocationList offset */) == TargetPointer.Null && target.ReadNInt(address + /* Delegate::InvocationCount offset */) != -1) + // Classify the delegate from its invocation count and auxiliary pointer. + DelegateType delegateType = target.ReadNInt(address + /* Delegate::InvocationCount offset */) switch { - delegateType = del.MethodPtrAux == TargetCodePointer.Null ? DelegateType.Closed : DelegateType.Open; - } + 0 => del.MethodPtrAux == TargetCodePointer.Null + ? DelegateType.Closed + : DelegateType.Open, + _ => DelegateType.Unknown, + }; // Pick the bound object and primary entry point based on the classification. // For Closed delegates the target is the bound `this` and MethodPtr is invoked on it. diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 5495a714aa3eed..d4b39f4024b909 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3262,14 +3262,23 @@ DacDbiInterfaceImpl::DelegateType DacDbiInterfaceImpl::GetDelegateType(VMPTR_Obj DelegateType delegateType = DelegateType::kUnknownDelegateType; PTR_DelegateObject pDelObj = dac_cast(delegateObject.GetDacPtr()); - INT_PTR invocationCount = pDelObj->GetInvocationCount(); - PTR_Object invocationList = OBJECTREFToObject(pDelObj->GetInvocationList()); - if (invocationList == NULL && invocationCount != DELEGATE_MARKER_UNMANAGEDFPTR) + + if (invocationCount == 0) { - // If this delegate points to an open delegate, this should be non-null + // If this delegate points to a static function or this is a open virtual delegate, this should be non-null + // Special case: This might fail in a VSD delegate (instance open virtual)... + // TODO: There is the special signatures cases missing. TADDR targetMethodPtr = PCODEToPINSTR(pDelObj->GetMethodPtrAux()); - delegateType = targetMethodPtr == (TADDR)NULL ? DelegateType::kClosedDelegate : DelegateType::kOpenDelegate; + if (targetMethodPtr == (TADDR)NULL) + { + // Static extension methods, other closed static delegates, and instance delegates fall into this category. + delegateType = DelegateType::kClosedDelegate; + } + else + { + delegateType = DelegateType::kOpenDelegate; + } } return delegateType; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs index 8800e535d9c9f6..0d56541dbb049c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs @@ -162,12 +162,18 @@ public TargetPointer GetSyncBlockAddress(TargetPointer address) public DelegateInfo GetDelegateInfo(TargetPointer address) { Data.Delegate del = _target.ProcessedData.GetOrAdd(address); - Data.MulticastDelegate multicast = _target.ProcessedData.GetOrAdd(address); + // Classify by invocation count first: + // anything other than 0 indicates a multicast/wrapper/special-sig delegate + // that this API does not interpret further. Only when invocationCount==0 + // do MethodPtr/MethodPtrAux unambiguously identify a closed/open delegate. DelegateType delegateType = DelegateType.Unknown; - if (multicast.InvocationList == TargetPointer.Null && multicast.InvocationCount.Value != -1) + if (del.InvocationCount.Value == 0) { - delegateType = del.MethodPtrAux == TargetCodePointer.Null ? DelegateType.Closed : DelegateType.Open; + if (del.MethodPtrAux == TargetCodePointer.Null) + delegateType = DelegateType.Closed; + else + delegateType = DelegateType.Open; } (TargetPointer targetObject, TargetCodePointer targetMethodPtr) = delegateType switch diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs index 37051deb63e97b..1b87c4beb4db0a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Delegate.cs @@ -9,11 +9,5 @@ internal sealed partial class Delegate : IData [Field] public TargetPointer Target { get; } [Field] public TargetCodePointer MethodPtr { get; } [Field] public TargetCodePointer MethodPtrAux { get; } -} - -[CdacType(nameof(DataType.MulticastDelegate))] -internal sealed partial class MulticastDelegate : IData -{ - [Field] public TargetPointer InvocationList { get; } [Field] public TargetNInt InvocationCount { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs index 2f29139bd9611b..4abbf4ce732740 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs @@ -100,7 +100,6 @@ public enum DataType InterpMethodContextFrame, Array, Delegate, - MulticastDelegate, TypedByRef, StackTraceArrayHeader, StackTraceElement, From c061b1f6fa5ae73ce57b17f6cc43525cc2d4a4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= Date: Sat, 27 Jun 2026 18:49:07 +0200 Subject: [PATCH 09/10] Address feedback --- docs/design/datacontracts/Object.md | 1 + .../src/System/Delegate.CoreCLR.cs | 10 ------- .../src/System/MulticastDelegate.CoreCLR.cs | 26 +++++++++++-------- src/coreclr/debug/daccess/dacdbiimpl.cpp | 3 +-- .../src/System/Delegate.cs | 2 +- .../Contracts/Object_1.cs | 6 ++--- 6 files changed, 20 insertions(+), 28 deletions(-) diff --git a/docs/design/datacontracts/Object.md b/docs/design/datacontracts/Object.md index b1deda0504f581..f0f6f107748e96 100644 --- a/docs/design/datacontracts/Object.md +++ b/docs/design/datacontracts/Object.md @@ -221,6 +221,7 @@ DelegateInfo GetDelegateInfo(TargetPointer address) Data.Delegate del = new Data.Delegate(target, address); // Classify the delegate from its invocation count and auxiliary pointer. + // This does not handle open virtual delegates correctly. DelegateType delegateType = target.ReadNInt(address + /* Delegate::InvocationCount offset */) switch { 0 => del.MethodPtrAux == TargetCodePointer.Null diff --git a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index 6c60f3a670e6e3..cc951806f2a4ea 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -219,9 +219,6 @@ protected virtual MethodInfo GetMethodImpl() return (MethodInfo)_helperObject; } - internal object? GetTarget() => Unsafe.As(this).GetTarget(); - - // V1 API. [RequiresUnreferencedCode("The target method might be removed")] public static Delegate? CreateDelegate(Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure) { @@ -257,7 +254,6 @@ protected virtual MethodInfo GetMethodImpl() return d; } - // V1 API. public static Delegate? CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllMethods)] Type target, string method, bool ignoreCase, bool throwOnBindFailure) { ArgumentNullException.ThrowIfNull(type); @@ -293,7 +289,6 @@ protected virtual MethodInfo GetMethodImpl() return d; } - // V1 API. public static Delegate? CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure) { ArgumentNullException.ThrowIfNull(type); @@ -328,7 +323,6 @@ protected virtual MethodInfo GetMethodImpl() return d; } - // V2 API. public static Delegate? CreateDelegate(Type type, object? firstArgument, MethodInfo method, bool throwOnBindFailure) { ArgumentNullException.ThrowIfNull(type); @@ -360,10 +354,6 @@ protected virtual MethodInfo GetMethodImpl() return d; } - // - // internal implementation details (FCALLS and utilities) - // - internal static Delegate CreateDelegateForDynamicMethod(Type type, object? target, RuntimeMethodHandle method, DynamicMethod dynamicMethod) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs index dda6a6c812e66c..f9e22594e1a3ad 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.CoreCLR.cs @@ -17,15 +17,15 @@ namespace System [ComVisible(true)] public abstract partial class MulticastDelegate : Delegate { - internal object? _invocationList; - + private object? _invocationList; + // This is set under 3 circumstances // 1. Multicast delegate // 2. Unmanaged function pointer // 3. Open virtual delegate - internal nint _invocationCount; + private nint _invocationCount; - internal bool IsUnmanagedFunctionPtr() + private bool IsUnmanagedFunctionPtr() { return _invocationCount == -1; } @@ -59,7 +59,7 @@ public sealed override bool Equals([NotNullWhen(true)] object? obj) // there are 3 kind of delegate kinds that fall into this bucket // 1- Multicast (_invocationList is Object[]) // 2- Unmanaged FntPtr (_invocationList == null) - // 3- Open virtual (_invocationCount == MethodDesc of target, _invocationList == null, LoaderAllocator, or DynamicResolver) + // 3- Open virtual (_invocationCount == MethodDesc of target, _invocationList == null) if (HasSingleTarget) { @@ -408,15 +408,19 @@ public sealed override int GetHashCode() } } - internal new object? GetTarget() + internal new object? Target { - if (_invocationList is object[] invocationList) + get { - // Multicast -> return the target of the last delegate in the list - int invocationCount = (int)_invocationCount; - return ((Delegate)invocationList[invocationCount - 1]).GetTarget(); + Delegate instance = this; + if (_invocationList is object[] invocationList) + { + // Multicast -> return the target of the last delegate in the list + int invocationCount = (int)_invocationCount; + instance = (Delegate)invocationList[invocationCount - 1]; + } + return instance._methodPtrAux == 0 ? instance._target : null; } - return _methodPtrAux == 0 ? _target : null; } protected override MethodInfo GetMethodImpl() diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index d4b39f4024b909..a35d84da2adb12 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -3267,8 +3267,7 @@ DacDbiInterfaceImpl::DelegateType DacDbiInterfaceImpl::GetDelegateType(VMPTR_Obj if (invocationCount == 0) { // If this delegate points to a static function or this is a open virtual delegate, this should be non-null - // Special case: This might fail in a VSD delegate (instance open virtual)... - // TODO: There is the special signatures cases missing. + // This does not handle open virtual delegates correctly. TADDR targetMethodPtr = PCODEToPINSTR(pDelObj->GetMethodPtrAux()); if (targetMethodPtr == (TADDR)NULL) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs index bd08fcd15e855a..1fddcf0357ba25 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs @@ -80,7 +80,7 @@ public abstract partial class Delegate : ICloneable, ISerializable /// true if the has a single invocation target. public bool HasSingleTarget => Unsafe.As(this).HasSingleTarget; - public object? Target => Unsafe.As(this).GetTarget(); + public object? Target => Unsafe.As(this).Target; #endif /// diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs index 0d56541dbb049c..6607f78295f1d5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Object_1.cs @@ -163,10 +163,8 @@ public DelegateInfo GetDelegateInfo(TargetPointer address) { Data.Delegate del = _target.ProcessedData.GetOrAdd(address); - // Classify by invocation count first: - // anything other than 0 indicates a multicast/wrapper/special-sig delegate - // that this API does not interpret further. Only when invocationCount==0 - // do MethodPtr/MethodPtrAux unambiguously identify a closed/open delegate. + // Classify by invocation count first to handle multicast and unmanaged. + // This does not handle open virtual delegates correctly. DelegateType delegateType = DelegateType.Unknown; if (del.InvocationCount.Value == 0) { From 4a14a235f2e868fc32c4e1cb87dafb4883e115f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= <35800402+MichalPetryka@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:51:31 +0200 Subject: [PATCH 10/10] Add new Target property to MulticastDelegate --- .../System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mono/System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs b/src/mono/System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs index c026cf46a88539..03f3716c908927 100644 --- a/src/mono/System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/MulticastDelegate.Mono.cs @@ -268,6 +268,8 @@ private static int LastIndexOf(Delegate[] haystack, Delegate[] needle) } } + internal new object? Target => GetTarget(); + internal override object? GetTarget() { return delegates?.Length > 0 ? delegates[delegates.Length - 1].GetTarget() : base.GetTarget();