Skip to content

feat(extractor): pooled buffers through the flow engine → OWN003/OWN002 (recall 4/9→6/9)#52

Merged
PhysShell merged 3 commits into
mainfrom
claude/flow-pool-tracking
Jun 20, 2026
Merged

feat(extractor): pooled buffers through the flow engine → OWN003/OWN002 (recall 4/9→6/9)#52
PhysShell merged 3 commits into
mainfrom
claude/flow-pool-tracking

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 20, 2026

Copy link
Copy Markdown
Owner

What

Routes pooled buffers through the path-sensitive flow engine, so the benchmark's two pool misses are caught soundly — corpus recall 4/9 → 6/9.

The extractor's pool pass was purely syntactic — "was this buffer Returned anywhere?" → only POOL001 (rent-without-return). A second Return, or a read after Return, was invisible. Counting Returns would be unsound (it false-positives on if (x) Return(b); else Return(b); — exactly one runs), and precision is sacred here. So instead, reuse the flow engine that already proves OWN002/OWN003 for IDisposable locals:

  • a *Pool.Rent(...) local is an acquire (candidates + LowerFlowStmt);
  • *Pool.Return(buf) is a release — the buffer is the argument, the pool the receiver (unlike Dispose, where the local is the receiver);
  • a read of the buffer — including in a return value — is a use (the return expression is now lowered, so return BuildResult(buf) after Return(buf) is the use-after-return);
  • pooled buffers do not escape on arg-passing (the ArrayPool convention is the renter returns it, so Work(buf)/Return(buf) are a borrow/release, not a transfer);
  • the syntactic POOL001 is suppressed under --flow-locals (no double-report); it stays for the legacy/no-flow path.

The core's CFG analysis then flags the double-release (OWN003) and the use-after-release (OWN002) path-sensitively.

The proof (benchmark, CI)

Green at --min-recall 6: arraypool-double-returnOWN003, arraypool-use-after-returnOWN002, both caught; specificity stays 9/9, 0 false positives. The wpf-extractor job confirms the C# compiles and the existing flow-locals tests don't regress.

Bonus: the benchmark diagnosed its own last gap — the first run stayed 4/9 because those reductions used file-scope static methods (parsed as top-level local functions, which the per-class flow pass doesn't walk). Wrapping each in a class (mirrored before/after, helpers stubbed — the same self-containment lesson as the loaded-subscription fixture) closed it. case.own unchanged.

Files

  • frontend/roslyn/OwnSharp.Extractor/Program.cs — pool acquire/release recognition, pool-aware escape, return-value lowering, POOL001 gated under --flow-locals, IsPoolRent/PoolReturnBuffer helpers.
  • corpus/real-world/arraypool-{double-return,use-after-return}/{before,after}.cs — wrapped in classes.
  • .github/workflows/ci.yml — floor --min-recall 6.
  • docs/corpus-benchmark.md + P-012 (the two-step ratchet to 6/9).

Remaining recall backlog (3): interprocedural ownership-handoff, a cross-method use-after-dispose, a region-escape shape.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Extended flow-sensitive analysis to track pooled buffers from Rent(...) through Return(...), improving detection accuracy for double-return and use-after-return scenarios.
  • Bug Fixes

    • Prevented duplicate pooled-buffer leak reports when flow-local analysis is enabled.
    • Improved escape handling so pooled-buffer arguments don’t get treated as escaping.
  • Documentation

    • Updated corpus benchmark notes and proposal text with revised recall metrics and rationale.
  • Tests

    • Restructured pooled-buffer corpus cases to be self-contained for extraction/validation.
  • Chores

    • Tightened CI benchmark gate to a higher minimum recall threshold.

claude added 2 commits June 20, 2026 14:29
…02 (recall 4/9->6/9)

The pool pass was purely syntactic ("was this buffer Returned anywhere?") — only
POOL001 (rent-without-return). A second Return or a read after Return was invisible,
so arraypool-double-return (OWN003) and arraypool-use-after-return (OWN002) were
benchmark misses. Counting Returns would be UNSOUND (false-positives on
`if (x) Return(b); else Return(b);`), and precision is sacred.

So route pooled buffers through the path-sensitive flow engine that already proves
OWN002/OWN003 for IDisposable locals:
  - a `*Pool.Rent(...)` local is an acquire (LowerFlowStmt + candidates);
  - `*Pool.Return(buf)` is a release — the buffer is the ARGUMENT, not the receiver
    (EmitFlowExpr);
  - a read of the buffer, including in a `return` value, is a use (the return
    expression is now lowered, so `return BuildResult(buf)` after Return(buf) is the
    use-after-return);
  - pooled buffers do NOT escape on arg-passing (the ArrayPool convention is the
    renter returns it, so Work(buf)/Return(buf) are a borrow/release, not a transfer);
  - the syntactic POOL001 is suppressed under --flow-locals so there is no
    double-report (it stays for the legacy/no-flow path).

The core's CFG analysis then flags the double-release and the use-after-release
soundly, path-sensitive. Recall is 6/9; --min-recall raised to 6; specificity stays
9/9, 0 FP. Validated end-to-end by the corpus-benchmark CI job (no local .NET SDK).
docs: corpus-benchmark.md + P-012.

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

The flow-pool change compiled and didn't regress (wpf-extractor green), but the
benchmark stayed 4/9: arraypool-double-return and arraypool-use-after-return
declared file-scope `static` methods, which parse as top-level local functions —
the extractor's per-class flow pass (cls.Members) never walks them, so the new
pool tracking never ran on them (the other corpus cases are classes, hence caught).

Wrap each method in a static class (mirrored before/after) + stub the helpers so
the reduction is self-contained — exactly the self-containment lesson from the
loaded-subscription fixture. Now the flow pass visits them: Rent -> acquire,
Return -> release, the read-after-Return / second-Return -> OWN002 / OWN003.
case.own is unchanged (test_corpus is unaffected). Targets recall 6/9.

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 20, 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: 8020e001-d093-4382-bac6-8c797cc3c26a

📥 Commits

Reviewing files that changed from the base of the PR and between c89b54d and 988f99d.

📒 Files selected for processing (4)
  • corpus/real-world/arraypool-use-after-return/after.cs
  • corpus/real-world/arraypool-use-after-return/before.cs
  • docs/notes/corpus-benchmark.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
✅ Files skipped from review due to trivial changes (1)
  • docs/notes/corpus-benchmark.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • corpus/real-world/arraypool-use-after-return/after.cs
  • corpus/real-world/arraypool-use-after-return/before.cs
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

📝 Walkthrough

Walkthrough

The PR extends the --flow-locals pipeline in Program.cs to model ArrayPool/MemoryPool Rent calls as acquire and Return calls as release, suppresses the syntactic POOL001 checker under flow-locals mode, and refines escape logic for pooled buffers. Corpus fixtures for arraypool-double-return and arraypool-use-after-return are wrapped in static classes to be self-contained for the extractor. The CI benchmark gate rises from --min-recall 4 to 6, and documentation is updated to reflect 6/9 recall.

Changes

Pooled-buffer flow tracking and corpus recall improvement

Layer / File(s) Summary
Flow engine: pooled-buffer acquire/release tracking
frontend/roslyn/OwnSharp.Extractor/Program.cs
LowerFlowStmt now recognises IsPoolRent(...) as an acquire operation; EmitFlowExpr emits a release fact for Pool.Return(buf) and returns early to prevent a duplicate use. New helpers IsPoolRent and PoolReturnBuffer are added. A poolBuffers set tracks rented locals per method, and escape filtering excludes pooled-buffer identifiers passed as arguments. POOL001 syntactic detection is skipped when --flow-locals is active.
Corpus fixtures: wrap in static classes
corpus/real-world/arraypool-double-return/before.cs, corpus/real-world/arraypool-double-return/after.cs, corpus/real-world/arraypool-use-after-return/before.cs, corpus/real-world/arraypool-use-after-return/after.cs
PoolDoubleReturn and PoolUseAfterReturn static classes are introduced to wrap the previously file-scoped methods, making each reduction self-contained with stub helper methods so the extractor's class-based flow analysis can exercise OWN002 and OWN003 patterns.
CI benchmark gate and documentation
.github/workflows/ci.yml, docs/notes/corpus-benchmark.md, docs/proposals/P-012-bug-corpus-mining.md
--min-recall is raised from 4 to 6 in the CI benchmark step. Notes and proposal docs are updated to report 6/9 recall with 0 FP, explain the path-sensitive flow engine change covering OWN002/OWN003, and reduce the remaining backlog to three extraction gaps.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PhysShell/Own.NET#15: Introduces the --flow-locals lowering pipeline in Program.cs that this PR extends with Rent/Return pooled-buffer tracking.
  • PhysShell/Own.NET#34: Modifies the same --flow-locals flow-expression emission code paths in Program.cs to emit release facts for different constructs (null-conditional dispose operations vs. pooled Return calls).
  • PhysShell/Own.NET#51: Adjusts the same corpus-benchmark CI gate threshold in .github/workflows/ci.yml, though for different underlying corpus issues (main PR: 4→6 for pooled-buffer flow; retrieved PR: 3→4 for earlier fixture updates).

Poem

🐇 Hop, hop, through the ArrayPool lane,
I Rent a buffer, then Return again—
No double-return, no use-after-free,
The flow engine tracks them all for me!
Six of nine bugs caught, three left to find,
A path-sensitive rabbit with a tidy mind. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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 directly reflects the main technical achievement: adding path-sensitive flow analysis for pooled buffers (ArrayPool) through the flow engine to improve defect detection from 4/9 to 6/9 recall, catching OWN003 (double-return) and OWN002 (use-after-return) violations.
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/flow-pool-tracking

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: c89b54d2e6

ℹ️ 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 thread frontend/roslyn/OwnSharp.Extractor/Program.cs Outdated
Comment thread corpus/real-world/arraypool-use-after-return/after.cs Outdated

@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: 3

🤖 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 `@docs/notes/corpus-benchmark.md`:
- Around line 29-31: The markdown file has a heading level violation in the
ratchet section where the heading "Ratchet → 6/9 (two ratchets)" is marked as H3
(###) and its subheading is marked as H4 (####), which creates an improper level
jump. To fix this, demote the main heading "### Ratchet → 6/9 (two ratchets)" to
H2 by changing it to "## Ratchet → 6/9 (two ratchets)", and adjust the
subheading "#### → 4/9: a fixture was understating us" to H3 by changing it to
"### → 4/9: a fixture was understating us" to maintain proper heading hierarchy.

In `@frontend/roslyn/OwnSharp.Extractor/Program.cs`:
- Around line 1157-1162: The POOL001 syntactic pass is being disabled entirely
when flowLocals is enabled, which removes detection for pool leaks in
field-assigned rentals since the flow-analysis only tracks locals in method
bodies. Instead of conditionally skipping the entire member iteration based on
the flowLocals flag, move the conditional check inside the loop over cls.Members
so that POOL001 suppression is applied selectively only to the locals and
methods that are actually flow-analysed (when flowLocals is true), while still
performing the POOL001 pass for other members like field assignments that fall
outside the flow-analysis scope.
- Around line 725-741: The IsPoolRent and PoolReturnBuffer methods use
unreliable text-based checking with Contains("Pool"/"pool") that can miss
aliased receivers and falsely match unrelated APIs. Replace this text-based
approach with semantic model-based type checking to properly identify ArrayPool
and MemoryPool types. The semantic model is already available at line 1276;
thread it as a parameter through the call chain LowerFlowBody → LowerFlowStmt →
EmitFlowExpr → IsPoolRent/PoolReturnBuffer so that these methods can use the
semantic model to check if the receiver type is actually an ArrayPool or
MemoryPool, eliminating the heuristic failures.
🪄 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: 84d7371b-f173-4e9c-a887-c8888502a981

📥 Commits

Reviewing files that changed from the base of the PR and between dbf66ab and c89b54d.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • corpus/real-world/arraypool-double-return/after.cs
  • corpus/real-world/arraypool-double-return/before.cs
  • corpus/real-world/arraypool-use-after-return/after.cs
  • corpus/real-world/arraypool-use-after-return/before.cs
  • docs/notes/corpus-benchmark.md
  • docs/proposals/P-012-bug-corpus-mining.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

Comment thread docs/notes/corpus-benchmark.md Outdated
Comment thread frontend/roslyn/OwnSharp.Extractor/Program.cs
Comment thread frontend/roslyn/OwnSharp.Extractor/Program.cs Outdated
…xed result, fix heading

Three review fixes on the flow-pool tracking change:

- POOL001 field-rent regression (Codex): gating the whole syntactic
  POOL001 pass on `!flowLocals` dropped coverage for field/assignment
  rents (`_buf = pool.Rent(n)`), which the flow engine never tracks (it
  only follows local declarations). Run the pass in both modes; under
  --flow-locals skip only the local-declaration rents (the flow pass owns
  those) so field-backed rents still get POOL001 and locals are not
  double-reported.

- use-after-return fixture aliasing (Codex): BuildResult `=> buffer`
  handed back the pooled array itself, so after.cs's "fixed" result still
  aliased the buffer it returned to the pool. Return a distinct copy
  (`(int[])buffer.Clone()`) in both before/after so the only bug under
  test is the use-after-return ordering.

- MD001 (CodeRabbit): the ratchet section jumped H1 -> H3. Promote it to
  ## and its two subsections to ###.

Recall stays 6/9; precision stays absolute (every after.cs silent).
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