diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index 15ec570949bb70..babcf894736ddf 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -673,7 +673,7 @@ private unsafe struct NoGCRegionCallbackFinalizerWorkItem public bool scheduled; public bool abandoned; - public GCHandle action; + public GCHandle action; } internal enum EnableNoGCRegionCallbackStatus @@ -709,7 +709,7 @@ public static unsafe void RegisterNoGCRegionCallback(long totalSize, Action call try { pWorkItem = (NoGCRegionCallbackFinalizerWorkItem*)NativeMemory.AllocZeroed((nuint)sizeof(NoGCRegionCallbackFinalizerWorkItem)); - pWorkItem->action = GCHandle.Alloc(callback); + pWorkItem->action = new GCHandle(callback); pWorkItem->callback = &Callback; EnableNoGCRegionCallbackStatus status = (EnableNoGCRegionCallbackStatus)_EnableNoGCRegionCallback(pWorkItem, totalSize); @@ -739,14 +739,13 @@ static void Callback(NoGCRegionCallbackFinalizerWorkItem* pWorkItem) { Debug.Assert(pWorkItem->scheduled); if (!pWorkItem->abandoned) - ((Action)(pWorkItem->action.Target!))(); + pWorkItem->action.Target(); Free(pWorkItem); } static void Free(NoGCRegionCallbackFinalizerWorkItem* pWorkItem) { - if (pWorkItem->action.IsAllocated) - pWorkItem->action.Free(); + pWorkItem->action.Dispose(); NativeMemory.Free(pWorkItem); } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs index 8fa7c789fd0afa..8c9f78af3d79a9 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs @@ -452,7 +452,7 @@ public static IntPtr GetCurrentCalleeOpenStaticDelegateFunctionPointer() /// /// Retrieves the current delegate that is being called /// - public static T GetCurrentCalleeDelegate() where T : class // constraint can't be System.Delegate + public static T GetCurrentCalleeDelegate() where T : Delegate { return PInvokeMarshal.GetCurrentCalleeDelegate(); } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 3e679f9f0b8f8f..7bb3b0f58e70e5 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -213,7 +213,6 @@ - diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs index 079bd82cf0bf5b..67d09f8b246a6d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs @@ -305,7 +305,7 @@ private unsafe struct NoGCRegionCallbackFinalizerWorkItem public bool scheduled; public bool abandoned; - public GCHandle action; + public GCHandle action; } public static unsafe void RegisterNoGCRegionCallback(long totalSize, Action callback) @@ -317,7 +317,7 @@ public static unsafe void RegisterNoGCRegionCallback(long totalSize, Action call try { pWorkItem = (NoGCRegionCallbackFinalizerWorkItem*)NativeMemory.AllocZeroed((nuint)sizeof(NoGCRegionCallbackFinalizerWorkItem)); - pWorkItem->action = GCHandle.Alloc(callback); + pWorkItem->action = new GCHandle(callback); pWorkItem->callback = &Callback; EnableNoGCRegionCallbackStatus status = (EnableNoGCRegionCallbackStatus)RuntimeImports.RhEnableNoGCRegionCallback(pWorkItem, totalSize); @@ -347,14 +347,13 @@ static void Callback(NoGCRegionCallbackFinalizerWorkItem* pWorkItem) { Debug.Assert(pWorkItem->scheduled); if (!pWorkItem->abandoned) - ((Action)(pWorkItem->action.Target!))(); + pWorkItem->action.Target(); Free(pWorkItem); } static void Free(NoGCRegionCallbackFinalizerWorkItem* pWorkItem) { - if (pWorkItem->action.IsAllocated) - pWorkItem->action.Free(); + pWorkItem->action.Dispose(); NativeMemory.Free(pWorkItem); } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs index 5ae29387480839..323544388d81e3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs @@ -100,7 +100,7 @@ private static ConditionalWeakTable GetPInvokeDe [StructLayout(LayoutKind.Sequential, Pack = 1)] internal unsafe struct ThunkContextData { - public GCHandle Handle; // A weak GCHandle to the delegate + public WeakGCHandle Handle; // A weak GCHandle to the delegate public IntPtr FunctionPtr; // Function pointer for open static delegates } @@ -133,7 +133,7 @@ public PInvokeDelegateThunk(Delegate del) ThunkContextData* thunkData = (ThunkContextData*)ContextData; // allocate a weak GChandle for the delegate - thunkData->Handle = GCHandle.Alloc(del, GCHandleType.WeakTrackResurrection); + thunkData->Handle = new WeakGCHandle(del, trackResurrection: true); thunkData->FunctionPtr = openStaticFunctionPointer; } @@ -148,17 +148,17 @@ public PInvokeDelegateThunk(Delegate del) if (ContextData != IntPtr.Zero) { // free the GCHandle - GCHandle handle = ((ThunkContextData*)ContextData)->Handle; + WeakGCHandle handle = ((ThunkContextData*)ContextData)->Handle; if (handle.IsAllocated) { // If the delegate is still alive, defer finalization. - if (handle.Target != null) + if (handle.TryGetTarget(out _)) { GC.ReRegisterForFinalize(this); return; } - handle.Free(); + handle.Dispose(); } // Free the allocated context data memory @@ -205,21 +205,20 @@ private static unsafe PInvokeDelegateThunk AllocateThunk(Delegate del) IntPtr pTarget; if (s_thunkPoolHeap != null && RuntimeAugments.TryGetThunkData(s_thunkPoolHeap, ptr, out pContext, out pTarget)) { - GCHandle handle; + WeakGCHandle handle; unsafe { // Pull out Handle from context handle = ((ThunkContextData*)pContext)->Handle; } - Delegate target = Unsafe.As(handle.Target); - // - // The delegate might already been garbage collected - // User should use GC.KeepAlive or whatever ways necessary to keep the delegate alive - // until they are done with the native function pointer - // - if (target == null) + if (!handle.TryGetTarget(out Delegate? target)) { + // + // The delegate might already been garbage collected + // User should use GC.KeepAlive or whatever ways necessary to keep the delegate alive + // until they are done with the native function pointer + // Environment.FailFast(SR.Delegate_GarbageCollected); } @@ -269,7 +268,7 @@ public static IntPtr GetCurrentCalleeOpenStaticDelegateFunctionPointer() /// /// Retrieves the current delegate that is being called /// - public static T GetCurrentCalleeDelegate() where T : class // constraint can't be System.Delegate + public static T GetCurrentCalleeDelegate() where T : Delegate { // // RH keeps track of the current thunk that is being called through a secret argument / thread @@ -280,7 +279,7 @@ public static T GetCurrentCalleeDelegate() where T : class // constraint can' Debug.Assert(pContext != IntPtr.Zero); - GCHandle handle; + WeakGCHandle handle; unsafe { // Pull out Handle from context @@ -288,18 +287,17 @@ public static T GetCurrentCalleeDelegate() where T : class // constraint can' } - T target = Unsafe.As(handle.Target); - - // - // The delegate might already been garbage collected - // User should use GC.KeepAlive or whatever ways necessary to keep the delegate alive - // until they are done with the native function pointer - // - if (target == null) + if (!handle.TryGetTarget(out Delegate? target)) { + // + // The delegate might already been garbage collected + // User should use GC.KeepAlive or whatever ways necessary to keep the delegate alive + // until they are done with the native function pointer + // Environment.FailFast(SR.Delegate_GarbageCollected); } - return target; + + return Unsafe.As(target); } #endregion diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/UnsafeGCHandle.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/UnsafeGCHandle.cs deleted file mode 100644 index 7e40696a5dbbda..00000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/UnsafeGCHandle.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Threading; - -using Internal.Runtime.CompilerServices; - -namespace System.Runtime.InteropServices -{ - /// - /// The unsafe version of the GCHandle structure. - /// - /// - /// Differences from the GCHandle structure: - /// - /// The constructor assumes the handle type is valid; no range check is performed. - /// The pinned flag is not stored in the _handle field. - /// The Target getter and setter assume the UnsafeGCHandle has been allocated. - /// No blittable check is performed when allocating a pinned UnsafeGCHandle or setting its target. - /// The GetRawTargetAddress method returns the raw address of the target (the pointer to - /// its m_pEEType field). - /// The Free method is not thread-safe and does not throw if the UnsafeGCHandle - /// has not been allocated or has been already freed. - /// - /// - [StructLayout(LayoutKind.Sequential)] - internal struct UnsafeGCHandle - { - // IMPORTANT: This must be kept in sync with the GCHandleType enum. - private const GCHandleType MaxHandleType = GCHandleType.Pinned; - - // The actual integer handle value that the EE uses internally. - private IntPtr _handle; - - // Allocate a handle storing the object and the type. - private UnsafeGCHandle(object value, GCHandleType type) - { - Debug.Assert((uint)type <= (uint)MaxHandleType, "Unexpected handle type"); - _handle = RuntimeImports.RhHandleAlloc(value, type); - } - - public static UnsafeGCHandle Alloc(object value, GCHandleType type = GCHandleType.Normal) - { - return new UnsafeGCHandle(value, type); - } - - // Target property - allows getting / updating of the handle's referent. - public object Target - { - get - { - Debug.Assert(IsAllocated, "Handle is not initialized"); - return RuntimeImports.RhHandleGet(_handle); - } - - set - { - Debug.Assert(IsAllocated, "Handle is not initialized"); - RuntimeImports.RhHandleSet(_handle, value); - } - } - - // Frees a GC handle. This method is not thread-safe! - public void Free() - { - if (_handle != default(IntPtr)) - { - RuntimeImports.RhHandleFree(_handle); - } - } - - // Returns the raw address of the target assuming it is pinned. - public unsafe IntPtr GetRawTargetAddress() - { - return *(IntPtr*)_handle; - } - - // Determine whether this handle has been allocated or not. - public bool IsAllocated - { - get - { - return _handle != default(IntPtr); - } - } - } -} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs index 06fb9c4ffc9e96..3cf12153d1f6d6 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs @@ -87,14 +87,14 @@ private bool JoinInternal(int millisecondsTimeout) } } - private unsafe bool CreateThread(GCHandle thisThreadHandle) + private unsafe bool CreateThread(GCHandle thisThreadHandle) { // Create the Stop event before starting the thread to make sure // it is ready to be signaled at thread shutdown time. // This also avoids OOM after creating the thread. _stopped = new ManualResetEvent(false); - if (!Interop.Sys.CreateThread((IntPtr)_startHelper!._maxStackSize, &ThreadEntryPoint, (IntPtr)thisThreadHandle)) + if (!Interop.Sys.CreateThread((IntPtr)_startHelper!._maxStackSize, &ThreadEntryPoint, GCHandle.ToIntPtr(thisThreadHandle))) { return false; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs index d708a313b077ce..15f8c25ae42eee 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Windows.cs @@ -175,7 +175,7 @@ private bool JoinInternal(int millisecondsTimeout) } } - private unsafe bool CreateThread(GCHandle thisThreadHandle) + private unsafe bool CreateThread(GCHandle thisThreadHandle) { const int AllocationGranularity = 0x10000; // 64 KiB @@ -197,7 +197,7 @@ private unsafe bool CreateThread(GCHandle thisThreadHandle) } _osHandle = Interop.Kernel32.CreateThread(IntPtr.Zero, (IntPtr)stackSize, - &ThreadEntryPoint, (IntPtr)thisThreadHandle, + &ThreadEntryPoint, GCHandle.ToIntPtr(thisThreadHandle), Interop.Kernel32.CREATE_SUSPENDED | Interop.Kernel32.STACK_SIZE_PARAM_IS_A_RESERVATION, out _); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs index dbe64d4381a3e4..3758fca9e8a0a2 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs @@ -379,7 +379,7 @@ private void StartCore() } bool waitingForThreadStart = false; - GCHandle threadHandle = GCHandle.Alloc(this); + GCHandle threadHandle = new GCHandle(this); try { @@ -404,7 +404,7 @@ private void StartCore() Debug.Assert(!waitingForThreadStart, "Leaked threadHandle"); if (!waitingForThreadStart) { - threadHandle.Free(); + threadHandle.Dispose(); } } @@ -422,8 +422,7 @@ private void StartCore() private static void StartThread(IntPtr parameter) { - GCHandle threadHandle = (GCHandle)parameter; - Thread thread = (Thread)threadHandle.Target!; + Thread thread = GCHandle.FromIntPtr(parameter).Target; try { diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs index ab3583c38d1456..16eda316798367 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeSystemContextFactory.cs @@ -16,7 +16,7 @@ public static class TypeSystemContextFactory { // Cache the most recent instance of TypeSystemContext in a weak handle, and reuse it if possible // This allows us to avoid recreating the type resolution context again and again, but still allows it to go away once the types are no longer being built - private static GCHandle s_cachedContext = GCHandle.Alloc(null, GCHandleType.Weak); + private static WeakGCHandle s_cachedContext = new WeakGCHandle(null); private static readonly Lock s_lock = new Lock(useTrivialWaits: true); @@ -24,10 +24,9 @@ public static TypeSystemContext Create() { using (s_lock.EnterScope()) { - TypeSystemContext context = (TypeSystemContext)s_cachedContext.Target; - if (context != null) + if (s_cachedContext.TryGetTarget(out TypeSystemContext? context)) { - s_cachedContext.Target = null; + s_cachedContext.SetTarget(null); return context; } } @@ -63,7 +62,7 @@ public static void Recycle(TypeSystemContext context) context.FlushTypeBuilderStates(); // No lock needed here - the reference assignment is atomic - s_cachedContext.Target = context; + s_cachedContext.SetTarget(context); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 1fc285e1c3af6a..ffe8e7daaba2be 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -974,6 +974,8 @@ + + @@ -1014,6 +1016,7 @@ + @@ -1038,6 +1041,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventProvider.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventProvider.cs index 398f94001a7e36..573581ff9fe8ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventProvider.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventProvider.cs @@ -10,7 +10,7 @@ internal sealed class EventPipeEventProvider : EventProviderImpl { private readonly WeakReference _eventProvider; private IntPtr _provHandle; - private GCHandle _gcHandle; + private GCHandle _gcHandle; internal EventPipeEventProvider(EventProvider eventProvider) { @@ -62,7 +62,7 @@ protected override unsafe void HandleEnableNotification( private static unsafe void Callback(byte* sourceId, int isEnabled, byte level, long matchAnyKeywords, long matchAllKeywords, Interop.Advapi32.EVENT_FILTER_DESCRIPTOR* filterData, void* callbackContext) { - EventPipeEventProvider _this = (EventPipeEventProvider)GCHandle.FromIntPtr((IntPtr)callbackContext).Target!; + EventPipeEventProvider _this = GCHandle.FromIntPtr((IntPtr)callbackContext).Target; if (_this._eventProvider.TryGetTarget(out EventProvider? target)) { _this.ProviderCallback(target, sourceId, isEnabled, level, matchAnyKeywords, matchAllKeywords, filterData); @@ -73,13 +73,13 @@ private static unsafe void Callback(byte* sourceId, int isEnabled, byte level, internal override unsafe void Register(Guid id, string name) { Debug.Assert(!_gcHandle.IsAllocated); - _gcHandle = GCHandle.Alloc(this); + _gcHandle = new GCHandle(this); - _provHandle = EventPipeInternal.CreateProvider(name, &Callback, (void*)GCHandle.ToIntPtr(_gcHandle)); + _provHandle = EventPipeInternal.CreateProvider(name, &Callback, (void*)GCHandle.ToIntPtr(_gcHandle)); if (_provHandle == 0) { // Unable to create the provider. - _gcHandle.Free(); + _gcHandle.Dispose(); throw new OutOfMemoryException(); } } @@ -95,10 +95,7 @@ internal override void Unregister() EventPipeInternal.DeleteProvider(_provHandle); _provHandle = 0; } - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } + _gcHandle.Dispose(); } // Write an event. diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventProvider.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventProvider.cs index 36fb1ca5497b44..c74d845a345def 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventProvider.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventProvider.cs @@ -748,7 +748,7 @@ internal SessionInfo(int sessionIdBit_, int etwSessionId_) private readonly WeakReference _eventProvider; private long _registrationHandle; - private GCHandle _gcHandle; + private GCHandle _gcHandle; private List? _liveSessions; // current live sessions (KeyValuePair) private Guid _providerId; @@ -817,7 +817,7 @@ protected override unsafe void HandleEnableNotification( private static unsafe void Callback(Guid* sourceId, int isEnabled, byte level, long matchAnyKeywords, long matchAllKeywords, Interop.Advapi32.EVENT_FILTER_DESCRIPTOR* filterData, void* callbackContext) { - EtwEventProvider _this = (EtwEventProvider)GCHandle.FromIntPtr((IntPtr)callbackContext).Target!; + EtwEventProvider _this = GCHandle.FromIntPtr((IntPtr)callbackContext).Target; if (_this._eventProvider.TryGetTarget(out EventProvider? target)) { @@ -830,7 +830,7 @@ private static unsafe void Callback(Guid* sourceId, int isEnabled, byte level, internal override unsafe void Register(Guid id, string name) { Debug.Assert(!_gcHandle.IsAllocated); - _gcHandle = GCHandle.Alloc(this); + _gcHandle = new GCHandle(this); long registrationHandle = 0; _providerId = id; @@ -838,11 +838,11 @@ internal override unsafe void Register(Guid id, string name) uint status = Interop.Advapi32.EventRegister( &providerId, &Callback, - (void*)GCHandle.ToIntPtr(_gcHandle), + (void*)GCHandle.ToIntPtr(_gcHandle), ®istrationHandle); if (status != 0) { - _gcHandle.Free(); + _gcHandle.Dispose(); throw new ArgumentException(Interop.Kernel32.GetMessage((int)status)); } @@ -859,10 +859,7 @@ internal override void Unregister() _registrationHandle = 0; } - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } + _gcHandle.Dispose(); } // Write an event. diff --git a/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs b/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs index c6f048f05d5b40..fbcbcc27aece52 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs @@ -15,7 +15,7 @@ internal sealed class Gen2GcCallback : CriticalFinalizerObject { private readonly Func? _callback0; private readonly Func? _callback1; - private GCHandle _weakTargetObj; + private WeakGCHandle _weakTargetObj; private Gen2GcCallback(Func callback) { @@ -25,7 +25,7 @@ private Gen2GcCallback(Func callback) private Gen2GcCallback(Func callback, object targetObj) { _callback1 = callback; - _weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak); + _weakTargetObj = new WeakGCHandle(targetObj); } /// @@ -56,11 +56,10 @@ public static void Register(Func callback, object targetObj) if (_weakTargetObj.IsAllocated) { // Check to see if the target object is still alive. - object? targetObj = _weakTargetObj.Target; - if (targetObj == null) + if (!_weakTargetObj.TryGetTarget(out object? targetObj)) { // The target object is dead, so this callback object is no longer needed. - _weakTargetObj.Free(); + _weakTargetObj.Dispose(); return; } @@ -71,7 +70,7 @@ public static void Register(Func callback, object targetObj) if (!_callback1(targetObj)) { // If the callback returns false, this callback object is no longer needed. - _weakTargetObj.Free(); + _weakTargetObj.Dispose(); return; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs new file mode 100644 index 00000000000000..4ede5e2fd86ce1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.T.cs @@ -0,0 +1,113 @@ +// 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.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Represents a strongly-typed GC handle to a managed object. + /// A GC handle is used to work with object references in unmanaged code. + /// + /// + /// This type corresponds to . + /// + /// This type is unsafe if used incorrectly. Incorrect usage like mismanagement + /// of lifetime, providing invalid handle value or concurrent disposal can result + /// in hard to diagnose crashes or data corruptions. + /// + /// + /// + /// The type of the object this tracks to. + public struct GCHandle : IEquatable>, IDisposable + where T : class? + { + // The actual integer handle value that the EE uses internally. + private IntPtr _handle; + + /// + /// Allocates a handle for the specified object. + /// + /// The object that uses the . + public GCHandle(T target) + { + _handle = GCHandle.InternalAlloc(target, GCHandleType.Normal); + } + + private GCHandle(IntPtr handle) => _handle = handle; + + /// Determine whether this handle has been allocated or not. + public readonly bool IsAllocated => _handle != IntPtr.Zero; + + /// Gets or sets the object this handle represents. + /// If the handle is not initialized or already disposed. + public readonly T Target + { + get + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + // Skip the type check to provide lowest overhead. + return Unsafe.As(GCHandle.InternalGet(handle)); + } + set + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + GCHandle.InternalSet(handle, value); + } + } + + /// + /// Returns a new object created from a handle to a managed object. + /// + /// An handle to a managed object to create a object from. + /// A new object that corresponds to the value parameter. + /// + /// This method doesn't validate the provided handle value. The caller must ensure the validity of the handle. + /// + /// The representation of is not + /// interchangable with . + /// + /// + public static GCHandle FromIntPtr(IntPtr value) => new GCHandle(value); + + /// + /// Returns the internal integer representation of a object. + /// + /// A object to retrieve an internal integer representation from. + /// An object that represents a object. + /// + /// The representation of is not + /// interchangable with . + /// + public static IntPtr ToIntPtr(GCHandle value) => value._handle; + + /// Releases this . + /// This method is not thread safe. + public void Dispose() + { + // Free the handle if it hasn't already been freed. + // Unlike GCHandle.Free, no thread safety is provided. + IntPtr handle = _handle; + if (handle != IntPtr.Zero) + { + _handle = IntPtr.Zero; + GCHandle.InternalFree(handle); + } + } + + /// + public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is GCHandle handle && Equals(handle); + + /// + public readonly bool Equals(GCHandle other) => _handle == other._handle; + + /// + /// Returns the hash code for the current instance. + /// + /// A hash code for the current instance. + public override readonly int GetHashCode() => _handle.GetHashCode(); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.cs index 14151c1c02705a..060143b509f517 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.cs @@ -21,6 +21,9 @@ namespace System.Runtime.InteropServices /// WeakTrackResurrection: Same as Weak, but stays until after object is really gone. /// Pinned - same as Normal, but allows the address of the actual object to be taken. /// + /// + /// + /// [StructLayout(LayoutKind.Sequential)] public partial struct GCHandle : IEquatable { @@ -191,5 +194,24 @@ private static void ThrowIfInvalid(IntPtr handle) ThrowHelper.ThrowInvalidOperationException_HandleIsNotInitialized(); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void CheckUninitialized(IntPtr handle) + { + // Check if the handle was never initialized or was freed. + // Throws NRE with minimal overhead, to avoid access violation from unmanaged code. + // Invalid handle is unsupported and will cause AV as expected. +#if MONO + // Mono doesn't handle reading null pointer as NRE. + // Throw a NRE manually. + if (handle == 0) + { + throw new NullReferenceException(); + } +#else + // The read will be combined with the read in InternalGet under Release. + _ = *(object*)handle; +#endif + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandleExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandleExtensions.cs new file mode 100644 index 00000000000000..121c6490b9e0dd --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandleExtensions.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Provides extension methods to operate with GC handles. + /// + public static class GCHandleExtensions + { + // The following methods are strongly typed generic specifications only on + // PinnedGCHandles with correct type. + + /// + /// Retrieves the address of array data in . + /// + /// The handle to retrieve pointer from. + /// + /// The address of 0th array element the pinned array, + /// or if the handle doesn't point to any object. + /// + /// If the handle is not initialized or already disposed. + [CLSCompliant(false)] + public static unsafe T* GetAddressOfArrayData( +#nullable disable // Nullable oblivious because no covariance between PinnedGCHandle and PinnedGCHandle + this PinnedGCHandle handle) +#nullable restore + { + T[]? array = handle.Target; + if (array is null) + return null; + + // Unsafe.AsPointer call is safe since object is pinned. + return (T*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(array)); + } + + /// + /// Retrieves the address string data in . + /// + /// The handle to retrieve pointer from. + /// + /// The address of 0th character of the pinned , + /// or if the handle doesn't point to any object. + /// + /// If the handle is not initialized or already disposed. + [CLSCompliant(false)] + public static unsafe char* GetAddressOfStringData( +#nullable disable // Nullable oblivious because no covariance between PinnedGCHandle and PinnedGCHandle + this PinnedGCHandle handle) +#nullable restore + { + string? str = handle.Target; + if (str is null) + return null; + + // Unsafe.AsPointer call is safe since object is pinned. + return (char*)Unsafe.AsPointer(ref str.GetRawStringData()); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs new file mode 100644 index 00000000000000..097849fdeb9a58 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PinnedGCHandle.T.cs @@ -0,0 +1,147 @@ +// 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.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Represents a strongly-typed GC handle to a managed object. + /// The object is pinned at fixed location in GC heap and allows its + /// address to be taken. + /// + /// + /// This type corresponds to . + /// + /// This type is unsafe if used incorrectly. Incorrect usage like mismanagement + /// of lifetime, providing invalid handle value or concurrent disposal can result + /// in hard to diagnose crashes or data corruptions. + /// + /// + /// + /// The type of the object this tracks to. + public struct PinnedGCHandle : IEquatable>, IDisposable + where T : class? + { + // The actual integer handle value that the EE uses internally. + private IntPtr _handle; + + /// + /// Allocates a handle for the specified object. + /// + /// The object that uses the . + public PinnedGCHandle(T target) + { + // Unlike GCHandle, pinning any object is allowed + _handle = GCHandle.InternalAlloc(target, GCHandleType.Pinned); + } + + private PinnedGCHandle(IntPtr handle) => _handle = handle; + + /// Determine whether this handle has been allocated or not. + public readonly bool IsAllocated => _handle != IntPtr.Zero; + + /// Gets or sets the object this handle represents. + /// If the handle is not initialized or already disposed. + public readonly T Target + { + get + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + // Skip the type check to provide lowest overhead. + return Unsafe.As(GCHandle.InternalGet(handle)); + } + set + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + // Unlike GCHandle, pinning any object is allowed + GCHandle.InternalSet(handle, value); + } + } + + /// + /// Retrieves the address of object data in a . + /// + /// + /// The address of first instance field of the pinned object, + /// or if the handle doesn't point to any object. + /// + /// + /// + /// This method is intended to be used with types other than array or . + /// For array or , use + /// or instead. + /// + /// + /// This method should only be used for blittable types. + /// + /// + /// If the handle is not initialized or already disposed. + [CLSCompliant(false)] + public readonly unsafe void* GetAddressOfObjectData() + { + object? target = Target; + if (target is null) + { + return null; + } + + // Unsafe.AsPointer is safe since object is pinned. + return Unsafe.AsPointer(ref target.GetRawData()); + } + + /// + /// Returns a new object created from a handle to a managed object. + /// + /// An handle to a managed object to create a object from. + /// A new object that corresponds to the value parameter. + /// + /// This method doesn't validate the provided handle value. The caller must ensure the validity of the handle. + /// + /// The representation of is not + /// interchangable with . + /// + /// + public static PinnedGCHandle FromIntPtr(IntPtr value) => new PinnedGCHandle(value); + + /// + /// Returns the internal integer representation of a object. + /// + /// A object to retrieve an internal integer representation from. + /// An object that represents a object. + /// + /// The representation of is not + /// interchangable with . + /// + public static IntPtr ToIntPtr(PinnedGCHandle value) => value._handle; + + /// Releases this . + /// This method is not thread safe. + public void Dispose() + { + // Free the handle if it hasn't already been freed. + // Unlike GCHandle.Free, no thread safety is provided. + IntPtr handle = _handle; + if (handle != IntPtr.Zero) + { + _handle = IntPtr.Zero; + GCHandle.InternalFree(handle); + } + } + + /// + public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is PinnedGCHandle handle && Equals(handle); + + /// + public readonly bool Equals(PinnedGCHandle other) => _handle == other._handle; + + /// + /// Returns the hash code for the current instance. + /// + /// A hash code for the current instance. + public override readonly int GetHashCode() => _handle.GetHashCode(); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs new file mode 100644 index 00000000000000..aedd61b177bff1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/WeakGCHandle.T.cs @@ -0,0 +1,121 @@ +// 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.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Represents a strongly-typed GC handle to a managed object. + /// The object is allowed to be collected. When the object is collected, handle target is set to null. + /// + /// + /// This type corresponds to or . + /// + /// This type is unsafe if used incorrectly. Incorrect usage like mismanagement + /// of lifetime, providing invalid handle value or concurrent disposal can result + /// in hard to diagnose crashes or data corruptions. + /// + /// + /// + /// The type of the object this tracks to. + public struct WeakGCHandle : IEquatable>, IDisposable + where T : class? + { + // The actual integer handle value that the EE uses internally. + private IntPtr _handle; + + /// + /// Allocates a handle for the specified object. + /// + /// The object that uses the . + /// Whether to track the object when it's resurrected in the finalizer. + public WeakGCHandle(T target, bool trackResurrection = false) + { + _handle = GCHandle.InternalAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak); + } + + private WeakGCHandle(IntPtr handle) => _handle = handle; + + /// Determine whether this handle has been allocated or not. + public readonly bool IsAllocated => _handle != IntPtr.Zero; + + /// + /// Tries to retrieve the target object that is referenced by the current object. + /// + /// When this method returns, contains the target object, if it is available. + /// if the target was retrieved; otherwise, . + /// If the handle is not initialized or already disposed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool TryGetTarget([NotNullWhen(true)] out T? target) + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + // Skip the type check to provide lowest overhead. + T? obj = Unsafe.As(GCHandle.InternalGet(handle)); + target = obj; + return obj != null; + } + + /// Sets the object this handle represents. + /// If the handle is not initialized or already disposed. + public readonly void SetTarget(T target) + { + IntPtr handle = _handle; + GCHandle.CheckUninitialized(handle); + GCHandle.InternalSet(handle, target); + } + + /// + /// Returns a new object created from a handle to a managed object. + /// + /// An handle to a managed object to create a object from. + /// A new object that corresponds to the value parameter. + /// + /// This method doesn't validate the provided handle value. The caller must ensure the validity of the handle. + /// + /// The representation of is not + /// interchangable with . + /// + /// + public static WeakGCHandle FromIntPtr(IntPtr value) => new WeakGCHandle(value); + + /// + /// Returns the internal integer representation of a object. + /// + /// A object to retrieve an internal integer representation from. + /// An object that represents a object. + /// + /// The representation of is not + /// interchangable with . + /// + public static IntPtr ToIntPtr(WeakGCHandle value) => value._handle; + + /// Releases this . + /// This method is not thread safe. + public void Dispose() + { + // Free the handle if it hasn't already been freed. + // Unlike GCHandle.Free, no thread safety is provided. + IntPtr handle = _handle; + if (handle != IntPtr.Zero) + { + _handle = IntPtr.Zero; + GCHandle.InternalFree(handle); + } + } + + /// + public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is WeakGCHandle handle && Equals(handle); + + /// + public readonly bool Equals(WeakGCHandle other) => _handle == other._handle; + + /// + /// Returns the hash code for the current instance. + /// + /// A hash code for the current instance. + public override readonly int GetHashCode() => _handle.GetHashCode(); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs index f993a8a9b62c51..f220fb4dbdaf7f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/RegisteredWaitHandle.WindowsThreadPool.cs @@ -20,7 +20,7 @@ public sealed partial class RegisteredWaitHandle : MarshalByRefObject private bool _unregistering; // Handle to this object to keep it alive - private GCHandle _gcHandle; + private GCHandle _gcHandle; // Pointer to the TP_WAIT structure private IntPtr _tpWait; @@ -40,13 +40,13 @@ internal unsafe RegisteredWaitHandle(SafeWaitHandle waitHandle, _ThreadPoolWaitO _repeating = repeating; // Allocate _gcHandle and _tpWait as the last step and make sure they are never leaked - _gcHandle = GCHandle.Alloc(this); + _gcHandle = new GCHandle(this); - _tpWait = Interop.Kernel32.CreateThreadpoolWait(&RegisteredWaitCallback, (IntPtr)_gcHandle, IntPtr.Zero); + _tpWait = Interop.Kernel32.CreateThreadpoolWait(&RegisteredWaitCallback, GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero); if (_tpWait == IntPtr.Zero) { - _gcHandle.Free(); + _gcHandle.Dispose(); throw new OutOfMemoryException(); } @@ -60,9 +60,9 @@ internal static void RegisteredWaitCallback(IntPtr instance, IntPtr context, Int { var wrapper = ThreadPoolCallbackWrapper.Enter(); - GCHandle handle = (GCHandle)context; - RegisteredWaitHandle registeredWaitHandle = (RegisteredWaitHandle)handle.Target!; - Debug.Assert((handle == registeredWaitHandle._gcHandle) && (wait == registeredWaitHandle._tpWait)); + GCHandle handle = GCHandle.FromIntPtr(context); + RegisteredWaitHandle registeredWaitHandle = handle.Target; + Debug.Assert((handle.Equals(registeredWaitHandle._gcHandle)) && (wait == registeredWaitHandle._tpWait)); bool timedOut = (waitResult == (uint)Interop.Kernel32.WAIT_TIMEOUT); registeredWaitHandle.PerformCallbackWindowsThreadPool(timedOut); @@ -90,7 +90,7 @@ private void PerformCallbackWindowsThreadPool(bool timedOut) { // This wait will not be fired again. Free the GC handle to allow the GC to collect this object. Debug.Assert(_gcHandle.IsAllocated); - _gcHandle.Free(); + _gcHandle.Dispose(); } } } @@ -160,10 +160,7 @@ private void FinishUnregistering() Interop.Kernel32.CloseThreadpoolWait(_tpWait); _tpWait = IntPtr.Zero; - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } + _gcHandle.Dispose(); Debug.Assert(_waitHandle != null); _waitHandle.DangerousRelease(); diff --git a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/GCHandleTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/GCHandleTests.cs index ace27b84d44e33..f46af70a0f1bfd 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/GCHandleTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/GCHandleTests.cs @@ -19,6 +19,36 @@ public void Ctor_Default() Assert.Equal(IntPtr.Zero, (IntPtr)handle); } + [Fact] + public void Ctor_Default_Generic() + { + var handle = new GCHandle(); + Assert.Throws(() => handle.Target); + Assert.False(handle.IsAllocated); + + Assert.Equal(IntPtr.Zero, GCHandle.ToIntPtr(handle)); + } + + [Fact] + public void Ctor_Default_Weak() + { + var handle = new WeakGCHandle(); + Assert.Throws(() => handle.TryGetTarget(out _)); + Assert.False(handle.IsAllocated); + + Assert.Equal(IntPtr.Zero, WeakGCHandle.ToIntPtr(handle)); + } + + [Fact] + public void Ctor_Default_Pinned() + { + var handle = new PinnedGCHandle(); + Assert.Throws(() => handle.Target); + Assert.False(handle.IsAllocated); + + Assert.Equal(IntPtr.Zero, PinnedGCHandle.ToIntPtr(handle)); + } + public static IEnumerable Alloc_TestData() { yield return new object[] { null }; @@ -38,6 +68,29 @@ public void Alloc_Value_ReturnsExpected(object value) ValidateGCHandle(handle, GCHandleType.Normal, value); } + [Fact] + public void Alloc_Value_ReturnsExpected_Genenic() + { + RunTest(null); + RunTest("String"); + RunTest(new int[1]); + RunTest(new object[1]); + RunTest(new NonBlittable[1]); + RunTest(new Blittable()); + RunTest(new NonBlittable()); + RunTest(new UnmanagedNonBlittable()); + RunTest(new ClassWithReferences()); + RunTest(new ClassWithoutReferences()); + + void RunTest(T value) where T : class + { + ValidateGCHandle(new GCHandle(value), value); + ValidateWeakGCHandle(new WeakGCHandle(value), value); + ValidateWeakGCHandle(new WeakGCHandle(value, trackResurrection: true), value); + ValidatePinnedGCHandle(new PinnedGCHandle(value), value); + } + } + public static IEnumerable Alloc_Type_TestData() { foreach (object[] data in Alloc_TestData()) @@ -98,10 +151,69 @@ public void FromIntPtr_Zero_ThrowsInvalidOperationException() } [Fact] - public void AddrOfPinnedObject_NotInitialized_ThrowsInvalidOperationException() + public void FromIntPtr_Generic_Zero_NoCheck() + { + var handle = GCHandle.FromIntPtr(IntPtr.Zero); + Assert.False(handle.IsAllocated); + var weakHandle = WeakGCHandle.FromIntPtr(IntPtr.Zero); + Assert.False(weakHandle.IsAllocated); + var pinnedHandle = PinnedGCHandle.FromIntPtr(IntPtr.Zero); + Assert.False(pinnedHandle.IsAllocated); + } + + [Fact] + public unsafe void AddrOfPinnedObject_NotInitialized_ThrowsException() { var handle = new GCHandle(); Assert.Throws(() => handle.AddrOfPinnedObject()); + var handleOfObject = new PinnedGCHandle(); + Assert.Throws(() => handleOfObject.GetAddressOfObjectData()); + var handleOfString = new PinnedGCHandle(); + Assert.Throws(() => handleOfString.GetAddressOfStringData()); + var handleOfArray = new PinnedGCHandle(); + Assert.Throws(() => handleOfArray.GetAddressOfArrayData()); + } + + [Fact] + public unsafe void AddrOfPinnedObject_ReturnsStringData() + { + string str = "String"; + fixed (char* ptr = str) + { + var handle = GCHandle.Alloc(str, GCHandleType.Pinned); + try + { + Assert.Equal((IntPtr)ptr, handle.AddrOfPinnedObject()); + using var handleOfString = new PinnedGCHandle(str); + Assert.NotEqual((IntPtr)ptr, (IntPtr)handleOfString.GetAddressOfObjectData()); + Assert.Equal((IntPtr)ptr, (IntPtr)handleOfString.GetAddressOfStringData()); + } + finally + { + handle.Free(); + } + } + } + + [Fact] + public unsafe void AddrOfPinnedObject_ReturnsArrayData() + { + int[] array = new int[1]; + fixed (int* ptr = array) + { + var handle = GCHandle.Alloc(array, GCHandleType.Pinned); + try + { + Assert.Equal((IntPtr)ptr, handle.AddrOfPinnedObject()); + using var handleOfArray = new PinnedGCHandle(array); + Assert.NotEqual((IntPtr)ptr, (IntPtr)handleOfArray.GetAddressOfObjectData()); + Assert.Equal((IntPtr)ptr, (IntPtr)handleOfArray.GetAddressOfArrayData()); + } + finally + { + handle.Free(); + } + } } [Fact] @@ -125,6 +237,17 @@ public void Free_NotInitialized_ThrowsInvalidOperationException() Assert.Throws(() => handle.Free()); } + [Fact] + public void Dispose_NotInitialized_NoThrow() + { + var handleOfObject = new GCHandle(); + handleOfObject.Dispose(); + var weakHandle = new WeakGCHandle(); + weakHandle.Dispose(); + var pinnedHandle = new PinnedGCHandle(); + pinnedHandle.Dispose(); + } + public static IEnumerable Equals_TestData() { GCHandle handle = GCHandle.Alloc(new object()); @@ -190,6 +313,73 @@ private static void ValidateGCHandle(GCHandle handle, GCHandleType type, object } } + private static void ValidateGCHandle(GCHandle handle, T target) + where T : class + { + try + { + Assert.Equal(target, handle.Target); + Assert.True(handle.IsAllocated); + + Assert.NotEqual(IntPtr.Zero, GCHandle.ToIntPtr(handle)); + } + finally + { + handle.Dispose(); + Assert.False(handle.IsAllocated); + } + } + + private static void ValidateWeakGCHandle(WeakGCHandle handle, T target) where T : class + { + try + { + if (target != null) + { + Assert.True(handle.TryGetTarget(out T outTarget)); + Assert.Equal(target, outTarget); + } + else + { + Assert.False(handle.TryGetTarget(out T outTarget)); + Assert.Null(outTarget); + } + Assert.True(handle.IsAllocated); + + Assert.NotEqual(IntPtr.Zero, WeakGCHandle.ToIntPtr(handle)); + } + finally + { + handle.Dispose(); + Assert.False(handle.IsAllocated); + } + } + + private static unsafe void ValidatePinnedGCHandle(PinnedGCHandle handle, T target) where T : class + { + try + { + Assert.Equal(target, handle.Target); + Assert.True(handle.IsAllocated); + + Assert.NotEqual(IntPtr.Zero, PinnedGCHandle.ToIntPtr(handle)); + + if (target == null) + { + Assert.Equal(IntPtr.Zero, (IntPtr)handle.GetAddressOfObjectData()); + } + else + { + Assert.NotEqual(IntPtr.Zero, (IntPtr)handle.GetAddressOfObjectData()); + } + } + finally + { + handle.Dispose(); + Assert.False(handle.IsAllocated); + } + } + public struct Blittable { public int _field; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index a2105c7843e8ea..b3ead528d620cb 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -14109,6 +14109,35 @@ public void Free() { } public static bool operator !=(System.Runtime.InteropServices.GCHandle a, System.Runtime.InteropServices.GCHandle b) { throw null; } public static System.IntPtr ToIntPtr(System.Runtime.InteropServices.GCHandle value) { throw null; } } + public partial struct GCHandle : System.IDisposable, System.IEquatable> where T : class? + { + private int _dummyPrimitive; + public void Dispose() { } + public override readonly bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public readonly bool Equals(System.Runtime.InteropServices.GCHandle other) { throw null; } + public static System.Runtime.InteropServices.GCHandle FromIntPtr(System.IntPtr value) { throw null; } + public override readonly int GetHashCode() { throw null; } + public GCHandle(T target) { } + public readonly bool IsAllocated { get { throw null; } } + public readonly T Target { get { throw null; } set { } } + public static System.IntPtr ToIntPtr(System.Runtime.InteropServices.GCHandle value) { throw null; } + } + public static class GCHandleExtensions + { + [System.CLSCompliantAttribute(false)] + public static unsafe T* GetAddressOfArrayData( +#nullable disable + this System.Runtime.InteropServices.PinnedGCHandle handle) +#nullable restore + { throw null; } + [System.CLSCompliantAttribute(false)] +#nullable disable + public static unsafe char* GetAddressOfStringData( +#nullable disable + this System.Runtime.InteropServices.PinnedGCHandle handle) +#nullable restore + { throw null; } + } public enum GCHandleType { Weak = 0, @@ -14178,6 +14207,21 @@ public sealed partial class OutAttribute : System.Attribute { public OutAttribute() { } } + public partial struct PinnedGCHandle : System.IDisposable, System.IEquatable> where T : class? + { + private int _dummyPrimitive; + public void Dispose() { } + public override readonly bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public readonly bool Equals(System.Runtime.InteropServices.PinnedGCHandle other) { throw null; } + public static System.Runtime.InteropServices.PinnedGCHandle FromIntPtr(System.IntPtr value) { throw null; } + [System.CLSCompliantAttribute(false)] + public readonly unsafe void* GetAddressOfObjectData() { throw null; } + public override readonly int GetHashCode() { throw null; } + public readonly bool IsAllocated { get { throw null; } } + public PinnedGCHandle(T target) { } + public T Target { readonly get { throw null; } set { } } + public static System.IntPtr ToIntPtr(System.Runtime.InteropServices.PinnedGCHandle value) { throw null; } + } public static partial class RuntimeInformation { public static string FrameworkDescription { get { throw null; } } @@ -14301,6 +14345,20 @@ public enum UnmanagedType HString = 47, LPUTF8Str = 48, } + public partial struct WeakGCHandle : System.IDisposable, System.IEquatable> where T : class? + { + private int _dummyPrimitive; + public void Dispose() { } + public override readonly bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public readonly bool Equals(System.Runtime.InteropServices.WeakGCHandle other) { throw null; } + public static System.Runtime.InteropServices.WeakGCHandle FromIntPtr(System.IntPtr value) { throw null; } + public override readonly int GetHashCode() { throw null; } + public readonly bool IsAllocated { get { throw null; } } + public readonly void SetTarget(T target) { } + public static System.IntPtr ToIntPtr(System.Runtime.InteropServices.WeakGCHandle value) { throw null; } + public readonly bool TryGetTarget([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out T? target) { throw null; } + public WeakGCHandle(T target, bool trackResurrection = false) { } + } } namespace System.Runtime.InteropServices.Marshalling { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PinAndClear.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PinAndClear.cs index 43435cda416193..97e4d47b36938b 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PinAndClear.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PinAndClear.cs @@ -8,15 +8,13 @@ namespace System.Security.Cryptography internal struct PinAndClear : IDisposable { private byte[] _data; - private GCHandle _gcHandle; + private PinnedGCHandle _gcHandle; internal static PinAndClear Track(byte[] data) { return new PinAndClear { - _gcHandle = GCHandle.Alloc( - data, - GCHandleType.Pinned), + _gcHandle = new PinnedGCHandle(data), _data = data, }; } @@ -24,7 +22,7 @@ internal static PinAndClear Track(byte[] data) public void Dispose() { Array.Clear(_data); - _gcHandle.Free(); + _gcHandle.Dispose(); } } }