X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2Fcorlib%2FSystem.Runtime.InteropServices%2FSafeHandle.cs;h=5a8f5ce05fda7356dc79ee4831ea5847e6069329;hb=f03a1b538bfb0d9be810688e8713731e122320b5;hp=52e48cda10ea798161a44672c54fb2eb7214d5e4;hpb=c251b192a55c255f278c9b9ad2ec949264a36526;p=mono.git diff --git a/mcs/class/corlib/System.Runtime.InteropServices/SafeHandle.cs b/mcs/class/corlib/System.Runtime.InteropServices/SafeHandle.cs index 52e48cda10e..5a8f5ce05fd 100644 --- a/mcs/class/corlib/System.Runtime.InteropServices/SafeHandle.cs +++ b/mcs/class/corlib/System.Runtime.InteropServices/SafeHandle.cs @@ -10,10 +10,10 @@ // 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 @@ -43,6 +43,13 @@ // 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; @@ -54,189 +61,171 @@ using System.Threading; 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; - IntPtr invalid_handle_value; - int refcount = 0; - bool owns_handle; - -#if NET_2_1 - protected SafeHandle () - { - throw new NotImplementedException (); - } -#endif - [ReliabilityContract (Consistency.WillNotCorruptState, Cer.MayFail)] - protected SafeHandle (IntPtr invalidHandleValue, bool ownsHandle) - { - invalid_handle_value = invalidHandleValue; - owns_handle = ownsHandle; - 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) - throw new ObjectDisposedException (GetType ().FullName); - - 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 && owns_handle && !IsInvalid){ - ReleaseHandle (); - handle = invalid_handle_value; - refcount = -1; - } - } + int old_state, new_state; + + do { + 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); + if (!_fullyInitialized) + throw new InvalidOperationException (); + + int old_state, new_state; - 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); - } + 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"); + + new_state = old_state + RefCount_One; + } while (Interlocked.CompareExchange (ref _state, new_state, old_state) != old_state); - return handle; + 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 && !IsInvalid){ - ReleaseHandle (); - handle = invalid_handle_value; - } + DangerousReleaseInternal (false); } - [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)] - public 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 && !IsInvalid){ + 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 (); } }