diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DisposableFieldsShouldBeDisposedTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DisposableFieldsShouldBeDisposedTests.cs index 4980031eef..f7460cb30f 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DisposableFieldsShouldBeDisposedTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DisposableFieldsShouldBeDisposedTests.cs @@ -3650,6 +3650,95 @@ End Class" }.RunAsync(); } + [Fact] + public async Task DisposeCoreAsync_NoDiagnosticAsync() + { + await new VerifyCS.Test + { + ReferenceAssemblies = AdditionalMetadataReferences.DefaultWithAsyncInterfaces, + TestCode = @" +using System; +using System.Threading.Tasks; + +class A : IAsyncDisposable +{ + public ValueTask DisposeAsync() + { + return default(ValueTask); + } +} + +// No diagnostic for DisposeCoreAsync. +class B : IAsyncDisposable +{ + private readonly object disposedValueLock = new object(); + private bool disposedValue; + private readonly A a; + + public B() + { + a = new A(); + } + + protected virtual async ValueTask DisposeCoreAsync() + { + lock (disposedValueLock) + { + if (disposedValue) + { + return; + } + + disposedValue = true; + } + + await a.DisposeAsync().ConfigureAwait(false); + } + + public async ValueTask DisposeAsync() + { + await DisposeCoreAsync().ConfigureAwait(false); + GC.SuppressFinalize(this); + } +} + +// No diagnostic for DisposeAsyncCore. +class C : IAsyncDisposable +{ + private readonly object disposedValueLock = new object(); + private bool disposedValue; + private readonly A a; + + public C() + { + a = new A(); + } + + protected virtual async ValueTask DisposeAsyncCore() + { + lock (disposedValueLock) + { + if (disposedValue) + { + return; + } + + disposedValue = true; + } + + await a.DisposeAsync().ConfigureAwait(false); + } + + public async ValueTask DisposeAsync() + { + await DisposeAsyncCore().ConfigureAwait(false); + GC.SuppressFinalize(this); + } +} +" + }.RunAsync(); + } + [Fact, WorkItem(5099, "https://github.com/dotnet/roslyn-analyzers/issues/5099")] public async Task OwnDisposableButDoesNotOverrideDisposableMember_Dispose() { diff --git a/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs b/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs index aa91ed936f..2879a9f4d2 100644 --- a/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs +++ b/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs @@ -288,11 +288,11 @@ private static bool HasDisposeAsyncMethodSignature(this IMethodSymbol method, } /// - /// Checks if the given method has the signature "override Task DisposeCoreAsync(bool)". + /// Checks if the given method has the signature "override Task DisposeCoreAsync(bool)" or "override Task DisposeAsyncCore(bool)". /// private static bool HasOverriddenDisposeCoreAsyncMethodSignature(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? task) { - return method.Name == "DisposeCoreAsync" && + return (method.Name == "DisposeAsyncCore" || method.Name == "DisposeCoreAsync") && method.MethodKind == MethodKind.Ordinary && method.IsOverride && SymbolEqualityComparer.Default.Equals(method.ReturnType, task) && @@ -300,6 +300,18 @@ private static bool HasOverriddenDisposeCoreAsyncMethodSignature(this IMethodSym method.Parameters[0].Type.SpecialType == SpecialType.System_Boolean; } + /// + /// Checks if the given method has the signature "virtual ValueTask DisposeCoreAsync()" or "virtual ValueTask DisposeAsyncCore()". + /// + private static bool HasVirtualDisposeCoreAsyncMethodSignature(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? valueTask) + { + return (method.Name == "DisposeAsyncCore" || method.Name == "DisposeCoreAsync") && + method.MethodKind == MethodKind.Ordinary && + method.IsVirtual && + SymbolEqualityComparer.Default.Equals(method.ReturnType, valueTask) && + method.Parameters.Length == 0; + } + /// /// Gets the for the given method. /// @@ -351,6 +363,10 @@ public static DisposeMethodKind GetDisposeMethodKind( { return DisposeMethodKind.DisposeCoreAsync; } + else if (method.HasVirtualDisposeCoreAsyncMethodSignature(valueTask)) + { + return DisposeMethodKind.DisposeCoreAsync; + } else if (method.HasDisposeCloseMethodSignature()) { return DisposeMethodKind.Close;