177 lines
7.7 KiB
Plaintext
177 lines
7.7 KiB
Plaintext
== Why is this an issue?
|
|
|
|
When writing https://learn.microsoft.com/en-us/dotnet/standard/managed-code[managed code], there is no need to worry about memory allocation or deallocation as it is taken care of by the https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection[garbage collector]. However, certain objects, such as `Bitmap`, utilize unmanaged memory for specific purposes like https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code[pointer arithmetic]. These objects may have substantial unmanaged memory footprints while having minimal managed footprints. Unfortunately, the garbage collector only recognizes the small managed footprint and does not promptly reclaim the corresponding unmanaged memory (by invoking the finalizer method of `Bitmap`) for efficiency reasons.
|
|
|
|
In addition, it's essential to manage other system resources besides memory. The operating system has limits on the number of https://en.wikipedia.org/wiki/File_descriptor[file descriptors] (e.g., `FileStream`) or https://en.wikipedia.org/wiki/Network_socket[sockets] (e.g., `WebClient`) that can remain open simultaneously. Therefore, it's crucial to `Dispose` of these resources promptly when they are no longer required, instead of relying on the garbage collector to invoke the finalizers of these objects at an unpredictable time in the future.
|
|
|
|
This rule keeps track of `private` fields and local variables of specific types that implement `IDisposable` or `IAsyncDisposable`. It identifies instances of these types that are not properly disposed, closed, aliased, returned, or passed to other methods. This applies to instances that are either directly created using the `new` operator or instantiated through a predefined list of factory methods.
|
|
|
|
Here is the list of predefined factory methods tracked by this rule:
|
|
|
|
* `System.IO.File.Create()`
|
|
* `System.IO.File.Open()`
|
|
* `System.Drawing.Image.FromFile()`
|
|
* `System.Drawing.Image.FromStream()`
|
|
|
|
=== Exceptions
|
|
|
|
`IDisposable` / `IAsyncDisposable` variables returned from a method or passed to other methods are ignored, as are local `IDisposable` / `IAsyncDisposable` objects that are initialized with other `IDisposable` / `IAsyncDisposable` objects.
|
|
|
|
[source,csharp]
|
|
----
|
|
public Stream WriteToFile(string path, string text)
|
|
{
|
|
var fs = new FileStream(path, FileMode.Open); // Compliant: it is returned
|
|
var bytes = Encoding.UTF8.GetBytes(text);
|
|
fs.Write(bytes, 0, bytes.Length);
|
|
return fs;
|
|
}
|
|
|
|
public void ReadFromStream(Stream s)
|
|
{
|
|
var sr = new StreamReader(s); // Compliant: it would close the underlying stream.
|
|
// ...
|
|
}
|
|
----
|
|
|
|
== How to fix it
|
|
|
|
It is essential to identify what kind of disposable resource variable is used to know how to fix this issue.
|
|
|
|
In the case of a disposable resource store as a member (either as field or property), it should be disposed at the same time as the class. The best way to achieve this is to follow the https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern[dispose pattern].
|
|
|
|
When creating the disposable resource for a one-time use (cases not covered by the exceptions), it should be disposed at the end of its creation scope. The easiest to ensure your resource is disposed when reaching the end of a scope is to either use https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using[the using statement or the using declaration]
|
|
|
|
=== Code examples
|
|
|
|
==== Noncompliant code example
|
|
|
|
[source,csharp,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
public class ResourceHolder
|
|
{
|
|
private FileStream fs; // Noncompliant: dispose or close are never called
|
|
|
|
public void OpenResource(string path)
|
|
{
|
|
this.fs = new FileStream(path, FileMode.Open);
|
|
}
|
|
|
|
public void WriteToFile(string path, string text)
|
|
{
|
|
var fs = new FileStream(path, FileMode.Open); // Noncompliant: not disposed, returned or initialized with another disposable object
|
|
var bytes = Encoding.UTF8.GetBytes(text);
|
|
fs.Write(bytes, 0, bytes.Length);
|
|
}
|
|
}
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,csharp,diff-id=1,diff-type=compliant]
|
|
----
|
|
public class ResourceHolder : IDisposable, IAsyncDisposable
|
|
{
|
|
private FileStream fs; // Compliant: disposed in Dispose/DisposeAsync methods
|
|
|
|
public void OpenResource(string path)
|
|
{
|
|
this.fs = new FileStream(path, FileMode.Open);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
this.fs.Dispose();
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
await fs.DisposeAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
public void WriteToFile(string path, string text)
|
|
{
|
|
using (var fs = new FileStream(path, FileMode.Open)) // Compliant: disposed at the end of the using block
|
|
{
|
|
var bytes = Encoding.UTF8.GetBytes(text);
|
|
fs.Write(bytes, 0, bytes.Length);
|
|
}
|
|
}
|
|
}
|
|
----
|
|
|
|
== Resources
|
|
|
|
=== Documentation
|
|
|
|
* https://learn.microsoft.com/en-us/dotnet/standard/managed-code[What is "managed code"?]
|
|
* https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection[Garbage collection]
|
|
* https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/finalizers[Finalizers]
|
|
* https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code[Unsafe code, pointer types, and function pointers]
|
|
* https://en.wikipedia.org/wiki/File_descriptor[File descriptor - Wiki]
|
|
* https://en.wikipedia.org/wiki/Network_socket[Network socket - Wiki]
|
|
* https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern[Dispose pattern]
|
|
** https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose[Implement a Dispose method]
|
|
** https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync[Implement a DisposeAsync method]
|
|
* https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using[using statement and using declaration]
|
|
* CWE - https://cwe.mitre.org/data/definitions/459[CWE-459 - Incomplete Cleanup]
|
|
|
|
ifdef::env-github,rspecator-view[]
|
|
|
|
'''
|
|
== Implementation Specification
|
|
(visible only on this page)
|
|
|
|
=== Message
|
|
|
|
Dispose "xxx" when it is no longer needed.
|
|
|
|
|
|
'''
|
|
== Comments And Links
|
|
(visible only on this page)
|
|
|
|
=== relates to: S2095
|
|
|
|
=== is related to: S2952
|
|
|
|
=== on 13 May 2015, 19:22:06 Ann Campbell wrote:
|
|
\[~tamas.vajk] if this rule comes from R#, please provide the R# rule key.
|
|
|
|
Also, there is the question of classes that `Dispose` of their `IDisposable` members, but not from their own `Dispose` methods. I.e. they call `Dispose` from some other, randomly-named method. Does this case merit coverage under this rule? A separate rule?
|
|
|
|
=== on 13 May 2015, 19:22:15 Ann Campbell wrote:
|
|
consulted: \http://stackoverflow.com/questions/10956140/does-a-class-need-to-implement-idisposable-when-all-members-are-explicitly-dispo
|
|
|
|
=== on 18 May 2015, 08:20:57 Tamas Vajk wrote:
|
|
\[~ann.campbell.2] I think the separate rule for "implementing IDisposable" (\http://jira.sonarsource.com/browse/RSPEC-2931) is a good idea. Let's keep it this way, we'll see if it generates loads of duplicate issues or not.
|
|
|
|
|
|
This rule is not in Resharper.
|
|
|
|
|
|
|
|
|
|
=== on 22 May 2015, 09:48:19 Tamas Vajk wrote:
|
|
LGTM
|
|
|
|
=== on 8 Jun 2015, 13:51:45 Ann Campbell wrote:
|
|
updated per SONARCSANA-129. See what you think [~tamas.vajk]
|
|
|
|
=== on 12 Jun 2015, 12:28:01 Tamas Vajk wrote:
|
|
\[~ann.campbell.2] it looks good. I added the exceptions part, could you run through it?
|
|
|
|
=== on 12 Jun 2015, 18:02:36 Ann Campbell wrote:
|
|
This begins to feel like a game of tennis. :-)
|
|
|
|
|
|
I edited "block" to "method". Double-check me, please.
|
|
|
|
=== on 15 Jun 2015, 06:28:58 Tamas Vajk wrote:
|
|
\[~ann.campbell.2] It looks good.
|
|
|
|
=== on 5 Feb 2021, 17:35:39 Čaba Šagi wrote:
|
|
Beside the types covered in the description, all types implementing IDisposable should be covered as well. See https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2000[CA2000]
|
|
|
|
endif::env-github,rspecator-view[]
|