summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Runtime/InteropServices/SafeBuffer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mscorlib/src/System/Runtime/InteropServices/SafeBuffer.cs')
-rw-r--r--src/mscorlib/src/System/Runtime/InteropServices/SafeBuffer.cs415
1 files changed, 415 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Runtime/InteropServices/SafeBuffer.cs b/src/mscorlib/src/System/Runtime/InteropServices/SafeBuffer.cs
new file mode 100644
index 0000000000..a659daf2b5
--- /dev/null
+++ b/src/mscorlib/src/System/Runtime/InteropServices/SafeBuffer.cs
@@ -0,0 +1,415 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+** Purpose: Unsafe code that uses pointers should use
+** SafePointer to fix subtle lifetime problems with the
+** underlying resource.
+**
+===========================================================*/
+
+// Design points:
+// *) Avoid handle-recycling problems (including ones triggered via
+// resurrection attacks) for all accesses via pointers. This requires tying
+// together the lifetime of the unmanaged resource with the code that reads
+// from that resource, in a package that uses synchronization to enforce
+// the correct semantics during finalization. We're using SafeHandle's
+// ref count as a gate on whether the pointer can be dereferenced because that
+// controls the lifetime of the resource.
+//
+// *) Keep the penalties for using this class small, both in terms of space
+// and time. Having multiple threads reading from a memory mapped file
+// will already require 2 additional interlocked operations. If we add in
+// a "current position" concept, that requires additional space in memory and
+// synchronization. Since the position in memory is often (but not always)
+// something that can be stored on the stack, we can save some memory by
+// excluding it from this object. However, avoiding the need for
+// synchronization is a more significant win. This design allows multiple
+// threads to read and write memory simultaneously without locks (as long as
+// you don't write to a region of memory that overlaps with what another
+// thread is accessing).
+//
+// *) Space-wise, we use the following memory, including SafeHandle's fields:
+// Object Header MT* handle int bool bool <2 pad bytes> length
+// On 32 bit platforms: 24 bytes. On 64 bit platforms: 40 bytes.
+// (We can safe 4 bytes on x86 only by shrinking SafeHandle)
+//
+// *) Wrapping a SafeHandle would have been a nice solution, but without an
+// ordering between critical finalizable objects, it would have required
+// changes to each SafeHandle subclass to opt in to being usable from a
+// SafeBuffer (or some clever exposure of SafeHandle's state fields and a
+// way of forcing ReleaseHandle to run even after the SafeHandle has been
+// finalized with a ref count > 1). We can use less memory and create fewer
+// objects by simply inserting a SafeBuffer into the class hierarchy.
+//
+// *) In an ideal world, we could get marshaling support for SafeBuffer that
+// would allow us to annotate a P/Invoke declaration, saying this parameter
+// specifies the length of the buffer, and the units of that length are X.
+// P/Invoke would then pass that size parameter to SafeBuffer.
+// [DllImport(...)]
+// static extern SafeMemoryHandle AllocCharBuffer(int numChars);
+// If we could put an attribute on the SafeMemoryHandle saying numChars is
+// the element length, and it must be multiplied by 2 to get to the byte
+// length, we can simplify the usage model for SafeBuffer.
+//
+// *) This class could benefit from a constraint saying T is a value type
+// containing no GC references.
+
+// Implementation notes:
+// *) The Initialize method must be called before you use any instance of
+// a SafeBuffer. To avoid race conditions when storing SafeBuffers in statics,
+// you either need to take a lock when publishing the SafeBuffer, or you
+// need to create a local, initialize the SafeBuffer, then assign to the
+// static variable (perhaps using Interlocked.CompareExchange). Of course,
+// assignments in a static class constructor are under a lock implicitly.
+
+
+namespace System.Runtime.InteropServices
+{
+using System;
+using System.Security.Permissions;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.Versioning;
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics.Contracts;
+
+
+ [System.Security.SecurityCritical]
+ public abstract unsafe class SafeBuffer : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ // Steal UIntPtr.MaxValue as our uninitialized value.
+ private static readonly UIntPtr Uninitialized = (UIntPtr.Size == 4) ?
+ ((UIntPtr) UInt32.MaxValue) : ((UIntPtr) UInt64.MaxValue);
+
+ private UIntPtr _numBytes;
+
+ protected SafeBuffer(bool ownsHandle) : base(ownsHandle)
+ {
+ _numBytes = Uninitialized;
+ }
+
+ /// <summary>
+ /// Specifies the size of the region of memory, in bytes. Must be
+ /// called before using the SafeBuffer.
+ /// </summary>
+ /// <param name="numBytes">Number of valid bytes in memory.</param>
+ [CLSCompliant(false)]
+ public void Initialize(ulong numBytes)
+ {
+ if (numBytes < 0)
+ throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ if (IntPtr.Size == 4 && numBytes > UInt32.MaxValue)
+ throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_AddressSpace"));
+ Contract.EndContractBlock();
+
+ if (numBytes >= (ulong)Uninitialized)
+ throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_UIntPtrMax-1"));
+
+ _numBytes = (UIntPtr) numBytes;
+ }
+
+ /// <summary>
+ /// Specifies the the size of the region in memory, as the number of
+ /// elements in an array. Must be called before using the SafeBuffer.
+ /// </summary>
+ [CLSCompliant(false)]
+ public void Initialize(uint numElements, uint sizeOfEachElement)
+ {
+ if (numElements < 0)
+ throw new ArgumentOutOfRangeException("numElements", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ if (sizeOfEachElement < 0)
+ throw new ArgumentOutOfRangeException("sizeOfEachElement", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+
+ if (IntPtr.Size == 4 && numElements * sizeOfEachElement > UInt32.MaxValue)
+ throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_AddressSpace"));
+ Contract.EndContractBlock();
+
+ if (numElements * sizeOfEachElement >= (ulong)Uninitialized)
+ throw new ArgumentOutOfRangeException("numElements", Environment.GetResourceString("ArgumentOutOfRange_UIntPtrMax-1"));
+
+ _numBytes = checked((UIntPtr) (numElements * sizeOfEachElement));
+ }
+
+ /// <summary>
+ /// Specifies the the size of the region in memory, as the number of
+ /// elements in an array. Must be called before using the SafeBuffer.
+ /// </summary>
+ [CLSCompliant(false)]
+ public void Initialize<T>(uint numElements) where T : struct
+ {
+ Initialize(numElements, Marshal.AlignedSizeOf<T>());
+ }
+
+ // Callers should ensure that they check whether the pointer ref param
+ // is null when AcquirePointer returns. If it is not null, they must
+ // call ReleasePointer in a CER. This method calls DangerousAddRef
+ // & exposes the pointer. Unlike Read, it does not alter the "current
+ // position" of the pointer. Here's how to use it:
+ //
+ // byte* pointer = null;
+ // RuntimeHelpers.PrepareConstrainedRegions();
+ // try {
+ // safeBuffer.AcquirePointer(ref pointer);
+ // // Use pointer here, with your own bounds checking
+ // }
+ // finally {
+ // if (pointer != null)
+ // safeBuffer.ReleasePointer();
+ // }
+ //
+ // Note: If you cast this byte* to a T*, you have to worry about
+ // whether your pointer is aligned. Additionally, you must take
+ // responsibility for all bounds checking with this pointer.
+ /// <summary>
+ /// Obtain the pointer from a SafeBuffer for a block of code,
+ /// with the express responsibility for bounds checking and calling
+ /// ReleasePointer later within a CER to ensure the pointer can be
+ /// freed later. This method either completes successfully or
+ /// throws an exception and returns with pointer set to null.
+ /// </summary>
+ /// <param name="pointer">A byte*, passed by reference, to receive
+ /// the pointer from within the SafeBuffer. You must set
+ /// pointer to null before calling this method.</param>
+ [CLSCompliant(false)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public void AcquirePointer(ref byte* pointer)
+ {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ pointer = null;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ }
+ finally
+ {
+ bool junk = false;
+ DangerousAddRef(ref junk);
+ pointer = (byte*)handle;
+ }
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public void ReleasePointer()
+ {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ DangerousRelease();
+ }
+
+ /// <summary>
+ /// Read a value type from memory at the given offset. This is
+ /// equivalent to: return *(T*)(bytePtr + byteOffset);
+ /// </summary>
+ /// <typeparam name="T">The value type to read</typeparam>
+ /// <param name="byteOffset">Where to start reading from memory. You
+ /// may have to consider alignment.</param>
+ /// <returns>An instance of T read from memory.</returns>
+ [CLSCompliant(false)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public T Read<T>(ulong byteOffset) where T : struct {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ uint sizeofT = Marshal.SizeOfType(typeof(T));
+ byte* ptr = (byte*)handle + byteOffset;
+ SpaceCheck(ptr, sizeofT);
+
+ // return *(T*) (_ptr + byteOffset);
+ T value;
+ bool mustCallRelease = false;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ DangerousAddRef(ref mustCallRelease);
+
+ GenericPtrToStructure<T>(ptr, out value, sizeofT);
+ }
+ finally
+ {
+ if (mustCallRelease)
+ DangerousRelease();
+ }
+ return value;
+ }
+
+ [CLSCompliant(false)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public void ReadArray<T>(ulong byteOffset, T[] array, int index, int count)
+ where T : struct
+ {
+ if (array == null)
+ throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer"));
+ if (index < 0)
+ throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ if (array.Length - index < count)
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
+ Contract.EndContractBlock();
+
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ uint sizeofT = Marshal.SizeOfType(typeof(T));
+ uint alignedSizeofT = Marshal.AlignedSizeOf<T>();
+ byte* ptr = (byte*)handle + byteOffset;
+ SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count)));
+
+ bool mustCallRelease = false;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ DangerousAddRef(ref mustCallRelease);
+
+ for (int i = 0; i < count; i++)
+ unsafe { GenericPtrToStructure<T>(ptr + alignedSizeofT * i, out array[i + index], sizeofT); }
+ }
+ finally
+ {
+ if (mustCallRelease)
+ DangerousRelease();
+ }
+ }
+
+ /// <summary>
+ /// Write a value type to memory at the given offset. This is
+ /// equivalent to: *(T*)(bytePtr + byteOffset) = value;
+ /// </summary>
+ /// <typeparam name="T">The type of the value type to write to memory.</typeparam>
+ /// <param name="byteOffset">The location in memory to write to. You
+ /// may have to consider alignment.</param>
+ /// <param name="value">The value type to write to memory.</param>
+ [CLSCompliant(false)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public void Write<T>(ulong byteOffset, T value) where T : struct {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ uint sizeofT = Marshal.SizeOfType(typeof(T));
+ byte* ptr = (byte*)handle + byteOffset;
+ SpaceCheck(ptr, sizeofT);
+
+ // *((T*) (_ptr + byteOffset)) = value;
+ bool mustCallRelease = false;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ DangerousAddRef(ref mustCallRelease);
+ GenericStructureToPtr(ref value, ptr, sizeofT);
+ }
+ finally
+ {
+ if (mustCallRelease)
+ DangerousRelease();
+ }
+ }
+
+ [CLSCompliant(false)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public void WriteArray<T>(ulong byteOffset, T[] array, int index, int count)
+ where T : struct
+ {
+ if (array == null)
+ throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer"));
+ if (index < 0)
+ throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ if (count < 0)
+ throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
+ if (array.Length - index < count)
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
+ Contract.EndContractBlock();
+
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ uint sizeofT = Marshal.SizeOfType(typeof(T));
+ uint alignedSizeofT = Marshal.AlignedSizeOf<T>();
+ byte* ptr = (byte*)handle + byteOffset;
+ SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count)));
+
+ bool mustCallRelease = false;
+ RuntimeHelpers.PrepareConstrainedRegions();
+ try
+ {
+ DangerousAddRef(ref mustCallRelease);
+ for (int i = 0; i < count; i++)
+ unsafe { GenericStructureToPtr(ref array[i + index], ptr + alignedSizeofT * i, sizeofT); }
+ }
+ finally
+ {
+ if (mustCallRelease)
+ DangerousRelease();
+ }
+ }
+
+
+ /// <summary>
+ /// Returns the number of bytes in the memory region.
+ /// </summary>
+ [CLSCompliant(false)]
+ public ulong ByteLength {
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ get {
+ if (_numBytes == Uninitialized)
+ throw NotInitialized();
+
+ return (ulong) _numBytes;
+ }
+ }
+
+ /* No indexer. The perf would be misleadingly bad. People should use
+ * AcquirePointer and ReleasePointer instead. */
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ private void SpaceCheck(byte* ptr, ulong sizeInBytes)
+ {
+ if ((ulong)_numBytes < sizeInBytes)
+ NotEnoughRoom();
+ if ((ulong)(ptr - (byte*) handle) > ((ulong)_numBytes) - sizeInBytes)
+ NotEnoughRoom();
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ private static void NotEnoughRoom()
+ {
+ throw new ArgumentException(Environment.GetResourceString("Arg_BufferTooSmall"));
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ private static InvalidOperationException NotInitialized()
+ {
+ Contract.Assert(false, "Uninitialized SafeBuffer! Someone needs to call Initialize before using this instance!");
+ return new InvalidOperationException(Environment.GetResourceString("InvalidOperation_MustCallInitialize"));
+ }
+
+ // FCALL limitations mean we can't have generic FCALL methods. However, we can pass
+ // TypedReferences to FCALL methods.
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ internal static void GenericPtrToStructure<T>(byte* ptr, out T structure, uint sizeofT) where T : struct
+ {
+ structure = default(T); // Dummy assignment to silence the compiler
+ PtrToStructureNative(ptr, __makeref(structure), sizeofT);
+ }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ private static extern void PtrToStructureNative(byte* ptr, /*out T*/ TypedReference structure, uint sizeofT);
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ internal static void GenericStructureToPtr<T>(ref T structure, byte* ptr, uint sizeofT) where T : struct
+ {
+ StructureToPtrNative(__makeref(structure), ptr, sizeofT);
+ }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ private static extern void StructureToPtrNative(/*ref T*/ TypedReference structure, byte* ptr, uint sizeofT);
+ }
+}