Guard related currency manager against re-entrant item changes#16
Draft
FahmiFuzi wants to merge 1 commit into
Draft
Guard related currency manager against re-entrant item changes#16FahmiFuzi wants to merge 1 commit into
FahmiFuzi wants to merge 1 commit into
Conversation
538fb85 to
2604982
Compare
|
we will need to avoid change winforms source code, why cannot we fix in Cargowise? |
There was a problem hiding this comment.
Pull request overview
This PR addresses a stack-overflow recursion path in WinForms related binding managers by adding a re-entrancy guard to RelatedCurrencyManager.ParentManager_CurrentItemChanged, and adds regression tests to ensure related manager updates do not re-enter during PullData().
Changes:
- Add an instance-level re-entrancy guard to
RelatedCurrencyManager.ParentManager_CurrentItemChanged. - Add a regression unit test reproducing re-entrant parent item changes during
PullData()for related lists. - Add equivalent regression coverage in the legacy test project.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/System.Windows.Forms/System/Windows/Forms/DataBinding/RelatedCurrencyManager.cs | Adds a guard intended to prevent re-entrant CurrentItemChanged handling and resulting recursion. |
| src/test/unit/System.Windows.Forms/System/Windows/Forms/BindingContextTests.cs | Adds a unit test and helper types to reproduce re-entrant updates via BindingContext and related managers. |
| src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/BindingContextTests.cs | Adds legacy test coverage mirroring the new regression scenario. |
Comments suppressed due to low confidence (3)
src/test/unit/System.Windows.Forms/System/Windows/Forms/BindingContextTests.cs:1109
- Avoid
this.qualification for field access here (enforced as error via.editorconfigdotnet_style_qualification_for_field=false:error). Use the unqualified field name in the setter.
this.value = value;
valueSetCallback();
}
src/test/unit/System.Windows.Forms/System/Windows/Forms/BindingContextTests.cs:1116
- This new field is missing an explicit accessibility modifier (required by
.editorconfigIDE0040), which will fail the build. Mark itprivateand keep field access unqualified.
private class ReentrantBindableComponent : BindableComponent
{
string value;
src/test/unit/System.Windows.Forms/System/Windows/Forms/BindingContextTests.cs:1128
- Avoid
this.qualification for field access in the equality check and assignment (enforced as error via.editorconfig). Use the unqualified field name instead.
{
if (this.value == value)
{
return;
}
this.value = value;
ValueChanged?.Invoke(this, EventArgs.Empty);
Comment on lines
+146
to
+152
| if (_handlingCurrentItemChanged) | ||
| { | ||
| OnDataError(ex); | ||
| return; | ||
| } | ||
|
|
||
| if (_parentManager is CurrencyManager currencyManager) | ||
| _handlingCurrentItemChanged = true; | ||
| try |
Comment on lines
+197
to
+200
| if (IgnoreItemChangedTable.Contains(currencyManager)) | ||
| { | ||
| IgnoreItemChangedTable.Remove(currencyManager); | ||
| } |
Comment on lines
+1084
to
+1092
| private class ReentrantChildDataSource | ||
| { | ||
| readonly Action valueSetCallback; | ||
| string value = "initial"; | ||
|
|
||
| public ReentrantChildDataSource(Action valueSetCallback) | ||
| { | ||
| this.valueSetCallback = valueSetCallback; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
CargoWise Group 7 net10 failures exposed a stack overflow path in related WinForms binding managers.
The recursive path is:
RelatedCurrencyManager.ParentManager_CurrentItemChangedresponds to the parent manager'sCurrentItemChangedevent.PullData()to push dirty bound-control values back into the current child object.RelatedCurrencyManagerre-entersParentManager_CurrentItemChanged, pulls the same dirty binding again, and repeats until stack overflow.Why .NET 8 Works But .NET 10 Fails
The root cause is not a new event subscription in
RelatedCurrencyManageritself — both net8 and net10 wire related managers toParentManager_CurrentItemChanged. The difference is in howBindingContextstores managers, which let CargoWise intercept and rewire that subscription in net8 but silently loses the interception in net10.Net 8:
Hashtablestorage — interception worksBindingContext._listManagersis a plainHashtablein the net8 fork (BindingContext.csline 18, net8.0_legacy):CargoWise's
ZBindingContextconstructor uses reflection to replace this field with a customBindingContextHashtablesubclass (ZBindingContext.cslines 19–22):When
EnsureListManagercreates a newRelatedCurrencyManagerand caches it via_listManagers.Add(key, new WeakReference(bindingManagerBase))(BindingContext.csline 331, net8), the swapped-in hashtable intercepts thatAddcall. TheBindingContextHashtable.Addoverride then:WeakReferenceto get the newRelatedCurrencyManagerparentManagerfieldparentManager.CurrentItemChanged -= ParentManager_CurrentItemChangedsubscriptionCurrentChangedhandler viaParentCurrentChangedHandler(ZBindingContext.cs~line 220)CurrentChangedfires only when the parent's current object actually changes, not on every item/property notification. The CargoWise handler also trackslastCurrentand skips redundant updates, so dirty-binding pushes fromPullData()cannot re-trigger it.Net 10:
Dictionarystorage — interception silently failsIn the net10/master fork
_listManagersis strongly typed (BindingContext.csline 16, master):The reflection assignment now fails silently: a
Hashtablesubclass cannot be set into aDictionary<HashKey, WeakReference>field.ZBindingContexthas a#if !NETFRAMEWORKfallback that tries to rewire inOnCollectionChanged, butEnsureListManagerdoes not callOnCollectionChangedwhen it caches internally created managers (BindingContext.csline 278, master), so the fallback is never reached for related managers created this way.Result: the related manager's
ParentManager_CurrentItemChangedsubscription on the broadCurrentItemChangedevent remains in place, unmodified.Re-entrancy execution flow (net10)
This is the exact scenario reproduced by the regression test: the
ReentrantChildDataSource.Valuesetter callsparentList.ResetItem(0)while the firstPullData()is still on the call stack, andValueSetCount > 1would throwInvalidOperationExceptionwithout the guard (BindingContextTests.cslines 57–72).Fix
Add an instance-level re-entrancy guard around
RelatedCurrencyManager.ParentManager_CurrentItemChanged.This fixes the unsafe recursion at the forked WinForms source, so every package consumer gets the corrected behavior without CargoWise-specific reflection against
BindingContextinternals. The outer handler still performs the normalPullData, related-list refresh, position update, and current-change notifications; only nested invocations against the same related manager are skipped while the handler is already active.Tests
Added regression coverage that binds through
BindingContextto a related child list, marks the binding dirty, and raises a parent item change. The child setter raises another parent item change whilePullData()is active. The test verifies the setter runs once and noDataErroris raised.Validation:
Build - Debugpassed for this PR.AxWMPLibmissing while buildingsrc\test\unit\.NET Framework\AxHosts\AxHosts.csproj).WTG.Windows.Forms.0.1.1-pr.16.1package was built from this branch and used to validate CargoWise.MatchEPaymentRecipientsBulkFormAccountingZFormBasherTest.TestBashingForm.