Skip to content

Commit 9c7eb9b

Browse files
committed
test(owned-api): cover Socket.Accept and TcpListener.AcceptSocket branches
CodeRabbit: the accept matcher has three hard-coded branches but only AcceptTcpClient had an end-to-end fixture, so a typo/regression in the Socket.Accept or TcpListener.AcceptSocket branch would slip the corpus benchmark (its after.cs specificity check + recall visibility). Add a corpus fixture for each: socket-accept-leak (Socket.Accept) and tcplistener-acceptsocket-leak (TcpListener.AcceptSocket) — before.cs leaks the accepted socket (OWN001), after.cs disposes it with `using` (clean), case.own reduces the pattern. case.own -> OWN001 locally (test_corpus 24/24); before/after verified by the CI corpus benchmark. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
1 parent 62b6fff commit 9c7eb9b

10 files changed

Lines changed: 94 additions & 0 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Net.Sockets;
2+
3+
static class SocketAcceptLeak
4+
{
5+
static bool Serve(Socket listener)
6+
{
7+
using var conn = listener.Accept(); // disposed at scope exit -> clean
8+
return conn.Connected;
9+
}
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Net.Sockets;
2+
3+
// A Socket returned by Socket.Accept() is a fresh owned IDisposable the caller must dispose;
4+
// dropping it leaks the accepted connection (handle held until finalization). The listening
5+
// socket is a borrowed parameter, so the ONLY leak is `conn`.
6+
static class SocketAcceptLeak
7+
{
8+
static bool Serve(Socket listener)
9+
{
10+
var conn = listener.Accept(); // fresh owned Socket -> OWN001 (never disposed)
11+
return conn.Connected; // used, but never disposed -> leak
12+
}
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// OwnLang model of the Socket.Accept() accept-loop leak (owned-API tranche). The accepted Socket
2+
// is a fresh owned IDisposable, used and never released -> OWN001. See notes.md.
3+
module Corpus
4+
resource Conn {
5+
acquire accept
6+
release dispose
7+
kind "disposable"
8+
emit_type "Socket"
9+
emit_acquire "{args}.Accept()"
10+
emit_release "{0}.Dispose()"
11+
}
12+
fn Serve(listener: int) {
13+
let conn = acquire Conn(listener); // var conn = listener.Accept()
14+
// conn used (conn.Connected); no `release conn;` -> OWN001
15+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OWN001
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# socket-accept-leak
2+
3+
`Socket.Accept()` returns a fresh **owned** `Socket` the caller must dispose; dropping it leaks the
4+
accepted connection. Covers the `Socket.Accept` branch of the accept-loop owned-API rule (sibling
5+
to `tcplistener-accept-leak`).
6+
7+
- **before.cs** — `var conn = listener.Accept();` used and never disposed → `OWN001`.
8+
- **after.cs** — `using var conn = …` → clean.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Net.Sockets;
2+
3+
static class AcceptSocketLeak
4+
{
5+
static bool Serve(TcpListener listener)
6+
{
7+
using var sock = listener.AcceptSocket(); // disposed at scope exit -> clean
8+
return sock.Connected;
9+
}
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Net.Sockets;
2+
3+
// A Socket returned by TcpListener.AcceptSocket() is a fresh owned IDisposable the caller must
4+
// dispose; dropping it leaks the accepted connection. The listener is a borrowed parameter, so the
5+
// ONLY leak is `sock`.
6+
static class AcceptSocketLeak
7+
{
8+
static bool Serve(TcpListener listener)
9+
{
10+
var sock = listener.AcceptSocket(); // fresh owned Socket -> OWN001 (never disposed)
11+
return sock.Connected; // used, but never disposed -> leak
12+
}
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// OwnLang model of the TcpListener.AcceptSocket() accept-loop leak (owned-API tranche). The
2+
// accepted Socket is a fresh owned IDisposable, used and never released -> OWN001. See notes.md.
3+
module Corpus
4+
resource Sock {
5+
acquire accept
6+
release dispose
7+
kind "disposable"
8+
emit_type "Socket"
9+
emit_acquire "{args}.AcceptSocket()"
10+
emit_release "{0}.Dispose()"
11+
}
12+
fn Serve(listener: int) {
13+
let sock = acquire Sock(listener); // var sock = listener.AcceptSocket()
14+
// sock used (sock.Connected); no `release sock;` -> OWN001
15+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OWN001
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# tcplistener-acceptsocket-leak
2+
3+
`TcpListener.AcceptSocket()` returns a fresh **owned** `Socket` the caller must dispose; dropping it
4+
leaks the accepted connection. Covers the `TcpListener.AcceptSocket` branch of the accept-loop
5+
owned-API rule (sibling to `tcplistener-accept-leak`, which covers `AcceptTcpClient`).
6+
7+
- **before.cs** — `var sock = listener.AcceptSocket();` used and never disposed → `OWN001`.
8+
- **after.cs** — `using var sock = …` → clean.

0 commit comments

Comments
 (0)