Skip to content
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

Do not crash when an invalid VirtualMode==true ListView is destroyed #11679

Conversation

SimonZhao888
Copy link
Member

@SimonZhao888 SimonZhao888 commented Jul 16, 2024

Fixes #11663

In VirtualMode = true mode, the behavior of the ListView throwing a missing valid ListViewItem exception is correct, but this should not include the delete operation, which is completely unconcerned about data integrity and validity when the user wants to delete the ListView, and we can't create an accessibility object for a null object.

In the ReleaseUiaProvider method, we used accessor to get the ListViewItem which did not filter for null, it will resulte in the ListView throwing a NullReferenceException exception and not being able to be deleted when the user performs the delete operation, which did not make sense, so we add a new internal access method which will be used in ReleaseUiaProvider method, to make sure when the ListViewItem is null, we don't need execute release method.

Improve the property grid description of the VirtualListSize property to make users more aware of how VirtualListSize is used.

Proposed changes

  • Improve property grid description.
  • Add a new internal access method that allows to return a nullable ListViewItem object, this method is used in ReleaseUiaProvider(HWND handle).

Customer Impact

  • Deleting an invalid ListView does not throw any exception in VirtualMode mode,

Regression?

  • Yes

Risk

  • Minimal

Screenshots

Before

image
image

After

image
tt1

Test methodology

  • Manually

Test environment(s)

  • 9.0.100-preview.4.24267.66
Microsoft Reviewers: Open in CodeFlow

Copy link

codecov bot commented Jul 16, 2024

Codecov Report

Attention: Patch coverage is 84.88372% with 13 lines in your changes missing coverage. Please review.

Project coverage is 74.84571%. Comparing base (9319f17) to head (bdd2fd3).
Report is 86 commits behind head on main.

Additional details and impacted files
@@                 Coverage Diff                 @@
##                main      #11679         +/-   ##
===================================================
+ Coverage   74.51156%   74.84571%   +0.33414%     
===================================================
  Files           3040        3014         -26     
  Lines         629560      629336        -224     
  Branches       46839       46698        -141     
===================================================
+ Hits          469095      471031       +1936     
+ Misses        157096      154947       -2149     
+ Partials        3369        3358         -11     
Flag Coverage Δ
Debug 74.84571% <84.88372%> (+0.33414%) ⬆️
integration 18.05083% <66.66667%> (+0.17225%) ⬆️
production 47.84806% <76.92308%> (+0.44572%) ⬆️
test 97.01179% <91.48936%> (+0.05964%) ⬆️
unit 44.87143% <76.92308%> (+0.35172%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Copy link
Member

@LeafShi1 LeafShi1 left a comment

Choose a reason for hiding this comment

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

If the user only sets VirtualListSize but does not provide virtual item, the item.count here should not be greater than one.

@lonitra
Copy link
Member

lonitra commented Jul 16, 2024

We should definitely fix surfacing a null reference exception to users, but it seems like we should still be calling ReleaseUiaProvider on items in a ListView during destroy, even if the ListView is in VirtualMode = true. The issue here looks more like RetrieveVirtualItem event was not handled on user end and we should probably surface a more helpful exception so users understand what went wrong. @Tanya-Solyanik could you correct me if I'm wrong here?

@Tanya-Solyanik
Copy link
Member

The issue here looks more like RetrieveVirtualItem event was not handled on user end and we should probably surface a more helpful exception so users understand what went wrong

@lonitra - you are right, we can say that this is an invalid scenario, but I don't think we should surface a message about populating virtual items on control disposal. We already have exception - https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.listview.virtuallistsize?view=windowsdesktop-8.0#exceptions about this. We could perhaps improve property grid description of the VirtualListSize property by adding "If VirtualMode is set to true, and the VirtualListSize property is greater than 0, you must handle the RetrieveVirtualItem event providing valid items."

I would just make sure that if items were created, their providers should be released. @SimonZhao888 - make sure that you don't create new items on the disposal stack.

@SimonZhao888
Copy link
Member Author

SimonZhao888 commented Jul 17, 2024

I would just make sure that if items were created, their providers should be released. @SimonZhao888 - make sure that you don't create new items on the disposal stack.

Yes, if the items were created, their providers will be released. I used this demo for testing. https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.listview.virtualmode?view=windowsdesktop-8.0#--

make sure that you don't create new items on the disposal stack.

I didn't find any codes of create new items on the disposal stack.

And this issue does not repo on .NET Framework.

@SimonZhao888 SimonZhao888 requested a review from LeafShi1 July 17, 2024 08:48
@SimonZhao888 SimonZhao888 changed the title Add VirtualMode check in the ReleaseUiaProvider method Improved the property grid description of the VirtualListSize property to make users more aware of how VirtualListSize is used Jul 17, 2024
@SimonZhao888 SimonZhao888 changed the title Improved the property grid description of the VirtualListSize property to make users more aware of how VirtualListSize is used Improved the property grid description of the VirtualListSize property Jul 17, 2024
@SimonZhao888 SimonZhao888 changed the title Improved the property grid description of the VirtualListSize property Improve the property grid description of the VirtualListSize property Jul 17, 2024
@Tanya-Solyanik
Copy link
Member

Yes, if the items were created, their providers will be released. I used this demo for testing. https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.forms.listview.virtualmode?view=windowsdesktop-8.0#--

Thank you! we also have samples with virtual items in our winforms controls app integration test in the repo.
But now that you reverted the virtual mode check, you will have the crash again, is it possible to avoid it?

@lonitra
Copy link
Member

lonitra commented Jul 17, 2024

we can say that this is an invalid scenario, but I don't think we should surface a message about populating virtual items on control disposal. We already have exception - https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.listview.virtuallistsize?view=windowsdesktop-8.0#exceptions about this. We could perhaps improve property grid description of the VirtualListSize property by adding "If VirtualMode is set to true, and the VirtualListSize property is greater than 0, you must handle the RetrieveVirtualItem event providing valid items."

@Tanya-Solyanik - I think that's a good idea to improve the description, but this doesn't resolve issue that we are surfacing a NRE to users. Based on the callstack in the issue, it seems like we are getting NRE here because we had assumed that it would be nonnull. Should we perhaps throw exception there and add a try/catch here? Catching to avoid exception during destroy.

@Tanya-Solyanik
Copy link
Member

@lonitra - yes, this exception is a regression and will block migration, we must fix it. I commented on the same at the same time as you did.
We can't call item indexer (

public ListViewItem this[int displayIndex]
{
get
{
_owner.ApplyUpdateCachedItems();
if (_owner.VirtualMode)
{
// if we are showing virtual items, we need to get the item from the user
RetrieveVirtualItemEventArgs rVI = new(displayIndex);
_owner.OnRetrieveVirtualItem(rVI);
rVI.Item!.SetItemIndex(_owner, displayIndex);
return rVI.Item;
}
) in virtual mode from the dispose stack because it might create items that had not been created previously. And yes, the null forgiving operator is unwarranted in that indexer - we received data from user code with no guarantees of it being properly implemented. Indexer should throw InvalidOperationException, as per docs.
Instead of proactively accessing indexer, we need to cache information about items being initialized in virtual mode( might be already available, I hadn't checked) and access only items that have been successfully created.

@Tanya-Solyanik Tanya-Solyanik added the waiting-author-feedback The team requires more information from the author label Jul 18, 2024
@dotnet-policy-service dotnet-policy-service bot removed the waiting-author-feedback The team requires more information from the author label Jul 18, 2024
Simon Zhao (BEYONDSOFT CONSULTING INC) added 2 commits July 18, 2024 16:53
@elachlan elachlan added the waiting-review This item is waiting on review by one or more members of team label Jul 18, 2024
@Tanya-Solyanik Tanya-Solyanik added waiting-author-feedback The team requires more information from the author and removed waiting-review This item is waiting on review by one or more members of team labels Jul 18, 2024
@@ -5231,8 +5228,50 @@ public void ListView_VirtualMode_ListViewItemReleaseSuccess(ListViewItem listIte
};
};

Action action = () => listView.Items.GetItemByIndex(0)?.ReleaseUiaProvider();
action.Should().NotThrow();
SubListViewAccessibleObject accessibleObject = new(listView);
Copy link
Member

Choose a reason for hiding this comment

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

Calling listView.AccessibilityObject property will associate accessible object with the control and you will avoid calling private methods.

int accessibilityProperty = listView.TestAccessor().Dynamic.s_accessibilityProperty;
listView.Properties.SetObject(accessibilityProperty, accessibleObject);
listView.IsAccessibilityObjectCreated.Should().BeTrue();
if (listItem is null)
Copy link
Member

Choose a reason for hiding this comment

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

Why do you have to special-case the null? Calling Control.ReleaseUiaProviders method better simulates the WM_DESTROY scenario

Copy link
Member Author

@SimonZhao888 SimonZhao888 Jul 29, 2024

Choose a reason for hiding this comment

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

In virtual mode, whether we create the control handle first and then add the ListViewItem with null value, or we set up the ListView that contains the null value and then get the control handle, they will both throw exceptions.

  1. The first one will throws the SR.ListViewVirtualListSizeDescr.
  2. The second one will throws An unhandled exception was encountered during a user callback.
    createResult = PInvoke.CreateWindowEx(
    (WINDOW_EX_STYLE)cp.ExStyle,
    windowClass._windowClassName,
    cp.Caption,
    (WINDOW_STYLE)cp.Style,
    cp.X,
    cp.Y,
    cp.Width,
    cp.Height,
    (HWND)cp.Parent,
    HMENU.Null,
    modHandle,
    cp.Param);

In either case, these exception will cause the test program to end prematurely and not be able to calling Control.ReleaseUiaProviders method, neither of which meets the purpose of our test, which is to ensure that a null ListViewItem can be released normally in virtual mode, so I made the null as special-case.

Copy link
Member

Choose a reason for hiding this comment

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

I see, Control.ReleaseUiaProvider takes a Handle as an argument. But you don't have to create a Handle when invoking this method. You can pass in a zero or InternalHandle, because we are only testing how the item providers are released, and items are not windows.

In general Control.HWND property is a dangerous one and should be used only when you must create a window, most often we use InternalHandle instead.

Copy link
Member

Choose a reason for hiding this comment

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

@Epica3055 - when you call InternalHandle, you don't have to special-case the null item case. Lines 5244-5247 will work for null item

Copy link
Member

@Tanya-Solyanik Tanya-Solyanik Aug 1, 2024

Choose a reason for hiding this comment

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

@Epica3055 , @SimonZhao888 Sorry about the confusion. I was suggesting to
1 avoid the private reflection because we have public property that can be used instead (AccessibilityObject)
2. invoke ReleaseUiaProviders in the same way as it is invoked in applications, by calling Control.ReleaseUiaProveders method.
3. validate that control handle had not been created.

   [WinFormsTheory]
   [MemberData(nameof(GetListViewItemTheoryData))]
   // Regression test for https://github.com/dotnet/winforms/issues/11663.
   public void ListView_VirtualMode_ReleaseUiaProvider_Success(ListViewItem listItem)
   {
       using ListView listView = new()
       {
           VirtualMode = true,
           VirtualListSize = 1
       };

       listView.RetrieveVirtualItem += (s, e) =>
       {
           e.Item = e.ItemIndex switch
           {
               0 => listItem,
               _ => throw new NotImplementedException()
           };
       };

       listView.AccessibilityObject.Should().NotBeNull();

       Action action = () => listView.ReleaseUiaProvider(listView.InternalHandle);
       action.Should().NotThrow();
       listView.IsAccessibilityObjectCreated.Should().BeFalse();
       listView.IsHandleCreated.Should().BeFalse();
   }

@Tanya-Solyanik Tanya-Solyanik added waiting-author-feedback The team requires more information from the author and removed waiting-review This item is waiting on review by one or more members of team labels Jul 26, 2024
@Tanya-Solyanik Tanya-Solyanik modified the milestones: .NET 9.0, 9.0 RC1 Jul 26, 2024
@dotnet-policy-service dotnet-policy-service bot removed the waiting-author-feedback The team requires more information from the author label Jul 29, 2024
@Epica3055
Copy link
Member

looks good .

@SimonZhao888 SimonZhao888 added the waiting-review This item is waiting on review by one or more members of team label Jul 31, 2024
@Tanya-Solyanik Tanya-Solyanik added waiting-author-feedback The team requires more information from the author and removed waiting-review This item is waiting on review by one or more members of team labels Jul 31, 2024
@Epica3055 Epica3055 added waiting-review This item is waiting on review by one or more members of team and removed waiting-author-feedback The team requires more information from the author labels Aug 1, 2024
@Tanya-Solyanik Tanya-Solyanik added waiting-author-feedback The team requires more information from the author and removed waiting-review This item is waiting on review by one or more members of team labels Aug 1, 2024
…an be used instead (AccessibilityObject)

2. invoke ReleaseUiaProviders in the same way as it is invoked in applications, by calling Control.ReleaseUiaProveders method.
3. validate that control handle had not been created.
@Tanya-Solyanik Tanya-Solyanik removed the waiting-author-feedback The team requires more information from the author label Aug 1, 2024
Tanya-Solyanik
Tanya-Solyanik previously approved these changes Aug 1, 2024
Copy link
Member

@lonitra lonitra left a comment

Choose a reason for hiding this comment

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

Just a comment about catching specific exceptions, otherwise LGTM 👍

@lonitra lonitra added the waiting-author-feedback The team requires more information from the author label Aug 1, 2024
@lonitra lonitra merged commit 6194fcc into dotnet:main Aug 1, 2024
8 checks passed
@dotnet-policy-service dotnet-policy-service bot removed the waiting-author-feedback The team requires more information from the author label Aug 1, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Sep 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unhandled exception has occurred when deleting ListView with setting its VirtualMode and VirtualListSize
7 participants