X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2Fcorlib%2FSystem.Runtime.InteropServices%2FSafeHandle.cs;h=b5b59a1ae81ffb3f37822fd47ba5619f08d7469a;hb=4ae209510f6d1f6cd86ea936fae1c748ce83caf2;hp=85f23dc3a54abf29b8702a3b59cf0436fbfa7e1b;hpb=599332d0e9b92f9045987d651e545733ff9f1f2e;p=mono.git diff --git a/mcs/class/corlib/System.Runtime.InteropServices/SafeHandle.cs b/mcs/class/corlib/System.Runtime.InteropServices/SafeHandle.cs index 85f23dc3a54..b5b59a1ae81 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; @@ -53,188 +60,171 @@ 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; - -#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; + [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); + 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 && owns_handle && !IsInvalid){ - ReleaseHandle (); - handle = invalid_handle_value; - 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 () - { - if (refcount <= 0){ - throw new ObjectDisposedException (GetType ().FullName); - } + int old_state, new_state; - return handle; + do { + old_state = _state; + + 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); + + 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); + if (!_fullyInitialized) + throw new InvalidOperationException (); + + DangerousReleaseInternal (true); GC.SuppressFinalize (this); } - // - // 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) + DangerousReleaseInternal (true); } - - [ReliabilityContract (Consistency.WillNotCorruptState, Cer.Success)] - protected virtual void Dispose (bool disposing) - { - 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? - // - } - } - - [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 () - { - if (owns_handle && !IsInvalid){ - ReleaseHandle (); - handle = invalid_handle_value; + 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 (); } } }