Skip to content

ImmutableList<T>.LastIndexOf(...) appears to be missing a guard clause #70950

@Saborion

Description

@Saborion

Description

Searching for the last index of an item in an instance of ImmutableList<T> that holds at least one matching item can return the wrong index. This happens when the index argument passed in to ImmutableList<T>.LastIndexOf(T item, int index, int count, IEqualityComparer<T>? equalityComparer) equals the collection size, at which point the returned index will be off by one (+1), e.g. if the actual index is 2 the returned index will be 3.

Reproduction Steps

A .NET 6 console application with the following top level statements:

using System.Collections.Immutable;

var lastIndexOf = new[] { "NonMatch", "Match", "Match", "NonMatch" }
    .ToImmutableList()
    .LastIndexOf("Match", index: 3, count: 4, equalityComparer: null); // Returns 2 (correct)

var lastIndexOf2 = new[] { "NonMatch", "Match", "Match", "NonMatch" }
    .ToImmutableList()
    .LastIndexOf("Match", index: 4, count: 4, equalityComparer: null); // Returns 3 (incorrect, should throw?)

Console.WriteLine($"Last index when 'index' argument equals count - 1: {lastIndexOf}");
Console.WriteLine($"Last index when 'index' argument equals count    : {lastIndexOf2}");

Expected behavior

One of the two below:

  1. ImmutableList<T>.LastIndexOf(T item, int index, int count, IEqualityComparer<T>? equalityComparer) should throw if index equals the size of the collection, the same way startIndex does for ImmutableArray<T>.LastIndexOf(T item, int startIndex, int count, IEqualityComparer<T>? equalityComparer ).
  2. ImmutableList<T>.LastIndexOf(T item, int index, int count, IEqualityComparer<T>? equalityComparer) should return the correct index of the last item found when the index argument equals the size of the collection.

Actual behavior

The returned index is bigger (+1) than the correct index.

Regression?

No response

Known Workarounds

No response

Configuration

  • .NET 6
  • Windows 10, 64-bit
  • x64
  • Don't know if it is specific to this version

Other information

I suspect that the culprit is a missing guard clause for the upper boundary for the index parameter in the internal int LastIndexOf(T item, int index, int count, IEqualityComparer<T>? equalityComparer) method (line 786 as of this writing) in ImmutableList_1.Node.cs. This method is called from ImmutableList_1.cs.

Contrast the guard clause in the above mentiond method with the one from ImmutableArray_1.Builder.cs
ImmutableList<T> - Requires.Range(index >= 0, nameof(index));
vs
ImmutableArray<T> - Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex));
where the latter makes more sense.

Metadata

Metadata

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions