Skip to content

Conversation

@layomia
Copy link
Contributor

@layomia layomia commented Aug 26, 2020

Ports #40914 to release/5.0.

Fixes #40878, #36329, and #41146 in .NET 5.

  • Support polymorphic converters that return value types (TypeToConvert == typeof(object) or an interface).
    • Works for both Nullable and non-Nullable value types.
  • Validate the return value from converters so they don't return an incompatible object on deserialization.

Customer impact

The serializer now has correct semantics to handle polymorphic converters, and will (de)serialize as expected in valid scenarios, and will throw the right exceptions for invalid cases. Exceptions avoided are InvalidProgramException and AccessViolationException which were leaked due to the serializer generating invalid IL for property getters/setters for these scenarios. Also prevents behavior regressions for previously supported polymorphic converter usages.

Testing and risk

The changes in this PR are specific to handling when polymorphic custom converters for value types are used i.e the type to convert is a struct, while the provided converter returns typeof(object) or an interface. This is an edge case scenario and will not affect the vast majority of custom converter usages/implementations.

This change is well tested. Tests have been added for various permutations of previously problematic inputs to the serializer.

…s returned by custom converters (dotnet#40914)

* IL emit Box/Unbox in member access

* Test case for object converter

* Test case for class with primitive members and object converter

* Address feedback

* Fixed issues with JsonConverter<object>and nullable value-types

* Address feedback

* Fix test for .NETStandard version

* Fix dotnet#41146

* Accidentally commited the local test hack

* Throw JsonException when a deserialized value cannot be assigned to the property/field

* Fix merge

* Fix merge again

* Undo Fix dotnet#41146

* Consolidate nullable annotations

* Addressed feedback

* Addressed feedback
Copy link
Contributor

@steveharter steveharter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; direct port

@layomia layomia requested a review from ericstj August 26, 2020 17:34
@ericstj
Copy link
Member

ericstj commented Aug 26, 2020

I agree this meets the bar for RC.

Tests have been added for various permutations of previously problematic inputs to the serializer.

Thanks for the info on testing: I assume we've done a pretty thorough run-through of the missing scenarios and increased coverage. Did you make sure to examine the 3.x behavior when establishing test assertions for these new cases?

Is there any perf concern that would warrant examining perf numbers before/after this change?

@layomia
Copy link
Contributor Author

layomia commented Aug 27, 2020

I assume we've done a pretty thorough run-through of the missing scenarios and increased coverage.

Yes, @steveharter and I reviewed the missing polymorphic converter scenarios closely offline and believe this PR and the added tests give us good coverage.

Did you make sure to examine the 3.x behavior when establishing test assertions for these new cases?

Yes, I manually ran each new test on the 3.1 build locally and verified that we have no behavorial regressions. In multiple cases, we were previously (in 3.1) leaking bad exceptions (InvalidCastException, NullReferenceException), but now we have appropriate support and throw intentional exceptions where applicable.

On the issue of tests, I added 8830d04 to this PR to prevent test issues due to non-deterministic reflection order on serialization.

Is there any perf concern that would warrant examining perf numbers before/after this change?

Wrt. the additional boxing/unboxing in the generated IL to call property getters/setters; this additional IL is only emitted in the very specific and edge-case scenario where a polymorphic converter is used for a value-type property. As such, I didn't expect any regressions to our main benchmarks. I verified this by running the serializer benchmarks; barring benchmark noise, there are no regressions.

The added validation for the value returned by external converters (user-provided custom converters) did cause a small throughput perf regression (~6%). This is mitigated with a new commit (8e6478e) by restricting the new checks to only happen when the converter is external and polymorphic.


The new changes will be committed in the 6.0 branch via #41427.

polymorphic_pr_converter_benchmarks.zip
polymorphic_pr_main_benchmarks.zip

@layomia layomia force-pushed the polymorphic_converter_5.0 branch from 8e6478e to a901679 Compare August 27, 2020 21:17
@ericstj
Copy link
Member

ericstj commented Aug 27, 2020

Meets 5.0 bar since it is a regression from 3.1.

Seems like we've done a sufficient amount of risk mitigation. Thank you for considering my requests.

I'd like one more sign-off on the final change since it has a couple small diffs with what @steveharter previously reviewed. @jozkee could you give it a look?

Copy link
Member

@jozkee jozkee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already discussed #41427 with @layomia offline and the changes in the last two commits are identical.

Lgtm.

@layomia
Copy link
Contributor Author

layomia commented Aug 27, 2020

Test failures unrelated:

System.Net.Http.Functional.Tests
System.Threading.Tasks.Parallel.Tests

@layomia layomia merged commit d5e0627 into dotnet:release/5.0 Aug 27, 2020
@layomia layomia deleted the polymorphic_converter_5.0 branch August 27, 2020 23:00
@ghost ghost locked as resolved and limited conversation to collaborators Dec 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants