From 5abde776b239e6b72dde31e1947d307c454e300d Mon Sep 17 00:00:00 2001 From: "Simon Zhao (BEYONDSOFT CONSULTING INC)" Date: Tue, 4 Aug 2026 14:11:38 +0800 Subject: [PATCH 1/3] Fix issue 14831: TaskDialogPage.GetBoundButtonByID throws IndexOutOfRangeException when TaskDialogIndirect returns unexpected button ID --- .../Forms/Dialogs/TaskDialog/TaskDialog.cs | 6 ++- .../Dialogs/TaskDialog/TaskDialogPage.cs | 15 +++++- .../System/Windows/Forms/TaskDialogTests.cs | 47 +++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialog.cs b/src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialog.cs index f5ddfce7d91..c9e16364ee6 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialog.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialog.cs @@ -1132,7 +1132,11 @@ private HRESULT HandleTaskDialogCallback( case TASKDIALOG_NOTIFICATIONS.TDN_RADIO_BUTTON_CLICKED: int radioButtonID = (int)wParam; - TaskDialogRadioButton radioButton = _boundPage.GetBoundRadioButtonByID(radioButtonID)!; + TaskDialogRadioButton? radioButton = _boundPage.GetBoundRadioButtonByID(radioButtonID); + if (radioButton is null) + { + break; + } checked { diff --git a/src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialogPage.cs b/src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialogPage.cs index a0b9bfb08e3..d5f59711b5b 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialogPage.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Dialogs/TaskDialog/TaskDialogPage.cs @@ -717,7 +717,10 @@ internal void DenyIfWaitingForInitialization() // Check if the button is part of the custom buttons. if (buttonID >= CustomButtonStartID) { - return _boundCustomButtons![buttonID - CustomButtonStartID]; + int customButtonIndex = buttonID - CustomButtonStartID; + return (uint)customButtonIndex < (uint)_boundCustomButtons!.Length + ? _boundCustomButtons[customButtonIndex] + : null; } else { @@ -736,7 +739,15 @@ internal void DenyIfWaitingForInitialization() throw new InvalidOperationException(); } - return buttonID == 0 ? null : _radioButtons[buttonID - RadioButtonStartID]; + if (buttonID == 0) + { + return null; + } + + int radioButtonIndex = buttonID - RadioButtonStartID; + return (uint)radioButtonIndex < (uint)_radioButtons.Count + ? _radioButtons[radioButtonIndex] + : null; } internal void Validate() diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs index 36b79d5f76a..eac180e0d89 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs @@ -4,6 +4,7 @@ #nullable disable using Microsoft.DotNet.RemoteExecutor; +using System.Reflection; namespace System.Windows.Forms.Tests; @@ -58,4 +59,50 @@ public void TaskDialog_ShowDialog_SetProperty_DifferentThread_ThrowsInvalidOpera // verify the remote process succeeded Assert.Equal(RemoteExecutor.SuccessExitCode, invokerHandle.ExitCode); } + + [WinFormsFact] + public void TaskDialogPage_GetBoundButtonByID_CustomRangeOutOfBounds_ReturnsNull() + { + TaskDialogPage page = new(); + PrepareBoundLikeState(page); + SetPrivateField(page, "_boundCustomButtons", Array.Empty()); + SetPrivateField(page, "_boundStandardButtonsByID", new Dictionary()); + + TaskDialogButton? button = page.GetBoundButtonByID(buttonID: 100); + + Assert.Null(button); + } + + [WinFormsFact] + public void TaskDialogPage_GetBoundRadioButtonByID_OutOfBounds_ReturnsNull() + { + TaskDialogPage page = new(); + PrepareBoundLikeState(page); + + TaskDialogRadioButton? radioButton = page.GetBoundRadioButtonByID(buttonID: 1); + + Assert.Null(radioButton); + } + + private static void PrepareBoundLikeState(TaskDialogPage page) + { + ConstructorInfo? constructor = typeof(TaskDialog).GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic, + binder: null, + Type.EmptyTypes, + modifiers: null); + Assert.NotNull(constructor); + + TaskDialog dialog = (TaskDialog)constructor.Invoke(null); + SetPrivateField(page, "k__BackingField", dialog); + } + + private static void SetPrivateField(object instance, string fieldName, object value) + { + FieldInfo? field = instance.GetType().GetField( + fieldName, + BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(field); + field.SetValue(instance, value); + } } From c36200cf06a3983fb3dde896e2f72968f1c25ca7 Mon Sep 17 00:00:00 2001 From: "Simon Zhao (BEYONDSOFT CONSULTING INC)" Date: Wed, 5 Aug 2026 11:58:59 +0800 Subject: [PATCH 2/3] Fix test issue --- .../System/Windows/Forms/TaskDialogTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs index eac180e0d89..01df61d449a 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs @@ -68,7 +68,7 @@ public void TaskDialogPage_GetBoundButtonByID_CustomRangeOutOfBounds_ReturnsNull SetPrivateField(page, "_boundCustomButtons", Array.Empty()); SetPrivateField(page, "_boundStandardButtonsByID", new Dictionary()); - TaskDialogButton? button = page.GetBoundButtonByID(buttonID: 100); + TaskDialogButton button = page.GetBoundButtonByID(buttonID: 100); Assert.Null(button); } @@ -79,14 +79,14 @@ public void TaskDialogPage_GetBoundRadioButtonByID_OutOfBounds_ReturnsNull() TaskDialogPage page = new(); PrepareBoundLikeState(page); - TaskDialogRadioButton? radioButton = page.GetBoundRadioButtonByID(buttonID: 1); + TaskDialogRadioButton radioButton = page.GetBoundRadioButtonByID(buttonID: 1); Assert.Null(radioButton); } private static void PrepareBoundLikeState(TaskDialogPage page) { - ConstructorInfo? constructor = typeof(TaskDialog).GetConstructor( + ConstructorInfo constructor = typeof(TaskDialog).GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, binder: null, Type.EmptyTypes, @@ -99,7 +99,7 @@ private static void PrepareBoundLikeState(TaskDialogPage page) private static void SetPrivateField(object instance, string fieldName, object value) { - FieldInfo? field = instance.GetType().GetField( + FieldInfo field = instance.GetType().GetField( fieldName, BindingFlags.Instance | BindingFlags.NonPublic); Assert.NotNull(field); From e90863359f5dc58d333a3b563508b1a4a34fbe61 Mon Sep 17 00:00:00 2001 From: "Simon Zhao (BEYONDSOFT CONSULTING INC)" Date: Wed, 5 Aug 2026 14:19:03 +0800 Subject: [PATCH 3/3] Handle feedback --- .../System/Windows/Forms/TaskDialogTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs index 01df61d449a..80ae70b895c 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/TaskDialogTests.cs @@ -65,8 +65,9 @@ public void TaskDialogPage_GetBoundButtonByID_CustomRangeOutOfBounds_ReturnsNull { TaskDialogPage page = new(); PrepareBoundLikeState(page); - SetPrivateField(page, "_boundCustomButtons", Array.Empty()); - SetPrivateField(page, "_boundStandardButtonsByID", new Dictionary()); + dynamic access = page.TestAccessor.Dynamic; + access._boundCustomButtons = Array.Empty(); + access._boundStandardButtonsByID = new Dictionary(); TaskDialogButton button = page.GetBoundButtonByID(buttonID: 100);