Merge pull request #1857 from slluis/fix-assembly-resolver
[mono.git] / mcs / class / corlib / System.Runtime.InteropServices / SafeHandle.cs
index f9ab33597369f49a6ae73cf9e78b1117b802e5ce..5a8f5ce05fda7356dc79ee4831ea5847e6069329 100644 (file)
 // 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
 //     For details, see:
 //     http://blogs.msdn.com/cbrumme/archive/2004/02/20/77460.aspx
 //
+//     CER-like behavior is implemented for Close and DangerousAddRef
+//     via the try/finally uninterruptible pattern in case of async
+//     exceptions like ThreadAbortException.
+//
 // On implementing SafeHandles:
 //     http://blogs.msdn.com/bclteam/archive/2005/03/15/396335.aspx
 //
 // Issues:
-//     The System.Runtime.ConstrainedExecution.ReliabilityContractAttribute has
-//     not been applied to any APIs here yet.
 //
 //     TODO: Although DangerousAddRef has been implemented, I need to
 //     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
 //
 
-#if NET_2_0
 using System;
 using System.Runtime.InteropServices;
 using System.Runtime.ConstrainedExecution;
+using System.Runtime.CompilerServices;
 using System.Threading;
 
 namespace System.Runtime.InteropServices
 {
-       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;
-               IntPtr invalid_handle_value;
-               int refcount = 0;
-               bool owns_handle;
-               
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)]
-               protected SafeHandle (IntPtr invalidHandleValue, bool ownsHandle)
-               {
-                       invalid_handle_value = invalidHandleValue;
-                       owns_handle = ownsHandle;
-                       refcount = 1;
+       [StructLayout (LayoutKind.Sequential)]
+       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)
-                               throw new ObjectDisposedException (GetType ().FullName);
+                       int old_state, new_state;
 
-                       int newcount, current;
                        do {
-                               current = refcount;
-                               newcount = current-1;
-                       } while (Interlocked.CompareExchange (ref refcount, newcount, current) != current);
+                               old_state = _state;
+                               new_state = old_state | (int) State.Closed;
+                       } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
 
-                       if (newcount == 0 && owns_handle){
-                               ReleaseHandle ();
-                               handle = invalid_handle_value;
-                       }
+                       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);
+                       if (!_fullyInitialized)
+                               throw new InvalidOperationException ();
+
+                       int old_state, new_state;
 
-                       int newcount, current;
                        do {
-                               current = refcount;
-                               newcount = current + 1;
-                               
-                               if (handle == invalid_handle_value || 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);
-                               }
-                       } while (Interlocked.CompareExchange (ref refcount, newcount, current) != current);
-                       success = true;
-               }
+                               old_state = _state;
 
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               public IntPtr DangerousGetHandle ()
-               {
-                       if (refcount == 0){
-                               throw new ObjectDisposedException (GetType ().FullName);
-                       }
+                               if ((old_state & (int) State.Closed) != 0)
+                                       throw new ObjectDisposedException ("handle");
 
-                       return handle;
+                               new_state = old_state + RefCount_One;
+                       } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
+
+                       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 DangerousRelease ()
                {
-                       if (refcount == 0)
-                               throw new ObjectDisposedException (GetType ().FullName);
-
-                       int newcount, current;
-                       do {
-                               current = refcount;
-                               newcount = current-1;
-                       } while (Interlocked.CompareExchange (ref refcount, newcount, current) != current);
-
-                       if (newcount == 0 && owns_handle){
-                               ReleaseHandle ();
-                               handle = invalid_handle_value;
-                       }
+                       DangerousReleaseInternal (false);
                }
 
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               public virtual void Dispose ()
+               void InternalDispose ()
                {
-                       Dispose (true);
-                       GC.SuppressFinalize (this);
+                       if (!_fullyInitialized)
+                               throw new InvalidOperationException ();
+                       DisposeInternal ();
                }
 
-               //
-               // See documentation, this invalidates the handle without
-               // closing it.
-               //
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               public void SetHandleAsInvalid ()
+               void InternalFinalize ()
                {
-                       handle = invalid_handle_value;
+                       if (_fullyInitialized)
+                               DisposeInternal ();
                }
-               
-               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
-               protected virtual void Dispose (bool disposing)
+
+               void DisposeInternal ()
                {
-                       if (disposing)
-                               Close ();
-                       else {
-                               //
-                               // The docs say `never call this with disposing=false',
-                               // the question is whether:
-                               //   * The runtime will ever call Dipose(false) for SafeHandles (special runtime case)
-                               //   * Whether we should just call ReleaseHandle regardless?
-                               //
-                       }
+                       DangerousReleaseInternal (true);
+                       GC.SuppressFinalize (this);
                }
 
-               [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;
-               }
+                       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 ()
-               {
-                       if (owns_handle){
+                       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)
+                                       return;
+
+                               /* 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");
+
+                               perform_release =
+                                       (old_state & RefCount_Mask) == RefCount_One
+                                        && (old_state & (int) State.Closed) == 0
+                                        && _ownsHandle;
+
+                               if (perform_release && IsInvalid)
+                                       perform_release = false;
+
+                               /* 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_One)
+                                        | ((old_state & RefCount_Mask) == RefCount_One ? (int) State.Closed : 0)
+                                        | (dispose ? (int) State.Disposed : 0);
+                       } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state);
+
+                       if (perform_release)
                                ReleaseHandle ();
-                               handle = invalid_handle_value;
-                       }
                }
+
+               /*
+                * Implement this abstract method in your derived class to specify how to
+                * free the handle. Be careful not write any code that's subject to faults
+                * in this method (the runtime will prepare the infrastructure for you so
+                * that no jit allocations etc. will occur, but don't allocate memory unless
+                * you can deal with the failure and still free the handle).
+                * The boolean returned should be true for success and false if the runtime
+                * should fire a SafeHandleCriticalFailure MDA (CustomerDebugProbe) if that
+                * MDA is enabled.
+                */
+               [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)]
+               protected abstract bool ReleaseHandle ();
        }
 }
-#endif