Revert use of raw pointers in System.Text.Json (#78741)" (#84899)
authorJan Kotas <jkotas@microsoft.com>
Mon, 17 Apr 2023 01:05:21 +0000 (18:05 -0700)
committerGitHub <noreply@github.com>
Mon, 17 Apr 2023 01:05:21 +0000 (18:05 -0700)
Revert use of raw pointers for .NET Standard support. This particular raw pointer use does not work with .NET UWP toolchain.

Also, convert uses of Enum.HasFlags to simple bit tests for better .NET Framework and .NET Standard support. Enum.HasFlags performs poorly (boxes) on .NET Framework and .NET Standard targets.

Fixes #84895

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.MetadataHandling.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleMetadata.cs

index d7153ae..e87b7f1 100644 (file)
@@ -161,7 +161,7 @@ namespace System.Text.Json.Serialization
                 }
 
                 // Dispatch to any polymorphic converters: should always be entered regardless of ObjectState progress
-                if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Type) &&
+                if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0 &&
                     state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStarted &&
                     ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter)
                 {
@@ -181,7 +181,7 @@ namespace System.Text.Json.Serialization
 
                     CreateCollection(ref reader, ref state, options);
 
-                    if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Id))
+                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Id) != 0)
                     {
                         Debug.Assert(state.ReferenceId != null);
                         Debug.Assert(options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve);
@@ -245,7 +245,7 @@ namespace System.Text.Json.Serialization
                     state.Current.ObjectState = StackFrameObjectState.EndToken;
 
                     // Array payload is nested inside a $values metadata property.
-                    if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Values))
+                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Values) != 0)
                     {
                         if (!reader.Read())
                         {
@@ -258,7 +258,7 @@ namespace System.Text.Json.Serialization
                 if (state.Current.ObjectState < StackFrameObjectState.EndTokenValidation)
                 {
                     // Array payload is nested inside a $values metadata property.
-                    if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Values))
+                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Values) != 0)
                     {
                         if (reader.TokenType != JsonTokenType.EndObject)
                         {
index e1dbc74..d637543 100644 (file)
@@ -185,7 +185,7 @@ namespace System.Text.Json.Serialization
                 }
 
                 // Dispatch to any polymorphic converters: should always be entered regardless of ObjectState progress
-                if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Type) &&
+                if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0 &&
                     state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStarted &&
                     ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter)
                 {
@@ -206,7 +206,7 @@ namespace System.Text.Json.Serialization
 
                     CreateCollection(ref reader, ref state);
 
-                    if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Id))
+                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Id) != 0)
                     {
                         Debug.Assert(state.ReferenceId != null);
                         Debug.Assert(options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve);
index 2568f58..1a3ddf3 100644 (file)
@@ -102,7 +102,7 @@ namespace System.Text.Json.Serialization.Converters
                 }
 
                 // Dispatch to any polymorphic converters: should always be entered regardless of ObjectState progress
-                if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Type) &&
+                if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0 &&
                     state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStarted &&
                     ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter)
                 {
@@ -133,7 +133,7 @@ namespace System.Text.Json.Serialization.Converters
 
                     obj = jsonTypeInfo.CreateObject()!;
 
-                    if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Id))
+                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Id) != 0)
                     {
                         Debug.Assert(state.ReferenceId != null);
                         Debug.Assert(options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve);
index 064a9a6..0edf2d2 100644 (file)
@@ -132,7 +132,7 @@ namespace System.Text.Json.Serialization.Converters
                 }
 
                 // Dispatch to any polymorphic converters: should always be entered regardless of ObjectState progress
-                if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Type) &&
+                if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0 &&
                     state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStarted &&
                     ResolvePolymorphicConverter(jsonTypeInfo, ref state) is JsonConverter polymorphicConverter)
                 {
@@ -170,7 +170,7 @@ namespace System.Text.Json.Serialization.Converters
 
                 obj = (T)CreateObject(ref state.Current);
 
-                if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Id))
+                if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Id) != 0)
                 {
                     Debug.Assert(state.ReferenceId != null);
                     Debug.Assert(options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve);
index 4a5edd1..08d3e37 100644 (file)
@@ -5,6 +5,7 @@ using System.Buffers;
 using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Globalization;
+using System.Runtime.CompilerServices;
 using System.Text.Encodings.Web;
 
 namespace System.Text.Json.Serialization.Converters
@@ -93,14 +94,13 @@ namespace System.Text.Json.Serialization.Converters
             }
         }
 
-#pragma warning disable 8500 // address of managed types
-        public override unsafe T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
         {
             JsonTokenType token = reader.TokenType;
 
             if (token == JsonTokenType.String)
             {
-                if (!_converterOptions.HasFlag(EnumConverterOptions.AllowStrings))
+                if ((_converterOptions & EnumConverterOptions.AllowStrings) == 0)
                 {
                     ThrowHelper.ThrowJsonException();
                     return default;
@@ -122,7 +122,7 @@ namespace System.Text.Json.Serialization.Converters
 #endif
             }
 
-            if (token != JsonTokenType.Number || !_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers))
+            if (token != JsonTokenType.Number || (_converterOptions & EnumConverterOptions.AllowNumbers) == 0)
             {
                 ThrowHelper.ThrowJsonException();
                 return default;
@@ -135,49 +135,51 @@ namespace System.Text.Json.Serialization.Converters
                 case TypeCode.Int32:
                     if (reader.TryGetInt32(out int int32))
                     {
-                        return *(T*)&int32;
+                        // Use Unsafe.As instead of raw pointers for .NET Standard support.
+                        // https://github.com/dotnet/runtime/issues/84895
+                        return Unsafe.As<int, T>(ref int32);
                     }
                     break;
                 case TypeCode.UInt32:
                     if (reader.TryGetUInt32(out uint uint32))
                     {
-                        return *(T*)&uint32;
+                        return Unsafe.As<uint, T>(ref uint32);
                     }
                     break;
                 case TypeCode.UInt64:
                     if (reader.TryGetUInt64(out ulong uint64))
                     {
-                        return *(T*)&uint64;
+                        return Unsafe.As<ulong, T>(ref uint64);
                     }
                     break;
                 case TypeCode.Int64:
                     if (reader.TryGetInt64(out long int64))
                     {
-                        return *(T*)&int64;
+                        return Unsafe.As<long, T>(ref int64);
                     }
                     break;
                 case TypeCode.SByte:
                     if (reader.TryGetSByte(out sbyte byte8))
                     {
-                        return *(T*)&byte8;
+                        return Unsafe.As<sbyte, T>(ref byte8);
                     }
                     break;
                 case TypeCode.Byte:
                     if (reader.TryGetByte(out byte ubyte8))
                     {
-                        return *(T*)&ubyte8;
+                        return Unsafe.As<byte, T>(ref ubyte8);
                     }
                     break;
                 case TypeCode.Int16:
                     if (reader.TryGetInt16(out short int16))
                     {
-                        return *(T*)&int16;
+                        return Unsafe.As<short, T>(ref int16);
                     }
                     break;
                 case TypeCode.UInt16:
                     if (reader.TryGetUInt16(out ushort uint16))
                     {
-                        return *(T*)&uint16;
+                        return Unsafe.As<ushort, T>(ref uint16);
                     }
                     break;
             }
@@ -186,10 +188,10 @@ namespace System.Text.Json.Serialization.Converters
             return default;
         }
 
-        public override unsafe void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
+        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
         {
             // If strings are allowed, attempt to write it out as a string value
-            if (_converterOptions.HasFlag(EnumConverterOptions.AllowStrings))
+            if ((_converterOptions & EnumConverterOptions.AllowStrings) != 0)
             {
                 ulong key = ConvertToUInt64(value);
 
@@ -226,7 +228,7 @@ namespace System.Text.Json.Serialization.Converters
                 }
             }
 
-            if (!_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers))
+            if ((_converterOptions & EnumConverterOptions.AllowNumbers) == 0)
             {
                 ThrowHelper.ThrowJsonException();
             }
@@ -234,35 +236,36 @@ namespace System.Text.Json.Serialization.Converters
             switch (s_enumTypeCode)
             {
                 case TypeCode.Int32:
-                    writer.WriteNumberValue(*(int*)&value);
+                    // Use Unsafe.As instead of raw pointers for .NET Standard support.
+                    // https://github.com/dotnet/runtime/issues/84895
+                    writer.WriteNumberValue(Unsafe.As<T, int>(ref value));
                     break;
                 case TypeCode.UInt32:
-                    writer.WriteNumberValue(*(uint*)&value);
+                    writer.WriteNumberValue(Unsafe.As<T, uint>(ref value));
                     break;
                 case TypeCode.UInt64:
-                    writer.WriteNumberValue(*(ulong*)&value);
+                    writer.WriteNumberValue(Unsafe.As<T, ulong>(ref value));
                     break;
                 case TypeCode.Int64:
-                    writer.WriteNumberValue(*(long*)&value);
+                    writer.WriteNumberValue(Unsafe.As<T, long>(ref value));
                     break;
                 case TypeCode.Int16:
-                    writer.WriteNumberValue(*(short*)&value);
+                    writer.WriteNumberValue(Unsafe.As<T, short>(ref value));
                     break;
                 case TypeCode.UInt16:
-                    writer.WriteNumberValue(*(ushort*)&value);
+                    writer.WriteNumberValue(Unsafe.As<T, ushort>(ref value));
                     break;
                 case TypeCode.Byte:
-                    writer.WriteNumberValue(*(byte*)&value);
+                    writer.WriteNumberValue(Unsafe.As<T, byte>(ref value));
                     break;
                 case TypeCode.SByte:
-                    writer.WriteNumberValue(*(sbyte*)&value);
+                    writer.WriteNumberValue(Unsafe.As<T, sbyte>(ref value));
                     break;
                 default:
                     ThrowHelper.ThrowJsonException();
                     break;
             }
         }
-#pragma warning restore 8500
 
         internal override T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
         {
index 74345fa..6a3de62 100644 (file)
@@ -15,7 +15,7 @@ namespace System.Text.Json.Serialization
         {
             Debug.Assert(!IsValueType);
             Debug.Assert(CanHaveMetadata);
-            Debug.Assert(state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Type));
+            Debug.Assert((state.Current.MetadataPropertyNames & MetadataPropertyName.Type) != 0);
             Debug.Assert(state.Current.PolymorphicSerializationState != PolymorphicSerializationState.PolymorphicReEntryStarted);
             Debug.Assert(jsonTypeInfo.PolymorphicTypeResolver?.UsesTypeDiscriminators == true);
 
index 4326af6..c9a9e60 100644 (file)
@@ -50,7 +50,7 @@ namespace System.Text.Json
                     // We just read a property. The only valid next tokens are EndObject and PropertyName.
                     Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);
 
-                    if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Ref))
+                    if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Ref) != 0)
                     {
                         // No properties whatsoever should follow a $ref property.
                         ThrowHelper.ThrowJsonException_MetadataReferenceObjectCannotContainOtherProperties(reader.GetSpan(), ref state);
@@ -427,7 +427,7 @@ namespace System.Text.Json
 
         internal static void ValidateMetadataForObjectConverter(ref ReadStack state)
         {
-            if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Values))
+            if ((state.Current.MetadataPropertyNames & MetadataPropertyName.Values) != 0)
             {
                 // Object converters do not support $values metadata.
                 ThrowHelper.ThrowJsonException_MetadataUnexpectedProperty(s_valuesPropertyName, ref state);