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 9b954ae
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 2 deletions.
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 DiagnosticDescriptor s_rule = DiagnosticDescriptorHelper.Create(
"CA1862",
"Title TODO",
"Message TODO",
DiagnosticCategory.Performance,
RuleLevel.IdeSuggestion,
"Description TODO",
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 9b954ae

Please sign in to comment.