diff --git a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs index 8b6e2c7bd16..792ab8de61a 100644 --- a/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs +++ b/analyzers/src/SonarAnalyzer.Common/AnalysisContext/SonarCompilationStartAnalysisContext.cs @@ -18,6 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Collections.Concurrent; +using Roslyn.Utilities; + namespace SonarAnalyzer.AnalysisContext; public sealed class SonarCompilationStartAnalysisContext : SonarAnalysisContextBase @@ -25,7 +28,6 @@ public sealed class SonarCompilationStartAnalysisContext : SonarAnalysisContextB public override Compilation Compilation => Context.Compilation; public override AnalyzerOptions Options => Context.Options; public override CancellationToken Cancel => Context.CancellationToken; - internal SonarCompilationStartAnalysisContext(SonarAnalysisContext analysisContext, CompilationStartAnalysisContext context) : base(analysisContext, context) { } public void RegisterSymbolAction(Action action, params SymbolKind[] symbolKinds) => @@ -37,27 +39,41 @@ public void RegisterCompilationEndAction(Action action) => Context.RegisterSemanticModelAction(x => action(new(AnalysisContext, x))); +#pragma warning disable HAA0303, HAA0302, HAA0301, HAA0502 + + [PerformanceSensitive("https://github.com/SonarSource/sonar-dotnet/issues/8406", AllowCaptures = false, AllowGenericEnumeration = false, AllowImplicitBoxing = false)] public void RegisterNodeAction(GeneratedCodeRecognizer generatedCodeRecognizer, Action action, params TSyntaxKind[] syntaxKinds) where TSyntaxKind : struct { if (HasMatchingScope(AnalysisContext.SupportedDiagnostics)) { + ConcurrentDictionary shouldAnalyzeCache = new(); Context.RegisterSyntaxNodeAction(x => - Execute( - new(AnalysisContext, x), action, x.Node.SyntaxTree, generatedCodeRecognizer), +// The hot path starts in the lambda below. +#pragma warning restore HAA0303, HAA0302, HAA0301, HAA0502 + { + if (!shouldAnalyzeCache.TryGetValue(x.Node.SyntaxTree, out var canProceedWithAnalysis)) + { + canProceedWithAnalysis = GetOrAddCanProceedWithAnalysis(generatedCodeRecognizer, shouldAnalyzeCache, x.Node.SyntaxTree); + } + + if (canProceedWithAnalysis) + { +#pragma warning disable HAA0502 + // https://github.com/SonarSource/sonar-dotnet/issues/8425 + action(new(AnalysisContext, x)); +#pragma warning restore HAA0502 + } + }, syntaxKinds); } } - /// - private void Execute(TSonarContext context, Action action, SyntaxTree sourceTree, GeneratedCodeRecognizer generatedCodeRecognizer = null) - where TSonarContext : SonarAnalysisContextBase - { - if (ShouldAnalyzeTree(sourceTree, generatedCodeRecognizer) - && SonarAnalysisContext.LegacyIsRegisteredActionEnabled(AnalysisContext.SupportedDiagnostics, sourceTree) - && AnalysisContext.ShouldAnalyzeRazorFile(sourceTree)) - { - action(context); - } - } + // Performance: Don't inline to avoid capture class and delegate allocations. + private bool GetOrAddCanProceedWithAnalysis(GeneratedCodeRecognizer codeRecognizer, ConcurrentDictionary cache, SyntaxTree tree) => + cache.GetOrAdd(tree, + x => + ShouldAnalyzeTree(x, codeRecognizer) + && SonarAnalysisContext.LegacyIsRegisteredActionEnabled(AnalysisContext.SupportedDiagnostics, x) + && AnalysisContext.ShouldAnalyzeRazorFile(x)); }