Repository navigation
Fix ImmutableArray builder self-insertion corruption - #136064
eiriktsarpalis with Copilot wants to merge 2 commits into
Conversation
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
|
Tagging subscribers to this area: @dotnet/area-system-collections |
| return true; | ||
| } | ||
|
|
||
| if (sequence is ImmutableArray<T>.Builder builder) |
There was a problem hiding this comment.
Why is this the general setup instead of checking sequence is ICollection<T> collection and calling collection.CopyTo(array, arrayIndex)? Presumably most if not all of these other cases are already optimizing their CopyTo to use Array/Span.CopyTo where possible and that ensures it works with overlapping src/dst already.
-- Separately, this is notably also consistently one of those cases where we find ourselves optimizing based on the ability to get a limited scoped ROSpan<T> from an arbitrary IEnumerable<T>. There's some nuance with it, like it "detaching" from the original collection if you modify the collection (not memory unsafe, just potential for a logic bug). But it is so commonly needed in our and user code for this type of thing that it's probably worth exposing anyways
There was a problem hiding this comment.
It's a good question. @copilot please investigate simplifying this to one ICollection type check.
There was a problem hiding this comment.
Investigated: yes, this can use a single ICollection<T> check on modern .NET. The concrete-type restriction was intentional—the remarks explain that a custom CopyTo could retain the immutable backing array—but #88181 explicitly proposes accepting that tradeoff.
There is one compatibility exception: on .NET Framework, non-exact array types need the enumeration fallback (e.g. uint[] passed as IEnumerable<int>, covered by #14794).
Open PR #125304 already implements exactly this simplification, including the #if !NET array guard, caller tests, and benchmark results. It also fixes this self-insertion issue, so I recommend landing that change there rather than duplicating it here, and retaining this PR’s self-insertion regression coverage. No files changed or tests run during this investigation.
There was a problem hiding this comment.
@tannergooding what do you think? Given this change is already scoped to that other PR I would be inclined to simply fix the builder issue without refactoring the containing scaffolding.
There was a problem hiding this comment.
Is it realistically a concern? Code can also do things like ImmutableCollectionsMarshal.AsArray, so is it worth pessimizing things and adding increasingly more custom checks as compared to the typical and optimal path? Do we really need to beholden ourselves to limitations of the type system as written 21 years ago and if so, should we consider some ICopyable API that supports a CopyTo(Span<T> array) so it cannot be captured?
I agree we should fix the general bug and look at discussing the other separately, but I think it's something we can/should settle on because this is a repeat problem and comes with repeated cost addition for every new if (T is ...) path we have to tack on.
There was a problem hiding this comment.
the remarks explain that a custom CopyTo could retain the immutable backing array
This is not a legitimate concern.
Open PR #125304 already implements exactly this simplification
Just merged
ImmutableArray<T>.Builder.InsertRangecorrupts values when inserting itself at a middle index:[1, 2, 3]inserted at index 1 yields[1, 1, 1, 1, 2, 3]instead of[1, 1, 2, 3, 2, 3].TryCopyTo’s trusted paths, using its overlap-safeCopyTowithout exposing backing arrays to arbitrary collection implementations.List<T>across empty, single-element, and three-element builders, every insertion index, and capacity growth/no growth.