Skip to content

Research: borrowed-view invalidation on collection mutation (CollectionsMarshal pilot) #400

Open

Description

@PhysShell

Classification

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 10

Official .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)

  1. Recognize by Roslyn symbol identity, not matching method-name text, CollectionsMarshal.AsSpan<T>(List<T>) as producing a view associated with one specific List<T> instance.
  2. Treat calls such as Add, Insert, Remove, RemoveAt, Clear on that instance as structural-mutation effects (and consider Capacity changes if evidence supports it).
  3. Find reads/writes/escapes of the same borrowed view after such an effect, within a single method and with narrow alias assumptions.
  4. 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.
  5. Test whether the existing flow/loan infrastructure can represent this without introducing a misleading release(owner) fact. If a new invalidate_views operation 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/Dispose invalidates 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)

  1. Reproduce and pin the bad/ok corpus; determine if a small, honest, flow-sensitive model can attain useful precision.
  2. Search real OSS C# projects for actual post-mutation uses of CollectionsMarshal views. Measure frequency and verify at least one concrete defect or compelling real-world pattern.
  3. Compare relevant existing .NET/Roslyn analyzers to avoid duplicating a mature rule.
  4. 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions