[release/6.0] Throw on unsupported types in src gen (#58983)
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Mon, 13 Sep 2021 17:27:16 +0000 (11:27 -0600)
committerGitHub <noreply@github.com>
Mon, 13 Sep 2021 17:27:16 +0000 (11:27 -0600)
* Throw on unsupported types in src gen

* Misc non-functional changes and feedback

* Fix typo

* Change disallowed terminology to unsupported

Co-authored-by: Steve Harter <steveharter@users.noreply.github.com>
19 files changed:
src/libraries/System.Text.Json/gen/ClassType.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DisallowedTypeConverter.cs with 89% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverterFactory.cs [moved from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DisallowedTypeConverterFactory.cs with 88% similarity]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
src/libraries/System.Text.Json/tests/Common/UnsupportedTypesTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/UnsupportedTypesTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/UnsupportedTypesTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj

index d0df943..80029b5 100644 (file)
@@ -9,13 +9,20 @@ namespace System.Text.Json.SourceGeneration
 {
     internal enum ClassType
     {
+        /// <summary>
+        /// Types that are not supported yet by source gen including types with constructor parameters.
+        /// </summary>
         TypeUnsupportedBySourceGen = 0,
         Object = 1,
         KnownType = 2,
-        TypeWithDesignTimeProvidedCustomConverter = 3,
-        Enumerable = 4,
-        Dictionary = 5,
-        Nullable = 6,
-        Enum = 7
+        /// <summary>
+        /// Known types such as System.Type and System.IntPtr that throw NotSupportedException.
+        /// </summary>
+        KnownUnsupportedType = 3,
+        TypeWithDesignTimeProvidedCustomConverter = 4,
+        Enumerable = 5,
+        Dictionary = 6,
+        Nullable = 7,
+        Enum = 8
     }
 }
index b2b3488..2f9a64f 100644 (file)
@@ -64,6 +64,7 @@ namespace System.Text.Json.SourceGeneration
             private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues";
             private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo";
             private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo";
+            private const string NotSupportedExceptionTypeRef = "global::System.NotSupportedException";
 
             private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor(
                 id: "SYSLIB1030",
@@ -254,6 +255,11 @@ namespace {@namespace}
                             }
                         }
                         break;
+                    case ClassType.KnownUnsupportedType:
+                        {
+                            source = GenerateForUnsupportedType(typeGenerationSpec);
+                        }
+                        break;
                     case ClassType.TypeUnsupportedBySourceGen:
                         {
                             _sourceProductionContext.ReportDiagnostic(
@@ -353,6 +359,16 @@ namespace {@namespace}
                 return GenerateForType(typeMetadata, metadataInitSource);
             }
 
+            private string GenerateForUnsupportedType(TypeGenerationSpec typeMetadata)
+            {
+                string typeCompilableName = typeMetadata.TypeRef;
+                string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
+
+                string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeCompilableName}>());";
+
+                return GenerateForType(typeMetadata, metadataInitSource);
+            }
+
             private string GenerateForEnum(TypeGenerationSpec typeMetadata)
             {
                 string typeCompilableName = typeMetadata.TypeRef;
index 2c5f3eb..199c0db 100644 (file)
@@ -36,6 +36,10 @@ namespace System.Text.Json.SourceGeneration
             private const string JsonSerializerAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute";
             private const string JsonSourceGenerationOptionsAttributeFullName = "System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute";
 
+            private const string DateOnlyFullName = "System.DateOnly";
+            private const string TimeOnlyFullName = "System.TimeOnly";
+            private const string IAsyncEnumerableFullName = "System.Collections.Generic.IAsyncEnumerable`1";
+
             private readonly Compilation _compilation;
             private readonly SourceProductionContext _sourceProductionContext;
             private readonly MetadataLoadContextInternal _metadataLoadContext;
@@ -74,8 +78,20 @@ namespace System.Text.Json.SourceGeneration
             private readonly Type? _versionType;
             private readonly Type? _jsonElementType;
 
+            // Unsupported types
+            private readonly Type _typeType;
+            private readonly Type _serializationInfoType;
+            private readonly Type _intPtrType;
+            private readonly Type _uIntPtrType;
+
+            // Unsupported types that may not resolve
+            private readonly Type? _iAsyncEnumerableGenericType;
+            private readonly Type? _dateOnlyType;
+            private readonly Type? _timeOnlyType;
+
             private readonly HashSet<Type> _numberTypes = new();
             private readonly HashSet<Type> _knownTypes = new();
+            private readonly HashSet<Type> _knownUnsupportedTypes = new();
 
             /// <summary>
             /// Type information for member types in input object graphs.
@@ -142,6 +158,15 @@ namespace System.Text.Json.SourceGeneration
                 _versionType = _metadataLoadContext.Resolve(typeof(Version));
                 _jsonElementType = _metadataLoadContext.Resolve(JsonElementFullName);
 
+                // Unsupported types.
+                _typeType = _metadataLoadContext.Resolve(typeof(Type));
+                _serializationInfoType = _metadataLoadContext.Resolve(typeof(Runtime.Serialization.SerializationInfo));
+                _intPtrType = _metadataLoadContext.Resolve(typeof(IntPtr));
+                _uIntPtrType = _metadataLoadContext.Resolve(typeof(UIntPtr));
+                _iAsyncEnumerableGenericType = _metadataLoadContext.Resolve(IAsyncEnumerableFullName);
+                _dateOnlyType = _metadataLoadContext.Resolve(DateOnlyFullName);
+                _timeOnlyType = _metadataLoadContext.Resolve(TimeOnlyFullName);
+
                 PopulateKnownTypes();
             }
 
@@ -765,6 +790,11 @@ namespace System.Text.Json.SourceGeneration
                         collectionValueType = _objectType;
                     }
                 }
+                else if (_knownUnsupportedTypes.Contains(type) ||
+                    ImplementsIAsyncEnumerableInterface(type))
+                {
+                    classType = ClassType.KnownUnsupportedType;
+                }
                 else
                 {
                     bool useDefaultCtorInAnnotatedStructs = !type.IsKeyValuePair(_keyValuePair);
@@ -893,6 +923,16 @@ namespace System.Text.Json.SourceGeneration
                 return typeMetadata;
             }
 
+            private bool ImplementsIAsyncEnumerableInterface(Type type)
+            {
+                if (_iAsyncEnumerableGenericType == null)
+                {
+                    return false;
+                }
+
+                return type.GetCompatibleGenericInterface(_iAsyncEnumerableGenericType) is not null;
+            }
+
             private Type GetCompatibleGenericBaseClass(Type type, Type baseType)
                 => type.GetCompatibleGenericBaseClass(baseType, _objectType);
 
@@ -1268,8 +1308,10 @@ namespace System.Text.Json.SourceGeneration
             private void PopulateKnownTypes()
             {
                 PopulateNumberTypes();
+
                 Debug.Assert(_knownTypes != null);
                 Debug.Assert(_numberTypes != null);
+                Debug.Assert(_knownUnsupportedTypes != null);
 
                 _knownTypes.UnionWith(_numberTypes);
                 _knownTypes.Add(_booleanType);
@@ -1283,6 +1325,21 @@ namespace System.Text.Json.SourceGeneration
                 _knownTypes.Add(_uriType);
                 _knownTypes.Add(_versionType);
                 _knownTypes.Add(_jsonElementType);
+
+                _knownUnsupportedTypes.Add(_typeType);
+                _knownUnsupportedTypes.Add(_serializationInfoType);
+                _knownUnsupportedTypes.Add(_intPtrType);
+                _knownUnsupportedTypes.Add(_uIntPtrType);
+
+                if (_dateOnlyType != null)
+                {
+                    _knownUnsupportedTypes.Add(_dateOnlyType);
+                }
+
+                if (_timeOnlyType != null)
+                {
+                    _knownUnsupportedTypes.Add(_timeOnlyType);
+                }
             }
         }
     }
index d4b05c6..09340a2 100644 (file)
@@ -999,6 +999,7 @@ namespace System.Text.Json.Serialization.Metadata
         public static JsonTypeInfo<TCollection> CreateStackInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc) where TCollection : System.Collections.Generic.Stack<TElement> { throw null; }
         public static JsonTypeInfo<TCollection> CreateStackOrQueueInfo<TCollection>(System.Text.Json.JsonSerializerOptions options, System.Func<TCollection>? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action<Utf8JsonWriter, TCollection>? serializeFunc, System.Action<TCollection, object?> addFunc) where TCollection : System.Collections.IEnumerable { throw null; }
         public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateValueInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; }
+        public static System.Text.Json.Serialization.JsonConverter<T> GetUnsupportedTypeConverter<T>() { throw null; }
         public static System.Text.Json.Serialization.JsonConverter<T> GetEnumConverter<T>(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; }
         public static System.Text.Json.Serialization.JsonConverter<T?> GetNullableConverter<T>(System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> underlyingTypeInfo) where T : struct { throw null; }
     }
index 7fc1bd8..e1cb127 100644 (file)
     <value>The type '{0}' is invalid for serialization or deserialization because it is a pointer type, is a ref struct, or contains generic parameters that have not been replaced by specific types.</value>
   </data>
   <data name="SerializeTypeInstanceNotSupported" xml:space="preserve">
-    <value>Serialization and deserialization of '{0}' instances are not supported and should be avoided since they can lead to security issues.</value>
+    <value>Serialization and deserialization of '{0}' instances are not supported.</value>
   </data>
   <data name="JsonIncludeOnNonPublicInvalid" xml:space="preserve">
     <value>The non-public property '{0}' on type '{1}' is annotated with 'JsonIncludeAttribute' which is invalid.</value>
index d18d2c0..4cfed9b 100644 (file)
@@ -179,8 +179,6 @@ System.Text.Json.Utf8JsonReader</PackageDescription>
     <Compile Include="System\Text\Json\Serialization\Converters\Value\DateTimeConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\DateTimeOffsetConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\DecimalConverter.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\Value\DisallowedTypeConverter.cs" />
-    <Compile Include="System\Text\Json\Serialization\Converters\Value\DisallowedTypeConverterFactory.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\DoubleConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\EnumConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\EnumConverterFactory.cs" />
@@ -201,6 +199,8 @@ System.Text.Json.Utf8JsonReader</PackageDescription>
     <Compile Include="System\Text\Json\Serialization\Converters\Value\UInt16Converter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\UInt32Converter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\UInt64Converter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\Value\UnsupportedTypeConverter.cs" />
+    <Compile Include="System\Text\Json\Serialization\Converters\Value\UnsupportedTypeConverterFactory.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\UriConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\Value\VersionConverter.cs" />
     <Compile Include="System\Text\Json\Serialization\IgnoreReferenceHandler.cs" />
@@ -3,7 +3,7 @@
 
 namespace System.Text.Json.Serialization.Converters
 {
-    internal sealed class DisallowedTypeConverter<T> : JsonConverter<T>
+    internal sealed class UnsupportedTypeConverter<T> : JsonConverter<T>
     {
         public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
             throw new NotSupportedException(SR.Format(SR.SerializeTypeInstanceNotSupported, typeof(T).FullName));
@@ -6,12 +6,11 @@ using System.Runtime.Serialization;
 
 namespace System.Text.Json.Serialization.Converters
 {
-    internal sealed class DisallowedTypeConverterFactory : JsonConverterFactory
+    internal sealed class UnsupportedTypeConverterFactory : JsonConverterFactory
     {
         public override bool CanConvert(Type type)
         {
-            // If a value type is added, also add a test that
-            // shows NSE is thrown when Nullable<T> is (de)serialized.
+            // If a type is added, also add to the SourceGeneration project.
 
             return
                 // There's no safe way to construct a Type from untrusted user input.
@@ -42,7 +41,7 @@ namespace System.Text.Json.Serialization.Converters
         public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
         {
             JsonConverter converter = (JsonConverter)Activator.CreateInstance(
-                typeof(DisallowedTypeConverter<>).MakeGenericType(type),
+                typeof(UnsupportedTypeConverter<>).MakeGenericType(type),
                 BindingFlags.Instance | BindingFlags.Public,
                 binder: null,
                 args: null,
index 912cabe..4276d37 100644 (file)
@@ -34,7 +34,7 @@ namespace System.Text.Json
             s_defaultFactoryConverters = new JsonConverter[]
             {
                 // Check for disallowed types.
-                new DisallowedTypeConverterFactory(),
+                new UnsupportedTypeConverterFactory(),
                 // Nullable converter should always be next since it forwards to any nullable type.
                 new NullableConverterFactory(),
                 new EnumConverterFactory(),
index 39ad153..2f3b8e7 100644 (file)
@@ -151,6 +151,14 @@ namespace System.Text.Json.Serialization.Metadata
         private static JsonConverter<Version>? s_versionConverter;
 
         /// <summary>
+        /// Creates a <see cref="JsonConverter{T}"/> instance that throws <see cref="NotSupportedException"/>.
+        /// </summary>
+        /// <typeparam name="T">The generic definition for the type.</typeparam>
+        /// <returns></returns>
+        public static JsonConverter<T> GetUnsupportedTypeConverter<T>()
+            => new UnsupportedTypeConverter<T>();
+
+        /// <summary>
         /// Creates a <see cref="JsonConverter{T}"/> instance that converts <typeparamref name="T"/> values.
         /// </summary>
         /// <typeparam name="T">The generic definition for the enum type.</typeparam>
index 7312f6f..d04de0c 100644 (file)
@@ -341,7 +341,7 @@ namespace System.Text.Json
             static void AppendStackFrame(StringBuilder sb, ref WriteStackFrame frame)
             {
                 // Append the property name.
-                string? propertyName = frame.DeclaredJsonPropertyInfo?.MemberInfo?.Name;
+                string? propertyName = frame.DeclaredJsonPropertyInfo?.ClrName;
                 if (propertyName == null)
                 {
                     // Attempt to get the JSON property name from the property name specified in re-entry.
diff --git a/src/libraries/System.Text.Json/tests/Common/UnsupportedTypesTests.cs b/src/libraries/System.Text.Json/tests/Common/UnsupportedTypesTests.cs
new file mode 100644 (file)
index 0000000..35359ca
--- /dev/null
@@ -0,0 +1,272 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public abstract class UnsupportedTypesTests : SerializerTests
+    {
+        private bool SupportsJsonPathOnSerialize { get; init; }
+
+        public UnsupportedTypesTests(
+            JsonSerializerWrapperForString serializerWrapper,
+            bool supportsJsonPathOnSerialize) : base(serializerWrapper)
+        {
+            SupportsJsonPathOnSerialize = supportsJsonPathOnSerialize;
+        }
+
+        [Fact]
+        public async Task DeserializeUnsupportedType()
+        {
+            // Any test payload is fine.
+            string json = @"""Some string""";
+
+            await RunTest<Type>(json);
+            await RunTest<SerializationInfo>(json);
+            await RunTest<IntPtr>(json);
+            await RunTest<IntPtr?>(json); // One nullable variation.
+            await RunTest<UIntPtr>(json);
+#if NETCOREAPP
+            await RunTest<DateOnly>(json);
+            await RunTest<TimeOnly>(json);
+#endif
+#if BUILDING_SOURCE_GENERATOR_TESTS
+            await RunTest<IAsyncEnumerable<int>>(json);
+            await RunTest<ClassThatImplementsIAsyncEnumerable>(json);
+#endif
+
+            async Task RunTest<T>(string json)
+            {
+                Type type = GetNullableOfTUnderlyingType(typeof(T), out bool isNullableOfT);
+                string fullName = type.FullName;
+
+                NotSupportedException ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<T>(json));
+                string exAsStr = ex.ToString();
+                Assert.Contains(fullName, exAsStr);
+                Assert.Contains("$", exAsStr);
+
+                json = $@"{{""Prop"":{json}}}";
+
+                ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithType<T>>(json));
+                exAsStr = ex.ToString();
+                Assert.Contains(fullName, exAsStr);
+                Assert.Contains("$.Prop", exAsStr);
+
+                // Verify Nullable<> semantics. NSE is not thrown because the serializer handles null.
+                if (isNullableOfT)
+                {
+                    Assert.Null(JsonSerializer.Deserialize<T>("null"));
+
+                    json = $@"{{""Prop"":null}}";
+                    ClassWithType<T> obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithType<T>>(json);
+                    Assert.Null(obj.Prop);
+                }
+            }
+        }
+
+        [Fact]
+        public async Task SerializeUnsupportedType()
+        {
+            await RunTest(typeof(int));
+            await RunTest(new SerializationInfo(typeof(Type), new FormatterConverter()));
+            await RunTest((IntPtr)123);
+            await RunTest<IntPtr?>(new IntPtr(123)); // One nullable variation.
+            await RunTest((UIntPtr)123);
+#if NETCOREAPP
+            await RunTest(DateOnly.MaxValue);
+            await RunTest(TimeOnly.MinValue);
+#endif
+#if BUILDING_SOURCE_GENERATOR_TESTS
+            await RunTest(new ClassThatImplementsIAsyncEnumerable());
+#endif
+
+            async Task RunTest<T>(T value)
+            {
+                Type type = GetNullableOfTUnderlyingType(typeof(T), out bool isNullableOfT);
+                string fullName = type.FullName;
+
+                NotSupportedException ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(value));
+                string exAsStr = ex.ToString();
+                Assert.Contains(fullName, exAsStr);
+                Assert.Contains("$", exAsStr);
+
+                ClassWithType<T> obj = new ClassWithType<T> { Prop = value };
+
+                ex = await Assert.ThrowsAsync<NotSupportedException>(async () => await JsonSerializerWrapperForString.SerializeWrapper(obj));
+                exAsStr = ex.ToString();
+                Assert.Contains(fullName, exAsStr);
+
+                if (SupportsJsonPathOnSerialize)
+                {
+                    Assert.Contains("$.Prop", exAsStr);
+                }
+                else
+                {
+                    Assert.Contains("$.", exAsStr);
+                    Assert.DoesNotContain("$.Prop", exAsStr);
+                }
+
+                // Verify null semantics. NSE is not thrown because the serializer handles null.
+                if (!type.IsValueType || isNullableOfT)
+                {
+                    string serialized = await JsonSerializerWrapperForString.SerializeWrapper<T>((T)(object)null);
+                    Assert.Equal("null", serialized);
+
+                    obj.Prop = (T)(object)null;
+                    serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+                    Assert.Equal(@"{""Prop"":null}", serialized);
+
+                    serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, new JsonSerializerOptions { IgnoreNullValues = true });
+                    Assert.Equal(@"{}", serialized);
+                }
+            }
+        }
+
+        public class ClassWithIntPtr
+        {
+            public IntPtr MyIntPtr { get; set; }
+        }
+
+        public class ClassWithIntPtrConverter
+        {
+            [JsonConverter(typeof(IntPtrConverter))]
+            public IntPtr MyIntPtr { get; set; }
+        }
+
+        public class IntPtrConverter : JsonConverter<IntPtr>
+        {
+            public override IntPtr Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                int value = reader.GetInt32();
+                return new IntPtr(value);
+            }
+
+            public override void Write(Utf8JsonWriter writer, IntPtr value, JsonSerializerOptions options)
+            {
+                writer.WriteNumberValue(value.ToInt32());
+            }
+        }
+
+        [Fact]
+        public async Task RuntimeConverterIsSupported_IntPtr()
+        {
+            const string Json = "{\"MyIntPtr\":42}";
+            string serialized;
+            JsonSerializerOptions options = new();
+            options.Converters.Add(new IntPtrConverter());
+
+            serialized = await JsonSerializerWrapperForString.SerializeWrapper(new IntPtr(42), options);
+            Assert.Equal("42", serialized);
+
+            IntPtr intPtr = await JsonSerializerWrapperForString.DeserializeWrapper<IntPtr>("42", options);
+            Assert.Equal(42, intPtr.ToInt32());
+
+            ClassWithIntPtr obj = new() { MyIntPtr = new IntPtr(42) };
+            serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+            Assert.Equal(Json, serialized);
+
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithIntPtr>(Json, options);
+            Assert.Equal(42, obj.MyIntPtr.ToInt32());
+        }
+
+        [Fact]
+        public async Task CompileTimeConverterIsSupported_IntPtr()
+        {
+            const string Json = "{\"MyIntPtr\":42}";
+
+            ClassWithIntPtrConverter obj = new() { MyIntPtr = new IntPtr(42) };
+            string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal(Json, serialized);
+
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithIntPtrConverter>(Json);
+            Assert.Equal(42, obj.MyIntPtr.ToInt32());
+        }
+
+        public class ClassWithAsyncEnumerableConverter
+        {
+            [JsonConverter(typeof(AsyncEnumerableConverter))]
+            public ClassThatImplementsIAsyncEnumerable MyAsyncEnumerable { get; set; }
+        }
+
+        public class ClassThatImplementsIAsyncEnumerable : IAsyncEnumerable<int>
+        {
+            public string Status { get; set; } = "Created";
+
+            // Should not be called.
+            IAsyncEnumerator<int> IAsyncEnumerable<int>.GetAsyncEnumerator(CancellationToken cancellationToken) => throw new NotImplementedException();
+        }
+
+        public class AsyncEnumerableConverter : JsonConverter<ClassThatImplementsIAsyncEnumerable>
+        {
+            public override ClassThatImplementsIAsyncEnumerable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                Assert.Equal(JsonTokenType.StartArray, reader.TokenType);
+                reader.Read();
+                Assert.Equal(JsonTokenType.EndArray, reader.TokenType);
+                return new ClassThatImplementsIAsyncEnumerable { Status = "Read" };
+            }
+
+            public override void Write(Utf8JsonWriter writer, ClassThatImplementsIAsyncEnumerable value, JsonSerializerOptions options)
+            {
+                writer.WriteStartArray();
+                writer.WriteEndArray();
+                value.Status = "Write";
+            }
+        }
+
+        [Fact]
+        public async Task RuntimeConverterIsSupported_AsyncEnumerable()
+        {
+            const string Json = "{\"MyAsyncEnumerable\":[]}";
+
+            string serialized;
+            JsonSerializerOptions options = new();
+            options.Converters.Add(new AsyncEnumerableConverter());
+
+            ClassThatImplementsIAsyncEnumerable obj = new();
+            Assert.Equal("Created", obj.Status);
+            serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj, options);
+            Assert.Equal("[]", serialized);
+            Assert.Equal("Write", obj.Status);
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassThatImplementsIAsyncEnumerable>("[]", options);
+            Assert.Equal("Read", obj.Status);
+
+            ClassWithAsyncEnumerableConverter poco = new();
+            poco.MyAsyncEnumerable = new();
+            Assert.Equal("Created", poco.MyAsyncEnumerable.Status);
+            serialized = await JsonSerializerWrapperForString.SerializeWrapper(poco, options);
+            Assert.Equal(Json, serialized);
+            Assert.Equal("Write", poco.MyAsyncEnumerable.Status);
+            poco = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithAsyncEnumerableConverter>(Json, options);
+            Assert.Equal("Read", poco.MyAsyncEnumerable.Status);
+        }
+
+        [Fact]
+        public async Task CompileTimeConverterIsSupported_AsyncEnumerable()
+        {
+            const string Json = "{\"MyAsyncEnumerable\":[]}";
+
+            ClassWithAsyncEnumerableConverter obj = new();
+            obj.MyAsyncEnumerable = new();
+            Assert.Equal("Created", obj.MyAsyncEnumerable.Status);
+
+            string serialized = await JsonSerializerWrapperForString.SerializeWrapper(obj);
+            Assert.Equal(Json, serialized);
+            Assert.Equal("Write", obj.MyAsyncEnumerable.Status);
+
+            obj = await JsonSerializerWrapperForString.DeserializeWrapper<ClassWithAsyncEnumerableConverter>(Json);
+            Assert.Equal("Read", obj.MyAsyncEnumerable.Status);
+        }
+
+        public static Type GetNullableOfTUnderlyingType(Type type, out bool isNullableOfT)
+        {
+            isNullableOfT = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+            return isNullableOfT ? type.GetGenericArguments()[0] : type;
+        }
+    }
+}
index d10300c..8681cc7 100644 (file)
@@ -6,6 +6,7 @@ using System.IO;
 using System.Linq;
 using System.Text.Json.Serialization;
 using System.Text.Json.Serialization.Metadata;
+using System.Text.Json.Serialization.Tests;
 using Xunit;
 
 namespace System.Text.Json.SourceGeneration.Tests
@@ -243,11 +244,11 @@ namespace System.Text.Json.SourceGeneration.Tests
         [Fact]
         public virtual void RoundTripWithCustomPropertyConverterFactory_Class()
         {
-            const string Json = "{\"MyEnum\":\"A\"}";
+            const string Json = "{\"MyEnum\":\"One\"}";
 
             ClassWithCustomConverterPropertyFactory obj = new()
             {
-                MyEnum = SampleEnum.A
+                MyEnum = SampleEnum.One
             };
 
             if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
@@ -267,18 +268,18 @@ namespace System.Text.Json.SourceGeneration.Tests
             else
             {
                 obj = JsonSerializer.Deserialize(Json, DefaultContext.ClassWithCustomConverterPropertyFactory);
-                Assert.Equal(SampleEnum.A, obj.MyEnum);
+                Assert.Equal(SampleEnum.One, obj.MyEnum);
             }
         }
 
         [Fact]
         public virtual void RoundTripWithCustomPropertyConverterFactory_Struct()
         {
-            const string Json = "{\"MyEnum\":\"A\"}";
+            const string Json = "{\"MyEnum\":\"One\"}";
 
             StructWithCustomConverterPropertyFactory obj = new()
             {
-                MyEnum = SampleEnum.A
+                MyEnum = SampleEnum.One
             };
 
             if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
@@ -298,7 +299,7 @@ namespace System.Text.Json.SourceGeneration.Tests
             else
             {
                 obj = JsonSerializer.Deserialize(Json, DefaultContext.StructWithCustomConverterPropertyFactory);
-                Assert.Equal(SampleEnum.A, obj.MyEnum);
+                Assert.Equal(SampleEnum.One, obj.MyEnum);
             }
         }
 
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/UnsupportedTypesTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/UnsupportedTypesTests.cs
new file mode 100644 (file)
index 0000000..fbe343e
--- /dev/null
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Tests;
+
+namespace System.Text.Json.SourceGeneration.Tests
+{
+    public sealed partial class UnsupportedTypesTests_Metadata : UnsupportedTypesTests
+    {
+        public UnsupportedTypesTests_Metadata() : base(
+            new StringSerializerWrapper(
+                UnsupportedTypesTestsContext_Metadata.Default,
+                (options) => new UnsupportedTypesTestsContext_Metadata(options)),
+            supportsJsonPathOnSerialize: true)
+        {
+        }
+
+        [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
+        // Supported types:
+        [JsonSerializable(typeof(int))]
+        [JsonSerializable(typeof(ClassWithIntPtrConverter))]
+        // Unsupported types:
+        [JsonSerializable(typeof(Type))]
+        [JsonSerializable(typeof(ClassWithType<Type>))]
+        [JsonSerializable(typeof(SerializationInfo))]
+        [JsonSerializable(typeof(ClassWithType<SerializationInfo>))]
+        [JsonSerializable(typeof(IntPtr))]
+        [JsonSerializable(typeof(ClassWithType<IntPtr>))]
+        [JsonSerializable(typeof(ClassWithIntPtr))]
+        [JsonSerializable(typeof(IntPtr?))]
+        [JsonSerializable(typeof(ClassWithType<IntPtr?>))]
+        [JsonSerializable(typeof(UIntPtr))]
+        [JsonSerializable(typeof(ClassWithType<UIntPtr>))]
+        [JsonSerializable(typeof(IAsyncEnumerable<int>))]
+        [JsonSerializable(typeof(ClassWithType<IAsyncEnumerable<int>>))]
+        [JsonSerializable(typeof(ClassThatImplementsIAsyncEnumerable))]
+        [JsonSerializable(typeof(ClassWithType<ClassThatImplementsIAsyncEnumerable>))]
+        [JsonSerializable(typeof(ClassWithAsyncEnumerableConverter))]
+
+#if NETCOREAPP
+        [JsonSerializable(typeof(DateOnly))]
+        [JsonSerializable(typeof(ClassWithType<DateOnly>))]
+        [JsonSerializable(typeof(TimeOnly))]
+        [JsonSerializable(typeof(ClassWithType<TimeOnly>))]
+#endif
+        internal sealed partial class UnsupportedTypesTestsContext_Metadata : JsonSerializerContext
+        {
+        }
+    }
+
+    public sealed partial class UnsupportedTypesTests_Default : UnsupportedTypesTests
+    {
+        public UnsupportedTypesTests_Default() : base(
+            new StringSerializerWrapper(
+                UnsupportedTypesTestsContext_Default.Default,
+                (options) => new UnsupportedTypesTestsContext_Default(options)),
+            supportsJsonPathOnSerialize: false)
+        {
+        }
+
+        // Supported types:
+        [JsonSerializable(typeof(int))]
+        [JsonSerializable(typeof(ClassWithIntPtrConverter))]
+        // Unsupported types:
+        [JsonSerializable(typeof(Type))]
+        [JsonSerializable(typeof(ClassWithType<Type>))]
+        [JsonSerializable(typeof(SerializationInfo))]
+        [JsonSerializable(typeof(ClassWithType<SerializationInfo>))]
+        [JsonSerializable(typeof(IntPtr))]
+        [JsonSerializable(typeof(ClassWithType<IntPtr>))]
+        [JsonSerializable(typeof(ClassWithIntPtr))]
+        [JsonSerializable(typeof(IntPtr?))]
+        [JsonSerializable(typeof(ClassWithType<IntPtr?>))]
+        [JsonSerializable(typeof(UIntPtr))]
+        [JsonSerializable(typeof(ClassWithType<UIntPtr>))]
+        [JsonSerializable(typeof(IAsyncEnumerable<int>))]
+        [JsonSerializable(typeof(ClassWithType<IAsyncEnumerable<int>>))]
+        [JsonSerializable(typeof(ClassThatImplementsIAsyncEnumerable))]
+        [JsonSerializable(typeof(ClassWithType<ClassThatImplementsIAsyncEnumerable>))]
+        [JsonSerializable(typeof(ClassWithAsyncEnumerableConverter))]
+#if NETCOREAPP
+        [JsonSerializable(typeof(DateOnly))]
+        [JsonSerializable(typeof(ClassWithType<DateOnly>))]
+        [JsonSerializable(typeof(TimeOnly))]
+        [JsonSerializable(typeof(ClassWithType<TimeOnly>))]
+#endif
+        internal sealed partial class UnsupportedTypesTestsContext_Default : JsonSerializerContext
+        {
+        }
+    }
+}
index 610accb..f0bdb59 100644 (file)
@@ -62,6 +62,7 @@
     <Compile Include="..\Common\TestClasses\TestClasses.SimpleTestStruct.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.SimpleTestStruct.cs" />
     <Compile Include="..\Common\TestClasses\TestClasses.SimpleTestStructWithFields.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.SimpleTestStructWithFields.cs" />
     <Compile Include="..\Common\TestClasses\TestClasses.ValueTypedMember.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.ValueTypedMember.cs" />
+    <Compile Include="..\Common\UnsupportedTypesTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\UnsupportedTypesTests.cs" />
     <Compile Include="ContextClasses.cs" />
     <Compile Include="JsonSerializerContextTests.cs" />
     <Compile Include="Serialization\CollectionTests.cs" />
@@ -76,6 +77,7 @@
     <Compile Include="SerializationLogicTests.cs" />
     <Compile Include="Serialization\PropertyNameTests.cs" />
     <Compile Include="Serialization\PropertyVisibilityTests.cs" />
+    <Compile Include="Serialization\UnsupportedTypesTests.cs" />
     <Compile Include="TestClasses.cs" />
     <Compile Include="TestClasses.CustomConverters.cs" />
   </ItemGroup>
index 27cf7fb..f2be092 100644 (file)
@@ -248,19 +248,13 @@ namespace System.Text.Json.SourceGeneration.Tests
     public struct ClassWithCustomConverterPropertyFactory
     {
         [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory
-        public SampleEnum MyEnum { get; set; }
+        public Serialization.Tests.SampleEnum MyEnum { get; set; }
     }
 
     public struct StructWithCustomConverterPropertyFactory
     {
         [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory
-        public SampleEnum MyEnum { get; set; }
-    }
-
-    public enum SampleEnum
-    {
-        A = 1,
-        B = 2
+        public Serialization.Tests.SampleEnum MyEnum { get; set; }
     }
 
     [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid
index cf1b43a..fcd13f9 100644 (file)
@@ -512,92 +512,6 @@ namespace System.Text.Json.Serialization.Tests
             Assert.DoesNotContain("Path: $", ex.Message);
         }
 
-        [Fact]
-        public static void DeserializeUnsupportedType()
-        {
-            // Any test payload is fine.
-            string json = @"""Some string""";
-
-            RunTest<Type>(json);
-            RunTest<SerializationInfo>(json);
-            RunTest<IntPtr>(json);
-            RunTest<UIntPtr>(json);
-#if NETCOREAPP
-            RunTest<DateOnly>(json);
-            RunTest<TimeOnly>(json);
-#endif
-
-            void RunTest<T>(string json)
-            {
-                string fullName = typeof(T).FullName;
-
-                NotSupportedException ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<T>(json));
-                string exAsStr = ex.ToString();
-                Assert.Contains(fullName, exAsStr);
-                Assert.Contains("$", exAsStr);
-
-                json = $@"{{""Prop"":{json}}}";
-
-                ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ClassWithType<T>>(json));
-                exAsStr = ex.ToString();
-                Assert.Contains(fullName, exAsStr);
-                Assert.Contains("$.Prop", exAsStr);
-
-                // NSE is not thrown because the serializer handles null.
-                if (!typeof(T).IsValueType)
-                {
-                    Assert.Null(JsonSerializer.Deserialize<T>("null"));
-
-                    ClassWithType<T> obj = JsonSerializer.Deserialize<ClassWithType<T>>(@"{""Prop"":null}");
-                    Assert.Null(obj.Prop);
-                }
-            }
-        }
-
-        [Fact]
-        public static void SerializeUnsupportedType()
-        {
-            RunTest(typeof(int));
-            RunTest(new SerializationInfo(typeof(Type), new FormatterConverter()));
-            RunTest((IntPtr)123);
-            RunTest((UIntPtr)123);
-#if NETCOREAPP
-            RunTest(DateOnly.MaxValue);
-            RunTest(TimeOnly.MinValue);
-#endif
-
-            void RunTest<T>(T value)
-            {
-                Type type = typeof(T);
-                string fullName = type.FullName;
-
-                NotSupportedException ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(value));
-                string exAsStr = ex.ToString();
-                Assert.Contains(fullName, exAsStr);
-                Assert.Contains("$", exAsStr);
-
-                ClassWithType<T> obj = new ClassWithType<T> { Prop = value };
-
-                ex = Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(obj));
-                exAsStr = ex.ToString();
-                Assert.Contains(fullName, exAsStr);
-                Assert.Contains("$.Prop", exAsStr);
-
-                if (!type.IsValueType)
-                {
-                    string serialized = JsonSerializer.Serialize((T)(object)null);
-                    Assert.Equal("null", serialized);
-
-                    obj.Prop = (T)(object)null;
-                    serialized = JsonSerializer.Serialize(obj);
-                    Assert.Equal(@"{""Prop"":null}", serialized);
-
-                    serialized = JsonSerializer.Serialize(obj, new JsonSerializerOptions { IgnoreNullValues = true });
-                    Assert.Equal(@"{}", serialized);
-                }
-            }
-        }
-
         [Theory]
         [InlineData(typeof(ClassWithBadCtor))]
         [InlineData(typeof(StructWithBadCtor))]
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/UnsupportedTypesTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/UnsupportedTypesTests.cs
new file mode 100644 (file)
index 0000000..8de7615
--- /dev/null
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json.Serialization.Tests
+{
+    public sealed partial class UnsupportedTypesTestsDynamic : UnsupportedTypesTests
+    {
+        public UnsupportedTypesTestsDynamic() : base(
+            JsonSerializerWrapperForString.StringSerializer,
+            supportsJsonPathOnSerialize: true)
+        {
+        }
+    }
+}
index a59430f..a5445b0 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>$(NetCoreAppCurrent);net461</TargetFrameworks>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -64,6 +64,7 @@
     <Compile Include="..\Common\TestClasses\TestClasses.SimpleTestStruct.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.SimpleTestStruct.cs" />
     <Compile Include="..\Common\TestClasses\TestClasses.SimpleTestStructWithFields.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.SimpleTestStructWithFields.cs" />
     <Compile Include="..\Common\TestClasses\TestClasses.ValueTypedMember.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\TestClasses\TestClasses.ValueTypedMember.cs" />
+    <Compile Include="..\Common\UnsupportedTypesTests.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\UnsupportedTypesTests.cs" />
     <Compile Include="BitStackTests.cs" />
     <Compile Include="BufferFactory.cs" />
     <Compile Include="BufferSegment.cs" />
     <Compile Include="Serialization\Stream.ReadTests.cs" />
     <Compile Include="Serialization\Stream.WriteTests.cs" />
     <Compile Include="Serialization\TestData.cs" />
+    <Compile Include="Serialization\UnsupportedTypesTests.cs" />
     <Compile Include="Serialization\Value.ReadTests.cs" />
     <Compile Include="Serialization\Value.WriteTests.cs" />
     <Compile Include="Serialization\WriteValueTests.cs" />