From: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 17:27:16 +0000 (-0600) Subject: [release/6.0] Throw on unsupported types in src gen (#58983) X-Git-Tag: accepted/tizen/unified/20220110.054933~161 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=83d5f57dbce6949f1c0427f45a3cb05af1a3e863;p=platform%2Fupstream%2Fdotnet%2Fruntime.git [release/6.0] Throw on unsupported types in src gen (#58983) * 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 --- diff --git a/src/libraries/System.Text.Json/gen/ClassType.cs b/src/libraries/System.Text.Json/gen/ClassType.cs index d0df943..80029b5 100644 --- a/src/libraries/System.Text.Json/gen/ClassType.cs +++ b/src/libraries/System.Text.Json/gen/ClassType.cs @@ -9,13 +9,20 @@ namespace System.Text.Json.SourceGeneration { internal enum ClassType { + /// + /// Types that are not supported yet by source gen including types with constructor parameters. + /// TypeUnsupportedBySourceGen = 0, Object = 1, KnownType = 2, - TypeWithDesignTimeProvidedCustomConverter = 3, - Enumerable = 4, - Dictionary = 5, - Nullable = 6, - Enum = 7 + /// + /// Known types such as System.Type and System.IntPtr that throw NotSupportedException. + /// + KnownUnsupportedType = 3, + TypeWithDesignTimeProvidedCustomConverter = 4, + Enumerable = 5, + Dictionary = 6, + Nullable = 7, + Enum = 8 } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index b2b3488..2f9a64f 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -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; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 2c5f3eb..199c0db 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -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 _numberTypes = new(); private readonly HashSet _knownTypes = new(); + private readonly HashSet _knownUnsupportedTypes = new(); /// /// 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); + } } } } diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index d4b05c6..09340a2 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -999,6 +999,7 @@ namespace System.Text.Json.Serialization.Metadata public static JsonTypeInfo CreateStackInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc) where TCollection : System.Collections.Generic.Stack { throw null; } public static JsonTypeInfo CreateStackOrQueueInfo(System.Text.Json.JsonSerializerOptions options, System.Func? createObjectFunc, System.Text.Json.Serialization.Metadata.JsonTypeInfo elementInfo, System.Text.Json.Serialization.JsonNumberHandling numberHandling, System.Action? serializeFunc, System.Action addFunc) where TCollection : System.Collections.IEnumerable { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateValueInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; } + public static System.Text.Json.Serialization.JsonConverter GetUnsupportedTypeConverter() { throw null; } public static System.Text.Json.Serialization.JsonConverter GetEnumConverter(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; } public static System.Text.Json.Serialization.JsonConverter GetNullableConverter(System.Text.Json.Serialization.Metadata.JsonTypeInfo underlyingTypeInfo) where T : struct { throw null; } } diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 7fc1bd8..e1cb127 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -492,7 +492,7 @@ 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. - Serialization and deserialization of '{0}' instances are not supported and should be avoided since they can lead to security issues. + Serialization and deserialization of '{0}' instances are not supported. The non-public property '{0}' on type '{1}' is annotated with 'JsonIncludeAttribute' which is invalid. diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index d18d2c0..4cfed9b 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -179,8 +179,6 @@ System.Text.Json.Utf8JsonReader - - @@ -201,6 +199,8 @@ System.Text.Json.Utf8JsonReader + + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DisallowedTypeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs similarity index 89% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DisallowedTypeConverter.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs index 27ed644..d4a432e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DisallowedTypeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs @@ -3,7 +3,7 @@ namespace System.Text.Json.Serialization.Converters { - internal sealed class DisallowedTypeConverter : JsonConverter + internal sealed class UnsupportedTypeConverter : JsonConverter { public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotSupportedException(SR.Format(SR.SerializeTypeInstanceNotSupported, typeof(T).FullName)); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DisallowedTypeConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverterFactory.cs similarity index 88% rename from src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DisallowedTypeConverterFactory.cs rename to src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverterFactory.cs index e771889..fdc632d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DisallowedTypeConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverterFactory.cs @@ -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 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, diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs index 912cabe..4276d37 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs @@ -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(), diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs index 39ad153..2f3b8e7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs @@ -151,6 +151,14 @@ namespace System.Text.Json.Serialization.Metadata private static JsonConverter? s_versionConverter; /// + /// Creates a instance that throws . + /// + /// The generic definition for the type. + /// + public static JsonConverter GetUnsupportedTypeConverter() + => new UnsupportedTypeConverter(); + + /// /// Creates a instance that converts values. /// /// The generic definition for the enum type. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 7312f6f..d04de0c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -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 index 0000000..35359ca --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/UnsupportedTypesTests.cs @@ -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(json); + await RunTest(json); + await RunTest(json); + await RunTest(json); // One nullable variation. + await RunTest(json); +#if NETCOREAPP + await RunTest(json); + await RunTest(json); +#endif +#if BUILDING_SOURCE_GENERATOR_TESTS + await RunTest>(json); + await RunTest(json); +#endif + + async Task RunTest(string json) + { + Type type = GetNullableOfTUnderlyingType(typeof(T), out bool isNullableOfT); + string fullName = type.FullName; + + NotSupportedException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper(json)); + string exAsStr = ex.ToString(); + Assert.Contains(fullName, exAsStr); + Assert.Contains("$", exAsStr); + + json = $@"{{""Prop"":{json}}}"; + + ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.DeserializeWrapper>(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("null")); + + json = $@"{{""Prop"":null}}"; + ClassWithType obj = await JsonSerializerWrapperForString.DeserializeWrapper>(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(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 value) + { + Type type = GetNullableOfTUnderlyingType(typeof(T), out bool isNullableOfT); + string fullName = type.FullName; + + NotSupportedException ex = await Assert.ThrowsAsync(async () => await JsonSerializerWrapperForString.SerializeWrapper(value)); + string exAsStr = ex.ToString(); + Assert.Contains(fullName, exAsStr); + Assert.Contains("$", exAsStr); + + ClassWithType obj = new ClassWithType { Prop = value }; + + ex = await Assert.ThrowsAsync(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)(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 + { + 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("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(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(Json); + Assert.Equal(42, obj.MyIntPtr.ToInt32()); + } + + public class ClassWithAsyncEnumerableConverter + { + [JsonConverter(typeof(AsyncEnumerableConverter))] + public ClassThatImplementsIAsyncEnumerable MyAsyncEnumerable { get; set; } + } + + public class ClassThatImplementsIAsyncEnumerable : IAsyncEnumerable + { + public string Status { get; set; } = "Created"; + + // Should not be called. + IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken) => throw new NotImplementedException(); + } + + public class AsyncEnumerableConverter : JsonConverter + { + 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("[]", 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(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(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; + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs index d10300c..8681cc7 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs @@ -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 index 0000000..fbe343e --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/UnsupportedTypesTests.cs @@ -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))] + [JsonSerializable(typeof(SerializationInfo))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(IntPtr))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(ClassWithIntPtr))] + [JsonSerializable(typeof(IntPtr?))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(UIntPtr))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(IAsyncEnumerable))] + [JsonSerializable(typeof(ClassWithType>))] + [JsonSerializable(typeof(ClassThatImplementsIAsyncEnumerable))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(ClassWithAsyncEnumerableConverter))] + +#if NETCOREAPP + [JsonSerializable(typeof(DateOnly))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(TimeOnly))] + [JsonSerializable(typeof(ClassWithType))] +#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))] + [JsonSerializable(typeof(SerializationInfo))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(IntPtr))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(ClassWithIntPtr))] + [JsonSerializable(typeof(IntPtr?))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(UIntPtr))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(IAsyncEnumerable))] + [JsonSerializable(typeof(ClassWithType>))] + [JsonSerializable(typeof(ClassThatImplementsIAsyncEnumerable))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(ClassWithAsyncEnumerableConverter))] +#if NETCOREAPP + [JsonSerializable(typeof(DateOnly))] + [JsonSerializable(typeof(ClassWithType))] + [JsonSerializable(typeof(TimeOnly))] + [JsonSerializable(typeof(ClassWithType))] +#endif + internal sealed partial class UnsupportedTypesTestsContext_Default : JsonSerializerContext + { + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj index 610accb..f0bdb59 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -62,6 +62,7 @@ + @@ -76,6 +77,7 @@ + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs index 27cf7fb..f2be092 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs @@ -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 diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs index cf1b43a..fcd13f9 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/ExceptionTests.cs @@ -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(json); - RunTest(json); - RunTest(json); - RunTest(json); -#if NETCOREAPP - RunTest(json); - RunTest(json); -#endif - - void RunTest(string json) - { - string fullName = typeof(T).FullName; - - NotSupportedException ex = Assert.Throws(() => JsonSerializer.Deserialize(json)); - string exAsStr = ex.ToString(); - Assert.Contains(fullName, exAsStr); - Assert.Contains("$", exAsStr); - - json = $@"{{""Prop"":{json}}}"; - - ex = Assert.Throws(() => JsonSerializer.Deserialize>(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("null")); - - ClassWithType obj = JsonSerializer.Deserialize>(@"{""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 value) - { - Type type = typeof(T); - string fullName = type.FullName; - - NotSupportedException ex = Assert.Throws(() => JsonSerializer.Serialize(value)); - string exAsStr = ex.ToString(); - Assert.Contains(fullName, exAsStr); - Assert.Contains("$", exAsStr); - - ClassWithType obj = new ClassWithType { Prop = value }; - - ex = Assert.Throws(() => 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 index 0000000..8de7615 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/UnsupportedTypesTests.cs @@ -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) + { + } + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index a59430f..a5445b0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);net461 true @@ -64,6 +64,7 @@ + @@ -179,6 +180,7 @@ +