Dispose Before Scope
ID |
csharp.dispose_before_scope |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
CWE:772, idisposable, reliability, resource-leak |
Description
Reports local IDisposable variables that exit their enclosing method without a
matching Dispose() call. Leaking an unmanaged resource (file handles, sockets,
database connections) keeps it allocated until the finaliser runs, which on hot
paths is enough to exhaust the process handle table. Passing the variable to the
constructor of another disposable only counts as ownership transfer when that
wrapper itself escapes the method (returned, assigned to a field, or stored in a
local that is itself returned or disposed) — a wrapper built only to be chained
into a further call and discarded does not prove anything was disposed.
Rationale
The IDisposable contract is the .NET runtime’s only deterministic mechanism for
releasing native resources. Forgetting to call Dispose() defers the cleanup to
the GC, which may take seconds or minutes; under load, the leak is fast enough to
break the program before the GC notices.
public class Reader
{
public string Bad(string path)
{
var stream = new FileStream(path, FileMode.Open); // FLAW
return new StreamReader(stream).ReadToEnd();
}
public string OkUsing(string path)
{
using (var stream = new FileStream(path, FileMode.Open)) // OK
{
return new StreamReader(stream).ReadToEnd();
}
}
public string OkUsingDecl(string path)
{
using var stream = new FileStream(path, FileMode.Open); // OK
return new StreamReader(stream).ReadToEnd();
}
public string OkManual(string path)
{
var stream = new FileStream(path, FileMode.Open); // OK, explicit Dispose
try { return new StreamReader(stream).ReadToEnd(); }
finally { stream.Dispose(); }
}
public HttpClient OkOwnershipTransfer()
{
var handler = new HttpClientHandler(); // OK, wrapped by a disposable owner
return new HttpClient(handler);
}
public string BadDiscardedWrapper()
{
var handler = new HttpClientHandler(); // FLAW, the HttpClient wrapping it is discarded
return new HttpClient(handler).ToString();
}
}
Remediation
Wrap the declaration in a using statement or the using var declaration form so
the compiler emits the matching Dispose() call. When the disposable must outlive
the method, transfer ownership explicitly (assign to a field, return it, pass it to
the constructor of a disposable wrapper that takes over disposal, or pass it to a
registry that documents disposal responsibility).