Skip to content

Commit 6bd932c

Browse files
authored
Fix null values in AutoCompleteCustomSource (#10905)
* Refactoring to add ArgumentNullException/InvalidOperationException to AutoCompleteCustomSource and add tests to verify Cleanup/Refactor of StringSource * Moved tests to AutoCompleteStringCollectionTests * Changes from review * change from review * Use Items property instead of private backing field
1 parent cd27216 commit 6bd932c

File tree

5 files changed

+91
-57
lines changed

5 files changed

+91
-57
lines changed

src/System.Windows.Forms/src/System/Windows/Forms/AutoCompleteStringCollection.cs

+35-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace System.Windows.Forms;
1212
public class AutoCompleteStringCollection : IList
1313
{
1414
private CollectionChangeEventHandler? _onCollectionChanged;
15-
private readonly List<string> _data = new();
15+
private readonly List<string> _data = [];
1616

1717
public AutoCompleteStringCollection()
1818
{
@@ -57,8 +57,17 @@ public event CollectionChangeEventHandler? CollectionChanged
5757
/// Adds a string with the specified value to the
5858
/// <see cref="AutoCompleteStringCollection"/> .
5959
/// </summary>
60+
/// <returns>
61+
/// The position into which the new element was inserted, or -1 to indicate that
62+
/// the item was not inserted into the collection.
63+
/// </returns>
6064
public int Add(string value)
6165
{
66+
if (value is null)
67+
{
68+
return -1;
69+
}
70+
6271
int index = ((IList)_data).Add(value);
6372
OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, value));
6473
return index;
@@ -71,7 +80,21 @@ public void AddRange(params string[] value)
7180
{
7281
ArgumentNullException.ThrowIfNull(value);
7382

74-
_data.AddRange(value);
83+
List<string> nonNullItems = [];
84+
foreach (string item in value)
85+
{
86+
if (item is not null)
87+
{
88+
nonNullItems.Add(item);
89+
}
90+
}
91+
92+
if (nonNullItems.Count <= 0)
93+
{
94+
return;
95+
}
96+
97+
_data.AddRange(nonNullItems);
7598
OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null));
7699
}
77100

@@ -110,8 +133,14 @@ public void Clear()
110133
/// </summary>
111134
public void Insert(int index, string value)
112135
{
113-
_data.Insert(index, value);
114-
OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, value));
136+
ArgumentOutOfRangeException.ThrowIfNegative(index);
137+
ArgumentOutOfRangeException.ThrowIfGreaterThan(index, _data.Count);
138+
139+
if (value is not null)
140+
{
141+
_data.Insert(index, value);
142+
OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, value));
143+
}
115144
}
116145

117146
/// <summary>
@@ -168,4 +197,6 @@ public void RemoveAt(int index)
168197
void ICollection.CopyTo(Array array, int index) => ((ICollection)_data).CopyTo(array, index);
169198

170199
public IEnumerator GetEnumerator() => _data.GetEnumerator();
200+
201+
internal string[] ToArray() => [.. _data];
171202
}

src/System.Windows.Forms/src/System/Windows/Forms/Controls/ComboBox/ComboBox.cs

+10-20
Original file line numberDiff line numberDiff line change
@@ -915,30 +915,20 @@ private int GetComboHeight()
915915
return cyCombo;
916916
}
917917

918-
private string[] GetStringsForAutoComplete(IList collection)
918+
private string[] GetStringsForAutoComplete()
919919
{
920-
if (collection is AutoCompleteStringCollection)
920+
if (Items is not null)
921921
{
922-
string[] strings = new string[AutoCompleteCustomSource.Count];
923-
for (int i = 0; i < AutoCompleteCustomSource.Count; i++)
924-
{
925-
strings[i] = AutoCompleteCustomSource[i];
926-
}
927-
928-
return strings;
929-
}
930-
else if (collection is ObjectCollection && _itemsCollection is not null)
931-
{
932-
string[] strings = new string[_itemsCollection.Count];
933-
for (int i = 0; i < _itemsCollection.Count; i++)
922+
string[] strings = new string[Items.Count];
923+
for (int i = 0; i < Items.Count; i++)
934924
{
935-
strings[i] = GetItemText(_itemsCollection[i])!;
925+
strings[i] = GetItemText(Items[i])!;
936926
}
937927

938928
return strings;
939929
}
940930

941-
return Array.Empty<string>();
931+
return [];
942932
}
943933

944934
/// <summary>
@@ -3283,15 +3273,15 @@ private void SetAutoComplete(bool reset, bool recreate)
32833273

32843274
if (_stringSource is null)
32853275
{
3286-
_stringSource = new StringSource(GetStringsForAutoComplete(AutoCompleteCustomSource));
3276+
_stringSource = new StringSource(AutoCompleteCustomSource.ToArray());
32873277
if (!_stringSource.Bind(_childEdit, (AUTOCOMPLETEOPTIONS)AutoCompleteMode))
32883278
{
32893279
throw new ArgumentException(SR.AutoCompleteFailure);
32903280
}
32913281
}
32923282
else
32933283
{
3294-
_stringSource.RefreshList(GetStringsForAutoComplete(AutoCompleteCustomSource));
3284+
_stringSource.RefreshList(AutoCompleteCustomSource.ToArray());
32953285
}
32963286

32973287
return;
@@ -3326,15 +3316,15 @@ private void SetAutoComplete(bool reset, bool recreate)
33263316

33273317
if (_stringSource is null)
33283318
{
3329-
_stringSource = new StringSource(GetStringsForAutoComplete(Items));
3319+
_stringSource = new StringSource(GetStringsForAutoComplete());
33303320
if (!_stringSource.Bind(_childEdit, (AUTOCOMPLETEOPTIONS)AutoCompleteMode))
33313321
{
33323322
throw new ArgumentException(SR.AutoCompleteFailureListItems);
33333323
}
33343324
}
33353325
else
33363326
{
3337-
_stringSource.RefreshList(GetStringsForAutoComplete(Items));
3327+
_stringSource.RefreshList(GetStringsForAutoComplete());
33383328
}
33393329

33403330
return;

src/System.Windows.Forms/src/System/Windows/Forms/Controls/TextBox/TextBox.cs

+2-13
Original file line numberDiff line numberDiff line change
@@ -721,17 +721,6 @@ private protected override void SelectInternal(int start, int length, int textLe
721721
base.SelectInternal(start, length, textLen);
722722
}
723723

724-
private string[] GetStringsForAutoComplete()
725-
{
726-
string[] strings = new string[AutoCompleteCustomSource.Count];
727-
for (int i = 0; i < AutoCompleteCustomSource.Count; i++)
728-
{
729-
strings[i] = AutoCompleteCustomSource[i];
730-
}
731-
732-
return strings;
733-
}
734-
735724
/// <summary>
736725
/// Sets the AutoComplete mode in TextBox.
737726
/// </summary>
@@ -767,15 +756,15 @@ private unsafe void SetAutoComplete(bool reset)
767756
{
768757
if (_stringSource is null)
769758
{
770-
_stringSource = new StringSource(GetStringsForAutoComplete());
759+
_stringSource = new StringSource(AutoCompleteCustomSource.ToArray());
771760
if (!_stringSource.Bind(this, (AUTOCOMPLETEOPTIONS)AutoCompleteMode))
772761
{
773762
throw new ArgumentException(SR.AutoCompleteFailure);
774763
}
775764
}
776765
else
777766
{
778-
_stringSource.RefreshList(GetStringsForAutoComplete());
767+
_stringSource.RefreshList(AutoCompleteCustomSource.ToArray());
779768
}
780769
}
781770
}

src/System.Windows.Forms/src/System/Windows/Forms/StringSource.cs

+17-20
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,18 @@ namespace System.Windows.Forms;
1313
/// </summary>
1414
internal unsafe class StringSource : IEnumString.Interface, IManagedWrapper<IEnumString>
1515
{
16-
private string[] strings;
17-
private int current;
18-
private int size;
16+
private string[] _strings;
17+
private int _current;
18+
private int _size;
1919
private IAutoComplete2* _autoComplete2;
2020

2121
/// <summary>
2222
/// Constructor.
2323
/// </summary>
2424
public StringSource(string[] strings)
2525
{
26-
Array.Clear(strings, 0, size);
27-
this.strings = strings;
28-
29-
current = 0;
30-
size = strings.Length;
26+
_strings = strings;
27+
_size = strings.Length;
3128

3229
PInvokeCore.CoCreateInstance(
3330
in CLSID.AutoComplete,
@@ -68,10 +65,10 @@ public void ReleaseAutoComplete()
6865

6966
public void RefreshList(string[] newSource)
7067
{
71-
Array.Clear(strings, 0, size);
72-
strings = newSource;
73-
current = 0;
74-
size = strings.Length;
68+
Array.Clear(_strings, 0, _size);
69+
_strings = newSource;
70+
_current = 0;
71+
_size = _strings.Length;
7572
}
7673

7774
public unsafe HRESULT Clone(IEnumString** ppenum)
@@ -81,7 +78,7 @@ public unsafe HRESULT Clone(IEnumString** ppenum)
8178
return HRESULT.E_POINTER;
8279
}
8380

84-
*ppenum = ComHelpers.GetComPointer<IEnumString>(new StringSource(strings) { current = current });
81+
*ppenum = ComHelpers.GetComPointer<IEnumString>(new StringSource(_strings) { _current = _current });
8582
return HRESULT.S_OK;
8683
}
8784

@@ -94,10 +91,10 @@ public unsafe HRESULT Next(uint celt, PWSTR* rgelt, [Optional] uint* pceltFetche
9491

9592
uint fetched = 0;
9693

97-
while (current < size && celt > 0)
94+
while (_current < _size && celt > 0)
9895
{
99-
rgelt[fetched] = (char*)Marshal.StringToCoTaskMemUni(strings[current]);
100-
current++;
96+
rgelt[fetched] = (char*)Marshal.StringToCoTaskMemUni(_strings[_current]);
97+
_current++;
10198
fetched++;
10299
celt--;
103100
}
@@ -112,19 +109,19 @@ public unsafe HRESULT Next(uint celt, PWSTR* rgelt, [Optional] uint* pceltFetche
112109

113110
public HRESULT Skip(uint celt)
114111
{
115-
int newCurrent = current + (int)celt;
116-
if (newCurrent >= size)
112+
int newCurrent = _current + (int)celt;
113+
if (newCurrent >= _size)
117114
{
118115
return HRESULT.S_FALSE;
119116
}
120117

121-
current = newCurrent;
118+
_current = newCurrent;
122119
return HRESULT.S_OK;
123120
}
124121

125122
public HRESULT Reset()
126123
{
127-
current = 0;
124+
_current = 0;
128125
return HRESULT.S_OK;
129126
}
130127
}

src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/AutoCompleteStringCollectionTests.cs

+27
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@ public void AutoCompleteStringCollection_AddRange_NullValue_ThrowsArgumentNullEx
3535
Assert.Throws<ArgumentNullException>("value", () => collection.AddRange(null));
3636
}
3737

38+
#nullable enable
39+
[WinFormsFact]
40+
public void AutoCompleteStringCollection_AddRange_NullValues_Nop()
41+
{
42+
AutoCompleteStringCollection collection = new();
43+
collection.AddRange([null!]);
44+
Assert.Empty(collection);
45+
}
46+
47+
[WinFormsFact]
48+
public void AutoCompleteStringCollection_Add_NullValue_ThrowsArgumentNullException()
49+
{
50+
AutoCompleteStringCollection collection = new();
51+
Assert.Throws<ArgumentNullException>("value", () => collection.AddRange(null!));
52+
}
53+
#nullable disable
54+
3855
[WinFormsFact]
3956
public void AutoCompleteStringCollection_Contains_Invoke_ReturnsExpected()
4057
{
@@ -181,6 +198,16 @@ public void AutoCompleteStringCollection_IListInsert_InvalidIndex_ThrowsArgument
181198
Assert.Throws<ArgumentOutOfRangeException>("index", () => collection.Insert(index, "value"));
182199
}
183200

201+
#nullable enable
202+
[WinFormsFact]
203+
public void AutoCompleteStringCollection_IListInsert_NullItem_Nop()
204+
{
205+
IList collection = new AutoCompleteStringCollection();
206+
collection.Insert(0, null);
207+
Assert.Empty(collection);
208+
}
209+
#nullable disable
210+
184211
[WinFormsFact]
185212
public void AutoCompleteStringCollection_Remove_String_Success()
186213
{

0 commit comments

Comments
 (0)