Skip to content

Commit

Permalink
Fix null values in AutoCompleteCustomSource (#10905)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
elachlan authored Feb 23, 2024
1 parent cd27216 commit 6bd932c
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace System.Windows.Forms;
public class AutoCompleteStringCollection : IList
{
private CollectionChangeEventHandler? _onCollectionChanged;
private readonly List<string> _data = new();
private readonly List<string> _data = [];

public AutoCompleteStringCollection()
{
Expand Down Expand Up @@ -57,8 +57,17 @@ public event CollectionChangeEventHandler? CollectionChanged
/// Adds a string with the specified value to the
/// <see cref="AutoCompleteStringCollection"/> .
/// </summary>
/// <returns>
/// The position into which the new element was inserted, or -1 to indicate that
/// the item was not inserted into the collection.
/// </returns>
public int Add(string value)
{
if (value is null)
{
return -1;
}

int index = ((IList)_data).Add(value);
OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, value));
return index;
Expand All @@ -71,7 +80,21 @@ public void AddRange(params string[] value)
{
ArgumentNullException.ThrowIfNull(value);

_data.AddRange(value);
List<string> nonNullItems = [];
foreach (string item in value)
{
if (item is not null)
{
nonNullItems.Add(item);
}
}

if (nonNullItems.Count <= 0)
{
return;
}

_data.AddRange(nonNullItems);
OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null));
}

Expand Down Expand Up @@ -110,8 +133,14 @@ public void Clear()
/// </summary>
public void Insert(int index, string value)
{
_data.Insert(index, value);
OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, value));
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThan(index, _data.Count);

if (value is not null)
{
_data.Insert(index, value);
OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, value));
}
}

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

public IEnumerator GetEnumerator() => _data.GetEnumerator();

internal string[] ToArray() => [.. _data];
}
Original file line number Diff line number Diff line change
Expand Up @@ -915,30 +915,20 @@ private int GetComboHeight()
return cyCombo;
}

private string[] GetStringsForAutoComplete(IList collection)
private string[] GetStringsForAutoComplete()
{
if (collection is AutoCompleteStringCollection)
if (Items is not null)
{
string[] strings = new string[AutoCompleteCustomSource.Count];
for (int i = 0; i < AutoCompleteCustomSource.Count; i++)
{
strings[i] = AutoCompleteCustomSource[i];
}

return strings;
}
else if (collection is ObjectCollection && _itemsCollection is not null)
{
string[] strings = new string[_itemsCollection.Count];
for (int i = 0; i < _itemsCollection.Count; i++)
string[] strings = new string[Items.Count];
for (int i = 0; i < Items.Count; i++)
{
strings[i] = GetItemText(_itemsCollection[i])!;
strings[i] = GetItemText(Items[i])!;
}

return strings;
}

return Array.Empty<string>();
return [];
}

/// <summary>
Expand Down Expand Up @@ -3283,15 +3273,15 @@ private void SetAutoComplete(bool reset, bool recreate)

if (_stringSource is null)
{
_stringSource = new StringSource(GetStringsForAutoComplete(AutoCompleteCustomSource));
_stringSource = new StringSource(AutoCompleteCustomSource.ToArray());
if (!_stringSource.Bind(_childEdit, (AUTOCOMPLETEOPTIONS)AutoCompleteMode))
{
throw new ArgumentException(SR.AutoCompleteFailure);
}
}
else
{
_stringSource.RefreshList(GetStringsForAutoComplete(AutoCompleteCustomSource));
_stringSource.RefreshList(AutoCompleteCustomSource.ToArray());
}

return;
Expand Down Expand Up @@ -3326,15 +3316,15 @@ private void SetAutoComplete(bool reset, bool recreate)

if (_stringSource is null)
{
_stringSource = new StringSource(GetStringsForAutoComplete(Items));
_stringSource = new StringSource(GetStringsForAutoComplete());
if (!_stringSource.Bind(_childEdit, (AUTOCOMPLETEOPTIONS)AutoCompleteMode))
{
throw new ArgumentException(SR.AutoCompleteFailureListItems);
}
}
else
{
_stringSource.RefreshList(GetStringsForAutoComplete(Items));
_stringSource.RefreshList(GetStringsForAutoComplete());
}

return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,17 +721,6 @@ private protected override void SelectInternal(int start, int length, int textLe
base.SelectInternal(start, length, textLen);
}

private string[] GetStringsForAutoComplete()
{
string[] strings = new string[AutoCompleteCustomSource.Count];
for (int i = 0; i < AutoCompleteCustomSource.Count; i++)
{
strings[i] = AutoCompleteCustomSource[i];
}

return strings;
}

/// <summary>
/// Sets the AutoComplete mode in TextBox.
/// </summary>
Expand Down Expand Up @@ -767,15 +756,15 @@ private unsafe void SetAutoComplete(bool reset)
{
if (_stringSource is null)
{
_stringSource = new StringSource(GetStringsForAutoComplete());
_stringSource = new StringSource(AutoCompleteCustomSource.ToArray());
if (!_stringSource.Bind(this, (AUTOCOMPLETEOPTIONS)AutoCompleteMode))
{
throw new ArgumentException(SR.AutoCompleteFailure);
}
}
else
{
_stringSource.RefreshList(GetStringsForAutoComplete());
_stringSource.RefreshList(AutoCompleteCustomSource.ToArray());
}
}
}
Expand Down
37 changes: 17 additions & 20 deletions src/System.Windows.Forms/src/System/Windows/Forms/StringSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,18 @@ namespace System.Windows.Forms;
/// </summary>
internal unsafe class StringSource : IEnumString.Interface, IManagedWrapper<IEnumString>
{
private string[] strings;
private int current;
private int size;
private string[] _strings;
private int _current;
private int _size;
private IAutoComplete2* _autoComplete2;

/// <summary>
/// Constructor.
/// </summary>
public StringSource(string[] strings)
{
Array.Clear(strings, 0, size);
this.strings = strings;

current = 0;
size = strings.Length;
_strings = strings;
_size = strings.Length;

PInvokeCore.CoCreateInstance(
in CLSID.AutoComplete,
Expand Down Expand Up @@ -68,10 +65,10 @@ public void ReleaseAutoComplete()

public void RefreshList(string[] newSource)
{
Array.Clear(strings, 0, size);
strings = newSource;
current = 0;
size = strings.Length;
Array.Clear(_strings, 0, _size);
_strings = newSource;
_current = 0;
_size = _strings.Length;
}

public unsafe HRESULT Clone(IEnumString** ppenum)
Expand All @@ -81,7 +78,7 @@ public unsafe HRESULT Clone(IEnumString** ppenum)
return HRESULT.E_POINTER;
}

*ppenum = ComHelpers.GetComPointer<IEnumString>(new StringSource(strings) { current = current });
*ppenum = ComHelpers.GetComPointer<IEnumString>(new StringSource(_strings) { _current = _current });
return HRESULT.S_OK;
}

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

uint fetched = 0;

while (current < size && celt > 0)
while (_current < _size && celt > 0)
{
rgelt[fetched] = (char*)Marshal.StringToCoTaskMemUni(strings[current]);
current++;
rgelt[fetched] = (char*)Marshal.StringToCoTaskMemUni(_strings[_current]);
_current++;
fetched++;
celt--;
}
Expand All @@ -112,19 +109,19 @@ public unsafe HRESULT Next(uint celt, PWSTR* rgelt, [Optional] uint* pceltFetche

public HRESULT Skip(uint celt)
{
int newCurrent = current + (int)celt;
if (newCurrent >= size)
int newCurrent = _current + (int)celt;
if (newCurrent >= _size)
{
return HRESULT.S_FALSE;
}

current = newCurrent;
_current = newCurrent;
return HRESULT.S_OK;
}

public HRESULT Reset()
{
current = 0;
_current = 0;
return HRESULT.S_OK;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ public void AutoCompleteStringCollection_AddRange_NullValue_ThrowsArgumentNullEx
Assert.Throws<ArgumentNullException>("value", () => collection.AddRange(null));
}

#nullable enable
[WinFormsFact]
public void AutoCompleteStringCollection_AddRange_NullValues_Nop()
{
AutoCompleteStringCollection collection = new();
collection.AddRange([null!]);
Assert.Empty(collection);
}

[WinFormsFact]
public void AutoCompleteStringCollection_Add_NullValue_ThrowsArgumentNullException()
{
AutoCompleteStringCollection collection = new();
Assert.Throws<ArgumentNullException>("value", () => collection.AddRange(null!));
}
#nullable disable

[WinFormsFact]
public void AutoCompleteStringCollection_Contains_Invoke_ReturnsExpected()
{
Expand Down Expand Up @@ -181,6 +198,16 @@ public void AutoCompleteStringCollection_IListInsert_InvalidIndex_ThrowsArgument
Assert.Throws<ArgumentOutOfRangeException>("index", () => collection.Insert(index, "value"));
}

#nullable enable
[WinFormsFact]
public void AutoCompleteStringCollection_IListInsert_NullItem_Nop()
{
IList collection = new AutoCompleteStringCollection();
collection.Insert(0, null);
Assert.Empty(collection);
}
#nullable disable

[WinFormsFact]
public void AutoCompleteStringCollection_Remove_String_Success()
{
Expand Down

0 comments on commit 6bd932c

Please sign in to comment.