This commit is contained in:
Miha Zupan 2025-07-30 14:38:23 +02:00 committed by GitHub
commit 4679ff76fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 104 additions and 175 deletions

View File

@ -48,10 +48,8 @@ namespace Microsoft.Extensions.Primitives
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public StringSegment(string buffer, int offset, int length)
{
// Validate arguments, check is minimal instructions with reduced branching for inlinable fast-path
// Negative values discovered though conversion to high values when converted to unsigned
// Failure should be rare and location determination and message is delegated to failure functions
if (buffer == null || (uint)offset > (uint)buffer.Length || (uint)length > (uint)(buffer.Length - offset))
if (buffer == null || !SliceArgumentsAreValid(buffer.Length, offset, length))
{
ThrowInvalidArguments(buffer, offset, length);
}
@ -128,12 +126,17 @@ namespace Microsoft.Extensions.Primitives
/// </exception>
public ReadOnlySpan<char> AsSpan(int start)
{
if (!HasValue || start < 0)
string? buffer = Buffer;
int offset = Offset + start;
int length = Length - start;
// Using the same validation as AsSpan(int, int) so that the compiler can delete the redundant validation.
if (buffer is null || start < 0 || !SliceArgumentsAreValid(buffer.Length, offset, length))
{
ThrowInvalidArguments(start, Length - start, ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
}
return Buffer.AsSpan(Offset + start, Length - start);
return buffer.AsSpan(offset, length);
}
/// <summary>
@ -150,7 +153,7 @@ namespace Microsoft.Extensions.Primitives
/// </exception>
public ReadOnlySpan<char> AsSpan(int start, int length)
{
if (!HasValue || start < 0 || length < 0 || (uint)(start + length) > (uint)Length)
if (!HasValue || !SliceArgumentsAreValid(Length, start, length))
{
ThrowInvalidArguments(start, length, ExceptionArgument.start);
}
@ -397,7 +400,7 @@ namespace Microsoft.Extensions.Primitives
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string Substring(int offset, int length)
{
if (!HasValue || offset < 0 || length < 0 || (uint)(offset + length) > (uint)Length)
if (!HasValue || !SliceArgumentsAreValid(Length, offset, length))
{
ThrowInvalidArguments(offset, length, ExceptionArgument.offset);
}
@ -430,7 +433,7 @@ namespace Microsoft.Extensions.Primitives
/// </exception>
public StringSegment Subsegment(int offset, int length)
{
if (!HasValue || offset < 0 || length < 0 || (uint)(offset + length) > (uint)Length)
if (!HasValue || !SliceArgumentsAreValid(Length, offset, length))
{
ThrowInvalidArguments(offset, length, ExceptionArgument.offset);
}
@ -671,6 +674,20 @@ namespace Microsoft.Extensions.Primitives
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool SliceArgumentsAreValid(int sourceLength, int start, int length)
{
// Copy of the internal MemoryExtensions.SliceArgumentsAreValid helper.
if (IntPtr.Size == 8)
{
return (ulong)(uint)start + (ulong)(uint)length <= (ulong)(uint)sourceLength;
}
else
{
return (uint)start <= (uint)sourceLength && (uint)length <= (uint)(sourceLength - start);
}
}
// Methods that do no return (i.e. throw) are not inlined
// https://github.com/dotnet/coreclr/pull/6103
[DoesNotReturn]

View File

@ -146,14 +146,14 @@ namespace System.MemoryTests
[MemberData(nameof(TestHelpers.StringSlice2ArgTestOutOfRangeData), MemberType = typeof(TestHelpers))]
public static unsafe void AsMemory_2Arg_OutOfRange(string text, int start)
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => text.AsMemory(start));
AssertExtensions.Throws<ArgumentOutOfRangeException>(null, () => text.AsMemory(start));
}
[Theory]
[MemberData(nameof(TestHelpers.StringSlice3ArgTestOutOfRangeData), MemberType = typeof(TestHelpers))]
public static unsafe void AsMemory_3Arg_OutOfRange(string text, int start, int length)
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => text.AsMemory(start, length));
AssertExtensions.Throws<ArgumentOutOfRangeException>(null, () => text.AsMemory(start, length));
}
[Fact]

View File

@ -103,10 +103,10 @@ namespace System.SpanTests
[MemberData(nameof(TestHelpers.StringSlice2ArgTestOutOfRangeData), MemberType = typeof(TestHelpers))]
public static unsafe void AsSpan_2Arg_OutOfRange(string text, int start)
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => text.AsSpan(start).DontBox());
AssertExtensions.Throws<ArgumentOutOfRangeException>(null, () => text.AsSpan(start).DontBox());
if (start >= 0)
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => text.AsSpan(new Index(start)).DontBox());
AssertExtensions.Throws<ArgumentOutOfRangeException>(null, () => text.AsSpan(new Index(start)).DontBox());
}
}
@ -114,10 +114,10 @@ namespace System.SpanTests
[MemberData(nameof(TestHelpers.StringSlice3ArgTestOutOfRangeData), MemberType = typeof(TestHelpers))]
public static unsafe void AsSpan_3Arg_OutOfRange(string text, int start, int length)
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => text.AsSpan(start, length).DontBox());
AssertExtensions.Throws<ArgumentOutOfRangeException>(null, () => text.AsSpan(start, length).DontBox());
if (start >= 0 && length >= 0 && start + length >= 0)
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => text.AsSpan(start..(start + length)).DontBox());
AssertExtensions.Throws<ArgumentOutOfRangeException>(null, () => text.AsSpan(start..(start + length)).DontBox());
}
}
}

View File

@ -53,10 +53,8 @@ namespace System
public ArraySegment(T[] array, int offset, int count)
{
// Validate arguments, check is minimal instructions with reduced branching for inlinable fast-path
// Negative values discovered though conversion to high values when converted to unsigned
// Failure should be rare and location determination and message is delegated to failure functions
if (array == null || (uint)offset > (uint)array.Length || (uint)count > (uint)(array.Length - offset))
if (array == null || !MemoryExtensions.SliceArgumentsAreValid(array.Length, offset, count))
ThrowHelper.ThrowArraySegmentCtorValidationFailedExceptions(array, offset, count);
_array = array;
@ -144,7 +142,7 @@ namespace System
{
ThrowInvalidOperationIfDefault();
if ((uint)index > (uint)_count || (uint)count > (uint)(_count - index))
if (!MemoryExtensions.SliceArgumentsAreValid(_count, index, count))
{
ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException();
}

View File

@ -41,6 +41,7 @@ namespace System
this = default;
return; // returns default
}
if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();
@ -59,6 +60,7 @@ namespace System
this = default;
return; // returns default
}
if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();
if ((uint)start > (uint)array.Length)
@ -91,16 +93,11 @@ namespace System
this = default;
return; // returns default
}
if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif
MemoryExtensions.ValidateSliceArguments(array.Length, start, length);
_object = array;
_index = start;
@ -226,7 +223,7 @@ namespace System
{
if ((uint)start > (uint)_length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
}
// It is expected for _index + start to be negative if the memory is already pre-pinned.
@ -244,14 +241,7 @@ namespace System
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Memory<T> Slice(int start, int length)
{
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif
MemoryExtensions.ValidateSliceArguments(_length, start, length);
// It is expected for _index + start to be negative if the memory is already pre-pinned.
return new Memory<T>(_object, _index + start, length);
@ -324,25 +314,12 @@ namespace System
// least to be in-bounds when compared with the original Memory<T> instance, so using the span won't
// AV the process.
// We use 'nuint' because it gives us a free early zero-extension to 64 bits when running on a 64-bit platform.
nuint desiredStartIndex = (uint)_index & (uint)ReadOnlyMemory<T>.RemoveFlagsBitMask;
int desiredStartIndex = _index & ReadOnlyMemory<T>.RemoveFlagsBitMask;
int desiredLength = _length;
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
#else
if ((uint)desiredStartIndex > (uint)lengthOfUnderlyingSpan || (uint)desiredLength > (uint)lengthOfUnderlyingSpan - (uint)desiredStartIndex)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
#endif
MemoryExtensions.ValidateSliceArguments(lengthOfUnderlyingSpan, desiredStartIndex, desiredLength);
refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex);
refToReturn = ref Unsafe.Add(ref refToReturn, (uint)desiredStartIndex);
lengthOfUnderlyingSpan = desiredLength;
}

View File

@ -19,6 +19,31 @@ namespace System
/// </summary>
public static partial class MemoryExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool SliceArgumentsAreValid(int sourceLength, int start, int length)
{
#if TARGET_64BIT
// Since start and length are both 32-bit, their sum can be computed across a 64-bit domain
// without loss of fidelity. The cast to uint before the cast to ulong ensures that the
// extension from 32- to 64-bit is zero-extending rather than sign-extending. The end result
// of this is that if either input is negative or if the input sum overflows past Int32.MaxValue,
// that information is captured correctly in the comparison against the source length.
// We don't use this same mechanism in a 32-bit process due to the overhead of 64-bit arithmetic.
return (ulong)(uint)start + (ulong)(uint)length <= (ulong)(uint)sourceLength;
#else
return (uint)start <= (uint)sourceLength && (uint)length <= (uint)(sourceLength - start);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ValidateSliceArguments(int sourceLength, int start, int length)
{
if (!SliceArgumentsAreValid(sourceLength, start, length))
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
}
/// <summary>
/// Creates a new span over the portion of the target array.
/// </summary>
@ -116,12 +141,12 @@ namespace System
if (text == null)
{
if (start != 0)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
return default;
}
if ((uint)start > (uint)text.Length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)start /* force zero-extension */), text.Length - start);
}
@ -137,7 +162,7 @@ namespace System
{
if (!startIndex.Equals(Index.Start))
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex);
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return default;
@ -146,7 +171,7 @@ namespace System
int actualIndex = startIndex.GetOffset(text.Length);
if ((uint)actualIndex > (uint)text.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex);
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)actualIndex /* force zero-extension */), text.Length - actualIndex);
@ -194,18 +219,11 @@ namespace System
if (text == null)
{
if (start != 0 || length != 0)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
return default;
}
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)text.Length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
#else
if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
#endif
ValidateSliceArguments(text.Length, start, length);
return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)start /* force zero-extension */), length);
}
@ -233,12 +251,12 @@ namespace System
if (text == null)
{
if (start != 0)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
return default;
}
if ((uint)start > (uint)text.Length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
return new ReadOnlyMemory<char>(text, start, text.Length - start);
}
@ -276,18 +294,11 @@ namespace System
if (text == null)
{
if (start != 0 || length != 0)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
return default;
}
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)text.Length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
#else
if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
#endif
ValidateSliceArguments(text.Length, start, length);
return new ReadOnlyMemory<char>(text, start, length);
}
@ -3739,7 +3750,7 @@ namespace System
public static Span<T> AsSpan<T>(this ArraySegment<T> segment, int start)
{
if (((uint)start) > (uint)segment.Count)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
return new Span<T>(segment.Array, segment.Offset + start, segment.Count - start);
}
@ -3772,10 +3783,7 @@ namespace System
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> AsSpan<T>(this ArraySegment<T> segment, int start, int length)
{
if (((uint)start) > (uint)segment.Count)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
if (((uint)length) > (uint)(segment.Count - start))
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
ValidateSliceArguments(segment.Count, start, length);
return new Span<T>(segment.Array, segment.Offset + start, length);
}
@ -3881,7 +3889,7 @@ namespace System
public static Memory<T> AsMemory<T>(this ArraySegment<T> segment, int start)
{
if (((uint)start) > (uint)segment.Count)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
return new Memory<T>(segment.Array, segment.Offset + start, segment.Count - start);
}
@ -3900,10 +3908,7 @@ namespace System
/// </exception>
public static Memory<T> AsMemory<T>(this ArraySegment<T> segment, int start, int length)
{
if (((uint)start) > (uint)segment.Count)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
if (((uint)length) > (uint)(segment.Count - start))
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
ValidateSliceArguments(segment.Count, start, length);
return new Memory<T>(segment.Array, segment.Offset + start, length);
}

View File

@ -128,7 +128,7 @@ namespace System
private static void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException("length");
throw new ArgumentOutOfRangeException();
}
}
}

View File

@ -71,14 +71,8 @@ namespace System
this = default;
return; // returns default
}
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif
MemoryExtensions.ValidateSliceArguments(array.Length, start, length);
_object = array;
_index = start;
@ -155,7 +149,7 @@ namespace System
{
if ((uint)start > (uint)_length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
ThrowHelper.ThrowArgumentOutOfRangeException();
}
// It is expected for _index + start to be negative if the memory is already pre-pinned.
@ -173,14 +167,7 @@ namespace System
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlyMemory<T> Slice(int start, int length)
{
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
#else
if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
#endif
MemoryExtensions.ValidateSliceArguments(_length, start, length);
// It is expected for _index + start to be negative if the memory is already pre-pinned.
return new ReadOnlyMemory<T>(_object, _index + start, length);
@ -246,25 +233,12 @@ namespace System
// least to be in-bounds when compared with the original Memory<T> instance, so using the span won't
// AV the process.
// We use 'nuint' because it gives us a free early zero-extension to 64 bits when running on a 64-bit platform.
nuint desiredStartIndex = (uint)_index & (uint)RemoveFlagsBitMask;
int desiredStartIndex = _index & RemoveFlagsBitMask;
int desiredLength = _length;
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
#else
if ((uint)desiredStartIndex > (uint)lengthOfUnderlyingSpan || (uint)desiredLength > (uint)lengthOfUnderlyingSpan - (uint)desiredStartIndex)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
#endif
MemoryExtensions.ValidateSliceArguments(lengthOfUnderlyingSpan, desiredStartIndex, desiredLength);
refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex);
refToReturn = ref Unsafe.Add(ref refToReturn, (uint)desiredStartIndex);
lengthOfUnderlyingSpan = desiredLength;
}

View File

@ -71,14 +71,8 @@ namespace System
this = default;
return; // returns default
}
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif
MemoryExtensions.ValidateSliceArguments(array.Length, start, length);
_reference = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), (nint)(uint)start /* force zero-extension */);
_length = length;
@ -387,14 +381,7 @@ namespace System
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<T> Slice(int start, int length)
{
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif
MemoryExtensions.ValidateSliceArguments(_length, start, length);
return new ReadOnlySpan<T>(ref Unsafe.Add(ref _reference, (nint)(uint)start /* force zero-extension */), length);
}

View File

@ -606,10 +606,11 @@ namespace System.Runtime.InteropServices
ThrowHelper.ThrowArgumentOutOfRangeException();
return default;
}
if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();
if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
MemoryExtensions.ValidateSliceArguments(array.Length, start, length);
// Before using _index, check if _index < 0, then 'and' it with RemoveFlagsBitMask
return new Memory<T>((object)array, start | (1 << 31), length);

View File

@ -45,6 +45,7 @@ namespace System
this = default;
return; // returns default
}
if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();
@ -74,16 +75,11 @@ namespace System
this = default;
return; // returns default
}
if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))
ThrowHelper.ThrowArrayTypeMismatchException();
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif
MemoryExtensions.ValidateSliceArguments(array.Length, start, length);
_reference = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), (nint)(uint)start /* force zero-extension */);
_length = length;
@ -411,19 +407,7 @@ namespace System
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> Slice(int start, int length)
{
#if TARGET_64BIT
// Since start and length are both 32-bit, their sum can be computed across a 64-bit domain
// without loss of fidelity. The cast to uint before the cast to ulong ensures that the
// extension from 32- to 64-bit is zero-extending rather than sign-extending. The end result
// of this is that if either input is negative or if the input sum overflows past Int32.MaxValue,
// that information is captured correctly in the comparison against the backing _length field.
// We don't use this same mechanism in a 32-bit process due to the overhead of 64-bit arithmetic.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif
MemoryExtensions.ValidateSliceArguments(_length, start, length);
return new Span<T>(ref Unsafe.Add(ref _reference, (nint)(uint)start /* force zero-extension */), length);
}

View File

@ -2237,12 +2237,7 @@ namespace System
public string Substring(int startIndex, int length)
{
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)startIndex + (ulong)(uint)length > (ulong)(uint)Length)
#else
if ((uint)startIndex > (uint)Length || (uint)length > (uint)(Length - startIndex))
#endif
if (!MemoryExtensions.SliceArgumentsAreValid(Length, startIndex, length))
{
ThrowSubstringArgumentOutOfRange(startIndex, length);
}

View File

@ -368,20 +368,11 @@ namespace System
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool TryGetSpan(int startIndex, int count, out ReadOnlySpan<char> slice)
{
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)startIndex + (ulong)(uint)count > (ulong)(uint)Length)
if (!MemoryExtensions.SliceArgumentsAreValid(Length, startIndex, count))
{
slice = default;
return false;
}
#else
if ((uint)startIndex > (uint)Length || (uint)count > (uint)(Length - startIndex))
{
slice = default;
return false;
}
#endif
slice = new ReadOnlySpan<char>(ref Unsafe.Add(ref _firstChar, (nint)(uint)startIndex /* force zero-extension */), count);
return true;