Skip to content

Commit aa458fa

Browse files
authored
Reduce unsafe from STJ (#114154)
1 parent ee9442d commit aa458fa

File tree

10 files changed

+103
-89
lines changed

10 files changed

+103
-89
lines changed

src/libraries/Common/src/SourceGenerators/SourceWriter.cs

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ private static ReadOnlySpan<char> GetNextLine(ref ReadOnlySpan<char> remainingTe
112112

113113
private static unsafe void AppendSpan(StringBuilder builder, ReadOnlySpan<char> span)
114114
{
115+
// There is no StringBuilder.Append(ReadOnlySpan<char>) overload in the NS2.0
115116
fixed (char* ptr = span)
116117
{
117118
builder.Append(ptr, span.Length);

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonDocument.DbRow.cs

+2-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics;
55
using System.Runtime.InteropServices;
6+
using System.Runtime.CompilerServices;
67

78
namespace System.Text.Json
89
{
@@ -51,19 +52,13 @@ internal readonly struct DbRow
5152

5253
internal const int UnknownSize = -1;
5354

54-
#if DEBUG
55-
static unsafe DbRow()
56-
{
57-
Debug.Assert(sizeof(DbRow) == Size);
58-
}
59-
#endif
60-
6155
internal DbRow(JsonTokenType jsonTokenType, int location, int sizeOrLength)
6256
{
6357
Debug.Assert(jsonTokenType > JsonTokenType.None && jsonTokenType <= JsonTokenType.Null);
6458
Debug.Assert((byte)jsonTokenType < 1 << 4);
6559
Debug.Assert(location >= 0);
6660
Debug.Assert(sizeOrLength >= UnknownSize);
61+
Debug.Assert(Unsafe.SizeOf<DbRow>() == Size);
6762

6863
_location = location;
6964
_sizeOrLengthUnion = sizeOrLength;

src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -1476,7 +1476,9 @@ public bool ValueEquals(ReadOnlySpan<byte> utf8Text)
14761476
if (TokenType == JsonTokenType.Null)
14771477
{
14781478
// This is different than Length == 0, in that it tests true for null, but false for ""
1479-
return Unsafe.IsNullRef(ref MemoryMarshal.GetReference(utf8Text));
1479+
#pragma warning disable CA2265
1480+
return utf8Text.Slice(0, 0) == default;
1481+
#pragma warning restore CA2265
14801482
}
14811483

14821484
return TextEqualsHelper(utf8Text, isPropertyName: false, shouldUnescape: true);
@@ -1504,7 +1506,9 @@ public bool ValueEquals(ReadOnlySpan<char> text)
15041506
if (TokenType == JsonTokenType.Null)
15051507
{
15061508
// This is different than Length == 0, in that it tests true for null, but false for ""
1507-
return Unsafe.IsNullRef(ref MemoryMarshal.GetReference(text));
1509+
#pragma warning disable CA2265
1510+
return text.Slice(0, 0) == default;
1511+
#pragma warning restore CA2265
15081512
}
15091513

15101514
return TextEqualsHelper(text, isPropertyName: false);

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs

+34-42
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,14 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
113113
case JsonTokenType.Number when (_converterOptions & EnumConverterOptions.AllowNumbers) != 0:
114114
switch (s_enumTypeCode)
115115
{
116-
case TypeCode.Int32 when reader.TryGetInt32(out int int32): return Unsafe.As<int, T>(ref int32);
117-
case TypeCode.UInt32 when reader.TryGetUInt32(out uint uint32): return Unsafe.As<uint, T>(ref uint32);
118-
case TypeCode.Int64 when reader.TryGetInt64(out long int64): return Unsafe.As<long, T>(ref int64);
119-
case TypeCode.UInt64 when reader.TryGetUInt64(out ulong uint64): return Unsafe.As<ulong, T>(ref uint64);
120-
case TypeCode.Byte when reader.TryGetByte(out byte ubyte8): return Unsafe.As<byte, T>(ref ubyte8);
121-
case TypeCode.SByte when reader.TryGetSByte(out sbyte byte8): return Unsafe.As<sbyte, T>(ref byte8);
122-
case TypeCode.Int16 when reader.TryGetInt16(out short int16): return Unsafe.As<short, T>(ref int16);
123-
case TypeCode.UInt16 when reader.TryGetUInt16(out ushort uint16): return Unsafe.As<ushort, T>(ref uint16);
116+
case TypeCode.Int32 when reader.TryGetInt32(out int int32): return (T)(object)int32;
117+
case TypeCode.UInt32 when reader.TryGetUInt32(out uint uint32): return (T)(object)uint32;
118+
case TypeCode.Int64 when reader.TryGetInt64(out long int64): return (T)(object)int64;
119+
case TypeCode.UInt64 when reader.TryGetUInt64(out ulong uint64): return (T)(object)uint64;
120+
case TypeCode.Byte when reader.TryGetByte(out byte ubyte8): return (T)(object)ubyte8;
121+
case TypeCode.SByte when reader.TryGetSByte(out sbyte byte8): return (T)(object)byte8;
122+
case TypeCode.Int16 when reader.TryGetInt16(out short int16): return (T)(object)int16;
123+
case TypeCode.UInt16 when reader.TryGetUInt16(out ushort uint16): return (T)(object)uint16;
124124
}
125125
break;
126126
}
@@ -350,51 +350,43 @@ private bool TryParseNamedEnum(
350350

351351
private static ulong ConvertToUInt64(T value)
352352
{
353-
switch (s_enumTypeCode)
354-
{
355-
case TypeCode.Int32 or TypeCode.UInt32: return Unsafe.As<T, uint>(ref value);
356-
case TypeCode.Int64 or TypeCode.UInt64: return Unsafe.As<T, ulong>(ref value);
357-
case TypeCode.Int16 or TypeCode.UInt16: return Unsafe.As<T, ushort>(ref value);
358-
default:
359-
Debug.Assert(s_enumTypeCode is TypeCode.SByte or TypeCode.Byte);
360-
return Unsafe.As<T, byte>(ref value);
353+
return s_enumTypeCode switch
354+
{
355+
TypeCode.Int32 => (ulong)(int)(object)value,
356+
TypeCode.UInt32 => (uint)(object)value,
357+
TypeCode.Int64 => (ulong)(long)(object)value,
358+
TypeCode.UInt64 => (ulong)(object)value,
359+
TypeCode.Int16 => (ulong)(short)(object)value,
360+
TypeCode.UInt16 => (ushort)(object)value,
361+
TypeCode.SByte => (ulong)(sbyte)(object)value,
362+
_ => (byte)(object)value
361363
};
362364
}
363365

364366
private static long ConvertToInt64(T value)
365367
{
366368
Debug.Assert(s_isSignedEnum);
367-
switch (s_enumTypeCode)
368-
{
369-
case TypeCode.Int32: return Unsafe.As<T, int>(ref value);
370-
case TypeCode.Int64: return Unsafe.As<T, long>(ref value);
371-
case TypeCode.Int16: return Unsafe.As<T, short>(ref value);
372-
default:
373-
Debug.Assert(s_enumTypeCode is TypeCode.SByte);
374-
return Unsafe.As<T, sbyte>(ref value);
369+
return s_enumTypeCode switch
370+
{
371+
TypeCode.Int32 => (int)(object)value,
372+
TypeCode.Int64 => (long)(object)value,
373+
TypeCode.Int16 => (short)(object)value,
374+
_ => (sbyte)(object)value,
375375
};
376376
}
377377

378378
private static T ConvertFromUInt64(ulong value)
379379
{
380-
switch (s_enumTypeCode)
381-
{
382-
case TypeCode.Int32 or TypeCode.UInt32:
383-
uint uintValue = (uint)value;
384-
return Unsafe.As<uint, T>(ref uintValue);
385-
386-
case TypeCode.Int64 or TypeCode.UInt64:
387-
ulong ulongValue = value;
388-
return Unsafe.As<ulong, T>(ref ulongValue);
389-
390-
case TypeCode.Int16 or TypeCode.UInt16:
391-
ushort ushortValue = (ushort)value;
392-
return Unsafe.As<ushort, T>(ref ushortValue);
393-
394-
default:
395-
Debug.Assert(s_enumTypeCode is TypeCode.SByte or TypeCode.Byte);
396-
byte byteValue = (byte)value;
397-
return Unsafe.As<byte, T>(ref byteValue);
380+
return s_enumTypeCode switch
381+
{
382+
TypeCode.Int32 => (T)(object)(int)value,
383+
TypeCode.UInt32 => (T)(object)(uint)value,
384+
TypeCode.Int64 => (T)(object)(long)value,
385+
TypeCode.UInt64 => (T)(object)value,
386+
TypeCode.Int16 => (T)(object)(short)value,
387+
TypeCode.UInt16 => (T)(object)(ushort)value,
388+
TypeCode.SByte => (T)(object)(sbyte)value,
389+
_ => (T)(object)(byte)value
398390
};
399391
}
400392

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs

-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ internal Dictionary<string, JsonPropertyInfo> PropertyIndex
7373
/// <summary>
7474
/// Defines the core property lookup logic for a given unescaped UTF-8 encoded property name.
7575
/// </summary>
76-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7776
internal JsonPropertyInfo? GetProperty(ReadOnlySpan<byte> propertyName, ref ReadStackFrame frame, out byte[] utf8PropertyName)
7877
{
7978
Debug.Assert(IsConfigured);

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/PropertyRef.cs

+11-19
Original file line numberDiff line numberDiff line change
@@ -52,28 +52,20 @@ public bool Equals(ReadOnlySpan<byte> propertyName, ulong key)
5252
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5353
public static ulong GetKey(ReadOnlySpan<byte> name)
5454
{
55-
ref byte reference = ref MemoryMarshal.GetReference(name);
5655
int length = name.Length;
5756
ulong key = (ulong)(byte)length << 56;
58-
59-
switch (length)
57+
key |= length switch
6058
{
61-
case 0: goto ComputedKey;
62-
case 1: goto OddLength;
63-
case 2: key |= Unsafe.ReadUnaligned<ushort>(ref reference); goto ComputedKey;
64-
case 3: key |= Unsafe.ReadUnaligned<ushort>(ref reference); goto OddLength;
65-
case 4: key |= Unsafe.ReadUnaligned<uint>(ref reference); goto ComputedKey;
66-
case 5: key |= Unsafe.ReadUnaligned<uint>(ref reference); goto OddLength;
67-
case 6: key |= Unsafe.ReadUnaligned<uint>(ref reference) | (ulong)Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref reference, 4)) << 32; goto ComputedKey;
68-
case 7: key |= Unsafe.ReadUnaligned<uint>(ref reference) | (ulong)Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref reference, 4)) << 32; goto OddLength;
69-
default: key |= Unsafe.ReadUnaligned<ulong>(ref reference) & 0x00ffffffffffffffL; goto ComputedKey;
70-
}
71-
72-
OddLength:
73-
int offset = length - 1;
74-
key |= (ulong)Unsafe.Add(ref reference, offset) << (offset * 8);
75-
76-
ComputedKey:
59+
0 => 0,
60+
1 => name[0],
61+
2 => MemoryMarshal.Read<ushort>(name),
62+
3 => MemoryMarshal.Read<ushort>(name) | ((ulong)name[2] << 16),
63+
4 => MemoryMarshal.Read<uint>(name),
64+
5 => MemoryMarshal.Read<uint>(name) | ((ulong)name[4] << 32),
65+
6 => MemoryMarshal.Read<uint>(name) | ((ulong)MemoryMarshal.Read<ushort>(name.Slice(4, 2)) << 32),
66+
7 => MemoryMarshal.Read<uint>(name) | ((ulong)MemoryMarshal.Read<ushort>(name.Slice(4, 2)) << 32) | ((ulong)name[6] << 48),
67+
_ => MemoryMarshal.Read<ulong>(name) & 0x00ffffffffffffffUL
68+
};
7769
#if DEBUG
7870
// Verify key contains the embedded bytes as expected.
7971
// Note: the expected properties do not hold true on big-endian platforms

src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static int NeedsEscaping(ReadOnlySpan<byte> value, JavaScriptEncoder? enc
5757
return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncodeUtf8(value);
5858
}
5959

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

69-
fixed (char* ptr = value)
69+
// Unfortunately, there is no public API for FindFirstCharacterToEncode(Span<char>) yet,
70+
// so we have to use the unsafe FindFirstCharacterToEncode(char*, int) instead.
71+
unsafe
7072
{
71-
return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncode(ptr, value.Length);
73+
fixed (char* ptr = value)
74+
{
75+
return (encoder ?? JavaScriptEncoder.Default).FindFirstCharacterToEncode(ptr, value.Length);
76+
}
7277
}
7378
}
7479

src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs

+13-7
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ internal static void ValidateNumber(ReadOnlySpan<byte> utf8FormattedNumber)
257257
private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
258258
#endif
259259

260-
public static unsafe bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
260+
public static bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
261261
{
262262
#if NET8_0_OR_GREATER
263263
return Utf8.IsValid(bytes);
@@ -269,9 +269,12 @@ public static unsafe bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
269269
#else
270270
if (!bytes.IsEmpty)
271271
{
272-
fixed (byte* ptr = bytes)
272+
unsafe
273273
{
274-
s_utf8Encoding.GetCharCount(ptr, bytes.Length);
274+
fixed (byte* ptr = bytes)
275+
{
276+
s_utf8Encoding.GetCharCount(ptr, bytes.Length);
277+
}
275278
}
276279
}
277280
#endif
@@ -284,7 +287,7 @@ public static unsafe bool IsValidUtf8String(ReadOnlySpan<byte> bytes)
284287
#endif
285288
}
286289

287-
internal static unsafe OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int written)
290+
internal static OperationStatus ToUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int written)
288291
{
289292
#if NET
290293
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<char> source, Span<by
297300
{
298301
if (!source.IsEmpty)
299302
{
300-
fixed (char* charPtr = source)
301-
fixed (byte* destPtr = destination)
303+
unsafe
302304
{
303-
written = s_utf8Encoding.GetBytes(charPtr, source.Length, destPtr, destination.Length);
305+
fixed (char* charPtr = source)
306+
fixed (byte* destPtr = destination)
307+
{
308+
written = s_utf8Encoding.GetBytes(charPtr, source.Length, destPtr, destination.Length);
309+
}
304310
}
305311
}
306312

src/libraries/System.Text.RegularExpressions/gen/Stubs.cs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ internal static class StringBuilderExtensions
1616
{
1717
public static unsafe StringBuilder Append(this StringBuilder stringBuilder, ReadOnlySpan<char> span)
1818
{
19+
// There is no StringBuilder.Append(ReadOnlySpan<char>) overload in the NS2.0
1920
fixed (char* ptr = &MemoryMarshal.GetReference(span))
2021
{
2122
return stringBuilder.Append(ptr, span.Length);

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs

+27-8
Original file line numberDiff line numberDiff line change
@@ -1543,7 +1543,7 @@ private static RegexCharClass ParseRecursive(string charClass, int start)
15431543
public static string OneToStringClass(char c)
15441544
=> CharsToStringClass([c]);
15451545

1546-
internal static unsafe string CharsToStringClass(ReadOnlySpan<char> chars)
1546+
internal static string CharsToStringClass(ReadOnlySpan<char> chars)
15471547
{
15481548
#if DEBUG
15491549
// Make sure they're all sorted with no duplicates
@@ -1592,20 +1592,15 @@ internal static unsafe string CharsToStringClass(ReadOnlySpan<char> chars)
15921592

15931593
// Get the pointer/length of the span to be able to pass it into string.Create.
15941594
ReadOnlySpan<char> tmpChars = chars; // avoid address exposing the span and impacting the other code in the method that uses it
1595-
return
15961595
#if NET
1597-
string
1598-
#else
1599-
StringExtensions
1600-
#endif
1601-
.Create(SetStartIndex + count, (IntPtr)(&tmpChars), static (span, charsPtr) =>
1596+
return string.Create(SetStartIndex + count, tmpChars, static (span, chars) =>
16021597
{
16031598
// Fill in the set string
16041599
span[FlagsIndex] = (char)0;
16051600
span[SetLengthIndex] = (char)(span.Length - SetStartIndex);
16061601
span[CategoryLengthIndex] = (char)0;
16071602
int i = SetStartIndex;
1608-
foreach (char c in *(ReadOnlySpan<char>*)charsPtr)
1603+
foreach (char c in chars)
16091604
{
16101605
span[i++] = c;
16111606
if (c != LastChar)
@@ -1615,8 +1610,32 @@ internal static unsafe string CharsToStringClass(ReadOnlySpan<char> chars)
16151610
}
16161611
Debug.Assert(i == span.Length);
16171612
});
1613+
#else
1614+
unsafe
1615+
{
1616+
return StringExtensions.Create(SetStartIndex + count, (IntPtr)(&tmpChars), static (span, charsPtr) =>
1617+
{
1618+
// Fill in the set string
1619+
span[FlagsIndex] = (char)0;
1620+
span[SetLengthIndex] = (char)(span.Length - SetStartIndex);
1621+
span[CategoryLengthIndex] = (char)0;
1622+
int i = SetStartIndex;
1623+
ReadOnlySpan<char> chars = *(ReadOnlySpan<char>*)charsPtr;
1624+
foreach (char c in chars)
1625+
{
1626+
span[i++] = c;
1627+
if (c != LastChar)
1628+
{
1629+
span[i++] = (char)(c + 1);
1630+
}
1631+
}
1632+
Debug.Assert(i == span.Length);
1633+
});
1634+
}
1635+
#endif
16181636
}
16191637

1638+
16201639
/// <summary>
16211640
/// Constructs the string representation of the class.
16221641
/// </summary>

0 commit comments

Comments
 (0)