Skip to content

feat(pool): POOL005 — full-length view of a pooled buffer over-reads its logical length → OWN025#71

Merged
PhysShell merged 3 commits into
mainfrom
claude/pool005-full-view
Jun 21, 2026
Merged

feat(pool): POOL005 — full-length view of a pooled buffer over-reads its logical length → OWN025#71
PhysShell merged 3 commits into
mainfrom
claude/pool005-full-view

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 21, 2026

Copy link
Copy Markdown
Owner

What

A pooled array is oversized: ArrayPool<T>.Shared.Rent(n) returns an array of Length >= n, not exactly n. A full-length view of it — buf.AsSpan() / buf.AsMemory() / new Span<T>(buf) with no length bound — reaches past the requested length n into the stale [n, Length) tail a previous renter left behind. Reading or copying through that view processes the stale data: a correctness bug (wrong length) and a potential information disclosure. The fix is a bounded view, buf.AsSpan(0, n).

This is P-007's POOL005 ("clear/copy past the logical length"), the last item on that profile's list. New diagnostic OWN025 in the buffer-policy family.

byte[] buf = ArrayPool<byte>.Shared.Rent(n);
Fill(buf, n);                 // valid payload is buf[0..n]
Emit(buf.AsSpan());           // ❌ OWN025: the WHOLE oversized array — n bytes + the stale tail
ArrayPool<byte>.Shared.Return(buf);
// fix: Emit(buf.AsSpan(0, n));

How — one core op, both front-ends converge on it

Unlike POOL002/004 (flow-sensitive use/escape), POOL005 is a property of the view-creation site — it fires regardless of the owner's flow state, beside the leak/use/release checks, and changes no state. Both the .own checker and the C# extractor lower to a single new core op, so there is one source of truth:

  • .own DSL — a new overspan b statement (lexer → parser → AST → a CFG Overspan instruction); analysis raises OWN025. codegen emits the unbounded AsSpan for it (exhaustiveness).
  • ExtractorFullViewOwner recognises an unbounded AsSpan/AsMemory/new Span over a Rented local by the resolved System.MemoryExtensions / System.Span<T> BCL symbols (like ViewOwner, so a look-alike isn't mistaken for it); EmitFlowExpr walks the whole expression so a view nested in a call (Emit(buf.AsSpan())) or chain (buf.AsSpan().ToArray()) is found. A bounded view (buf.AsSpan(0, n)) is not matched.
  • Bridge (ownir) — the overspan fact lowers to the same Overspan op; the finding is reported at the view line (not the Rent site), tagged [resource: pooled buffer].

OWN025 only fires for pooled buffers: among tracked locals only a Rented array is what the BCL AsSpan/Span symbols bind to, so it never fires on a tracked IDisposable / factory result. Distinct from OWN024 (a sensitive buffer cleared too little) — this is reading too much.

Corpus & tests

  • Corpus arraypool-fullspan-overreadbefore.cs over-reads via Emit(buf.AsSpan()); after.cs bounds it to buf.AsSpan(0, n). case.own reduces it (overspan buf → OWN025). Benchmark recall floor 13 → 14.
  • run_tests CASES — trigger, leak-coexistence (OWN025 + OWN001), in-branch, not-owned (OWN034), undefined (OWN030), bounded-view-clean.
  • test_spec Buffer-B9, test_ownir overspan → OWN025 at the view line tagged pooled buffer, gallery example 11.
  • Spec — Diagnostics OWN025, BufferPolicies B9, P-007 status.

Full local suite green (analysis 132/132, corpus 12/12, gallery 12/12, spec 23/23, ownir 117/117, codegen 43/43). The extractor (Program.cs) is CI-validated.

Scope (first slice)

Catches the unbounded view used in an expression. A view stored in a local first (var s = buf.AsSpan(); Use(s);) and the Array.Clear(buf, 0, buf.Length) spelling are follow-ups noted in P-007.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added overspan support and detection for unbounded full-length views from pooled buffers that can over-read stale tail bytes.
    • Introduced diagnostic OWN025 for this unsafe pattern.
  • Documentation
    • Updated the buffer policy spec with rule B9 requiring bounded views.
    • Expanded proposal/notes for POOL005 and added a new illustrative example.
  • Tests
    • Added regression cases for overspan, gallery validation for OWN025, and conformance coverage for Buffer-B9.
  • Chores
    • Tightened a CI corpus benchmark recall threshold.

…its logical length → OWN025

A pooled array is oversized (ArrayPool.Rent(n) returns Length >= n), so a full-length
view of it — buf.AsSpan() / buf.AsMemory() / new Span<T>(buf) with NO length bound —
reaches past the requested length n into the stale [n, Length) tail a previous renter
left. Reading or copying through it processes that stale data: a correctness bug and a
potential information disclosure (P-007 POOL005). The fix is a bounded view,
buf.AsSpan(0, n).

New diagnostic OWN025, raised at the view site. The .own checker and the C# extractor
converge on ONE core flow op, so there is a single source of truth:

  - .own DSL: a new `overspan b` statement (lexer/parser/AST) lowered to a CFG Overspan
    instruction; analysis raises OWN025. It is a property of the view-creation site —
    it fires regardless of the owner's flow state, beside the leak/use/release checks,
    and changes no state. codegen emits the unbounded AsSpan for it (exhaustiveness).
  - extractor: FullViewOwner recognises an unbounded AsSpan/AsMemory/new Span over a
    Rent'd local (by the resolved System.MemoryExtensions / System.Span<T> BCL symbols,
    like ViewOwner) and EmitFlowExpr emits an `overspan` fact, walking the whole
    expression so a view nested in a call (Emit(buf.AsSpan())) or chain
    (buf.AsSpan().ToArray()) is found; a bounded view (buf.AsSpan(0, n)) is not matched.
  - bridge (ownir): the `overspan` fact lowers to the same Overspan op; the finding is
    reported at the VIEW line (not the Rent site), tagged [resource: pooled buffer].

Corpus: arraypool-fullspan-overread (before.cs over-reads via Emit(buf.AsSpan());
after.cs bounds it to buf.AsSpan(0, n)). Benchmark recall floor 13 -> 14.

Tests: run_tests CASES (trigger / leak-coexist / branch / not-owned / undefined),
test_spec Buffer-B9, test_ownir overspan->OWN025 bridge check, gallery example 11.
Spec: Diagnostics OWN025, BufferPolicies B9, P-007 status.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9de041d3-bc46-441a-b2d6-91cc5edbfd96

📥 Commits

Reviewing files that changed from the base of the PR and between 57bc1ba and 05ad3e3.

📒 Files selected for processing (6)
  • corpus/real-world/arraypool-fullspan-overread/after.cs
  • corpus/real-world/arraypool-fullspan-overread/before.cs
  • corpus/real-world/arraypool-fullspan-overread/notes.md
  • docs/proposals/P-007-arraypool-span.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • scripts/metamorphic.py
✅ Files skipped from review due to trivial changes (2)
  • corpus/real-world/arraypool-fullspan-overread/after.cs
  • docs/proposals/P-007-arraypool-span.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • corpus/real-world/arraypool-fullspan-overread/before.cs
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

📝 Walkthrough

Walkthrough

This PR adds end-to-end detection of the POOL005 pattern (OWN025): an ArrayPool<T> buffer used with an unbounded full-length span/memory view that can read stale tail bytes past the logical length. The overspan keyword and statement are added to OwnLang (lexer → parser → AST → CFG → analyzer → codegen → OwnIR), the Roslyn extractor emits overspan facts for matching C# expressions, spec rules and diagnostics are documented, a real-world corpus case with before/after examples is added, regression tests are extended, and the CI recall gate is raised from 13 to 14.

Changes

POOL005/OWN025 overspan detection pipeline

Layer / File(s) Summary
OwnLang language: overspan keyword, parser, AST
ownlang/lexer.py, ownlang/parser.py, ownlang/ast_nodes.py
Adds Tok.OVERSPAN to the lexer and KEYWORDS map, adds the overspan grammar production and parse_overspan dispatch in the parser, and introduces the Overspan frozen dataclass AST node with the updated Stmt union.
CFG instruction, analyzer step, diagnostics, codegen, OwnIR bridge
ownlang/cfg.py, ownlang/analysis.py, ownlang/diagnostics.py, ownlang/codegen.py, ownlang/ownir.py
Defines the Overspan CFG instruction, extends _Builder.lower_stmt to emit OWN034 for non-owned targets, registers OWN025 in the diagnostics title map, adds the analyzer step that reports OWN025, lowers to <var>.AsSpan() in codegen, and wires the overspan op through the OwnIR bridge with kind="pooled buffer" for OWN025 findings.
Roslyn extractor: FullViewOwner and overspan fact emission
frontend/roslyn/OwnSharp.Extractor/Program.cs
Adds FullViewOwner(ExpressionSyntax, SemanticModel) to identify zero-arg AsSpan/AsMemory calls and single-arg Span/Memory constructors over tracked pooled buffers; extends flow-lowering to emit overspan facts in both local initializer and general expression contexts.
Spec rules, diagnostics table, and proposal update
spec/BufferPolicies.md, spec/Diagnostics.md, docs/proposals/P-007-arraypool-span.md
Inserts normative rule B9 prescribing bounded views and citing OWN025, adds OWN025 to the Diagnostics.md table, and updates the ArrayPool/Span proposal with POOL005 full-view description and revised Scope section wording.
Real-world corpus case with before/after examples and notes
corpus/real-world/arraypool-fullspan-overread/before.cs, corpus/real-world/arraypool-fullspan-overread/after.cs, corpus/real-world/arraypool-fullspan-overread/case.own, corpus/real-world/arraypool-fullspan-overread/expected-diagnostics.txt, corpus/real-world/arraypool-fullspan-overread/notes.md
Adds a complete ArrayPool full-length view scenario: before.cs demonstrates the problematic unbounded span usage, after.cs shows the corrected bounded span pattern, case.own models the scenario in OwnLang expecting OWN025, and notes.md documents POOL005/OWN025 semantics and the fix.
Gallery example and regression tests
examples/gallery/11_overspan_full_view.own, tests/run_tests.py, tests/test_gallery.py, tests/test_ownir.py, tests/test_spec.py
Adds the 11_overspan_full_view.own gallery example, extends run_tests.py with overspan diagnostic cases, adds test_gallery manifest entry, test_ownir POOL005 assertion, and test_spec Buffer-B9 conformance case.
Metamorphic transform support
scripts/metamorphic.py
Updates metamorphic.py to recognize Overspan statements in variable substitution, identifier collection, and statement adjacency reordering, ensuring overspan is handled consistently with other simple statements for metamorphic testing.
CI benchmark recall gate bump
.github/workflows/ci.yml
Raises the --min-recall threshold from 13 to 14 in the corpus-benchmark CI job to reflect the new corpus case coverage.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over RoslynExtractor,OwnIRBridge: C# source → OwnIR facts
    participant RoslynExtractor as OwnSharp.Extractor
    participant OwnIRBridge as ownir.py (_lower_flow)
    RoslynExtractor->>RoslynExtractor: FullViewOwner detects buf.AsSpan()
    RoslynExtractor->>OwnIRBridge: emit overspan fact (owner, line)
  end
  rect rgba(144, 238, 144, 0.5)
    Note over OwnIRBridge,Analyzer: OwnLang pipeline
    participant Analyzer as analysis.py
    participant Findings as OWN025 Finding
    OwnIRBridge->>OwnIRBridge: op=="overspan" → A.Overspan node
    OwnIRBridge->>OwnIRBridge: _Builder.lower_stmt → CFG Overspan instr
    OwnIRBridge->>Analyzer: transfer Overspan(sym, line)
    Analyzer->>Findings: report OWN025, kind="pooled buffer"
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • PhysShell/Own.NET#6: Introduced the tests/test_gallery.py harness that this PR extends with the 11_overspan_full_view.own fixture and OWN025 expected code.
  • PhysShell/Own.NET#15: Both PRs extend the --flow-locals OwnIR/CFG ingestion and ownlang/ownir.py bridging for flow-local diagnostics; this PR adds overspan/OWN025 handling on top of the flow-local framework.
  • PhysShell/Own.NET#50: Introduced the corpus-benchmark CI job with the --min-recall gate that this PR bumps from 13 to 14.

Poem

🐇 A span without bounds — what a dangerous view,
Stale bytes in the tail, a bug hiding in queue.
I added overspan, the checker now sees,
OWN025 fires whenever you please.
Bounded your slice with AsSpan(0, n) tight,
The rabbit hops on — the pool's flowing right! 🌊

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: implementing POOL005 detection (unbounded views over pooled buffers) and the corresponding OWN025 diagnostic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/pool005-full-view

Comment @coderabbitai help to get the list of available commands and usage tips.

…(<=100)

The 11_overspan_full_view.own entry's analog description ran to 116 chars; trim it
to keep the lint gate (ruff + mypy --strict) green. No behaviour change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e1a68f18c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +807 to +811
foreach (var node in expr.DescendantNodesAndSelf().OfType<ExpressionSyntax>())
if (FullViewOwner(node, model) is { } owner && tracked.Contains(owner))
overspanned.Add(owner);
foreach (var o in overspanned)
nodes.Add(new { op = "overspan", var = o, line = LineOf(expr) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan full-span views in local initializers

When the over-copy is written as a local declaration initializer, e.g. var copy = buf.AsSpan().ToArray();, LowerFlowStmt takes the LocalDeclarationStatementSyntax branch and never calls EmitFlowExpr on non-acquire initializers, so this new descendant scan is never reached. That leaves a common POOL005 over-read silent even though the implementation comment calls out chains such as .ToArray(); please also scan declaration initializers before returning from that branch.

Useful? React with 👍 / 👎.

…l views (POOL005)

Two follow-ups after PR #71's first CI run:

1. Metamorphic selftest (the failing job): the alpha-rename / reorder transforms in
   scripts/metamorphic.py did not know the new `overspan` statement, so renaming `buf`
   left `overspan buf` referencing the vanished name (OWN025 -> OWN030, non-invariant).
   Teach _sub_stmt / _all_names / _touches that `overspan` reads its var, exactly like
   `use`/`release`. Selftest back to 8/8.

2. Codex review (Program.cs): a full view written as a local-declaration initializer
   (`var copy = buf.AsSpan().ToArray();`) was missed — LowerFlowStmt's
   LocalDeclarationStatementSyntax branch never calls EmitFlowExpr on a non-acquire
   initializer. Refactor the overspan scan into a shared EmitOverspans helper and call
   it from the local-decl branch too, so the over-copy initializer AND a view-local
   declaration (`Span<T> s = buf.AsSpan();`) are both caught. The corpus before.cs now
   exercises both spellings; after.cs bounds both.

Spec / notes / P-007 updated for expression + initializer coverage (only the
`Array.Clear(buf, 0, buf.Length)` spelling remains a follow-up). The corpus benchmark
already validated recall 14 (before.cs -> OWN025) on the prior run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
@PhysShell PhysShell merged commit f206dbf into main Jun 21, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants