Skip to content

Commit

Permalink
Initial prototype of AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed May 19, 2023
1 parent c2fd19b commit 9a4e68c
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 3 deletions.
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";

}
}

0 comments on commit 9a4e68c

Please sign in to comment.