Skip to content

Commit

Permalink
Implement an AppContext compatibility switch re-enabling reflection f…
Browse files Browse the repository at this point in the history
…allback in STJ source generators. (#75615) (#75694)

* Implement an AppContext compatibility switch re-enabling reflection fallback in sourcegen.

* address feedback
  • Loading branch information
eiriktsarpalis committed Sep 16, 2022
1 parent 393e1ab commit f0eb5ce
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
<Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
<Compile Include="..\Common\ReflectionExtensions.cs" Link="Common\System\Text\Json\Serialization\ReflectionExtensions.cs" />
<Compile Include="System\Text\Json\AppContextSwitchHelper.cs" />
<Compile Include="System\Text\Json\BitStack.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.DbRow.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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
{
internal static class AppContextSwitchHelper
{
public static bool IsSourceGenReflectionFallbackEnabled => s_isSourceGenReflectionFallbackEnabled;

private static readonly bool s_isSourceGenReflectionFallbackEnabled =
AppContext.TryGetSwitch(
switchName: "System.Text.Json.Serialization.EnableSourceGenReflectionFallback",
isEnabled: out bool value)
? value : false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -628,14 +628,30 @@ internal void InitializeForReflectionSerializer()
// Even if a resolver has already been specified, we need to root
// the default resolver to gain access to the default converters.
DefaultJsonTypeInfoResolver defaultResolver = DefaultJsonTypeInfoResolver.RootDefaultInstance();
_typeInfoResolver ??= defaultResolver;

switch (_typeInfoResolver)
{
case null:
// Use the default reflection-based resolver if no resolver has been specified.
_typeInfoResolver = defaultResolver;
break;

case JsonSerializerContext ctx when AppContextSwitchHelper.IsSourceGenReflectionFallbackEnabled:
// .NET 6 compatibility mode: enable fallback to reflection metadata for JsonSerializerContext
_effectiveJsonTypeInfoResolver = JsonTypeInfoResolver.Combine(ctx, defaultResolver);
break;
}

IsImmutable = true;
_isInitializedForReflectionSerializer = true;
}

internal bool IsInitializedForReflectionSerializer => _isInitializedForReflectionSerializer;
private volatile bool _isInitializedForReflectionSerializer;

// Only populated in .NET 6 compatibility mode encoding reflection fallback in source gen
private IJsonTypeInfoResolver? _effectiveJsonTypeInfoResolver;

internal void InitializeForMetadataGeneration()
{
if (_typeInfoResolver is null)
Expand All @@ -648,7 +664,7 @@ internal void InitializeForMetadataGeneration()

private JsonTypeInfo? GetTypeInfoNoCaching(Type type)
{
JsonTypeInfo? info = _typeInfoResolver?.GetTypeInfo(type, this);
JsonTypeInfo? info = (_effectiveJsonTypeInfoResolver ?? _typeInfoResolver)?.GetTypeInfo(type, this);

if (info != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,9 +437,18 @@ public static void Options_JsonSerializerContext_DoesNotFallbackToReflection()
}

[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToReflectionConverter()
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData(false)]
[InlineData(true)]
public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToReflectionConverter(bool isCompatibilitySwitchExplicitlyDisabled)
{
var options = new RemoteInvokeOptions();

if (isCompatibilitySwitchExplicitlyDisabled)
{
options.RuntimeConfigurationOptions.Add("System.Text.Json.Serialization.EnableSourceGenReflectionFallback", false);
}

RemoteExecutor.Invoke(static () =>
{
JsonContext context = JsonContext.Default;
Expand All @@ -460,7 +469,40 @@ public static void Options_JsonSerializerContext_GetConverter_DoesNotFallBackToR
Assert.Throws<NotSupportedException>(() => context.Options.GetConverter(typeof(MyClass)));
Assert.Throws<NotSupportedException>(() => JsonSerializer.Serialize(unsupportedValue, context.Options));
}).Dispose();
}, options).Dispose();
}

[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public static void Options_JsonSerializerContext_Net6CompatibilitySwitch_FallsBackToReflectionResolver()
{
var options = new RemoteInvokeOptions
{
RuntimeConfigurationOptions =
{
["System.Text.Json.Serialization.EnableSourceGenReflectionFallback"] = true
}
};

RemoteExecutor.Invoke(static () =>
{
var unsupportedValue = new MyClass { Value = "value" };
// JsonSerializerContext does not return metadata for the type
Assert.Null(JsonContext.Default.GetTypeInfo(typeof(MyClass)));
// Serialization fails using the JsonSerializerContext overload
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(unsupportedValue, unsupportedValue.GetType(), JsonContext.Default));
// Serialization uses reflection fallback using the JsonSerializerOptions overload
string json = JsonSerializer.Serialize(unsupportedValue, JsonContext.Default.Options);
JsonTestHelper.AssertJsonEqual("""{"Value":"value", "Thing":null}""", json);
// A converter can be resolved when looking up JsonSerializerOptions
JsonConverter converter = JsonContext.Default.Options.GetConverter(typeof(MyClass));
Assert.IsAssignableFrom<JsonConverter<MyClass>>(converter);
}, options).Dispose();
}

[Fact]
Expand Down

0 comments on commit f0eb5ce

Please sign in to comment.