Skip to content

feat(pool): MemoryPool using-owned view escape — using owner; return owner.Memory → OWN002 (POOL004)#74

Merged
PhysShell merged 2 commits into
mainfrom
claude/memorypool-using-escape
Jun 22, 2026
Merged

feat(pool): MemoryPool using-owned view escape — using owner; return owner.Memory → OWN002 (POOL004)#74
PhysShell merged 2 commits into
mainfrom
claude/memorypool-using-escape

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 22, 2026

Copy link
Copy Markdown
Owner

What

The follow-up Codex flagged on #73. The idiomatic MemoryPool dangle:

static Memory<byte> Borrow(int n)
{
    using IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(n);
    return owner.Memory;   // ❌ OWN002: `using` disposes owner as we return →
                           //    caller gets a view of buffer already back in the pool
}
// fix: return the IMemoryOwner itself (no `using`) — transfer ownership to the caller

using owner; return owner.Memory; is exactly try { return owner.Memory; } finally { owner.Dispose(); } — the MemoryPool twin of the ArrayPool try/finally Memory escape (#70). It was silent because the flow-locals candidate pass skips using locals (auto-disposed → not leak candidates), so the owner never entered tracked.

How

A new LowerFlowStatements desugars a tracked using IMemoryOwner = MemoryPool.Rent(...) declaration into acquire; try { rest } finally { release }: the implicit scope-exit dispose is threaded onto the rest's returns/throws (reusing #70's onReturn + ReturnedViewOwners machinery), so the returned view's caller-use lands after the release → OWN002.

  • Routed from LowerFlowBody, the BlockSyntax case, and the try-body.
  • With no such using declaration, LowerFlowStatements lowers each statement exactly as the old loop — so every existing shape is unchanged (verified: feat(pool): MemoryPool view borrow — owner.Memory[.Span] used after Dispose → OWN002 (POOL002) #73's using owner; Consume(owner.Memory.Span) after.cs stays silent — acquire→use→release, balanced).
  • The candidate pass now tracks using MemoryPool owners (gated to IsMemoryPoolRent); a return owner directly still escapes/untracks as before.

Extractor + corpus only — reuses OWN002 and the borrow lattice.

Validation

  • Corpus: memorypool-using-view-escape (before → OWN002; after, ownership-transfer → silent) — the MemoryPool twin of arraypool-memory-view-escape.
  • Corpus 16/16, suite EXIT 0, metamorphic 8/8 (40 files), ruff + mypy clean.
  • Benchmark recall floor 17 → 18. The flow-lowering change is CI-validated.
  • P-007 status updated.

Scope

Completes the MemoryPool escape story (use-after-explicit-Dispose from #73 + the using-owned return-view dangle here). The remaining P-007 backlog item is a POOL005 view stored in a FIELD.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added/expanded detection for pooled buffer ownership bugs where memory views escape after a pooled owner is disposed.
  • Bug Fixes
    • Improved analysis coverage for MemoryPool using declaration and using (...) { } statement patterns, including more consistent flow handling.
  • Documentation
    • Updated proposal/notes to clarify the MemoryPool ownership escape patterns and recommended ownership-transfer fix.
  • Tests
    • Added real-world corpus examples and expected diagnostic coverage for OWN002.
  • Chores
    • Tightened the corpus benchmark regression gate in CI (higher minimum recall).

… owner.Memory` → OWN002 (POOL004)

The follow-up Codex flagged on #73. `using IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(n);
return owner.Memory;` is sugar for `try { return owner.Memory; } finally { owner.Dispose(); }` — the
implicit scope-exit dispose hands the caller a Memory backed by a buffer already returned to the pool:
a dangling borrow. Previously silent because the flow-locals candidate pass skips `using` locals
(auto-disposed, non-leak), so the owner never entered `tracked`.

Fix (extractor only, reuses #70's onReturn / ReturnedViewOwners machinery): a new LowerFlowStatements
desugars a TRACKED `using IMemoryOwner = MemoryPool.Rent(...)` declaration into
`acquire; try { rest } finally { release }` — the using-dispose is threaded onto the rest's
returns/throws, so a returned view of the owner is read after the release -> OWN002. Routed from
LowerFlowBody, the BlockSyntax case, and the try-body. With no such `using` declaration,
LowerFlowStatements lowers each statement exactly as the old loop, so every existing shape (incl. #73's
`using owner; Consume(owner.Memory.Span)` after.cs) is unchanged. The candidate pass now tracks `using`
MemoryPool owners (gated to IsMemoryPoolRent).

Corpus: memorypool-using-view-escape (before: `using owner; return owner.Memory` -> OWN002; after:
return the IMemoryOwner, ownership transferred -> silent) — the MemoryPool twin of
arraypool-memory-view-escape (#70). Benchmark recall floor 17 -> 18. P-007 status updated.

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 22, 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: 9a40f807-bef0-4784-a1de-949fdff1a3b4

📥 Commits

Reviewing files that changed from the base of the PR and between 609fdbd and 9230647.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • corpus/real-world/memorypool-using-statement-view-escape/after.cs
  • corpus/real-world/memorypool-using-statement-view-escape/before.cs
  • corpus/real-world/memorypool-using-statement-view-escape/case.own
  • corpus/real-world/memorypool-using-statement-view-escape/expected-diagnostics.txt
  • corpus/real-world/memorypool-using-statement-view-escape/notes.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
✅ Files skipped from review due to trivial changes (2)
  • corpus/real-world/memorypool-using-statement-view-escape/expected-diagnostics.txt
  • corpus/real-world/memorypool-using-statement-view-escape/case.own
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

📝 Walkthrough

Walkthrough

Extends the --flow-locals lowering in the Roslyn extractor to recognize using-scoped MemoryPool.Rent locals in both declaration and statement forms, lowering them into explicit acquire/release with return/throw continuations to enable OWN002 detection for dangling-view escapes. Adds two matching corpus test cases, expands the P-007 proposal, and raises the CI benchmark recall gate from 17 to 19.

Changes

MemoryPool using-view escape detection (OWN002)

Layer / File(s) Summary
Flow lowering for using MemoryPool.Rent → acquire/release continuations
frontend/roslyn/OwnSharp.Extractor/Program.cs
LowerFlowBody delegates to a new LowerFlowStatements helper that threads canEscape/onThrow/onReturn continuations. Both using IMemoryOwner = MemoryPool.Rent(...) declarations and using (IMemoryOwner owner = MemoryPool.Rent(...)) { ... } statements are lowered into explicit acquire + try/finally release wiring onto return/throw paths. BlockSyntax and TryStatementSyntax cases in LowerFlowStmt delegate to LowerFlowStatements. Candidate selection now includes using locals (both forms) whose initializer is MemoryPool.Rent.
New corpus test case: memorypool-using-view-escape (declaration form)
corpus/real-world/memorypool-using-view-escape/before.cs, after.cs, case.own, expected-diagnostics.txt, notes.md
Adds before.cs (dangling-view anti-pattern: using declaration + return owner.Memory), after.cs (fix: return IMemoryOwner directly), case.own (OwnLang acquire/release model), expected-diagnostics.txt (OWN002), and notes.md (POOL004 documentation for the declaration form).
New corpus test case: memorypool-using-statement-view-escape (statement form)
corpus/real-world/memorypool-using-statement-view-escape/before.cs, after.cs, case.own, expected-diagnostics.txt, notes.md
Adds before.cs (dangling-view anti-pattern: using statement + return owner.Memory), after.cs (fix: return IMemoryOwner directly), case.own (OwnLang acquire/release model), expected-diagnostics.txt (OWN002), and notes.md (POOL004 documentation for the statement form).
P-007 proposal update and CI recall gate bump
docs/proposals/P-007-arraypool-span.md, .github/workflows/ci.yml
P-007 gains MemoryPool-specific semantics stating that both using owner = MemoryPool.Rent(...); return owner.Memory and using (...) { return owner.Memory; } are modeled as POOL004/OWN002 via implicit try/finally release. CI benchmark --min-recall is raised from 17 to 19.

Sequence Diagram(s)

sequenceDiagram
  participant Extractor as Roslyn Extractor (--flow-locals)
  participant LowerFlowBody
  participant LowerFlowStatements
  participant OwnLang as OwnLang Emitter
  participant Checker as OWN002 Checker

  Extractor->>LowerFlowBody: process method body with continuations
  LowerFlowBody->>LowerFlowStatements: lower statement list (canEscape, onThrow, onReturn)
  LowerFlowStatements->>LowerFlowStatements: detect `using` MemoryPool.Rent (declaration or statement)
  LowerFlowStatements->>OwnLang: emit acquire(owner)
  LowerFlowStatements->>OwnLang: inject release(owner) onto return/throw paths (try/finally wiring)
  OwnLang->>Checker: submit flow graph with owner.Memory read after release
  Checker-->>Extractor: report OWN002 (use-after-release on returned Memory view)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

  • PhysShell/Own.NET#72: Adds base MemoryPool<T>.Rent/IMemoryOwner<T> tracking in Program.cs; the current PR builds directly on that by lowering using-scoped owners to surface the OWN002 escape.
  • PhysShell/Own.NET#73: Adds ViewOwner borrow recognition for owner.Memory/owner.Memory.Span leading to OWN002, which the current PR's using-scope lowering relies on to trigger the same violation.
  • PhysShell/Own.NET#31: Both PRs modify LowerFlowBody/TryStatementSyntax lowering to thread return/throw continuations through statement lists, directly intersecting at the same code path.

Poem

🐇 Hoppity-hop through the memory pool,
using scopes once made the buffer a fool—
Both declarations and statements let views escape free,
Now OWN002 catches what the eye couldn't see!
Return the IMemoryOwner, let the caller hold tight,
No dangling Memory<T> dancing through the night. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 title clearly and specifically summarizes the main change: detecting a MemoryPool memory safety vulnerability (POOL004) where using-owned IMemoryOwner returns create dangling references (OWN002).
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/memorypool-using-escape

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

@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: 609fdbd9da

ℹ️ 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 +1991 to +1992
if (IsMemoryPoolRent(v.Initializer?.Value, model))
candidates.Add(v.Identifier.Text);

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 Keep returned using owners tracked

When a using MemoryPool owner is returned directly (for example using var owner = MemoryPool<byte>.Shared.Rent(n); return owner;), this line adds it as a candidate, but the later escape pass still removes direct ReturnStatementSyntax references as ownership transfers. In C#, the using declaration disposes the owner as the method returns, so the caller receives an already-disposed IMemoryOwner<T>; the new using-desugaring never runs because the local has been untracked.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Valid, and distinct from what this PR catches. #74 targets the dangling view escape (return owner.Memory), where the owner stays tracked. Returning the owner itself (using owner; return owner;) is a different shape: the escape pass untracks any directly-returned local as an ownership transfer — correct for a non-using owner, but wrong for a using owner (which is disposed at scope exit, so the caller gets an already-disposed IMemoryOwner).

Catching it needs the escape pass to treat a using owner's direct return as a dangling rather than a transfer (and the return handler to model the caller's use after the scope-exit release) — a separate slice from the view escape. Scoped as a follow-up and noted in the corpus notes.md; not folding it into this PR to keep the view-escape change focused. Thanks for the catch.


Generated by Claude Code

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/roslyn/OwnSharp.Extractor/Program.cs`:
- Around line 431-449: The LowerFlowStatements function currently only applies
MemoryPool owner tracking and acquire/release desugaring to using declarations
(LocalDeclarationStatementSyntax with UsingKeyword), but not to the equivalent
using statement form (UsingStatementSyntax). Both syntactic forms can contain
MemoryPool owners requiring the same scope-exit disposal handling. Add logic to
detect UsingStatementSyntax in addition to the existing
LocalDeclarationStatementSyntax check, apply the same IsMemoryPoolRent
validation to the statement's resource declaration, and apply the same
acquire/release desugaring pattern. Additionally, update the candidate selection
logic (around lines 1983-1993) to also scan for UsingStatementSyntax forms when
building the tracked set, so statement-form MemoryPool owners are properly
tracked throughout the lowering process.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75967709-7219-4743-bfc4-192a5e9b4912

📥 Commits

Reviewing files that changed from the base of the PR and between 377c182 and 609fdbd.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • corpus/real-world/memorypool-using-view-escape/after.cs
  • corpus/real-world/memorypool-using-view-escape/before.cs
  • corpus/real-world/memorypool-using-view-escape/case.own
  • corpus/real-world/memorypool-using-view-escape/expected-diagnostics.txt
  • corpus/real-world/memorypool-using-view-escape/notes.md
  • docs/proposals/P-007-arraypool-span.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

Comment thread frontend/roslyn/OwnSharp.Extractor/Program.cs
…ool view escape (CodeRabbit)

#74's using-desugar only matched the `using` DECLARATION form (LocalDeclarationStatementSyntax with
UsingKeyword). The equivalent `using (IMemoryOwner owner = MemoryPool.Rent(n)) { return owner.Memory; }`
STATEMENT form has the same scope-exit dispose, so the returned view dangles the same way — but
UsingStatementSyntax only lowered its body and the candidate pass only scanned
LocalDeclarationStatementSyntax, so the statement-form owner was untracked and silent.

Extend both: the candidate pass now scans UsingStatementSyntax declarations for IsMemoryPoolRent, and
the UsingStatementSyntax flow case desugars a tracked MemoryPool owner into
`acquire; try { body } finally { release }` exactly like the declaration form. Corpus:
memorypool-using-statement-view-escape (-> OWN002; after.cs returns the owner -> silent). Benchmark
recall floor 18 -> 19.

Not in scope (distinct follow-up, Codex review): `using owner; return owner;` — returning the OWNER
itself (not a view) stays silent because the escape pass untracks directly-returned owners as
ownership transfers; catching it needs the escape pass to treat a using-owner's direct return as a
dangling rather than a transfer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
@PhysShell PhysShell merged commit d646d8d into main Jun 22, 2026
12 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