Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions corpus/real-world/socket-accept-leak/after.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Net.Sockets;

static class SocketAcceptLeak
{
static bool Serve(Socket listener)
{
using var conn = listener.Accept(); // disposed at scope exit -> clean
return conn.Connected;
}
}
13 changes: 13 additions & 0 deletions corpus/real-world/socket-accept-leak/before.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Net.Sockets;

// A Socket returned by Socket.Accept() is a fresh owned IDisposable the caller must dispose;
// dropping it leaks the accepted connection (handle held until finalization). The listening
// socket is a borrowed parameter, so the ONLY leak is `conn`.
static class SocketAcceptLeak
{
static bool Serve(Socket listener)
{
var conn = listener.Accept(); // fresh owned Socket -> OWN001 (never disposed)
return conn.Connected; // used, but never disposed -> leak
}
}
15 changes: 15 additions & 0 deletions corpus/real-world/socket-accept-leak/case.own
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// OwnLang model of the Socket.Accept() accept-loop leak (owned-API tranche). The accepted Socket
// is a fresh owned IDisposable, used and never released -> OWN001. See notes.md.
module Corpus
resource Conn {
acquire accept
release dispose
kind "disposable"
emit_type "Socket"
emit_acquire "{args}.Accept()"
emit_release "{0}.Dispose()"
}
fn Serve(listener: int) {
let conn = acquire Conn(listener); // var conn = listener.Accept()
// conn used (conn.Connected); no `release conn;` -> OWN001
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OWN001
8 changes: 8 additions & 0 deletions corpus/real-world/socket-accept-leak/notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# socket-accept-leak

`Socket.Accept()` returns a fresh **owned** `Socket` the caller must dispose; dropping it leaks the
accepted connection. Covers the `Socket.Accept` branch of the accept-loop owned-API rule (sibling
to `tcplistener-accept-leak`).

- **before.cs** — `var conn = listener.Accept();` used and never disposed → `OWN001`.
- **after.cs** — `using var conn = …` → clean.
11 changes: 11 additions & 0 deletions corpus/real-world/tcplistener-accept-leak/after.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Net.Sockets;

// FIX: own the accepted client for the scope with `using`, so it is disposed on every exit path.
static class AcceptLeak
{
static bool Serve(TcpListener listener)
{
using var client = listener.AcceptTcpClient(); // disposed at scope exit -> clean
return client.Connected;
}
}
14 changes: 14 additions & 0 deletions corpus/real-world/tcplistener-accept-leak/before.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Net.Sockets;

// A TcpClient returned by TcpListener.AcceptTcpClient() is a fresh owned IDisposable the caller
// must dispose; dropping it leaks the accepted connection (the socket handle is held until
// finalization) — a classic accept-loop server leak. The listener is a borrowed parameter, so the
// ONLY leak is `client`.
static class AcceptLeak
{
static bool Serve(TcpListener listener)
{
var client = listener.AcceptTcpClient(); // fresh owned TcpClient -> OWN001 (never disposed)
return client.Connected; // used, but never disposed -> leak
}
}
18 changes: 18 additions & 0 deletions corpus/real-world/tcplistener-accept-leak/case.own
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// OwnLang model of the canonical TCP accept-loop leak (owned-API tranche). A TcpClient from
// TcpListener.AcceptTcpClient() is a fresh owned IDisposable the caller must dispose; here it is
// accepted, used, and never released — the generic OWN001 leak. The listener is a borrowed
// parameter and the client does not escape, so it stays tracked. Sibling accept members
// (Socket.Accept, TcpListener.AcceptSocket) are recognised the same way. See notes.md.
module Corpus
resource Client {
acquire accept
release dispose
kind "disposable"
emit_type "TcpClient"
emit_acquire "{args}.AcceptTcpClient()"
emit_release "{0}.Dispose()"
}
fn Serve(listener: int) {
let client = acquire Client(listener); // var client = listener.AcceptTcpClient()
// client used (client.Connected); no `release client;` — never disposed (OWN001)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OWN001
15 changes: 15 additions & 0 deletions corpus/real-world/tcplistener-accept-leak/notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# tcplistener-accept-leak

`TcpListener.AcceptTcpClient()` returns a fresh **owned** `TcpClient` the caller must dispose.
Dropping it leaks the accepted connection — the socket handle stays open until finalization, the
classic accept-loop server resource leak.

- **before.cs** — `var client = listener.AcceptTcpClient();` used and never disposed → `OWN001`.
The listener is a borrowed parameter, so the only leak is the client.
- **after.cs** — `using var client = …` disposes it on every path → clean.

Recognised by the extractor's `IsOwningFactory` (owned-API tranche): an instance "accept" member
matched by the concrete BCL receiver type + method name (`Socket.Accept`,
`TcpListener.AcceptSocket`, `TcpListener.AcceptTcpClient` in `System.Net.Sockets`) with the result
pinned to `IDisposable`, so the async variants (`AcceptTcpClientAsync` → `Task`/`ValueTask`) are
excluded.
10 changes: 10 additions & 0 deletions corpus/real-world/tcplistener-acceptsocket-leak/after.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Net.Sockets;

static class AcceptSocketLeak
{
static bool Serve(TcpListener listener)
{
using var sock = listener.AcceptSocket(); // disposed at scope exit -> clean
return sock.Connected;
}
}
13 changes: 13 additions & 0 deletions corpus/real-world/tcplistener-acceptsocket-leak/before.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Net.Sockets;

// A Socket returned by TcpListener.AcceptSocket() is a fresh owned IDisposable the caller must
// dispose; dropping it leaks the accepted connection. The listener is a borrowed parameter, so the
// ONLY leak is `sock`.
static class AcceptSocketLeak
{
static bool Serve(TcpListener listener)
{
var sock = listener.AcceptSocket(); // fresh owned Socket -> OWN001 (never disposed)
return sock.Connected; // used, but never disposed -> leak
}
}
15 changes: 15 additions & 0 deletions corpus/real-world/tcplistener-acceptsocket-leak/case.own
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// OwnLang model of the TcpListener.AcceptSocket() accept-loop leak (owned-API tranche). The
// accepted Socket is a fresh owned IDisposable, used and never released -> OWN001. See notes.md.
module Corpus
resource Sock {
acquire accept
release dispose
kind "disposable"
emit_type "Socket"
emit_acquire "{args}.AcceptSocket()"
emit_release "{0}.Dispose()"
}
fn Serve(listener: int) {
let sock = acquire Sock(listener); // var sock = listener.AcceptSocket()
// sock used (sock.Connected); no `release sock;` -> OWN001
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OWN001
8 changes: 8 additions & 0 deletions corpus/real-world/tcplistener-acceptsocket-leak/notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# tcplistener-acceptsocket-leak

`TcpListener.AcceptSocket()` returns a fresh **owned** `Socket` the caller must dispose; dropping it
leaks the accepted connection. Covers the `TcpListener.AcceptSocket` branch of the accept-loop
owned-API rule (sibling to `tcplistener-accept-leak`, which covers `AcceptTcpClient`).

- **before.cs** — `var sock = listener.AcceptSocket();` used and never disposed → `OWN001`.
- **after.cs** — `using var sock = …` → clean.
16 changes: 16 additions & 0 deletions frontend/roslyn/OwnSharp.Extractor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,22 @@
&& ImplementsSystemDataInterface(sym.ContainingType, "IDbConnection")
&& ImplementsSystemDataInterface(sym.ReturnType, "IDbTransaction"))))
return true;
// Network "accept" owned factories — a server that accepts a connection OWNS the returned
// client/socket and must dispose it; dropping it leaks the accepted connection (the handle is
// held until finalization), a classic accept-loop server leak. INSTANCE methods, matched by the
// concrete BCL receiver type + method name (Socket / TcpListener are not subclassed in practice,
// like the File branch) with the result's IDisposable pinned, so the async variants
// (AcceptAsync / AcceptTcpClientAsync -> Task/ValueTask, not IDisposable) are excluded:
// * Socket.Accept() -> a new connected Socket the caller must dispose
// * TcpListener.AcceptSocket() -> a new connected Socket the caller must dispose
// * TcpListener.AcceptTcpClient() -> a new TcpClient the caller must dispose
if (!AnyDisposableArgument(i, model)
&& ImplementsIDisposable(sym.ReturnType)
&& ((sym.Name == "Accept" && sym.ContainingType is { Name: "Socket" })
|| (sym.Name is "AcceptSocket" or "AcceptTcpClient"
&& sym.ContainingType is { Name: "TcpListener" }))
&& IsInNamespace(sym.ContainingType, "System", "Net", "Sockets"))
return true;
return false;
}

Expand Down Expand Up @@ -3021,7 +3037,7 @@
.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)
.Where(p => p.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
.ToList();
var refNames = new HashSet<string>(tpa.Select(Path.GetFileName), StringComparer.OrdinalIgnoreCase);

Check warning on line 3040 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / P-014 Tier B — external reference resolution (--ref-dir)

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 3040 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / own-check SARIF -> GitHub code scanning (dog-food)

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 3040 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / P-014 Tier B — external reference resolution (--ref-dir)

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 3040 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / own-check SARIF -> GitHub code scanning (dog-food)

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 3040 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / own-check repo scan (github + msbuild) + composite action

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 3040 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / own-check repo scan (github + msbuild) + composite action

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 3040 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / C# leak extractor (Roslyn) -> OwnIR -> core

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.

Check warning on line 3040 in frontend/roslyn/OwnSharp.Extractor/Program.cs

View workflow job for this annotation

GitHub Actions / C# leak extractor (Roslyn) -> OwnIR -> core

Argument of type 'IEnumerable<string?>' cannot be used for parameter 'collection' of type 'IEnumerable<string>' in 'HashSet<string>.HashSet(IEnumerable<string> collection, IEqualityComparer<string>? comparer)' due to differences in the nullability of reference types.
var references = tpa.Select(p => (MetadataReference)MetadataReference.CreateFromFile(p)).ToList();
// P-004 WPF profile: widen the reference set with assemblies named by the
// OWN_EXTRA_REF_DIRS env var (colon-separated dirs) — e.g. the WindowsDesktop ref
Expand Down
Loading