diff --git a/analyzers/its/expected/Nancy/Nancy--net452-S2930.json b/analyzers/its/expected/Nancy/Nancy--net452-S2930.json new file mode 100644 index 00000000000..9b32352c3b7 --- /dev/null +++ b/analyzers/its/expected/Nancy/Nancy--net452-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'engineDisposedCts' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/Nancy/src/Nancy/NancyEngine.cs#L98", +"region": { +"startLine": 98, +"startColumn": 13, +"endLine": 98, +"endColumn": 67 +} +} +} +] +} diff --git a/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2930.json b/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2930.json new file mode 100644 index 00000000000..9b32352c3b7 --- /dev/null +++ b/analyzers/its/expected/Nancy/Nancy--netstandard2.0-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'engineDisposedCts' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/Nancy/src/Nancy/NancyEngine.cs#L98", +"region": { +"startLine": 98, +"startColumn": 13, +"endLine": 98, +"endColumn": 67 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2930.json b/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2930.json new file mode 100644 index 00000000000..6586e02fa89 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka--netstandard2.0-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'cancellationSource' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/core/Akka/Actor/Futures.cs#L356", +"region": { +"startLine": 356, +"startColumn": 17, +"endLine": 356, +"endColumn": 67 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka.Cluster.Tests.Performance--netcoreapp3.1-S2930.json b/analyzers/its/expected/akka.net/Akka.Cluster.Tests.Performance--netcoreapp3.1-S2930.json new file mode 100644 index 00000000000..3f24ada1775 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Cluster.Tests.Performance--netcoreapp3.1-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'cts' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/core/Akka.Cluster.Tests.Performance/Startup/ClusterStartupSpec.cs#L58", +"region": { +"startLine": 58, +"startColumn": 17, +"endLine": 58, +"endColumn": 76 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka.Persistence.Sql.Common--netstandard2.0-S2930.json b/analyzers/its/expected/akka.net/Akka.Persistence.Sql.Common--netstandard2.0-S2930.json new file mode 100644 index 00000000000..52a615e23fe --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Persistence.Sql.Common--netstandard2.0-S2930.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose '_pendingRequestsCancellation' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/contrib/persistence/Akka.Persistence.Sql.Common/Journal/SqlJournal.cs#L45", +"region": { +"startLine": 45, +"startColumn": 13, +"endLine": 45, +"endColumn": 73 +} +} +}, +{ +"id": "S2930", +"message": "Dispose '_pendingRequestsCancellation' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/contrib/persistence/Akka.Persistence.Sql.Common/Snapshot/SqlSnapshotStore.cs#L53", +"region": { +"startLine": 53, +"startColumn": 13, +"endLine": 53, +"endColumn": 73 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2930.json b/analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2930.json new file mode 100644 index 00000000000..3268095c46c --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Remote.Tests.Performance--netcoreapp3.1-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'cts' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/core/Akka.Remote.Tests.Performance/Transports/RemoteMessagingThroughputSpecBase.cs#L56", +"region": { +"startLine": 56, +"startColumn": 29, +"endLine": 56, +"endColumn": 88 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2930.json b/analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2930.json new file mode 100644 index 00000000000..97aa1684a42 --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Streams--netstandard2.0-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose '_cancellation' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/core/Akka.Streams/Implementation/IO/OutputStreamSourceStage.cs#L124", +"region": { +"startLine": 124, +"startColumn": 54, +"endLine": 124, +"endColumn": 99 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2930.json b/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2930.json new file mode 100644 index 00000000000..9cc3c0a49ec --- /dev/null +++ b/analyzers/its/expected/akka.net/Akka.Tests.Performance--netcoreapp3.1-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'cancel' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/core/Akka.Tests.Performance/IO/TcpInboundOnlySpec.cs#L73", +"region": { +"startLine": 73, +"startColumn": 41, +"endLine": 73, +"endColumn": 79 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/RemotePingPong--net471-S2930.json b/analyzers/its/expected/akka.net/RemotePingPong--net471-S2930.json new file mode 100644 index 00000000000..4e2ba4ff544 --- /dev/null +++ b/analyzers/its/expected/akka.net/RemotePingPong--net471-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'cts' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/benchmark/RemotePingPong/Program.cs#L239", +"region": { +"startLine": 239, +"startColumn": 29, +"endLine": 239, +"endColumn": 88 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2930.json b/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2930.json new file mode 100644 index 00000000000..4e2ba4ff544 --- /dev/null +++ b/analyzers/its/expected/akka.net/RemotePingPong--net5.0-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'cts' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/benchmark/RemotePingPong/Program.cs#L239", +"region": { +"startLine": 239, +"startColumn": 29, +"endLine": 239, +"endColumn": 88 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2930.json b/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2930.json new file mode 100644 index 00000000000..4e2ba4ff544 --- /dev/null +++ b/analyzers/its/expected/akka.net/RemotePingPong--netcoreapp3.1-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'cts' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/benchmark/RemotePingPong/Program.cs#L239", +"region": { +"startLine": 239, +"startColumn": 29, +"endLine": 239, +"endColumn": 88 +} +} +} +] +} diff --git a/analyzers/its/expected/akka.net/Samples.Akka.AspNetCore--netcoreapp3.1-S2930.json b/analyzers/its/expected/akka.net/Samples.Akka.AspNetCore--netcoreapp3.1-S2930.json new file mode 100644 index 00000000000..91186eeb5e5 --- /dev/null +++ b/analyzers/its/expected/akka.net/Samples.Akka.AspNetCore--netcoreapp3.1-S2930.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S2930", +"message": "Dispose 'cts' when it is no longer needed.", +"location": { +"uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/akka.net/src/examples/AspNetCore/Samples.Akka.AspNetCore/Startup.cs#L56", +"region": { +"startLine": 56, +"startColumn": 25, +"endLine": 56, +"endColumn": 83 +} +} +} +] +} diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs index 8bce8b18b00..ea409602bb9 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs @@ -38,7 +38,8 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer KnownType.System_IO_StreamWriter, KnownType.System_Net_WebClient, KnownType.System_Net_Sockets_TcpClient, - KnownType.System_Net_Sockets_UdpClient); + KnownType.System_Net_Sockets_UdpClient, + KnownType.System_Threading_CancellationTokenSource); private static readonly ImmutableArray DisposableTypes = ImmutableArray.Create(KnownType.System_IDisposable, KnownType.System_IAsyncDisposable); @@ -49,7 +50,8 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer "System.IO.File.Create", "System.IO.File.Open", "System.Drawing.Image.FromFile", - "System.Drawing.Image.FromStream" + "System.Drawing.Image.FromStream", + "System.Threading.CancellationTokenSource.CreateLinkedTokenSource" }; public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs index 80bd7521330..8ef14099c73 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs @@ -474,6 +474,7 @@ public sealed partial class KnownType public static readonly KnownType System_Text_RegularExpressions_Regex = new("System.Text.RegularExpressions.Regex"); public static readonly KnownType System_Text_RegularExpressions_RegexOptions = new("System.Text.RegularExpressions.RegexOptions"); public static readonly KnownType System_Text_StringBuilder = new("System.Text.StringBuilder"); + public static readonly KnownType System_Threading_CancellationTokenSource = new("System.Threading.CancellationTokenSource"); public static readonly KnownType System_Threading_Monitor = new("System.Threading.Monitor"); public static readonly KnownType System_Threading_Mutex = new("System.Threading.Mutex"); public static readonly KnownType System_Threading_ReaderWriterLock = new("System.Threading.ReaderWriterLock"); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs index 57cfd032185..07ee68ea179 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs @@ -2,6 +2,7 @@ using System.IO; using System.Net; using System.Net.Sockets; +using System.Threading; namespace Tests.Diagnostics { @@ -109,6 +110,11 @@ public DisposableNotDisposed(FileStream fs) field_fs7 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant - even if field_fs7's type is object NoOperation(this.field_fs8); + + var tokenSource1 = new CancellationTokenSource(); // Noncompliant + var tokenSource2 = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken.None, CancellationToken.None); // Noncompliant + var tokenSource3 = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken.None, CancellationToken.None); // Compliant, disposed + tokenSource3.Dispose(); } private void Conditions(bool cond, string x)