From d9dfe475d3e41b0ad1d6adfa52e5dbcfcbf228d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:13:21 +0000 Subject: [PATCH 1/9] Initial plan From c0c5981ee806e54cf1862d47c2b1384c1ea54f15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:38:06 +0000 Subject: [PATCH 2/9] Share ObjectiveCMarshal tracking implementation on CoreCLR Co-authored-by: AaronRobinsonMSFT <30635565+AaronRobinsonMSFT@users.noreply.github.com> --- .../ObjectiveCMarshal.CoreCLR.cs | 104 ++++++++++++++++-- src/coreclr/vm/conditionalweaktable.cpp | 10 +- src/coreclr/vm/conditionalweaktable.h | 12 +- src/coreclr/vm/corelib.h | 3 + .../vm/datadescriptor/datadescriptor.inc | 2 +- src/coreclr/vm/interoplibinterface.h | 26 +++-- src/coreclr/vm/interoplibinterface_objc.cpp | 89 ++------------- .../ObjectiveC/ObjectiveCMarshal.cs | 18 +-- .../Contracts/ObjectiveCMarshal_2.cs | 36 ++++++ .../CoreCLRContracts.cs | 1 + .../Data/ObjcTrackingInformation.cs | 11 ++ .../Data/ObjectiveCMarshal.cs | 11 ++ 12 files changed, 206 insertions(+), 117 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_2.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjcTrackingInformation.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjectiveCMarshal.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs index e6b81fdc13acd0..5676fc95c24d78 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs @@ -8,6 +8,9 @@ namespace System.Runtime.InteropServices.ObjectiveC { public static partial class ObjectiveCMarshal { + private static bool s_initialized; + private static ConditionalWeakTable s_objects = new(); + /// /// Sets a pending exception to be thrown the next time the runtime is entered from an Objective-C msgSend P/Invoke. /// @@ -31,19 +34,41 @@ private static partial bool TrySetGlobalMessageSendCallback( private static unsafe partial bool TryInitializeReferenceTracker( delegate* unmanaged beginEndCallback, delegate* unmanaged isReferencedCallback, - delegate* unmanaged trackedObjectEnteredFinalization); + delegate* unmanaged trackedObjectEnteredFinalization, + ObjectHandleOnStack objectTrackingInfoTable); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjCMarshal_AllocateReferenceTrackingHandle")] + private static partial IntPtr AllocateReferenceTrackingHandle(ObjectHandleOnStack obj); - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjCMarshal_CreateReferenceTrackingHandle")] - private static partial IntPtr CreateReferenceTrackingHandleInternal( - ObjectHandleOnStack obj, + private static IntPtr CreateReferenceTrackingHandleInternal( + object obj, out int memInSizeT, - out IntPtr mem); + out IntPtr mem) + { + // Rely on GetOrCreateReferenceTrackingMemoryInternal for state checking. + GetOrCreateReferenceTrackingMemoryInternal(obj, out memInSizeT, out mem); + return AllocateReferenceTrackingHandle(ObjectHandleOnStack.Create(ref obj)); + } - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjCMarshal_GetOrCreateReferenceTrackingMemory")] - private static partial void GetOrCreateReferenceTrackingMemoryInternal( - ObjectHandleOnStack obj, + private static unsafe void GetOrCreateReferenceTrackingMemoryInternal( + object obj, out int memInSizeT, - out IntPtr mem); + out IntPtr mem) + { + if (!s_initialized) + { + throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCMarshalNotInitialized); + } + + if (!obj.GetMethodTable()->IsTrackedReferenceWithFinalizer) + { + throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCTypeNoFinalizer); + } + + ObjcTrackingInformation trackerInfo = s_objects.GetOrAdd(obj, static _ => new ObjcTrackingInformation()); + trackerInfo.EnsureInitialized(obj); + trackerInfo.GetTaggedMemory(out memInSizeT, out mem); + } [UnmanagedCallersOnly] internal static unsafe void* InvokeUnhandledExceptionPropagation(Exception* pExceptionArg, IntPtr methodDesc, IntPtr* pContext, Exception* pException) @@ -63,5 +88,66 @@ private static partial void GetOrCreateReferenceTrackingMemoryInternal( return null; } } + + internal sealed class ObjcTrackingInformation + { + // Keep in sync with NativeAOT. + private const int TAGGED_MEMORY_SIZE_IN_POINTERS = 2; + + internal IntPtr _memory; + private GCHandle _longWeakHandle; + + public ObjcTrackingInformation() + { + _memory = (IntPtr)NativeMemory.AllocZeroed(TAGGED_MEMORY_SIZE_IN_POINTERS * (nuint)IntPtr.Size); + } + + public void GetTaggedMemory(out int memInSizeT, out IntPtr mem) + { + memInSizeT = TAGGED_MEMORY_SIZE_IN_POINTERS; + mem = _memory; + } + + public void EnsureInitialized(object o) + { + if (_longWeakHandle.IsAllocated) + { + return; + } + + GCHandle newHandle = GCHandle.Alloc(o, GCHandleType.WeakTrackResurrection); + lock (this) + { + if (_longWeakHandle.IsAllocated) + { + newHandle.Free(); + } + else + { + _longWeakHandle = newHandle; + } + } + } + + ~ObjcTrackingInformation() + { + if (_longWeakHandle.IsAllocated && _longWeakHandle.Target != null) + { + GC.ReRegisterForFinalize(this); + return; + } + + if (_memory != IntPtr.Zero) + { + NativeMemory.Free((void*)_memory); + _memory = IntPtr.Zero; + } + + if (_longWeakHandle.IsAllocated) + { + _longWeakHandle.Free(); + } + } + } } } diff --git a/src/coreclr/vm/conditionalweaktable.cpp b/src/coreclr/vm/conditionalweaktable.cpp index 03b7798fcde9f5..d0865db877c957 100644 --- a/src/coreclr/vm/conditionalweaktable.cpp +++ b/src/coreclr/vm/conditionalweaktable.cpp @@ -8,8 +8,14 @@ bool ConditionalWeakTableContainerObject::TryGetValue(OBJECTREF key, OBJECTREF* value) { - STANDARD_VM_CONTRACT; - SUPPORTS_DAC; + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(key != nullptr && value != nullptr); + } + CONTRACTL_END; _ASSERTE(key != nullptr && value != nullptr); INT32 hashCode = key->TryGetHashCode(); diff --git a/src/coreclr/vm/conditionalweaktable.h b/src/coreclr/vm/conditionalweaktable.h index 24885ea170771f..65d2c3eec89b89 100644 --- a/src/coreclr/vm/conditionalweaktable.h +++ b/src/coreclr/vm/conditionalweaktable.h @@ -39,9 +39,7 @@ class ConditionalWeakTableContainerObject final : public Object ENTRYARRAYREF _entries; public: -#ifdef DACCESS_COMPILE bool TryGetValue(OBJECTREF key, OBJECTREF* value); -#endif }; class ConditionalWeakTableObject final : public Object @@ -50,11 +48,9 @@ class ConditionalWeakTableObject final : public Object OBJECTREF _lock; VolatilePtr _container; public: -#ifdef DACCESS_COMPILE - // Currently, we only use this for access from the DAC, so we don't need to worry about - // locking or tracking the active enumerator count. - // If we need to use this in a context where the runtime isn't suspended, we need to add - // the locking and tracking support. + // This helper can be used when the runtime is suspended. If we need to use this + // in a context where the runtime isn't suspended, we need to add locking and + // active enumerator tracking support. template bool TryGetValue(TKey key, TValue* value) { @@ -67,10 +63,8 @@ class ConditionalWeakTableObject final : public Object { *value = (TValue)valueObj; } - return found; } -#endif }; #endif // CONDITIONAL_WEAK_TABLE_H diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index cc515fd5f610e8..a9dfe415c73dd1 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -460,6 +460,9 @@ DEFINE_FIELD_U(_buckets, GCHandleSetObject, _buckets) #ifdef FEATURE_OBJCMARSHAL DEFINE_CLASS(OBJCMARSHAL, ObjectiveC, ObjectiveCMarshal) DEFINE_METHOD(OBJCMARSHAL, INVOKEUNHANDLEDEXCEPTIONPROPAGATION, InvokeUnhandledExceptionPropagation, SM_PtrException_IntPtr_PtrIntPtr_PtrException_RetVoidPtr) +DEFINE_FIELD(OBJCMARSHAL, OBJECTS, s_objects) +DEFINE_CLASS_U(ObjectiveC, ObjectiveCMarshal+ObjcTrackingInformation, ObjcTrackingInformationObject) +DEFINE_FIELD_U(_memory, ObjcTrackingInformationObject, _memory) #endif // FEATURE_OBJCMARSHAL DEFINE_CLASS_U(Interop, TypeMapLazyDictionary+CallbackContext, CallbackContext) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 70dc6b27ebff2d..38a5e5207df52b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1694,7 +1694,7 @@ CDAC_GLOBAL_CONTRACT(Loader, c1) CDAC_GLOBAL_CONTRACT(ManagedTypeSource, c1) CDAC_GLOBAL_CONTRACT(Notifications, c1) #ifdef FEATURE_OBJCMARSHAL -CDAC_GLOBAL_CONTRACT(ObjectiveCMarshal, c1) +CDAC_GLOBAL_CONTRACT(ObjectiveCMarshal, c2) #endif // FEATURE_OBJCMARSHAL CDAC_GLOBAL_CONTRACT(Object, c1) CDAC_GLOBAL_CONTRACT(PlatformMetadata, c1) diff --git a/src/coreclr/vm/interoplibinterface.h b/src/coreclr/vm/interoplibinterface.h index ca80319828c0a8..3db7b8b3b7880a 100644 --- a/src/coreclr/vm/interoplibinterface.h +++ b/src/coreclr/vm/interoplibinterface.h @@ -52,21 +52,27 @@ class ObjCMarshalNative static void OnEnteredFinalizerQueue(_In_ OBJECTREF object); }; +class ObjcTrackingInformationObject final : public Object +{ + friend class CoreLibBinder; +public: + INTPTR _memory; +}; + +#ifdef USE_CHECKED_OBJECTREFS +using OBJC_TRACKING_INFO_REF = REF; +#else +using OBJC_TRACKING_INFO_REF = DPTR(ObjcTrackingInformationObject); +#endif + extern "C" BOOL QCALLTYPE ObjCMarshal_TryInitializeReferenceTracker( _In_ ObjCMarshalNative::BeginEndCallback beginEndCallback, _In_ ObjCMarshalNative::IsReferencedCallback isReferencedCallback, - _In_ ObjCMarshalNative::EnteredFinalizationCallback trackedObjectEnteredFinalization); - -extern "C" void* QCALLTYPE ObjCMarshal_CreateReferenceTrackingHandle( - _In_ QCall::ObjectHandleOnStack obj, - _Out_ int* memInSizeT, - _Outptr_ void** mem); + _In_ ObjCMarshalNative::EnteredFinalizationCallback trackedObjectEnteredFinalization, + _In_ QCall::ObjectHandleOnStack objectTrackingInfoTable); -extern "C" void QCALLTYPE ObjCMarshal_GetOrCreateReferenceTrackingMemory( - _In_ QCall::ObjectHandleOnStack obj, - _Out_ int* memInSizeT, - _Outptr_ void** mem); +extern "C" void* QCALLTYPE ObjCMarshal_AllocateReferenceTrackingHandle(_In_ QCall::ObjectHandleOnStack obj); extern "C" BOOL QCALLTYPE ObjCMarshal_TrySetGlobalMessageSendCallback( _In_ ObjCMarshalNative::MessageSendFunction msgSendFunction, diff --git a/src/coreclr/vm/interoplibinterface_objc.cpp b/src/coreclr/vm/interoplibinterface_objc.cpp index 1cc9749de02368..bbf26520967805 100644 --- a/src/coreclr/vm/interoplibinterface_objc.cpp +++ b/src/coreclr/vm/interoplibinterface_objc.cpp @@ -5,6 +5,7 @@ // Runtime headers #include "common.h" +#include "conditionalweaktable.h" // Interop library header #include @@ -21,12 +22,14 @@ namespace ObjCMarshalNative::BeginEndCallback g_BeginEndCallback; ObjCMarshalNative::IsReferencedCallback g_IsReferencedCallback; ObjCMarshalNative::EnteredFinalizationCallback g_TrackedObjectEnteredFinalizationCallback; + OBJECTHANDLE g_ObjectiveCTrackingInfoTable; } extern "C" BOOL QCALLTYPE ObjCMarshal_TryInitializeReferenceTracker( _In_ ObjCMarshalNative::BeginEndCallback beginEndCallback, _In_ ObjCMarshalNative::IsReferencedCallback isReferencedCallback, - _In_ ObjCMarshalNative::EnteredFinalizationCallback trackedObjectEnteredFinalization) + _In_ ObjCMarshalNative::EnteredFinalizationCallback trackedObjectEnteredFinalization, + _In_ QCall::ObjectHandleOnStack objectTrackingInfoTable) { QCALL_CONTRACT; _ASSERTE(beginEndCallback != NULL @@ -47,6 +50,7 @@ extern "C" BOOL QCALLTYPE ObjCMarshal_TryInitializeReferenceTracker( g_BeginEndCallback = beginEndCallback; g_IsReferencedCallback = isReferencedCallback; g_TrackedObjectEnteredFinalizationCallback = trackedObjectEnteredFinalization; + g_ObjectiveCTrackingInfoTable = GetAppDomain()->CreateHandle(objectTrackingInfoTable.Get()); success = TRUE; } @@ -57,92 +61,24 @@ extern "C" BOOL QCALLTYPE ObjCMarshal_TryInitializeReferenceTracker( return success; } -namespace -{ - void* GetTaggedMemoryForObject( - _In_ QCall::ObjectHandleOnStack obj, - _Out_ size_t* memInSizeT) - { - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_COOPERATIVE; - PRECONDITION(CheckPointer(memInSizeT)); - } - CONTRACTL_END; - - // The reference tracking system must be initialized. - if (!g_ReferenceTrackerInitialized) - COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCMarshalNotInitialized")); - - // The object's type must be marked appropriately and with a finalizer. - if (!obj.Get()->GetMethodTable()->IsTrackedReferenceWithFinalizer()) - COMPlusThrow(kInvalidOperationException, W("InvalidOperation_ObjectiveCTypeNoFinalizer")); - - // Initialize the syncblock for this instance. - SyncBlock* syncBlock = obj.Get()->GetSyncBlock(); - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); - void* taggedMemoryLocal = interopInfo->EnsureTaggedMemoryAllocated(memInSizeT); - _ASSERTE(taggedMemoryLocal != NULL); - - return taggedMemoryLocal; - } -} - -extern "C" void* QCALLTYPE ObjCMarshal_CreateReferenceTrackingHandle( - _In_ QCall::ObjectHandleOnStack obj, - _Out_ int* memInSizeT, - _Outptr_ void** mem) +extern "C" void* QCALLTYPE ObjCMarshal_AllocateReferenceTrackingHandle(_In_ QCall::ObjectHandleOnStack obj) { QCALL_CONTRACT; - _ASSERTE(memInSizeT != NULL); - _ASSERTE(mem != NULL); OBJECTHANDLE instHandle; - size_t memInSizeTLocal; - void* taggedMemoryLocal; BEGIN_QCALL; // Switch to Cooperative mode since object references // are being manipulated. GCX_COOP(); - taggedMemoryLocal = GetTaggedMemoryForObject(obj, &memInSizeTLocal); instHandle = GetAppDomain()->CreateTypedHandle(obj.Get(), HNDTYPE_REFCOUNTED); END_QCALL; - *memInSizeT = (int)memInSizeTLocal; - *mem = taggedMemoryLocal; return (void*)instHandle; } -extern "C" void QCALLTYPE ObjCMarshal_GetOrCreateReferenceTrackingMemory( - _In_ QCall::ObjectHandleOnStack obj, - _Out_ int* memInSizeT, - _Outptr_ void** mem) -{ - QCALL_CONTRACT; - _ASSERTE(memInSizeT != NULL); - _ASSERTE(mem != NULL); - - size_t memInSizeTLocal; - void* taggedMemoryLocal; - - BEGIN_QCALL; - - // Switch to Cooperative mode since object references - // are being manipulated. - GCX_COOP(); - taggedMemoryLocal = GetTaggedMemoryForObject(obj, &memInSizeTLocal); - - END_QCALL; - - *memInSizeT = (int)memInSizeTLocal; - *mem = taggedMemoryLocal; -} - namespace { BOOL s_msgSendOverridden = FALSE; @@ -218,17 +154,16 @@ namespace } CONTRACTL_END; - SyncBlock* syncBlock = object->PassiveGetSyncBlock(); - if (syncBlock == NULL) + CONDITIONAL_WEAK_TABLE_REF trackingTable = (CONDITIONAL_WEAK_TABLE_REF)ObjectFromHandle(g_ObjectiveCTrackingInfoTable); + if (trackingTable == NULL) return false; - InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfoNoCreate(); - if (interopInfo == NULL) + OBJECTREF trackingInfoObj = NULL; + if (!trackingTable->TryGetValue(object, &trackingInfoObj)) return false; - // If no tagged memory is allocated, then the instance is not - // being tracked. - void* taggedLocal = interopInfo->GetTaggedMemory(); + OBJC_TRACKING_INFO_REF trackingInfo = (OBJC_TRACKING_INFO_REF)trackingInfoObj; + void* taggedLocal = (void*)trackingInfo->_memory; if (taggedLocal == NULL) return false; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs index c590b2e5e6f2c5..e9b8f606c65875 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs @@ -70,10 +70,18 @@ public static unsafe void Initialize( ArgumentNullException.ThrowIfNull(unhandledExceptionPropagationHandler); if (s_unhandledExceptionPropagationHandler != null - || !TryInitializeReferenceTracker(beginEndCallback, isReferencedCallback, trackedObjectEnteredFinalization)) + || !TryInitializeReferenceTracker( + beginEndCallback, + isReferencedCallback, + trackedObjectEnteredFinalization +#if !NATIVEAOT + , ObjectHandleOnStack.Create(ref s_objects) +#endif + )) { throw new InvalidOperationException(SR.InvalidOperation_ReinitializeObjectiveCMarshal); } + s_initialized = true; s_unhandledExceptionPropagationHandler = unhandledExceptionPropagationHandler; } @@ -112,11 +120,7 @@ public static GCHandle CreateReferenceTrackingHandle( ArgumentNullException.ThrowIfNull(obj); IntPtr refCountHandle = CreateReferenceTrackingHandleInternal( -#if NATIVEAOT obj, -#else - ObjectHandleOnStack.Create(ref obj), -#endif out int memInSizeT, out IntPtr mem); @@ -158,11 +162,7 @@ public static Span GetOrCreateReferenceTrackingMemory(object obj) ArgumentNullException.ThrowIfNull(obj); GetOrCreateReferenceTrackingMemoryInternal( -#if NATIVEAOT obj, -#else - ObjectHandleOnStack.Create(ref obj), -#endif out int memInSizeT, out IntPtr mem); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_2.cs new file mode 100644 index 00000000000000..917f4902f50619 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_2.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct ObjectiveCMarshal_2 : IObjectiveCMarshal +{ + private readonly Target _target; + + internal ObjectiveCMarshal_2(Target target) + { + _target = target; + } + + public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) + { + size = default; + + TargetPointer? objectsTable = Data.ObjectiveCMarshal.ObjectTrackingInfoTable(_target); + if (objectsTable is null || objectsTable.Value == TargetPointer.Null) + return TargetPointer.Null; + + IConditionalWeakTable cwt = _target.Contracts.ConditionalWeakTable; + if (!cwt.TryGetValue(objectsTable.Value, address, out TargetPointer trackingInfoAddress)) + return TargetPointer.Null; + + ObjcTrackingInformation trackingInfo = _target.ProcessedData.GetOrAdd(trackingInfoAddress); + if (trackingInfo.Memory == TargetPointer.Null) + return TargetPointer.Null; + + size = new TargetNUInt(2 * (ulong)_target.PointerSize); + return trackingInfo.Memory; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index fe3fabf7f8d8f9..07c6574079e147 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -28,6 +28,7 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new Signature_1(t)); registry.Register("c1", static t => new BuiltInCOM_1(t)); registry.Register("c1", static t => new ObjectiveCMarshal_1(t)); + registry.Register("c2", static t => new ObjectiveCMarshal_2(t)); registry.Register("c1", static t => new ConditionalWeakTable_1(t)); registry.Register("c1", static t => new ManagedTypeSource_1(t)); registry.Register("c1", static t => new AuxiliarySymbols_1(t)); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjcTrackingInformation.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjcTrackingInformation.cs new file mode 100644 index 00000000000000..c48241451e2d0a --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjcTrackingInformation.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType("System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal+ObjcTrackingInformation")] +internal sealed partial class ObjcTrackingInformation : IData +{ + [Field("_memory")] + public TargetPointer Memory { get; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjectiveCMarshal.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjectiveCMarshal.cs new file mode 100644 index 00000000000000..fb5afee81171aa --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ObjectiveCMarshal.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +[CdacType("System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal")] +internal sealed partial class ObjectiveCMarshal : IData +{ + [StaticReference("s_objects")] + public static partial TargetPointer? ObjectTrackingInfoTable(Target target); +} From a2f8c37c70a7bf2b2f6ce0a08326f266c3b1b1ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:44:14 +0000 Subject: [PATCH 3/9] Address review feedback on ObjectiveCMarshal sharing Co-authored-by: AaronRobinsonMSFT <30635565+AaronRobinsonMSFT@users.noreply.github.com> --- .../ObjectiveCMarshal.CoreCLR.cs | 36 +++++++++---------- src/coreclr/vm/conditionalweaktable.cpp | 1 - src/coreclr/vm/conditionalweaktable.h | 8 +++-- src/coreclr/vm/interoplibinterface_objc.cpp | 3 ++ 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs index 5676fc95c24d78..a262bc34b73634 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Versioning; +using System.Threading; namespace System.Runtime.InteropServices.ObjectiveC { @@ -45,6 +46,8 @@ private static IntPtr CreateReferenceTrackingHandleInternal( out int memInSizeT, out IntPtr mem) { + ArgumentNullException.ThrowIfNull(obj); + // Rely on GetOrCreateReferenceTrackingMemoryInternal for state checking. GetOrCreateReferenceTrackingMemoryInternal(obj, out memInSizeT, out mem); return AllocateReferenceTrackingHandle(ObjectHandleOnStack.Create(ref obj)); @@ -55,6 +58,8 @@ private static unsafe void GetOrCreateReferenceTrackingMemoryInternal( out int memInSizeT, out IntPtr mem) { + ArgumentNullException.ThrowIfNull(obj); + if (!s_initialized) { throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCMarshalNotInitialized); @@ -95,7 +100,7 @@ internal sealed class ObjcTrackingInformation private const int TAGGED_MEMORY_SIZE_IN_POINTERS = 2; internal IntPtr _memory; - private GCHandle _longWeakHandle; + private IntPtr _longWeakHandle; public ObjcTrackingInformation() { @@ -110,42 +115,37 @@ public void GetTaggedMemory(out int memInSizeT, out IntPtr mem) public void EnsureInitialized(object o) { - if (_longWeakHandle.IsAllocated) + if (_longWeakHandle != IntPtr.Zero) { return; } - GCHandle newHandle = GCHandle.Alloc(o, GCHandleType.WeakTrackResurrection); - lock (this) + IntPtr newHandle = GCHandle.ToIntPtr(GCHandle.Alloc(o, GCHandleType.WeakTrackResurrection)); + if (Interlocked.CompareExchange(ref _longWeakHandle, newHandle, IntPtr.Zero) != IntPtr.Zero) { - if (_longWeakHandle.IsAllocated) - { - newHandle.Free(); - } - else - { - _longWeakHandle = newHandle; - } + GCHandle.FromIntPtr(newHandle).Free(); } } ~ObjcTrackingInformation() { - if (_longWeakHandle.IsAllocated && _longWeakHandle.Target != null) + IntPtr longWeakHandle = Volatile.Read(ref _longWeakHandle); + if (longWeakHandle != IntPtr.Zero && GCHandle.FromIntPtr(longWeakHandle).Target != null) { GC.ReRegisterForFinalize(this); return; } - if (_memory != IntPtr.Zero) + IntPtr memory = Interlocked.Exchange(ref _memory, IntPtr.Zero); + if (memory != IntPtr.Zero) { - NativeMemory.Free((void*)_memory); - _memory = IntPtr.Zero; + NativeMemory.Free((void*)memory); } - if (_longWeakHandle.IsAllocated) + longWeakHandle = Interlocked.Exchange(ref _longWeakHandle, IntPtr.Zero); + if (longWeakHandle != IntPtr.Zero) { - _longWeakHandle.Free(); + GCHandle.FromIntPtr(longWeakHandle).Free(); } } } diff --git a/src/coreclr/vm/conditionalweaktable.cpp b/src/coreclr/vm/conditionalweaktable.cpp index d0865db877c957..6488261b6afe6a 100644 --- a/src/coreclr/vm/conditionalweaktable.cpp +++ b/src/coreclr/vm/conditionalweaktable.cpp @@ -16,7 +16,6 @@ bool ConditionalWeakTableContainerObject::TryGetValue(OBJECTREF key, OBJECTREF* PRECONDITION(key != nullptr && value != nullptr); } CONTRACTL_END; - _ASSERTE(key != nullptr && value != nullptr); INT32 hashCode = key->TryGetHashCode(); diff --git a/src/coreclr/vm/conditionalweaktable.h b/src/coreclr/vm/conditionalweaktable.h index 65d2c3eec89b89..8f2749d94c1f72 100644 --- a/src/coreclr/vm/conditionalweaktable.h +++ b/src/coreclr/vm/conditionalweaktable.h @@ -48,9 +48,11 @@ class ConditionalWeakTableObject final : public Object OBJECTREF _lock; VolatilePtr _container; public: - // This helper can be used when the runtime is suspended. If we need to use this - // in a context where the runtime isn't suspended, we need to add locking and - // active enumerator tracking support. + // This helper can be used when the runtime is suspended (for example, DAC access + // or Objective-C interop callbacks that run under GC suspension). During suspension + // the table shape is stable since no mutations can run, so lock-free lookup is safe. + // If we need to use this in a context where the runtime isn't suspended, we need to + // add locking and active enumerator tracking support. template bool TryGetValue(TKey key, TValue* value) { diff --git a/src/coreclr/vm/interoplibinterface_objc.cpp b/src/coreclr/vm/interoplibinterface_objc.cpp index bbf26520967805..b0c2674fd9f089 100644 --- a/src/coreclr/vm/interoplibinterface_objc.cpp +++ b/src/coreclr/vm/interoplibinterface_objc.cpp @@ -154,6 +154,9 @@ namespace } CONTRACTL_END; + if (g_ObjectiveCTrackingInfoTable == NULL) + return false; + CONDITIONAL_WEAK_TABLE_REF trackingTable = (CONDITIONAL_WEAK_TABLE_REF)ObjectFromHandle(g_ObjectiveCTrackingInfoTable); if (trackingTable == NULL) return false; From 0af2c2a0b1c026a6e4a0b84fcf10d58aa0e4a822 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:50:50 +0000 Subject: [PATCH 4/9] Fold ObjectiveCMarshal cDAC updates into contract v1 Co-authored-by: AaronRobinsonMSFT <30635565+AaronRobinsonMSFT@users.noreply.github.com> --- .../vm/datadescriptor/datadescriptor.inc | 2 +- .../Contracts/ObjectiveCMarshal_1.cs | 18 ++++++++++ .../Contracts/ObjectiveCMarshal_2.cs | 36 ------------------- .../CoreCLRContracts.cs | 1 - 4 files changed, 19 insertions(+), 38 deletions(-) delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_2.cs diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 38a5e5207df52b..70dc6b27ebff2d 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1694,7 +1694,7 @@ CDAC_GLOBAL_CONTRACT(Loader, c1) CDAC_GLOBAL_CONTRACT(ManagedTypeSource, c1) CDAC_GLOBAL_CONTRACT(Notifications, c1) #ifdef FEATURE_OBJCMARSHAL -CDAC_GLOBAL_CONTRACT(ObjectiveCMarshal, c2) +CDAC_GLOBAL_CONTRACT(ObjectiveCMarshal, c1) #endif // FEATURE_OBJCMARSHAL CDAC_GLOBAL_CONTRACT(Object, c1) CDAC_GLOBAL_CONTRACT(PlatformMetadata, c1) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs index 5fec96d68afcd2..87979da1f6c859 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Diagnostics.DataContractReader.Data; + namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal readonly struct ObjectiveCMarshal_1 : IObjectiveCMarshal @@ -16,6 +18,21 @@ public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size { size = default; + TargetPointer? objectsTable = Data.ObjectiveCMarshal.ObjectTrackingInfoTable(_target); + if (objectsTable is not null && objectsTable.Value != TargetPointer.Null) + { + IConditionalWeakTable cwt = _target.Contracts.ConditionalWeakTable; + if (cwt.TryGetValue(objectsTable.Value, address, out TargetPointer trackingInfoAddress)) + { + ObjcTrackingInformation trackingInfo = _target.ProcessedData.GetOrAdd(trackingInfoAddress); + if (trackingInfo.Memory != TargetPointer.Null) + { + size = new TargetNUInt(2 * (ulong)_target.PointerSize); + return trackingInfo.Memory; + } + } + } + TargetPointer syncBlock = _target.Contracts.Object.GetSyncBlockAddress(address); if (syncBlock == TargetPointer.Null) return TargetPointer.Null; @@ -24,6 +41,7 @@ public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size TargetPointer taggedMemory = sb.InteropInfo?.TaggedMemory ?? TargetPointer.Null; if (taggedMemory != TargetPointer.Null) size = new TargetNUInt(2 * (ulong)_target.PointerSize); + return taggedMemory; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_2.cs deleted file mode 100644 index 917f4902f50619..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_2.cs +++ /dev/null @@ -1,36 +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 Microsoft.Diagnostics.DataContractReader.Data; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -internal readonly struct ObjectiveCMarshal_2 : IObjectiveCMarshal -{ - private readonly Target _target; - - internal ObjectiveCMarshal_2(Target target) - { - _target = target; - } - - public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size) - { - size = default; - - TargetPointer? objectsTable = Data.ObjectiveCMarshal.ObjectTrackingInfoTable(_target); - if (objectsTable is null || objectsTable.Value == TargetPointer.Null) - return TargetPointer.Null; - - IConditionalWeakTable cwt = _target.Contracts.ConditionalWeakTable; - if (!cwt.TryGetValue(objectsTable.Value, address, out TargetPointer trackingInfoAddress)) - return TargetPointer.Null; - - ObjcTrackingInformation trackingInfo = _target.ProcessedData.GetOrAdd(trackingInfoAddress); - if (trackingInfo.Memory == TargetPointer.Null) - return TargetPointer.Null; - - size = new TargetNUInt(2 * (ulong)_target.PointerSize); - return trackingInfo.Memory; - } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index 07c6574079e147..fe3fabf7f8d8f9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -28,7 +28,6 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new Signature_1(t)); registry.Register("c1", static t => new BuiltInCOM_1(t)); registry.Register("c1", static t => new ObjectiveCMarshal_1(t)); - registry.Register("c2", static t => new ObjectiveCMarshal_2(t)); registry.Register("c1", static t => new ConditionalWeakTable_1(t)); registry.Register("c1", static t => new ManagedTypeSource_1(t)); registry.Register("c1", static t => new AuxiliarySymbols_1(t)); From 9bc515aa0e557c13dd71f9d6995e933ab02a043c Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 8 Jun 2026 11:13:09 -0700 Subject: [PATCH 5/9] Unify ObjectiveCMarshal reference tracking between CoreCLR and NativeAOT - Share ObjcTrackingInformation class in common ObjectiveCMarshal.cs - Remove syncblock-based tagged memory storage entirely - Use ConditionalWeakTable for both runtimes - Update DAC/cDAC to use CWT lookup for tagged memory - Split GetDataPtr into GetDataPtr/GetGCSafeDataPtr for clarity - Use GetDependentHandleSecondary for CWT value retrieval Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../RuntimeHelpers.CoreCLR.cs | 7 ++ .../ObjectiveCMarshal.CoreCLR.cs | 105 ++-------------- src/coreclr/debug/daccess/request.cpp | 33 +++-- src/coreclr/inc/dacvars.h | 4 + .../ObjectiveCMarshal.NativeAot.cs | 90 +------------- src/coreclr/vm/CMakeLists.txt | 2 +- src/coreclr/vm/binder.cpp | 2 +- src/coreclr/vm/conditionalweaktable.cpp | 15 ++- src/coreclr/vm/conditionalweaktable.h | 2 +- .../vm/datadescriptor/datadescriptor.inc | 3 - src/coreclr/vm/interoplibinterface.h | 4 +- src/coreclr/vm/interoplibinterface_objc.cpp | 1 - src/coreclr/vm/object.h | 33 ++--- src/coreclr/vm/qcallentrypoints.cpp | 3 +- src/coreclr/vm/syncblk.h | 51 -------- src/coreclr/vm/vars.cpp | 4 + src/coreclr/vm/vars.hpp | 5 + .../ObjectiveC/ObjectiveCMarshal.cs | 116 ++++++++++++++---- .../Contracts/ObjectiveCMarshal_1.cs | 30 ++--- .../Data/InteropSyncBlockInfo.cs | 1 - .../MockDescriptors/MockDescriptors.Object.cs | 8 +- .../MockDescriptors.SyncBlock.cs | 17 +-- .../tests/UnitTests/ObjectiveCMarshalTests.cs | 52 +------- 23 files changed, 206 insertions(+), 382 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index bc87d4fdd3d62f..55e5f4c77a7680 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -844,6 +844,9 @@ internal unsafe struct MethodTable #if FEATURE_TYPEEQUIVALENCE private const uint enum_flag_HasTypeEquivalence = 0x02000000; #endif // FEATURE_TYPEEQUIVALENCE +#if FEATURE_OBJCMARSHAL + private const uint enum_flag_IsTrackedReferenceWithFinalizer = 0x04000000; +#endif // FEATURE_OBJCMARSHAL private const uint enum_flag_HasFinalizer = 0x00100000; private const uint enum_flag_Collectible = 0x00200000; private const uint enum_flag_Category_Mask = 0x000F0000; @@ -904,6 +907,10 @@ internal unsafe struct MethodTable public bool HasTypeEquivalence => (Flags & enum_flag_HasTypeEquivalence) != 0; #endif // FEATURE_TYPEEQUIVALENCE +#if FEATURE_OBJCMARSHAL + public bool IsTrackedReferenceWithFinalizer => (Flags & enum_flag_IsTrackedReferenceWithFinalizer) != 0; +#endif // FEATURE_OBJCMARSHAL + public bool HasFinalizer => (Flags & enum_flag_HasFinalizer) != 0; public bool IsCollectible => (Flags & enum_flag_Collectible) != 0; diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs index a262bc34b73634..2064253e5b2e1a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs @@ -3,15 +3,11 @@ using System.Runtime.CompilerServices; using System.Runtime.Versioning; -using System.Threading; namespace System.Runtime.InteropServices.ObjectiveC { public static partial class ObjectiveCMarshal { - private static bool s_initialized; - private static ConditionalWeakTable s_objects = new(); - /// /// Sets a pending exception to be thrown the next time the runtime is entered from an Objective-C msgSend P/Invoke. /// @@ -38,42 +34,21 @@ private static unsafe partial bool TryInitializeReferenceTracker( delegate* unmanaged trackedObjectEnteredFinalization, ObjectHandleOnStack objectTrackingInfoTable); + private static bool TryInitializeReferenceTracker( + delegate* unmanaged beginEndCallback, + delegate* unmanaged isReferencedCallback, + delegate* unmanaged trackedObjectEnteredFinalization) + => TryInitializeReferenceTracker( + beginEndCallback, + isReferencedCallback, + trackedObjectEnteredFinalization, + ObjectHandleOnStack.Create(ref s_objects)); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjCMarshal_AllocateReferenceTrackingHandle")] private static partial IntPtr AllocateReferenceTrackingHandle(ObjectHandleOnStack obj); - private static IntPtr CreateReferenceTrackingHandleInternal( - object obj, - out int memInSizeT, - out IntPtr mem) - { - ArgumentNullException.ThrowIfNull(obj); - - // Rely on GetOrCreateReferenceTrackingMemoryInternal for state checking. - GetOrCreateReferenceTrackingMemoryInternal(obj, out memInSizeT, out mem); - return AllocateReferenceTrackingHandle(ObjectHandleOnStack.Create(ref obj)); - } - - private static unsafe void GetOrCreateReferenceTrackingMemoryInternal( - object obj, - out int memInSizeT, - out IntPtr mem) - { - ArgumentNullException.ThrowIfNull(obj); - - if (!s_initialized) - { - throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCMarshalNotInitialized); - } - - if (!obj.GetMethodTable()->IsTrackedReferenceWithFinalizer) - { - throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCTypeNoFinalizer); - } - - ObjcTrackingInformation trackerInfo = s_objects.GetOrAdd(obj, static _ => new ObjcTrackingInformation()); - trackerInfo.EnsureInitialized(obj); - trackerInfo.GetTaggedMemory(out memInSizeT, out mem); - } + private static IntPtr AllocateReferenceTrackingHandle(object obj) + => AllocateReferenceTrackingHandle(ObjectHandleOnStack.Create(ref obj)); [UnmanagedCallersOnly] internal static unsafe void* InvokeUnhandledExceptionPropagation(Exception* pExceptionArg, IntPtr methodDesc, IntPtr* pContext, Exception* pException) @@ -93,61 +68,5 @@ private static unsafe void GetOrCreateReferenceTrackingMemoryInternal( return null; } } - - internal sealed class ObjcTrackingInformation - { - // Keep in sync with NativeAOT. - private const int TAGGED_MEMORY_SIZE_IN_POINTERS = 2; - - internal IntPtr _memory; - private IntPtr _longWeakHandle; - - public ObjcTrackingInformation() - { - _memory = (IntPtr)NativeMemory.AllocZeroed(TAGGED_MEMORY_SIZE_IN_POINTERS * (nuint)IntPtr.Size); - } - - public void GetTaggedMemory(out int memInSizeT, out IntPtr mem) - { - memInSizeT = TAGGED_MEMORY_SIZE_IN_POINTERS; - mem = _memory; - } - - public void EnsureInitialized(object o) - { - if (_longWeakHandle != IntPtr.Zero) - { - return; - } - - IntPtr newHandle = GCHandle.ToIntPtr(GCHandle.Alloc(o, GCHandleType.WeakTrackResurrection)); - if (Interlocked.CompareExchange(ref _longWeakHandle, newHandle, IntPtr.Zero) != IntPtr.Zero) - { - GCHandle.FromIntPtr(newHandle).Free(); - } - } - - ~ObjcTrackingInformation() - { - IntPtr longWeakHandle = Volatile.Read(ref _longWeakHandle); - if (longWeakHandle != IntPtr.Zero && GCHandle.FromIntPtr(longWeakHandle).Target != null) - { - GC.ReRegisterForFinalize(this); - return; - } - - IntPtr memory = Interlocked.Exchange(ref _memory, IntPtr.Zero); - if (memory != IntPtr.Zero) - { - NativeMemory.Free((void*)memory); - } - - longWeakHandle = Interlocked.Exchange(ref _longWeakHandle, IntPtr.Zero); - if (longWeakHandle != IntPtr.Zero) - { - GCHandle.FromIntPtr(longWeakHandle).Free(); - } - } - } } } diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 2a1cb9f719617e..289f3c23c30932 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -23,6 +23,10 @@ #include #endif // FEATURE_COMWRAPPERS +#if defined(FEATURE_OBJCMARSHAL) +#include +#endif // FEATURE_OBJCMARSHAL + #ifndef TARGET_UNIX // It is unfortunate having to include this header just to get the definition of GenericModeBlock #include @@ -2351,7 +2355,7 @@ ClrDataAccess::GetObjectData(CLRDATA_ADDRESS addr, struct DacpObjectData *object objectData->ElementTypeHandle = (CLRDATA_ADDRESS)(thElem.AsTAddr()); objectData->dwRank = mt->GetRank(); objectData->dwNumComponents = pArrayObj->GetNumComponents (); - objectData->ArrayDataPtr = PTR_CDADDR(pArrayObj->GetDataPtr (TRUE)); + objectData->ArrayDataPtr = PTR_CDADDR(pArrayObj->GetGCSafeDataPtr()); objectData->ArrayBoundsPtr = HOST_CDADDR(pArrayObj->GetBoundsPtr()); objectData->ArrayLowerBoundsPtr = HOST_CDADDR(pArrayObj->GetLowerBoundsPtr()); } @@ -5393,21 +5397,26 @@ namespace #ifdef FEATURE_OBJCMARSHAL EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY { - PTR_SyncBlock pSyncBlk = DACGetSyncBlockFromObjectPointer(CLRDATA_ADDRESS_TO_TADDR(objAddr), target); - if (pSyncBlk != NULL) + if (g_ObjectiveCTrackingInfoTable != NULL) { - PTR_InteropSyncBlockInfo pInfo = pSyncBlk->GetInteropInfoNoCreate(); - if (pInfo != NULL) + CONDITIONAL_WEAK_TABLE_REF trackingTable = (CONDITIONAL_WEAK_TABLE_REF)ObjectFromHandle(g_ObjectiveCTrackingInfoTable); + if (trackingTable != NULL) { - CLRDATA_ADDRESS taggedMemoryLocal = PTR_CDADDR(pInfo->GetTaggedMemory()); - if (taggedMemoryLocal != NULL) + OBJECTREF object = OBJECTREF(CLRDATA_ADDRESS_TO_TADDR(objAddr)); + OBJC_TRACKING_INFO_REF trackingInfo = NULL; + if (trackingTable->TryGetValue(object, &trackingInfo) && trackingInfo != NULL) { - hasTaggedMemory = TRUE; - if (taggedMemory) - *taggedMemory = taggedMemoryLocal; + TADDR memory = (TADDR)trackingInfo->_memory; + if (memory != NULL) + { + hasTaggedMemory = TRUE; + if (taggedMemory) + *taggedMemory = (CLRDATA_ADDRESS)memory; - if (taggedMemorySizeInBytes) - *taggedMemorySizeInBytes = pInfo->GetTaggedMemorySizeInBytes(); + constexpr int TAGGED_MEMORY_SIZE_IN_POINTERS = 2; + if (taggedMemorySizeInBytes) + *taggedMemorySizeInBytes = TAGGED_MEMORY_SIZE_IN_POINTERS * sizeof(void*); + } } } } diff --git a/src/coreclr/inc/dacvars.h b/src/coreclr/inc/dacvars.h index 5e32a086c7c61f..dbb6732b32ff9b 100644 --- a/src/coreclr/inc/dacvars.h +++ b/src/coreclr/inc/dacvars.h @@ -214,6 +214,10 @@ DEFINE_DACVAR(UNKNOWN_POINTER_TYPE, dac__g_pRCWCleanupList, ::g_pRCWCleanupList) DEFINE_DACVAR(UNKNOWN_POINTER_TYPE, dac__g_knownQueryInterfaceImplementations, InteropLib::ABI::g_knownQueryInterfaceImplementations) #endif // FEATURE_COMWRAPPERS +#ifdef FEATURE_OBJCMARSHAL +DEFINE_DACVAR(UNKNOWN_POINTER_TYPE, dac__g_ObjectiveCTrackingInfoTable, ::g_ObjectiveCTrackingInfoTable) +#endif // FEATURE_OBJCMARSHAL + #ifndef TARGET_UNIX DEFINE_DACVAR(SIZE_T, dac__g_runtimeLoadedBaseAddress, ::g_runtimeLoadedBaseAddress) DEFINE_DACVAR(SIZE_T, dac__g_runtimeVirtualSize, ::g_runtimeVirtualSize) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs index 053b99b15e4be1..622da74f5d132f 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs @@ -11,8 +11,6 @@ namespace System.Runtime.InteropServices.ObjectiveC public static unsafe partial class ObjectiveCMarshal { private static readonly IntPtr[] s_ObjcMessageSendFunctions = new IntPtr[(int)MessageSendFunction.MsgSendSuperStret + 1]; - private static bool s_initialized; - private static readonly ConditionalWeakTable s_objects = new(); private static delegate* unmanaged[SuppressGCTransition] s_IsTrackedReferenceCallback; private static delegate* unmanaged[SuppressGCTransition] s_OnEnteredFinalizerQueueCallback; @@ -116,94 +114,10 @@ private static bool TryInitializeReferenceTracker( s_IsTrackedReferenceCallback = (delegate* unmanaged[SuppressGCTransition])isReferencedCallback; s_OnEnteredFinalizerQueueCallback = (delegate* unmanaged[SuppressGCTransition])trackedObjectEnteredFinalization; - s_initialized = true; - return true; } - private static IntPtr CreateReferenceTrackingHandleInternal( - object obj, - out int memInSizeT, - out IntPtr mem) - { - // Rely on GetOrCreateReferenceTrackingMemoryInternal for state checking. - GetOrCreateReferenceTrackingMemoryInternal(obj, out memInSizeT, out mem); - return RuntimeImports.RhHandleAllocRefCounted(obj); - } - - private static void GetOrCreateReferenceTrackingMemoryInternal( - object obj, - out int memInSizeT, - out IntPtr mem) - { - if (!s_initialized) - { - throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCMarshalNotInitialized); - } - - if (!obj.GetMethodTable()->IsTrackedReferenceWithFinalizer) - { - throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCTypeNoFinalizer); - } - - var trackerInfo = s_objects.GetOrAdd(obj, static o => new ObjcTrackingInformation()); - trackerInfo.EnsureInitialized(obj); - trackerInfo.GetTaggedMemory(out memInSizeT, out mem); - } - - internal class ObjcTrackingInformation - { - // This matches the CoreCLR implementation. See - // InteropSyncBlockInfo::m_taggedAlloc in syncblk.h . - private const int TAGGED_MEMORY_SIZE_IN_POINTERS = 2; - - internal IntPtr _memory; - private IntPtr _longWeakHandle; - - public ObjcTrackingInformation() - { - _memory = (IntPtr)NativeMemory.AllocZeroed(TAGGED_MEMORY_SIZE_IN_POINTERS * (nuint)IntPtr.Size); - } - - public void GetTaggedMemory(out int memInSizeT, out IntPtr mem) - { - memInSizeT = TAGGED_MEMORY_SIZE_IN_POINTERS; - mem = _memory; - } - - public void EnsureInitialized(object o) - { - if (_longWeakHandle != IntPtr.Zero) - { - return; - } - - IntPtr newHandle = RuntimeImports.RhHandleAlloc(o, GCHandleType.WeakTrackResurrection); - if (Interlocked.CompareExchange(ref _longWeakHandle, newHandle, IntPtr.Zero) != IntPtr.Zero) - { - RuntimeImports.RhHandleFree(newHandle); - } - } - - ~ObjcTrackingInformation() - { - if (_longWeakHandle != IntPtr.Zero && RuntimeImports.RhHandleGet(_longWeakHandle) != null) - { - GC.ReRegisterForFinalize(this); - return; - } - - if (_memory != IntPtr.Zero) - { - NativeMemory.Free((void*)_memory); - _memory = IntPtr.Zero; - } - if (_longWeakHandle != IntPtr.Zero) - { - RuntimeImports.RhHandleFree(_longWeakHandle); - _longWeakHandle = IntPtr.Zero; - } - } - } + private static IntPtr AllocateReferenceTrackingHandle(object obj) + => RuntimeImports.RhHandleAllocRefCounted(obj); } } diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 0c101444bd7918..7d6945c79e37d9 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -59,6 +59,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON clsload.cpp codeman.cpp codeversion.cpp + conditionalweaktable.cpp contractimpl.cpp corhost.cpp crst.cpp @@ -277,7 +278,6 @@ list(APPEND VM_HEADERS_DAC_AND_WKS_COMMON set(VM_SOURCES_DAC ${VM_SOURCES_DAC_AND_WKS_COMMON} - conditionalweaktable.cpp # The usage of conditionalweaktable is only in the DAC, but we put the headers in the VM to enable validation. ) set(VM_HEADERS_DAC diff --git a/src/coreclr/vm/binder.cpp b/src/coreclr/vm/binder.cpp index 877d0b1ec39f1b..5c992c1ad3a3f7 100644 --- a/src/coreclr/vm/binder.cpp +++ b/src/coreclr/vm/binder.cpp @@ -21,7 +21,7 @@ #include "sigbuilder.h" #include "configuration.h" #include "conditionalweaktable.h" -#include "interoplibinterface_comwrappers.h" +#include "interoplibinterface.h" #include "assemblynative.hpp" // diff --git a/src/coreclr/vm/conditionalweaktable.cpp b/src/coreclr/vm/conditionalweaktable.cpp index 6488261b6afe6a..c6bc1b09207a69 100644 --- a/src/coreclr/vm/conditionalweaktable.cpp +++ b/src/coreclr/vm/conditionalweaktable.cpp @@ -13,9 +13,10 @@ bool ConditionalWeakTableContainerObject::TryGetValue(OBJECTREF key, OBJECTREF* NOTHROW; GC_NOTRIGGER; MODE_ANY; - PRECONDITION(key != nullptr && value != nullptr); } CONTRACTL_END; + SUPPORTS_DAC; + _ASSERTE(key != nullptr && value != nullptr); INT32 hashCode = key->TryGetHashCode(); @@ -29,11 +30,19 @@ bool ConditionalWeakTableContainerObject::TryGetValue(OBJECTREF key, OBJECTREF* int bucket = hashCode & (_buckets->GetNumComponents() - 1); PTR_int32_t buckets = _buckets->GetDirectPointerToNonObjectElements(); DPTR(Entry) entries = _entries->GetDirectPointerToNonObjectElements(); + for (int entriesIndex = buckets[bucket]; entriesIndex != -1; entriesIndex = entries[entriesIndex].Next) { - if (entries[entriesIndex].HashCode == hashCode && ObjectFromHandle(entries[entriesIndex].depHnd) == key) + const Entry& entry = entries[entriesIndex]; + if (entry.HashCode == hashCode && ObjectFromHandle(entry.depHnd) == key) { - *value = HndGetHandleExtraInfo(entries[entriesIndex].depHnd); +#ifdef DACCESS_COMPILE + // In the DACCESS_COMPILE, the handle helper is directly accessible. + *value = GetDependentHandleSecondary(entry.depHnd); +#else + IGCHandleManager* mgr = GCHandleUtilities::GetGCHandleManager(); + *value = ObjectToOBJECTREF(mgr->GetDependentHandleSecondary(entry.depHnd)); +#endif // !DACCESS_COMPILE return true; } } diff --git a/src/coreclr/vm/conditionalweaktable.h b/src/coreclr/vm/conditionalweaktable.h index 8f2749d94c1f72..a04ce8b6576e94 100644 --- a/src/coreclr/vm/conditionalweaktable.h +++ b/src/coreclr/vm/conditionalweaktable.h @@ -46,7 +46,7 @@ class ConditionalWeakTableObject final : public Object { friend class CoreLibBinder; OBJECTREF _lock; - VolatilePtr _container; + VolatilePtr _container; public: // This helper can be used when the runtime is suspended (for example, DAC access // or Objective-C interop callbacks that run under GC suspension). During suspension diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 70dc6b27ebff2d..0cc320903e7560 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -220,9 +220,6 @@ CDAC_TYPE_FIELD(InteropSyncBlockInfo, T_POINTER, CCW, cdac_data::RCW) CDAC_TYPE_FIELD(InteropSyncBlockInfo, T_POINTER, CCF, cdac_data::CCF) #endif // FEATURE_COMINTEROP -#ifdef FEATURE_OBJCMARSHAL -CDAC_TYPE_FIELD(InteropSyncBlockInfo, T_POINTER, TaggedMemory, cdac_data::TaggedMemory) -#endif // FEATURE_OBJCMARSHAL CDAC_TYPE_END(InteropSyncBlockInfo) CDAC_TYPE_BEGIN(SyncBlock) diff --git a/src/coreclr/vm/interoplibinterface.h b/src/coreclr/vm/interoplibinterface.h index 3db7b8b3b7880a..9aea6e1e42e349 100644 --- a/src/coreclr/vm/interoplibinterface.h +++ b/src/coreclr/vm/interoplibinterface.h @@ -56,7 +56,8 @@ class ObjcTrackingInformationObject final : public Object { friend class CoreLibBinder; public: - INTPTR _memory; + INT_PTR _memory; + INT_PTR _longWeakHandle; }; #ifdef USE_CHECKED_OBJECTREFS @@ -77,6 +78,7 @@ extern "C" void* QCALLTYPE ObjCMarshal_AllocateReferenceTrackingHandle(_In_ QCal extern "C" BOOL QCALLTYPE ObjCMarshal_TrySetGlobalMessageSendCallback( _In_ ObjCMarshalNative::MessageSendFunction msgSendFunction, _In_ void* fptr); + #endif // FEATURE_OBJCMARSHAL #ifdef FEATURE_JAVAMARSHAL diff --git a/src/coreclr/vm/interoplibinterface_objc.cpp b/src/coreclr/vm/interoplibinterface_objc.cpp index b0c2674fd9f089..9e026858389747 100644 --- a/src/coreclr/vm/interoplibinterface_objc.cpp +++ b/src/coreclr/vm/interoplibinterface_objc.cpp @@ -22,7 +22,6 @@ namespace ObjCMarshalNative::BeginEndCallback g_BeginEndCallback; ObjCMarshalNative::IsReferencedCallback g_IsReferencedCallback; ObjCMarshalNative::EnteredFinalizationCallback g_TrackedObjectEnteredFinalizationCallback; - OBJECTHANDLE g_ObjectiveCTrackingInfoTable; } extern "C" BOOL QCALLTYPE ObjCMarshal_TryInitializeReferenceTracker( diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index f473dc738b8909..a7fdce3949bb5b 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -491,7 +491,7 @@ class ArrayBase : public Object inline DWORD GetNumComponents() const; // Get pointer to elements, handles any number of dimensions - PTR_BYTE GetDataPtr(BOOL inGC = FALSE) const { + PTR_BYTE GetGCSafeDataPtr() const { LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; #ifdef _DEBUG @@ -500,7 +500,21 @@ class ArrayBase : public Object #endif #endif return dac_cast(this) + - GetDataPtrOffset(inGC ? GetGCSafeMethodTable() : GetMethodTable()); + GetDataPtrOffset(GetGCSafeMethodTable()); + } + + PTR_BYTE GetDataPtr() const { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; +#ifdef _DEBUG +#ifndef DACCESS_COMPILE + EnableStressHeapHelper(); +#endif +#endif + PTR_BYTE result = dac_cast(this) + + GetDataPtrOffset(GetMethodTable()); + _ASSERTE(result == GetGCSafeDataPtr()); + return result; } // The component size is actually 16-bit WORD, but this method is returning SIZE_T to ensure @@ -586,24 +600,15 @@ class Array : public ArrayBase public: typedef DPTR(KIND) PTR_KIND; - typedef DPTR(const KIND) PTR_CKIND; KIND m_Array[1]; - PTR_KIND GetDirectPointerToNonObjectElements() + PTR_KIND GetDirectPointerToNonObjectElements() const { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; // return m_Array; - return PTR_KIND(GetDataPtr()); // This also handles arrays of dim 1 with lower bounds present - - } - - PTR_CKIND GetDirectConstPointerToNonObjectElements() const - { - WRAPPER_NO_CONTRACT; - // return m_Array; - return PTR_CKIND(GetDataPtr()); // This also handles arrays of dim 1 with lower bounds present + return PTR_KIND(GetGCSafeDataPtr()); // This also handles arrays of dim 1 with lower bounds present } }; @@ -1952,7 +1957,7 @@ class StackTraceArray WRAPPER_NO_CONTRACT; _ASSERTE(!!m_array); - return const_cast(m_array)->GetDirectPointerToNonObjectElements(); + return m_array->GetDirectPointerToNonObjectElements(); } PTR_INT8 GetRaw() diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index d4580003379208..85b240951b7ca1 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -440,8 +440,7 @@ static const Entry s_QCall[] = #if defined(FEATURE_OBJCMARSHAL) DllImportEntry(ObjCMarshal_TrySetGlobalMessageSendCallback) DllImportEntry(ObjCMarshal_TryInitializeReferenceTracker) - DllImportEntry(ObjCMarshal_CreateReferenceTrackingHandle) - DllImportEntry(ObjCMarshal_GetOrCreateReferenceTrackingMemory) + DllImportEntry(ObjCMarshal_AllocateReferenceTrackingHandle) #endif #if defined(FEATURE_JAVAMARSHAL) DllImportEntry(JavaMarshal_Initialize) diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 0c31692ae78323..10938071588b3a 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -169,10 +169,6 @@ class InteropSyncBlockInfo #endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION , m_pRCW{} #endif // FEATURE_COMINTEROP -#ifdef FEATURE_OBJCMARSHAL - , m_taggedMemory{} - , m_taggedAlloc{} -#endif // FEATURE_OBJCMARSHAL { LIMITED_METHOD_CONTRACT; } @@ -328,50 +324,6 @@ class InteropSyncBlockInfo #endif // FEATURE_COMINTEROP -#ifdef FEATURE_OBJCMARSHAL -public: -#ifndef DACCESS_COMPILE - PTR_VOID EnsureTaggedMemoryAllocated(_Out_ size_t* memoryInSizeT) - { - LIMITED_METHOD_CONTRACT; - _ASSERTE(memoryInSizeT != NULL); - - *memoryInSizeT = GetTaggedMemorySizeInBytes() / sizeof(SIZE_T); - - // The allocation is meant to indicate that memory - // has been made available by the system. Calling the 'get' - // without allocating memory indicates there has been - // no request for reference tracking tagged memory. - m_taggedMemory = m_taggedAlloc; - return m_taggedMemory; - } -#endif // !DACCESS_COMPILE - - PTR_VOID GetTaggedMemory() - { - LIMITED_METHOD_CONTRACT; - return m_taggedMemory; - } - - size_t GetTaggedMemorySizeInBytes() - { - LIMITED_METHOD_CONTRACT; - return ARRAY_SIZE(m_taggedAlloc); - } - -private: - PTR_VOID m_taggedMemory; - - // Two pointers worth of bytes of the requirement for - // the current consuming implementation so that is what - // is being allocated. - // If the size of this array is changed, the NativeAOT version - // should be updated as well. - // See the TAGGED_MEMORY_SIZE_IN_POINTERS constant in - // ObjectiveCMarshal.NativeAot.cs - BYTE m_taggedAlloc[2 * sizeof(void*)]; -#endif // FEATURE_OBJCMARSHAL - friend struct ::cdac_data; }; @@ -383,9 +335,6 @@ struct cdac_data static constexpr size_t RCW = offsetof(InteropSyncBlockInfo, m_pRCW); static constexpr size_t CCF = offsetof(InteropSyncBlockInfo, m_pCCF); #endif // FEATURE_COMINTEROP -#ifdef FEATURE_OBJCMARSHAL - static constexpr size_t TaggedMemory = offsetof(InteropSyncBlockInfo, m_taggedMemory); -#endif // FEATURE_OBJCMARSHAL }; typedef DPTR(InteropSyncBlockInfo) PTR_InteropSyncBlockInfo; diff --git a/src/coreclr/vm/vars.cpp b/src/coreclr/vm/vars.cpp index 8567eabc0c1d1c..57046ff71c5a8b 100644 --- a/src/coreclr/vm/vars.cpp +++ b/src/coreclr/vm/vars.cpp @@ -100,6 +100,10 @@ GPTR_IMPL(RCWCleanupList,g_pRCWCleanupList); GARY_IMPL(TADDR, g_knownQueryInterfaceImplementations, g_numKnownQueryInterfaceImplementations); #endif // FEATURE_COMWRAPPERS +#ifdef FEATURE_OBJCMARSHAL +GVAL_IMPL_INIT(OBJECTHANDLE, g_ObjectiveCTrackingInfoTable, NULL); +#endif // FEATURE_OBJCMARSHAL + #ifdef FEATURE_INTEROP_DEBUGGING GVAL_IMPL_INIT(DWORD, g_debuggerWordTLSIndex, TLS_OUT_OF_INDEXES); #endif diff --git a/src/coreclr/vm/vars.hpp b/src/coreclr/vm/vars.hpp index 73df857cee3d40..6960585b9e7482 100644 --- a/src/coreclr/vm/vars.hpp +++ b/src/coreclr/vm/vars.hpp @@ -52,6 +52,11 @@ namespace InteropLib { namespace ABI { GARY_DECL(TADDR, g_knownQueryInterfaceImplementations, g_numKnownQueryInterfaceImplementations); #endif // FEATURE_COMWRAPPERS + +#ifdef FEATURE_OBJCMARSHAL +GVAL_DECL(OBJECTHANDLE, g_ObjectiveCTrackingInfoTable); +#endif // FEATURE_OBJCMARSHAL + class DebugInterface; class DebugInfoManager; class EEDbgInterfaceImpl; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs index e9b8f606c65875..47cb2d5ca4cbe5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.Versioning; +using System.Threading; namespace System.Runtime.InteropServices.ObjectiveC { @@ -37,6 +38,8 @@ public static partial class ObjectiveCMarshal out IntPtr context); private static UnhandledExceptionPropagationHandler? s_unhandledExceptionPropagationHandler; + private static bool s_initialized; + private static ConditionalWeakTable s_objects = new(); /// /// Initialize the Objective-C marshalling API. @@ -73,11 +76,7 @@ public static unsafe void Initialize( || !TryInitializeReferenceTracker( beginEndCallback, isReferencedCallback, - trackedObjectEnteredFinalization -#if !NATIVEAOT - , ObjectHandleOnStack.Create(ref s_objects) -#endif - )) + trackedObjectEnteredFinalization)) { throw new InvalidOperationException(SR.InvalidOperation_ReinitializeObjectiveCMarshal); } @@ -117,19 +116,9 @@ public static GCHandle CreateReferenceTrackingHandle( object obj, out Span taggedMemory) { - ArgumentNullException.ThrowIfNull(obj); - - IntPtr refCountHandle = CreateReferenceTrackingHandleInternal( - obj, - out int memInSizeT, - out IntPtr mem); - - unsafe - { - taggedMemory = new Span(mem.ToPointer(), memInSizeT); - } - - return GCHandle.FromIntPtr(refCountHandle); + // Defer to GetOrCreateReferenceTrackingMemory for argument/state validation. + taggedMemory = GetOrCreateReferenceTrackingMemory(obj); + return GCHandle.FromIntPtr(AllocateReferenceTrackingHandle(obj)); } /// @@ -161,10 +150,22 @@ public static Span GetOrCreateReferenceTrackingMemory(object obj) { ArgumentNullException.ThrowIfNull(obj); - GetOrCreateReferenceTrackingMemoryInternal( - obj, - out int memInSizeT, - out IntPtr mem); + if (!s_initialized) + { + throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCMarshalNotInitialized); + } + + unsafe + { + if (!RuntimeHelpers.GetMethodTable(obj)->IsTrackedReferenceWithFinalizer) + { + throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCTypeNoFinalizer); + } + } + + ObjcTrackingInformation trackerInfo = s_objects.GetOrAdd(obj, static _ => new ObjcTrackingInformation()); + trackerInfo.EnsureInitialized(obj); + trackerInfo.GetTaggedMemory(out int memInSizeT, out IntPtr mem); unsafe { @@ -218,5 +219,76 @@ public static void SetMessageSendCallback(MessageSendFunction msgSendFunction, I if (!TrySetGlobalMessageSendCallback(msgSendFunction, func)) throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalObjectiveCMsgSend); } + + internal sealed class ObjcTrackingInformation + { + private const int TAGGED_MEMORY_SIZE_IN_POINTERS = 2; + + internal IntPtr _memory; + private IntPtr _longWeakHandle; + + public ObjcTrackingInformation() + { + _memory = (IntPtr)NativeMemory.AllocZeroed(TAGGED_MEMORY_SIZE_IN_POINTERS * (nuint)IntPtr.Size); + } + + public void GetTaggedMemory(out int memInSizeT, out IntPtr mem) + { + memInSizeT = TAGGED_MEMORY_SIZE_IN_POINTERS; + mem = _memory; + } + + public void EnsureInitialized(object o) + { + if (_longWeakHandle != IntPtr.Zero) + { + return; + } + +#if NATIVEAOT + IntPtr newHandle = RuntimeImports.RhHandleAlloc(o, GCHandleType.WeakTrackResurrection); +#else + IntPtr newHandle = GCHandle.ToIntPtr(GCHandle.Alloc(o, GCHandleType.WeakTrackResurrection)); +#endif + if (Interlocked.CompareExchange(ref _longWeakHandle, newHandle, IntPtr.Zero) != IntPtr.Zero) + { +#if NATIVEAOT + RuntimeImports.RhHandleFree(newHandle); +#else + GCHandle.FromIntPtr(newHandle).Free(); +#endif + } + } + + ~ObjcTrackingInformation() + { + IntPtr longWeakHandle = Volatile.Read(ref _longWeakHandle); +#if NATIVEAOT + if (longWeakHandle != IntPtr.Zero && RuntimeImports.RhHandleGet(longWeakHandle) != null) +#else + if (longWeakHandle != IntPtr.Zero && GCHandle.FromIntPtr(longWeakHandle).Target != null) +#endif + { + GC.ReRegisterForFinalize(this); + return; + } + + IntPtr memory = Interlocked.Exchange(ref _memory, IntPtr.Zero); + if (memory != IntPtr.Zero) + { + NativeMemory.Free((void*)memory); + } + + longWeakHandle = Interlocked.Exchange(ref _longWeakHandle, IntPtr.Zero); + if (longWeakHandle != IntPtr.Zero) + { +#if NATIVEAOT + RuntimeImports.RhHandleFree(longWeakHandle); +#else + GCHandle.FromIntPtr(longWeakHandle).Free(); +#endif + } + } + } } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs index 87979da1f6c859..2f54bdb960a0b8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ObjectiveCMarshal_1.cs @@ -19,29 +19,21 @@ public TargetPointer GetTaggedMemory(TargetPointer address, out TargetNUInt size size = default; TargetPointer? objectsTable = Data.ObjectiveCMarshal.ObjectTrackingInfoTable(_target); - if (objectsTable is not null && objectsTable.Value != TargetPointer.Null) + if (objectsTable is null || objectsTable.Value == TargetPointer.Null) + return TargetPointer.Null; + + IConditionalWeakTable cwt = _target.Contracts.ConditionalWeakTable; + if (cwt.TryGetValue(objectsTable.Value, address, out TargetPointer trackingInfoAddress)) { - IConditionalWeakTable cwt = _target.Contracts.ConditionalWeakTable; - if (cwt.TryGetValue(objectsTable.Value, address, out TargetPointer trackingInfoAddress)) + ObjcTrackingInformation trackingInfo = _target.ProcessedData.GetOrAdd(trackingInfoAddress); + if (trackingInfo.Memory != TargetPointer.Null) { - ObjcTrackingInformation trackingInfo = _target.ProcessedData.GetOrAdd(trackingInfoAddress); - if (trackingInfo.Memory != TargetPointer.Null) - { - size = new TargetNUInt(2 * (ulong)_target.PointerSize); - return trackingInfo.Memory; - } + const int TAGGED_MEMORY_SIZE_IN_POINTERS = 2; + size = new TargetNUInt(TAGGED_MEMORY_SIZE_IN_POINTERS * (ulong)_target.PointerSize); + return trackingInfo.Memory; } } - TargetPointer syncBlock = _target.Contracts.Object.GetSyncBlockAddress(address); - if (syncBlock == TargetPointer.Null) - return TargetPointer.Null; - - Data.SyncBlock sb = _target.ProcessedData.GetOrAdd(syncBlock); - TargetPointer taggedMemory = sb.InteropInfo?.TaggedMemory ?? TargetPointer.Null; - if (taggedMemory != TargetPointer.Null) - size = new TargetNUInt(2 * (ulong)_target.PointerSize); - - return taggedMemory; + return TargetPointer.Null; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs index 273fb2a294a2d9..ac5e2ed4e8f4a4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InteropSyncBlockInfo.cs @@ -9,5 +9,4 @@ internal sealed partial class InteropSyncBlockInfo : IData [Field] public TargetPointer? RCW { get; } [Field] public TargetPointer? CCW { get; } [Field] public TargetPointer? CCF { get; } - [Field] public TargetPointer? TaggedMemory { get; } } diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs index 4fef96a60ee26e..cec103cb971efd 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Object.cs @@ -259,7 +259,7 @@ internal ulong AddObject(ulong methodTable, uint prefixSize = 0) return mockObject.Address; } - internal ulong AddObjectWithSyncBlock(ulong methodTable, uint syncBlockIndex, ulong rcw, ulong ccw, ulong ccf, ulong taggedMemory = 0) + internal ulong AddObjectWithSyncBlock(ulong methodTable, uint syncBlockIndex, ulong rcw, ulong ccw, ulong ccf) { const uint IsSyncBlockIndexBits = 0x08000000; const uint SyncBlockIndexMask = (1 << 26) - 1; @@ -274,7 +274,7 @@ internal ulong AddObjectWithSyncBlock(ulong methodTable, uint syncBlockIndex, ul ulong syncTableValueAddress = address - TestSyncBlockValueToObjectOffset; Builder.TargetTestHelpers.Write(Builder.BorrowAddressRange(syncTableValueAddress, sizeof(uint)), syncTableValue); - AddSyncBlock(syncBlockIndex, rcw, ccw, ccf, taggedMemory); + AddSyncBlock(syncBlockIndex, rcw, ccw, ccf); return address; } @@ -371,9 +371,9 @@ private void AddSyncTableEntriesPointer() Builder.AddHeapFragment(fragment); } - private void AddSyncBlock(uint index, ulong rcw, ulong ccw, ulong ccf, ulong taggedMemory = 0) + private void AddSyncBlock(uint index, ulong rcw, ulong ccw, ulong ccf) { - MockSyncBlock syncBlock = SyncBlockBuilder.AddSyncBlock(rcw, ccw, ccf, taggedMemory: taggedMemory, name: $"Sync Block {index}"); + MockSyncBlock syncBlock = SyncBlockBuilder.AddSyncBlock(rcw, ccw, ccf, name: $"Sync Block {index}"); ulong syncTableEntryAddress = TestSyncTableEntriesAddress + ((ulong)index * (ulong)SyncTableEntryLayout.Size); MockMemorySpace.HeapFragment syncTableEntryFragment = new() diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.SyncBlock.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.SyncBlock.cs index 2fd2580b46cfd8..26bc4cf1503e83 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.SyncBlock.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.SyncBlock.cs @@ -34,14 +34,12 @@ internal sealed class MockInteropSyncBlockInfo : TypedView private const string RCWFieldName = "RCW"; private const string CCWFieldName = "CCW"; private const string CCFFieldName = "CCF"; - private const string TaggedMemoryFieldName = "TaggedMemory"; public static Layout CreateLayout(MockTarget.Architecture architecture) => new SequentialLayoutBuilder("InteropSyncBlockInfo", architecture) .AddPointerField(RCWFieldName) .AddPointerField(CCWFieldName) .AddPointerField(CCFFieldName) - .AddPointerField(TaggedMemoryFieldName) .Build(); public ulong RCW @@ -61,12 +59,6 @@ public ulong CCF get => ReadPointerField(CCFFieldName); set => WritePointerField(CCFFieldName, value); } - - public ulong TaggedMemory - { - get => ReadPointerField(TaggedMemoryFieldName); - set => WritePointerField(TaggedMemoryFieldName, value); - } } internal sealed class MockSyncBlock : TypedView @@ -160,8 +152,7 @@ internal MockSyncBlock AddSyncBlock( ulong ccw, ulong ccf, bool hasInteropInfo = true, - string name = "SyncBlock", - ulong taggedMemory = 0) + string name = "SyncBlock") { int totalSize = SyncBlockLayout.Size + (hasInteropInfo ? InteropSyncBlockInfoLayout.Size : 0); MockMemorySpace.HeapFragment fragment = _allocator.Allocate((ulong)totalSize, name); @@ -178,7 +169,6 @@ internal MockSyncBlock AddSyncBlock( interopInfo.RCW = rcw; interopInfo.CCW = ccw; interopInfo.CCF = ccf; - interopInfo.TaggedMemory = taggedMemory; syncBlock.InteropInfo = interopAddress; } @@ -192,16 +182,15 @@ internal MockSyncBlock AddSyncBlock( /// CCW pointer to store (pass 0 for none). /// CCF pointer to store (pass 0 for none). /// When false, the InteropInfo pointer in the SyncBlock is left null. - /// Tagged memory pointer to store (pass 0 for none). internal MockSyncBlock AddSyncBlockToCleanupList( - ulong rcw, ulong ccw, ulong ccf, bool hasInteropInfo = true, ulong taggedMemory = 0) + ulong rcw, ulong ccw, ulong ccf, bool hasInteropInfo = true) { if (_syncBlockCache is null) { throw new InvalidOperationException("Cleanup-list support requires the cache/global initialization path."); } - MockSyncBlock syncBlock = AddSyncBlock(rcw, ccw, ccf, hasInteropInfo, "SyncBlock (cleanup)", taggedMemory); + MockSyncBlock syncBlock = AddSyncBlock(rcw, ccw, ccf, hasInteropInfo, "SyncBlock (cleanup)"); syncBlock.LinkNext = _cleanupListHeadAddress; _cleanupListHeadAddress = syncBlock.Address; _syncBlockCache.CleanupBlockList = _cleanupListHeadAddress; diff --git a/src/native/managed/cdac/tests/UnitTests/ObjectiveCMarshalTests.cs b/src/native/managed/cdac/tests/UnitTests/ObjectiveCMarshalTests.cs index d3f242b5410b83..7fa59a9fd3dcdf 100644 --- a/src/native/managed/cdac/tests/UnitTests/ObjectiveCMarshalTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ObjectiveCMarshalTests.cs @@ -60,13 +60,12 @@ private static (string Name, ulong Value)[] CreateContractGlobals(MockDescriptor [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetTaggedMemory_NoSyncBlockIndex_ReturnsNull(MockTarget.Architecture arch) + public void GetTaggedMemory_NoTrackingTable_ReturnsNull(MockTarget.Architecture arch) { ulong testObjectAddress = 0; TestPlaceholderTarget target = CreateObjectiveCMarshalTarget(arch, objectBuilder => { - // AddObject with only the ObjectHeader prefix but no sync block index testObjectAddress = objectBuilder.AddObject(0, prefixSize: (uint)objectBuilder.ObjectHeaderLayout.Size); }); @@ -76,53 +75,4 @@ public void GetTaggedMemory_NoSyncBlockIndex_ReturnsNull(MockTarget.Architecture Assert.Equal(TargetPointer.Null, result); Assert.Equal(default, size); } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void GetTaggedMemory_NullTaggedMemory_ReturnsNull(MockTarget.Architecture arch) - { - ulong testObjectAddress = 0; - TestPlaceholderTarget target = CreateObjectiveCMarshalTarget(arch, - objectBuilder => - { - testObjectAddress = objectBuilder.AddObjectWithSyncBlock( - methodTable: 0, - syncBlockIndex: 1, - rcw: 0, - ccw: 0, - ccf: 0, - taggedMemory: 0); - }); - - IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; - TargetPointer result = contract.GetTaggedMemory(testObjectAddress, out TargetNUInt size); - - Assert.Equal(TargetPointer.Null, result); - Assert.Equal(default, size); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void GetTaggedMemory_HasTaggedMemory_ReturnsPointerAndSize(MockTarget.Architecture arch) - { - ulong testObjectAddress = 0; - const ulong expectedTaggedMemory = 0x5000; - TestPlaceholderTarget target = CreateObjectiveCMarshalTarget(arch, - objectBuilder => - { - testObjectAddress = objectBuilder.AddObjectWithSyncBlock( - methodTable: 0, - syncBlockIndex: 1, - rcw: 0, - ccw: 0, - ccf: 0, - taggedMemory: expectedTaggedMemory); - }); - - IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; - TargetPointer result = contract.GetTaggedMemory(testObjectAddress, out TargetNUInt size); - - Assert.Equal(expectedTaggedMemory, result.Value); - Assert.Equal(2ul * (ulong)target.PointerSize, size.Value); - } } From 29929c7241e3dbfa7ba2e5ddbe8559aa8145f053 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 8 Jun 2026 11:51:49 -0700 Subject: [PATCH 6/9] Fix DAC pointer size, readonly field, and conditional include - Use sizeof(TADDR) instead of sizeof(void*) in DAC for target size - Mark s_objects as readonly; use local copy for ObjectHandleOnStack - Guard gcinterface.dac.h include with DACCESS_COMPILE Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../InteropServices/ObjectiveCMarshal.CoreCLR.cs | 13 +++++++++++-- src/coreclr/debug/daccess/request.cpp | 2 +- src/coreclr/vm/conditionalweaktable.cpp | 3 +++ .../InteropServices/ObjectiveC/ObjectiveCMarshal.cs | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs index 2064253e5b2e1a..5eed38bdabad5d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.Versioning; @@ -38,11 +39,19 @@ private static bool TryInitializeReferenceTracker( delegate* unmanaged beginEndCallback, delegate* unmanaged isReferencedCallback, delegate* unmanaged trackedObjectEnteredFinalization) - => TryInitializeReferenceTracker( + { + // Make a local of the readonly field because it needs to be passed by ref to a QCall + // and it is marked as readonly to prevent accidental reassignment. + ConditionalWeakTable objects = s_objects; + bool result = TryInitializeReferenceTracker( beginEndCallback, isReferencedCallback, trackedObjectEnteredFinalization, - ObjectHandleOnStack.Create(ref s_objects)); + ObjectHandleOnStack.Create(ref objects)); + Debug.Assert(object.ReferenceEquals(objects, s_objects)); + + return result; + } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ObjCMarshal_AllocateReferenceTrackingHandle")] private static partial IntPtr AllocateReferenceTrackingHandle(ObjectHandleOnStack obj); diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 289f3c23c30932..1ebbdf40d9acf2 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -5415,7 +5415,7 @@ namespace constexpr int TAGGED_MEMORY_SIZE_IN_POINTERS = 2; if (taggedMemorySizeInBytes) - *taggedMemorySizeInBytes = TAGGED_MEMORY_SIZE_IN_POINTERS * sizeof(void*); + *taggedMemorySizeInBytes = TAGGED_MEMORY_SIZE_IN_POINTERS * sizeof(TADDR); } } } diff --git a/src/coreclr/vm/conditionalweaktable.cpp b/src/coreclr/vm/conditionalweaktable.cpp index c6bc1b09207a69..91b10b98175506 100644 --- a/src/coreclr/vm/conditionalweaktable.cpp +++ b/src/coreclr/vm/conditionalweaktable.cpp @@ -4,7 +4,10 @@ #include "conditionalweaktable.h" #include "gchandleutilities.h" + +#ifdef DACCESS_COMPILE #include "../debug/daccess/gcinterface.dac.h" +#endif // DACCESS_COMPILE bool ConditionalWeakTableContainerObject::TryGetValue(OBJECTREF key, OBJECTREF* value) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs index 47cb2d5ca4cbe5..6ac79be702f0e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs @@ -39,7 +39,7 @@ public static partial class ObjectiveCMarshal private static UnhandledExceptionPropagationHandler? s_unhandledExceptionPropagationHandler; private static bool s_initialized; - private static ConditionalWeakTable s_objects = new(); + private static readonly ConditionalWeakTable s_objects = new(); /// /// Initialize the Objective-C marshalling API. From a09b04534bb869f52a25b008130caa0192977de7 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 8 Jun 2026 18:43:44 -0700 Subject: [PATCH 7/9] Add cDAC ObjectiveCMarshal CWT-based unit tests Add tests exercising the ConditionalWeakTable-based GetTaggedMemory path: - ObjectNotInTable: CWT exists but object not tracked - NullMemory: object tracked but memory pointer is null - HasMemory: object tracked with valid tagged memory Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/UnitTests/ObjectiveCMarshalTests.cs | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/src/native/managed/cdac/tests/UnitTests/ObjectiveCMarshalTests.cs b/src/native/managed/cdac/tests/UnitTests/ObjectiveCMarshalTests.cs index 7fa59a9fd3dcdf..47c8a826a3eecd 100644 --- a/src/native/managed/cdac/tests/UnitTests/ObjectiveCMarshalTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ObjectiveCMarshalTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.TestInfrastructure; +using Moq; using Xunit; namespace Microsoft.Diagnostics.DataContractReader.Tests; @@ -15,6 +16,9 @@ public class ObjectiveCMarshalTests private const uint SyncBlockIsHashCode = 0x04000000; private const uint SyncBlockIndexMask = (1u << 26) - 1; + private const string ObjectiveCMarshalTypeName = "System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal"; + private const string ObjcTrackingInformationTypeName = "System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal+ObjcTrackingInformation"; + private static TestPlaceholderTarget CreateObjectiveCMarshalTarget( MockTarget.Architecture arch, Action configure) @@ -35,6 +39,65 @@ private static TestPlaceholderTarget CreateObjectiveCMarshalTarget( return targetBuilder.Build(); } + private static TestPlaceholderTarget CreateObjectiveCMarshalTargetWithCWT( + MockTarget.Architecture arch, + Mock mockCwt, + Action configure) + { + const ulong globalSlotAddress = 0x7000; + const ulong cwtAddress = 0x8000; + + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(targetBuilder.MemoryBuilder); + MockDescriptors.MockObjectBuilder objectBuilder = new(rtsBuilder); + + configure(objectBuilder, targetBuilder.MemoryBuilder); + + var helpers = new TargetTestHelpers(arch); + + // Allocate a heap fragment for the s_objects global slot and write the CWT address into it. + byte[] slotData = new byte[helpers.PointerSize]; + helpers.WritePointer(slotData, cwtAddress); + targetBuilder.MemoryBuilder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Address = globalSlotAddress, + Data = slotData, + Name = "ObjectiveCMarshal.s_objects slot" + }); + + var globals = new List<(string Name, ulong Value)>(CreateContractGlobals(objectBuilder)) + { + (ObjectiveCMarshalTypeName + ".s_objects", globalSlotAddress) + }; + + // Register ObjcTrackingInformation type with a single pointer-sized _memory field at offset 0. + var trackingInfoType = new Target.TypeInfo + { + Size = (uint)helpers.PointerSize, + Fields = new Dictionary + { + ["_memory"] = new Target.FieldInfo { Offset = 0 } + } + }; + + var types = CreateContractTypes(objectBuilder); + var stringTypes = new Dictionary + { + [ObjcTrackingInformationTypeName] = trackingInfoType + }; + + targetBuilder + .AddTypes(types) + .AddTypes(stringTypes) + .AddGlobals(globals.ToArray()) + .AddContract(version: "c1") + .AddContract(version: "c1") + .AddContract(version: "c1") + .AddMockContract(mockCwt); + + return targetBuilder.Build(); + } + private static Dictionary CreateContractTypes(MockDescriptors.MockObjectBuilder objectBuilder) => new Dictionary { @@ -75,4 +138,108 @@ public void GetTaggedMemory_NoTrackingTable_ReturnsNull(MockTarget.Architecture Assert.Equal(TargetPointer.Null, result); Assert.Equal(default, size); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTaggedMemory_ObjectNotInTable_ReturnsNull(MockTarget.Architecture arch) + { + ulong testObjectAddress = 0; + var mockCwt = new Mock(); + mockCwt + .Setup(c => c.TryGetValue(It.IsAny(), It.IsAny(), out It.Ref.IsAny)) + .Returns(false); + + TestPlaceholderTarget target = CreateObjectiveCMarshalTargetWithCWT(arch, mockCwt, + (objectBuilder, _) => + { + testObjectAddress = objectBuilder.AddObject(0, prefixSize: (uint)objectBuilder.ObjectHeaderLayout.Size); + }); + + IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; + TargetPointer result = contract.GetTaggedMemory(testObjectAddress, out TargetNUInt size); + + Assert.Equal(TargetPointer.Null, result); + Assert.Equal(default, size); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTaggedMemory_NullMemory_ReturnsNull(MockTarget.Architecture arch) + { + ulong testObjectAddress = 0; + var helpers = new TargetTestHelpers(arch); + + const ulong trackingInfoAddress = 0xA000; + + var mockCwt = new Mock(); + + TestPlaceholderTarget target = CreateObjectiveCMarshalTargetWithCWT(arch, mockCwt, + (objectBuilder, memBuilder) => + { + testObjectAddress = objectBuilder.AddObject(0, prefixSize: (uint)objectBuilder.ObjectHeaderLayout.Size); + + // Write a zero pointer at the tracking info address (_memory field = 0) + byte[] trackingData = new byte[helpers.PointerSize]; + helpers.WritePointer(trackingData, 0); + memBuilder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Address = trackingInfoAddress, + Data = trackingData, + Name = "ObjcTrackingInformation (null memory)" + }); + }); + + // Setup CWT mock to return the tracking info address for our object + TargetPointer trackingInfoOut = new TargetPointer(trackingInfoAddress); + mockCwt + .Setup(c => c.TryGetValue(It.IsAny(), new TargetPointer(testObjectAddress), out trackingInfoOut)) + .Returns(true); + + IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; + TargetPointer result = contract.GetTaggedMemory(testObjectAddress, out TargetNUInt size); + + Assert.Equal(TargetPointer.Null, result); + Assert.Equal(default, size); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetTaggedMemory_HasMemory_ReturnsPointerAndSize(MockTarget.Architecture arch) + { + ulong testObjectAddress = 0; + var helpers = new TargetTestHelpers(arch); + + const ulong trackingInfoAddress = 0xA000; + const ulong expectedTaggedMemory = 0x5000; + + var mockCwt = new Mock(); + + TestPlaceholderTarget target = CreateObjectiveCMarshalTargetWithCWT(arch, mockCwt, + (objectBuilder, memBuilder) => + { + testObjectAddress = objectBuilder.AddObject(0, prefixSize: (uint)objectBuilder.ObjectHeaderLayout.Size); + + // Write the tagged memory pointer at the tracking info address (_memory field) + byte[] trackingData = new byte[helpers.PointerSize]; + helpers.WritePointer(trackingData, expectedTaggedMemory); + memBuilder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Address = trackingInfoAddress, + Data = trackingData, + Name = "ObjcTrackingInformation (with memory)" + }); + }); + + // Setup CWT mock to return the tracking info address for our object + TargetPointer trackingInfoOut = new TargetPointer(trackingInfoAddress); + mockCwt + .Setup(c => c.TryGetValue(It.IsAny(), new TargetPointer(testObjectAddress), out trackingInfoOut)) + .Returns(true); + + IObjectiveCMarshal contract = target.Contracts.ObjectiveCMarshal; + TargetPointer result = contract.GetTaggedMemory(testObjectAddress, out TargetNUInt size); + + Assert.Equal(expectedTaggedMemory, result.Value); + Assert.Equal(2ul * (ulong)helpers.PointerSize, size.Value); + } } From 185f9c5f6ba25a52c0a2c06369e7e48ad082870d Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 8 Jun 2026 18:52:01 -0700 Subject: [PATCH 8/9] Simplify Array::GetDirectPointerToNonObjectElements and update Mono ObjC stubs - Use m_Array directly instead of GetGCSafeDataPtr() per review feedback - Update Mono ObjectiveCMarshal stubs to match shared API changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/object.h | 3 +-- .../Runtime/InteropServices/ObjectiveCMarshal.Mono.cs | 11 ++--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index a7fdce3949bb5b..b12fc5dc0d988d 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -607,8 +607,7 @@ class Array : public ArrayBase { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; - // return m_Array; - return PTR_KIND(GetGCSafeDataPtr()); // This also handles arrays of dim 1 with lower bounds present + return PTR_KIND(m_Array); } }; diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs index b6b3baa888d128..3d2f33fb2e852f 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs @@ -29,14 +29,7 @@ private static unsafe bool TryInitializeReferenceTracker( delegate* unmanaged trackedObjectEnteredFinalization) => throw new NotImplementedException(); - private static IntPtr CreateReferenceTrackingHandleInternal( - ObjectHandleOnStack obj, - out int memInSizeT, - out IntPtr mem) => throw new NotImplementedException(); - - private static void GetOrCreateReferenceTrackingMemoryInternal( - ObjectHandleOnStack obj, - out int memInSizeT, - out IntPtr mem) => throw new NotImplementedException(); + private static IntPtr AllocateReferenceTrackingHandle(object obj) + => throw new NotImplementedException(); } } From 5ed7f09c772e85be1e9d61fbccec3707ce113689 Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Mon, 8 Jun 2026 19:50:06 -0700 Subject: [PATCH 9/9] Move IsTrackedReferenceWithFinalizer check to platform-specific partial methods Extract the unsafe MethodTable check into a partial method on each runtime (CoreCLR, NativeAOT, Mono stub) so the shared ObjectiveCMarshal.cs no longer requires an unsafe context for the validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs | 3 +++ .../Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs | 3 +++ .../InteropServices/ObjectiveC/ObjectiveCMarshal.cs | 7 ++----- .../Runtime/InteropServices/ObjectiveCMarshal.Mono.cs | 3 +++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs index 5eed38bdabad5d..957d5a31cfe8dd 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs @@ -59,6 +59,9 @@ private static bool TryInitializeReferenceTracker( private static IntPtr AllocateReferenceTrackingHandle(object obj) => AllocateReferenceTrackingHandle(ObjectHandleOnStack.Create(ref obj)); + private static unsafe bool IsTrackedReferenceWithFinalizer(object obj) + => RuntimeHelpers.GetMethodTable(obj)->IsTrackedReferenceWithFinalizer; + [UnmanagedCallersOnly] internal static unsafe void* InvokeUnhandledExceptionPropagation(Exception* pExceptionArg, IntPtr methodDesc, IntPtr* pContext, Exception* pException) { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs index 622da74f5d132f..76f039c9f28edb 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.NativeAot.cs @@ -119,5 +119,8 @@ private static bool TryInitializeReferenceTracker( private static IntPtr AllocateReferenceTrackingHandle(object obj) => RuntimeImports.RhHandleAllocRefCounted(obj); + + private static bool IsTrackedReferenceWithFinalizer(object obj) + => RuntimeHelpers.GetMethodTable(obj)->IsTrackedReferenceWithFinalizer; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs index 6ac79be702f0e9..036a3ff67f83ab 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs @@ -155,12 +155,9 @@ public static Span GetOrCreateReferenceTrackingMemory(object obj) throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCMarshalNotInitialized); } - unsafe + if (!IsTrackedReferenceWithFinalizer(obj)) { - if (!RuntimeHelpers.GetMethodTable(obj)->IsTrackedReferenceWithFinalizer) - { - throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCTypeNoFinalizer); - } + throw new InvalidOperationException(SR.InvalidOperation_ObjectiveCTypeNoFinalizer); } ObjcTrackingInformation trackerInfo = s_objects.GetOrAdd(obj, static _ => new ObjcTrackingInformation()); diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs index 3d2f33fb2e852f..7db1f00f622167 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.Mono.cs @@ -31,5 +31,8 @@ private static unsafe bool TryInitializeReferenceTracker( private static IntPtr AllocateReferenceTrackingHandle(object obj) => throw new NotImplementedException(); + + private static bool IsTrackedReferenceWithFinalizer(object obj) + => throw new NotImplementedException(); } }