-
Notifications
You must be signed in to change notification settings - Fork 5.3k
[wasm][debugger] Fix DebuggerTypeProxy for structs and for classes with multiple constructors #77039
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
Conversation
|
Tagging subscribers to this area: @thaystg Issue DetailsAs a result of the discussion here: 14a2be2#r995534127 I separated the test fix into this PR.
|
|
What's the failure with the test? Is this related to #76876 ? |
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.
background:
List<T>allocates a private array (_items) ofCapacitysize. The size for that can be obtained fromCapacity, or_items.Length.- It also has a private
int _size, which has the number of actual elements in the list (== list.Count). And this changes when you add/remove elements to the list. - At some threshold,
_itemsarray is reallocated to have a bigger size, and the elements are copied over.
runtime/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs
Lines 25 to 26 in 51e3afb
| internal T[] _items; // Do not rename (binary serialization) | |
| internal int _size; // Do not rename (binary serialization) |
Based on that:
maxSizeis really the size of the internal array_itemshere makes sense, since that is a fixed private field inList<T>(specifically inList<T>though)- Also, the type has
[DebuggerTypeProxy(typeof(ICollectionDebugView<>))]which has:
runtime/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ICollectionDebugView.cs
Lines 19 to 28 in 51e3afb
| [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] | |
| public T[] Items | |
| { | |
| get | |
| { | |
| T[] items = new T[_collection.Count]; | |
| _collection.CopyTo(items, 0); | |
| return items; | |
| } | |
| } |
.. which returns an array of size == list.Count .
- So, is this using the type proxy, or are we reading from the actual object? If it's from the former then
Items, and for the latter_itemswould be relevant. Feel free to correct me, I might be missing something.
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 don't see any errors in the above. I only cannot understand what mechanism makes the decision if to use Items or _items. I don't think that it's too important but if we knew the rule it would help in simplifying the code. I think I will add some comments to the current code
|
Oh, found the comment - #75958 (comment) . |
|
Actually it's strange. I reverted the changes on #75958 that were fixing this failure and it's passing the CI. It might be something platform-related then because on my machine they are failing after the revert. |
|
I tried to look at this, and found:
About the the corresponding test:
JObject[] expectedArrayElements = new[]
{
TNumber(11),
TNumber(22)
};
JObject[] expectedListRootHiddenElements = new[]
{
TNumber(1),
TNumber(2)
};
//in Console App names are in []
//adding variable name to make elements unique
for (int i = 0; i < expectedArrayElements.Length; i ++)
{
var actualValObj = GetAndAssertObjectWithName(testRootHiddenProps, $"arrayRootHidden[{i}]");
await CheckValue(actualValObj["value"], expectedArrayElements[i], "x");
}
await CheckProps(refArrayProp, expectedArrayElements, "arrayRootHidden");
for (int i = 0; i < expectedListRootHiddenElements.Length; i ++)
{
var actualValObj = GetAndAssertObjectWithName(testRootHiddenProps, $"listRootHidden[{i}]");
await CheckValue(actualValObj["value"], expectedListRootHiddenElements[i], "listRootHidden");
}
await CheckProps(refListProp, expectedListRootHiddenElements, "listRootHidden");This is the incomplete patch that I was able to come up with. |
EvaluateBrowsableRootHiddena35471d to
bf51e21
Compare
| // a collection - expose elements to be of array scheme | ||
| var memberNamedItems = members | ||
| .Where(m => m["name"]?.Value<string>() == "Items" || m["name"]?.Value<string>() == "_items") | ||
| .Where(m => m["name"]?.Value<string>() == "Items") |
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.
In what cases(example code) would we be executing this block?
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.
All cases in DebuggerTests.EvaluateOnCallFrameTests.EvaluateBrowsableRootHidden because
Error Message:
[] Could not find variable 'listRootHidden[0]'
This PR fixes only the problem with Items vs _items (now it's always Items). But it does not fix the fact that they need to get expanded manually - rootHidden info does not exist on them and cannot be easily extracted. The issue where we address it is this one: #76876.
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.
Is my understanding here correct?:
List<T>has[DebuggerTypeProxy(typeof(ICollectionDebugView<>))], so we look at the proxy- the proxy has
Itemsproperty Itemshas[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)], which isn't working yet ([wasm][debugger] RootHidden is not working correctly in DebuggerTypeProxy #76876)- So, we are expanding
Itemshere to work specifically forList<T>?
If so, then is this only to get EvaluateBrowsableRootHidden passing?
Also, does #76876 still happen after the DebuggerTypeProxy fix here?
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 looked at it, and (4) is true. The test for listRootHidden should be disabled for now, and a comment mentioning the issue. And we can remove this incorrect hack.
And in a follow up PR, the root hidden issue can be fixed.
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 would say it's a misunderstanding caused by me not deleting the dependency on non-fixed values in the tests properly. Manual expansion was not done on an object decorated with RootHidden but on testPropertiesNone. The else block is not to make the tests pass, it is necessary till RootHidden on Items will be received correctly by Proxy. It was the test that was not 100% logical.
I think now it will make more sense after the last commit.
Yes, the issue is still here. Proxy is not receiving any DebuggerAttributes assigned to Items, so it does not know it's of rootHidden type.
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 looked at it, and (4) is true. The test for
listRootHiddenshould be disabled for now, and a comment mentioning the issue. And we can remove this incorrect hack. And in a follow up PR, the root hidden issue can be fixed.
Sorry, this comment did not display for me when I was investigating. I don't agree. This will break mechanism for all Collections that are in rootHidden objects. The else block can be removed when we have the fix already, so that we won't be changing the behavior from working to not working.
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.
runtime/src/mono/wasm/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs
Lines 149 to 161 in 8064bfe
| else | |
| { | |
| // a collection - expose elements to be of array scheme | |
| var memberNamedItems = members | |
| .Where(m => m["name"]?.Value<string>() == "Items" || m["name"]?.Value<string>() == "_items") | |
| .FirstOrDefault(); | |
| if (memberNamedItems is not null && | |
| (DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value<string>(), out DotnetObjectId itemsObjectId)) && | |
| itemsObjectId.Scheme == "array") | |
| { | |
| rootObjectId = itemsObjectId; | |
| } | |
| } |
This code is very explicitly looking for a Items member which is an array. So, the else arm is affecting only the very specific types that have such a member, which is the case for List<T>. The proxy, or the debugger agent aren't populating it as an extra thing.
I agree that we shouldn't break the behavior in this PR. But it is incorrect. We want to avoid depending on magic like that, especially when proper mechanisms are available to get the information - DebuggerTypeProxy in this case.
The rootHidden issue is in addition to this, and that's fine to fix separately.
src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
Outdated
Show resolved
Hide resolved
| // CheckProps for JArrays care about the order and here the order differs between InlineData | ||
| var testRootHiddenPropsSorted = new JArray(testRootHiddenProps.OrderBy(v => (string)v["name"])); |
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.
The order matters, so we shouldn't adjust the results to match the test. The test types seem to have slightly different ordering of the fields/properties, which is causing the difference that you see. Making that consistent would allow not having to sort here.
I made these changes to the tests https://gist.github.com/radical/208598ae72b6310224dc531e3b66eff8 . Removing the sorting actually reveals that in case of root hidden, the members don't get added in the correct order. For example, you get arrayRootHidden[0] as the first member, but arrayRootHidden[1] is not the next one (it was the last in my run).
In general, we want to test the result as-is, as much as possible. And want to avoid adjusting the tests to match incorrect behavior. In case of incorrect behavior, we can have correct tests, but disabled with a corresponding issue.
But since we already have overlapping issues, you can either disable some tests, or patch them up as needed, to merge this PR. And in follow ups fix things to do it correctly.. thank you for being patient with it ;) Make sure to have some issue(s) tracking the TODOs.
src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs
Outdated
Show resolved
Hide resolved
thaystg
left a comment
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.
LGTM.
Fixes #68390.
As a result of the discussion here: #75958 (comment) I separated the test fix into this PR.
EvaluateBrowsableRootHiddenwhen run on main is failing, so the quoted PR changes are not connected with the problem.The problem:
DebuggerProxywas working only in a specific case - when the proxy class had only one constructor with 1 argument or the constructor we intended to use was declared as the first one.Fix:
DebuggerProxyfor value types (not only classes).Connected issues:
#77315
#77768