Repository navigation
Research: borrowed-view invalidation on collection mutation (CollectionsMarshal pilot) #400
Description
Classification
- Type: research / narrowly scoped analyzer experiment
- Priority: P2 / backlog; not a request to implement a broad new feature immediately
- Trigger: ideas from Flux 1.0's split between iterable/collection and stable positions
- Goal: establish a distinct, reproducible bug class before changing OwnCore/OwnIR
Problem
Own.NET already models an owner being released while a borrowed Span<T>/Memory<T> remains in use (P-007). Another class of bugs is subtler: the collection is still alive, but a mutation can invalidate a view/reference into its previous internal storage. There may be no Dispose, Return, exception, or GC lifetime error; a write may silently target a detached array.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
var list = new List<int>(capacity: 1) { 10 };
Span<int> view = CollectionsMarshal.AsSpan(list);
list.Add(20); // reallocates storage in this controlled example
view[0] = 99; // writes to the OLD array
Console.WriteLine(list[0]); // still 10Official .NET contracts expressly prohibit adding/removing elements while such a view/reference is in use:
Do not equate every Add with actual reallocation. Even with spare capacity, structural mutations violate the documented contract; the analyzer should report potential invalidation, not claim a detached backing array in every case.
Proposed first slice (research pilot)
- Recognize by Roslyn symbol identity, not matching method-name text,
CollectionsMarshal.AsSpan<T>(List<T>)as producing a view associated with one specificList<T>instance. - Treat calls such as
Add,Insert,Remove,RemoveAt,Clearon that instance as structural-mutation effects (and considerCapacitychanges if evidence supports it). - Find reads/writes/escapes of the same borrowed view after such an effect, within a single method and with narrow alias assumptions.
- Produce a conservative possible-invalidated-view finding with an evidence chain: view acquisition → mutation → subsequent use. Do not imply memory unsafety/use-after-free: the old array may remain GC-live.
- Test whether the existing flow/loan infrastructure can represent this without introducing a misleading
release(owner)fact. If a newinvalidate_viewsoperation or storage-generation relation is needed, record a separate spec/OwnIR design proposal; changes to must-understand flow ops require an OwnIR version bump.
Scope control: Start only with List<T>/AsSpan. The corresponding Dictionary<TKey,TValue> ref-return APIs (GetValueRefOrNullRef / GetValueRefOrAddDefault) are a candidate second slice, not an excuse to model all mutable collections now.
Required bad/ok corpus
| Case | Expected |
|---|---|
AsSpan(list) → list.Add(x) → span[0] |
possible invalidation (bad) |
AsSpan(list) → list.RemoveAt(i) → use view |
possible invalidation (bad) |
AsSpan(list) → use view → list.Add(x) → no further use |
clean |
AsSpan(list) → span[0] = x (no structural mutation) |
clean |
AsSpan(a) → mutate unrelated b → use view of a |
clean |
AsSpan(list) → conditional mutation → use view |
possible on one path, not definite |
| Reassign view to a fresh one after mutation → use fresh view | clean when provenance is proven |
Keep a forced-reallocation runtime fixture for the first bad case to demonstrate the observable stale-view behavior, not just a syntactic match. Don't misdiagnose harmless normal List<T> usage.
Relationship to existing work
- P-007 ArrayPool / Span:
Return/Disposeinvalidates a view by ending a resource lifetime. This proposal instead models mutation invalidating a borrowed view while its container remains alive. - OwnCore: loans are presently block-scoped; consider a last-use-sensitive lifetime only if the pilot requires it. Avoid launching a full non-lexical-lifetimes project.
- OwnIR v2: keep semantic verdict logic in the core and preserve existing versioning/parity guarantees.
- Related pooled cross-member view escape: P-007 POOL004: object-level (cross-member) pooled-view-in-a-field escape #205, but not the same bug.
Decision gate (before implementation commitment)
- Reproduce and pin the bad/ok corpus; determine if a small, honest, flow-sensitive model can attain useful precision.
- Search real OSS C# projects for actual post-mutation uses of
CollectionsMarshalviews. Measure frequency and verify at least one concrete defect or compelling real-world pattern. - Compare relevant existing .NET/Roslyn analyzers to avoid duplicating a mature rule.
- Proceed only if the evidence shows adequate precision and practical value; otherwise document the result and keep this as research, not a new core feature.
Non-goals
No universal alias/heap analysis, full generational memory model, LINQ/deferred-enumeration checker, optimization of CollectionsMarshal, or copying Flux's library API. Deferred IEnumerable capturing disposed resources is an adjacent separate research idea.
Activity