Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public CodeNamespaceImport this[int index]
get => (CodeNamespaceImport)_data[index];
set
{
_data[index] = value;
_data[index] = Validated(value);
SyncKeys();
}
}
Expand Down Expand Up @@ -52,6 +52,17 @@ public void Clear()
_keys.Clear();
}

// Add(CodeNamespaceImport) reads value.Namespace before touching _data, so a null
// throws before the collection is modified. The IList members and the indexer setter
// store first, which leaves an element behind that SyncKeys() cannot enumerate.
// Reading Namespace here reproduces that same NullReferenceException at the same
// point, before the store.
private static CodeNamespaceImport Validated(CodeNamespaceImport value)
{
_ = value.Namespace;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the correct way to throw NullReferenceException. The compilers are allowed to treat the _ access as unused, despite they aren't currently doing so.

Also, it's not the pattern we use to validate a parameter then return it in a helper method.

return value;
}

private void SyncKeys()
{
_keys.Clear();
Expand Down Expand Up @@ -83,7 +94,7 @@ object IList.this[int index]

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

int IList.Add(object value) => _data.Add((CodeNamespaceImport)value);
int IList.Add(object value) => _data.Add(Validated((CodeNamespaceImport)value));

void IList.Clear() => Clear();

Expand All @@ -93,7 +104,7 @@ object IList.this[int index]

void IList.Insert(int index, object value)
{
_data.Insert(index, (CodeNamespaceImport)value);
_data.Insert(index, Validated((CodeNamespaceImport)value));
SyncKeys();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,40 @@ public void Add_Null_ThrowsNullReferenceException()
Assert.Throws<NullReferenceException>(() => collection.Add(null));
}

[Fact]
public void IListAdd_Null_ThrowsNullReferenceExceptionAndLeavesCollectionUsable()
{
IList collection = new CodeNamespaceImportCollection();
Assert.Throws<NullReferenceException>(() => collection.Add(null));

// The failed add must not leave an element behind, otherwise the next
// operation throws while rebuilding the namespace keys.
collection.Insert(0, new CodeNamespaceImport("Namespace"));
Assert.Equal(1, collection.Count);
}

[Fact]
public void IListInsert_Null_ThrowsNullReferenceExceptionAndLeavesCollectionUsable()
{
IList collection = new CodeNamespaceImportCollection();
Assert.Throws<NullReferenceException>(() => collection.Insert(0, null));

collection.Insert(0, new CodeNamespaceImport("Namespace"));
Assert.Equal(1, collection.Count);
}

[Fact]
public void ItemSet_Null_ThrowsNullReferenceExceptionAndLeavesCollectionUsable()
{
var collection = new CodeNamespaceImportCollection();
collection.Add(new CodeNamespaceImport("Namespace"));

Assert.Throws<NullReferenceException>(() => collection[0] = null);

Assert.Equal(1, collection.Count);
Assert.Equal("Namespace", collection[0].Namespace);
}

public static IEnumerable<object[]> AddRange_TestData()
{
yield return new object[] { new CodeNamespaceImport[0] };
Expand Down
Loading