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

Initial prototype of AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer #6643

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ CA1858 | Performance | Info | UseStartsWithInsteadOfIndexOfComparisonWithZero, [
CA1859 | Performance | Info | UseConcreteTypeAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1859)
CA1860 | Performance | Info | PreferLengthCountIsEmptyOverAnyAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860)
CA1861 | Performance | Info | AvoidConstArrays, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861)
CA1862 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862)
CA2021 | Reliability | Warning | DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021)

### Removed Rules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2054,4 +2054,13 @@ Widening and user defined conversions are not supported with generic types.</val
<data name="PreferIsEmptyOverAnyMessage" xml:space="preserve">
<value>Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance</value>
</data>
</root>
<data name="AvoidPotentiallyExpensiveCallWhenLoggingDescription" xml:space="preserve">
<value>In many situations, logging is disabled or set at a log level that will result in an unnecessary evaluation for this argument.</value>
</data>
<data name="AvoidPotentiallyExpensiveCallWhenLoggingMessage" xml:space="preserve">
<value>This argument may be expensive and evaluated unnecessarily if logging is disabled</value>
</data>
<data name="AvoidPotentiallyExpensiveCallWhenLoggingTitle" xml:space="preserve">
<value>Avoid expensive logging</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.Analyzers.Performance
{
using static MicrosoftNetCoreAnalyzersResources;

[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
internal sealed class AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor s_rule = DiagnosticDescriptorHelper.Create(
"CA1862",
AvoidPotentiallyExpensiveCallWhenLoggingTitle,
AvoidPotentiallyExpensiveCallWhenLoggingMessage,
DiagnosticCategory.Performance,
RuleLevel.IdeSuggestion,
AvoidPotentiallyExpensiveCallWhenLoggingDescription,
isPortedFxCopRule: false,
isDataflowRule: false);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterCompilationStartAction(context =>
{
var loggerMessageAttribute = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingLoggerMessageAttribute);
var loggerExtensionsSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingLoggerExtensions);
var iloggerLogMethodSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingILogger)?.GetMembers("Log").FirstOrDefault() as IMethodSymbol;
context.RegisterOperationAction(context => AnalyzeInvocationOperation(context, loggerMessageAttribute, loggerExtensionsSymbol, iloggerLogMethodSymbol), OperationKind.Invocation);
});

}

private static void AnalyzeInvocationOperation(OperationAnalysisContext context, INamedTypeSymbol? loggerMessageAttribute, INamedTypeSymbol? loggerExtensionsSymbol, IMethodSymbol? iloggerLogMethodSymbol)
{
var invocation = (IInvocationOperation)context.Operation;
if (!IsValidLoggingInvocation(invocation, loggerMessageAttribute, loggerExtensionsSymbol, iloggerLogMethodSymbol))
{
return;
}

foreach (var argument in invocation.Arguments)
{
if (!IsGoodArgument(argument))
{
context.ReportDiagnostic(argument.CreateDiagnostic(s_rule));
}
}
}

private static bool IsGoodArgument(IArgumentOperation argumentOperation)
{
if (argumentOperation.Value is ILiteralOperation or ILocalReferenceOperation)
{
return true;
}

return IsGoodArgumentRecursive(argumentOperation.Value);
}

private static bool IsGoodArgumentRecursive(IOperation operationValue)
{
if (operationValue is null)
{
return true;
}

if (operationValue is IMemberReferenceOperation memberReference)
{
return IsGoodArgumentRecursive(memberReference.Instance);
}

if (operationValue is IArrayElementReferenceOperation arrayElementReference)
{
return IsGoodArgumentRecursive(arrayElementReference.ArrayReference);
}

return false;
}

private static bool IsValidLoggingInvocation(IInvocationOperation invocation, INamedTypeSymbol? loggerMessageAttribute, INamedTypeSymbol? loggerExtensionsSymbol, IMethodSymbol? iloggerLogMethodSymbol)
{
var method = invocation.TargetMethod;
if (method.Equals(iloggerLogMethodSymbol, SymbolEqualityComparer.Default))
{
return true;
}

if (method.ContainingType.Equals(loggerExtensionsSymbol, SymbolEqualityComparer.Default))
{
return true;
}

if (loggerMessageAttribute is not null &&
method.GetAttributes().Any((att, arg) => att.AttributeClass.Equals(arg, SymbolEqualityComparer.Default), loggerMessageAttribute))
{
return true;
}

return false;
}
}
}
2 changes: 1 addition & 1 deletion src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
Design: CA2210, CA1000-CA1070
Globalization: CA2101, CA1300-CA1311
Mobility: CA1600-CA1601
Performance: HA, CA1800-CA1861
Performance: HA, CA1800-CA1862
Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405
Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2260
Naming: CA1700-CA1727
Expand Down
2 changes: 1 addition & 1 deletion src/Utilities/Compiler/WellKnownTypeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ internal static class WellKnownTypeNames
public const string MicrosoftExtensionsLoggingILogger = "Microsoft.Extensions.Logging.ILogger";
public const string MicrosoftExtensionsLoggingLoggerExtensions = "Microsoft.Extensions.Logging.LoggerExtensions";
public const string MicrosoftExtensionsLoggingLoggerMessage = "Microsoft.Extensions.Logging.LoggerMessage";
public const string MicrosoftExtensionsLoggingLoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute";
public const string MicrosoftIdentityModelTokensAudienceValidator = "Microsoft.IdentityModel.Tokens.AudienceValidator";
public const string MicrosoftIdentityModelTokensLifetimeValidator = "Microsoft.IdentityModel.Tokens.LifetimeValidator";
public const string MicrosoftIdentityModelTokensSecurityToken = "Microsoft.IdentityModel.Tokens.SecurityToken";
Expand Down Expand Up @@ -593,6 +594,5 @@ internal static class WellKnownTypeNames
public const string XunitFactAttribute = "Xunit.FactAttribute";
public const string XunitSdkDataAttribute = "Xunit.Sdk.DataAttribute";
public const string XunitTraitAttribute = "Xunit.TraitAttribute";

}
}