Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions src/libraries/Common/src/SourceGenerators/SourceWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,11 @@ private static ReadOnlySpan<char> GetNextLine(ref ReadOnlySpan<char> remainingTe
return next;
}

private static unsafe void AppendSpan(StringBuilder builder, ReadOnlySpan<char> span)
private static void AppendSpan(StringBuilder builder, ReadOnlySpan<char> span)
{
fixed (char* ptr = span)
foreach (char c in span)
{
builder.Append(ptr, span.Length);
builder.Append(c);
Comment thread
EgorBo marked this conversation as resolved.
Outdated
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace System.Text.Json
{
Expand Down Expand Up @@ -51,19 +52,13 @@ internal readonly struct DbRow

internal const int UnknownSize = -1;

#if DEBUG
static unsafe DbRow()
{
Debug.Assert(sizeof(DbRow) == Size);
Comment thread
eiriktsarpalis marked this conversation as resolved.
}
#endif

internal DbRow(JsonTokenType jsonTokenType, int location, int sizeOrLength)
{
Debug.Assert(jsonTokenType > JsonTokenType.None && jsonTokenType <= JsonTokenType.Null);
Debug.Assert((byte)jsonTokenType < 1 << 4);
Debug.Assert(location >= 0);
Debug.Assert(sizeOrLength >= UnknownSize);
Debug.Assert(Unsafe.SizeOf<DbRow>() == Size);
Comment thread
stephentoub marked this conversation as resolved.

_location = location;
_sizeOrLengthUnion = sizeOrLength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1476,7 +1476,10 @@ public bool ValueEquals(ReadOnlySpan<byte> utf8Text)
if (TokenType == JsonTokenType.Null)
{
// This is different than Length == 0, in that it tests true for null, but false for ""
return Unsafe.IsNullRef(ref MemoryMarshal.GetReference(utf8Text));
// TODO: Refactor as it is considered a bad practice to rely on null-ness of a Span._reference field.
Comment thread
stephentoub marked this conversation as resolved.
Outdated
#pragma warning disable CA2265
return utf8Text.Slice(0, 0) == default;
#pragma warning restore CA2265
}

return TextEqualsHelper(utf8Text, isPropertyName: false, shouldUnescape: true);
Expand Down Expand Up @@ -1504,7 +1507,10 @@ public bool ValueEquals(ReadOnlySpan<char> text)
if (TokenType == JsonTokenType.Null)
{
// This is different than Length == 0, in that it tests true for null, but false for ""
return Unsafe.IsNullRef(ref MemoryMarshal.GetReference(text));
// TODO: Refactor as it is considered a bad practice to rely on null-ness of a Span._reference field.
#pragma warning disable CA2265
return text.Slice(0, 0) == default;
#pragma warning restore CA2265
Comment thread
stephentoub marked this conversation as resolved.
}

return TextEqualsHelper(text, isPropertyName: false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,14 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
case JsonTokenType.Number when (_converterOptions & EnumConverterOptions.AllowNumbers) != 0:
switch (s_enumTypeCode)
{
case TypeCode.Int32 when reader.TryGetInt32(out int int32): return Unsafe.As<int, T>(ref int32);
case TypeCode.UInt32 when reader.TryGetUInt32(out uint uint32): return Unsafe.As<uint, T>(ref uint32);
case TypeCode.Int64 when reader.TryGetInt64(out long int64): return Unsafe.As<long, T>(ref int64);
case TypeCode.UInt64 when reader.TryGetUInt64(out ulong uint64): return Unsafe.As<ulong, T>(ref uint64);
case TypeCode.Byte when reader.TryGetByte(out byte ubyte8): return Unsafe.As<byte, T>(ref ubyte8);
case TypeCode.SByte when reader.TryGetSByte(out sbyte byte8): return Unsafe.As<sbyte, T>(ref byte8);
case TypeCode.Int16 when reader.TryGetInt16(out short int16): return Unsafe.As<short, T>(ref int16);
case TypeCode.UInt16 when reader.TryGetUInt16(out ushort uint16): return Unsafe.As<ushort, T>(ref uint16);
case TypeCode.Int32 when reader.TryGetInt32(out int int32): return (T)(object)int32;
case TypeCode.UInt32 when reader.TryGetUInt32(out uint uint32): return (T)(object)uint32;
case TypeCode.Int64 when reader.TryGetInt64(out long int64): return (T)(object)int64;
case TypeCode.UInt64 when reader.TryGetUInt64(out ulong uint64): return (T)(object)uint64;
case TypeCode.Byte when reader.TryGetByte(out byte ubyte8): return (T)(object)ubyte8;
case TypeCode.SByte when reader.TryGetSByte(out sbyte byte8): return (T)(object)byte8;
case TypeCode.Int16 when reader.TryGetInt16(out short int16): return (T)(object)int16;
case TypeCode.UInt16 when reader.TryGetUInt16(out ushort uint16): return (T)(object)uint16;
}
break;
}
Expand Down Expand Up @@ -350,51 +350,42 @@ private bool TryParseNamedEnum(

private static ulong ConvertToUInt64(T value)
{
switch (s_enumTypeCode)
{
case TypeCode.Int32 or TypeCode.UInt32: return Unsafe.As<T, uint>(ref value);
case TypeCode.Int64 or TypeCode.UInt64: return Unsafe.As<T, ulong>(ref value);
case TypeCode.Int16 or TypeCode.UInt16: return Unsafe.As<T, ushort>(ref value);
default:
Debug.Assert(s_enumTypeCode is TypeCode.SByte or TypeCode.Byte);
return Unsafe.As<T, byte>(ref value);
return s_enumTypeCode switch
{
TypeCode.Int32 => (ulong)(int)(object)value,
TypeCode.UInt32 => (uint)(object)value,
TypeCode.Int64 => (ulong)(long)(object)value,
TypeCode.UInt64 => (ulong)(object)value,
TypeCode.Int16 => (ulong)(short)(object)value,
TypeCode.UInt16 => (ushort)(object)value,
TypeCode.SByte => (ulong)(sbyte)(object)value,
_ => (byte)(object)value
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The previous code was asserting in the drain, and you're not. If a new TypeCode were to be introduced, is the exception that comes out of this cast going to be useful enough?

Copy link
Copy Markdown
Member Author

@EgorBo EgorBo Apr 2, 2025

Choose a reason for hiding this comment

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

The previous code was asserting in the drain, and you're not. If a new TypeCode were to be introduced, is the exception that comes out of this cast going to be useful enough?

if a new type code is added, the default will throw an InvalidCastException? we can only unbox byte out of a boxed byte-enum.
PS: unfortunately, C# does not support statemens in pattern-match switch

};
}

private static long ConvertToInt64(T value)
{
Debug.Assert(s_isSignedEnum);
switch (s_enumTypeCode)
{
case TypeCode.Int32: return Unsafe.As<T, int>(ref value);
case TypeCode.Int64: return Unsafe.As<T, long>(ref value);
case TypeCode.Int16: return Unsafe.As<T, short>(ref value);
default:
Debug.Assert(s_enumTypeCode is TypeCode.SByte);
return Unsafe.As<T, sbyte>(ref value);
return s_enumTypeCode switch
{
TypeCode.Int32 => (int)(object)value,
TypeCode.Int64 => (long)(object)value,
TypeCode.Int16 => (short)(object)value,
_ => (sbyte)(object)value,
Comment thread
stephentoub marked this conversation as resolved.
};
}

private static T ConvertFromUInt64(ulong value)
{
switch (s_enumTypeCode)
{
case TypeCode.Int32 or TypeCode.UInt32:
uint uintValue = (uint)value;
return Unsafe.As<uint, T>(ref uintValue);

case TypeCode.Int64 or TypeCode.UInt64:
ulong ulongValue = value;
return Unsafe.As<ulong, T>(ref ulongValue);

case TypeCode.Int16 or TypeCode.UInt16:
ushort ushortValue = (ushort)value;
return Unsafe.As<ushort, T>(ref ushortValue);

default:
Debug.Assert(s_enumTypeCode is TypeCode.SByte or TypeCode.Byte);
byte byteValue = (byte)value;
return Unsafe.As<byte, T>(ref byteValue);
return s_enumTypeCode switch
{
TypeCode.Int32 => (T)(object)(int)value,
TypeCode.UInt32 => (T)(object)(uint)value,
TypeCode.Int64 => (T)(object)(long)value,
TypeCode.UInt64 => (T)(object)value,
TypeCode.Int16 => (T)(object)(short)value,
TypeCode.UInt16 => (T)(object)(ushort)value,
TypeCode.SByte => (T)(object)(sbyte)value,
_ => (T)(object)(byte)value
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ internal Dictionary<string, JsonPropertyInfo> PropertyIndex
/// <summary>
/// Defines the core property lookup logic for a given unescaped UTF-8 encoded property name.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Comment thread
stephentoub marked this conversation as resolved.
internal JsonPropertyInfo? GetProperty(ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame, out byte[] utf8PropertyName)
{
Debug.Assert(IsConfigured);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,20 @@ public bool Equals(ReadOnlySpan<byte> propertyName, ulong key)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong GetKey(ReadOnlySpan<byte> name)
{
ref byte reference = ref MemoryMarshal.GetReference(name);
int length = name.Length;
ulong key = (ulong)(byte)length << 56;

switch (length)
key |= length switch
{
case 0: goto ComputedKey;
case 1: goto OddLength;
case 2: key |= Unsafe.ReadUnaligned<ushort>(ref reference); goto ComputedKey;
case 3: key |= Unsafe.ReadUnaligned<ushort>(ref reference); goto OddLength;
case 4: key |= Unsafe.ReadUnaligned<uint>(ref reference); goto ComputedKey;
case 5: key |= Unsafe.ReadUnaligned<uint>(ref reference); goto OddLength;
case 6: key |= Unsafe.ReadUnaligned<uint>(ref reference) | (ulong)Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref reference, 4)) << 32; goto ComputedKey;
case 7: key |= Unsafe.ReadUnaligned<uint>(ref reference) | (ulong)Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref reference, 4)) << 32; goto OddLength;
default: key |= Unsafe.ReadUnaligned<ulong>(ref reference) & 0x00ffffffffffffffL; goto ComputedKey;
}

OddLength:
int offset = length - 1;
key |= (ulong)Unsafe.Add(ref reference, offset) << (offset * 8);

ComputedKey:
0 => 0,
1 => name[0],
2 => MemoryMarshal.Read<ushort>(name),
3 => MemoryMarshal.Read<ushort>(name) | ((ulong)name[2] << 16),
4 => MemoryMarshal.Read<uint>(name),
5 => MemoryMarshal.Read<uint>(name) | ((ulong)name[4] << 32),
6 => MemoryMarshal.Read<uint>(name) | ((ulong)MemoryMarshal.Read<ushort>(name.Slice(4, 2)) << 32),
7 => MemoryMarshal.Read<uint>(name) | ((ulong)MemoryMarshal.Read<ushort>(name.Slice(4, 2)) << 32) | ((ulong)name[6] << 48),
_ => MemoryMarshal.Read<ulong>(name) & 0x00ffffffffffffffUL
};
#if DEBUG
// Verify key contains the embedded bytes as expected.
// Note: the expected properties do not hold true on big-endian platforms
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static int NeedsEscaping(ReadOnlySpan<byte> value, JavaScriptEncoder? enc
return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncodeUtf8(value);
}

public static unsafe int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncoder? encoder)
public static int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncoder? encoder)
{
// Some implementations of JavaScriptEncoder.FindFirstCharacterToEncode may not accept
// null pointers and guard against that. Hence, check up-front to return -1.
Expand All @@ -66,9 +66,14 @@ public static unsafe int NeedsEscaping(ReadOnlySpan<char> value, JavaScriptEncod
return -1;
}

fixed (char* ptr = value)
// Unfortunately, there is no public API for FindFirstCharacterToEncode(Span<char>) yet,
Comment thread
stephentoub marked this conversation as resolved.
// so we have to use the unsafe FindFirstCharacterToEncode(char*, int) instead.
unsafe
{
return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncode(ptr, value.Length);
fixed (char* ptr = value)
{
return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncode(ptr, value.Length);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ internal static void ValidateNumber(ReadOnlySpan<byte> utf8FormattedNumber)
private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
#endif

public static unsafe bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
public static bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
{
#if NET8_0_OR_GREATER
return Utf8.IsValid(bytes);
Expand All @@ -269,9 +269,12 @@ public static unsafe bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
#else
if (!bytes.IsEmpty)
{
fixed (byte* ptr = bytes)
unsafe
{
s_utf8Encoding.GetCharCount(ptr, bytes.Length);
fixed (byte* ptr = bytes)
{
s_utf8Encoding.GetCharCount(ptr, bytes.Length);
}
}
}
#endif
Expand All @@ -284,7 +287,7 @@ public static unsafe bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
#endif
}

internal static unsafe OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int written)
internal static OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int written)
{
#if NET
OperationStatus status = Utf8.FromUtf16(source, destination, out int charsRead, out written, replaceInvalidSequences: false, isFinalBlock: true);
Expand All @@ -297,10 +300,13 @@ internal static unsafe OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<by
{
if (!source.IsEmpty)
{
fixed (char* charPtr = source)
fixed (byte* destPtr = destination)
unsafe
{
written = s_utf8Encoding.GetBytes(charPtr, source.Length, destPtr, destination.Length);
fixed (char* charPtr = source)
fixed (byte* destPtr = destination)
{
written = s_utf8Encoding.GetBytes(charPtr, source.Length, destPtr, destination.Length);
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/libraries/System.Text.RegularExpressions/gen/Stubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ namespace System.Text
{
internal static class StringBuilderExtensions
{
public static unsafe StringBuilder Append(this StringBuilder stringBuilder, ReadOnlySpan<char> span)
public static StringBuilder Append(this StringBuilder stringBuilder, ReadOnlySpan<char> span)
{
fixed (char* ptr = &MemoryMarshal.GetReference(span))
foreach (char c in span)
{
return stringBuilder.Append(ptr, span.Length);
stringBuilder.Append(c);
Comment thread
stephentoub marked this conversation as resolved.
Outdated
}
return stringBuilder;
}

public static ReadOnlyMemory<char>[] GetChunks(this StringBuilder stringBuilder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,11 @@ private static RegexCharClass ParseRecursive(string charClass, int start)
public static string OneToStringClass(char c)
=> CharsToStringClass([c]);

internal static unsafe string CharsToStringClass(ReadOnlySpan<char> chars)
internal static
#if !NET
unsafe
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This method is safe for callers. Move this unsafe into the method so that it wraps the unsafe code only?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I ended up duplicating code since netfx needs unsafe context in two places

#endif
string CharsToStringClass(ReadOnlySpan<char> chars)
{
#if DEBUG
// Make sure they're all sorted with no duplicates
Expand Down Expand Up @@ -1594,18 +1598,20 @@ internal static unsafe string CharsToStringClass(ReadOnlySpan<char> chars)
ReadOnlySpan<char> tmpChars = chars; // avoid address exposing the span and impacting the other code in the method that uses it
return
#if NET
string
string.Create(SetStartIndex + count, tmpChars, static (span, chars) =>
#else
StringExtensions
StringExtensions.Create(SetStartIndex + count, (IntPtr)(&tmpChars), static (span, charsPtr) =>
#endif
.Create(SetStartIndex + count, (IntPtr)(&tmpChars), static (span, charsPtr) =>
{
// Fill in the set string
span[FlagsIndex] = (char)0;
span[SetLengthIndex] = (char)(span.Length - SetStartIndex);
span[CategoryLengthIndex] = (char)0;
int i = SetStartIndex;
foreach (char c in *(ReadOnlySpan<char>*)charsPtr)
#if !NET
ReadOnlySpan<char> chars = *(ReadOnlySpan<char>*)charsPtr;
#endif
foreach (char c in chars)
{
span[i++] = c;
if (c != LastChar)
Expand Down
Loading