From f3b1496e2745aa41d13cca4d4b9b0908623a30b4 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 2 Apr 2025 16:25:15 +0200 Subject: [PATCH 1/6] Remove unsafe from STJ --- .../src/SourceGenerators/SourceWriter.cs | 6 +- .../Text/Json/Document/JsonDocument.DbRow.cs | 9 +-- .../System/Text/Json/Document/JsonElement.cs | 10 ++- .../Converters/Value/EnumConverter.cs | 77 ++++++++----------- .../Metadata/JsonTypeInfo.Cache.cs | 1 - .../Serialization/Metadata/PropertyRef.cs | 30 +++----- .../Json/Writer/JsonWriterHelper.Escaping.cs | 11 ++- .../Text/Json/Writer/JsonWriterHelper.cs | 20 +++-- .../gen/Stubs.cs | 7 +- .../Text/RegularExpressions/RegexCharClass.cs | 16 ++-- 10 files changed, 94 insertions(+), 93 deletions(-) diff --git a/src/libraries/Common/src/SourceGenerators/SourceWriter.cs b/src/libraries/Common/src/SourceGenerators/SourceWriter.cs index 0cc443d86fc3fd..0cfcafa680556d 100644 --- a/src/libraries/Common/src/SourceGenerators/SourceWriter.cs +++ b/src/libraries/Common/src/SourceGenerators/SourceWriter.cs @@ -110,11 +110,11 @@ private static ReadOnlySpan GetNextLine(ref ReadOnlySpan remainingTe return next; } - private static unsafe void AppendSpan(StringBuilder builder, ReadOnlySpan span) + private static void AppendSpan(StringBuilder builder, ReadOnlySpan span) { - fixed (char* ptr = span) + foreach (char c in span) { - builder.Append(ptr, span.Length); + builder.Append(c); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.DbRow.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.DbRow.cs index 1a391e3ebdc53f..8894abd7e71227 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.DbRow.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.DbRow.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace System.Text.Json { @@ -51,19 +52,13 @@ internal readonly struct DbRow internal const int UnknownSize = -1; -#if DEBUG - static unsafe DbRow() - { - Debug.Assert(sizeof(DbRow) == Size); - } -#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() == Size); _location = location; _sizeOrLengthUnion = sizeOrLength; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs index e71c9ae3aa9fe9..55a00292f28f08 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs @@ -1476,7 +1476,10 @@ public bool ValueEquals(ReadOnlySpan 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. +#pragma warning disable CA2265 + return utf8Text.Slice(0, 0) == default; +#pragma warning restore CA2265 } return TextEqualsHelper(utf8Text, isPropertyName: false, shouldUnescape: true); @@ -1504,7 +1507,10 @@ public bool ValueEquals(ReadOnlySpan 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 } return TextEqualsHelper(text, isPropertyName: false); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs index fa3186f0096995..e2b48f4ff93b14 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs @@ -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(ref int32); - case TypeCode.UInt32 when reader.TryGetUInt32(out uint uint32): return Unsafe.As(ref uint32); - case TypeCode.Int64 when reader.TryGetInt64(out long int64): return Unsafe.As(ref int64); - case TypeCode.UInt64 when reader.TryGetUInt64(out ulong uint64): return Unsafe.As(ref uint64); - case TypeCode.Byte when reader.TryGetByte(out byte ubyte8): return Unsafe.As(ref ubyte8); - case TypeCode.SByte when reader.TryGetSByte(out sbyte byte8): return Unsafe.As(ref byte8); - case TypeCode.Int16 when reader.TryGetInt16(out short int16): return Unsafe.As(ref int16); - case TypeCode.UInt16 when reader.TryGetUInt16(out ushort uint16): return Unsafe.As(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; } @@ -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(ref value); - case TypeCode.Int64 or TypeCode.UInt64: return Unsafe.As(ref value); - case TypeCode.Int16 or TypeCode.UInt16: return Unsafe.As(ref value); - default: - Debug.Assert(s_enumTypeCode is TypeCode.SByte or TypeCode.Byte); - return Unsafe.As(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 }; } private static long ConvertToInt64(T value) { - Debug.Assert(s_isSignedEnum); - switch (s_enumTypeCode) - { - case TypeCode.Int32: return Unsafe.As(ref value); - case TypeCode.Int64: return Unsafe.As(ref value); - case TypeCode.Int16: return Unsafe.As(ref value); - default: - Debug.Assert(s_enumTypeCode is TypeCode.SByte); - return Unsafe.As(ref value); + return s_enumTypeCode switch + { + TypeCode.Int32 => (int)(object)value, + TypeCode.Int64 => (long)(object)value, + TypeCode.Int16 => (short)(object)value, + _ => (sbyte)(object)value, }; } private static T ConvertFromUInt64(ulong value) { - switch (s_enumTypeCode) - { - case TypeCode.Int32 or TypeCode.UInt32: - uint uintValue = (uint)value; - return Unsafe.As(ref uintValue); - - case TypeCode.Int64 or TypeCode.UInt64: - ulong ulongValue = value; - return Unsafe.As(ref ulongValue); - - case TypeCode.Int16 or TypeCode.UInt16: - ushort ushortValue = (ushort)value; - return Unsafe.As(ref ushortValue); - - default: - Debug.Assert(s_enumTypeCode is TypeCode.SByte or TypeCode.Byte); - byte byteValue = (byte)value; - return Unsafe.As(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 }; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs index 13f0821f8b32d5..3fec42d3518c7e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs @@ -73,7 +73,6 @@ internal Dictionary PropertyIndex /// /// Defines the core property lookup logic for a given unescaped UTF-8 encoded property name. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal JsonPropertyInfo? GetProperty(ReadOnlySpan propertyName, ref ReadStackFrame frame, out byte[] utf8PropertyName) { Debug.Assert(IsConfigured); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs index d1f4c0afaf8dc9..aabcfdd2ee5f9f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs @@ -52,28 +52,20 @@ public bool Equals(ReadOnlySpan propertyName, ulong key) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong GetKey(ReadOnlySpan 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(ref reference); goto ComputedKey; - case 3: key |= Unsafe.ReadUnaligned(ref reference); goto OddLength; - case 4: key |= Unsafe.ReadUnaligned(ref reference); goto ComputedKey; - case 5: key |= Unsafe.ReadUnaligned(ref reference); goto OddLength; - case 6: key |= Unsafe.ReadUnaligned(ref reference) | (ulong)Unsafe.ReadUnaligned(ref Unsafe.Add(ref reference, 4)) << 32; goto ComputedKey; - case 7: key |= Unsafe.ReadUnaligned(ref reference) | (ulong)Unsafe.ReadUnaligned(ref Unsafe.Add(ref reference, 4)) << 32; goto OddLength; - default: key |= Unsafe.ReadUnaligned(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(name), + 3 => MemoryMarshal.Read(name) | ((ulong)name[2] << 16), + 4 => MemoryMarshal.Read(name), + 5 => MemoryMarshal.Read(name) | ((ulong)name[4] << 32), + 6 => MemoryMarshal.Read(name) | ((ulong)MemoryMarshal.Read(name.Slice(4, 2)) << 32), + 7 => MemoryMarshal.Read(name) | ((ulong)MemoryMarshal.Read(name.Slice(4, 2)) << 32) | ((ulong)name[6] << 48), + _ => MemoryMarshal.Read(name) & 0x00ffffffffffffffUL + }; #if DEBUG // Verify key contains the embedded bytes as expected. // Note: the expected properties do not hold true on big-endian platforms diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs index 3010e31cbd6fd8..cb2e6e1c81feeb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs @@ -57,7 +57,7 @@ public static int NeedsEscaping(ReadOnlySpan value, JavaScriptEncoder? enc return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncodeUtf8(value); } - public static unsafe int NeedsEscaping(ReadOnlySpan value, JavaScriptEncoder? encoder) + public static int NeedsEscaping(ReadOnlySpan value, JavaScriptEncoder? encoder) { // Some implementations of JavaScriptEncoder.FindFirstCharacterToEncode may not accept // null pointers and guard against that. Hence, check up-front to return -1. @@ -66,9 +66,14 @@ public static unsafe int NeedsEscaping(ReadOnlySpan value, JavaScriptEncod return -1; } - fixed (char* ptr = value) + // Unfortunately, there is no public API for FindFirstCharacterToEncode(Span) yet, + // 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); + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs index b2e05f589fc10c..8844132888beee 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs @@ -257,7 +257,7 @@ internal static void ValidateNumber(ReadOnlySpan utf8FormattedNumber) private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); #endif - public static unsafe bool IsValidUtf8String(ReadOnlySpan bytes) + public static bool IsValidUtf8String(ReadOnlySpan bytes) { #if NET8_0_OR_GREATER return Utf8.IsValid(bytes); @@ -269,9 +269,12 @@ public static unsafe bool IsValidUtf8String(ReadOnlySpan 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 @@ -284,7 +287,7 @@ public static unsafe bool IsValidUtf8String(ReadOnlySpan bytes) #endif } - internal static unsafe OperationStatus ToUtf8(ReadOnlySpan source, Span destination, out int written) + internal static OperationStatus ToUtf8(ReadOnlySpan source, Span destination, out int written) { #if NET OperationStatus status = Utf8.FromUtf16(source, destination, out int charsRead, out written, replaceInvalidSequences: false, isFinalBlock: true); @@ -297,10 +300,13 @@ internal static unsafe OperationStatus ToUtf8(ReadOnlySpan source, Span span) + public static StringBuilder Append(this StringBuilder stringBuilder, ReadOnlySpan span) { - fixed (char* ptr = &MemoryMarshal.GetReference(span)) + foreach (char c in span) { - return stringBuilder.Append(ptr, span.Length); + stringBuilder.Append(c); } + return stringBuilder; } public static ReadOnlyMemory[] GetChunks(this StringBuilder stringBuilder) diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs index 8a4acef3785be3..8946c204953814 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs @@ -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 chars) + internal static +#if !NET + unsafe +#endif + string CharsToStringClass(ReadOnlySpan chars) { #if DEBUG // Make sure they're all sorted with no duplicates @@ -1594,18 +1598,20 @@ internal static unsafe string CharsToStringClass(ReadOnlySpan chars) ReadOnlySpan 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*)charsPtr) +#if !NET + ReadOnlySpan chars = *(ReadOnlySpan*)charsPtr; +#endif + foreach (char c in chars) { span[i++] = c; if (c != LastChar) From 6d080560a6da8de158a7773c94f3d1daabd6d58f Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Wed, 9 Apr 2025 10:06:19 +0300 Subject: [PATCH 2/6] revert changes in SourceWriter --- src/libraries/Common/src/SourceGenerators/SourceWriter.cs | 8 ++++++-- src/libraries/System.Text.RegularExpressions/gen/Stubs.cs | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/src/SourceGenerators/SourceWriter.cs b/src/libraries/Common/src/SourceGenerators/SourceWriter.cs index 0cfcafa680556d..ed98cc44f2153d 100644 --- a/src/libraries/Common/src/SourceGenerators/SourceWriter.cs +++ b/src/libraries/Common/src/SourceGenerators/SourceWriter.cs @@ -112,9 +112,13 @@ private static ReadOnlySpan GetNextLine(ref ReadOnlySpan remainingTe private static void AppendSpan(StringBuilder builder, ReadOnlySpan span) { - foreach (char c in span) + // There is no StringBuilder.Append(ROS) overload in the NS2.0 + unsafe { - builder.Append(c); + fixed (char* ptr = span) + { + builder.Append(ptr, span.Length); + } } } } diff --git a/src/libraries/System.Text.RegularExpressions/gen/Stubs.cs b/src/libraries/System.Text.RegularExpressions/gen/Stubs.cs index 20cb98341da492..4ffbb64addd1b2 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Stubs.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/Stubs.cs @@ -16,6 +16,7 @@ internal static class StringBuilderExtensions { public static StringBuilder Append(this StringBuilder stringBuilder, ReadOnlySpan span) { + stringBuilder.EnsureCapacity(span.Length); foreach (char c in span) { stringBuilder.Append(c); From afaad683d2d3aa3955460d10d2e1bee04f5ed809 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 9 Apr 2025 17:50:50 +0300 Subject: [PATCH 3/6] feedback --- .../Common/src/SourceGenerators/SourceWriter.cs | 11 ++++------- .../src/System/Text/Json/Document/JsonElement.cs | 2 -- .../Serialization/Converters/Value/EnumConverter.cs | 1 + .../System.Text.RegularExpressions/gen/Stubs.cs | 9 ++++----- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/libraries/Common/src/SourceGenerators/SourceWriter.cs b/src/libraries/Common/src/SourceGenerators/SourceWriter.cs index ed98cc44f2153d..d1307be214f1c1 100644 --- a/src/libraries/Common/src/SourceGenerators/SourceWriter.cs +++ b/src/libraries/Common/src/SourceGenerators/SourceWriter.cs @@ -110,15 +110,12 @@ private static ReadOnlySpan GetNextLine(ref ReadOnlySpan remainingTe return next; } - private static void AppendSpan(StringBuilder builder, ReadOnlySpan span) + private static unsafe void AppendSpan(StringBuilder builder, ReadOnlySpan span) { - // There is no StringBuilder.Append(ROS) overload in the NS2.0 - unsafe + // There is no StringBuilder.Append(ReadOnlySpan) overload in the NS2.0 + fixed (char* ptr = span) { - fixed (char* ptr = span) - { - builder.Append(ptr, span.Length); - } + builder.Append(ptr, span.Length); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs index 55a00292f28f08..ad561c5dbff392 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs @@ -1476,7 +1476,6 @@ public bool ValueEquals(ReadOnlySpan utf8Text) if (TokenType == JsonTokenType.Null) { // This is different than Length == 0, in that it tests true for null, but false for "" - // TODO: Refactor as it is considered a bad practice to rely on null-ness of a Span._reference field. #pragma warning disable CA2265 return utf8Text.Slice(0, 0) == default; #pragma warning restore CA2265 @@ -1507,7 +1506,6 @@ public bool ValueEquals(ReadOnlySpan text) if (TokenType == JsonTokenType.Null) { // This is different than Length == 0, in that it tests true for null, but false for "" - // 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 diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs index e2b48f4ff93b14..d6e1d6cbd3a301 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs @@ -365,6 +365,7 @@ private static ulong ConvertToUInt64(T value) private static long ConvertToInt64(T value) { + Debug.Assert(s_isSignedEnum); return s_enumTypeCode switch { TypeCode.Int32 => (int)(object)value, diff --git a/src/libraries/System.Text.RegularExpressions/gen/Stubs.cs b/src/libraries/System.Text.RegularExpressions/gen/Stubs.cs index 4ffbb64addd1b2..e066210a20674a 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/Stubs.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/Stubs.cs @@ -14,14 +14,13 @@ namespace System.Text { internal static class StringBuilderExtensions { - public static StringBuilder Append(this StringBuilder stringBuilder, ReadOnlySpan span) + public static unsafe StringBuilder Append(this StringBuilder stringBuilder, ReadOnlySpan span) { - stringBuilder.EnsureCapacity(span.Length); - foreach (char c in span) + // There is no StringBuilder.Append(ReadOnlySpan) overload in the NS2.0 + fixed (char* ptr = &MemoryMarshal.GetReference(span)) { - stringBuilder.Append(c); + return stringBuilder.Append(ptr, span.Length); } - return stringBuilder; } public static ReadOnlyMemory[] GetChunks(this StringBuilder stringBuilder) From 4b667841353ec94e41e311f6eed0d784ce1526c6 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 9 Apr 2025 18:00:14 +0300 Subject: [PATCH 4/6] fb --- .../Text/RegularExpressions/RegexCharClass.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs index 8946c204953814..48275834da6dbd 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs @@ -1543,11 +1543,7 @@ private static RegexCharClass ParseRecursive(string charClass, int start) public static string OneToStringClass(char c) => CharsToStringClass([c]); - internal static -#if !NET - unsafe -#endif - string CharsToStringClass(ReadOnlySpan chars) + internal static string CharsToStringClass(ReadOnlySpan chars) { #if DEBUG // Make sure they're all sorted with no duplicates @@ -1596,11 +1592,16 @@ string CharsToStringClass(ReadOnlySpan chars) // Get the pointer/length of the span to be able to pass it into string.Create. ReadOnlySpan tmpChars = chars; // avoid address exposing the span and impacting the other code in the method that uses it - return + #if NET - string.Create(SetStartIndex + count, tmpChars, static (span, chars) => + return string.Create(SetStartIndex + count, tmpChars, static (span, chars) => #else - StringExtensions.Create(SetStartIndex + count, (IntPtr)(&tmpChars), static (span, charsPtr) => + IntPtr pTmpChars; + unsafe + { + pTmpChars = &tmpChars; + } + return StringExtensions.Create(SetStartIndex + count, pTmpChars, static (span, charsPtr) => #endif { // Fill in the set string From e7345c8f838b507b5d11a7df566fbfdb1565af63 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 9 Apr 2025 18:25:07 +0300 Subject: [PATCH 5/6] FB --- .../src/System/Text/RegularExpressions/RegexCharClass.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs index 48275834da6dbd..c9aede953c9437 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs @@ -1599,7 +1599,7 @@ internal static string CharsToStringClass(ReadOnlySpan chars) IntPtr pTmpChars; unsafe { - pTmpChars = &tmpChars; + pTmpChars = (IntPtr)(&tmpChars); } return StringExtensions.Create(SetStartIndex + count, pTmpChars, static (span, charsPtr) => #endif @@ -1610,7 +1610,11 @@ internal static string CharsToStringClass(ReadOnlySpan chars) span[CategoryLengthIndex] = (char)0; int i = SetStartIndex; #if !NET - ReadOnlySpan chars = *(ReadOnlySpan*)charsPtr; + ReadOnlySpan chars; + unsafe + { + chars = *(ReadOnlySpan*)charsPtr; + } #endif foreach (char c in chars) { From 872135068b42e354153190aad92f5921ea338a03 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 9 Apr 2025 18:28:46 +0300 Subject: [PATCH 6/6] cleanup --- .../Text/RegularExpressions/RegexCharClass.cs | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs index c9aede953c9437..a2c5e98e0ed3fb 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs @@ -1592,30 +1592,14 @@ internal static string CharsToStringClass(ReadOnlySpan chars) // Get the pointer/length of the span to be able to pass it into string.Create. ReadOnlySpan tmpChars = chars; // avoid address exposing the span and impacting the other code in the method that uses it - #if NET return string.Create(SetStartIndex + count, tmpChars, static (span, chars) => -#else - IntPtr pTmpChars; - unsafe - { - pTmpChars = (IntPtr)(&tmpChars); - } - return StringExtensions.Create(SetStartIndex + count, pTmpChars, static (span, charsPtr) => -#endif { // Fill in the set string span[FlagsIndex] = (char)0; span[SetLengthIndex] = (char)(span.Length - SetStartIndex); span[CategoryLengthIndex] = (char)0; int i = SetStartIndex; -#if !NET - ReadOnlySpan chars; - unsafe - { - chars = *(ReadOnlySpan*)charsPtr; - } -#endif foreach (char c in chars) { span[i++] = c; @@ -1626,8 +1610,32 @@ internal static string CharsToStringClass(ReadOnlySpan chars) } Debug.Assert(i == span.Length); }); +#else + unsafe + { + return StringExtensions.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; + ReadOnlySpan chars = *(ReadOnlySpan*)charsPtr; + foreach (char c in chars) + { + span[i++] = c; + if (c != LastChar) + { + span[i++] = (char)(c + 1); + } + } + Debug.Assert(i == span.Length); + }); + } +#endif } + /// /// Constructs the string representation of the class. ///