Skip to content

Commit

Permalink
Merge pull request #184 from Windows10CE/remove-unsafe-as-ub
Browse files Browse the repository at this point in the history
Remove Unsafe.As UB in {,ReadOnly}Span backports
  • Loading branch information
nike4613 committed Jun 4, 2024
2 parents 3fcd145 + 1147b11 commit 53fec67
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ public Span<T> Span
// and then cast to a Memory<T>. Such a cast can only be done with unsafe or marshaling code,
// in which case that's the dangerous operation performed by the dev, and we're just following
// suit here to make it work as best as possible.
return new Span<T>(Unsafe.As<Pinnable<T>>(s), MemoryExtensions.StringAdjustmentHolder.StringAdjustment, s.Length).Slice(_index, _length);
return new Span<T>(s, (nint)RuntimeHelpers.OffsetToStringData, s.Length).Slice(_index, _length);
}
else if (_object != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ public static ReadOnlySpan<char> AsSpan(this string? text)
if (text == null)
return default;

return new ReadOnlySpan<char>(Unsafe.As<Pinnable<char>>(text), StringAdjustmentHolder.StringAdjustment, text.Length);
return new ReadOnlySpan<char>(text, (nint)RuntimeHelpers.OffsetToStringData, text.Length);
}

/// <summary>
Expand All @@ -302,7 +302,7 @@ public static ReadOnlySpan<char> AsSpan(this string? text, int start)
if ((uint)start > (uint)text.Length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);

return new ReadOnlySpan<char>(Unsafe.As<Pinnable<char>>(text), StringAdjustmentHolder.StringAdjustment + start * sizeof(char), text.Length - start);
return new ReadOnlySpan<char>(text, (nint)RuntimeHelpers.OffsetToStringData + start * sizeof(char), text.Length - start);
}

/// <summary>
Expand All @@ -327,7 +327,7 @@ public static ReadOnlySpan<char> AsSpan(this string? text, int start, int length
if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);

return new ReadOnlySpan<char>(Unsafe.As<Pinnable<char>>(text), StringAdjustmentHolder.StringAdjustment + start * sizeof(char), length);
return new ReadOnlySpan<char>(text, (nint)RuntimeHelpers.OffsetToStringData + start * sizeof(char), length);
}

/// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
Expand Down Expand Up @@ -383,25 +383,5 @@ public static ReadOnlyMemory<char> AsMemory(this string? text, int start, int le

return new ReadOnlyMemory<char>(text, start, length);
}


internal static class StringAdjustmentHolder
{
internal static readonly nint StringAdjustment = MeasureStringAdjustment();

private static nint MeasureStringAdjustment()
{
string sampleString = "a";
unsafe
{
// NOTE: On some old versions of Mono, pinning strings doesn't work, and hard crashes the runtime with an assert. (See docs/RuntimeIssueNodes.md)
// We therefore use a somewhat unusual method of working out the right string adjustment: we have an IL-implemented method for measuring the difference
// between the start of the object and the start of the object data, as measured relative to Pinnable<T>.Data. We can then subtract this amount from
// the normal RuntimeHelpers.OffsetToStringData to get the correct string adjustment.
var offset = (nint)ILHelpers.GetRawDataOffset(sampleString, ref Unsafe.As<Pinnable<byte>>(sampleString).Data);
return RuntimeHelpers.OffsetToStringData - offset;
}
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ public ReadOnlySpan<T> Span
else if (typeof(T) == typeof(char) && _object is string s)
{
Debug.Assert(_length >= 0);
return new ReadOnlySpan<T>(Unsafe.As<Pinnable<T>>(s), MemoryExtensions.StringAdjustmentHolder.StringAdjustment, s.Length).Slice(_index, _length);
return new ReadOnlySpan<T>(s, (nint)RuntimeHelpers.OffsetToStringData, s.Length).Slice(_index, _length);
}
else if (_object != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public ReadOnlySpan(T[]? array)
}

_length = array.Length;
_pinnable = Unsafe.As<Pinnable<T>>(array);
_pinnable = array;
_byteOffset = SpanHelpers.PerTypeValues<T>.ArrayAdjustment;
}

Expand Down Expand Up @@ -63,7 +63,7 @@ public ReadOnlySpan(T[]? array, int start, int length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);

_length = length;
_pinnable = Unsafe.As<Pinnable<T>>(array);
_pinnable = array;
_byteOffset = SpanHelpers.PerTypeValues<T>.ArrayAdjustment.Add<T>(start);
}

Expand Down Expand Up @@ -97,7 +97,7 @@ public unsafe ReadOnlySpan(void* pointer, int length)

// Constructor for internal use only.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ReadOnlySpan(Pinnable<T>? pinnable, IntPtr byteOffset, int length)
internal ReadOnlySpan(object? pinnable, IntPtr byteOffset, int length)
{
Debug.Assert(length >= 0);

Expand All @@ -122,10 +122,7 @@ public ref readonly T this[int index]
if ((uint)index >= ((uint)_length))
ThrowHelper.ThrowIndexOutOfRangeException();

if (_pinnable == null)
unsafe { return ref Unsafe.Add<T>(ref Unsafe.AsRef<T>(_byteOffset.ToPointer()), index); }
else
return ref Unsafe.Add<T>(ref Unsafe.AddByteOffset<T>(ref _pinnable.Data, _byteOffset), index);
return ref Unsafe.Add(ref DangerousGetPinnableReference(), index);
}
}

Expand All @@ -138,11 +135,7 @@ public unsafe ref readonly T GetPinnableReference()
{
if (_length != 0)
{
if (_pinnable == null)
{
return ref Unsafe.AsRef<T>(_byteOffset.ToPointer());
}
return ref Unsafe.AddByteOffset<T>(ref _pinnable.Data, _byteOffset);
return ref DangerousGetPinnableReference();
}
return ref Unsafe.AsRef<T>(null);
}
Expand Down Expand Up @@ -205,7 +198,7 @@ public override string ToString()
if (typeof(T) == typeof(char))
{
// If this wraps a string and represents the full length of the string, just return the wrapped string.
if (_byteOffset == MemoryExtensions.StringAdjustmentHolder.StringAdjustment)
if (_byteOffset == (nint)RuntimeHelpers.OffsetToStringData)
{
object? obj = Unsafe.As<object?>(_pinnable); // minimize chances the compilers will optimize away the 'is' check
if (obj is string str && _length == str.Length)
Expand Down Expand Up @@ -276,22 +269,15 @@ public T[] ToArray()
}

/// <summary>
/// This method is obsolete, use System.Runtime.InteropServices.MemoryMarshal.GetReference instead.
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[EditorBrowsable(EditorBrowsableState.Never)]
internal ref T DangerousGetPinnableReference()
{
if (_pinnable == null)
unsafe { return ref Unsafe.AsRef<T>(_byteOffset.ToPointer()); }
else
return ref Unsafe.AddByteOffset<T>(ref _pinnable.Data, _byteOffset);
}
internal ref T DangerousGetPinnableReference() =>
ref Unsafe.AddByteOffset(ref ILHelpers.ObjectAsRef<T>(_pinnable), _byteOffset);

// These expose the internal representation for Span-related apis use only.
internal Pinnable<T>? Pinnable => _pinnable;
internal object? Pinnable => _pinnable;
internal IntPtr ByteOffset => _byteOffset;

//
Expand All @@ -305,7 +291,7 @@ internal ref T DangerousGetPinnableReference()
// _pinnable = null
// _byteOffset = the pointer
//
private readonly Pinnable<T>? _pinnable;
private readonly object? _pinnable;
private readonly IntPtr _byteOffset;
private readonly int _length;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static Span<byte> AsBytes<T>(Span<T> span)
ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T));

int newLength = checked(span.Length * Unsafe.SizeOf<T>());
return new Span<byte>(Unsafe.As<Pinnable<byte>>(span.Pinnable), span.ByteOffset, newLength);
return new Span<byte>(span.Pinnable, span.ByteOffset, newLength);
}

/// <summary>
Expand All @@ -53,7 +53,7 @@ public static ReadOnlySpan<byte> AsBytes<T>(ReadOnlySpan<T> span)
ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T));

int newLength = checked(span.Length * Unsafe.SizeOf<T>());
return new ReadOnlySpan<byte>(Unsafe.As<Pinnable<byte>>(span.Pinnable), span.ByteOffset, newLength);
return new ReadOnlySpan<byte>(span.Pinnable, span.ByteOffset, newLength);
}

/// <summary>Creates a <see cref="Memory{T}"/> from a <see cref="ReadOnlyMemory{T}"/>.</summary>
Expand All @@ -72,25 +72,13 @@ public static Memory<T> AsMemory<T>(ReadOnlyMemory<T> memory) =>
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
public static ref T GetReference<T>(Span<T> span)
{
if (span.Pinnable == null)
unsafe { return ref Unsafe.AsRef<T>(span.ByteOffset.ToPointer()); }
else
return ref Unsafe.AddByteOffset<T>(ref span.Pinnable.Data, span.ByteOffset);
}
public static ref T GetReference<T>(Span<T> span) => ref span.DangerousGetPinnableReference();

/// <summary>
/// Returns a reference to the 0th element of the ReadOnlySpan. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
public static ref T GetReference<T>(ReadOnlySpan<T> span)
{
if (span.Pinnable == null)
unsafe { return ref Unsafe.AsRef<T>(span.ByteOffset.ToPointer()); }
else
return ref Unsafe.AddByteOffset<T>(ref span.Pinnable.Data, span.ByteOffset);
}
public static ref T GetReference<T>(ReadOnlySpan<T> span) => ref Unsafe.AsRef(in span.GetPinnableReference());

/// <summary>
/// Casts a Span of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>.
Expand All @@ -114,7 +102,7 @@ public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span)
ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(TTo));

int newLength = checked((int)((long)span.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()));
return new Span<TTo>(Unsafe.As<Pinnable<TTo>>(span.Pinnable), span.ByteOffset, newLength);
return new Span<TTo>(span.Pinnable, span.ByteOffset, newLength);
}

/// <summary>
Expand All @@ -139,7 +127,7 @@ public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span)
ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(TTo));

int newLength = checked((int)((long)span.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()));
return new ReadOnlySpan<TTo>(Unsafe.As<Pinnable<TTo>>(span.Pinnable), span.ByteOffset, newLength);
return new ReadOnlySpan<TTo>(span.Pinnable, span.ByteOffset, newLength);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public Span(T[]? array)
ThrowHelper.ThrowArrayTypeMismatchException();

_length = array.Length;
_pinnable = Unsafe.As<Pinnable<T>>(array);
_pinnable = array;
_byteOffset = SpanHelpers.PerTypeValues<T>.ArrayAdjustment;
}

Expand All @@ -58,7 +58,7 @@ internal static Span<T> Create(T[]? array, int start)

IntPtr byteOffset = SpanHelpers.PerTypeValues<T>.ArrayAdjustment.Add<T>(start);
int length = array.Length - start;
return new Span<T>(pinnable: Unsafe.As<Pinnable<T>>(array), byteOffset: byteOffset, length: length);
return new Span<T>(pinnable: array, byteOffset: byteOffset, length: length);
}

/// <summary>
Expand Down Expand Up @@ -89,7 +89,7 @@ public Span(T[]? array, int start, int length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);

_length = length;
_pinnable = Unsafe.As<Pinnable<T>>(array);
_pinnable = array;
_byteOffset = SpanHelpers.PerTypeValues<T>.ArrayAdjustment.Add<T>(start);
}

Expand Down Expand Up @@ -123,7 +123,7 @@ public unsafe Span(void* pointer, int length)

// Constructor for internal use only.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Span(Pinnable<T>? pinnable, IntPtr byteOffset, int length)
internal Span(object? pinnable, IntPtr byteOffset, int length)
{
Debug.Assert(length >= 0);

Expand All @@ -148,10 +148,7 @@ public ref T this[int index]
if ((uint)index >= ((uint)_length))
ThrowHelper.ThrowIndexOutOfRangeException();

if (_pinnable == null)
unsafe { return ref Unsafe.Add<T>(ref Unsafe.AsRef<T>(_byteOffset.ToPointer()), index); }
else
return ref Unsafe.Add<T>(ref Unsafe.AddByteOffset<T>(ref _pinnable.Data, _byteOffset), index);
return ref Unsafe.Add(ref DangerousGetPinnableReference(), index);
}
}

Expand All @@ -160,17 +157,14 @@ public ref T this[int index]
/// It can be used for pinning and is required to support the use of span within a fixed statement.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public unsafe ref T GetPinnableReference()
public ref T GetPinnableReference()
{
if (_length != 0)
{
if (_pinnable == null)
{
return ref Unsafe.AsRef<T>(_byteOffset.ToPointer());
}
return ref Unsafe.AddByteOffset<T>(ref _pinnable.Data, _byteOffset);
return ref DangerousGetPinnableReference();
}
return ref Unsafe.AsRef<T>(null);

return ref Unsafe.NullRef<T>();
}

/// <summary>
Expand All @@ -195,9 +189,7 @@ public unsafe void Clear()
}
else
{
ref byte b = ref Unsafe.As<T, byte>(ref Unsafe.AddByteOffset<T>(ref _pinnable.Data, _byteOffset));

SpanHelpers.ClearLessThanPointerSized(ref b, byteLength);
SpanHelpers.ClearLessThanPointerSized(ref Unsafe.As<T, byte>(ref DangerousGetPinnableReference()), byteLength);
}
}
else
Expand Down Expand Up @@ -232,15 +224,7 @@ public unsafe void Fill(T value)
if (Unsafe.SizeOf<T>() == 1)
{
byte fill = Unsafe.As<T, byte>(ref value);
if (_pinnable == null)
{
Unsafe.InitBlockUnaligned(_byteOffset.ToPointer(), fill, (uint)length);
}
else
{
ref byte r = ref Unsafe.As<T, byte>(ref Unsafe.AddByteOffset<T>(ref _pinnable.Data, _byteOffset));
Unsafe.InitBlockUnaligned(ref r, fill, (uint)length);
}
Unsafe.InitBlockUnaligned(ref Unsafe.As<T, byte>(ref DangerousGetPinnableReference()), fill, (uint)length);
}
else
{
Expand Down Expand Up @@ -401,22 +385,15 @@ public T[] ToArray()
}

/// <summary>
/// This method is obsolete, use System.Runtime.InteropServices.MemoryMarshal.GetReference instead.
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
/// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[EditorBrowsable(EditorBrowsableState.Never)]
internal ref T DangerousGetPinnableReference()
{
if (_pinnable == null)
unsafe { return ref Unsafe.AsRef<T>(_byteOffset.ToPointer()); }
else
return ref Unsafe.AddByteOffset<T>(ref _pinnable.Data, _byteOffset);
}
internal ref T DangerousGetPinnableReference() =>
ref Unsafe.AddByteOffset(ref ILHelpers.ObjectAsRef<T>(_pinnable), _byteOffset);

// These expose the internal representation for Span-related apis use only.
internal Pinnable<T>? Pinnable => _pinnable;
internal object? Pinnable => _pinnable;
internal IntPtr ByteOffset => _byteOffset;

//
Expand All @@ -430,7 +407,7 @@ internal ref T DangerousGetPinnableReference()
// _pinnable = null
// _byteOffset = the pointer
//
private readonly Pinnable<T>? _pinnable;
private readonly object? _pinnable;
private readonly IntPtr _byteOffset;
private readonly int _length;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ public static class PerTypeValues<T>
private static IntPtr MeasureArrayAdjustment()
{
T[] sampleArray = new T[1];
return Unsafe.ByteOffset<T>(ref Unsafe.As<Pinnable<T>>(sampleArray).Data, ref sampleArray[0]);
return Unsafe.ByteOffset<T>(ref ILHelpers.ObjectAsRef<T>(sampleArray), ref sampleArray[0]);
}
}
}
Expand Down
Loading

0 comments on commit 53fec67

Please sign in to comment.