feat(pool): POOL005 .Length spelling + MemoryPool tracking (POOL001/002/003)#72
Conversation
…02/003) Two pooled-buffer extractor additions, both riding machinery that already exists in the core — so there are NO core changes, only the Roslyn extractor + corpus + spec. POOL005 — the `.Length` spelling of the over-read/over-clear (sibling of #71's unbounded `buf.AsSpan()`). A pooled array is oversized (Rent(n) -> Length >= n), so passing the buffer's OWN `buf.Length` as the operative length reaches the whole array, past the rented n, into the stale [n, Length) tail: - FullViewOwner now also matches buf.AsSpan(0, buf.Length) / buf.AsMemory(0, buf.Length) and new Span<T>(buf, 0, buf.Length) (length arg is the receiver's own .Length); - a new ArrayClearOverOwner matches Array.Clear(buf, 0, buf.Length) (the over-clear). A real bound (buf.AsSpan(0, n), Array.Clear(buf, 0, n)) is not matched; the single-arg Array.Clear(buf) wipe is deliberately left alone. All emit the existing `overspan` fact -> OWN025. Corpus: arraypool-clear-past-length. MemoryPool — the OTHER pool was untracked entirely. MemoryPool<T>.Shared.Rent returns an IMemoryOwner<T> released by Dispose (no Return), so it is tracked as a regular Dispose-released local (IsMemoryPoolRent, mirroring IsPoolRent but NOT a poolBuffer: arg-passing is an escape, like any IDisposable). With that one acquire recognised, the existing flow covers POOL001 (never disposed -> OWN001), POOL002 (use after Dispose -> OWN002) and POOL003 (double Dispose -> OWN003). Corpus: memorypool-double-dispose (the MemoryPool twin of arraypool-double-return) -> OWN003. POOL003 for ArrayPool was already green (try/finally + aliased-receiver, both in the benchmark) — this adds its MemoryPool sibling. Benchmark recall floor 14 -> 16. Spec: BufferPolicies B9 (the .Length spelling), P-007 (status, scope, open Q#4 resolved). metamorphic.py already knows `overspan`; the new .own models reuse acquire/release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
💤 Files with no reviewable changes (1)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds two new real-world corpus test cases — ChangesPOOL003 / POOL005 Extractor and Corpus Extension
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3bf5b1095
ℹ️ 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".
| return args.Count == 3 | ||
| && args[0].Expression is IdentifierNameSyntax buf | ||
| && IsLengthOf(args[2].Expression, buf.Identifier.Text) | ||
| ? buf.Identifier.Text : null; |
There was a problem hiding this comment.
Do not flag whole-buffer clears as overspans
In cleanup code that intentionally wipes a rented buffer before returning it, Array.Clear(buf, 0, buf.Length) is the same whole-buffer wipe as Array.Clear(buf) or returning with clearing enabled; it only overwrites the pooled tail and does not read or expose stale bytes. This condition turns that safe idiom into OWN025 whenever the array came from ArrayPool, so correct fixes that clear the full buffer before Return will be reported as bugs.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
spec/BufferPolicies.md (1)
42-53: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueRule B9 revision is accurate and aligns with POOL005 overspan detection.
The expansion correctly identifies both unsafe patterns (full-length views and
.Length-based forms), distinguishes them from the bounded-by-nsafe case, and clearly separates B9 (touching too much) from B6 (clearing too little). The spec precisely matches the extractor's pattern-matching logic.Consider a brief note that the oversizing principle applies to all pooled buffer types:
ArrayPool<T>.Rent(n)returns an oversized array directly, whileMemoryPool<T>.Rent(n)returns anIMemoryOwner<T>wrapping an oversized backing array (accessed via.Memory.Length). Both exhibit the same over-reach risk when using the backing.Lengthinstead of the requestedn.🤖 Prompt for 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. In `@spec/BufferPolicies.md` around lines 42 - 53, The spec section for B9 currently focuses on ArrayPool<T>.Rent(n) patterns but should clarify that the oversizing principle applies to all pooled buffer types. Add a brief explanatory note in the B9 section clarifying that while ArrayPool<T>.Rent(n) returns an oversized array directly, MemoryPool<T>.Rent(n) returns an IMemoryOwner<T> wrapping an oversized backing array (accessible via .Memory.Length), and both exhibit the same over-reach risk when operations use the backing .Length instead of the requested n parameter. This ensures the spec accurately represents the threat model across all pooled buffer abstractions.
🤖 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 @.github/workflows/ci.yml:
- Around line 791-799: The benchmark script command in the CI workflow is
configured with --min-recall 16, but the test corpus only contains 14 cases (7
arraypool cases and 1 memorypool case), creating an impossible threshold that
will always fail. Either add the two missing test cases referenced in the PR
objectives to the corpus to bring it to 16 cases, or reduce the --min-recall
threshold value from 16 to 14 to match the current actual corpus size. Verify
the corpus size is accurate before adjusting the threshold.
In `@frontend/roslyn/OwnSharp.Extractor/Program.cs`:
- Around line 956-957: The conditions checking IsLengthOf at lines 957, 970, and
995 match any .Length expression without verifying that the start or index
parameter is set to 0, causing false positives beyond the intended POOL005
pattern. For each of these three locations, add an additional validation check
to the condition to ensure the start/index argument is explicitly 0. This
requires examining the second argument in the args collection and confirming its
value is a literal 0 before marking it as a valid POOL005 match.
---
Nitpick comments:
In `@spec/BufferPolicies.md`:
- Around line 42-53: The spec section for B9 currently focuses on
ArrayPool<T>.Rent(n) patterns but should clarify that the oversizing principle
applies to all pooled buffer types. Add a brief explanatory note in the B9
section clarifying that while ArrayPool<T>.Rent(n) returns an oversized array
directly, MemoryPool<T>.Rent(n) returns an IMemoryOwner<T> wrapping an oversized
backing array (accessible via .Memory.Length), and both exhibit the same
over-reach risk when operations use the backing .Length instead of the requested
n parameter. This ensures the spec accurately represents the threat model across
all pooled buffer abstractions.
🪄 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: 3e410335-6435-4e61-8767-6ab51a9841cb
📒 Files selected for processing (14)
.github/workflows/ci.ymlcorpus/real-world/arraypool-clear-past-length/after.cscorpus/real-world/arraypool-clear-past-length/before.cscorpus/real-world/arraypool-clear-past-length/case.owncorpus/real-world/arraypool-clear-past-length/expected-diagnostics.txtcorpus/real-world/arraypool-clear-past-length/notes.mdcorpus/real-world/memorypool-double-dispose/after.cscorpus/real-world/memorypool-double-dispose/before.cscorpus/real-world/memorypool-double-dispose/case.owncorpus/real-world/memorypool-double-dispose/expected-diagnostics.txtcorpus/real-world/memorypool-double-dispose/notes.mddocs/proposals/P-007-arraypool-span.mdfrontend/roslyn/OwnSharp.Extractor/Program.csspec/BufferPolicies.md
…re zero start Review feedback on #72 (Codex + CodeRabbit), all precision tightening on an already-green PR: 1. Codex: do not flag Array.Clear(buf, 0, buf.Length). It is a WRITE/wipe — it only zeros the pooled tail (a safe clear-before-Return idiom) and exposes nothing; the over-read bug is READING/COPYING the tail through a view. Removed ArrayClearOverOwner entirely; EmitOverspans now flags only read-capable VIEWS (FullViewOwner). Renamed the corpus case arraypool-clear-past-length -> arraypool-length-overread and made it the `.Length` VIEW over-read (buf.AsSpan(0, buf.Length)); after.cs bounds by n. 2. CodeRabbit: the `.Length` view match now also requires the START/index to be the constant 0 (IsZeroInt via the SemanticModel) — buf.AsSpan(k, buf.Length) for k != 0 is out of range, not this pattern. Applies to AsSpan/AsMemory(0, buf.Length) and new Span<T>(buf, 0, buf.Length). 3. CodeRabbit (spec nitpick): B9 now notes the oversize principle covers MemoryPool too (IMemoryOwner.Memory wraps an oversized backing via .Memory.Length), and that a write/wipe is not flagged. NOT changed: --min-recall stays 16. CodeRabbit flagged it as "impossible (corpus is 14)" but that miscounts — the benchmark scores corpus/real-world (14) AND corpus/wpf (4) = 18 total, and --min-recall is the CAUGHT floor, not the corpus size. #72's benchmark is green at 16 (16 caught of 18); the `.Length` VIEW form still catches arraypool-length- overread after dropping Array.Clear. spec/P-007/ci.yml comments updated to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
What
Two pooled-buffer additions, both riding machinery that already exists in the core — so there are no core changes, only the Roslyn extractor + corpus + spec.
POOL005 — the
.Lengthspelling (sibling of #71's unboundedbuf.AsSpan())A pooled array is oversized (
Rent(n)→Length >= n), so passing the buffer's ownbuf.Lengthas the operative length reaches the whole array — past the rentedn, into the stale[n, Length)tail:FullViewOwnernow also matchesbuf.AsSpan(0, buf.Length)/AsMemory(0, buf.Length)andnew Span<T>(buf, 0, buf.Length)(the length argument is the receiver's own.Length).ArrayClearOverOwnermatchesArray.Clear(buf, 0, buf.Length).buf.AsSpan(0, n)) is not matched; the single-argArray.Clear(buf)whole-buffer wipe is deliberately left alone (low-FP). All emit the existingoverspanfact → OWN025.arraypool-clear-past-length.MemoryPool — the other pool was untracked entirely
MemoryPool<T>.Shared.Rentreturns anIMemoryOwner<T>released byDispose(there is noReturn). It's now tracked as a regular Dispose-released local (IsMemoryPoolRent, mirroringIsPoolRentbut not apoolBuffer— arg-passing is an escape, like any IDisposable). With that one acquire recognised, the existing flow covers the whole family:memorypool-double-dispose(the MemoryPool twin ofarraypool-double-return) → OWN003.On POOL003
ArrayPool double-return was already green before this PR — the benchmark catches it as OWN003 in two variants (
arraypool-double-returntry/finally andarraypool-aliased-receiver). This PR adds its MemoryPool sibling, which was the real gap.Validation
overspan→OWN025 (feat(pool): POOL005 — full-length view of a pooled buffer over-reads its logical length → OWN025 #71) andacquire/release→OWN003 already exist; this is extractor + corpus + spec only.Program.cs) is CI-validated..Lengthspelling), P-007 (status, scope, open Q#4 — the one-model-two-spellings question — resolved).Scope
Catches the
.Lengthover-read/over-clear and the MemoryPool owner lifecycle. TheIMemoryOwner.Memory.Spanview borrow and a POOL005 view stored in a FIELD are noted follow-ups.🤖 Generated with Claude Code
https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
Generated by Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores