Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't emit CA1849 for DbContext methods #6719

Merged
merged 7 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
public sealed class UseAsyncMethodInAsyncContext : DiagnosticAnalyzer
{
internal const string RuleId = "CA1849";
internal const string AsyncMethodKeyName = "AsyncMethodName";
internal const string MandatoryAsyncSuffix = "Async";

private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(UseAsyncMethodInAsyncContextTitle));
Expand Down Expand Up @@ -56,41 +55,43 @@
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(context =>
{
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation);
ConcurrentDictionary<string, INamedTypeSymbol> syncBlockingTypes = new();
GetTypeAndAddToDictionary("Task", WellKnownTypeNames.SystemThreadingTasksTask, syncBlockingTypes, context.Compilation);
GetTypeAndAddToDictionary("TaskGeneric", WellKnownTypeNames.SystemThreadingTasksTask1, syncBlockingTypes, context.Compilation);
GetTypeAndAddToDictionary("ValueTask", WellKnownTypeNames.SystemThreadingTasksValueTask, syncBlockingTypes, context.Compilation);
GetTypeAndAddToDictionary("IAsyncEnumerableGeneric", WellKnownTypeNames.SystemCollectionsGenericIAsyncEnumerable1, syncBlockingTypes, context.Compilation);
GetTypeAndAddToDictionary("AsyncMethodBuilderAttribute", WellKnownTypeNames.SystemRuntimeCompilerServicesAsyncMethodBuilderAttribute, syncBlockingTypes, context.Compilation);
GetTypeAndAddToDictionary("Task", WellKnownTypeNames.SystemThreadingTasksTask, syncBlockingTypes, wellKnownTypeProvider);
GetTypeAndAddToDictionary("TaskGeneric", WellKnownTypeNames.SystemThreadingTasksTask1, syncBlockingTypes, wellKnownTypeProvider);
GetTypeAndAddToDictionary("ValueTask", WellKnownTypeNames.SystemThreadingTasksValueTask, syncBlockingTypes, wellKnownTypeProvider);
GetTypeAndAddToDictionary("IAsyncEnumerableGeneric", WellKnownTypeNames.SystemCollectionsGenericIAsyncEnumerable1, syncBlockingTypes, wellKnownTypeProvider);
GetTypeAndAddToDictionary("AsyncMethodBuilderAttribute", WellKnownTypeNames.SystemRuntimeCompilerServicesAsyncMethodBuilderAttribute, syncBlockingTypes, wellKnownTypeProvider);

List<SyncBlockingSymbol> syncBlockingSymbols = new();
GetSymbolAndAddToList("Wait", WellKnownTypeNames.SystemThreadingTasksTask, SymbolKind.Method, syncBlockingSymbols, context.Compilation);
GetSymbolAndAddToList("WaitAll", WellKnownTypeNames.SystemThreadingTasksTask, SymbolKind.Method, syncBlockingSymbols, context.Compilation);
GetSymbolAndAddToList("WaitAny", WellKnownTypeNames.SystemThreadingTasksTask, SymbolKind.Method, syncBlockingSymbols, context.Compilation);
GetSymbolAndAddToList("Result", WellKnownTypeNames.SystemThreadingTasksTask1, SymbolKind.Property, syncBlockingSymbols, context.Compilation);
GetSymbolAndAddToList("Result", WellKnownTypeNames.SystemThreadingTasksValueTask, SymbolKind.Property, syncBlockingSymbols, context.Compilation);
GetSymbolAndAddToList("GetResult", WellKnownTypeNames.SystemRuntimeCompilerServicesTaskAwaiter, SymbolKind.Method, syncBlockingSymbols, context.Compilation);
GetSymbolAndAddToList("GetResult", WellKnownTypeNames.SystemRuntimeCompilerServicesValueTaskAwaiter, SymbolKind.Method, syncBlockingSymbols, context.Compilation);
GetSymbolAndAddToList("Sleep", WellKnownTypeNames.SystemThreadingThread, SymbolKind.Method, syncBlockingSymbols, context.Compilation);

if (!syncBlockingTypes.Any())
GetSymbolAndAddToList("Wait", WellKnownTypeNames.SystemThreadingTasksTask, SymbolKind.Method, syncBlockingSymbols, wellKnownTypeProvider);
GetSymbolAndAddToList("WaitAll", WellKnownTypeNames.SystemThreadingTasksTask, SymbolKind.Method, syncBlockingSymbols, wellKnownTypeProvider);
GetSymbolAndAddToList("WaitAny", WellKnownTypeNames.SystemThreadingTasksTask, SymbolKind.Method, syncBlockingSymbols, wellKnownTypeProvider);
GetSymbolAndAddToList("Result", WellKnownTypeNames.SystemThreadingTasksTask1, SymbolKind.Property, syncBlockingSymbols, wellKnownTypeProvider);
GetSymbolAndAddToList("Result", WellKnownTypeNames.SystemThreadingTasksValueTask, SymbolKind.Property, syncBlockingSymbols, wellKnownTypeProvider);
GetSymbolAndAddToList("GetResult", WellKnownTypeNames.SystemRuntimeCompilerServicesTaskAwaiter, SymbolKind.Method, syncBlockingSymbols, wellKnownTypeProvider);
GetSymbolAndAddToList("GetResult", WellKnownTypeNames.SystemRuntimeCompilerServicesValueTaskAwaiter, SymbolKind.Method, syncBlockingSymbols, wellKnownTypeProvider);
GetSymbolAndAddToList("Sleep", WellKnownTypeNames.SystemThreadingThread, SymbolKind.Method, syncBlockingSymbols, wellKnownTypeProvider);

if (syncBlockingTypes.IsEmpty)
{
return;
}

if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemObsoleteAttribute, out INamedTypeSymbol? systemObsoleteAttribute))
if (!wellKnownTypeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemObsoleteAttribute, out INamedTypeSymbol? systemObsoleteAttribute))
{
return;
}

ImmutableArray<IMethodSymbol> excludedMethods = GetExcludedMethods(wellKnownTypeProvider);
context.RegisterOperationAction(context =>
{
if (IsInTaskReturningMethodOrDelegate(context, syncBlockingTypes))
{
if (context.Operation is IInvocationOperation invocationOperation)
{
var methodSymbol = invocationOperation.TargetMethod;
if (InspectAndReportBlockingMemberAccess(context, methodSymbol, syncBlockingSymbols, SymbolKind.Method))
if (excludedMethods.Contains(methodSymbol.OriginalDefinition, SymbolEqualityComparer.Default) || InspectAndReportBlockingMemberAccess(context, methodSymbol, syncBlockingSymbols, SymbolKind.Method))
{
// Don't return double-diagnostics.
return;
Expand Down Expand Up @@ -162,29 +163,45 @@
public ISymbol Value { get; set; }
}

private static void GetTypeAndAddToDictionary(string key, string typeName, ConcurrentDictionary<string, INamedTypeSymbol> syncBlockingTypes, Compilation compilation)
private static void GetTypeAndAddToDictionary(string key, string typeName, ConcurrentDictionary<string, INamedTypeSymbol> syncBlockingTypes, WellKnownTypeProvider wellKnownTypeProvider)
{
if (compilation.TryGetOrCreateTypeByMetadataName(typeName, out INamedTypeSymbol? typeValue))
if (wellKnownTypeProvider.TryGetOrCreateTypeByMetadataName(typeName, out INamedTypeSymbol? typeValue))
{
syncBlockingTypes.AddOrUpdate(key, typeValue, (k, v) => v);
}
}

private static void GetSymbolAndAddToList(string symbolName, string Namespace, SymbolKind kind, List<SyncBlockingSymbol> syncBlockingSymbols, Compilation compilation)
private static void GetSymbolAndAddToList(string symbolName, string metadataName, SymbolKind kind, List<SyncBlockingSymbol> syncBlockingSymbols, WellKnownTypeProvider wellKnownTypeProvider)
{
if (compilation.TryGetOrCreateTypeByMetadataName(Namespace, out INamedTypeSymbol? typeValue))
if (wellKnownTypeProvider.TryGetOrCreateTypeByMetadataName(metadataName, out INamedTypeSymbol? typeValue))
{
ISymbol? symbolValue = typeValue
.GetMembers(symbolName)
.FirstOrDefault(s => s.Kind == kind);

if (symbolValue is not null)
{
syncBlockingSymbols.Add(new SyncBlockingSymbol(symbolName, Namespace, kind, symbolValue));
syncBlockingSymbols.Add(new SyncBlockingSymbol(symbolName, metadataName, kind, symbolValue));
}
}
}

private static ImmutableArray<IMethodSymbol> GetExcludedMethods(WellKnownTypeProvider wellKnownTypeProvider)
{
var methodsBuilder = ImmutableArray.CreateBuilder<IMethodSymbol>();
if (wellKnownTypeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftEntityFrameworkCoreDbContext, out INamedTypeSymbol? dbContextType))
{
foreach (var method in dbContextType.GetMembers().OfType<IMethodSymbol>())
{
if (method.Name is "Add" or "AddRange")
{
methodsBuilder.Add(method);
}
}
}

Check failure on line 201 in src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseAsyncMethodInAsyncContext.cs

View check run for this annotation

Azure Pipelines / roslyn-analyzers-CI (Ubuntu Release)

src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseAsyncMethodInAsyncContext.cs#L201

src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseAsyncMethodInAsyncContext.cs(201,13): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement

Check failure on line 201 in src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseAsyncMethodInAsyncContext.cs

View check run for this annotation

Azure Pipelines / roslyn-analyzers-CI (Ubuntu Debug)

src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseAsyncMethodInAsyncContext.cs#L201

src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseAsyncMethodInAsyncContext.cs(201,13): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
return methodsBuilder.ToImmutable();
}

/// <summary>
/// Determines whether the given method has parameters to cover all the parameter types in another method.
/// </summary>
Expand All @@ -195,7 +212,7 @@
/// </returns>
private static bool HasSupersetOfParameterTypes(IMethodSymbol candidateMethod, IMethodSymbol baselineMethod)
{
return candidateMethod.Parameters.All(candidateParameter => baselineMethod.Parameters.Any(baselineParameter => baselineParameter.Type?.Equals(candidateParameter.Type) ?? false));
return candidateMethod.Parameters.All(candidateParameter => candidateParameter.HasExplicitDefaultValue || baselineMethod.Parameters.Any(baselineParameter => baselineParameter.Type?.Equals(candidateParameter.Type) ?? false));
}

private static bool HasAsyncCompatibleReturnType(IMethodSymbol methodSymbol, ConcurrentDictionary<string, INamedTypeSymbol> syncBlockingTypes)
Expand Down Expand Up @@ -276,4 +293,4 @@
return false;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using Test.Utilities;
Expand All @@ -15,6 +16,8 @@ namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests
{
public class UseAsyncMethodInAsyncContextTests
{
private static readonly ImmutableArray<PackageIdentity> EntityFrameworkPackages = ImmutableArray.Create(new PackageIdentity("Microsoft.EntityFrameworkCore", "7.0.8"));

[Fact]
public async Task TaskWaitInTaskReturningMethodGeneratesWarning()
{
Expand Down Expand Up @@ -1243,6 +1246,64 @@ End Module
await CreateVBTestAndRunAsync(testVB);
}

[Fact]
[WorkItem(6684, "https://github.com/dotnet/roslyn-analyzers/issues/6684")]
public Task DbContextAdd_NoDiagnostic()
{
return new VerifyCS.Test
{
TestCode = @"
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

class Test {
public async Task RunAsync(DbContext ctx) {
ctx.Add(1);
}
}",
ReferenceAssemblies = ReferenceAssemblies.Net.Net70.WithPackages(EntityFrameworkPackages)
}.RunAsync();
}

[Fact]
[WorkItem(6684, "https://github.com/dotnet/roslyn-analyzers/issues/6684")]
public Task DbContextAddRange_NoDiagnostic()
{
return new VerifyCS.Test
{
TestCode = @"
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

class Test {
public async Task RunAsync(DbContext ctx) {
ctx.AddRange(1, 2);
}
}",
ReferenceAssemblies = ReferenceAssemblies.Net.Net70.WithPackages(EntityFrameworkPackages)
}.RunAsync();
}

[Fact]
[WorkItem(6684, "https://github.com/dotnet/roslyn-analyzers/issues/6684")]
public Task DbContextFactoryCreateDbContext_Diagnostic()
{
return new VerifyCS.Test
{
TestCode = @"
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

class Test {
public async Task RunAsync(IDbContextFactory<DbContext> factory) {
var context = {|#0:factory.CreateDbContext()|};
}
}",
ExpectedDiagnostics = { new DiagnosticResult(UseAsyncMethodInAsyncContext.Descriptor).WithLocation(0).WithArguments("IDbContextFactory<DbContext>.CreateDbContext()", "IDbContextFactory<DbContext>.CreateDbContextAsync(CancellationToken)") },
ReferenceAssemblies = ReferenceAssemblies.Net.Net70.WithPackages(EntityFrameworkPackages)
}.RunAsync();
}

private static async Task CreateCSTestAndRunAsync(string testCS)
{
var csTestVerify = new VerifyCS.Test
Expand Down
1 change: 1 addition & 0 deletions src/Utilities/Compiler/WellKnownTypeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ internal static class WellKnownTypeNames
public const string MicrosoftCodeAnalysisVisualBasicExtensions = "Microsoft.CodeAnalysis.VisualBasicExtensions";
public const string MicrosoftCodeAnalysisVisualBasicVisualBasicCompilation = "Microsoft.CodeAnalysis.VisualBasic.VisualBasicCompilation";
public const string MicrosoftCodeAnalysisVisualBasicVisualBasicExtensions = "Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions";
public const string MicrosoftEntityFrameworkCoreDbContext = "Microsoft.EntityFrameworkCore.DbContext";
public const string MicrosoftEntityFrameworkCoreEntityFrameworkQueryableExtensions = "Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions";
public const string MicrosoftEntityFrameworkCoreRelationalQueryableExtensions = "Microsoft.EntityFrameworkCore.RelationalQueryableExtensions";
public const string MicrosoftExtensionsLoggingILogger = "Microsoft.Extensions.Logging.ILogger";
Expand Down
Loading