Skip to content

Commit 83d5f57

Browse files
[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 <steveharter@users.noreply.github.com>
1 parent 8b70244 commit 83d5f57

19 files changed

+497
-116
lines changed

src/libraries/System.Text.Json/gen/ClassType.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@ namespace System.Text.Json.SourceGeneration
99
{
1010
internal enum ClassType
1111
{
12+
/// <summary>
13+
/// Types that are not supported yet by source gen including types with constructor parameters.
14+
/// </summary>
1215
TypeUnsupportedBySourceGen = 0,
1316
Object = 1,
1417
KnownType = 2,
15-
TypeWithDesignTimeProvidedCustomConverter = 3,
16-
Enumerable = 4,
17-
Dictionary = 5,
18-
Nullable = 6,
19-
Enum = 7
18+
/// <summary>
19+
/// Known types such as System.Type and System.IntPtr that throw NotSupportedException.
20+
/// </summary>
21+
KnownUnsupportedType = 3,
22+
TypeWithDesignTimeProvidedCustomConverter = 4,
23+
Enumerable = 5,
24+
Dictionary = 6,
25+
Nullable = 7,
26+
Enum = 8
2027
}
2128
}

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ private sealed partial class Emitter
6464
private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues";
6565
private const string JsonPropertyInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfo";
6666
private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo";
67+
private const string NotSupportedExceptionTypeRef = "global::System.NotSupportedException";
6768

6869
private static DiagnosticDescriptor TypeNotSupported { get; } = new DiagnosticDescriptor(
6970
id: "SYSLIB1030",
@@ -254,6 +255,11 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec)
254255
}
255256
}
256257
break;
258+
case ClassType.KnownUnsupportedType:
259+
{
260+
source = GenerateForUnsupportedType(typeGenerationSpec);
261+
}
262+
break;
257263
case ClassType.TypeUnsupportedBySourceGen:
258264
{
259265
_sourceProductionContext.ReportDiagnostic(
@@ -353,6 +359,16 @@ private string GenerateForNullable(TypeGenerationSpec typeMetadata)
353359
return GenerateForType(typeMetadata, metadataInitSource);
354360
}
355361

362+
private string GenerateForUnsupportedType(TypeGenerationSpec typeMetadata)
363+
{
364+
string typeCompilableName = typeMetadata.TypeRef;
365+
string typeFriendlyName = typeMetadata.TypeInfoPropertyName;
366+
367+
string metadataInitSource = $"_{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, {JsonMetadataServicesTypeRef}.GetUnsupportedTypeConverter<{typeCompilableName}>());";
368+
369+
return GenerateForType(typeMetadata, metadataInitSource);
370+
}
371+
356372
private string GenerateForEnum(TypeGenerationSpec typeMetadata)
357373
{
358374
string typeCompilableName = typeMetadata.TypeRef;

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ private sealed class Parser
3636
private const string JsonSerializerAttributeFullName = "System.Text.Json.Serialization.JsonSerializableAttribute";
3737
private const string JsonSourceGenerationOptionsAttributeFullName = "System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute";
3838

39+
private const string DateOnlyFullName = "System.DateOnly";
40+
private const string TimeOnlyFullName = "System.TimeOnly";
41+
private const string IAsyncEnumerableFullName = "System.Collections.Generic.IAsyncEnumerable`1";
42+
3943
private readonly Compilation _compilation;
4044
private readonly SourceProductionContext _sourceProductionContext;
4145
private readonly MetadataLoadContextInternal _metadataLoadContext;
@@ -74,8 +78,20 @@ private sealed class Parser
7478
private readonly Type? _versionType;
7579
private readonly Type? _jsonElementType;
7680

81+
// Unsupported types
82+
private readonly Type _typeType;
83+
private readonly Type _serializationInfoType;
84+
private readonly Type _intPtrType;
85+
private readonly Type _uIntPtrType;
86+
87+
// Unsupported types that may not resolve
88+
private readonly Type? _iAsyncEnumerableGenericType;
89+
private readonly Type? _dateOnlyType;
90+
private readonly Type? _timeOnlyType;
91+
7792
private readonly HashSet<Type> _numberTypes = new();
7893
private readonly HashSet<Type> _knownTypes = new();
94+
private readonly HashSet<Type> _knownUnsupportedTypes = new();
7995

8096
/// <summary>
8197
/// Type information for member types in input object graphs.
@@ -142,6 +158,15 @@ public Parser(Compilation compilation, in SourceProductionContext sourceProducti
142158
_versionType = _metadataLoadContext.Resolve(typeof(Version));
143159
_jsonElementType = _metadataLoadContext.Resolve(JsonElementFullName);
144160

161+
// Unsupported types.
162+
_typeType = _metadataLoadContext.Resolve(typeof(Type));
163+
_serializationInfoType = _metadataLoadContext.Resolve(typeof(Runtime.Serialization.SerializationInfo));
164+
_intPtrType = _metadataLoadContext.Resolve(typeof(IntPtr));
165+
_uIntPtrType = _metadataLoadContext.Resolve(typeof(UIntPtr));
166+
_iAsyncEnumerableGenericType = _metadataLoadContext.Resolve(IAsyncEnumerableFullName);
167+
_dateOnlyType = _metadataLoadContext.Resolve(DateOnlyFullName);
168+
_timeOnlyType = _metadataLoadContext.Resolve(TimeOnlyFullName);
169+
145170
PopulateKnownTypes();
146171
}
147172

@@ -765,6 +790,11 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
765790
collectionValueType = _objectType;
766791
}
767792
}
793+
else if (_knownUnsupportedTypes.Contains(type) ||
794+
ImplementsIAsyncEnumerableInterface(type))
795+
{
796+
classType = ClassType.KnownUnsupportedType;
797+
}
768798
else
769799
{
770800
bool useDefaultCtorInAnnotatedStructs = !type.IsKeyValuePair(_keyValuePair);
@@ -893,6 +923,16 @@ void CacheMemberHelper()
893923
return typeMetadata;
894924
}
895925

926+
private bool ImplementsIAsyncEnumerableInterface(Type type)
927+
{
928+
if (_iAsyncEnumerableGenericType == null)
929+
{
930+
return false;
931+
}
932+
933+
return type.GetCompatibleGenericInterface(_iAsyncEnumerableGenericType) is not null;
934+
}
935+
896936
private Type GetCompatibleGenericBaseClass(Type type, Type baseType)
897937
=> type.GetCompatibleGenericBaseClass(baseType, _objectType);
898938

@@ -1268,8 +1308,10 @@ private void PopulateNumberTypes()
12681308
private void PopulateKnownTypes()
12691309
{
12701310
PopulateNumberTypes();
1311+
12711312
Debug.Assert(_knownTypes != null);
12721313
Debug.Assert(_numberTypes != null);
1314+
Debug.Assert(_knownUnsupportedTypes != null);
12731315

12741316
_knownTypes.UnionWith(_numberTypes);
12751317
_knownTypes.Add(_booleanType);
@@ -1283,6 +1325,21 @@ private void PopulateKnownTypes()
12831325
_knownTypes.Add(_uriType);
12841326
_knownTypes.Add(_versionType);
12851327
_knownTypes.Add(_jsonElementType);
1328+
1329+
_knownUnsupportedTypes.Add(_typeType);
1330+
_knownUnsupportedTypes.Add(_serializationInfoType);
1331+
_knownUnsupportedTypes.Add(_intPtrType);
1332+
_knownUnsupportedTypes.Add(_uIntPtrType);
1333+
1334+
if (_dateOnlyType != null)
1335+
{
1336+
_knownUnsupportedTypes.Add(_dateOnlyType);
1337+
}
1338+
1339+
if (_timeOnlyType != null)
1340+
{
1341+
_knownUnsupportedTypes.Add(_timeOnlyType);
1342+
}
12861343
}
12871344
}
12881345
}

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,7 @@ public static partial class JsonMetadataServices
999999
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; }
10001000
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; }
10011001
public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateValueInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.JsonConverter converter) { throw null; }
1002+
public static System.Text.Json.Serialization.JsonConverter<T> GetUnsupportedTypeConverter<T>() { throw null; }
10021003
public static System.Text.Json.Serialization.JsonConverter<T> GetEnumConverter<T>(System.Text.Json.JsonSerializerOptions options) where T : struct { throw null; }
10031004
public static System.Text.Json.Serialization.JsonConverter<T?> GetNullableConverter<T>(System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> underlyingTypeInfo) where T : struct { throw null; }
10041005
}

src/libraries/System.Text.Json/src/Resources/Strings.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@
492492
<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>
493493
</data>
494494
<data name="SerializeTypeInstanceNotSupported" xml:space="preserve">
495-
<value>Serialization and deserialization of '{0}' instances are not supported and should be avoided since they can lead to security issues.</value>
495+
<value>Serialization and deserialization of '{0}' instances are not supported.</value>
496496
</data>
497497
<data name="JsonIncludeOnNonPublicInvalid" xml:space="preserve">
498498
<value>The non-public property '{0}' on type '{1}' is annotated with 'JsonIncludeAttribute' which is invalid.</value>

src/libraries/System.Text.Json/src/System.Text.Json.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,6 @@ System.Text.Json.Utf8JsonReader</PackageDescription>
179179
<Compile Include="System\Text\Json\Serialization\Converters\Value\DateTimeConverter.cs" />
180180
<Compile Include="System\Text\Json\Serialization\Converters\Value\DateTimeOffsetConverter.cs" />
181181
<Compile Include="System\Text\Json\Serialization\Converters\Value\DecimalConverter.cs" />
182-
<Compile Include="System\Text\Json\Serialization\Converters\Value\DisallowedTypeConverter.cs" />
183-
<Compile Include="System\Text\Json\Serialization\Converters\Value\DisallowedTypeConverterFactory.cs" />
184182
<Compile Include="System\Text\Json\Serialization\Converters\Value\DoubleConverter.cs" />
185183
<Compile Include="System\Text\Json\Serialization\Converters\Value\EnumConverter.cs" />
186184
<Compile Include="System\Text\Json\Serialization\Converters\Value\EnumConverterFactory.cs" />
@@ -201,6 +199,8 @@ System.Text.Json.Utf8JsonReader</PackageDescription>
201199
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt16Converter.cs" />
202200
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt32Converter.cs" />
203201
<Compile Include="System\Text\Json\Serialization\Converters\Value\UInt64Converter.cs" />
202+
<Compile Include="System\Text\Json\Serialization\Converters\Value\UnsupportedTypeConverter.cs" />
203+
<Compile Include="System\Text\Json\Serialization\Converters\Value\UnsupportedTypeConverterFactory.cs" />
204204
<Compile Include="System\Text\Json\Serialization\Converters\Value\UriConverter.cs" />
205205
<Compile Include="System\Text\Json\Serialization\Converters\Value\VersionConverter.cs" />
206206
<Compile Include="System\Text\Json\Serialization\IgnoreReferenceHandler.cs" />
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace System.Text.Json.Serialization.Converters
55
{
6-
internal sealed class DisallowedTypeConverter<T> : JsonConverter<T>
6+
internal sealed class UnsupportedTypeConverter<T> : JsonConverter<T>
77
{
88
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
99
throw new NotSupportedException(SR.Format(SR.SerializeTypeInstanceNotSupported, typeof(T).FullName));
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66

77
namespace System.Text.Json.Serialization.Converters
88
{
9-
internal sealed class DisallowedTypeConverterFactory : JsonConverterFactory
9+
internal sealed class UnsupportedTypeConverterFactory : JsonConverterFactory
1010
{
1111
public override bool CanConvert(Type type)
1212
{
13-
// If a value type is added, also add a test that
14-
// shows NSE is thrown when Nullable<T> is (de)serialized.
13+
// If a type is added, also add to the SourceGeneration project.
1514

1615
return
1716
// There's no safe way to construct a Type from untrusted user input.
@@ -42,7 +41,7 @@ public override bool CanConvert(Type type)
4241
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
4342
{
4443
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
45-
typeof(DisallowedTypeConverter<>).MakeGenericType(type),
44+
typeof(UnsupportedTypeConverter<>).MakeGenericType(type),
4645
BindingFlags.Instance | BindingFlags.Public,
4746
binder: null,
4847
args: null,

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Converters.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ private void RootBuiltInConverters()
3434
s_defaultFactoryConverters = new JsonConverter[]
3535
{
3636
// Check for disallowed types.
37-
new DisallowedTypeConverterFactory(),
37+
new UnsupportedTypeConverterFactory(),
3838
// Nullable converter should always be next since it forwards to any nullable type.
3939
new NullableConverterFactory(),
4040
new EnumConverterFactory(),

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Converters.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ public static partial class JsonMetadataServices
150150
public static JsonConverter<Version> VersionConverter => s_versionConverter ??= new VersionConverter();
151151
private static JsonConverter<Version>? s_versionConverter;
152152

153+
/// <summary>
154+
/// Creates a <see cref="JsonConverter{T}"/> instance that throws <see cref="NotSupportedException"/>.
155+
/// </summary>
156+
/// <typeparam name="T">The generic definition for the type.</typeparam>
157+
/// <returns></returns>
158+
public static JsonConverter<T> GetUnsupportedTypeConverter<T>()
159+
=> new UnsupportedTypeConverter<T>();
160+
153161
/// <summary>
154162
/// Creates a <see cref="JsonConverter{T}"/> instance that converts <typeparamref name="T"/> values.
155163
/// </summary>

0 commit comments

Comments
 (0)