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..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 @@ -4,6 +4,7 @@ #nullable disable using Microsoft.DotNet.RemoteExecutor; +using System.Reflection; namespace System.Windows.Forms.Tests; @@ -58,4 +59,51 @@ 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); + dynamic access = page.TestAccessor.Dynamic; + access._boundCustomButtons = Array.Empty(); + access._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); + } }