未验证 提交 e445329e 编写于 作者: V Vladimir Sadov 提交者: GitHub

[NativeAOT] Remove WeakReference.m_trackResurrection field (#76115)

* remove m_trackResurrection field

* switch to GCHandle.Internal  for portability

* remove ENABLE_WINRT dead code

* move IsTrackResurrection

* TracksResurrectionBit

* native part

* move some parts of implementation to common

* tweak a comment

* tweak a comment

* use libraries naming conventions for _handleAndKind

* Apply suggestions from code review
Co-authored-by: NJan Kotas <jkotas@microsoft.com>

* Remove Mono and NativeAOT implementations. Move the handle field to the top of the class per coding conventions
Co-authored-by: NJan Kotas <jkotas@microsoft.com>
上级 b0920912
......@@ -136,6 +136,5 @@ static uintptr_t const MAX_STRING_LENGTH = 0x3FFFFFDF;
class WeakReference : public Object
{
public:
uintptr_t m_Handle;
uint8_t m_trackResurrection;
uintptr_t m_HandleAndKind;
};
......@@ -1182,9 +1182,9 @@ bool GCToEEInterface::EagerFinalized(Object* obj)
ASSERT(GCHeapUtilities::GetGCHeap()->IsGCInProgressHelper());
WeakReference* weakRefObj = (WeakReference*)obj;
OBJECTHANDLE handle = (OBJECTHANDLE)weakRefObj->m_Handle;
weakRefObj->m_Handle = 0;
HandleType handleType = weakRefObj->m_trackResurrection ? HandleType::HNDTYPE_WEAK_LONG : HandleType::HNDTYPE_WEAK_SHORT;
OBJECTHANDLE handle = (OBJECTHANDLE)(weakRefObj->m_HandleAndKind & ~(uintptr_t)1);
HandleType handleType = (weakRefObj->m_HandleAndKind & 1) ? HandleType::HNDTYPE_WEAK_LONG : HandleType::HNDTYPE_WEAK_SHORT;
weakRefObj->m_HandleAndKind &= (uintptr_t)1;
GCHandleUtilities::GetGCHandleManager()->DestroyHandleOfType(handle, handleType);
return true;
}
......
......@@ -247,8 +247,6 @@
<Compile Include="System\Runtime\RuntimeImportAttribute.cs" />
<Compile Include="System\Runtime\CompilerServices\ClassConstructorRunner.cs" />
<Compile Include="System\Runtime\CompilerServices\RuntimeHelpers.NativeAot.cs" />
<Compile Include="System\WeakReference.NativeAot.cs" />
<Compile Include="System\WeakReference.T.NativeAot.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)'=='true'">
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.ExitProcess.cs">
......
......@@ -86,7 +86,7 @@ public static int GetGeneration(WeakReference wo)
{
// note - this throws an NRE if given a null weak reference. This isn't
// documented, but it's the behavior of Desktop and CoreCLR.
object? obj = RuntimeImports.RhHandleGet(wo.m_handle);
object? obj = RuntimeImports.RhHandleGet(wo.Handle);
if (obj == null)
{
throw new ArgumentNullException(nameof(wo));
......
......@@ -5,12 +5,12 @@ namespace System.Runtime.InteropServices
{
public partial struct GCHandle
{
private static IntPtr InternalAlloc(object value, GCHandleType type) => RuntimeImports.RhHandleAlloc(value, type);
internal static IntPtr InternalAlloc(object value, GCHandleType type) => RuntimeImports.RhHandleAlloc(value, type);
private static void InternalFree(IntPtr handle) => RuntimeImports.RhHandleFree(handle);
internal static void InternalFree(IntPtr handle) => RuntimeImports.RhHandleFree(handle);
private static object? InternalGet(IntPtr handle) => RuntimeImports.RhHandleGet(handle);
internal static object? InternalGet(IntPtr handle) => RuntimeImports.RhHandleGet(handle);
private static void InternalSet(IntPtr handle, object? value) => RuntimeImports.RhHandleSet(handle, value);
internal static void InternalSet(IntPtr handle, object? value) => RuntimeImports.RhHandleSet(handle, value);
}
}
// 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;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
namespace System
{
public partial class WeakReference
{
// If you fix bugs here, please fix them in WeakReference<T> at the same time.
// Most methods using m_handle should use GC.KeepAlive(this) to avoid potential handle recycling
// attacks (i.e. if the WeakReference instance is finalized away underneath you when you're still
// handling a cached value of the handle then the handle could be freed and reused).
// the instance fields are effectively readonly
internal IntPtr m_handle;
private bool m_trackResurrection;
private void Create(object? target, bool trackResurrection)
{
m_handle = RuntimeImports.RhHandleAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
m_trackResurrection = trackResurrection;
if (target != null)
{
// Set the conditional weak table if the target is a __ComObject.
TrySetComTarget(target);
}
}
//Determines whether or not this instance of WeakReference still refers to an object
//that has not been collected.
public virtual bool IsAlive
{
get
{
IntPtr h = m_handle;
// In determining whether it is valid to use this object, we need to at least expose this
// without throwing an exception.
if (default(IntPtr) == h)
return false;
bool result = (RuntimeImports.RhHandleGet(h) != null || TryGetComTarget() != null);
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return result;
}
}
//Gets the Object stored in the handle if it's accessible.
// Or sets it.
public virtual object? Target
{
get
{
IntPtr h = m_handle;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// GC can finalize the instance if it becomes F-Reachable.
// That, however, cannot happen while we use the instance.
//
// A derived class will be finalized with an actual Finalize, but the finalizer queue is single threaded,
// thus the default implementation will never access Target concurrently with finalizing.
//
// There is a possibility that a derived type overrides the default finalizer and arranges concurrent access.
// There is nothing that we can do about that and a few other exotic ways to break this.
//
if (default(IntPtr) == h)
return default;
object? target = RuntimeImports.RhHandleGet(h) ?? TryGetComTarget();
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return target;
}
set
{
IntPtr h = m_handle;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// See the comment in the getter.
if (default(IntPtr) == h)
throw new InvalidOperationException(SR.InvalidOperation_HandleIsNotInitialized);
// Update the conditionalweakTable in case the target is __ComObject.
TrySetComTarget(value);
RuntimeImports.RhHandleSet(h, value);
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
}
}
/// <summary>
/// This method checks whether the target to the weakreference is a native COMObject in which case the native object might still be alive although the RuntimeHandle could be null.
/// Hence we check in the conditionalweaktable maintained by the System.private.Interop.dll that maps weakreferenceInstance->nativeComObject to check whether the native COMObject is alive or not.
/// and gets\create a new RCW in case it is alive.
/// </summary>
private static object? TryGetComTarget()
{
#if ENABLE_WINRT
WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
if (callbacks != null)
{
return callbacks.GetCOMWeakReferenceTarget(this);
}
else
{
Debug.Fail("WinRTInteropCallback is null");
}
#endif // ENABLE_WINRT
return null;
}
/// <summary>
/// This method notifies the System.private.Interop.dll to update the conditionalweaktable for weakreferenceInstance->target in case the target is __ComObject. This ensures that we have a means to
/// go from the managed weak reference to the actual native object even though the managed counterpart might have been collected.
/// </summary>
private static void TrySetComTarget(object? target)
{
#if ENABLE_WINRT
WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
if (callbacks != null)
{
callbacks.SetCOMWeakReferenceTarget(this, target);
}
else
{
Debug.Fail("WinRTInteropCallback is null");
}
#endif // ENABLE_WINRT
}
// Free all system resources associated with this reference.
~WeakReference()
{
// Note: While WeakReference is formally a finalizable type, the finalizer does not actually run.
// Instead the instances are treated specially in GC when scanning for no longer strongly-reachable
// finalizable objects.
// Unlike WeakReference<T> case, it is possible that this finalizer runs for a derived type.
Debug.Assert(this.GetType() != typeof(WeakReference));
IntPtr handle = m_handle;
if (handle != default(IntPtr))
{
((GCHandle)handle).Free();
m_handle = default(IntPtr);
}
}
//Returns a boolean indicating whether or not we're tracking objects until they're collected (true)
//or just until they're finalized (false).
private bool IsTrackResurrection() => m_trackResurrection;
}
}
// 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;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System
{
public sealed partial class WeakReference<T>
where T : class?
{
// If you fix bugs here, please fix them in WeakReference at the same time.
// the instance fields are effectively readonly
private IntPtr m_handle;
private bool m_trackResurrection;
//Creates a new WeakReference that keeps track of target.
private void Create(T target, bool trackResurrection)
{
m_handle = RuntimeImports.RhHandleAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
m_trackResurrection = trackResurrection;
if (target != null)
{
// Set the conditional weak table if the target is a __ComObject.
TrySetComTarget(target);
}
}
public void SetTarget(T target)
{
IntPtr h = m_handle;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// GC can finalize the instance if it becomes F-Reachable.
// That, however, cannot happen while we use the instance.
if (default(IntPtr) == h)
throw new InvalidOperationException(SR.InvalidOperation_HandleIsNotInitialized);
// Update the conditionalweakTable in case the target is __ComObject.
TrySetComTarget(target);
RuntimeImports.RhHandleSet(h, target);
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
}
private T? Target
{
get
{
IntPtr h = m_handle;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// GC can finalize the instance if it becomes F-Reachable.
// That, however, cannot happen while we use the instance.
if (default(IntPtr) == h)
return default;
// unsafe cast is ok as the handle cannot be destroyed and recycled while we keep the instance alive
T? target = Unsafe.As<T?>(RuntimeImports.RhHandleGet(h)) ?? TryGetComTarget() as T;
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return target;
}
}
/// <summary>
/// This method checks whether the target to the weakreference is a native COMObject in which case the native object might still be alive although the RuntimeHandle could be null.
/// Hence we check in the conditionalweaktable maintained by the System.Private.Interop.dll that maps weakreferenceInstance->nativeComObject to check whether the native COMObject is alive or not.
/// and gets\create a new RCW in case it is alive.
/// </summary>
private static object? TryGetComTarget()
{
#if ENABLE_WINRT
WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
if (callbacks != null)
{
return callbacks.GetCOMWeakReferenceTarget(this);
}
else
{
Debug.Fail("WinRTInteropCallback is null");
}
#endif // ENABLE_WINRT
return null;
}
/// <summary>
/// This method notifies the System.Private.Interop.dll to update the conditionalweaktable for weakreferenceInstance->target in case the target is __ComObject. This ensures that we have a means to
/// go from the managed weak reference to the actual native object even though the managed counterpart might have been collected.
/// </summary>
private static void TrySetComTarget(object? target)
{
#if ENABLE_WINRT
WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
if (callbacks != null)
callbacks.SetCOMWeakReferenceTarget(this, target);
else
{
Debug.Fail("WinRTInteropCallback is null");
}
#endif // ENABLE_WINRT
}
// Note: While WeakReference<T> is formally a finalizable type, the finalizer does not actually run.
// Instead the instances are treated specially in GC when scanning for no longer strongly-reachable
// finalizable objects.
#pragma warning disable CA1821 // Remove empty Finalizers
~WeakReference()
{
Debug.Assert(false, " WeakReference<T> finalizer should never run");
}
#pragma warning restore CA1821 // Remove empty Finalizers
private bool IsTrackResurrection() => m_trackResurrection;
}
}
......@@ -3,7 +3,9 @@
using System.Runtime.Serialization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
namespace System
{
......@@ -13,7 +15,19 @@ namespace System
public sealed partial class WeakReference<T> : ISerializable
where T : class?
{
// If you fix bugs here, please fix them in WeakReference at the same time.
// If you fix bugs here, please fix them in WeakReference<T> at the same time.
// Most methods using the handle should use GC.KeepAlive(this) to avoid potential handle recycling
// attacks (i.e. if the WeakReference instance is finalized away underneath you when you're still
// handling a cached value of the handle then the handle could be freed and reused).
#if !CORECLR
// the handle field is effectively readonly until the object is finalized.
private IntPtr _handleAndKind;
// the lowermost bit is used to indicate whether the handle is tracking resurrection
private const nint TracksResurrectionBit = 1;
#endif
// Creates a new WeakReference that keeps track of target.
// Assumes a Short Weak Reference (ie TrackResurrection is false.)
......@@ -63,5 +77,86 @@ public void GetObjectData(SerializationInfo info, StreamingContext context)
info.AddValue("TrackedObject", this.Target, typeof(T)); // Do not rename (binary serialization)
info.AddValue("TrackResurrection", IsTrackResurrection()); // Do not rename (binary serialization)
}
#if !CORECLR
// Returns a boolean indicating whether or not we're tracking objects until they're collected (true)
// or just until they're finalized (false).
private bool IsTrackResurrection() => (_handleAndKind & TracksResurrectionBit) != 0;
// Creates a new WeakReference that keeps track of target.
private void Create(T target, bool trackResurrection)
{
IntPtr h = GCHandle.InternalAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
_handleAndKind = trackResurrection ?
h | TracksResurrectionBit :
h;
}
private IntPtr Handle => _handleAndKind & ~TracksResurrectionBit;
public void SetTarget(T target)
{
IntPtr h = Handle;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// GC can finalize the instance if it becomes F-Reachable.
// That, however, cannot happen while we use the instance.
if (default(IntPtr) == h)
throw new InvalidOperationException(SR.InvalidOperation_HandleIsNotInitialized);
GCHandle.InternalSet(h, target);
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
}
private T? Target
{
get
{
IntPtr h = Handle;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// GC can finalize the instance if it becomes F-Reachable.
// That, however, cannot happen while we use the instance.
if (default(IntPtr) == h)
return default;
// unsafe cast is ok as the handle cannot be destroyed and recycled while we keep the instance alive
T? target = Unsafe.As<T?>(GCHandle.InternalGet(h));
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return target;
}
}
// eager finalization is NYI on Mono
#if !MONO
// Note: While WeakReference<T> is formally a finalizable type, the finalizer does not actually run.
// Instead the instances are treated specially in GC when scanning for no longer strongly-reachable
// finalizable objects.
#pragma warning disable CA1821 // Remove empty Finalizers
~WeakReference()
{
Debug.Assert(false, " WeakReference<T> finalizer should never run");
}
#pragma warning restore CA1821 // Remove empty Finalizers
#else
// Free all system resources associated with this reference.
~WeakReference()
{
IntPtr handle = Handle;
if (handle != default(IntPtr))
{
GCHandle.InternalFree(handle);
// keep the bit that indicates whether this reference was tracking resurrection
_handleAndKind &= TracksResurrectionBit;
}
}
#endif
#endif
}
}
// 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.InteropServices;
using System.Runtime.Serialization;
namespace System
......@@ -11,6 +13,18 @@ public partial class WeakReference : ISerializable
{
// If you fix bugs here, please fix them in WeakReference<T> at the same time.
// Most methods using the handle should use GC.KeepAlive(this) to avoid potential handle recycling
// attacks (i.e. if the WeakReference instance is finalized away underneath you when you're still
// handling a cached value of the handle then the handle could be freed and reused).
#if !CORECLR
// the handle field is effectively readonly until the object is finalized.
private IntPtr _handleAndKind;
// the lowermost bit is used to indicate whether the handle is tracking resurrection
private const nint TracksResurrectionBit = 1;
#endif
// Creates a new WeakReference that keeps track of target.
// Assumes a Short Weak Reference (ie TrackResurrection is false.)
//
......@@ -44,7 +58,112 @@ public virtual void GetObjectData(SerializationInfo info, StreamingContext conte
// Returns a boolean indicating whether or not we're tracking objects until they're collected (true)
// or just until they're finalized (false).
//
public virtual bool TrackResurrection => IsTrackResurrection();
#if !CORECLR
private void Create(object? target, bool trackResurrection)
{
IntPtr h = GCHandle.InternalAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
_handleAndKind = trackResurrection ?
h | TracksResurrectionBit :
h;
}
// Returns a boolean indicating whether or not we're tracking objects until they're collected (true)
// or just until they're finalized (false).
private bool IsTrackResurrection() => (_handleAndKind & TracksResurrectionBit) != 0;
internal IntPtr Handle => _handleAndKind & ~TracksResurrectionBit;
// Determines whether or not this instance of WeakReference still refers to an object
// that has not been collected.
public virtual bool IsAlive
{
get
{
IntPtr h = Handle;
// In determining whether it is valid to use this object, we need to at least expose this
// without throwing an exception.
if (default(IntPtr) == h)
return false;
bool result = GCHandle.InternalGet(h) != null;
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return result;
}
}
//Gets the Object stored in the handle if it's accessible.
// Or sets it.
public virtual object? Target
{
get
{
IntPtr h = Handle;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// GC can finalize the instance if it becomes F-Reachable.
// That, however, cannot happen while we use the instance.
//
// A derived class will be finalized with an actual Finalize, but the finalizer queue is single threaded,
// thus the default implementation will never access Target concurrently with finalizing.
//
// There is a possibility that a derived type overrides the default finalizer and arranges concurrent access.
// There is nothing that we can do about that and a few other exotic ways to break this.
//
if (default(IntPtr) == h)
return default;
object? target = GCHandle.InternalGet(h);
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
return target;
}
set
{
IntPtr h = Handle;
// Should only happen for corner cases, like using a WeakReference from a finalizer.
// See the comment in the getter.
if (default(IntPtr) == h)
throw new InvalidOperationException(SR.InvalidOperation_HandleIsNotInitialized);
GCHandle.InternalSet(h, value);
// must keep the instance alive as long as we use the handle.
GC.KeepAlive(this);
}
}
// Free all system resources associated with this reference.
~WeakReference()
{
// Note: While WeakReference is formally a finalizable type, the finalizer does not actually run.
// Instead the instances are treated specially in GC when scanning for no longer strongly-reachable
// finalizable objects.
//
// Unlike WeakReference<T> case, the instance could be of a derived type and
// in such case it is finalized via a finalizer.
// eager finalization is NYI on Mono
#if !MONO
Debug.Assert(this.GetType() != typeof(WeakReference));
#endif
IntPtr handle = Handle;
if (handle != default(IntPtr))
{
GCHandle.InternalFree(handle);
// keep the bit that indicates whether this reference was tracking resurrection
_handleAndKind &= TracksResurrectionBit;
}
}
#endif
}
}
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableDefaultItems>false</EnableDefaultItems>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
......@@ -197,8 +197,6 @@
<Compile Include="$(BclSourcesRoot)\System\TypeLoadException.Mono.cs" />
<Compile Include="$(BclSourcesRoot)\System\TypeNameParser.cs" />
<Compile Include="$(BclSourcesRoot)\System\ValueType.cs" />
<Compile Include="$(BclSourcesRoot)\System\WeakReference.Mono.cs" />
<Compile Include="$(BclSourcesRoot)\System\WeakReference.T.Mono.cs" />
<Compile Include="$(BclSourcesRoot)\System\__ComObject.cs" />
<Compile Include="$(BclSourcesRoot)\System\Collections\Generic\ArraySortHelper.Mono.cs" />
<Compile Include="$(BclSourcesRoot)\System\Collections\Generic\Comparer.Mono.cs" />
......
......@@ -8,15 +8,15 @@ namespace System.Runtime.InteropServices
public partial struct GCHandle
{
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern IntPtr InternalAlloc(object? value, GCHandleType type);
internal static extern IntPtr InternalAlloc(object? value, GCHandleType type);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void InternalFree(IntPtr handle);
internal static extern void InternalFree(IntPtr handle);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern object? InternalGet(IntPtr handle);
internal static extern object? InternalGet(IntPtr handle);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void InternalSet(IntPtr handle, object? value);
internal static extern void InternalSet(IntPtr handle, object? value);
}
}
// 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.InteropServices;
namespace System
{
public partial class WeakReference
{
private bool trackResurrection;
private GCHandle handle;
public virtual bool IsAlive => Target != null;
public virtual object? Target
{
get
{
if (!handle.IsAllocated)
return null;
return handle.Target;
}
set
{
handle.Target = value;
}
}
~WeakReference()
{
handle.Free();
}
private void Create(object? target, bool trackResurrection)
{
if (trackResurrection)
{
this.trackResurrection = true;
handle = GCHandle.Alloc(target, GCHandleType.WeakTrackResurrection);
}
else
{
handle = GCHandle.Alloc(target, GCHandleType.Weak);
}
}
private bool IsTrackResurrection() => trackResurrection;
}
}
// 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.InteropServices;
namespace System
{
public partial class WeakReference<T>
{
private GCHandle handle;
private bool trackResurrection;
private T? Target
{
get
{
GCHandle h = handle;
return h.IsAllocated ? (T?)h.Target : null;
}
}
~WeakReference()
{
handle.Free();
}
private void Create(T target, bool trackResurrection)
{
if (trackResurrection)
{
this.trackResurrection = true;
handle = GCHandle.Alloc(target, GCHandleType.WeakTrackResurrection);
}
else
{
handle = GCHandle.Alloc(target, GCHandleType.Weak);
}
}
public void SetTarget(T target)
{
handle.Target = target;
}
private bool IsTrackResurrection() => trackResurrection;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册