Skip to content

feat(corpus): lock the ShareX TcpListener + StreamReader leaks as regressions (recall 10→12)#61

Closed
PhysShell wants to merge 1 commit into
mainfrom
claude/corpus-sharex-minor-leaks
Closed

feat(corpus): lock the ShareX TcpListener + StreamReader leaks as regressions (recall 10→12)#61
PhysShell wants to merge 1 commit into
mainfrom
claude/corpus-sharex-minor-leaks

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Closed — these are false positives, not real leaks (Codex review, #discussion_r3448065424).

  • TcpListener: Dispose() delegates to Stop() (which disposes the listen socket), so a Stop()'d listener holds no resource — flagging it OWN001 is an FP.
  • StreamReader: its MemoryStream is separately using'd, so not disposing the reader leaks nothing.

Locking these as must-catch OWN001 regressions would pin false positives against the 0-FP precision principle. Instead of locking the TcpListener FP, the follow-up hardens the extractor to model tcpListener.Stop() as a release (TcpListener-specific), so a stopped listener stays silent while a never-stopped one still leaks.


Original description below

The two remaining real (if minor) local-disposable leaks from the ShareX triage — both already caught by the extractor, now pinned as corpus regressions. (Superseded: see above.)

…ressions (recall 10→12)

The two remaining real (if minor) local-disposable leaks from the ShareX triage — both
already caught by the extractor (plain `new` acquires), now pinned as corpus regressions so
the catch can't silently regress:

  * sharex-tcplistener-not-disposed — ShareX.HelpersLib/Helpers/WebHelpers.cs:191
    (GetRandomUnusedPort): a TcpListener is Start()'d and Stop()'d but never disposed.
    TcpListener is IDisposable and Stop() is not Dispose() -> the listen socket leaks
    (CA2000 / CodeQL cs/local-not-disposed). The "grab a free port" idiom.
  * sharex-streamreader-not-disposed — ShareX.HelpersLib/Helpers/Helpers.cs:854: a
    StreamReader wraps a MemoryStream to read it to a string and is never disposed — a
    separate IDisposable from the (using-scoped) stream.

Each in the established shape (before.cs / after.cs / case.own / expected-diagnostics.txt /
notes.md), self-contained on BCL types (no ref pack). after.cs `using`-scopes the reader /
listener -> silent. Ratchets the benchmark floor 10->12 so both catches are pinned.

Validated locally: both case.own -> OWN001; tests/test_corpus.py 10/10; the modeled
before.cs facts -> OWN001 'is never disposed' for both. The before.cs->facts extractor step
is validated in CI (corpus-benchmark).

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: e1e0a0a5-0ef6-4e4d-b06d-526c6d5436de

📥 Commits

Reviewing files that changed from the base of the PR and between cfcf7f6 and 637c490.

📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • corpus/real-world/sharex-streamreader-not-disposed/after.cs
  • corpus/real-world/sharex-streamreader-not-disposed/before.cs
  • corpus/real-world/sharex-streamreader-not-disposed/case.own
  • corpus/real-world/sharex-streamreader-not-disposed/expected-diagnostics.txt
  • corpus/real-world/sharex-streamreader-not-disposed/notes.md
  • corpus/real-world/sharex-tcplistener-not-disposed/after.cs
  • corpus/real-world/sharex-tcplistener-not-disposed/before.cs
  • corpus/real-world/sharex-tcplistener-not-disposed/case.own
  • corpus/real-world/sharex-tcplistener-not-disposed/expected-diagnostics.txt
  • corpus/real-world/sharex-tcplistener-not-disposed/notes.md

📝 Walkthrough

Walkthrough

Two new real-world ShareX corpus cases are added for undisposed StreamReader and TcpListener resources. Each case includes a buggy before.cs, a fixed after.cs, an OwnLang model (case.own), an expected OWN001 diagnostic, and a notes.md. The CI corpus-benchmark job's --min-recall threshold is raised from 10 to 12.

Changes

ShareX Corpus Cases and CI Threshold

Layer / File(s) Summary
StreamReader not-disposed corpus case
corpus/real-world/sharex-streamreader-not-disposed/before.cs, corpus/real-world/sharex-streamreader-not-disposed/after.cs, corpus/real-world/sharex-streamreader-not-disposed/case.own, corpus/real-world/sharex-streamreader-not-disposed/expected-diagnostics.txt, corpus/real-world/sharex-streamreader-not-disposed/notes.md
before.cs defines XmlFormatter.ToText creating a StreamReader without disposing it; after.cs wraps it in a using scope; case.own models the Reader resource with acquire/release semantics and omits the release to represent the leak; expected-diagnostics.txt declares OWN001; notes.md documents the ShareX finding at Helpers.cs:854.
TcpListener not-disposed corpus case
corpus/real-world/sharex-tcplistener-not-disposed/before.cs, corpus/real-world/sharex-tcplistener-not-disposed/after.cs, corpus/real-world/sharex-tcplistener-not-disposed/case.own, corpus/real-world/sharex-tcplistener-not-disposed/expected-diagnostics.txt, corpus/real-world/sharex-tcplistener-not-disposed/notes.md
before.cs defines PortFinder.GetRandomUnusedPort calling Stop() but not Dispose(); after.cs wraps TcpListener in a using scope; case.own models the Listener resource with acquire/release and omits the release in GetRandomUnusedPort; expected-diagnostics.txt declares OWN001; notes.md documents the ShareX finding at WebHelpers.cs:191.
CI benchmark min-recall threshold bump
.github/workflows/ci.yml
Changes --min-recall in the corpus-benchmark job from 10 to 12.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Possibly related PRs

  • PhysShell/Own.NET#50: Targets the same python scripts/benchmark.py --min-recall threshold gate in the CI corpus-benchmark job.
  • PhysShell/Own.NET#54: Modifies the same CI corpus-benchmark step's --min-recall threshold, connected through shared OWN001 leak detection benchmarking.
  • PhysShell/Own.NET#58: Also updates the --min-recall threshold in .github/workflows/ci.yml's corpus-benchmark job invocation.

Poem

🐇 Hop, hop! Resources freed at last,
StreamReader and TcpListener bound fast.
using scopes wrap them up tight,
OWN001 catches each forgotten plight.
The recall bar rises — twelve is the floor,
No leaky disposables slip through anymore! 🎉

🚥 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 accurately summarizes the main change: adding two ShareX regression test cases for resource disposal leaks and increasing recall threshold from 10 to 12.
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/corpus-sharex-minor-leaks

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: 637c4900b0

ℹ️ 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".

TcpListener listener = new TcpListener(IPAddress.Loopback, 0); // <-- OWN001: never disposed
listener.Start();
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop(); // Stop() releases the listen socket but is NOT Dispose() -> the listener leaks

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 Do not pin Stop-cleaned TcpListener as a leak

For the ShareX pattern this case is trying to model, Stop() is the cleanup: in the .NET implementation used by the benchmark, TcpListener.Dispose() just delegates to Stop(), and Stop() disposes the underlying socket and clears it. Once this line runs (the real method does it in finally), there is no leaked listen socket, so adding this case with expected OWN001 and ratcheting CI to require it makes the corpus protect a false positive rather than a regression.

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.

You're right, and this is the important kind of catch — thank you. TcpListener.Dispose() delegates to Stop() (which disposes the listen socket), so a Stop()'d listener holds no resource: flagging it OWN001 is a false positive, and locking it would have pinned an FP against our 0-FP principle. The same applies to the StreamReader case (its MemoryStream is separately using'd).

So I'm closing this PR rather than locking these. And since the extractor genuinely over-fires on Stop()-cleaned TcpListener, that's a real precision gap worth fixing — I'll harden it to model tcpListener.Stop() as a release (TcpListener-specific, resolved via the symbol; Stop() on a Timer/Process/etc. is untouched), so a never-stopped listener still leaks but a stopped one stays silent. Following up in a separate PR.


Generated by Claude Code

@PhysShell PhysShell closed this Jun 21, 2026
PhysShell added a commit that referenced this pull request Jun 21, 2026
…eanup) (#62)

Codex review on #61: triaging the ShareX re-mine, the undisposed-TcpListener finding
(WebHelpers.GetRandomUnusedPort: Start() then Stop() in a finally, no Dispose()) is a FALSE
POSITIVE. TcpListener.Dispose() just delegates to Stop(), which disposes the listen socket
and clears it — so a Stop()'d listener holds no resource. Flagging it OWN001 dents the 0-FP
precision claim (CA2000 / CodeQL flag it conventionally, but it is not a real leak).

EmitFlowExpr now models tcpListener.Stop() as a release of the local, alongside the
Dispose()/Close()/pool-Return/Show() releases. TcpListener-specific, resolved on the
method's containing type via the SemanticModel (Stop() on a Timer / Process / Stopwatch /
etc. does NOT dispose and stays a tracked use), and path-sensitive like the WinForms Show()
model: a listener never Stop()'d (nor disposed) still leaks.

Pinned by two FlowLocalsSample cases in the wpf-extractor --flow-locals step:
TcpListenerStopped (Start() + Stop() -> silent, the FP this removes) and
TcpListenerNeverStopped (Start(), no Stop -> OWN001, the control proving the release is
Stop()-specific).

The sibling StreamReader FP from the same triage is deliberately NOT changed: the
extractor's general behaviour there is correct (a StreamReader over a stream not otherwise
disposed genuinely leaks it); only the narrow using-scoped-stream variant is a non-leak, a
rare accepted residual. See docs/notes/tcplistener-stop-release.md. Supersedes the closed
#61 (which would have locked these FPs).

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