// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using static System.WeakReferenceHandleTags; namespace System { internal static class WeakReferenceHandleTags { // the lowermost bit is used to indicate whether the handle is tracking resurrection // handles are at least 2-byte aligned, so we can use one bit for tagging internal const nint TracksResurrectionBit = 1; #if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS // one more bit is used to track whether the handle refers to an instance of ComAwareWeakReference // we can use this bit because on COM-supporting platforms a handle is at least 4-byte aligned internal const nint ComAwareBit = 2; internal const nint HandleTagBits = TracksResurrectionBit | ComAwareBit; #else internal const nint HandleTagBits = TracksResurrectionBit; #endif } [Serializable] [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] 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). private nint _taggedHandle; // Creates a new WeakReference that keeps track of target. // Assumes a Short Weak Reference (ie TrackResurrection is false.) // public WeakReference(object? target) : this(target, false) { } public WeakReference(object? target, bool trackResurrection) { Create(target, trackResurrection); } [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] protected WeakReference(SerializationInfo info, StreamingContext context) { ArgumentNullException.ThrowIfNull(info); object? target = info.GetValue("TrackedObject", typeof(object)); // Do not rename (binary serialization) bool trackResurrection = info.GetBoolean("TrackResurrection"); // Do not rename (binary serialization) Create(target, trackResurrection); } [Obsolete(Obsoletions.LegacyFormatterImplMessage, DiagnosticId = Obsoletions.LegacyFormatterImplDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] [EditorBrowsable(EditorBrowsableState.Never)] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { ArgumentNullException.ThrowIfNull(info); info.AddValue("TrackedObject", Target, typeof(object)); // Do not rename (binary serialization) info.AddValue("TrackResurrection", IsTrackResurrection()); // Do not rename (binary serialization) } // 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(); private void Create(object? target, bool trackResurrection) { nint h = GCHandle.InternalAlloc(target, trackResurrection ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak); _taggedHandle = trackResurrection ? h | TracksResurrectionBit : h; #if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS ComAwareWeakReference.ComInfo? comInfo = ComAwareWeakReference.ComInfo.FromObject(target); if (comInfo != null) { ComAwareWeakReference.SetComInfoInConstructor(ref _taggedHandle, comInfo); } #endif } // 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() => (_taggedHandle & TracksResurrectionBit) != 0; internal nint WeakHandle { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { nint th = _taggedHandle; #if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS if ((th & ComAwareBit) != 0) return ComAwareWeakReference.GetFromTaggedReference(th).WeakHandle; #endif return th & ~HandleTagBits; } } // Determines whether or not this instance of WeakReference still refers to an object // that has not been collected. public virtual bool IsAlive { get { nint wh = WeakHandle; // In determining whether it is valid to use this object, we need to at least expose this // without throwing an exception. if (wh == 0) return false; #if FEATURE_JAVAMARSHAL bool result = GCHandle.InternalGetBridgeWait(wh) != null; #else bool result = GCHandle.InternalGet(wh) != null; #endif // 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 { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { nint th = _taggedHandle & ~TracksResurrectionBit; // 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 (th == 0) return default; object? target; #if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS if ((th & ComAwareBit) != 0) { ComAwareWeakReference cwr = ComAwareWeakReference.GetFromTaggedReference(th); target = cwr.Target ?? cwr.RehydrateTarget<object>(); // must keep the instance alive as long as we use the handle. GC.KeepAlive(this); return target; } #endif #if FEATURE_JAVAMARSHAL target = GCHandle.InternalGetBridgeWait(th); #else // unsafe cast is ok as the handle cannot be destroyed and recycled while we keep the instance alive target = GCHandle.InternalGet(th); #endif // must keep the instance alive as long as we use the handle. GC.KeepAlive(this); return target; } set { nint th = _taggedHandle & ~TracksResurrectionBit; // 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 (th == 0) throw new InvalidOperationException(SR.InvalidOperation_HandleIsNotInitialized); #if FEATURE_COMINTEROP || FEATURE_COMWRAPPERS var comInfo = ComAwareWeakReference.ComInfo.FromObject(value); if ((th & ComAwareBit) != 0 || comInfo != null) { ComAwareWeakReference.SetTarget(ref _taggedHandle, value, comInfo); // must keep the instance alive as long as we use the handle. GC.KeepAlive(this); return; } #endif GCHandle.InternalSet(th, 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. Debug.Assert(this.GetType() != typeof(WeakReference)); nint handle = _taggedHandle & ~HandleTagBits; if (handle != 0) { GCHandle.InternalFree(handle); // keep the bit that indicates whether this reference was tracking resurrection, clear the rest. _taggedHandle &= TracksResurrectionBit; } } } }