// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
-//
+//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
-//
+//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// find out whether the runtime performs the P/Invoke if the
// handle has been disposed already.
//
+
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+// Files:
+// - mscorlib/system/runtime/interopservices/safehandle.cs
//
using System;
namespace System.Runtime.InteropServices
{
[StructLayout (LayoutKind.Sequential)]
- public abstract class SafeHandle : CriticalFinalizerObject, IDisposable {
- //
- // Warning: the offset of handle is mapped inside the runtime
- // if you move this, you must updated the runtime definition of
- // MonoSafeHandle
- //
- protected IntPtr handle;
- int refcount;
- bool owns_handle;
-
-#if NET_2_1
- protected SafeHandle ()
- {
- throw new NotImplementedException ();
- }
-#endif
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
- protected SafeHandle (IntPtr invalidHandleValue, bool ownsHandle)
- {
- handle = invalidHandleValue;
-
- if (!ownsHandle) {
- GC.SuppressFinalize (this);
- } else {
- owns_handle = true;
- }
-
- refcount = 1;
+ public abstract partial class SafeHandle
+ {
+ const int RefCount_Mask = 0x7ffffffc;
+ const int RefCount_One = 0x4;
+
+ enum State {
+ Closed = 0x00000001,
+ Disposed = 0x00000002,
}
+ /*
+ * This should only be called for cases when you know for a fact that
+ * your handle is invalid and you want to record that information.
+ * An example is calling a syscall and getting back ERROR_INVALID_HANDLE.
+ * This method will normally leak handles!
+ */
[ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- public void Close ()
+ public void SetHandleAsInvalid ()
{
- if (refcount <= 0) {
- if (refcount == 0)
- throw new ObjectDisposedException (GetType ().FullName);
-
- return;
- }
+ try {}
+ finally {
+ int old_state, new_state;
- int newcount = 0, current = 0;
- bool registered = false;
- RuntimeHelpers.PrepareConstrainedRegions ();
- try {
do {
- current = refcount;
- newcount = current-1;
-
- // perform changes in finally to avoid async interruptions
- try {}
- finally {
- if (Interlocked.CompareExchange (ref refcount, newcount, current) == current)
- registered = true;
- }
- } while (!registered);
- } finally {
- if (registered && newcount == 0) {
- if (owns_handle && !IsInvalid)
- ReleaseHandle ();
- refcount = -1;
- }
+ old_state = _state;
+ new_state = old_state | (int) State.Closed;
+ } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
+
+ GC.SuppressFinalize (this);
}
}
- //
- // I do not know when we could not be able to increment the
- // reference count and set success to false. It might just
- // be a convention used for the following code pattern:
- //
- // bool release = false
- // try { x.DangerousAddRef (ref release); ... }
- // finally { if (release) x.DangerousRelease (); }
- //
+ /*
+ * Add a reason why this handle should not be relinquished (i.e. have
+ * ReleaseHandle called on it). This method has dangerous in the name since
+ * it must always be used carefully (e.g. called within a CER) to avoid
+ * leakage of the handle. It returns a boolean indicating whether the
+ * increment was actually performed to make it easy for program logic to
+ * back out in failure cases (i.e. is a call to DangerousRelease needed).
+ * It is passed back via a ref parameter rather than as a direct return so
+ * that callers need not worry about the atomicity of calling the routine
+ * and assigning the return value to a variable (the variable should be
+ * explicitly set to false prior to the call). The only failure cases are
+ * when the method is interrupted prior to processing by a thread abort or
+ * when the handle has already been (or is in the process of being)
+ * released.
+ */
[ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
public void DangerousAddRef (ref bool success)
{
- if (refcount <= 0)
- throw new ObjectDisposedException (GetType ().FullName);
-
- bool registered = false;
- int newcount, current;
- do {
- current = refcount;
- newcount = current + 1;
-
- if (current <= 0){
- //
- // In MS, calling sf.Close () followed by a call
- // to P/Invoke with SafeHandles throws this, but
- // am left wondering: when would "success" be
- // set to false?
- //
- throw new ObjectDisposedException (GetType ().FullName);
- }
-
- // perform changes in finally to avoid async interruptions
- RuntimeHelpers.PrepareConstrainedRegions ();
- try {}
- finally {
- if (Interlocked.CompareExchange (ref refcount, newcount, current) == current)
- registered = success = true;
- }
- } while (!registered);
- }
+ try {}
+ finally {
+ if (!_fullyInitialized)
+ throw new InvalidOperationException ();
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- public IntPtr DangerousGetHandle ()
- {
- return handle;
- }
+ int old_state, new_state;
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- public void DangerousRelease ()
- {
- if (refcount <= 0)
- throw new ObjectDisposedException (GetType ().FullName);
+ do {
+ old_state = _state;
+
+ if ((old_state & (int) State.Closed) != 0)
+ throw new ObjectDisposedException ("handle");
- int newcount, current;
- do {
- current = refcount;
- newcount = current-1;
- } while (Interlocked.CompareExchange (ref refcount, newcount, current) != current);
+ new_state = old_state + RefCount_One;
+ } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
- if (newcount == 0 && owns_handle && !IsInvalid){
- ReleaseHandle ();
+ success = true;
}
}
+ /*
+ * Partner to DangerousAddRef. This should always be successful when used in
+ * a correct manner (i.e. matching a successful DangerousAddRef and called
+ * from a region such as a CER where a thread abort cannot interrupt
+ * processing). In the same way that unbalanced DangerousAddRef calls can
+ * cause resource leakage, unbalanced DangerousRelease calls may cause
+ * invalid handle states to become visible to other threads. This
+ * constitutes a potential security hole (via handle recycling) as well as a
+ * correctness problem -- so don't ever expose Dangerous* calls out to
+ * untrusted code.
+ */
[ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- public void Dispose ()
+ public void DangerousRelease ()
{
- Dispose (true);
- GC.SuppressFinalize (this);
+ DangerousReleaseInternal (false);
}
- //
- // See documentation, this invalidates the handle without
- // closing it.
- //
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- public void SetHandleAsInvalid ()
+ void InternalDispose ()
{
- refcount = -1;
+ if (!_fullyInitialized)
+ throw new InvalidOperationException ();
+
+ DangerousReleaseInternal (true);
+ GC.SuppressFinalize (this);
}
-
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- protected virtual void Dispose (bool disposing)
+
+ void InternalFinalize ()
{
- if (disposing) {
- Close ();
- } else {
- if (owns_handle && !IsInvalid){
- ReleaseHandle ();
- }
- }
+ if (_fullyInitialized)
+ DangerousReleaseInternal (true);
}
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- protected abstract bool ReleaseHandle ();
-
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- protected void SetHandle (IntPtr handle)
+ void DangerousReleaseInternal (bool dispose)
{
- this.handle = handle;
- }
+ try {}
+ finally {
+ if (!_fullyInitialized)
+ throw new InvalidOperationException ();
- public bool IsClosed {
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- get {
- return refcount <= 0;
- }
- }
+ int old_state, new_state;
- public abstract bool IsInvalid {
- [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
- get;
- }
+ /* See AddRef above for the design of the synchronization here. Basically we
+ * will try to decrement the current ref count and, if that would take us to
+ * zero refs, set the closed state on the handle as well. */
+ bool perform_release = false;
- ~SafeHandle ()
- {
- Dispose (false);
+ do {
+ old_state = _state;
+
+ /* If this is a Dispose operation we have additional requirements (to
+ * ensure that Dispose happens at most once as the comments in AddRef
+ * detail). We must check that the dispose bit is not set in the old
+ * state and, in the case of successful state update, leave the disposed
+ * bit set. Silently do nothing if Dispose has already been called
+ * (because we advertise that as a semantic of Dispose). */
+ if (dispose && (old_state & (int) State.Disposed) != 0) {
+ /* we cannot use `return` in a finally block, so we have to ensure
+ * that we are not releasing the handle */
+ perform_release = false;
+ break;
+ }
+
+ /* We should never see a ref count of zero (that would imply we have
+ * unbalanced AddRef and Releases). (We might see a closed state before
+ * hitting zero though -- that can happen if SetHandleAsInvalid is
+ * used). */
+ if ((old_state & RefCount_Mask) == 0)
+ throw new ObjectDisposedException ("handle");
+
+ if ((old_state & RefCount_Mask) != RefCount_One)
+ perform_release = false;
+ else if ((old_state & (int) State.Closed) != 0)
+ perform_release = false;
+ else if (!_ownsHandle)
+ perform_release = false;
+ else if (IsInvalid)
+ perform_release = false;
+ else
+ perform_release = true;
+
+ /* Attempt the update to the new state, fail and retry if the initial
+ * state has been modified in the meantime. Decrement the ref count by
+ * substracting SH_RefCountOne from the state then OR in the bits for
+ * Dispose (if that's the reason for the Release) and closed (if the
+ * initial ref count was 1). */
+ new_state = (old_state & RefCount_Mask) - RefCount_One;
+ if ((old_state & RefCount_Mask) == RefCount_One)
+ new_state |= (int) State.Closed;
+ if (dispose)
+ new_state |= (int) State.Disposed;
+ } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
+
+ if (perform_release)
+ ReleaseHandle ();
+ }
}
}
}