-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Contract metadata cached by the source generator are user-modifiable. #76535
Comments
Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis Issue Details
JsonTypeInfo<Person> typeInfo = SourceGenContext.Default.Person;
foreach (JsonPropertyInfo property in typeInfo.Properties)
{
property.Name = property.Name.ToUpperInvariant();
}
string json = JsonSerializer.Serialize(person, typeInfo);
// Fail: regular-case is serialized instead (because fast-path is taken; metadata should be used instead since we have it).
JsonTestHelper.AssertJsonEqual(@"{""FIRSTNAME"":""Jane"",""LASTNAME"":""Doe""}", json);
Person person = JsonSerializer.Deserialize(json, typeInfo);
Assert.Equal("Jane", person.FirstName);
Assert.Equal("Doe", person.LastName); Originally posted by @layomia in #76531 (comment)
|
This is only true until first usage. Make sure to test recursive types here if we decide to fix this. Also I think only .Default JsonTypeInfos should be locked |
There's another issue: making the instances mutable effectively breaks fast-path delegate invalidation so you can end up with inconsistent serialization outputs. This was the original issue highlighted by @layomia's repro: |
A more insidious instantiation of the bug above is that mutating results of JsonTypeInfo foo = MySerializerContext.Default.GetTypeInfo(typeof(MyClass));
foo.Properties.Clear();
Console.WriteLine(MySerializerContext.Default.MyClass.Properties.Count); // 0, properties dropped from a seemingly unrelated instance
[JsonSerializable(typeof(MyClass))]
public partial class MySerializerContext : JsonSerializerContext
{
}
public class MyClass
{
public int A0 { get; set; }
} Bare minimum, we should make sure that modifications don't allow cross-contamination in .NET 7. |
Adding a caching layer specifically for Parallel.For(0, 100, i =>
{
// Simulates multiple threads attempting to independently modify metadata:
JsonTypeInfo typeInfo = MySerializerContext.Default.GetTypeInfo(typeof(MyClass));
typeInfo.Properties[0].Name = typeInfo.Properties[0].Name + ".suffix";
});
// Bar.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix.suffix
Console.WriteLine(MySerializerContext.Default.GetTypeInfo(typeof(MyClass)).Properties[0].Name);
[JsonSerializable(typeof(MyClass))]
public partial class MySerializerContext : JsonSerializerContext
{
}
public class MyClass
{
public int Bar { get; set; }
} |
The inclusion of the contract customization feature exposes a number of APIs that makes it possible for users to modify aspects of the pre-existing
JsonTypeInfo
contract metadata model. However, it seems that we neglected to freeze modifications for instances instantiated and cached by the source generator:At first glance this problem might be considered benign, however it has the potential to create a couple of issues:
The source generator is aggressively caching metadata instances for performance, so this is effectively introducing global mutable state. Changes in one component can cause unforeseen changes in an unrelated context:
or might be the cause of races when multiple threads are independently attempting to modify contracts:
Direct mutation of source gen metadata breaks the fast-path invalidation logic:
We should make sure that source generated metadata properties on
JsonSerializerContext
are locked for modification. Any change should ensure that theIJsonTypeInfoResolver
implementation still returns mutable instances, so that contract customization scenaria over source gen are not broken.Originally posted by @layomia in #76531 (comment)
The text was updated successfully, but these errors were encountered: