From 2dc99d9ceb262d352a2af04141e5463dd0cbf2cd Mon Sep 17 00:00:00 2001 From: "Milko Venkov (INFRAGISTICS INC)" Date: Wed, 8 Jul 2026 16:18:00 +0300 Subject: [PATCH 1/3] Harden BindingGroup loops against re-entrant binding-expression removal (#1690) --- .../System/Windows/Data/BindingGroup.cs | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs index 0cef67b8e2c..8182a1fc375 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs @@ -382,9 +382,14 @@ public void CancelEdit() } // update targets - for (int i=_bindingExpressions.Count - 1; i>=0; --i) + BindingExpressionBase[] bindingExpressions = CopyBindingExpressions(); + for (int i=bindingExpressions.Length - 1; i>=0; --i) { - _bindingExpressions[i].UpdateTarget(); + BindingExpressionBase bindingExpression = bindingExpressions[i]; + if (bindingExpression.BindingGroup != this) + continue; + + bindingExpression.UpdateTarget(); } // also update dependent targets. These are one-way bindings that @@ -1127,13 +1132,26 @@ private void UpdateNotifyDataErrorValidationErrors(WeakReference itemWR, List=0; --i) + BindingExpressionBase[] bindingExpressions = CopyBindingExpressions(); + for (int i=bindingExpressions.Length-1; i>=0; --i) { - result = _bindingExpressions[i].ObtainConvertedProposedValue(this) && result; + BindingExpressionBase bindingExpression = bindingExpressions[i]; + if (bindingExpression.BindingGroup != this) + continue; + + result = bindingExpression.ObtainConvertedProposedValue(this) && result; } return result; @@ -1144,9 +1162,14 @@ private bool UpdateValues() { bool result = true; - for (int i=_bindingExpressions.Count-1; i>=0; --i) + BindingExpressionBase[] bindingExpressions = CopyBindingExpressions(); + for (int i=bindingExpressions.Length-1; i>=0; --i) { - result = _bindingExpressions[i].UpdateSource(this) && result; + BindingExpressionBase bindingExpression = bindingExpressions[i]; + if (bindingExpression.BindingGroup != this) + continue; + + result = bindingExpression.UpdateSource(this) && result; } if (_proposedValueBindingExpressions != null) @@ -1172,9 +1195,16 @@ private bool CheckValidationRules() ClearValidationErrors(_validationStep); // check rules attached to the bindings - for (int i=_bindingExpressions.Count-1; i>=0; --i) + BindingExpressionBase[] bindingExpressions = CopyBindingExpressions(); + for (int i=bindingExpressions.Length-1; i>=0; --i) { - if (!_bindingExpressions[i].CheckValidationRules(this, _validationStep)) + BindingExpressionBase bindingExpression = bindingExpressions[i]; + if (bindingExpression.BindingGroup != this) + { + continue; + } + + if (!bindingExpression.CheckValidationRules(this, _validationStep)) { result = false; } From 6a573e375bdf1a1a30147c830a28a4ac81c6555d Mon Sep 17 00:00:00 2001 From: Milko Venkov Date: Wed, 8 Jul 2026 16:33:56 +0300 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../System/Windows/Data/BindingGroup.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs index 8182a1fc375..8bf62205d9e 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs @@ -1135,7 +1135,11 @@ private void UpdateNotifyDataErrorValidationErrors(WeakReference itemWR, List(); + + BindingExpressionBase[] copy = new BindingExpressionBase[count]; _bindingExpressions.CopyTo(copy, 0); return copy; } From 24124afa69822d22e9c18cb4c43aa39dc3c17039 Mon Sep 17 00:00:00 2001 From: "Milko Venkov (INFRAGISTICS INC)" Date: Thu, 9 Jul 2026 11:02:28 +0300 Subject: [PATCH 3/3] Guard BindingGroup commit/validate loops against re-entrant collection changes --- .../System/Windows/Data/BindingGroup.cs | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs index 8182a1fc375..8712c9a327d 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/BindingGroup.cs @@ -382,10 +382,14 @@ public void CancelEdit() } // update targets - BindingExpressionBase[] bindingExpressions = CopyBindingExpressions(); - for (int i=bindingExpressions.Length - 1; i>=0; --i) + for (int i=_bindingExpressions.Count-1; i>=0; --i) { - BindingExpressionBase bindingExpression = bindingExpressions[i]; + // a re-entrant callout can shrink the collection - see + // https://github.com/dotnet/wpf/issues/1690 + if (i >= _bindingExpressions.Count) + continue; + + BindingExpressionBase bindingExpression = _bindingExpressions[i]; if (bindingExpression.BindingGroup != this) continue; @@ -1132,22 +1136,18 @@ private void UpdateNotifyDataErrorValidationErrors(WeakReference itemWR, List=0; --i) + for (int i=_bindingExpressions.Count-1; i>=0; --i) { - BindingExpressionBase bindingExpression = bindingExpressions[i]; + // a re-entrant callout can shrink the collection - see + // https://github.com/dotnet/wpf/issues/1690 + if (i >= _bindingExpressions.Count) + continue; + + BindingExpressionBase bindingExpression = _bindingExpressions[i]; if (bindingExpression.BindingGroup != this) continue; @@ -1162,10 +1162,14 @@ private bool UpdateValues() { bool result = true; - BindingExpressionBase[] bindingExpressions = CopyBindingExpressions(); - for (int i=bindingExpressions.Length-1; i>=0; --i) + for (int i=_bindingExpressions.Count-1; i>=0; --i) { - BindingExpressionBase bindingExpression = bindingExpressions[i]; + // a re-entrant callout can shrink the collection - see + // https://github.com/dotnet/wpf/issues/1690 + if (i >= _bindingExpressions.Count) + continue; + + BindingExpressionBase bindingExpression = _bindingExpressions[i]; if (bindingExpression.BindingGroup != this) continue; @@ -1195,19 +1199,19 @@ private bool CheckValidationRules() ClearValidationErrors(_validationStep); // check rules attached to the bindings - BindingExpressionBase[] bindingExpressions = CopyBindingExpressions(); - for (int i=bindingExpressions.Length-1; i>=0; --i) + for (int i=_bindingExpressions.Count-1; i>=0; --i) { - BindingExpressionBase bindingExpression = bindingExpressions[i]; + // a re-entrant callout can shrink the collection - see + // https://github.com/dotnet/wpf/issues/1690 + if (i >= _bindingExpressions.Count) + continue; + + BindingExpressionBase bindingExpression = _bindingExpressions[i]; if (bindingExpression.BindingGroup != this) - { continue; - } if (!bindingExpression.CheckValidationRules(this, _validationStep)) - { result = false; - } } // include the bindings for proposed values, for the last two steps