From a32b94326e5203cdadaf9b9666145c5f4e9ae7ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:07:32 +0000 Subject: [PATCH 01/16] Fix range checks in System.Collections.Immutable helpers for consistency and overflow handling Update range validation in Sort, GetRange, Reverse, CopyTo, BinarySearch, and FindIndex methods across ImmutableArray, ImmutableList, and their Builders to use the consistent pattern: - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); This ensures: 1. The correct parameter is reported as out of range (index vs count) 2. Overflow is handled via unsigned cast comparison 3. All methods use a consistent validation pattern Add test coverage for range validation in all updated methods. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f96d676a-8dac-4803-a986-ffa3fa632c37 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableArray_1.Builder.cs | 4 +- .../Collections/Immutable/ImmutableArray_1.cs | 8 +- .../Immutable/ImmutableList_1.Builder.cs | 17 ++-- .../Immutable/ImmutableList_1.Node.cs | 23 ++--- .../Collections/Immutable/ImmutableList_1.cs | 17 ++-- .../tests/ImmutableArrayBuilderTest.cs | 2 + .../tests/ImmutableArrayTest.cs | 3 +- .../tests/ImmutableListBuilderTest.cs | 53 ++++++++++++ .../tests/ImmutableListTest.cs | 84 +++++++++++++++++++ 9 files changed, 174 insertions(+), 37 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index 5d8dee3c2b8c85..1736fdb5590f43 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -1002,8 +1002,8 @@ public void Sort(int index, int count, IComparer? comparer) { // Don't rely on Array.Sort's argument validation since our internal array may exceed // the bounds of the publicly addressable region. - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0 && index + count <= this.Count, nameof(count)); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); if (count > 1) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 661818735afcb7..7eb48692b63fe9 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -173,8 +173,8 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa return -1; } - Requires.Range(startIndex >= 0 && startIndex <= self.Length, nameof(startIndex)); - Requires.Range(count >= 0 && (uint)(startIndex + count) <= (uint)self.Length, nameof(count)); + Requires.Range(startIndex >= 0 && startIndex <= self.Length, nameof(startIndex)); + Requires.Range(count >= 0 && (uint)(startIndex + count) <= (uint)self.Length, nameof(count)); equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) @@ -826,8 +826,8 @@ public ImmutableArray Sort(int index, int count, IComparer? comparer) { ImmutableArray self = this; self.ThrowNullRefIfNotInitialized(); - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0 && index + count <= self.Length, nameof(count)); + Requires.Range(index >= 0 && index <= self.Length, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)self.Length, nameof(count)); // 0 and 1 element arrays don't need to be sorted. if (count > 1) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs index 77021fc61681d7..f198214e3b8a2f 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs @@ -319,9 +319,8 @@ public void ForEach(Action action) /// public ImmutableList GetRange(int index, int count) { - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); - Requires.Range(index + count <= this.Count, nameof(count)); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); return ImmutableList.WrapNode(Node.NodeTreeFromList(this, index, count)); } @@ -851,9 +850,8 @@ public void Reverse() /// The number of elements in the range to reverse. public void Reverse(int index, int count) { - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); - Requires.Range(index + count <= this.Count, nameof(count)); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); this.Root = this.Root.Reverse(index, count); } @@ -910,9 +908,8 @@ public void Sort(IComparer? comparer) /// public void Sort(int index, int count, IComparer? comparer) { - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); - Requires.Range(index + count <= this.Count, nameof(count)); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); this.Root = this.Root.Sort(index, count, comparer); } @@ -993,6 +990,8 @@ public int BinarySearch(T item, IComparer? comparer) /// public int BinarySearch(int index, int count, T item, IComparer? comparer) { + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); return this.Root.BinarySearch(index, count, item, comparer); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs index c98d3719e21616..4a09c8f6408eff 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs @@ -534,9 +534,8 @@ internal Node ReplaceAt(int index, T value) /// The reversed list. internal Node Reverse(int index, int count) { - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); - Requires.Range(index + count <= this.Count, nameof(index)); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); Node result = this; int start = index; @@ -608,9 +607,8 @@ internal Node Sort(Comparison comparison) /// The sorted list. internal Node Sort(int index, int count, IComparer? comparer) { - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); - Requires.Argument(index + count <= this.Count); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); // PERF: Eventually this might be reimplemented in a way that does not require allocating an array. var array = new T[this.Count]; @@ -889,11 +887,9 @@ internal void CopyTo(T[] array, int arrayIndex) internal void CopyTo(int index, T[] array, int arrayIndex, int count) { Requires.NotNull(array, nameof(array)); - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); - Requires.Range(index + count <= this.Count, nameof(count)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(arrayIndex + count <= array.Length, nameof(arrayIndex)); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length, nameof(arrayIndex)); using (var enumerator = new Enumerator(this, startIndex: index, count: count)) { @@ -1124,9 +1120,8 @@ internal int FindIndex(int startIndex, Predicate match) internal int FindIndex(int startIndex, int count, Predicate match) { Requires.NotNull(match, nameof(match)); - Requires.Range(startIndex >= 0, nameof(startIndex)); - Requires.Range(count >= 0, nameof(count)); - Requires.Range(startIndex <= this.Count - count, nameof(count)); + Requires.Range(startIndex >= 0 && startIndex <= this.Count, nameof(startIndex)); + Requires.Range(count >= 0 && (uint)(startIndex + count) <= (uint)this.Count, nameof(count)); using (var enumerator = new Enumerator(this, startIndex: startIndex, count: count)) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs index dfd338d69959c3..043629427ccafc 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs @@ -121,7 +121,12 @@ private ImmutableList(Node root) /// cannot find an implementation of the generic interface /// or the interface for type . /// - public int BinarySearch(int index, int count, T item, IComparer? comparer) => _root.BinarySearch(index, count, item, comparer); + public int BinarySearch(int index, int count, T item, IComparer? comparer) + { + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + return _root.BinarySearch(index, count, item, comparer); + } #region IImmutableList Properties @@ -470,9 +475,8 @@ public ImmutableList Sort(Comparison comparison) /// The sorted list. public ImmutableList Sort(int index, int count, IComparer? comparer) { - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); - Requires.Range(index + count <= this.Count, nameof(count)); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); return this.Wrap(_root.Sort(index, count, comparer)); } @@ -553,9 +557,8 @@ public void ForEach(Action action) /// public ImmutableList GetRange(int index, int count) { - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); - Requires.Range(index + count <= this.Count, nameof(count)); + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); return this.Wrap(Node.NodeTreeFromList(this, index, count)); } diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs index 6dc60935452e19..44fa1b4266b677 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs @@ -533,8 +533,10 @@ public void SortRange() var builder = ImmutableArray.CreateBuilder(); builder.AddRange(2, 4, 1, 3); AssertExtensions.Throws("index", () => builder.Sort(-1, 2, Comparer.Default)); + AssertExtensions.Throws("index", () => builder.Sort(5, 0, Comparer.Default)); AssertExtensions.Throws("count", () => builder.Sort(1, 4, Comparer.Default)); AssertExtensions.Throws("count", () => builder.Sort(0, -1, Comparer.Default)); + AssertExtensions.Throws("count", () => builder.Sort(1, int.MaxValue, Comparer.Default)); builder.Sort(builder.Count, 0, Comparer.Default); Assert.Equal(new int[] { 2, 4, 1, 3 }, builder); diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 6787c977ed55d4..f4966409c9ae98 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -2120,8 +2120,9 @@ public void SortComparerInvalid(IEnumerable source) AssertExtensions.Throws("index", () => array.Sort(-1, 0, Comparer.Default)); AssertExtensions.Throws("count", () => array.Sort(0, -1, Comparer.Default)); - AssertExtensions.Throws("count", () => array.Sort(array.Length + 1, 0, Comparer.Default)); + AssertExtensions.Throws("index", () => array.Sort(array.Length + 1, 0, Comparer.Default)); AssertExtensions.Throws("count", () => array.Sort(0, array.Length + 1, Comparer.Default)); + AssertExtensions.Throws("count", () => array.Sort(0, int.MaxValue, Comparer.Default)); } [Theory] diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs index 7d2b9fe483bf4a..9d90dbeebb8376 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs @@ -484,6 +484,59 @@ public void ToImmutableList() AssertExtensions.Throws("builder", () => nullBuilder.ToImmutableList()); } + [Fact] + public void SortRangeValidation() + { + ImmutableList.Builder builder = ImmutableList.Create(1, 2, 3).ToBuilder(); + AssertExtensions.Throws("index", () => builder.Sort(-1, 0, null)); + AssertExtensions.Throws("index", () => builder.Sort(4, 0, null)); + AssertExtensions.Throws("count", () => builder.Sort(0, -1, null)); + AssertExtensions.Throws("count", () => builder.Sort(0, 4, null)); + AssertExtensions.Throws("count", () => builder.Sort(2, 2, null)); + AssertExtensions.Throws("count", () => builder.Sort(1, int.MaxValue, null)); + builder.Sort(3, 0, null); + Assert.Equal(new[] { 1, 2, 3 }, builder); + } + + [Fact] + public void GetRangeValidation() + { + ImmutableList.Builder builder = ImmutableList.Create(1, 2, 3).ToBuilder(); + AssertExtensions.Throws("index", () => builder.GetRange(-1, 0)); + AssertExtensions.Throws("index", () => builder.GetRange(4, 0)); + AssertExtensions.Throws("count", () => builder.GetRange(0, -1)); + AssertExtensions.Throws("count", () => builder.GetRange(0, 4)); + AssertExtensions.Throws("count", () => builder.GetRange(2, 2)); + AssertExtensions.Throws("count", () => builder.GetRange(1, int.MaxValue)); + Assert.True(builder.GetRange(3, 0).IsEmpty); + } + + [Fact] + public void ReverseRangeValidation() + { + ImmutableList.Builder builder = ImmutableList.Create(1, 2, 3).ToBuilder(); + AssertExtensions.Throws("index", () => builder.Reverse(-1, 0)); + AssertExtensions.Throws("index", () => builder.Reverse(4, 0)); + AssertExtensions.Throws("count", () => builder.Reverse(0, -1)); + AssertExtensions.Throws("count", () => builder.Reverse(0, 4)); + AssertExtensions.Throws("count", () => builder.Reverse(2, 2)); + AssertExtensions.Throws("count", () => builder.Reverse(1, int.MaxValue)); + builder.Reverse(3, 0); + Assert.Equal(new[] { 1, 2, 3 }, builder); + } + + [Fact] + public void BinarySearchRangeValidation() + { + ImmutableList.Builder builder = ImmutableList.Create(1, 2, 3, 4, 5).ToBuilder(); + AssertExtensions.Throws("index", () => builder.BinarySearch(-1, 0, 1, null)); + AssertExtensions.Throws("index", () => builder.BinarySearch(6, 0, 1, null)); + AssertExtensions.Throws("count", () => builder.BinarySearch(0, -1, 1, null)); + AssertExtensions.Throws("count", () => builder.BinarySearch(0, 6, 1, null)); + AssertExtensions.Throws("count", () => builder.BinarySearch(3, 3, 1, null)); + AssertExtensions.Throws("count", () => builder.BinarySearch(1, int.MaxValue, 1, null)); + } + protected override IEnumerable GetEnumerableOf(params T[] contents) { return ImmutableList.Empty.AddRange(contents).ToBuilder(); diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableListTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableListTest.cs index cc6fe537864641..2e7cc5d03c14ae 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableListTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableListTest.cs @@ -882,6 +882,90 @@ public void ItemRef_OutOfBounds() Assert.Throws(() => list.ItemRef(5)); } + [Fact] + public void SortRangeValidation() + { + ImmutableList list = ImmutableList.Create(1, 2, 3); + AssertExtensions.Throws("index", () => list.Sort(-1, 0, null)); + AssertExtensions.Throws("index", () => list.Sort(4, 0, null)); + AssertExtensions.Throws("count", () => list.Sort(0, -1, null)); + AssertExtensions.Throws("count", () => list.Sort(0, 4, null)); + AssertExtensions.Throws("count", () => list.Sort(2, 2, null)); + AssertExtensions.Throws("count", () => list.Sort(1, int.MaxValue, null)); + Assert.Equal(list, list.Sort(3, 0, null)); + Assert.Equal(list, list.Sort(0, 0, null)); + } + + [Fact] + public void GetRangeValidation() + { + ImmutableList list = ImmutableList.Create(1, 2, 3); + AssertExtensions.Throws("index", () => list.GetRange(-1, 0)); + AssertExtensions.Throws("index", () => list.GetRange(4, 0)); + AssertExtensions.Throws("count", () => list.GetRange(0, -1)); + AssertExtensions.Throws("count", () => list.GetRange(0, 4)); + AssertExtensions.Throws("count", () => list.GetRange(2, 2)); + AssertExtensions.Throws("count", () => list.GetRange(1, int.MaxValue)); + Assert.True(list.GetRange(3, 0).IsEmpty); + Assert.True(list.GetRange(0, 0).IsEmpty); + } + + [Fact] + public void ReverseRangeValidation() + { + ImmutableList list = ImmutableList.Create(1, 2, 3); + AssertExtensions.Throws("index", () => list.Reverse(-1, 0)); + AssertExtensions.Throws("index", () => list.Reverse(4, 0)); + AssertExtensions.Throws("count", () => list.Reverse(0, -1)); + AssertExtensions.Throws("count", () => list.Reverse(0, 4)); + AssertExtensions.Throws("count", () => list.Reverse(2, 2)); + AssertExtensions.Throws("count", () => list.Reverse(1, int.MaxValue)); + Assert.Equal(list, list.Reverse(3, 0)); + Assert.Equal(list, list.Reverse(0, 0)); + } + + [Fact] + public void CopyToRangeValidation() + { + ImmutableList list = ImmutableList.Create(1, 2, 3); + var array = new int[5]; + AssertExtensions.Throws("index", () => list.CopyTo(-1, array, 0, 0)); + AssertExtensions.Throws("index", () => list.CopyTo(4, array, 0, 0)); + AssertExtensions.Throws("count", () => list.CopyTo(0, array, 0, -1)); + AssertExtensions.Throws("count", () => list.CopyTo(0, array, 0, 4)); + AssertExtensions.Throws("count", () => list.CopyTo(2, array, 0, 2)); + AssertExtensions.Throws("count", () => list.CopyTo(0, array, 0, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => list.CopyTo(0, array, -1, 3)); + AssertExtensions.Throws("arrayIndex", () => list.CopyTo(0, array, 3, 3)); + AssertExtensions.Throws("arrayIndex", () => list.CopyTo(0, array, 4, 2)); + } + + [Fact] + public void BinarySearchRangeValidation() + { + ImmutableList list = ImmutableList.Create(1, 2, 3, 4, 5); + AssertExtensions.Throws("index", () => list.BinarySearch(-1, 0, 1, null)); + AssertExtensions.Throws("index", () => list.BinarySearch(6, 0, 1, null)); + AssertExtensions.Throws("count", () => list.BinarySearch(0, -1, 1, null)); + AssertExtensions.Throws("count", () => list.BinarySearch(0, 6, 1, null)); + AssertExtensions.Throws("count", () => list.BinarySearch(3, 3, 1, null)); + AssertExtensions.Throws("count", () => list.BinarySearch(1, int.MaxValue, 1, null)); + } + + [Fact] + public void FindIndexRangeValidation() + { + ImmutableList list = ImmutableList.Create(1, 2, 3); + AssertExtensions.Throws("startIndex", () => list.FindIndex(-1, 0, _ => true)); + AssertExtensions.Throws("startIndex", () => list.FindIndex(4, 0, _ => true)); + AssertExtensions.Throws("count", () => list.FindIndex(0, -1, _ => true)); + AssertExtensions.Throws("count", () => list.FindIndex(0, 4, _ => true)); + AssertExtensions.Throws("count", () => list.FindIndex(2, 2, _ => true)); + AssertExtensions.Throws("count", () => list.FindIndex(1, int.MaxValue, _ => true)); + Assert.Equal(-1, list.FindIndex(3, 0, _ => true)); + Assert.Equal(-1, list.FindIndex(0, 0, _ => true)); + } + protected override IEnumerable GetEnumerableOf(params T[] contents) { return ImmutableList.Empty.AddRange((IEnumerable)contents); From 371d0110d9caa19ea106fab7e83357d550507e74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 19:44:49 +0000 Subject: [PATCH 02/16] Convert redundant Requires.Range to Debug.Assert in Node.BinarySearch Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ce7b2aae-5f0d-42b0-a9d0-7055fae4370b Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../src/System/Collections/Immutable/ImmutableList_1.Node.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs index 4a09c8f6408eff..ccbc9d586f03fc 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs @@ -648,8 +648,8 @@ internal Node Sort(int index, int count, IComparer? comparer) /// internal int BinarySearch(int index, int count, T item, IComparer? comparer) { - Requires.Range(index >= 0, nameof(index)); - Requires.Range(count >= 0, nameof(count)); + Debug.Assert(index >= 0); + Debug.Assert(count >= 0); comparer ??= Comparer.Default; if (this.IsEmpty || count <= 0) From 625bbda271cf6d69cd5cb05a16ff21d117e636ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:05:07 +0000 Subject: [PATCH 03/16] Convert redundant checks in Node.Sort and Node.Reverse to Debug.Assert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both public callers (ImmutableList and ImmutableList.Builder) already validate arguments before delegating to Node.Sort and Node.Reverse. The Node's own Requires.Range/NotNull calls are therefore unreachable and can be converted to Debug.Assert. For ImmutableList.Reverse(int, int), validation was previously only in the Node — added Requires.Range to the public method to maintain the public contract, then converted the Node's checks to Debug.Assert. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/38cf8f63-313a-4f03-b51a-7f322887754c Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Collections/Immutable/ImmutableList_1.Node.cs | 10 +++++----- .../System/Collections/Immutable/ImmutableList_1.cs | 8 +++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs index ccbc9d586f03fc..bc4626d9d40d3d 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs @@ -534,8 +534,8 @@ internal Node ReplaceAt(int index, T value) /// The reversed list. internal Node Reverse(int index, int count) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Debug.Assert(index >= 0 && index <= this.Count); + Debug.Assert(count >= 0 && (uint)(index + count) <= (uint)this.Count); Node result = this; int start = index; @@ -570,7 +570,7 @@ internal Node Reverse(int index, int count) /// The sorted list. internal Node Sort(Comparison comparison) { - Requires.NotNull(comparison, nameof(comparison)); + Debug.Assert(comparison != null); // PERF: Eventually this might be reimplemented in a way that does not require allocating an array. var array = new T[this.Count]; @@ -607,8 +607,8 @@ internal Node Sort(Comparison comparison) /// The sorted list. internal Node Sort(int index, int count, IComparer? comparer) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Debug.Assert(index >= 0 && index <= this.Count); + Debug.Assert(count >= 0 && (uint)(index + count) <= (uint)this.Count); // PERF: Eventually this might be reimplemented in a way that does not require allocating an array. var array = new T[this.Count]; diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs index 043629427ccafc..1366ccd222a8f5 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs @@ -424,7 +424,13 @@ public ImmutableList Replace(T oldValue, T newValue, IEqualityComparer? eq /// The zero-based starting index of the range to reverse. /// The number of elements in the range to reverse. /// The reversed list. - public ImmutableList Reverse(int index, int count) => this.Wrap(_root.Reverse(index, count)); + public ImmutableList Reverse(int index, int count) + { + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + + return this.Wrap(_root.Reverse(index, count)); + } /// /// Sorts the elements in the entire using From 5c1e0b014ec3df20072a3e55574ee67512ecd19e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:16:11 +0000 Subject: [PATCH 04/16] Add Requires.ValidateRange shared helper and normalize blank lines after validation Introduce Requires.ValidateRange(index, count, listCount, indexParameterName) to consolidate the repeated index/count range validation pattern. Applied across ImmutableList, Builder, and Node, replacing 10 duplicate two-line Requires.Range pairs with single ValidateRange calls. Also normalize whitespace so every validation block is followed by a blank line before the method body. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c34e5da8-17ce-4dc8-b489-4316b2fce76b Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableList_1.Builder.cs | 19 ++++++++++-------- .../Immutable/ImmutableList_1.Node.cs | 9 +++------ .../Collections/Immutable/ImmutableList_1.cs | 18 +++++++++-------- .../src/Validation/Requires.cs | 20 +++++++++++++++++++ 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs index f198214e3b8a2f..6642a3595bad4a 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs @@ -54,6 +54,7 @@ public sealed class Builder : IList, IList, IReadOnlyList internal Builder(ImmutableList list) { Requires.NotNull(list, nameof(list)); + _root = list._root; _immutable = list; } @@ -319,8 +320,8 @@ public void ForEach(Action action) /// public ImmutableList GetRange(int index, int count) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); + return ImmutableList.WrapNode(Node.NodeTreeFromList(this, index, count)); } @@ -342,6 +343,7 @@ public ImmutableList GetRange(int index, int count) public ImmutableList ConvertAll(Func converter) { Requires.NotNull(converter, nameof(converter)); + return ImmutableList.WrapNode(_root.ConvertAll(converter)); } @@ -850,8 +852,7 @@ public void Reverse() /// The number of elements in the range to reverse. public void Reverse(int index, int count) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); this.Root = this.Root.Reverse(index, count); } @@ -876,6 +877,7 @@ public void Sort() public void Sort(Comparison comparison) { Requires.NotNull(comparison, nameof(comparison)); + this.Root = this.Root.Sort(comparison); } @@ -908,8 +910,8 @@ public void Sort(IComparer? comparer) /// public void Sort(int index, int count, IComparer? comparer) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); + this.Root = this.Root.Sort(index, count, comparer); } @@ -990,8 +992,8 @@ public int BinarySearch(T item, IComparer? comparer) /// public int BinarySearch(int index, int count, T item, IComparer? comparer) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); + return this.Root.BinarySearch(index, count, item, comparer); } @@ -1177,6 +1179,7 @@ internal sealed class ImmutableListBuilderDebuggerProxy public ImmutableListBuilderDebuggerProxy(ImmutableList.Builder builder) { Requires.NotNull(builder, nameof(builder)); + _list = builder; } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs index bc4626d9d40d3d..5992820fb4949d 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs @@ -757,8 +757,7 @@ internal int BinarySearch(int index, int count, T item, IComparer? comparer) /// internal int IndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); equalityComparer ??= EqualityComparer.Default; using (var enumerator = new Enumerator(this, startIndex: index, count: count)) @@ -887,8 +886,7 @@ internal void CopyTo(T[] array, int arrayIndex) internal void CopyTo(int index, T[] array, int arrayIndex, int count) { Requires.NotNull(array, nameof(array)); - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length, nameof(arrayIndex)); using (var enumerator = new Enumerator(this, startIndex: index, count: count)) @@ -1120,8 +1118,7 @@ internal int FindIndex(int startIndex, Predicate match) internal int FindIndex(int startIndex, int count, Predicate match) { Requires.NotNull(match, nameof(match)); - Requires.Range(startIndex >= 0 && startIndex <= this.Count, nameof(startIndex)); - Requires.Range(count >= 0 && (uint)(startIndex + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); using (var enumerator = new Enumerator(this, startIndex: startIndex, count: count)) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs index 1366ccd222a8f5..2776e380a34111 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs @@ -123,8 +123,8 @@ private ImmutableList(Node root) /// public int BinarySearch(int index, int count, T item, IComparer? comparer) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); + return _root.BinarySearch(index, count, item, comparer); } @@ -264,6 +264,7 @@ internal ImmutableList AddRange(ReadOnlySpan items) public ImmutableList Insert(int index, T item) { Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + return this.Wrap(_root.Insert(index, item)); } @@ -366,6 +367,7 @@ public ImmutableList RemoveRange(IEnumerable items, IEqualityComparer? public ImmutableList RemoveAt(int index) { Requires.Range(index >= 0 && index < this.Count, nameof(index)); + ImmutableList.Node result = _root.RemoveAt(index); return this.Wrap(result); } @@ -426,8 +428,7 @@ public ImmutableList Replace(T oldValue, T newValue, IEqualityComparer? eq /// The reversed list. public ImmutableList Reverse(int index, int count) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); return this.Wrap(_root.Reverse(index, count)); } @@ -450,6 +451,7 @@ public ImmutableList Reverse(int index, int count) public ImmutableList Sort(Comparison comparison) { Requires.NotNull(comparison, nameof(comparison)); + return this.Wrap(_root.Sort(comparison)); } @@ -481,8 +483,7 @@ public ImmutableList Sort(Comparison comparison) /// The sorted list. public ImmutableList Sort(int index, int count, IComparer? comparer) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); return this.Wrap(_root.Sort(index, count, comparer)); } @@ -563,8 +564,8 @@ public void ForEach(Action action) /// public ImmutableList GetRange(int index, int count) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); + return this.Wrap(Node.NodeTreeFromList(this, index, count)); } @@ -586,6 +587,7 @@ public ImmutableList GetRange(int index, int count) public ImmutableList ConvertAll(Func converter) { Requires.NotNull(converter, nameof(converter)); + return ImmutableList.WrapNode(_root.ConvertAll(converter)); } diff --git a/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs b/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs index ac23bd81eed829..fdf710288a7055 100644 --- a/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs +++ b/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs @@ -106,6 +106,26 @@ public static void FailRange(string? parameterName, string? message = null) } } + /// + /// Validates that and represent a valid range + /// within a collection of size . + /// + /// The zero-based starting index of the range. + /// The number of elements in the range. + /// The total number of elements in the collection. + /// The name of the index parameter for exception messages. + /// + /// is negative or greater than , + /// or is negative or index + count exceeds . + /// + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateRange(int index, int count, int listCount, string indexParameterName = "index") + { + Range(index >= 0 && index <= listCount, indexParameterName); + Range(count >= 0 && (uint)(index + count) <= (uint)listCount, nameof(count)); + } + /// /// Throws an if a condition does not evaluate to true. /// From 77cc8ccc46ddcd80b14862261a444133aeba8f46 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:45:23 +0000 Subject: [PATCH 05/16] Apply Requires.ValidateRange to ImmutableArray and ImmutableArray.Builder Replace duplicate index+count range validation pairs with the shared ValidateRange helper in four locations: - ImmutableArray_1.cs IndexOf (startIndex, count) - ImmutableArray_1.cs Sort (index, count) - ImmutableArray_1.Builder.cs IndexOf (startIndex, count) - ImmutableArray_1.Builder.cs Sort (index, count) Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d4cf6216-8bd8-470d-becc-84c58ee08b67 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Collections/Immutable/ImmutableArray_1.Builder.cs | 6 ++---- .../src/System/Collections/Immutable/ImmutableArray_1.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index 1736fdb5590f43..ea320e00a37bfc 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -798,8 +798,7 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa return -1; } - Requires.Range(startIndex >= 0 && startIndex <= this.Count, nameof(startIndex)); - Requires.Range(count >= 0 && (uint)(startIndex + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) @@ -1002,8 +1001,7 @@ public void Sort(int index, int count, IComparer? comparer) { // Don't rely on Array.Sort's argument validation since our internal array may exceed // the bounds of the publicly addressable region. - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)this.Count, nameof(count)); + Requires.ValidateRange(index, count, this.Count); if (count > 1) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 7eb48692b63fe9..9bdf320978a7c4 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -173,8 +173,7 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa return -1; } - Requires.Range(startIndex >= 0 && startIndex <= self.Length, nameof(startIndex)); - Requires.Range(count >= 0 && (uint)(startIndex + count) <= (uint)self.Length, nameof(count)); + Requires.ValidateRange(startIndex, count, self.Length, nameof(startIndex)); equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) @@ -826,8 +825,7 @@ public ImmutableArray Sort(int index, int count, IComparer? comparer) { ImmutableArray self = this; self.ThrowNullRefIfNotInitialized(); - Requires.Range(index >= 0 && index <= self.Length, nameof(index)); - Requires.Range(count >= 0 && (uint)(index + count) <= (uint)self.Length, nameof(count)); + Requires.ValidateRange(index, count, self.Length); // 0 and 1 element arrays don't need to be sorted. if (count > 1) From 06ce7021bb9713fbfa98b0d8865694ba7cec3fa3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 23:13:40 +0000 Subject: [PATCH 06/16] Replace duplicated Requires.* with Debug.Assert in ImmutableList.Node Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/1fe4a74d-5349-47fc-b3f6-9db3815d884d Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableList_1.Node.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs index 5992820fb4949d..784329acf261ee 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs @@ -81,8 +81,8 @@ private Node() /// Whether this node is prefrozen. private Node(T key, Node left, Node right, bool frozen = false) { - Requires.NotNull(left, nameof(left)); - Requires.NotNull(right, nameof(right)); + Debug.Assert(left is not null); + Debug.Assert(right is not null); Debug.Assert(!frozen || (left._frozen && right._frozen)); _key = key; @@ -242,9 +242,9 @@ private ref readonly T ItemRefUnchecked(int index) /// The root of the created node tree. internal static Node NodeTreeFromList(IReadOnlyList items, int start, int length) { - Requires.NotNull(items, nameof(items)); - Requires.Range(start >= 0, nameof(start)); - Requires.Range(length >= 0, nameof(length)); + Debug.Assert(items is not null); + Debug.Assert(start >= 0); + Debug.Assert(length >= 0); if (length == 0) { @@ -330,7 +330,7 @@ internal Node Insert(int index, T key) /// The new tree. internal Node AddRange(IEnumerable keys) { - Requires.NotNull(keys, nameof(keys)); + Debug.Assert(keys is not null); if (this.IsEmpty) { @@ -367,8 +367,8 @@ internal Node AddRange(ReadOnlySpan keys) /// The new tree. internal Node InsertRange(int index, IEnumerable keys) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); - Requires.NotNull(keys, nameof(keys)); + Debug.Assert(index >= 0 && index <= this.Count); + Debug.Assert(keys is not null); if (this.IsEmpty) { @@ -459,7 +459,7 @@ internal Node RemoveAt(int index) /// internal Node RemoveAll(Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match is not null); ImmutableList.Node result = this; var enumerator = new Enumerator(result); @@ -1442,8 +1442,8 @@ private Node BalanceMany() /// The mutated (or created) node. private Node MutateBoth(Node left, Node right) { - Requires.NotNull(left, nameof(left)); - Requires.NotNull(right, nameof(right)); + Debug.Assert(left is not null); + Debug.Assert(right is not null); Debug.Assert(!this.IsEmpty); if (_frozen) @@ -1468,7 +1468,7 @@ private Node MutateBoth(Node left, Node right) /// The mutated (or created) node. private Node MutateLeft(Node left) { - Requires.NotNull(left, nameof(left)); + Debug.Assert(left is not null); Debug.Assert(!this.IsEmpty); if (_frozen) @@ -1492,7 +1492,7 @@ private Node MutateLeft(Node left) /// The mutated (or created) node. private Node MutateRight(Node right) { - Requires.NotNull(right, nameof(right)); + Debug.Assert(right is not null); Debug.Assert(!this.IsEmpty); if (_frozen) From ff4da43b2af3cc491cf2142373e502c5739b3b8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:02:33 +0000 Subject: [PATCH 07/16] Move validation from Node to public API surface; convert Node checks to Debug.Assert Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3b4d1d4f-650f-475e-b3bf-7815be22bc4b Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableList_1.Builder.cs | 142 ++++++++++++++--- .../Immutable/ImmutableList_1.Node.cs | 79 +++++----- .../Collections/Immutable/ImmutableList_1.cs | 149 +++++++++++++++--- 3 files changed, 290 insertions(+), 80 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs index 6642a3595bad4a..38b0d762295b36 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs @@ -126,10 +126,12 @@ public T this[int index] { get { + Requires.Range(index >= 0 && index < this.Count, nameof(index)); return this.Root.ItemRef(index); } set { + Requires.Range(index >= 0 && index < this.Count, nameof(index)); this.Root = this.Root.ReplaceAt(index, value); } } @@ -141,6 +143,7 @@ public T this[int index] /// A read-only reference to the value at the specified index. public ref readonly T ItemRef(int index) { + Requires.Range(index >= 0 && index < this.Count, nameof(index)); return ref this.Root.ItemRef(index); } @@ -161,6 +164,7 @@ public int IndexOf(T item) /// public void Insert(int index, T item) { + Requires.Range(index >= 0 && index <= this.Count, nameof(index)); this.Root = this.Root.Insert(index, item); } @@ -169,6 +173,7 @@ public void Insert(int index, T item) /// public void RemoveAt(int index) { + Requires.Range(index >= 0 && index < this.Count, nameof(index)); this.Root = this.Root.RemoveAt(index); } @@ -271,7 +276,12 @@ public void ForEach(Action action) /// copied from ImmutableList<T>. The System.Array must have /// zero-based indexing. /// - public void CopyTo(T[] array) => _root.CopyTo(array); + public void CopyTo(T[] array) + { + Requires.NotNull(array, nameof(array)); + Requires.Range(array.Length >= this.Count, nameof(array)); + _root.CopyTo(array); + } /// /// Copies the entire ImmutableList<T> to a compatible one-dimensional @@ -285,7 +295,13 @@ public void ForEach(Action action) /// /// The zero-based index in array at which copying begins. /// - public void CopyTo(T[] array, int arrayIndex) => _root.CopyTo(array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) + { + Requires.NotNull(array, nameof(array)); + Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); + Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + _root.CopyTo(array, arrayIndex); + } /// /// Copies a range of elements from the ImmutableList<T> to @@ -303,7 +319,13 @@ public void ForEach(Action action) /// /// The zero-based index in array at which copying begins. /// The number of elements to copy. - public void CopyTo(int index, T[] array, int arrayIndex, int count) => _root.CopyTo(index, array, arrayIndex, count); + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + Requires.NotNull(array, nameof(array)); + Requires.ValidateRange(index, count, this.Count); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length, nameof(arrayIndex)); + _root.CopyTo(index, array, arrayIndex, count); + } /// /// Creates a shallow copy of a range of elements in the source ImmutableList<T>. @@ -360,7 +382,11 @@ public ImmutableList ConvertAll(Func converter) /// that match the conditions defined by the specified predicate; otherwise, /// false. /// - public bool Exists(Predicate match) => _root.Exists(match); + public bool Exists(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.Exists(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -374,7 +400,11 @@ public ImmutableList ConvertAll(Func converter) /// The first element that matches the conditions defined by the specified predicate, /// if found; otherwise, the default value for type T. /// - public T? Find(Predicate match) => _root.Find(match); + public T? Find(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.Find(match); + } /// /// Retrieves all the elements that match the conditions defined by the specified @@ -389,7 +419,11 @@ public ImmutableList ConvertAll(Func converter) /// the conditions defined by the specified predicate, if found; otherwise, an /// empty ImmutableList<T>. /// - public ImmutableList FindAll(Predicate match) => _root.FindAll(match); + public ImmutableList FindAll(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.FindAll(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -404,7 +438,11 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the first occurrence of an element that matches the /// conditions defined by match, if found; otherwise, -1. /// - public int FindIndex(Predicate match) => _root.FindIndex(match); + public int FindIndex(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.FindIndex(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -418,7 +456,12 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the first occurrence of an element that matches the /// conditions defined by match, if found; otherwise, -1. /// - public int FindIndex(int startIndex, Predicate match) => _root.FindIndex(startIndex, match); + public int FindIndex(int startIndex, Predicate match) + { + Requires.NotNull(match, nameof(match)); + Requires.Range(startIndex >= 0 && startIndex <= this.Count, nameof(startIndex)); + return _root.FindIndex(startIndex, match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -433,7 +476,12 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the first occurrence of an element that matches the /// conditions defined by match, if found; otherwise, -1. /// - public int FindIndex(int startIndex, int count, Predicate match) => _root.FindIndex(startIndex, count, match); + public int FindIndex(int startIndex, int count, Predicate match) + { + Requires.NotNull(match, nameof(match)); + Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); + return _root.FindIndex(startIndex, count, match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -447,7 +495,11 @@ public ImmutableList ConvertAll(Func converter) /// The last element that matches the conditions defined by the specified predicate, /// if found; otherwise, the default value for type T. /// - public T? FindLast(Predicate match) => _root.FindLast(match); + public T? FindLast(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.FindLast(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -462,7 +514,11 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the last occurrence of an element that matches the /// conditions defined by match, if found; otherwise, -1. /// - public int FindLastIndex(Predicate match) => _root.FindLastIndex(match); + public int FindLastIndex(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.FindLastIndex(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -477,7 +533,13 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the last occurrence of an element that matches the /// conditions defined by match, if found; otherwise, -1. /// - public int FindLastIndex(int startIndex, Predicate match) => _root.FindLastIndex(startIndex, match); + public int FindLastIndex(int startIndex, Predicate match) + { + Requires.NotNull(match, nameof(match)); + Requires.Range(startIndex >= 0, nameof(startIndex)); + Requires.Range(startIndex == 0 || startIndex < this.Count, nameof(startIndex)); + return _root.FindLastIndex(startIndex, match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -495,7 +557,19 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the last occurrence of an element that matches the /// conditions defined by match, if found; otherwise, -1. /// - public int FindLastIndex(int startIndex, int count, Predicate match) => _root.FindLastIndex(startIndex, count, match); + public int FindLastIndex(int startIndex, int count, Predicate match) + { + Requires.NotNull(match, nameof(match)); + if (startIndex == 0 && count == 0) + { + return -1; + } + + Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); + Requires.Range(count >= 0 && count <= this.Count, nameof(count)); + Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + return _root.FindLastIndex(startIndex, count, match); + } /// /// Searches for the specified object and returns the zero-based index of the @@ -515,8 +589,11 @@ public ImmutableList ConvertAll(Func converter) /// elements in the ImmutableList<T> that extends from index /// to the last element, if found; otherwise, -1. /// - public int IndexOf(T item, int index) => - _root.IndexOf(item, index, this.Count - index, EqualityComparer.Default); + public int IndexOf(T item, int index) + { + Requires.ValidateRange(index, this.Count - index, this.Count); + return _root.IndexOf(item, index, this.Count - index, EqualityComparer.Default); + } /// /// Searches for the specified object and returns the zero-based index of the @@ -540,7 +617,7 @@ public int IndexOf(T item, int index) => /// contains count number of elements, if found; otherwise, -1. /// public int IndexOf(T item, int index, int count) => - _root.IndexOf(item, index, count, EqualityComparer.Default); + this.IndexOf(item, index, count, EqualityComparer.Default); /// /// Searches for the specified object and returns the zero-based index of the @@ -567,8 +644,11 @@ public int IndexOf(T item, int index, int count) => /// elements in the ImmutableList<T> that starts at index and /// contains count number of elements, if found; otherwise, -1. /// - public int IndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) => - _root.IndexOf(item, index, count, equalityComparer); + public int IndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) + { + Requires.ValidateRange(index, count, this.Count); + return _root.IndexOf(item, index, count, equalityComparer); + } /// /// Searches for the specified object and returns the zero-based index of the @@ -618,6 +698,7 @@ public int LastIndexOf(T item, int startIndex) return -1; } + Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); return _root.LastIndexOf(item, startIndex, startIndex + 1, EqualityComparer.Default); } @@ -639,7 +720,7 @@ public int LastIndexOf(T item, int startIndex) /// and ends at index, if found; otherwise, -1. /// public int LastIndexOf(T item, int startIndex, int count) => - _root.LastIndexOf(item, startIndex, count, EqualityComparer.Default); + this.LastIndexOf(item, startIndex, count, EqualityComparer.Default); /// /// Searches for the specified object and returns the zero-based index of the @@ -659,8 +740,18 @@ public int LastIndexOf(T item, int startIndex, int count) => /// in the ImmutableList<T> that contains count number of elements /// and ends at index, if found; otherwise, -1. /// - public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? equalityComparer) => - _root.LastIndexOf(item, startIndex, count, equalityComparer); + public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? equalityComparer) + { + if (startIndex == 0 && count == 0) + { + return -1; + } + + Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); + Requires.Range(count >= 0 && count <= this.Count, nameof(count)); + Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + return _root.LastIndexOf(item, startIndex, count, equalityComparer); + } /// /// Determines whether every element in the ImmutableList<T> @@ -675,7 +766,11 @@ public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? /// conditions defined by the specified predicate; otherwise, false. If the list /// has no elements, the return value is true. /// - public bool TrueForAll(Predicate match) => _root.TrueForAll(match); + public bool TrueForAll(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.TrueForAll(match); + } #endregion @@ -1138,6 +1233,9 @@ void IList.Remove(object? value) /// The zero-based index in at which copying begins. void ICollection.CopyTo(Array array, int arrayIndex) { + Requires.NotNull(array, nameof(array)); + Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); + Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); this.Root.CopyTo(array, arrayIndex); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs index 784329acf261ee..4607b452dedeee 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs @@ -149,7 +149,7 @@ internal T this[int index] { get { - Requires.Range(index >= 0 && index < this.Count, nameof(index)); + Debug.Assert(index >= 0 && index < this.Count); Debug.Assert(_left != null && _right != null); if (index < _left._count) @@ -173,7 +173,7 @@ internal T this[int index] /// A read-only reference to the element at the given position. internal ref readonly T ItemRef(int index) { - Requires.Range(index >= 0 && index < this.Count, nameof(index)); + Debug.Assert(index >= 0 && index < this.Count); return ref ItemRefUnchecked(index); } @@ -302,7 +302,7 @@ internal Node Add(T key) /// The new tree. internal Node Insert(int index, T key) { - Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + Debug.Assert(index >= 0 && index <= this.Count); if (this.IsEmpty) { @@ -397,7 +397,7 @@ internal Node InsertRange(int index, IEnumerable keys) /// The new tree. internal Node RemoveAt(int index) { - Requires.Range(index >= 0 && index < this.Count, nameof(index)); + Debug.Assert(index >= 0 && index < this.Count); Debug.Assert(_left != null && _right != null); Node result; @@ -497,7 +497,7 @@ internal Node RemoveAll(Predicate match) /// The new tree. internal Node ReplaceAt(int index, T value) { - Requires.Range(index >= 0 && index < this.Count, nameof(index)); + Debug.Assert(index >= 0 && index < this.Count); Debug.Assert(!this.IsEmpty); Node result; @@ -757,7 +757,8 @@ internal int BinarySearch(int index, int count, T item, IComparer? comparer) /// internal int IndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) { - Requires.ValidateRange(index, count, this.Count); + Debug.Assert(index >= 0 && index <= this.Count); + Debug.Assert(count >= 0 && (uint)(index + count) <= (uint)this.Count); equalityComparer ??= EqualityComparer.Default; using (var enumerator = new Enumerator(this, startIndex: index, count: count)) @@ -801,9 +802,9 @@ internal int LastIndexOf(T item, int startIndex, int count, IEqualityComparer return -1; } - Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); - Requires.Range(count >= 0 && count <= this.Count, nameof(count)); - Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + Debug.Assert(startIndex >= 0 && startIndex < this.Count); + Debug.Assert(count >= 0 && count <= this.Count); + Debug.Assert(startIndex - count + 1 >= 0); equalityComparer ??= EqualityComparer.Default; using (var enumerator = new Enumerator(this, startIndex: startIndex, count: count, reversed: true)) @@ -833,8 +834,8 @@ internal int LastIndexOf(T item, int startIndex, int count, IEqualityComparer /// internal void CopyTo(T[] array) { - Requires.NotNull(array, nameof(array)); - Requires.Range(array.Length >= this.Count, nameof(array)); + Debug.Assert(array != null); + Debug.Assert(array.Length >= this.Count); int index = 0; foreach (T element in this) @@ -857,9 +858,9 @@ internal void CopyTo(T[] array) /// internal void CopyTo(T[] array, int arrayIndex) { - Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Debug.Assert(array != null); + Debug.Assert(arrayIndex >= 0); + Debug.Assert(array.Length >= arrayIndex + this.Count); foreach (T element in this) { @@ -885,9 +886,10 @@ internal void CopyTo(T[] array, int arrayIndex) /// The number of elements to copy. internal void CopyTo(int index, T[] array, int arrayIndex, int count) { - Requires.NotNull(array, nameof(array)); - Requires.ValidateRange(index, count, this.Count); - Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length, nameof(arrayIndex)); + Debug.Assert(array != null); + Debug.Assert(index >= 0 && index <= this.Count); + Debug.Assert(count >= 0 && (uint)(index + count) <= (uint)this.Count); + Debug.Assert(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length); using (var enumerator = new Enumerator(this, startIndex: index, count: count)) { @@ -905,9 +907,9 @@ internal void CopyTo(int index, T[] array, int arrayIndex, int count) /// The zero-based index in at which copying begins. internal void CopyTo(Array array, int arrayIndex) { - Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Debug.Assert(array != null); + Debug.Assert(arrayIndex >= 0); + Debug.Assert(array.Length >= arrayIndex + this.Count); foreach (T element in this) { @@ -956,7 +958,7 @@ internal ImmutableList.Node ConvertAll(Func conver /// internal bool TrueForAll(Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match != null); foreach (T item in this) { @@ -984,7 +986,7 @@ internal bool TrueForAll(Predicate match) /// internal bool Exists(Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match != null); foreach (T item in this) { @@ -1011,7 +1013,7 @@ internal bool Exists(Predicate match) /// internal T? Find(Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match != null); foreach (T item in this) { @@ -1039,7 +1041,7 @@ internal bool Exists(Predicate match) /// internal ImmutableList FindAll(Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match != null); if (this.IsEmpty) { @@ -1077,7 +1079,7 @@ internal ImmutableList FindAll(Predicate match) /// internal int FindIndex(Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match != null); return this.FindIndex(0, _count, match); } @@ -1096,8 +1098,8 @@ internal int FindIndex(Predicate match) /// internal int FindIndex(int startIndex, Predicate match) { - Requires.NotNull(match, nameof(match)); - Requires.Range(startIndex >= 0 && startIndex <= this.Count, nameof(startIndex)); + Debug.Assert(match != null); + Debug.Assert(startIndex >= 0 && startIndex <= this.Count); return this.FindIndex(startIndex, this.Count - startIndex, match); } @@ -1117,8 +1119,9 @@ internal int FindIndex(int startIndex, Predicate match) /// internal int FindIndex(int startIndex, int count, Predicate match) { - Requires.NotNull(match, nameof(match)); - Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); + Debug.Assert(match != null); + Debug.Assert(startIndex >= 0 && startIndex <= this.Count); + Debug.Assert(count >= 0 && (uint)(startIndex + count) <= (uint)this.Count); using (var enumerator = new Enumerator(this, startIndex: startIndex, count: count)) { @@ -1151,7 +1154,7 @@ internal int FindIndex(int startIndex, int count, Predicate match) /// internal T? FindLast(Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match != null); using (var enumerator = new Enumerator(this, reversed: true)) { @@ -1182,7 +1185,7 @@ internal int FindIndex(int startIndex, int count, Predicate match) /// internal int FindLastIndex(Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match != null); return this.IsEmpty ? -1 : this.FindLastIndex(this.Count - 1, this.Count, match); } @@ -1202,9 +1205,9 @@ internal int FindLastIndex(Predicate match) /// internal int FindLastIndex(int startIndex, Predicate match) { - Requires.NotNull(match, nameof(match)); - Requires.Range(startIndex >= 0, nameof(startIndex)); - Requires.Range(startIndex == 0 || startIndex < this.Count, nameof(startIndex)); + Debug.Assert(match != null); + Debug.Assert(startIndex >= 0); + Debug.Assert(startIndex == 0 || startIndex < this.Count); return this.IsEmpty ? -1 : this.FindLastIndex(startIndex, startIndex + 1, match); } @@ -1227,16 +1230,16 @@ internal int FindLastIndex(int startIndex, Predicate match) /// internal int FindLastIndex(int startIndex, int count, Predicate match) { - Requires.NotNull(match, nameof(match)); + Debug.Assert(match != null); if (startIndex == 0 && count == 0) { return -1; } - Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); - Requires.Range(count >= 0 && count <= this.Count, nameof(count)); - Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + Debug.Assert(startIndex >= 0 && startIndex < this.Count); + Debug.Assert(count >= 0 && count <= this.Count); + Debug.Assert(startIndex - count + 1 >= 0); using (var enumerator = new Enumerator(this, startIndex: startIndex, count: count, reversed: true)) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs index 2776e380a34111..b848c85329a067 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs @@ -175,7 +175,14 @@ public int BinarySearch(int index, int count, T item, IComparer? comparer) /// The 0-based index of the element in the set to return. /// The element at the given position. /// is negative or not less than . - public T this[int index] => _root.ItemRef(index); + public T this[int index] + { + get + { + Requires.Range(index >= 0 && index < this.Count, nameof(index)); + return _root.ItemRef(index); + } + } /// /// Gets a read-only reference to the element of the set at the given index. @@ -183,7 +190,11 @@ public int BinarySearch(int index, int count, T item, IComparer? comparer) /// The 0-based index of the element in the set to return. /// A read-only reference to the element at the given position. /// is negative or not less than . - public ref readonly T ItemRef(int index) => ref _root.ItemRef(index); + public ref readonly T ItemRef(int index) + { + Requires.Range(index >= 0 && index < this.Count, nameof(index)); + return ref _root.ItemRef(index); + } #endregion @@ -393,7 +404,11 @@ public ImmutableList RemoveAll(Predicate match) /// /// See the interface. /// - public ImmutableList SetItem(int index, T value) => this.Wrap(_root.ReplaceAt(index, value)); + public ImmutableList SetItem(int index, T value) + { + Requires.Range(index >= 0 && index < this.Count, nameof(index)); + return this.Wrap(_root.ReplaceAt(index, value)); + } /// /// See the interface. @@ -515,7 +530,12 @@ public void ForEach(Action action) /// copied from . The must have /// zero-based indexing. /// - public void CopyTo(T[] array) => _root.CopyTo(array); + public void CopyTo(T[] array) + { + Requires.NotNull(array, nameof(array)); + Requires.Range(array.Length >= this.Count, nameof(array)); + _root.CopyTo(array); + } /// /// Copies the entire to a compatible one-dimensional @@ -529,7 +549,13 @@ public void ForEach(Action action) /// /// The zero-based index in array at which copying begins. /// - public void CopyTo(T[] array, int arrayIndex) => _root.CopyTo(array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) + { + Requires.NotNull(array, nameof(array)); + Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); + Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + _root.CopyTo(array, arrayIndex); + } /// /// Copies a range of elements from the to @@ -547,7 +573,13 @@ public void ForEach(Action action) /// /// The zero-based index in array at which copying begins. /// The number of elements to copy. - public void CopyTo(int index, T[] array, int arrayIndex, int count) => _root.CopyTo(index, array, arrayIndex, count); + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + Requires.NotNull(array, nameof(array)); + Requires.ValidateRange(index, count, this.Count); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length, nameof(arrayIndex)); + _root.CopyTo(index, array, arrayIndex, count); + } /// /// Creates a shallow copy of a range of elements in the source . @@ -604,7 +636,11 @@ public ImmutableList ConvertAll(Func converter) /// that match the conditions defined by the specified predicate; otherwise, /// false. /// - public bool Exists(Predicate match) => _root.Exists(match); + public bool Exists(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.Exists(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -618,7 +654,11 @@ public ImmutableList ConvertAll(Func converter) /// The first element that matches the conditions defined by the specified predicate, /// if found; otherwise, the default value for type . /// - public T? Find(Predicate match) => _root.Find(match); + public T? Find(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.Find(match); + } /// /// Retrieves all the elements that match the conditions defined by the specified @@ -633,7 +673,11 @@ public ImmutableList ConvertAll(Func converter) /// the conditions defined by the specified predicate, if found; otherwise, an /// empty . /// - public ImmutableList FindAll(Predicate match) => _root.FindAll(match); + public ImmutableList FindAll(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.FindAll(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -648,7 +692,11 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the first occurrence of an element that matches the /// conditions defined by , if found; otherwise, -1. /// - public int FindIndex(Predicate match) => _root.FindIndex(match); + public int FindIndex(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.FindIndex(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -662,7 +710,12 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the first occurrence of an element that matches the /// conditions defined by , if found; otherwise, -1. /// - public int FindIndex(int startIndex, Predicate match) => _root.FindIndex(startIndex, match); + public int FindIndex(int startIndex, Predicate match) + { + Requires.NotNull(match, nameof(match)); + Requires.Range(startIndex >= 0 && startIndex <= this.Count, nameof(startIndex)); + return _root.FindIndex(startIndex, match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -677,7 +730,12 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the first occurrence of an element that matches the /// conditions defined by , if found; otherwise, -1. /// - public int FindIndex(int startIndex, int count, Predicate match) => _root.FindIndex(startIndex, count, match); + public int FindIndex(int startIndex, int count, Predicate match) + { + Requires.NotNull(match, nameof(match)); + Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); + return _root.FindIndex(startIndex, count, match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -691,7 +749,11 @@ public ImmutableList ConvertAll(Func converter) /// The last element that matches the conditions defined by the specified predicate, /// if found; otherwise, the default value for type . /// - public T? FindLast(Predicate match) => _root.FindLast(match); + public T? FindLast(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.FindLast(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -706,7 +768,11 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the last occurrence of an element that matches the /// conditions defined by , if found; otherwise, -1. /// - public int FindLastIndex(Predicate match) => _root.FindLastIndex(match); + public int FindLastIndex(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.FindLastIndex(match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -721,7 +787,13 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the last occurrence of an element that matches the /// conditions defined by , if found; otherwise, -1. /// - public int FindLastIndex(int startIndex, Predicate match) => _root.FindLastIndex(startIndex, match); + public int FindLastIndex(int startIndex, Predicate match) + { + Requires.NotNull(match, nameof(match)); + Requires.Range(startIndex >= 0, nameof(startIndex)); + Requires.Range(startIndex == 0 || startIndex < this.Count, nameof(startIndex)); + return _root.FindLastIndex(startIndex, match); + } /// /// Searches for an element that matches the conditions defined by the specified @@ -739,7 +811,19 @@ public ImmutableList ConvertAll(Func converter) /// The zero-based index of the last occurrence of an element that matches the /// conditions defined by , if found; otherwise, -1. /// - public int FindLastIndex(int startIndex, int count, Predicate match) => _root.FindLastIndex(startIndex, count, match); + public int FindLastIndex(int startIndex, int count, Predicate match) + { + Requires.NotNull(match, nameof(match)); + if (startIndex == 0 && count == 0) + { + return -1; + } + + Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); + Requires.Range(count >= 0 && count <= this.Count, nameof(count)); + Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + return _root.FindLastIndex(startIndex, count, match); + } /// /// Searches for the specified object and returns the zero-based index of the @@ -765,7 +849,11 @@ public ImmutableList ConvertAll(Func converter) /// elements in the that starts at and /// contains number of elements, if found; otherwise, -1. /// - public int IndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) => _root.IndexOf(item, index, count, equalityComparer); + public int IndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) + { + Requires.ValidateRange(index, count, this.Count); + return _root.IndexOf(item, index, count, equalityComparer); + } /// /// Searches for the specified object and returns the zero-based index of the @@ -787,7 +875,18 @@ public ImmutableList ConvertAll(Func converter) /// in the that contains number of elements /// and ends at , if found; otherwise, -1. /// - public int LastIndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) => _root.LastIndexOf(item, index, count, equalityComparer); + public int LastIndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) + { + if (index == 0 && count == 0) + { + return -1; + } + + Requires.Range(index >= 0 && index < this.Count, nameof(index)); + Requires.Range(count >= 0 && count <= this.Count, nameof(count)); + Requires.Range(index - count + 1 >= 0, nameof(count)); + return _root.LastIndexOf(item, index, count, equalityComparer); + } /// /// Determines whether every element in the @@ -802,7 +901,11 @@ public ImmutableList ConvertAll(Func converter) /// conditions defined by the specified predicate; otherwise, false. If the list /// has no elements, the return value is true. /// - public bool TrueForAll(Predicate match) => _root.TrueForAll(match); + public bool TrueForAll(Predicate match) + { + Requires.NotNull(match, nameof(match)); + return _root.TrueForAll(match); + } #endregion @@ -993,7 +1096,13 @@ T IList.this[int index] /// /// See the interface. /// - void System.Collections.ICollection.CopyTo(Array array, int arrayIndex) => _root.CopyTo(array, arrayIndex); + void System.Collections.ICollection.CopyTo(Array array, int arrayIndex) + { + Requires.NotNull(array, nameof(array)); + Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); + Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + _root.CopyTo(array, arrayIndex); + } #endregion From e91d47ad7a1590015c5fc8216582e89d1551bb83 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:09:31 +0000 Subject: [PATCH 08/16] Add blank lines after Requires blocks; update ValidateRange to use Debug.Assert + uint cast Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/3b4d1d4f-650f-475e-b3bf-7815be22bc4b Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableList_1.Builder.cs | 25 +++++++++++++++++++ .../Collections/Immutable/ImmutableList_1.cs | 23 ++++++++++++++++- .../src/Validation/Requires.cs | 3 ++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs index 38b0d762295b36..15990c5e52d406 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs @@ -127,11 +127,13 @@ public T this[int index] get { Requires.Range(index >= 0 && index < this.Count, nameof(index)); + return this.Root.ItemRef(index); } set { Requires.Range(index >= 0 && index < this.Count, nameof(index)); + this.Root = this.Root.ReplaceAt(index, value); } } @@ -144,6 +146,7 @@ public T this[int index] public ref readonly T ItemRef(int index) { Requires.Range(index >= 0 && index < this.Count, nameof(index)); + return ref this.Root.ItemRef(index); } @@ -165,6 +168,7 @@ public int IndexOf(T item) public void Insert(int index, T item) { Requires.Range(index >= 0 && index <= this.Count, nameof(index)); + this.Root = this.Root.Insert(index, item); } @@ -174,6 +178,7 @@ public void Insert(int index, T item) public void RemoveAt(int index) { Requires.Range(index >= 0 && index < this.Count, nameof(index)); + this.Root = this.Root.RemoveAt(index); } @@ -280,6 +285,7 @@ public void CopyTo(T[] array) { Requires.NotNull(array, nameof(array)); Requires.Range(array.Length >= this.Count, nameof(array)); + _root.CopyTo(array); } @@ -300,6 +306,7 @@ public void CopyTo(T[] array, int arrayIndex) Requires.NotNull(array, nameof(array)); Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + _root.CopyTo(array, arrayIndex); } @@ -324,6 +331,7 @@ public void CopyTo(int index, T[] array, int arrayIndex, int count) Requires.NotNull(array, nameof(array)); Requires.ValidateRange(index, count, this.Count); Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length, nameof(arrayIndex)); + _root.CopyTo(index, array, arrayIndex, count); } @@ -385,6 +393,7 @@ public ImmutableList ConvertAll(Func converter) public bool Exists(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.Exists(match); } @@ -403,6 +412,7 @@ public bool Exists(Predicate match) public T? Find(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.Find(match); } @@ -422,6 +432,7 @@ public bool Exists(Predicate match) public ImmutableList FindAll(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.FindAll(match); } @@ -441,6 +452,7 @@ public ImmutableList FindAll(Predicate match) public int FindIndex(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.FindIndex(match); } @@ -460,6 +472,7 @@ public int FindIndex(int startIndex, Predicate match) { Requires.NotNull(match, nameof(match)); Requires.Range(startIndex >= 0 && startIndex <= this.Count, nameof(startIndex)); + return _root.FindIndex(startIndex, match); } @@ -480,6 +493,7 @@ public int FindIndex(int startIndex, int count, Predicate match) { Requires.NotNull(match, nameof(match)); Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); + return _root.FindIndex(startIndex, count, match); } @@ -498,6 +512,7 @@ public int FindIndex(int startIndex, int count, Predicate match) public T? FindLast(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.FindLast(match); } @@ -517,6 +532,7 @@ public int FindIndex(int startIndex, int count, Predicate match) public int FindLastIndex(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.FindLastIndex(match); } @@ -538,6 +554,7 @@ public int FindLastIndex(int startIndex, Predicate match) Requires.NotNull(match, nameof(match)); Requires.Range(startIndex >= 0, nameof(startIndex)); Requires.Range(startIndex == 0 || startIndex < this.Count, nameof(startIndex)); + return _root.FindLastIndex(startIndex, match); } @@ -560,6 +577,7 @@ public int FindLastIndex(int startIndex, Predicate match) public int FindLastIndex(int startIndex, int count, Predicate match) { Requires.NotNull(match, nameof(match)); + if (startIndex == 0 && count == 0) { return -1; @@ -568,6 +586,7 @@ public int FindLastIndex(int startIndex, int count, Predicate match) Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); Requires.Range(count >= 0 && count <= this.Count, nameof(count)); Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + return _root.FindLastIndex(startIndex, count, match); } @@ -592,6 +611,7 @@ public int FindLastIndex(int startIndex, int count, Predicate match) public int IndexOf(T item, int index) { Requires.ValidateRange(index, this.Count - index, this.Count); + return _root.IndexOf(item, index, this.Count - index, EqualityComparer.Default); } @@ -647,6 +667,7 @@ public int IndexOf(T item, int index, int count) => public int IndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) { Requires.ValidateRange(index, count, this.Count); + return _root.IndexOf(item, index, count, equalityComparer); } @@ -699,6 +720,7 @@ public int LastIndexOf(T item, int startIndex) } Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); + return _root.LastIndexOf(item, startIndex, startIndex + 1, EqualityComparer.Default); } @@ -750,6 +772,7 @@ public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); Requires.Range(count >= 0 && count <= this.Count, nameof(count)); Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + return _root.LastIndexOf(item, startIndex, count, equalityComparer); } @@ -769,6 +792,7 @@ public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? public bool TrueForAll(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.TrueForAll(match); } @@ -1236,6 +1260,7 @@ void ICollection.CopyTo(Array array, int arrayIndex) Requires.NotNull(array, nameof(array)); Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + this.Root.CopyTo(array, arrayIndex); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs index b848c85329a067..ebf5555792edb4 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs @@ -180,6 +180,7 @@ public T this[int index] get { Requires.Range(index >= 0 && index < this.Count, nameof(index)); + return _root.ItemRef(index); } } @@ -193,6 +194,7 @@ public T this[int index] public ref readonly T ItemRef(int index) { Requires.Range(index >= 0 && index < this.Count, nameof(index)); + return ref _root.ItemRef(index); } @@ -407,6 +409,7 @@ public ImmutableList RemoveAll(Predicate match) public ImmutableList SetItem(int index, T value) { Requires.Range(index >= 0 && index < this.Count, nameof(index)); + return this.Wrap(_root.ReplaceAt(index, value)); } @@ -534,6 +537,7 @@ public void CopyTo(T[] array) { Requires.NotNull(array, nameof(array)); Requires.Range(array.Length >= this.Count, nameof(array)); + _root.CopyTo(array); } @@ -554,6 +558,7 @@ public void CopyTo(T[] array, int arrayIndex) Requires.NotNull(array, nameof(array)); Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + _root.CopyTo(array, arrayIndex); } @@ -578,6 +583,7 @@ public void CopyTo(int index, T[] array, int arrayIndex, int count) Requires.NotNull(array, nameof(array)); Requires.ValidateRange(index, count, this.Count); Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length, nameof(arrayIndex)); + _root.CopyTo(index, array, arrayIndex, count); } @@ -639,6 +645,7 @@ public ImmutableList ConvertAll(Func converter) public bool Exists(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.Exists(match); } @@ -657,6 +664,7 @@ public bool Exists(Predicate match) public T? Find(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.Find(match); } @@ -676,6 +684,7 @@ public bool Exists(Predicate match) public ImmutableList FindAll(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.FindAll(match); } @@ -695,6 +704,7 @@ public ImmutableList FindAll(Predicate match) public int FindIndex(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.FindIndex(match); } @@ -714,6 +724,7 @@ public int FindIndex(int startIndex, Predicate match) { Requires.NotNull(match, nameof(match)); Requires.Range(startIndex >= 0 && startIndex <= this.Count, nameof(startIndex)); + return _root.FindIndex(startIndex, match); } @@ -734,6 +745,7 @@ public int FindIndex(int startIndex, int count, Predicate match) { Requires.NotNull(match, nameof(match)); Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); + return _root.FindIndex(startIndex, count, match); } @@ -752,6 +764,7 @@ public int FindIndex(int startIndex, int count, Predicate match) public T? FindLast(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.FindLast(match); } @@ -771,6 +784,7 @@ public int FindIndex(int startIndex, int count, Predicate match) public int FindLastIndex(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.FindLastIndex(match); } @@ -792,6 +806,7 @@ public int FindLastIndex(int startIndex, Predicate match) Requires.NotNull(match, nameof(match)); Requires.Range(startIndex >= 0, nameof(startIndex)); Requires.Range(startIndex == 0 || startIndex < this.Count, nameof(startIndex)); + return _root.FindLastIndex(startIndex, match); } @@ -814,6 +829,7 @@ public int FindLastIndex(int startIndex, Predicate match) public int FindLastIndex(int startIndex, int count, Predicate match) { Requires.NotNull(match, nameof(match)); + if (startIndex == 0 && count == 0) { return -1; @@ -822,6 +838,7 @@ public int FindLastIndex(int startIndex, int count, Predicate match) Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); Requires.Range(count >= 0 && count <= this.Count, nameof(count)); Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + return _root.FindLastIndex(startIndex, count, match); } @@ -852,6 +869,7 @@ public int FindLastIndex(int startIndex, int count, Predicate match) public int IndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) { Requires.ValidateRange(index, count, this.Count); + return _root.IndexOf(item, index, count, equalityComparer); } @@ -882,9 +900,10 @@ public int LastIndexOf(T item, int index, int count, IEqualityComparer? equal return -1; } - Requires.Range(index >= 0 && index < this.Count, nameof(index)); + Requires.Range(index >= 0 && index < this.Count, "startIndex"); Requires.Range(count >= 0 && count <= this.Count, nameof(count)); Requires.Range(index - count + 1 >= 0, nameof(count)); + return _root.LastIndexOf(item, index, count, equalityComparer); } @@ -904,6 +923,7 @@ public int LastIndexOf(T item, int index, int count, IEqualityComparer? equal public bool TrueForAll(Predicate match) { Requires.NotNull(match, nameof(match)); + return _root.TrueForAll(match); } @@ -1101,6 +1121,7 @@ void System.Collections.ICollection.CopyTo(Array array, int arrayIndex) Requires.NotNull(array, nameof(array)); Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + _root.CopyTo(array, arrayIndex); } diff --git a/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs b/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs index fdf710288a7055..ba898e78867f2d 100644 --- a/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs +++ b/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs @@ -122,7 +122,8 @@ public static void FailRange(string? parameterName, string? message = null) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ValidateRange(int index, int count, int listCount, string indexParameterName = "index") { - Range(index >= 0 && index <= listCount, indexParameterName); + Debug.Assert(listCount >= 0); + Range((uint)index <= (uint)listCount, indexParameterName); Range(count >= 0 && (uint)(index + count) <= (uint)listCount, nameof(count)); } From 0df455437544b094ed8fca6d2fa56cb0dd76d371 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 01:17:50 +0000 Subject: [PATCH 09/16] Drop 'startIndex == 0 && count == 0' fast paths; add Requires.ValidateReverseRange helper Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/781e992a-9b7f-4694-b96b-ad274867bc99 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableList_1.Builder.cs | 19 ++--------------- .../Immutable/ImmutableList_1.Node.cs | 17 ++++----------- .../Collections/Immutable/ImmutableList_1.cs | 19 ++--------------- .../src/Validation/Requires.cs | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+), 47 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs index 15990c5e52d406..9ea13c4e6b60c2 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs @@ -577,15 +577,7 @@ public int FindLastIndex(int startIndex, Predicate match) public int FindLastIndex(int startIndex, int count, Predicate match) { Requires.NotNull(match, nameof(match)); - - if (startIndex == 0 && count == 0) - { - return -1; - } - - Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); - Requires.Range(count >= 0 && count <= this.Count, nameof(count)); - Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + Requires.ValidateReverseRange(startIndex, count, this.Count, nameof(startIndex)); return _root.FindLastIndex(startIndex, count, match); } @@ -764,14 +756,7 @@ public int LastIndexOf(T item, int startIndex, int count) => /// public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? equalityComparer) { - if (startIndex == 0 && count == 0) - { - return -1; - } - - Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); - Requires.Range(count >= 0 && count <= this.Count, nameof(count)); - Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + Requires.ValidateReverseRange(startIndex, count, this.Count, nameof(startIndex)); return _root.LastIndexOf(item, startIndex, count, equalityComparer); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs index 4607b452dedeee..6ce574e756aa82 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs @@ -797,12 +797,8 @@ internal int IndexOf(T item, int index, int count, IEqualityComparer? equalit /// internal int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? equalityComparer) { - if (startIndex == 0 && count == 0) - { - return -1; - } - - Debug.Assert(startIndex >= 0 && startIndex < this.Count); + Debug.Assert(startIndex >= 0); + Debug.Assert(startIndex == 0 || startIndex < this.Count); Debug.Assert(count >= 0 && count <= this.Count); Debug.Assert(startIndex - count + 1 >= 0); @@ -1231,13 +1227,8 @@ internal int FindLastIndex(int startIndex, Predicate match) internal int FindLastIndex(int startIndex, int count, Predicate match) { Debug.Assert(match != null); - - if (startIndex == 0 && count == 0) - { - return -1; - } - - Debug.Assert(startIndex >= 0 && startIndex < this.Count); + Debug.Assert(startIndex >= 0); + Debug.Assert(startIndex == 0 || startIndex < this.Count); Debug.Assert(count >= 0 && count <= this.Count); Debug.Assert(startIndex - count + 1 >= 0); diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs index ebf5555792edb4..f92bf896cec0e2 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs @@ -829,15 +829,7 @@ public int FindLastIndex(int startIndex, Predicate match) public int FindLastIndex(int startIndex, int count, Predicate match) { Requires.NotNull(match, nameof(match)); - - if (startIndex == 0 && count == 0) - { - return -1; - } - - Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); - Requires.Range(count >= 0 && count <= this.Count, nameof(count)); - Requires.Range(startIndex - count + 1 >= 0, nameof(count)); + Requires.ValidateReverseRange(startIndex, count, this.Count, nameof(startIndex)); return _root.FindLastIndex(startIndex, count, match); } @@ -895,14 +887,7 @@ public int IndexOf(T item, int index, int count, IEqualityComparer? equalityC /// public int LastIndexOf(T item, int index, int count, IEqualityComparer? equalityComparer) { - if (index == 0 && count == 0) - { - return -1; - } - - Requires.Range(index >= 0 && index < this.Count, "startIndex"); - Requires.Range(count >= 0 && count <= this.Count, nameof(count)); - Requires.Range(index - count + 1 >= 0, nameof(count)); + Requires.ValidateReverseRange(index, count, this.Count); return _root.LastIndexOf(item, index, count, equalityComparer); } diff --git a/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs b/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs index ba898e78867f2d..7ceb820cd34638 100644 --- a/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs +++ b/src/libraries/System.Collections.Immutable/src/Validation/Requires.cs @@ -127,6 +127,27 @@ public static void ValidateRange(int index, int count, int listCount, string ind Range(count >= 0 && (uint)(index + count) <= (uint)listCount, nameof(count)); } + /// + /// Validates that and represent a valid + /// reverse (backward) range within a collection of size . + /// + /// The zero-based starting index of the backward search. + /// The number of elements in the range. + /// The total number of elements in the collection. + /// The name of the startIndex parameter for exception messages. + /// + /// is negative or not a valid index (unless both it and are zero), + /// or is negative, exceeds , or the backward range extends before index 0. + /// + [DebuggerStepThrough] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateReverseRange(int startIndex, int count, int listCount, string startIndexParameterName = "startIndex") + { + Debug.Assert(listCount >= 0); + Range(startIndex == 0 || (uint)startIndex < (uint)listCount, startIndexParameterName); + Range((uint)count <= (uint)listCount && startIndex - count + 1 >= 0, nameof(count)); + } + /// /// Throws an if a condition does not evaluate to true. /// From 426c717df6cdfa95f545c4f12b36f4cbdcd71b53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:46:10 +0000 Subject: [PATCH 10/16] Replace separate arrayIndex checks with overflow-safe combined form; use single Range check for FindLastIndex(startIndex, match); remove Builder.LastIndexOf fast path Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c527b2ab-9167-426d-9181-4b2065835708 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableList_1.Builder.cs | 14 +++----------- .../Collections/Immutable/ImmutableList_1.Node.cs | 6 ++---- .../Collections/Immutable/ImmutableList_1.cs | 9 +++------ 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs index 9ea13c4e6b60c2..f03edd10745589 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs @@ -304,8 +304,7 @@ public void CopyTo(T[] array) public void CopyTo(T[] array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); _root.CopyTo(array, arrayIndex); } @@ -552,8 +551,7 @@ public int FindLastIndex(Predicate match) public int FindLastIndex(int startIndex, Predicate match) { Requires.NotNull(match, nameof(match)); - Requires.Range(startIndex >= 0, nameof(startIndex)); - Requires.Range(startIndex == 0 || startIndex < this.Count, nameof(startIndex)); + Requires.Range(startIndex == 0 || (uint)startIndex < (uint)this.Count, nameof(startIndex)); return _root.FindLastIndex(startIndex, match); } @@ -706,11 +704,6 @@ public int LastIndexOf(T item) /// public int LastIndexOf(T item, int startIndex) { - if (this.Count == 0 && startIndex == 0) - { - return -1; - } - Requires.Range(startIndex >= 0 && startIndex < this.Count, nameof(startIndex)); return _root.LastIndexOf(item, startIndex, startIndex + 1, EqualityComparer.Default); @@ -1243,8 +1236,7 @@ void IList.Remove(object? value) void ICollection.CopyTo(Array array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); this.Root.CopyTo(array, arrayIndex); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs index 6ce574e756aa82..6da2ee0f98e9db 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Node.cs @@ -855,8 +855,7 @@ internal void CopyTo(T[] array) internal void CopyTo(T[] array, int arrayIndex) { Debug.Assert(array != null); - Debug.Assert(arrayIndex >= 0); - Debug.Assert(array.Length >= arrayIndex + this.Count); + Debug.Assert(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length); foreach (T element in this) { @@ -904,8 +903,7 @@ internal void CopyTo(int index, T[] array, int arrayIndex, int count) internal void CopyTo(Array array, int arrayIndex) { Debug.Assert(array != null); - Debug.Assert(arrayIndex >= 0); - Debug.Assert(array.Length >= arrayIndex + this.Count); + Debug.Assert(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length); foreach (T element in this) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs index f92bf896cec0e2..778e774f5494bb 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.cs @@ -556,8 +556,7 @@ public void CopyTo(T[] array) public void CopyTo(T[] array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); _root.CopyTo(array, arrayIndex); } @@ -804,8 +803,7 @@ public int FindLastIndex(Predicate match) public int FindLastIndex(int startIndex, Predicate match) { Requires.NotNull(match, nameof(match)); - Requires.Range(startIndex >= 0, nameof(startIndex)); - Requires.Range(startIndex == 0 || startIndex < this.Count, nameof(startIndex)); + Requires.Range(startIndex == 0 || (uint)startIndex < (uint)this.Count, nameof(startIndex)); return _root.FindLastIndex(startIndex, match); } @@ -1104,8 +1102,7 @@ T IList.this[int index] void System.Collections.ICollection.CopyTo(Array array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); _root.CopyTo(array, arrayIndex); } From 5092452faf3ee0f693f34bbe6ad50212406aebe5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:11:42 +0000 Subject: [PATCH 11/16] Remove extension method LastIndexOf fast path; add overflow and empty-list validation tests Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c527b2ab-9167-426d-9181-4b2065835708 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Collections/Immutable/ImmutableList.cs | 5 ---- .../tests/ImmutableListBuilderTest.cs | 27 +++++++++++++++++++ .../tests/ImmutableListTest.cs | 27 +++++++++++++++++++ .../tests/IndexOfTests.cs | 1 - 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList.cs index 43c0b23bc954cf..52b5feb60980fb 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList.cs @@ -298,11 +298,6 @@ public static int LastIndexOf(this IImmutableList list, T item, int startI { Requires.NotNull(list, nameof(list)); - if (list.Count == 0 && startIndex == 0) - { - return -1; - } - return list.LastIndexOf(item, startIndex, startIndex + 1, EqualityComparer.Default); } diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs index 9d90dbeebb8376..3e3a943ec78f5b 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs @@ -349,6 +349,33 @@ public void LastIndexOf() (b, v, i, c, eq) => b.LastIndexOf(v, i, c, eq)); } + [Fact] + public void CopyToOverflowValidation() + { + ImmutableList.Builder builder = ImmutableList.Create(1, 2, 3).ToBuilder(); + var array = new int[5]; + + // Overflow scenarios for CopyTo(T[], arrayIndex) + AssertExtensions.Throws("arrayIndex", () => builder.CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => builder.CopyTo(array, -1)); + + // Overflow scenarios for CopyTo(int, T[], arrayIndex, count) + AssertExtensions.Throws("arrayIndex", () => builder.CopyTo(0, array, int.MaxValue, 3)); + + // Overflow scenarios for ICollection.CopyTo + AssertExtensions.Throws("arrayIndex", () => ((ICollection)builder).CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection)builder).CopyTo(array, -1)); + } + + [Fact] + public void LastIndexOfWithStartIndexThrowsOnEmptyBuilder() + { + ImmutableList.Builder builder = ImmutableList.Empty.ToBuilder(); + + // LastIndexOf(item, startIndex) should throw on empty builder, matching List behavior. + AssertExtensions.Throws("startIndex", () => builder.LastIndexOf(5, 0)); + } + [Fact] public void GetEnumeratorExplicit() { diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableListTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableListTest.cs index 2e7cc5d03c14ae..0878dadbdb1b69 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableListTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableListTest.cs @@ -566,6 +566,15 @@ public void LastIndexOfConsistentWithImmutableArray() } } + [Fact] + public void LastIndexOfWithStartIndexThrowsOnEmptyList() + { + ImmutableList list = ImmutableList.Empty; + + // LastIndexOf(item, startIndex, count, eq) with count=1 on empty list throws on count. + AssertExtensions.Throws("count", () => list.LastIndexOf(5, 0, 1, equalityComparer: null)); + } + [Fact] public void ReplaceTest() { @@ -940,6 +949,24 @@ public void CopyToRangeValidation() AssertExtensions.Throws("arrayIndex", () => list.CopyTo(0, array, 4, 2)); } + [Fact] + public void CopyToOverflowValidation() + { + ImmutableList list = ImmutableList.Create(1, 2, 3); + var array = new int[5]; + + // Overflow scenarios for CopyTo(T[], arrayIndex) + AssertExtensions.Throws("arrayIndex", () => list.CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => list.CopyTo(array, -1)); + + // Overflow scenarios for CopyTo(int, T[], arrayIndex, count) + AssertExtensions.Throws("arrayIndex", () => list.CopyTo(0, array, int.MaxValue, 3)); + + // Overflow scenarios for ICollection.CopyTo + AssertExtensions.Throws("arrayIndex", () => ((ICollection)list).CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection)list).CopyTo(array, -1)); + } + [Fact] public void BinarySearchRangeValidation() { diff --git a/src/libraries/System.Collections.Immutable/tests/IndexOfTests.cs b/src/libraries/System.Collections.Immutable/tests/IndexOfTests.cs index 6fe6fff1cc273d..7873d95b1e204f 100644 --- a/src/libraries/System.Collections.Immutable/tests/IndexOfTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/IndexOfTests.cs @@ -132,7 +132,6 @@ public static void LastIndexOfTest( Assert.Equal(-1, lastIndexOfItem(emptyCollection, 5)); Assert.Equal(-1, lastIndexOfItemEQ(emptyCollection, 5, EqualityComparer.Default)); - Assert.Equal(-1, lastIndexOfItemIndex(emptyCollection, 5, 0)); Assert.Equal(-1, lastIndexOfItemIndexCount(emptyCollection, 5, 0, 0)); Assert.Equal(0, lastIndexOfItem(singleCollection, 10)); From 33730cc61fa1e09f9242e4eec1ea73e597682da3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:02:46 +0000 Subject: [PATCH 12/16] Replace two-line CopyTo range checks with single overflow-safe check and simplify IndexOf validation Replace separate Requires.Range(arrayIndex >= 0) + Requires.Range(array.Length >= arrayIndex + count) with combined Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + count) <= (uint)array.Length) in 13 CopyTo locations across immutable collection types. Also replace wasteful Requires.ValidateRange(index, this.Count - index, this.Count) in ImmutableList.Builder.IndexOf(T, int) with direct Requires.Range((uint)index <= (uint)this.Count). Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6843c2b4-a169-4ebc-a8f8-eea958164182 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Collections/Immutable/ImmutableDictionary_2.Builder.cs | 3 +-- .../System/Collections/Immutable/ImmutableDictionary_2.cs | 6 ++---- .../Collections/Immutable/ImmutableHashSet_1.Builder.cs | 3 +-- .../src/System/Collections/Immutable/ImmutableHashSet_1.cs | 6 ++---- .../System/Collections/Immutable/ImmutableList_1.Builder.cs | 2 +- .../Immutable/ImmutableSortedDictionary_2.Node.cs | 6 ++---- .../Collections/Immutable/ImmutableSortedDictionary_2.cs | 3 +-- .../Collections/Immutable/ImmutableSortedSet_1.Node.cs | 6 ++---- .../Collections/Immutable/KeysOrValuesCollectionAccessor.cs | 6 ++---- 9 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableDictionary_2.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableDictionary_2.Builder.cs index a78409ee75c6a8..0521316838b91f 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableDictionary_2.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableDictionary_2.Builder.cs @@ -329,8 +329,7 @@ void IDictionary.Remove(object key) void ICollection.CopyTo(Array array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (KeyValuePair item in this) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableDictionary_2.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableDictionary_2.cs index db90561410c6f5..447c5a81768a38 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableDictionary_2.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableDictionary_2.cs @@ -616,8 +616,7 @@ bool ICollection>.Remove(KeyValuePair i void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (KeyValuePair item in this) { @@ -756,8 +755,7 @@ void IDictionary.Clear() void ICollection.CopyTo(Array array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (KeyValuePair item in this) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.Builder.cs index 85c7cc72e20e02..c9d98aeab1ce12 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.Builder.cs @@ -393,8 +393,7 @@ void ICollection.Add(T item) void ICollection.CopyTo(T[] array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (T item in this) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.cs index c6b6f3140e4cc2..3669f74f13b0ec 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.cs @@ -492,8 +492,7 @@ bool ICollection.IsReadOnly void ICollection.CopyTo(T[] array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (T item in this) { @@ -537,8 +536,7 @@ bool ICollection.Remove(T item) void ICollection.CopyTo(Array array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (T item in this) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs index f03edd10745589..aedb36eabbad0c 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableList_1.Builder.cs @@ -600,7 +600,7 @@ public int FindLastIndex(int startIndex, int count, Predicate match) /// public int IndexOf(T item, int index) { - Requires.ValidateRange(index, this.Count - index, this.Count); + Requires.Range((uint)index <= (uint)this.Count, nameof(index)); return _root.IndexOf(item, index, this.Count - index, EqualityComparer.Default); } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedDictionary_2.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedDictionary_2.Node.cs index 280682da09e557..aca3489048efa1 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedDictionary_2.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedDictionary_2.Node.cs @@ -199,8 +199,7 @@ internal Enumerator GetEnumerator(Builder builder) internal void CopyTo(KeyValuePair[] array, int arrayIndex, int dictionarySize) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + dictionarySize, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + dictionarySize) <= (uint)array.Length, nameof(arrayIndex)); foreach (KeyValuePair item in this) { @@ -214,8 +213,7 @@ internal void CopyTo(KeyValuePair[] array, int arrayIndex, int dic internal void CopyTo(Array array, int arrayIndex, int dictionarySize) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + dictionarySize, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + dictionarySize) <= (uint)array.Length, nameof(arrayIndex)); foreach (KeyValuePair item in this) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedDictionary_2.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedDictionary_2.cs index ab7d775fd5c37c..23c2ac56263015 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedDictionary_2.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedDictionary_2.cs @@ -521,8 +521,7 @@ bool ICollection>.Remove(KeyValuePair i void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (KeyValuePair item in this) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedSet_1.Node.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedSet_1.Node.cs index f81689196614e5..ac4a76790beccd 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedSet_1.Node.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedSet_1.Node.cs @@ -306,8 +306,7 @@ internal Enumerator GetEnumerator(Builder builder) internal void CopyTo(T[] array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (T item in this) { array[arrayIndex++] = item; @@ -320,8 +319,7 @@ internal void CopyTo(T[] array, int arrayIndex) internal void CopyTo(Array array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (T item in this) { diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/KeysOrValuesCollectionAccessor.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/KeysOrValuesCollectionAccessor.cs index 5dc7d8205e83af..fb4c4fc6e1842d 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/KeysOrValuesCollectionAccessor.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/KeysOrValuesCollectionAccessor.cs @@ -90,8 +90,7 @@ public void Clear() public void CopyTo(T[] array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (T item in this) { @@ -131,8 +130,7 @@ IEnumerator IEnumerable.GetEnumerator() void ICollection.CopyTo(Array array, int arrayIndex) { Requires.NotNull(array, nameof(array)); - Requires.Range(arrayIndex >= 0, nameof(arrayIndex)); - Requires.Range(array.Length >= arrayIndex + this.Count, nameof(arrayIndex)); + Requires.Range(arrayIndex >= 0 && (uint)(arrayIndex + this.Count) <= (uint)array.Length, nameof(arrayIndex)); foreach (T item in this) { From 4d21fe0adc1ed767ab6e0c5d9733a9831078d07c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:13:36 +0000 Subject: [PATCH 13/16] Add CopyTo and IndexOf overflow validation tests for immutable collections Add tests verifying that CopyTo with int.MaxValue arrayIndex correctly throws ArgumentOutOfRangeException for: - ImmutableDictionary/SortedDictionary (base test class) - ImmutableDictionary/SortedDictionary Builder (base test class) - ImmutableHashSet/SortedSet (base test class) - ImmutableHashSet Builder - ImmutableSortedSet Builder - ImmutableSortedDictionary Keys/Values (KeysOrValuesCollectionAccessor) - ImmutableList Builder IndexOf(T, int) with overflow index Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6843c2b4-a169-4ebc-a8f8-eea958164182 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../ImmutableDictionaryBuilderTestBase.cs | 11 ++++++++ .../tests/ImmutableDictionaryTestBase.cs | 13 ++++++++++ .../tests/ImmutableHashSetBuilderTest.cs | 10 ++++++++ .../tests/ImmutableListBuilderTest.cs | 9 +++++++ .../tests/ImmutableSetTest.cs | 13 ++++++++++ .../tests/ImmutableSortedDictionaryTest.cs | 25 +++++++++++++++++++ .../tests/ImmutableSortedSetBuilderTest.cs | 16 ++++++++++++ 7 files changed, 97 insertions(+) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableDictionaryBuilderTestBase.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableDictionaryBuilderTestBase.cs index ce47d4caab77b7..a48aab93a9cbbd 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableDictionaryBuilderTestBase.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableDictionaryBuilderTestBase.cs @@ -106,6 +106,17 @@ public void CopyTo() AssertExtensions.Throws("array", () => builder.CopyTo(null, 0)); } + [Fact] + public void CopyToOverflowValidation() + { + IImmutableDictionary map = this.GetEmptyImmutableDictionary().Add("a", 1); + IDictionary builder = this.GetBuilder(map); + + var collection = (ICollection)builder; + AssertExtensions.Throws("arrayIndex", () => collection.CopyTo(new object[5], int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => collection.CopyTo(new object[5], -1)); + } + [Fact] public void IsReadOnly() { diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableDictionaryTestBase.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableDictionaryTestBase.cs index b4aafc1995604c..52e7739b638488 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableDictionaryTestBase.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableDictionaryTestBase.cs @@ -140,6 +140,19 @@ public void ICollectionMembers() Assert.Equal(new DictionaryEntry("a", 1), (DictionaryEntry)array[1]); } + [Fact] + public void CopyToOverflowValidation() + { + var dictionary = Empty().Add("a", 1); + var array = new KeyValuePair[5]; + + AssertExtensions.Throws("arrayIndex", () => ((ICollection>)dictionary).CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection>)dictionary).CopyTo(array, -1)); + + AssertExtensions.Throws("arrayIndex", () => ((ICollection)dictionary).CopyTo(new object[5], int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection)dictionary).CopyTo(new object[5], -1)); + } + [Fact] public void IDictionaryOfKVMembers() { diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableHashSetBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableHashSetBuilderTest.cs index f738552c8b8f4e..be6108366160f3 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableHashSetBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableHashSetBuilderTest.cs @@ -279,6 +279,16 @@ public void ICollectionOfTMethods() CollectionAssertAreEquivalent(new[] { "a", "b" }, builder.ToArray()); // tests enumerator } + [Fact] + public void CopyToOverflowValidation() + { + ICollection builder = ImmutableHashSet.Create("a").ToBuilder(); + var array = new string[5]; + + AssertExtensions.Throws("arrayIndex", () => builder.CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => builder.CopyTo(array, -1)); + } + [Fact] public void NullHandling() { diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs index 3e3a943ec78f5b..fb388193d6d1a9 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableListBuilderTest.cs @@ -367,6 +367,15 @@ public void CopyToOverflowValidation() AssertExtensions.Throws("arrayIndex", () => ((ICollection)builder).CopyTo(array, -1)); } + [Fact] + public void IndexOfWithIndexOverflowValidation() + { + ImmutableList.Builder builder = ImmutableList.Create(1, 2, 3).ToBuilder(); + + AssertExtensions.Throws("index", () => builder.IndexOf(1, int.MaxValue)); + AssertExtensions.Throws("index", () => builder.IndexOf(1, -1)); + } + [Fact] public void LastIndexOfWithStartIndexThrowsOnEmptyBuilder() { diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableSetTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableSetTest.cs index d3add493299621..6d12111c9297d0 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableSetTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableSetTest.cs @@ -192,6 +192,19 @@ public void ICollectionMethods() Assert.Same(builder.SyncRoot, builder.SyncRoot); } + [Fact] + public void CopyToOverflowValidation() + { + IImmutableSet set = this.Empty().Add("a"); + var array = new string[5]; + + AssertExtensions.Throws("arrayIndex", () => ((ICollection)set).CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection)set).CopyTo(array, -1)); + + AssertExtensions.Throws("arrayIndex", () => ((ICollection)set).CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection)set).CopyTo(array, -1)); + } + [Fact] public void NullHandling() { diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableSortedDictionaryTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableSortedDictionaryTest.cs index 47725fdb2ac991..c4e71498be4012 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableSortedDictionaryTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableSortedDictionaryTest.cs @@ -467,5 +467,30 @@ private static IImmutableDictionary Empty(IComparer< { return ImmutableSortedDictionary.Empty.WithComparers(keyComparer, valueComparer); } + + [Fact] + public void KeysAndValuesCopyToOverflowValidation() + { + ImmutableSortedDictionary dictionary = ImmutableSortedDictionary.Empty.Add("a", 1); + + // Access Keys/Values through the IDictionary interface to get KeysCollectionAccessor/ValuesCollectionAccessor + IDictionary idict = dictionary; + + ICollection keys = idict.Keys; + var keysArray = new string[5]; + AssertExtensions.Throws("arrayIndex", () => keys.CopyTo(keysArray, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => keys.CopyTo(keysArray, -1)); + + AssertExtensions.Throws("arrayIndex", () => ((ICollection)keys).CopyTo(keysArray, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection)keys).CopyTo(keysArray, -1)); + + ICollection values = idict.Values; + var valuesArray = new int[5]; + AssertExtensions.Throws("arrayIndex", () => values.CopyTo(valuesArray, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => values.CopyTo(valuesArray, -1)); + + AssertExtensions.Throws("arrayIndex", () => ((ICollection)values).CopyTo(valuesArray, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection)values).CopyTo(valuesArray, -1)); + } } } diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableSortedSetBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableSortedSetBuilderTest.cs index e4d7464cea2124..c170b3ccdd4e9b 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableSortedSetBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableSortedSetBuilderTest.cs @@ -346,6 +346,22 @@ public void ICollectionMethods() Assert.Same(builder.SyncRoot, builder.SyncRoot); } + [Fact] + public void CopyToOverflowValidation() + { + ImmutableSortedSet.Builder builder = ImmutableSortedSet.Create(1).ToBuilder(); + var array = new int[5]; + + ((ICollection)builder).CopyTo(array, 0); + + AssertExtensions.Throws("arrayIndex", () => ((ICollection)builder).CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => ((ICollection)builder).CopyTo(array, -1)); + + ICollection collection = builder; + AssertExtensions.Throws("arrayIndex", () => collection.CopyTo(array, int.MaxValue)); + AssertExtensions.Throws("arrayIndex", () => collection.CopyTo(array, -1)); + } + [Fact] public void Indexer() { From 383214fad436334c620dddfbfc762554e7623448 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 13:16:57 +0000 Subject: [PATCH 14/16] Remove unnecessary success-path CopyTo call from SortedSet builder test Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6843c2b4-a169-4ebc-a8f8-eea958164182 Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../tests/ImmutableSortedSetBuilderTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableSortedSetBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableSortedSetBuilderTest.cs index c170b3ccdd4e9b..dbb00a77921a44 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableSortedSetBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableSortedSetBuilderTest.cs @@ -352,8 +352,6 @@ public void CopyToOverflowValidation() ImmutableSortedSet.Builder builder = ImmutableSortedSet.Create(1).ToBuilder(); var array = new int[5]; - ((ICollection)builder).CopyTo(array, 0); - AssertExtensions.Throws("arrayIndex", () => ((ICollection)builder).CopyTo(array, int.MaxValue)); AssertExtensions.Throws("arrayIndex", () => ((ICollection)builder).CopyTo(array, -1)); From 1ddc14e293e5bb850f478af9929ee671ae2ae5ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:06:28 +0000 Subject: [PATCH 15/16] Optimize intermediate IndexOf/LastIndexOf overloads to validate index directly In ImmutableArray and ImmutableArray.Builder, intermediate overloads that compute count as Length/Count - startIndex were delegating to the full overload which called ValidateRange on both index and count. Since count derived from Length - index is guaranteed valid when index is valid, the count validation was wasteful. Extract private IndexOfCore/LastIndexOfCore helpers and have intermediate overloads validate just the index before calling them directly, matching the approach already used in ImmutableList.Builder.IndexOf(T, int). Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33415a3f-837b-4dd6-b39c-a9859f8736fe Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableArray_1.Builder.cs | 11 +++++-- .../Collections/Immutable/ImmutableArray_1.cs | 30 ++++++++++++++----- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index ea320e00a37bfc..c7c4c92d809bed 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -765,7 +765,8 @@ public int IndexOf(T item) /// The 0-based index into the array where the item was found; or -1 if it could not be found. public int IndexOf(T item, int startIndex) { - return this.IndexOf(item, startIndex, this.Count - startIndex, EqualityComparer.Default); + Requires.Range((uint)startIndex <= (uint)this.Count, nameof(startIndex)); + return IndexOfCore(item, startIndex, this.Count - startIndex, EqualityComparer.Default); } /// @@ -800,6 +801,11 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); + return IndexOfCore(item, startIndex, count, equalityComparer); + } + + private int IndexOfCore(T item, int startIndex, int count, IEqualityComparer? equalityComparer) + { equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) { @@ -831,7 +837,8 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa /// The 0-based index into the array where the item was found; or -1 if it could not be found. public int IndexOf(T item, int startIndex, IEqualityComparer? equalityComparer) { - return this.IndexOf(item, startIndex, this.Count - startIndex, equalityComparer); + Requires.Range((uint)startIndex <= (uint)this.Count, nameof(startIndex)); + return IndexOfCore(item, startIndex, this.Count - startIndex, equalityComparer); } /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 9bdf320978a7c4..5f931eec8d0ae8 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -125,7 +125,9 @@ public int IndexOf(T item) public int IndexOf(T item, int startIndex, IEqualityComparer? equalityComparer) { ImmutableArray self = this; - return self.IndexOf(item, startIndex, self.Length - startIndex, equalityComparer); + self.ThrowNullRefIfNotInitialized(); + Requires.Range((uint)startIndex <= (uint)self.Length, nameof(startIndex)); + return self.IndexOfCore(item, startIndex, self.Length - startIndex, equalityComparer); } /// @@ -137,7 +139,9 @@ public int IndexOf(T item, int startIndex, IEqualityComparer? equalityCompare public int IndexOf(T item, int startIndex) { ImmutableArray self = this; - return self.IndexOf(item, startIndex, self.Length - startIndex, EqualityComparer.Default); + self.ThrowNullRefIfNotInitialized(); + Requires.Range((uint)startIndex <= (uint)self.Length, nameof(startIndex)); + return self.IndexOfCore(item, startIndex, self.Length - startIndex, EqualityComparer.Default); } /// @@ -175,16 +179,21 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa Requires.ValidateRange(startIndex, count, self.Length, nameof(startIndex)); + return self.IndexOfCore(item, startIndex, count, equalityComparer); + } + + private int IndexOfCore(T item, int startIndex, int count, IEqualityComparer? equalityComparer) + { equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) { - return Array.IndexOf(self.array, item, startIndex, count); + return Array.IndexOf(array!, item, startIndex, count); } else { for (int i = startIndex; i < startIndex + count; i++) { - if (equalityComparer.Equals(self.array[i], item)) + if (equalityComparer.Equals(array![i], item)) { return i; } @@ -219,12 +228,14 @@ public int LastIndexOf(T item) public int LastIndexOf(T item, int startIndex) { ImmutableArray self = this; + self.ThrowNullRefIfNotInitialized(); if (self.IsEmpty && startIndex == 0) { return -1; } - return self.LastIndexOf(item, startIndex, startIndex + 1, EqualityComparer.Default); + Requires.Range((uint)startIndex < (uint)self.Length, nameof(startIndex)); + return self.LastIndexOfCore(item, startIndex, startIndex + 1, EqualityComparer.Default); } /// @@ -260,16 +271,21 @@ public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? Requires.Range(startIndex >= 0 && startIndex < self.Length, nameof(startIndex)); Requires.Range(count >= 0 && startIndex - count + 1 >= 0, nameof(count)); + return self.LastIndexOfCore(item, startIndex, count, equalityComparer); + } + + private int LastIndexOfCore(T item, int startIndex, int count, IEqualityComparer? equalityComparer) + { equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) { - return Array.LastIndexOf(self.array, item, startIndex, count); + return Array.LastIndexOf(array!, item, startIndex, count); } else { for (int i = startIndex; i >= startIndex - count + 1; i--) { - if (equalityComparer.Equals(item, self.array[i])) + if (equalityComparer.Equals(item, array![i])) { return i; } From 2717ba75b957c800aad804d9459cf898c6860fe0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:31:55 +0000 Subject: [PATCH 16/16] Revert "Optimize intermediate IndexOf/LastIndexOf overloads to validate index directly" This reverts commit 1ddc14e293e5bb850f478af9929ee671ae2ae5ab. Co-authored-by: MihaZupan <25307628+MihaZupan@users.noreply.github.com> --- .../Immutable/ImmutableArray_1.Builder.cs | 11 ++----- .../Collections/Immutable/ImmutableArray_1.cs | 30 +++++-------------- 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index c7c4c92d809bed..ea320e00a37bfc 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -765,8 +765,7 @@ public int IndexOf(T item) /// The 0-based index into the array where the item was found; or -1 if it could not be found. public int IndexOf(T item, int startIndex) { - Requires.Range((uint)startIndex <= (uint)this.Count, nameof(startIndex)); - return IndexOfCore(item, startIndex, this.Count - startIndex, EqualityComparer.Default); + return this.IndexOf(item, startIndex, this.Count - startIndex, EqualityComparer.Default); } /// @@ -801,11 +800,6 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa Requires.ValidateRange(startIndex, count, this.Count, nameof(startIndex)); - return IndexOfCore(item, startIndex, count, equalityComparer); - } - - private int IndexOfCore(T item, int startIndex, int count, IEqualityComparer? equalityComparer) - { equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) { @@ -837,8 +831,7 @@ private int IndexOfCore(T item, int startIndex, int count, IEqualityComparer? /// The 0-based index into the array where the item was found; or -1 if it could not be found. public int IndexOf(T item, int startIndex, IEqualityComparer? equalityComparer) { - Requires.Range((uint)startIndex <= (uint)this.Count, nameof(startIndex)); - return IndexOfCore(item, startIndex, this.Count - startIndex, equalityComparer); + return this.IndexOf(item, startIndex, this.Count - startIndex, equalityComparer); } /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 5f931eec8d0ae8..9bdf320978a7c4 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -125,9 +125,7 @@ public int IndexOf(T item) public int IndexOf(T item, int startIndex, IEqualityComparer? equalityComparer) { ImmutableArray self = this; - self.ThrowNullRefIfNotInitialized(); - Requires.Range((uint)startIndex <= (uint)self.Length, nameof(startIndex)); - return self.IndexOfCore(item, startIndex, self.Length - startIndex, equalityComparer); + return self.IndexOf(item, startIndex, self.Length - startIndex, equalityComparer); } /// @@ -139,9 +137,7 @@ public int IndexOf(T item, int startIndex, IEqualityComparer? equalityCompare public int IndexOf(T item, int startIndex) { ImmutableArray self = this; - self.ThrowNullRefIfNotInitialized(); - Requires.Range((uint)startIndex <= (uint)self.Length, nameof(startIndex)); - return self.IndexOfCore(item, startIndex, self.Length - startIndex, EqualityComparer.Default); + return self.IndexOf(item, startIndex, self.Length - startIndex, EqualityComparer.Default); } /// @@ -179,21 +175,16 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa Requires.ValidateRange(startIndex, count, self.Length, nameof(startIndex)); - return self.IndexOfCore(item, startIndex, count, equalityComparer); - } - - private int IndexOfCore(T item, int startIndex, int count, IEqualityComparer? equalityComparer) - { equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) { - return Array.IndexOf(array!, item, startIndex, count); + return Array.IndexOf(self.array, item, startIndex, count); } else { for (int i = startIndex; i < startIndex + count; i++) { - if (equalityComparer.Equals(array![i], item)) + if (equalityComparer.Equals(self.array[i], item)) { return i; } @@ -228,14 +219,12 @@ public int LastIndexOf(T item) public int LastIndexOf(T item, int startIndex) { ImmutableArray self = this; - self.ThrowNullRefIfNotInitialized(); if (self.IsEmpty && startIndex == 0) { return -1; } - Requires.Range((uint)startIndex < (uint)self.Length, nameof(startIndex)); - return self.LastIndexOfCore(item, startIndex, startIndex + 1, EqualityComparer.Default); + return self.LastIndexOf(item, startIndex, startIndex + 1, EqualityComparer.Default); } /// @@ -271,21 +260,16 @@ public int LastIndexOf(T item, int startIndex, int count, IEqualityComparer? Requires.Range(startIndex >= 0 && startIndex < self.Length, nameof(startIndex)); Requires.Range(count >= 0 && startIndex - count + 1 >= 0, nameof(count)); - return self.LastIndexOfCore(item, startIndex, count, equalityComparer); - } - - private int LastIndexOfCore(T item, int startIndex, int count, IEqualityComparer? equalityComparer) - { equalityComparer ??= EqualityComparer.Default; if (equalityComparer == EqualityComparer.Default) { - return Array.LastIndexOf(array!, item, startIndex, count); + return Array.LastIndexOf(self.array, item, startIndex, count); } else { for (int i = startIndex; i >= startIndex - count + 1; i--) { - if (equalityComparer.Equals(item, array![i])) + if (equalityComparer.Equals(item, self.array[i])) { return i; }