diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DisposableFieldsShouldBeDisposedTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DisposableFieldsShouldBeDisposedTests.cs index f7460cb30f..68cb85b41a 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DisposableFieldsShouldBeDisposedTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DisposableFieldsShouldBeDisposedTests.cs @@ -3739,6 +3739,95 @@ public async ValueTask DisposeAsync() }.RunAsync(); } + [Fact] + public async Task DisposeCoreAsync_Override_NoDiagnosticAsync() + { + await new VerifyCS.Test + { + ReferenceAssemblies = AdditionalMetadataReferences.DefaultWithAsyncInterfaces, + TestCode = @" +using System; +using System.Threading.Tasks; + +class A : IAsyncDisposable +{ + private readonly object disposedValueLock = new object(); + private bool disposedValue; + private readonly Inner innerA; + + public A() + { + innerA = new Inner(); + } + + protected virtual async ValueTask DisposeCoreAsync() + { + lock (disposedValueLock) + { + if (disposedValue) + { + return; + } + + disposedValue = true; + } + + await innerA.DisposeAsync().ConfigureAwait(false); + } + + public ValueTask DisposeAsync() + { + return default(ValueTask); + } +} + +class B : A +{ + private readonly object disposedValueLock = new object(); + private bool disposedValue; + + // Newly, we want to test that no diagnostic is reported on this line. + private readonly Inner innerB; + + public B() : base() + { + innerB = new Inner(); + } + + protected override async ValueTask DisposeCoreAsync() + { + lock (disposedValueLock) + { + if (disposedValue) + { + return; + } + + disposedValue = true; + } + + await innerB.DisposeAsync().ConfigureAwait(false); + + await base.DisposeCoreAsync().ConfigureAwait(false); + } +} + +// Declares an IAsyncDisposable type. +class Inner : IAsyncDisposable +{ + public Inner() + { + } + + public ValueTask DisposeAsync() + { + return default(ValueTask); + } +} +" + }.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 2879a9f4d2..2755200dc8 100644 --- a/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs +++ b/src/Utilities/Compiler/Extensions/IMethodSymbolExtensions.cs @@ -301,13 +301,13 @@ private static bool HasOverriddenDisposeCoreAsyncMethodSignature(this IMethodSym } /// - /// Checks if the given method has the signature "virtual ValueTask DisposeCoreAsync()" or "virtual ValueTask DisposeAsyncCore()". + /// Checks if the given method has the signature "{virtual|override} ValueTask DisposeCoreAsync()" or "{virtual|override} ValueTask DisposeAsyncCore()". /// - private static bool HasVirtualDisposeCoreAsyncMethodSignature(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? valueTask) + private static bool HasVirtualOrOverrideDisposeCoreAsyncMethodSignature(this IMethodSymbol method, [NotNullWhen(returnValue: true)] INamedTypeSymbol? valueTask) { return (method.Name == "DisposeAsyncCore" || method.Name == "DisposeCoreAsync") && method.MethodKind == MethodKind.Ordinary && - method.IsVirtual && + (method.IsVirtual || method.IsOverride) && SymbolEqualityComparer.Default.Equals(method.ReturnType, valueTask) && method.Parameters.Length == 0; } @@ -363,7 +363,7 @@ public static DisposeMethodKind GetDisposeMethodKind( { return DisposeMethodKind.DisposeCoreAsync; } - else if (method.HasVirtualDisposeCoreAsyncMethodSignature(valueTask)) + else if (method.HasVirtualOrOverrideDisposeCoreAsyncMethodSignature(valueTask)) { return DisposeMethodKind.DisposeCoreAsync; }