-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
[Debug] IndexOutOfRangeException when extension data doesn't have two generic parameters #33014
[Debug] IndexOutOfRangeException when extension data doesn't have two generic parameters #33014
Conversation
@@ -109,14 +109,15 @@ public static partial class JsonSerializer | |||
IDictionary? extensionData = (IDictionary?)jsonPropertyInfo.GetValueAsObject(obj); | |||
if (extensionData == null) | |||
{ | |||
#if DEBUG |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so I wasn't sure if it was better to put this discussion in an issue or PR, but chose PR to better discuss the code changes
A PR is a fine choice to discuss code changes, particularly to fix an issue.
should I expect that #if DEBUG would change the behavior of tests running in Debug configuration?
Simply wrapping the Debug.Assert
calls as this PR does has no effect. They only run in debug mode to begin with.
This #if directive allows us to execute code that will only run in debug
builds of System.Text.Json. The reason I had it in https://gist.github.com/layomia/d77aeea5d3250785de5a7aa4dee95c46#file-create_data_extension_property-cs-L8-L20 was to make the following assignments which we can use to perform assertions in debug mode.
Type underlyingIDictionaryType = jsonPropertyInfo.DeclaredPropertyType.GetCompatibleGenericInterface(typeof(IDictionary<,>))!;
Type[] genericArgs = underlyingIDictionaryType.GetGenericArguments();
We don't want to do this in release mode as these calls have a non-trivial perf cost.
underlyingIDictionaryType
is the type of IDictionary<,>
the extension data implements. This is the type for which we should perform the checks that the first generic parameter is string
, and the second generic parameter is object
or JsonElement
. Performing these checks on the declared type is what leads to the IndexOutOfRangeException
(because the assertions may not hold).
Is there a reason to keep the Debug.Assert statements here at all?
The reason we want the assertions is to ensure that no code changes upstream that violates our expectations of the type we think we are processing in this method.
If conceptually System.Text.Json supports dictionaries that don't match the conditions tested by the Debug.Assert statements, what is their purpose?
This area of code is only concerned with initializing extension data dictionaries. We support dictionaries with string keys and values of any type, but extension properties must be of types assignable to IDictionary<string, object/JsonElement>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦♂ I only saw the DEBUG
part of the whole gist. This whole thing makes 100% more sense. Will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in e398543
Now, I'm finding that dictionaries that implement IDictionary<string,object>
but not IDictionary
run into an InvalidCastException
when attempting to cast to (IDictionary?)
at
Line 130 in e398543
extensionData = (IDictionary?)jsonPropertyInfo.RuntimeClassInfo.CreateObject(); |
The same cast is used beforehand at:
Lines 110 to 111 in e398543
IDictionary? extensionData = (IDictionary?)jsonPropertyInfo.GetValueAsObject(obj); | |
if (extensionData == null) |
Based on your comment above:
We support dictionaries with string keys and values of any type, but extension properties must be of types assignable to
IDictionary<string, object/JsonElement>
.
IDictionary<TKey,TValue>
does not inherit the non-generic IDictionary
. Must ExtensionData
properties also implement IDictionary
as a conceptual constraint, or is that something that should change?
FWIW, tests pass locally in netcoreapp5.0 if I remove the casts, but I don't know if changing the behavior of the InvalidCastException
is desired or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should remove the casts; particularly because they are to the wrong type. The requirement is IDictionary<string,object/JsonElement>
. The IDictionary
cast must have been an oversight.
We've already verified the type, and are only creating the object here; so we don't need to cast it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in d19fff9
[Fact] | ||
public static void DeserializeIntoGenericDictionaryParameterCount() | ||
{ | ||
// baseline |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no need for this line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 721b807
...ystem.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
Outdated
Show resolved
Hide resolved
Co-Authored-By: Layomi Akinrinade <layomia@gmail.com>
JsonSerializer.Deserialize<ClassWithExtensionPropertyNoGenericParameters>("{\"hello\":\"world\"}"); | ||
JsonSerializer.Deserialize<ClassWithExtensionPropertyOneGenericParameter>("{\"hello\":\"world\"}"); | ||
JsonSerializer.Deserialize<ClassWithExtensionPropertyThreeGenericParameters>("{\"hello\":\"world\"}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should validate the results here.
I am curious, what happens in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than additional test validation, looks good.
Yes, things work as expected in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Merging to avoid CI reset. Will tack on the additional validation in an oncoming PR.
WIP for #33011
@layomia, I added the proposed tests and code change, but I still see the tests failing in debug through both VS and the CLI (EX:
Process terminated. Assertion failed
).I'm new to contributing to the runtime, so I wasn't sure if it was better to put this discussion in an issue or PR, but chose PR to better discuss the code changes. Also, pardon my newbie questions:
#if DEBUG
would change the behavior of tests running in Debug configuration?Debug.Assert
statements here at all? If conceptuallySystem.Text.Json
supports dictionaries that don't match the conditions tested by theDebug.Assert
statements, what is their purpose?