Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 4 additions & 15 deletions src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,24 +183,13 @@ internal static unsafe void ConvertFixedToNative(int flags, string strManaged, I

internal static unsafe string ConvertFixedToManaged(IntPtr cstr, int length)
{
int allocSize = length + 2;
if (allocSize < length)
int end = SpanHelpers.IndexOf(ref *(byte*)cstr, 0, length);
if (end != -1)
{
throw new OutOfMemoryException();
length = end;
}
Span<sbyte> originalBuffer = new Span<sbyte>((byte*)cstr, length);

Span<sbyte> tempBuf = stackalloc sbyte[allocSize];

originalBuffer.CopyTo(tempBuf);
tempBuf[length - 1] = 0;
tempBuf[length] = 0;
tempBuf[length + 1] = 0;

fixed (sbyte* buffer = tempBuf)
{
return new string(buffer, 0, string.strlen((byte*)buffer));
}
return new string((sbyte*)cstr, 0, length);
}
} // class CSTRMarshaler

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -897,23 +897,13 @@ private static void FormatCurrency(ref ValueStringBuilder sb, ref NumberBuffer n
}
}

private static unsafe int wcslen(char* s)
{
int result = 0;
while (*s++ != '\0')
{
result++;
}
return result;
}

private static unsafe void FormatFixed(ref ValueStringBuilder sb, ref NumberBuffer number, int nMinDigits, int nMaxDigits, NumberFormatInfo info, int[]? groupDigits, string sDecimal, string? sGroup)
{
Debug.Assert(sGroup != null || groupDigits == null);

int digPos = number.scale;
char* dig = number.digits;
int digLength = wcslen(dig);
int digLength = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(dig).Length;

if (digPos > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,22 +475,15 @@ private unsafe void ProcessEvents(int numEvents,

static ParsedEvent ParseEvent(byte* nativeEventPath)
{
int byteCount = 0;
Debug.Assert(nativeEventPath != null);
byte* temp = nativeEventPath;

// Finds the position of null character.
while (*temp != 0)
{
temp++;
byteCount++;
}
ReadOnlySpan<byte> eventPath = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(nativeEventPath);
Debug.Assert(!eventPath.IsEmpty, "Empty events are not supported");

Debug.Assert(byteCount > 0, "Empty events are not supported");
char[] tempBuffer = ArrayPool<char>.Shared.Rent(Encoding.UTF8.GetMaxCharCount(byteCount));
char[] tempBuffer = ArrayPool<char>.Shared.Rent(Encoding.UTF8.GetMaxCharCount(eventPath.Length));

// Converting an array of bytes to UTF-8 char array
int charCount = Encoding.UTF8.GetChars(new ReadOnlySpan<byte>(nativeEventPath, byteCount), tempBuffer);
int charCount = Encoding.UTF8.GetChars(eventPath, tempBuffer);
return new ParsedEvent(tempBuffer.AsSpan(0, charCount), tempBuffer);
}

Expand Down
4 changes: 4 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@ public static partial class MemoryMarshal
public static System.Span<TTo> Cast<TFrom, TTo>(System.Span<TFrom> span) where TFrom : struct where TTo : struct { throw null; }
public static System.Memory<T> CreateFromPinnedArray<T>(T[]? array, int start, int length) { throw null; }
public static System.ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length) { throw null; }
[System.CLSCompliant(false)]
public static unsafe ReadOnlySpan<byte> CreateReadOnlySpanFromNullTerminated(byte* value) { throw null; }
[System.CLSCompliant(false)]
public static unsafe ReadOnlySpan<char> CreateReadOnlySpanFromNullTerminated(char* value) { throw null; }
public static System.Span<T> CreateSpan<T>(ref T reference, int length) { throw null; }
public static ref T GetArrayDataReference<T>(T[] array) { throw null; }
public static ref T GetReference<T>(System.ReadOnlySpan<T> span) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Xunit;
using System.Runtime.InteropServices;
using System.Buffers;
using Microsoft.DotNet.XUnitExtensions;

namespace System.SpanTests
{
public static partial class MemoryMarshalTests
{
[Fact]
public static unsafe void CreateReadOnlySpanFromNullTerminated_Char_Null()
{
Assert.True(MemoryMarshal.CreateReadOnlySpanFromNullTerminated((char*)null).IsEmpty);
Assert.True(MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)null).IsEmpty);
}

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(256)]
public static unsafe void CreateReadOnlySpanFromNullTerminated_Char(int expectedLength)
{
using BoundedMemory<char> data = BoundedMemory.Allocate<char>(expectedLength + 1);
data.Span.Fill('s');
data.Span[^1] = '\0';
data.MakeReadonly();

fixed (char* expectedPtr = data.Span)
{
ReadOnlySpan<char> actual = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(expectedPtr);
Assert.Equal(expectedLength, actual.Length);
fixed (char* actualPtr = &MemoryMarshal.GetReference(actual))
{
Assert.Equal((IntPtr)expectedPtr, (IntPtr)actualPtr);
}
}
}

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(256)]
public static unsafe void CreateReadOnlySpanFromNullTerminated_Byte(int expectedLength)
{
using BoundedMemory<byte> data = BoundedMemory.Allocate<byte>(expectedLength + 1);
data.Span.Fill(0xFF);
data.Span[^1] = 0;
data.MakeReadonly();

fixed (byte* expectedPtr = data.Span)
{
ReadOnlySpan<byte> actual = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(expectedPtr);
Assert.Equal(expectedLength, actual.Length);
fixed (byte* actualPtr = &MemoryMarshal.GetReference(actual))
{
Assert.Equal((IntPtr)expectedPtr, (IntPtr)actualPtr);
}
}
}

[OuterLoop]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.Is64BitProcess))]
public static unsafe void CreateReadOnlySpanFromNullTerminated_Char_ExceedsMaximum()
{
char* mem;
try
{
mem = (char*)Marshal.AllocHGlobal((IntPtr)(sizeof(char) * (2L + int.MaxValue)));
}
catch (OutOfMemoryException)
{
throw new SkipTestException("Unable to allocate 4GB of memory");
}

try
{
new Span<char>(mem, int.MaxValue).Fill('s');
*(mem + int.MaxValue) = 's';
*(mem + int.MaxValue + 1) = '\0';

Assert.Throws<ArgumentException>(() => MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mem));
}
finally
{
Marshal.FreeHGlobal((IntPtr)mem);
}
}

[OuterLoop]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.Is64BitProcess))]
public static unsafe void CreateReadOnlySpanFromNullTerminated_Byte_ExceedsMaximum()
{
byte* mem;
try
{
mem = (byte*)Marshal.AllocHGlobal((IntPtr)(2L + int.MaxValue));
}
catch (OutOfMemoryException)
{
throw new SkipTestException("Unable to allocate 2GB of memory");
}

try
{
new Span<byte>(mem, int.MaxValue).Fill(0xFF);
*(mem + int.MaxValue) = 0xFF;
*(mem + int.MaxValue + 1) = 0;

Assert.Throws<ArgumentException>(() => MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mem));
}
finally
{
Marshal.FreeHGlobal((IntPtr)mem);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<Compile Include="MemoryMarshal\AsReadOnlyRef.cs" />
<Compile Include="MemoryMarshal\CreateSpan.cs" />
<Compile Include="MemoryMarshal\CreateReadOnlySpan.cs" />
<Compile Include="MemoryMarshal\CreateReadOnlySpanFromNullTerminated.cs" />
<Compile Include="$(CommonPath)..\tests\System\RealFormatterTestsBase.cs"
Link="ParsersAndFormatters\Formatter\RealFormatterTestsBase.cs" />
<Compile Include="ParsersAndFormatters\Formatter\RealFormatterTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,12 @@ public static unsafe IDictionary GetEnvironmentVariables()
char* currentPtr = stringPtr;
while (true)
{
int variableLength = string.wcslen(currentPtr);
if (variableLength == 0)
ReadOnlySpan<char> variable = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(currentPtr);
if (variable.IsEmpty)
{
break;
}

var variable = new ReadOnlySpan<char>(currentPtr, variableLength);

// Find the = separating the key and value. We skip entries that begin with =. We also skip entries that don't
// have =, which can happen on some older OSes when the environment block gets corrupted.
int i = variable.IndexOf('=');
Expand All @@ -111,7 +109,7 @@ public static unsafe IDictionary GetEnvironmentVariables()
}

// Move to the end of this variable, after its terminator.
currentPtr += variableLength + 1;
currentPtr += variable.Length + 1;
}

return results;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ private static unsafe void EnumCalendarInfoCallback(char* calendarStringPtr, Int
{
try
{
var calendarStringSpan = new ReadOnlySpan<char>(calendarStringPtr, string.wcslen(calendarStringPtr));
ReadOnlySpan<char> calendarStringSpan = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(calendarStringPtr);
ref IcuEnumCalendarsData callbackContext = ref Unsafe.As<byte, IcuEnumCalendarsData>(ref *(byte*)context);

if (callbackContext.DisallowDuplicates)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,27 +211,49 @@ ref Unsafe.As<TFrom, TTo>(ref MemoryMarshal.GetReference(span)),
}

/// <summary>
/// Create a new span over a portion of a regular managed object. This can be useful
/// Creates a new span over a portion of a regular managed object. This can be useful
/// if part of a managed object represents a "fixed array." This is dangerous because the
/// <paramref name="length"/> is not checked.
/// </summary>
/// <param name="reference">A reference to data.</param>
/// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
/// <returns>The lifetime of the returned span will not be validated for safety by span-aware languages.</returns>
/// <returns>A span representing the specified reference and length.</returns>
/// <remarks>The lifetime of the returned span will not be validated for safety by span-aware languages.</remarks>
Comment on lines +220 to +221
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<T> CreateSpan<T>(ref T reference, int length) => new Span<T>(ref reference, length);

/// <summary>
/// Create a new read-only span over a portion of a regular managed object. This can be useful
/// Creates a new read-only span over a portion of a regular managed object. This can be useful
/// if part of a managed object represents a "fixed array." This is dangerous because the
/// <paramref name="length"/> is not checked.
/// </summary>
/// <param name="reference">A reference to data.</param>
/// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param>
/// <returns>The lifetime of the returned span will not be validated for safety by span-aware languages.</returns>
/// <returns>A read-only span representing the specified reference and length.</returns>
/// <remarks>The lifetime of the returned span will not be validated for safety by span-aware languages.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length) => new ReadOnlySpan<T>(ref reference, length);

/// <summary>Creates a new read-only span for a null-terminated string.</summary>
/// <param name="value">The pointer to the null-terminated string of characters.</param>
/// <returns>A read-only span representing the specified null-terminated string, or an empty span if the pointer is null.</returns>
/// <remarks>The returned span does not include the null terminator.</remarks>
/// <exception cref="ArgumentException">The string is longer than <see cref="int.MaxValue"/>.</exception>
[CLSCompliant(false)]
public static unsafe ReadOnlySpan<char> CreateReadOnlySpanFromNullTerminated(char* value) =>
value != null ? new ReadOnlySpan<char>(value, string.wcslen(value)) :
default;

/// <summary>Creates a new read-only span for a null-terminated UTF8 string.</summary>
/// <param name="value">The pointer to the null-terminated string of bytes.</param>
/// <returns>A read-only span representing the specified null-terminated string, or an empty span if the pointer is null.</returns>
/// <remarks>The returned span does not include the null terminator, nor does it validate the well-formedness of the UTF8 data.</remarks>
/// <exception cref="ArgumentException">The string is longer than <see cref="int.MaxValue"/>.</exception>
[CLSCompliant(false)]
public static unsafe ReadOnlySpan<byte> CreateReadOnlySpanFromNullTerminated(byte* value) =>
value != null ? new ReadOnlySpan<byte>(value, string.strlen(value)) :
default;

/// <summary>
/// Get an array segment from the underlying memory.
/// If unable to get the array segment, return false with a default array segment.
Expand Down