// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { /// /// Driver to execute diagnostic analyzers for a given compilation. /// It uses a of s to drive its analysis. /// internal abstract partial class AnalyzerDriver : IDisposable { // Protect against vicious analyzers that provide large values for SymbolKind. private const int MaxSymbolKind = 100; // Cache delegates for static methods private static readonly Func s_IsCompilerAnalyzerFunc = IsCompilerAnalyzer; private readonly Func _isGeneratedCode; // Lazy fields/properties private CancellationTokenRegistration _queueRegistration; protected ImmutableArray Analyzers { get; private set; } protected AnalyzerManager AnalyzerManager { get; private set; } protected AnalyzerExecutor AnalyzerExecutor { get; private set; } protected CompilationData CurrentCompilationData { get; private set; } protected AnalyzerActions AnalyzerActions { get; private set; } /// /// Cache of additional analyzer actions to be executed per symbol per analyzer, which are registered in symbol start actions. /// We cache the tuple: /// 1. myActions: analyzer actions registered in the symbol start actions of containing namespace/type, which are to be executed for this symbol /// 2. childActions: analyzer actions registered in this symbol's start actions, which are to be executed for member symbols. /// private ConcurrentDictionary<(ISymbol, DiagnosticAnalyzer), AnalyzerActions> _perSymbolAnalyzerActionsCache; private ImmutableDictionary>> _symbolActionsByKind; private ImmutableDictionary> _semanticModelActionsMap; private ImmutableDictionary> _syntaxTreeActionsMap; // Compilation actions and compilation end actions have separate maps so that it is easy to // execute the compilation actions before the compilation end actions. private ImmutableDictionary> _compilationActionsMap; private ImmutableDictionary> _compilationEndActionsMap; /// /// Default analysis mode for generated code. /// /// /// This mode should always guarantee that analyzer action callbacks are enabled for generated code, i.e. is set. /// However, the default diagnostic reporting mode is liable to change in future. /// internal const GeneratedCodeAnalysisFlags DefaultGeneratedCodeAnalysisFlags = GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics; /// /// Map from non-concurrent analyzers to the gate guarding callback into the analyzer. /// private ImmutableDictionary _analyzerGateMap = ImmutableDictionary.Empty; /// /// Map from analyzers to their setting. /// private ImmutableDictionary _generatedCodeAnalysisFlagsMap; /// /// Set of unsuppressed analyzers that need to be executed. /// private ImmutableHashSet _unsuppressedAnalyzers; /// /// True if all analyzers need to analyze and report diagnostics in generated code - we can assume all code to be non-generated code. /// private bool _treatAllCodeAsNonGeneratedCode; /// /// True if no analyzer needs generated code analysis - we can skip all analysis on a generated code symbol/tree. /// private bool _doNotAnalyzeGeneratedCode; /// /// Lazily populated dictionary indicating whether a source file is a generated code file or not - we populate it lazily to avoid realizing all syntax trees in the compilation upfront. /// private ConcurrentDictionary _lazyGeneratedCodeFilesMap; /// /// Lazily populated dictionary from tree to declared symbols with GeneratedCodeAttribute. /// private Dictionary> _lazyGeneratedCodeSymbolsForTreeMap; /// /// Lazily populated dictionary from symbol to a bool indicating if it is a generated code symbol. /// private ConcurrentDictionary _lazyIsGeneratedCodeSymbolMap; /// /// Lazily populated dictionary indicating whether a source file has any hidden regions - we populate it lazily to avoid realizing all syntax trees in the compilation upfront. /// private ConcurrentDictionary _lazyTreesWithHiddenRegionsMap; /// /// Symbol for . /// private INamedTypeSymbol _generatedCodeAttribute; /// /// Driver task which initializes all analyzers. /// This task is initialized and executed only once at start of analysis. /// private Task _initializeTask; /// /// Flag to indicate if the was successfully started. /// private bool _initializeSucceeded = false; /// /// Primary driver task which processes all events, runs analyzer actions and signals completion of at the end. /// private Task _primaryTask; /// /// Number of worker tasks processing compilation events and executing analyzer actions. /// private readonly int _workerCount = Environment.ProcessorCount; /// /// Events queue for analyzer execution. /// public AsyncQueue CompilationEventQueue { get; private set; } /// /// that is fed the diagnostics as they are computed. /// public DiagnosticQueue DiagnosticQueue { get; private set; } /// /// Create an analyzer driver. /// /// The set of analyzers to include in the analysis /// AnalyzerManager to manage analyzers for analyzer host's lifetime. /// Delegate to identify if the given trivia is a comment. protected AnalyzerDriver(ImmutableArray analyzers, AnalyzerManager analyzerManager, Func isComment) { this.Analyzers = analyzers; this.AnalyzerManager = analyzerManager; _isGeneratedCode = (tree, ct) => GeneratedCodeUtilities.IsGeneratedCode(tree, isComment, ct); } /// /// Initializes the and related actions maps for the analyzer driver. /// It kicks off the task for initialization. /// Note: This method must be invoked exactly once on the driver. /// private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagnosticQueue, CompilationData compilationData, CancellationToken cancellationToken) { try { Debug.Assert(_initializeTask == null); this.AnalyzerExecutor = analyzerExecutor; this.CurrentCompilationData = compilationData; this.DiagnosticQueue = diagnosticQueue; // Compute the set of effective actions based on suppression, and running the initial analyzers _initializeTask = Task.Run(async () => { (AnalyzerActions, _unsuppressedAnalyzers) = await GetAnalyzerActionsAsync(Analyzers, AnalyzerManager, analyzerExecutor).ConfigureAwait(false); _analyzerGateMap = await CreateAnalyzerGateMapAsync(_unsuppressedAnalyzers, AnalyzerManager, analyzerExecutor).ConfigureAwait(false); _generatedCodeAnalysisFlagsMap = await CreateGeneratedCodeAnalysisFlagsMapAsync(_unsuppressedAnalyzers, AnalyzerManager, analyzerExecutor).ConfigureAwait(false); _doNotAnalyzeGeneratedCode = ComputeShouldSkipAnalysisOnGeneratedCode(_unsuppressedAnalyzers); _treatAllCodeAsNonGeneratedCode = ComputeShouldTreatAllCodeAsNonGeneratedCode(_unsuppressedAnalyzers, _generatedCodeAnalysisFlagsMap); _lazyGeneratedCodeFilesMap = _treatAllCodeAsNonGeneratedCode ? null : new ConcurrentDictionary(); _lazyGeneratedCodeSymbolsForTreeMap = _treatAllCodeAsNonGeneratedCode ? null : new Dictionary>(); _lazyIsGeneratedCodeSymbolMap = _treatAllCodeAsNonGeneratedCode ? null : new ConcurrentDictionary(); _lazyTreesWithHiddenRegionsMap = _treatAllCodeAsNonGeneratedCode ? null : new ConcurrentDictionary(); _generatedCodeAttribute = analyzerExecutor.Compilation?.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute"); _symbolActionsByKind = MakeSymbolActionsByKind(this.AnalyzerActions); _semanticModelActionsMap = MakeSemanticModelActionsByAnalyzer(this.AnalyzerActions); _syntaxTreeActionsMap = MakeSyntaxTreeActionsByAnalyzer(this.AnalyzerActions); _compilationActionsMap = MakeCompilationActionsByAnalyzer(this.AnalyzerActions.CompilationActions); _compilationEndActionsMap = MakeCompilationActionsByAnalyzer(this.AnalyzerActions.CompilationEndActions); if (this.AnalyzerActions.SymbolStartActionsCount > 0) { _perSymbolAnalyzerActionsCache = new ConcurrentDictionary<(ISymbol, DiagnosticAnalyzer), AnalyzerActions>(); } }, cancellationToken); // create the primary driver task. cancellationToken.ThrowIfCancellationRequested(); _initializeSucceeded = true; } finally { if (_initializeTask == null) { // Set initializeTask to be a cancelled task. var tcs = new TaskCompletionSource(); tcs.SetCanceled(); _initializeTask = tcs.Task; // Set primaryTask to be a cancelled task. tcs = new TaskCompletionSource(); tcs.SetCanceled(); _primaryTask = tcs.Task; // Try to set the DiagnosticQueue to be complete. this.DiagnosticQueue.TryComplete(); } } } internal void Initialize( Compilation compilation, CompilationWithAnalyzersOptions analysisOptions, CompilationData compilationData, bool categorizeDiagnostics, CancellationToken cancellationToken) { Debug.Assert(_initializeTask == null); var diagnosticQueue = DiagnosticQueue.Create(categorizeDiagnostics); Action addNotCategorizedDiagnosticOpt = null; Action addCategorizedLocalDiagnosticOpt = null; Action addCategorizedNonLocalDiagnosticOpt = null; if (categorizeDiagnostics) { addCategorizedLocalDiagnosticOpt = GetDiagnosticSink(diagnosticQueue.EnqueueLocal, compilation); addCategorizedNonLocalDiagnosticOpt = GetDiagnosticSink(diagnosticQueue.EnqueueNonLocal, compilation); } else { addNotCategorizedDiagnosticOpt = GetDiagnosticSink(diagnosticQueue.Enqueue, compilation); } // Wrap onAnalyzerException to pass in filtered diagnostic. Action newOnAnalyzerException = (ex, analyzer, diagnostic) => { var filteredDiagnostic = GetFilteredDiagnostic(diagnostic, compilation); if (filteredDiagnostic != null) { if (analysisOptions.OnAnalyzerException != null) { analysisOptions.OnAnalyzerException(ex, analyzer, filteredDiagnostic); } else if (categorizeDiagnostics) { addCategorizedNonLocalDiagnosticOpt(filteredDiagnostic, analyzer); } else { addNotCategorizedDiagnosticOpt(filteredDiagnostic); } } }; var analyzerExecutor = AnalyzerExecutor.Create( compilation, analysisOptions.Options ?? AnalyzerOptions.Empty, addNotCategorizedDiagnosticOpt, newOnAnalyzerException, analysisOptions.AnalyzerExceptionFilter, IsCompilerAnalyzer, AnalyzerManager, ShouldSkipAnalysisOnGeneratedCode, ShouldSuppressGeneratedCodeDiagnostic, IsGeneratedOrHiddenCodeLocation, GetAnalyzerGate, analysisOptions.LogAnalyzerExecutionTime, addCategorizedLocalDiagnosticOpt, addCategorizedNonLocalDiagnosticOpt, cancellationToken); Initialize(analyzerExecutor, diagnosticQueue, compilationData, cancellationToken); } private SemaphoreSlim GetAnalyzerGate(DiagnosticAnalyzer analyzer) { SemaphoreSlim gate; if (_analyzerGateMap.TryGetValue(analyzer, out gate)) { // Non-concurrent analyzer, needs all the callbacks guarded by a gate. Debug.Assert(gate != null); return gate; } // Concurrent analyzer. return null; } private bool ComputeShouldSkipAnalysisOnGeneratedCode(ImmutableHashSet analyzers) { foreach (var analyzer in analyzers) { if (!ShouldSkipAnalysisOnGeneratedCode(analyzer)) { return false; } } return true; } /// /// Returns true if all analyzers need to analyze and report diagnostics in generated code - we can assume all code to be non-generated code. /// private static bool ComputeShouldTreatAllCodeAsNonGeneratedCode(ImmutableHashSet analyzers, ImmutableDictionary generatedCodeAnalysisFlagsMap) { foreach (var analyzer in analyzers) { var flags = generatedCodeAnalysisFlagsMap[analyzer]; var analyze = (flags & GeneratedCodeAnalysisFlags.Analyze) != 0; var report = (flags & GeneratedCodeAnalysisFlags.ReportDiagnostics) != 0; if (!analyze || !report) { return false; } } return true; } private bool ShouldSkipAnalysisOnGeneratedCode(DiagnosticAnalyzer analyzer) { if (_treatAllCodeAsNonGeneratedCode) { return false; } var mode = _generatedCodeAnalysisFlagsMap[analyzer]; return (mode & GeneratedCodeAnalysisFlags.Analyze) == 0; } private bool ShouldSuppressGeneratedCodeDiagnostic(Diagnostic diagnostic, DiagnosticAnalyzer analyzer, Compilation compilation, CancellationToken cancellationToken) { if (_treatAllCodeAsNonGeneratedCode) { return false; } var generatedCodeAnalysisFlags = _generatedCodeAnalysisFlagsMap[analyzer]; var suppressInGeneratedCode = (generatedCodeAnalysisFlags & GeneratedCodeAnalysisFlags.ReportDiagnostics) == 0; return suppressInGeneratedCode && IsInGeneratedCode(diagnostic.Location, compilation, cancellationToken); } /// /// Attaches a pre-populated event queue to the driver and processes all events in the queue. /// /// Compilation events to analyze. /// Scope of analysis. /// An optional object to track partial analysis state. /// Cancellation token to abort analysis. /// Driver must be initialized before invoking this method, i.e. method must have been invoked and must be non-null. internal async Task AttachQueueAndProcessAllEventsAsync(AsyncQueue eventQueue, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { try { if (_initializeSucceeded) { this.CompilationEventQueue = eventQueue; _queueRegistration = default(CancellationTokenRegistration); await ExecutePrimaryAnalysisTaskAsync(analysisScope, analysisStateOpt, usingPrePopulatedEventQueue: true, cancellationToken: cancellationToken).ConfigureAwait(false); _primaryTask = Task.FromResult(true); } } finally { if (_primaryTask == null) { // Set primaryTask to be a cancelled task. var tcs = new TaskCompletionSource(); tcs.SetCanceled(); _primaryTask = tcs.Task; } } } /// /// Attaches event queue to the driver and start processing all events pertaining to the given analysis scope. /// /// Compilation events to analyze. /// Scope of analysis. /// Cancellation token to abort analysis. /// Driver must be initialized before invoking this method, i.e. method must have been invoked and must be non-null. internal void AttachQueueAndStartProcessingEvents(AsyncQueue eventQueue, AnalysisScope analysisScope, CancellationToken cancellationToken) { try { if (_initializeSucceeded) { this.CompilationEventQueue = eventQueue; _queueRegistration = cancellationToken.Register(() => { this.CompilationEventQueue.TryComplete(); this.DiagnosticQueue.TryComplete(); }); _primaryTask = ExecutePrimaryAnalysisTaskAsync(analysisScope, analysisStateOpt: null, usingPrePopulatedEventQueue: false, cancellationToken: cancellationToken) .ContinueWith(c => DiagnosticQueue.TryComplete(), cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } } finally { if (_primaryTask == null) { // Set primaryTask to be a cancelled task. var tcs = new TaskCompletionSource(); tcs.SetCanceled(); _primaryTask = tcs.Task; // Try to set the DiagnosticQueue to be complete. this.DiagnosticQueue.TryComplete(); } } } private async Task ExecutePrimaryAnalysisTaskAsync(AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool usingPrePopulatedEventQueue, CancellationToken cancellationToken) { Debug.Assert(analysisScope != null); Debug.Assert(WhenInitializedTask != null); await WhenInitializedTask.ConfigureAwait(false); if (WhenInitializedTask.IsFaulted) { OnDriverException(WhenInitializedTask, this.AnalyzerExecutor, analysisScope.Analyzers); } else if (!WhenInitializedTask.IsCanceled) { this.AnalyzerExecutor = this.AnalyzerExecutor.WithCancellationToken(cancellationToken); await ProcessCompilationEventsAsync(analysisScope, analysisStateOpt, usingPrePopulatedEventQueue, cancellationToken).ConfigureAwait(false); // If not using pre-populated event queue (batch mode), then verify all symbol end actions were processed. if (!usingPrePopulatedEventQueue) { AnalyzerManager.VerifyAllSymbolEndActionsExecuted(); } } } private static void OnDriverException(Task faultedTask, AnalyzerExecutor analyzerExecutor, ImmutableArray analyzers) { Debug.Assert(faultedTask.IsFaulted); var innerException = faultedTask.Exception?.InnerException; if (innerException == null || innerException is OperationCanceledException) { return; } var diagnostic = AnalyzerExecutor.CreateDriverExceptionDiagnostic(innerException); // Just pick the first analyzer from the scope for the onAnalyzerException callback. // The exception diagnostic's message and description will not include the analyzer, but explicitly state its a driver exception. var analyzer = analyzers[0]; analyzerExecutor.OnAnalyzerException(innerException, analyzer, diagnostic); } private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { if (analysisScope.IsTreeAnalysis && !analysisScope.IsSyntaxOnlyTreeAnalysis) { // For partial analysis, only execute syntax tree actions if performing syntax analysis. return; } foreach (var tree in analysisScope.SyntaxTrees) { var isGeneratedCode = IsGeneratedCode(tree); if (isGeneratedCode && DoNotAnalyzeGeneratedCode) { analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analysisScope.Analyzers); continue; } foreach (var analyzer in analysisScope.Analyzers) { cancellationToken.ThrowIfCancellationRequested(); ImmutableArray syntaxTreeActions; if (_syntaxTreeActionsMap.TryGetValue(analyzer, out syntaxTreeActions)) { // Execute actions for a given analyzer sequentially. AnalyzerExecutor.TryExecuteSyntaxTreeActions(syntaxTreeActions, analyzer, tree, analysisScope, analysisStateOpt, isGeneratedCode); } else { analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analyzer); } } } } /// /// Create an and attach it to the given compilation. /// /// The compilation to which the new driver should be attached. /// The set of analyzers to include in the analysis. /// Options that are passed to analyzers. /// AnalyzerManager to manage analyzers for the lifetime of analyzer host. /// Delegate to add diagnostics generated for exceptions from third party analyzers. /// Report additional information related to analyzers, such as analyzer execution time. /// The new compilation with the analyzer driver attached. /// A cancellation token that can be used to abort analysis. /// A newly created analyzer driver /// /// Note that since a compilation is immutable, the act of creating a driver and attaching it produces /// a new compilation. Any further actions on the compilation should use the new compilation. /// public static AnalyzerDriver CreateAndAttachToCompilation( Compilation compilation, ImmutableArray analyzers, AnalyzerOptions options, AnalyzerManager analyzerManager, Action addExceptionDiagnostic, bool reportAnalyzer, out Compilation newCompilation, CancellationToken cancellationToken) { Action onAnalyzerException = (ex, analyzer, diagnostic) => addExceptionDiagnostic?.Invoke(diagnostic); Func nullFilter = null; return CreateAndAttachToCompilation(compilation, analyzers, options, analyzerManager, onAnalyzerException, nullFilter, reportAnalyzer, out newCompilation, cancellationToken: cancellationToken); } // internal for testing purposes internal static AnalyzerDriver CreateAndAttachToCompilation( Compilation compilation, ImmutableArray analyzers, AnalyzerOptions options, AnalyzerManager analyzerManager, Action onAnalyzerException, Func analyzerExceptionFilter, bool reportAnalyzer, out Compilation newCompilation, CancellationToken cancellationToken) { AnalyzerDriver analyzerDriver = compilation.AnalyzerForLanguage(analyzers, analyzerManager); newCompilation = compilation.WithEventQueue(new AsyncQueue()); var categorizeDiagnostics = false; var analysisOptions = new CompilationWithAnalyzersOptions(options, onAnalyzerException, analyzerExceptionFilter: analyzerExceptionFilter, concurrentAnalysis: true, logAnalyzerExecutionTime: reportAnalyzer, reportSuppressedDiagnostics: false); analyzerDriver.Initialize(newCompilation, analysisOptions, new CompilationData(newCompilation), categorizeDiagnostics, cancellationToken); var analysisScope = new AnalysisScope(newCompilation, analyzers, concurrentAnalysis: newCompilation.Options.ConcurrentBuild, categorizeDiagnostics: categorizeDiagnostics); analyzerDriver.AttachQueueAndStartProcessingEvents(newCompilation.EventQueue, analysisScope, cancellationToken: cancellationToken); return analyzerDriver; } /// /// Returns all diagnostics computed by the analyzers since the last time this was invoked. /// If has been completed with all compilation events, then it waits for /// task for the driver to finish processing all events and generate remaining analyzer diagnostics. /// public async Task> GetDiagnosticsAsync(Compilation compilation) { var allDiagnostics = DiagnosticBag.GetInstance(); if (CompilationEventQueue.IsCompleted) { await this.WhenCompletedTask.ConfigureAwait(false); if (this.WhenCompletedTask.IsFaulted) { OnDriverException(this.WhenCompletedTask, this.AnalyzerExecutor, this.Analyzers); } } var suppressMessageState = CurrentCompilationData.SuppressMessageAttributeState; var reportSuppressedDiagnostics = compilation.Options.ReportSuppressedDiagnostics; Diagnostic d; while (DiagnosticQueue.TryDequeue(out d)) { d = suppressMessageState.ApplySourceSuppressions(d); if (reportSuppressedDiagnostics || !d.IsSuppressed) { allDiagnostics.Add(d); } } return allDiagnostics.ToReadOnlyAndFree(); } public ImmutableArray DequeueLocalDiagnostics(DiagnosticAnalyzer analyzer, bool syntax, Compilation compilation) { var diagnostics = syntax ? DiagnosticQueue.DequeueLocalSyntaxDiagnostics(analyzer) : DiagnosticQueue.DequeueLocalSemanticDiagnostics(analyzer); return FilterDiagnosticsSuppressedInSource(diagnostics, compilation, CurrentCompilationData.SuppressMessageAttributeState); } public ImmutableArray DequeueNonLocalDiagnostics(DiagnosticAnalyzer analyzer, Compilation compilation) { var diagnostics = DiagnosticQueue.DequeueNonLocalDiagnostics(analyzer); return FilterDiagnosticsSuppressedInSource(diagnostics, compilation, CurrentCompilationData.SuppressMessageAttributeState); } private ImmutableArray FilterDiagnosticsSuppressedInSource(ImmutableArray diagnostics, Compilation compilation, SuppressMessageAttributeState suppressMessageState) { if (diagnostics.IsEmpty) { return diagnostics; } var reportSuppressedDiagnostics = compilation.Options.ReportSuppressedDiagnostics; var builder = ImmutableArray.CreateBuilder(); for (var i = 0; i < diagnostics.Length; i++) { #if DEBUG // We should have ignored diagnostics with invalid locations and reported analyzer exception diagnostic for the same. DiagnosticAnalysisContextHelpers.VerifyDiagnosticLocationsInCompilation(diagnostics[i], compilation); #endif var diagnostic = suppressMessageState.ApplySourceSuppressions(diagnostics[i]); if (!reportSuppressedDiagnostics && diagnostic.IsSuppressed) { // Diagnostic suppressed in source. continue; } builder.Add(diagnostic); } return builder.ToImmutable(); } private bool IsInGeneratedCode(Location location, Compilation compilation, CancellationToken cancellationToken) { if (_treatAllCodeAsNonGeneratedCode || !location.IsInSource) { return false; } // Check if this is a generated code location. if (IsGeneratedOrHiddenCodeLocation(location.SourceTree, location.SourceSpan)) { return true; } // Check if the file has generated code definitions (i.e. symbols with GeneratedCodeAttribute). if (_generatedCodeAttribute != null && _lazyGeneratedCodeSymbolsForTreeMap != null) { var generatedCodeSymbolsInTree = GetOrComputeGeneratedCodeSymbolsInTree(location.SourceTree, compilation, cancellationToken); if (generatedCodeSymbolsInTree.Count > 0) { var model = compilation.GetSemanticModel(location.SourceTree); for (var node = location.SourceTree.GetRoot(cancellationToken).FindNode(location.SourceSpan, getInnermostNodeForTie: true); node != null; node = node.Parent) { var declaredSymbols = model.GetDeclaredSymbolsForNode(node, cancellationToken); Debug.Assert(declaredSymbols != null); foreach (var symbol in declaredSymbols) { if (generatedCodeSymbolsInTree.Contains(symbol)) { return true; } } } } } return false; } private ImmutableHashSet GetOrComputeGeneratedCodeSymbolsInTree(SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken) { Debug.Assert(_lazyGeneratedCodeSymbolsForTreeMap != null); ImmutableHashSet generatedCodeSymbols; lock (_lazyGeneratedCodeSymbolsForTreeMap) { if (_lazyGeneratedCodeSymbolsForTreeMap.TryGetValue(tree, out generatedCodeSymbols)) { return generatedCodeSymbols; } } generatedCodeSymbols = ComputeGeneratedCodeSymbolsInTree(tree, compilation, cancellationToken); lock (_lazyGeneratedCodeSymbolsForTreeMap) { ImmutableHashSet existingGeneratedCodeSymbols; if (!_lazyGeneratedCodeSymbolsForTreeMap.TryGetValue(tree, out existingGeneratedCodeSymbols)) { _lazyGeneratedCodeSymbolsForTreeMap.Add(tree, generatedCodeSymbols); } else { Debug.Assert(existingGeneratedCodeSymbols.SetEquals(generatedCodeSymbols)); } } return generatedCodeSymbols; } private ImmutableHashSet ComputeGeneratedCodeSymbolsInTree(SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken) { // PERF: Bail out early if file doesn't have "GeneratedCode" text. var text = tree.GetText(cancellationToken).ToString(); if (!text.Contains("GeneratedCode")) { return ImmutableHashSet.Empty; } var model = compilation.GetSemanticModel(tree); var root = tree.GetRoot(cancellationToken); var span = root.FullSpan; var declarationInfoBuilder = ArrayBuilder.GetInstance(); model.ComputeDeclarationsInSpan(span, getSymbol: true, builder: declarationInfoBuilder, cancellationToken: cancellationToken); ImmutableHashSet.Builder generatedSymbolsBuilderOpt = null; foreach (var declarationInfo in declarationInfoBuilder) { var symbol = declarationInfo.DeclaredSymbol; if (symbol != null && GeneratedCodeUtilities.IsGeneratedSymbolWithGeneratedCodeAttribute(symbol, _generatedCodeAttribute)) { generatedSymbolsBuilderOpt = generatedSymbolsBuilderOpt ?? ImmutableHashSet.CreateBuilder(); generatedSymbolsBuilderOpt.Add(symbol); } } declarationInfoBuilder.Free(); return generatedSymbolsBuilderOpt != null ? generatedSymbolsBuilderOpt.ToImmutable() : ImmutableHashSet.Empty; } /// /// Return a task that completes when the driver is initialized. /// public Task WhenInitializedTask => _initializeTask; /// /// Return a task that completes when the driver is done producing diagnostics. /// public Task WhenCompletedTask => _primaryTask; internal ImmutableDictionary AnalyzerExecutionTimes => AnalyzerExecutor.AnalyzerExecutionTimes; internal TimeSpan ResetAnalyzerExecutionTime(DiagnosticAnalyzer analyzer) => AnalyzerExecutor.ResetAnalyzerExecutionTime(analyzer); private static ImmutableDictionary>> MakeSymbolActionsByKind(AnalyzerActions analyzerActions) { var builder = ImmutableDictionary.CreateBuilder>>(); var actionsByAnalyzers = analyzerActions.SymbolActions.GroupBy(action => action.Analyzer); foreach (var analyzerAndActions in actionsByAnalyzers) { var actionsByKindBuilder = new List>(); foreach (var symbolAction in analyzerAndActions) { var kinds = symbolAction.Kinds; foreach (int kind in kinds.Distinct()) { if (kind > MaxSymbolKind) continue; // protect against vicious analyzers while (kind >= actionsByKindBuilder.Count) { actionsByKindBuilder.Add(ArrayBuilder.GetInstance()); } actionsByKindBuilder[kind].Add(symbolAction); } } var actionsByKind = actionsByKindBuilder.Select(a => a.ToImmutableAndFree()).ToImmutableArray(); builder.Add(analyzerAndActions.Key, actionsByKind); } return builder.ToImmutable(); } private static ImmutableDictionary> MakeSyntaxTreeActionsByAnalyzer(AnalyzerActions analyzerActions) { var builder = ImmutableDictionary.CreateBuilder>(); var actionsByAnalyzers = analyzerActions.SyntaxTreeActions.GroupBy(action => action.Analyzer); foreach (var analyzerAndActions in actionsByAnalyzers) { builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArray()); } return builder.ToImmutable(); } private static ImmutableDictionary> MakeSemanticModelActionsByAnalyzer(AnalyzerActions analyzerActions) { var builder = ImmutableDictionary.CreateBuilder>(); var actionsByAnalyzers = analyzerActions.SemanticModelActions.GroupBy(action => action.Analyzer); foreach (var analyzerAndActions in actionsByAnalyzers) { builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArray()); } return builder.ToImmutable(); } private static ImmutableDictionary> MakeCompilationActionsByAnalyzer(ImmutableArray compilationActions) { var builder = ImmutableDictionary.CreateBuilder>(); var actionsByAnalyzers = compilationActions.GroupBy(action => action.Analyzer); foreach (var analyzerAndActions in actionsByAnalyzers) { builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArray()); } return builder.ToImmutable(); } private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool prePopulatedEventQueue, CancellationToken cancellationToken) { try { CompilationCompletedEvent completedEvent = null; if (analysisScope.ConcurrentAnalysis) { // Kick off worker tasks to process all compilation events (except the compilation end event) in parallel. // Compilation end event must be processed after all other events. var workerCount = prePopulatedEventQueue ? Math.Min(CompilationEventQueue.Count, _workerCount) : _workerCount; var workerTasks = new Task[workerCount]; for (int i = 0; i < workerCount; i++) { // Create separate worker tasks to process all compilation events - we do not want to process any events on the main thread. workerTasks[i] = Task.Run(async () => await ProcessCompilationEventsCoreAsync(analysisScope, analysisStateOpt, prePopulatedEventQueue, cancellationToken).ConfigureAwait(false)); } cancellationToken.ThrowIfCancellationRequested(); // Kick off tasks to execute syntax tree actions. var syntaxTreeActionsTask = Task.Run(() => ExecuteSyntaxTreeActions(analysisScope, analysisStateOpt, cancellationToken)); // Wait for all worker threads to complete processing events. await Task.WhenAll(workerTasks.Concat(syntaxTreeActionsTask)).ConfigureAwait(false); for (int i = 0; i < workerCount; i++) { if (workerTasks[i].Status == TaskStatus.RanToCompletion && workerTasks[i].Result != null) { completedEvent = workerTasks[i].Result; break; } } } else { completedEvent = await ProcessCompilationEventsCoreAsync(analysisScope, analysisStateOpt, prePopulatedEventQueue, cancellationToken).ConfigureAwait(false); ExecuteSyntaxTreeActions(analysisScope, analysisStateOpt, cancellationToken); } // Finally process the compilation completed event, if any. if (completedEvent != null) { await ProcessEventAsync(completedEvent, analysisScope, analysisStateOpt, cancellationToken).ConfigureAwait(false); } } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } private async Task ProcessCompilationEventsCoreAsync(AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool prePopulatedEventQueue, CancellationToken cancellationToken) { try { CompilationCompletedEvent completedEvent = null; while (true) { cancellationToken.ThrowIfCancellationRequested(); // NOTE: IsCompleted guarantees that Count will not increase // the reverse is not true, so we need to check IsCompleted first and then check the Count if ((prePopulatedEventQueue || CompilationEventQueue.IsCompleted) && CompilationEventQueue.Count == 0) { break; } CompilationEvent e; try { if (!CompilationEventQueue.TryDequeue(out e)) { if (!prePopulatedEventQueue) { e = await CompilationEventQueue.DequeueAsync(cancellationToken).ConfigureAwait(false); } else { return completedEvent; } } } catch (TaskCanceledException) when (!prePopulatedEventQueue) { // When the queue is completed with a pending DequeueAsync return then a // TaskCanceledException will be thrown. This just signals the queue is // complete and we should finish processing it. // This failure is being tracked by https://github.com/dotnet/roslyn/issues/5962 // Debug.Assert(CompilationEventQueue.IsCompleted, "DequeueAsync should never throw unless the AsyncQueue is completed."); break; } // Don't process the compilation completed event as other worker threads might still be processing other compilation events. // The caller will wait for all workers to complete and finally process this event. var compilationCompletedEvent = e as CompilationCompletedEvent; if (compilationCompletedEvent != null) { completedEvent = compilationCompletedEvent; continue; } await ProcessEventAsync(e, analysisScope, analysisStateOpt, cancellationToken).ConfigureAwait(false); } return completedEvent; } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } } private async Task ProcessEventAsync(CompilationEvent e, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { EventProcessedState eventProcessedState = await TryProcessEventCoreAsync(e, analysisScope, analysisStateOpt, cancellationToken).ConfigureAwait(false); ImmutableArray processedAnalyzers; switch (eventProcessedState.Kind) { case EventProcessedStateKind.Processed: processedAnalyzers = analysisScope.Analyzers; break; case EventProcessedStateKind.PartiallyProcessed: processedAnalyzers = eventProcessedState.SubsetProcessedAnalyzers; break; default: return; } await OnEventProcessedCoreAsync(e, processedAnalyzers, analysisStateOpt, cancellationToken).ConfigureAwait(false); } private async Task OnEventProcessedCoreAsync(CompilationEvent e, ImmutableArray processedAnalyzers, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { if (analysisStateOpt != null) { await analysisStateOpt.OnCompilationEventProcessedAsync(e, processedAnalyzers, onSymbolAndMembersProcessedAsync).ConfigureAwait(false); } else if (AnalyzerActions.SymbolStartActionsCount > 0 && e is SymbolDeclaredCompilationEvent symbolDeclaredEvent) { foreach (var analyzer in processedAnalyzers) { await onSymbolAndMembersProcessedAsync(symbolDeclaredEvent.Symbol, analyzer).ConfigureAwait(false); } } async Task onSymbolAndMembersProcessedAsync(ISymbol symbol, DiagnosticAnalyzer analyzer) { if (AnalyzerActions.SymbolStartActionsCount == 0 || symbol.IsImplicitlyDeclared) { return; } _perSymbolAnalyzerActionsCache.TryRemove((symbol, analyzer), out _); await processContainerOnMemberCompletedAsync(symbol.ContainingNamespace, symbol, analyzer).ConfigureAwait(false); await processContainerOnMemberCompletedAsync(symbol.ContainingType, symbol, analyzer).ConfigureAwait(false); } async Task processContainerOnMemberCompletedAsync(INamespaceOrTypeSymbol containerSymbol, ISymbol processedMemberSymbol, DiagnosticAnalyzer analyzer) { if (containerSymbol != null && AnalyzerExecutor.TryExecuteSymbolEndActionsForContainer(containerSymbol, processedMemberSymbol, analyzer, GetTopmostNodeForAnalysis, analysisStateOpt, out SymbolDeclaredCompilationEvent processedContainerEvent)) { await OnEventProcessedCoreAsync(processedContainerEvent, ImmutableArray.Create(analyzer), analysisStateOpt, cancellationToken).ConfigureAwait(false); } } } private async Task TryProcessEventCoreAsync(CompilationEvent e, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var symbolEvent = e as SymbolDeclaredCompilationEvent; if (symbolEvent != null) { return await TryProcessSymbolDeclaredAsync(symbolEvent, analysisScope, analysisStateOpt, cancellationToken).ConfigureAwait(false); } var completedEvent = e as CompilationUnitCompletedEvent; if (completedEvent != null) { return TryProcessCompilationUnitCompleted(completedEvent, analysisScope, analysisStateOpt, cancellationToken) ? EventProcessedState.Processed : EventProcessedState.NotProcessed; } var endEvent = e as CompilationCompletedEvent; if (endEvent != null) { return TryProcessCompilationCompleted(endEvent, analysisScope, analysisStateOpt, cancellationToken) ? EventProcessedState.Processed : EventProcessedState.NotProcessed; } var startedEvent = e as CompilationStartedEvent; if (startedEvent != null) { return TryProcessCompilationStarted(startedEvent, analysisScope, analysisStateOpt, cancellationToken) ? EventProcessedState.Processed : EventProcessedState.NotProcessed; } throw new InvalidOperationException("Unexpected compilation event of type " + e.GetType().Name); } /// /// Tries to execute symbol action, symbol start/end actions and declaration actions for the given symbol. /// /// /// indicating the current state of processing of the given compilation event. /// private async Task TryProcessSymbolDeclaredAsync(SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { try { // Attempt to execute all analyzer actions. var processedState = EventProcessedState.Processed; var symbol = symbolEvent.Symbol; var isGeneratedCodeSymbol = IsGeneratedCodeSymbol(symbol); var skipSymbolAnalysis = AnalysisScope.ShouldSkipSymbolAnalysis(symbolEvent); var skipDeclarationAnalysis = AnalysisScope.ShouldSkipDeclarationAnalysis(symbol); var hasPerSymbolActions = AnalyzerActions.SymbolStartActionsCount > 0 && (!skipSymbolAnalysis || !skipDeclarationAnalysis); AnalyzerActions perSymbolActions = hasPerSymbolActions ? await GetPerSymbolAnalyzerActionsAsync(symbol, analysisScope, analysisStateOpt, cancellationToken).ConfigureAwait(false) : null; if (!skipSymbolAnalysis && !TryExecuteSymbolActions(symbolEvent, analysisScope, analysisStateOpt, isGeneratedCodeSymbol, cancellationToken)) { processedState = EventProcessedState.NotProcessed; } if (!skipDeclarationAnalysis && !TryExecuteDeclaringReferenceActions(symbolEvent, analysisScope, analysisStateOpt, isGeneratedCodeSymbol, perSymbolActions, cancellationToken)) { processedState = EventProcessedState.NotProcessed; } ImmutableArray subsetProcessedAnalyzers = default; if (processedState.Kind == EventProcessedStateKind.Processed && hasPerSymbolActions && !TryExecuteSymbolEndActions(perSymbolActions, symbolEvent, analysisScope, analysisStateOpt, cancellationToken, out subsetProcessedAnalyzers)) { processedState = subsetProcessedAnalyzers.IsDefaultOrEmpty ? EventProcessedState.NotProcessed : EventProcessedState.CreatePartiallyProcessed(subsetProcessedAnalyzers); } return processedState; } finally { symbolEvent.FlushCache(); } } /// /// Tries to execute symbol actions. /// /// /// True, if successfully executed the actions for the given analysis scope OR no actions were required to be executed for the given analysis scope. /// False, otherwise. /// private bool TryExecuteSymbolActions(SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool isGeneratedCodeSymbol, CancellationToken cancellationToken) { var symbol = symbolEvent.Symbol; if (!analysisScope.ShouldAnalyze(symbol)) { return true; } var success = true; foreach (var analyzer in analysisScope.Analyzers) { // Invoke symbol analyzers only for source symbols. ImmutableArray> actionsByKind; if (_symbolActionsByKind.TryGetValue(analyzer, out actionsByKind) && (int)symbol.Kind < actionsByKind.Length) { if (!AnalyzerExecutor.TryExecuteSymbolActions(actionsByKind[(int)symbol.Kind], analyzer, symbolEvent, GetTopmostNodeForAnalysis, analysisScope, analysisStateOpt, isGeneratedCodeSymbol)) { success = false; } } else { analysisStateOpt?.MarkSymbolComplete(symbol, analyzer); } } return success; } private bool TryExecuteSymbolEndActions( AnalyzerActions perSymbolActions, SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken, out ImmutableArray subsetProcessedAnalyzers) { Debug.Assert(AnalyzerActions.SymbolStartActionsCount > 0); subsetProcessedAnalyzers = default; var symbol = symbolEvent.Symbol; var symbolEndActions = perSymbolActions?.SymbolEndActions ?? ImmutableArray.Empty; if (!analysisScope.ShouldAnalyze(symbol) || symbolEndActions.IsEmpty) { analysisStateOpt?.MarkSymbolEndAnalysisComplete(symbol, analysisScope.Analyzers); return true; } var success = true; ArrayBuilder subsetProcessedAnalyzersBuilderOpt = null; for (int i = 0; i < analysisScope.Analyzers.Length; i++) { var analyzer = analysisScope.Analyzers[i]; var analyzerSuccess = true; var symbolEndActionsForAnalyzer = symbolEndActions.Where(a => a.Analyzer == analyzer).ToImmutableArrayOrEmpty(); if (!symbolEndActionsForAnalyzer.IsEmpty) { if (!AnalyzerExecutor.TryExecuteSymbolEndActions(symbolEndActionsForAnalyzer, analyzer, symbolEvent, GetTopmostNodeForAnalysis, analysisStateOpt)) { if (subsetProcessedAnalyzersBuilderOpt == null) { subsetProcessedAnalyzersBuilderOpt = ArrayBuilder.GetInstance(); subsetProcessedAnalyzersBuilderOpt.AddRange(analysisScope.Analyzers, i); } analyzerSuccess = false; success = false; } } if (analyzerSuccess) { AnalyzerExecutor.MarkSymbolEndAnalysisComplete(symbol, analyzer, analysisStateOpt); subsetProcessedAnalyzersBuilderOpt?.Add(analyzer); } } if (subsetProcessedAnalyzersBuilderOpt != null) { Debug.Assert(!success); Debug.Assert(subsetProcessedAnalyzersBuilderOpt.Count < analysisScope.Analyzers.Length); if (subsetProcessedAnalyzersBuilderOpt.Count > 0) { subsetProcessedAnalyzers = subsetProcessedAnalyzersBuilderOpt.ToImmutableAndFree(); } else { subsetProcessedAnalyzersBuilderOpt.Free(); } } return success; } private static SyntaxNode GetTopmostNodeForAnalysis(ISymbol symbol, SyntaxReference syntaxReference, Compilation compilation) { var model = compilation.GetSemanticModel(syntaxReference.SyntaxTree); return model.GetTopmostNodeForDiagnosticAnalysis(symbol, syntaxReference.GetSyntax()); } protected abstract bool TryExecuteDeclaringReferenceActions( SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool isGeneratedCodeSymbol, AnalyzerActions additionalPerSymbolActions, CancellationToken cancellationToken); /// /// Tries to execute compilation unit actions. /// /// /// True, if successfully executed the actions for the given analysis scope OR no actions were required to be executed for the given analysis scope. /// False, otherwise. /// private bool TryProcessCompilationUnitCompleted(CompilationUnitCompletedEvent completedEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { // When the compiler is finished with a compilation unit, we can run user diagnostics which // might want to ask the compiler for all the diagnostics in the source file, for example // to get information about unnecessary usings. var semanticModel = analysisStateOpt != null ? CurrentCompilationData.GetOrCreateCachedSemanticModel(completedEvent.CompilationUnit, completedEvent.Compilation, cancellationToken) : completedEvent.SemanticModel; if (!analysisScope.ShouldAnalyze(semanticModel.SyntaxTree)) { return true; } var isGeneratedCode = IsGeneratedCode(semanticModel.SyntaxTree); if (isGeneratedCode && DoNotAnalyzeGeneratedCode) { analysisStateOpt?.MarkEventComplete(completedEvent, analysisScope.Analyzers); return true; } try { var success = true; foreach (var analyzer in analysisScope.Analyzers) { ImmutableArray semanticModelActions; if (_semanticModelActionsMap.TryGetValue(analyzer, out semanticModelActions)) { // Execute actions for a given analyzer sequentially. if (!AnalyzerExecutor.TryExecuteSemanticModelActions(semanticModelActions, analyzer, semanticModel, completedEvent, analysisScope, analysisStateOpt, isGeneratedCode)) { success = false; } } else { analysisStateOpt?.MarkEventComplete(completedEvent, analyzer); } } return success; } finally { completedEvent.FlushCache(); } } /// /// Tries to execute compilation started actions. /// /// /// True, if successfully executed the actions for the given analysis scope OR no actions were required to be executed for the given analysis scope. /// False, otherwise. /// private bool TryProcessCompilationStarted(CompilationStartedEvent startedEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { return TryExecuteCompilationActions(_compilationActionsMap, startedEvent, analysisScope, analysisStateOpt, cancellationToken); } /// /// Tries to execute compilation completed actions. /// /// /// True, if successfully executed the actions for the given analysis scope OR no actions were required to be executed for the given analysis scope. /// False, otherwise. /// private bool TryProcessCompilationCompleted(CompilationCompletedEvent endEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { return TryExecuteCompilationActions(_compilationEndActionsMap, endEvent, analysisScope, analysisStateOpt, cancellationToken); } /// /// Tries to execute compilation actions. /// /// /// True, if successfully executed the actions for the given analysis scope OR no actions were required to be executed for the given analysis scope. /// False, otherwise. /// private bool TryExecuteCompilationActions( ImmutableDictionary> compilationActionsMap, CompilationEvent compilationEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { Debug.Assert(compilationEvent is CompilationStartedEvent || compilationEvent is CompilationCompletedEvent); try { var success = true; foreach (var analyzer in analysisScope.Analyzers) { ImmutableArray compilationActions; if (compilationActionsMap.TryGetValue(analyzer, out compilationActions)) { if (!AnalyzerExecutor.TryExecuteCompilationActions(compilationActions, analyzer, compilationEvent, analysisScope, analysisStateOpt)) { success = false; } } else { analysisStateOpt?.MarkEventComplete(compilationEvent, analyzer); } } return success; } finally { compilationEvent.FlushCache(); } } internal static Action GetDiagnosticSink(Action addDiagnosticCore, Compilation compilation) { return diagnostic => { var filteredDiagnostic = GetFilteredDiagnostic(diagnostic, compilation); if (filteredDiagnostic != null) { addDiagnosticCore(filteredDiagnostic); } }; } internal static Action GetDiagnosticSink(Action addLocalDiagnosticCore, Compilation compilation) { return (diagnostic, analyzer, isSyntaxDiagnostic) => { var filteredDiagnostic = GetFilteredDiagnostic(diagnostic, compilation); if (filteredDiagnostic != null) { addLocalDiagnosticCore(filteredDiagnostic, analyzer, isSyntaxDiagnostic); } }; } internal static Action GetDiagnosticSink(Action addDiagnosticCore, Compilation compilation) { return (diagnostic, analyzer) => { var filteredDiagnostic = GetFilteredDiagnostic(diagnostic, compilation); if (filteredDiagnostic != null) { addDiagnosticCore(filteredDiagnostic, analyzer); } }; } private static Diagnostic GetFilteredDiagnostic(Diagnostic diagnostic, Compilation compilation) { return compilation.Options.FilterDiagnostic(diagnostic); } private static async Task<(AnalyzerActions actions, ImmutableHashSet unsuppressedAnalyzers)> GetAnalyzerActionsAsync( ImmutableArray analyzers, AnalyzerManager analyzerManager, AnalyzerExecutor analyzerExecutor) { var allAnalyzerActions = new AnalyzerActions(); var unsuppressedAnalyzersBuilder = PooledHashSet.GetInstance(); foreach (var analyzer in analyzers) { if (!IsDiagnosticAnalyzerSuppressed(analyzer, analyzerExecutor.Compilation.Options, analyzerManager, analyzerExecutor)) { unsuppressedAnalyzersBuilder.Add(analyzer); var analyzerActions = await analyzerManager.GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false); if (analyzerActions != null) { allAnalyzerActions = allAnalyzerActions.Append(analyzerActions); } } } var unsuppressedAnalyzers = unsuppressedAnalyzersBuilder.ToImmutableHashSet(); unsuppressedAnalyzersBuilder.Free(); return (allAnalyzerActions, unsuppressedAnalyzers); } public bool HasSymbolStartedActions => this.AnalyzerActions.SymbolStartActionsCount > 0; private async Task GetPerSymbolAnalyzerActionsAsync( ISymbol symbol, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { if (AnalyzerActions.SymbolStartActionsCount == 0 || symbol.IsImplicitlyDeclared) { return default; } var allActions = new AnalyzerActions(); foreach (var analyzer in analysisScope.Analyzers) { var analyzerActions = await GetPerSymbolAnalyzerActionsAsync(symbol, analyzer, analysisStateOpt, cancellationToken).ConfigureAwait(false); if (analyzerActions != null) { allActions = allActions.Append(analyzerActions); } } return allActions; } private async Task GetPerSymbolAnalyzerActionsAsync( ISymbol symbol, DiagnosticAnalyzer analyzer, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { Debug.Assert(AnalyzerActions.SymbolStartActionsCount > 0); if (symbol.IsImplicitlyDeclared) { return null; } if (_perSymbolAnalyzerActionsCache.TryGetValue((symbol, analyzer), out var actions)) { return actions; } // Compute additional inherited actions for this symbol by running the containing symbol's start actions. AnalyzerActions inheritedActions = await getInheritedActionsAsync().ConfigureAwait(false); // Execute the symbol start actions for this symbol to compute additional actions for its members. AnalyzerActions myActions = await getSymbolActionsCoreAsync().ConfigureAwait(false); AnalyzerActions allActions = myActions != null ? inheritedActions.Append(myActions) : inheritedActions; return _perSymbolAnalyzerActionsCache.GetOrAdd((symbol, analyzer), allActions); async Task getInheritedActionsAsync() { if (symbol.ContainingSymbol != null) { // Get container symbol's per-symbol actions, which also forces its start actions to execute. var containerActions = await GetPerSymbolAnalyzerActionsAsync(symbol.ContainingSymbol, analyzer, analysisStateOpt, cancellationToken).ConfigureAwait(false); if (containerActions != null) { // Don't inherit actions for nested type and namespace from its containing type and namespace respectively. // However, note that we bail out **after** computing container's per-symbol actions above. // This is done to ensure that we have executed symbol started actions for the container before our start actions are executed. if (symbol.ContainingSymbol.Kind != symbol.Kind) { // Don't inherit the symbol start and symbol end actions. return new AnalyzerActions().Append(containerActions, appendSymbolStartAndSymbolEndActions: false); } } } return new AnalyzerActions(); } async Task getSymbolActionsCoreAsync() { if (!_unsuppressedAnalyzers.Contains(analyzer) || IsGeneratedCodeSymbol(symbol) && ShouldSkipAnalysisOnGeneratedCode(analyzer)) { return null; } else { return await AnalyzerManager.GetPerSymbolAnalyzerActionsAsync(symbol, analyzer, AnalyzerExecutor).ConfigureAwait(false); } } } private static async Task> CreateAnalyzerGateMapAsync( ImmutableHashSet analyzers, AnalyzerManager analyzerManager, AnalyzerExecutor analyzerExecutor) { var builder = ImmutableDictionary.CreateBuilder(); foreach (var analyzer in analyzers) { Debug.Assert(!IsDiagnosticAnalyzerSuppressed(analyzer, analyzerExecutor.Compilation.Options, analyzerManager, analyzerExecutor)); var isConcurrent = await analyzerManager.IsConcurrentAnalyzerAsync(analyzer, analyzerExecutor).ConfigureAwait(false); if (!isConcurrent) { // Non-concurrent analyzers need their action callbacks from the analyzer driver to be guarded by a gate. var gate = new SemaphoreSlim(initialCount: 1); builder.Add(analyzer, gate); } } return builder.ToImmutable(); } private static async Task> CreateGeneratedCodeAnalysisFlagsMapAsync( ImmutableHashSet analyzers, AnalyzerManager analyzerManager, AnalyzerExecutor analyzerExecutor) { var builder = ImmutableDictionary.CreateBuilder(); foreach (var analyzer in analyzers) { Debug.Assert(!IsDiagnosticAnalyzerSuppressed(analyzer, analyzerExecutor.Compilation.Options, analyzerManager, analyzerExecutor)); var generatedCodeAnalysisFlags = await analyzerManager.GetGeneratedCodeAnalysisFlagsAsync(analyzer, analyzerExecutor).ConfigureAwait(false); builder.Add(analyzer, generatedCodeAnalysisFlags); } return builder.ToImmutable(); } [PerformanceSensitive( "https://github.com/dotnet/roslyn/pull/23637", AllowLocks = false)] private bool IsGeneratedCodeSymbol(ISymbol symbol) { if (_treatAllCodeAsNonGeneratedCode) { return false; } return _lazyIsGeneratedCodeSymbolMap.TryGetValue(symbol, out bool isGeneratedCodeSymbol) ? isGeneratedCodeSymbol : _lazyIsGeneratedCodeSymbolMap.GetOrAdd(symbol, computeIsGeneratedCodeSymbol()); bool computeIsGeneratedCodeSymbol() { if (_generatedCodeAttribute != null && GeneratedCodeUtilities.IsGeneratedSymbolWithGeneratedCodeAttribute(symbol, _generatedCodeAttribute)) { return true; } foreach (var declaringRef in symbol.DeclaringSyntaxReferences) { if (!IsGeneratedOrHiddenCodeLocation(declaringRef.SyntaxTree, declaringRef.Span)) { return false; } } return true; } } [PerformanceSensitive( "https://github.com/dotnet/roslyn/pull/23637", AllowLocks = false)] protected bool IsGeneratedCode(SyntaxTree tree) { if (_treatAllCodeAsNonGeneratedCode) { return false; } Debug.Assert(_lazyGeneratedCodeFilesMap != null); bool isGenerated; if (!_lazyGeneratedCodeFilesMap.TryGetValue(tree, out isGenerated)) { isGenerated = _isGeneratedCode(tree, AnalyzerExecutor.CancellationToken); _lazyGeneratedCodeFilesMap.TryAdd(tree, isGenerated); } return isGenerated; } protected bool DoNotAnalyzeGeneratedCode => _doNotAnalyzeGeneratedCode; // Location is in generated code if either the containing tree is a generated code file OR if it is a hidden source location. protected bool IsGeneratedOrHiddenCodeLocation(SyntaxTree syntaxTree, TextSpan span) => IsGeneratedCode(syntaxTree) || IsHiddenSourceLocation(syntaxTree, span); protected bool IsHiddenSourceLocation(SyntaxTree syntaxTree, TextSpan span) => HasHiddenRegions(syntaxTree) && syntaxTree.IsHiddenPosition(span.Start); [PerformanceSensitive( "https://github.com/dotnet/roslyn/pull/23637", AllowLocks = false)] private bool HasHiddenRegions(SyntaxTree tree) { Debug.Assert(tree != null); if (_lazyTreesWithHiddenRegionsMap == null) { return false; } bool hasHiddenRegions; if (!_lazyTreesWithHiddenRegionsMap.TryGetValue(tree, out hasHiddenRegions)) { hasHiddenRegions = tree.HasHiddenRegions(); _lazyTreesWithHiddenRegionsMap.TryAdd(tree, hasHiddenRegions); } return hasHiddenRegions; } internal async Task GetAnalyzerActionCountsAsync(DiagnosticAnalyzer analyzer, CompilationOptions compilationOptions, CancellationToken cancellationToken) { var executor = AnalyzerExecutor.WithCancellationToken(cancellationToken); if (IsDiagnosticAnalyzerSuppressed(analyzer, compilationOptions, AnalyzerManager, executor)) { return AnalyzerActionCounts.Empty; } var analyzerActions = await AnalyzerManager.GetAnalyzerActionsAsync(analyzer, executor).ConfigureAwait(false); return new AnalyzerActionCounts(analyzerActions); } /// /// Returns true if all the diagnostics that can be produced by this analyzer are suppressed through options. /// internal static bool IsDiagnosticAnalyzerSuppressed( DiagnosticAnalyzer analyzer, CompilationOptions options, AnalyzerManager analyzerManager, AnalyzerExecutor analyzerExecutor) { return analyzerManager.IsDiagnosticAnalyzerSuppressed(analyzer, options, s_IsCompilerAnalyzerFunc, analyzerExecutor); } private static bool IsCompilerAnalyzer(DiagnosticAnalyzer analyzer) { return analyzer is CompilerDiagnosticAnalyzer; } public void Dispose() { this.CompilationEventQueue?.TryComplete(); this.DiagnosticQueue?.TryComplete(); _queueRegistration.Dispose(); } } /// /// Driver to execute diagnostic analyzers for a given compilation. /// It uses a of s to drive its analysis. /// internal partial class AnalyzerDriver : AnalyzerDriver where TLanguageKindEnum : struct { private readonly Func _getKind; private GroupedAnalyzerActions _lazyCoreActions; /// /// Create an analyzer driver. /// /// The set of analyzers to include in the analysis /// A delegate that returns the language-specific kind for a given syntax node /// AnalyzerManager to manage analyzers for the lifetime of analyzer host. /// Delegate to identify if the given trivia is a comment. internal AnalyzerDriver(ImmutableArray analyzers, Func getKind, AnalyzerManager analyzerManager, Func isComment) : base(analyzers, analyzerManager, isComment) { _getKind = getKind; } private GroupedAnalyzerActions CoreActions { get { if (_lazyCoreActions == null) { Interlocked.CompareExchange(ref _lazyCoreActions, GroupedAnalyzerActions.Create(base.AnalyzerActions), null); } return _lazyCoreActions; } } private static bool ShouldExecuteSyntaxNodeActions(GroupedAnalyzerActions coreActions, GroupedAnalyzerActions additionalActions, AnalysisScope analysisScope) { return coreActions.ShouldExecuteSyntaxNodeActions(analysisScope) || additionalActions.ShouldExecuteSyntaxNodeActions(analysisScope); } private static bool ShouldExecuteCodeBlockActions(GroupedAnalyzerActions coreActions, GroupedAnalyzerActions additionalActions, AnalysisScope analysisScope, ISymbol symbol) { return coreActions.ShouldExecuteCodeBlockActions(analysisScope, symbol) || additionalActions.ShouldExecuteCodeBlockActions(analysisScope, symbol); } private static bool ShouldExecuteOperationActions(GroupedAnalyzerActions coreActions, GroupedAnalyzerActions additionalActions, AnalysisScope analysisScope) { return coreActions.ShouldExecuteOperationActions(analysisScope) || additionalActions.ShouldExecuteOperationActions(analysisScope); } private static bool ShouldExecuteOperationBlockActions(GroupedAnalyzerActions coreActions, GroupedAnalyzerActions additionalActions, AnalysisScope analysisScope, ISymbol symbol) { return coreActions.ShouldExecuteOperationBlockActions(analysisScope, symbol) || additionalActions.ShouldExecuteOperationBlockActions(analysisScope, symbol); } /// /// Tries to execute syntax node, code block and operation actions for all declarations for the given symbol. /// /// /// True, if successfully executed the actions for the given analysis scope OR no actions were required to be executed for the given analysis scope. /// False, otherwise. /// protected override bool TryExecuteDeclaringReferenceActions( SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool isGeneratedCodeSymbol, AnalyzerActions additionalPerSymbolActionsOpt, CancellationToken cancellationToken) { var symbol = symbolEvent.Symbol; var additionalGroupedPerSymbolActions = GroupedAnalyzerActions.Create(additionalPerSymbolActionsOpt); var executeSyntaxNodeActions = ShouldExecuteSyntaxNodeActions(CoreActions, additionalGroupedPerSymbolActions, analysisScope); var executeCodeBlockActions = ShouldExecuteCodeBlockActions(CoreActions, additionalGroupedPerSymbolActions, analysisScope, symbol); var executeOperationActions = ShouldExecuteOperationActions(CoreActions, additionalGroupedPerSymbolActions, analysisScope); var executeOperationBlockActions = ShouldExecuteOperationBlockActions(CoreActions, additionalGroupedPerSymbolActions, analysisScope, symbol); var success = true; if (executeSyntaxNodeActions || executeOperationActions || executeCodeBlockActions || executeOperationBlockActions) { var declaringReferences = symbolEvent.DeclaringSyntaxReferences; for (var i = 0; i < declaringReferences.Length; i++) { cancellationToken.ThrowIfCancellationRequested(); var decl = declaringReferences[i]; if (analysisScope.FilterTreeOpt != null && analysisScope.FilterTreeOpt != decl.SyntaxTree) { continue; } var isInGeneratedCode = isGeneratedCodeSymbol || IsGeneratedOrHiddenCodeLocation(decl.SyntaxTree, decl.Span); if (isInGeneratedCode && DoNotAnalyzeGeneratedCode) { analysisStateOpt?.MarkDeclarationComplete(symbol, i, analysisScope.Analyzers); continue; } if (!TryExecuteDeclaringReferenceActions(decl, i, symbolEvent, analysisScope, analysisStateOpt, additionalGroupedPerSymbolActions, executeSyntaxNodeActions, executeOperationActions, executeCodeBlockActions, executeOperationBlockActions, isInGeneratedCode, cancellationToken)) { success = false; } } } else if (analysisStateOpt != null) { cancellationToken.ThrowIfCancellationRequested(); analysisStateOpt.MarkDeclarationsComplete(symbol, analysisScope.Analyzers); var declaringReferences = symbolEvent.DeclaringSyntaxReferences; for (var i = 0; i < declaringReferences.Length; i++) { var decl = declaringReferences[i]; ClearCachedAnalysisDataIfAnalyzed(decl, symbol, i, analysisStateOpt); } } return success; } private void ClearCachedAnalysisDataIfAnalyzed(SyntaxReference declaration, ISymbol symbol, int declarationIndex, AnalysisState analysisState) { Debug.Assert(analysisState != null); if (!analysisState.IsDeclarationComplete(symbol, declarationIndex)) { return; } CurrentCompilationData.ClearDeclarationAnalysisData(declaration); } private DeclarationAnalysisData ComputeDeclarationAnalysisData( ISymbol symbol, SyntaxReference declaration, SemanticModel semanticModel, AnalysisScope analysisScope, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var builder = ArrayBuilder.GetInstance(); SyntaxNode declaringReferenceSyntax = declaration.GetSyntax(cancellationToken); SyntaxNode topmostNodeForAnalysis = semanticModel.GetTopmostNodeForDiagnosticAnalysis(symbol, declaringReferenceSyntax); ComputeDeclarationsInNode(semanticModel, symbol, declaringReferenceSyntax, topmostNodeForAnalysis, builder, cancellationToken); ImmutableArray declarationInfos = builder.ToImmutableAndFree(); bool isPartialDeclAnalysis = analysisScope.FilterSpanOpt.HasValue && !analysisScope.ContainsSpan(topmostNodeForAnalysis.FullSpan); ImmutableArray nodesToAnalyze = GetSyntaxNodesToAnalyze(topmostNodeForAnalysis, symbol, declarationInfos, analysisScope, isPartialDeclAnalysis, semanticModel, AnalyzerExecutor); return new DeclarationAnalysisData(declaringReferenceSyntax, topmostNodeForAnalysis, declarationInfos, nodesToAnalyze, isPartialDeclAnalysis); } private static void ComputeDeclarationsInNode(SemanticModel semanticModel, ISymbol declaredSymbol, SyntaxNode declaringReferenceSyntax, SyntaxNode topmostNodeForAnalysis, ArrayBuilder builder, CancellationToken cancellationToken) { // We only care about the top level symbol declaration and its immediate member declarations. int? levelsToCompute = 2; var getSymbol = topmostNodeForAnalysis != declaringReferenceSyntax || declaredSymbol.Kind == SymbolKind.Namespace; semanticModel.ComputeDeclarationsInNode(topmostNodeForAnalysis, getSymbol, builder, cancellationToken, levelsToCompute); } /// /// Tries to execute syntax node, code block and operation actions for the given declaration. /// /// /// True, if successfully executed the actions for the given analysis scope OR no actions were required to be executed for the given analysis scope. /// False, otherwise. /// private bool TryExecuteDeclaringReferenceActions( SyntaxReference decl, int declarationIndex, SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, GroupedAnalyzerActions additionalPerSymbolActions, bool shouldExecuteSyntaxNodeActions, bool shouldExecuteOperationActions, bool shouldExecuteCodeBlockActions, bool shouldExecuteOperationBlockActions, bool isInGeneratedCode, CancellationToken cancellationToken) { Debug.Assert(shouldExecuteSyntaxNodeActions || shouldExecuteOperationActions || shouldExecuteCodeBlockActions || shouldExecuteOperationBlockActions); Debug.Assert(!isInGeneratedCode || !DoNotAnalyzeGeneratedCode); var symbol = symbolEvent.Symbol; SemanticModel semanticModel = analysisStateOpt != null ? CurrentCompilationData.GetOrCreateCachedSemanticModel(decl.SyntaxTree, symbolEvent.Compilation, cancellationToken) : symbolEvent.SemanticModel(decl); var cacheAnalysisData = analysisScope.Analyzers.Length < Analyzers.Length && (!analysisScope.FilterSpanOpt.HasValue || analysisScope.FilterSpanOpt.Value.Length >= decl.SyntaxTree.GetRoot(cancellationToken).Span.Length); var declarationAnalysisData = CurrentCompilationData.GetOrComputeDeclarationAnalysisData( decl, computeDeclarationAnalysisData: () => ComputeDeclarationAnalysisData(symbol, decl, semanticModel, analysisScope, cancellationToken), cacheAnalysisData: cacheAnalysisData); if (!analysisScope.ShouldAnalyze(declarationAnalysisData.TopmostNodeForAnalysis)) { return true; } var success = true; // Execute stateless syntax node actions. executeNodeActions(); // Execute actions in executable code: code block actions, operation actions and operation block actions. executeExecutableCodeActions(); // Mark completion if we successfully executed all actions and only if we are analyzing a span containing the entire syntax node. if (success && analysisStateOpt != null && !declarationAnalysisData.IsPartialAnalysis) { // Ensure that we do not mark declaration complete/clear state if cancellation was requested. // Other thread(s) might still be executing analysis, and clearing state could lead to corrupt execution // or unknown exceptions. cancellationToken.ThrowIfCancellationRequested(); foreach (var analyzer in analysisScope.Analyzers) { analysisStateOpt.MarkDeclarationComplete(symbol, declarationIndex, analyzer); } if (cacheAnalysisData) { ClearCachedAnalysisDataIfAnalyzed(decl, symbol, declarationIndex, analysisStateOpt); } } return success; void executeNodeActions() { if (shouldExecuteSyntaxNodeActions) { var nodesToAnalyze = declarationAnalysisData.DescendantNodesToAnalyze; foreach (var analyzer in analysisScope.Analyzers) { executeNodeActionsByKind(analyzer, nodesToAnalyze, CoreActions); executeNodeActionsByKind(analyzer, nodesToAnalyze, additionalPerSymbolActions); } } } void executeNodeActionsByKind(DiagnosticAnalyzer analyzer, ImmutableArray nodesToAnalyze, GroupedAnalyzerActions groupedActions) { if (groupedActions.NodeActionsByAnalyzerAndKind.TryGetValue(analyzer, out var nodeActionsByKind) && !nodeActionsByKind.IsEmpty) { if (!AnalyzerExecutor.TryExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, analyzer, semanticModel, _getKind, declarationAnalysisData.TopmostNodeForAnalysis.FullSpan, decl, declarationIndex, symbol, analysisScope, analysisStateOpt, isInGeneratedCode)) { success = false; } } } void executeExecutableCodeActions() { if (!shouldExecuteCodeBlockActions && !shouldExecuteOperationActions && !shouldExecuteOperationBlockActions) { return; } // Compute the executable code blocks of interest. var executableCodeBlocks = ImmutableArray.Empty; IEnumerable codeBlockActions = null; foreach (var declInNode in declarationAnalysisData.DeclarationsInNode) { if (declInNode.DeclaredNode == declarationAnalysisData.TopmostNodeForAnalysis || declInNode.DeclaredNode == declarationAnalysisData.DeclaringReferenceSyntax) { executableCodeBlocks = declInNode.ExecutableCodeBlocks; if (!executableCodeBlocks.IsEmpty) { if (shouldExecuteCodeBlockActions || shouldExecuteOperationBlockActions) { codeBlockActions = CoreActions.GetCodeBlockActions(analysisScope) .Concat(additionalPerSymbolActions.GetCodeBlockActions(analysisScope)); } // Execute operation actions. if (shouldExecuteOperationActions || shouldExecuteOperationBlockActions) { var operationBlocksToAnalyze = GetOperationBlocksToAnalyze(executableCodeBlocks, semanticModel, cancellationToken); var operationsToAnalyze = getOperationsToAnalyzeWithStackGuard(operationBlocksToAnalyze); if (!operationsToAnalyze.IsEmpty) { executeOperationsActions(operationsToAnalyze); executeOperationsBlockActions(operationBlocksToAnalyze, operationsToAnalyze, codeBlockActions); } } break; } } } executeCodeBlockActions(executableCodeBlocks, codeBlockActions); } ImmutableArray getOperationsToAnalyzeWithStackGuard(ImmutableArray operationBlocksToAnalyze) { try { return GetOperationsToAnalyze(operationBlocksToAnalyze); } catch (Exception ex) when (ex is InsufficientExecutionStackException || FatalError.ReportWithoutCrashUnlessCanceled(ex)) { // the exception filter will short-circuit if `ex` is `InsufficientExecutionStackException` (from OperationWalker) // and no non-fatal-watson will be logged as a result. var diagnostic = AnalyzerExecutor.CreateDriverExceptionDiagnostic(ex); var analyzer = this.Analyzers[0]; AnalyzerExecutor.OnAnalyzerException(ex, analyzer, diagnostic); return ImmutableArray.Empty; } } void executeOperationsActions(ImmutableArray operationsToAnalyze) { if (shouldExecuteOperationActions) { foreach (var analyzer in analysisScope.Analyzers) { executeOperationsActionsByKind(analyzer, operationsToAnalyze, CoreActions); executeOperationsActionsByKind(analyzer, operationsToAnalyze, additionalPerSymbolActions); } } } void executeOperationsActionsByKind(DiagnosticAnalyzer analyzer, ImmutableArray operationsToAnalyze, GroupedAnalyzerActions groupedActions) { if (groupedActions.OperationActionsByAnalyzerAndKind.TryGetValue(analyzer, out var operationActionsByKind) && !operationActionsByKind.IsEmpty) { if (!AnalyzerExecutor.TryExecuteOperationActions(operationsToAnalyze, operationActionsByKind, analyzer, semanticModel, declarationAnalysisData.TopmostNodeForAnalysis.FullSpan, decl, declarationIndex, symbol, analysisScope, analysisStateOpt, isInGeneratedCode)) { success = false; } } } void executeOperationsBlockActions(ImmutableArray operationBlocksToAnalyze, ImmutableArray operationsToAnalyze, IEnumerable codeBlockActions) { if (!shouldExecuteOperationBlockActions) { return; } foreach (var analyzerActions in codeBlockActions) { if (analyzerActions.OperationBlockStartActions.IsEmpty && analyzerActions.OperationBlockActions.IsEmpty && analyzerActions.OperationBlockEndActions.IsEmpty) { continue; } if (!AnalyzerExecutor.TryExecuteOperationBlockActions( analyzerActions.OperationBlockStartActions, analyzerActions.OperationBlockActions, analyzerActions.OperationBlockEndActions, analyzerActions.Analyzer, declarationAnalysisData.TopmostNodeForAnalysis, symbol, operationBlocksToAnalyze, operationsToAnalyze, semanticModel, decl, declarationIndex, analysisScope, analysisStateOpt, isInGeneratedCode)) { success = false; } } } void executeCodeBlockActions(ImmutableArray executableCodeBlocks, IEnumerable codeBlockActions) { if (executableCodeBlocks.IsEmpty || !shouldExecuteCodeBlockActions) { return; } foreach (var analyzerActions in codeBlockActions) { if (analyzerActions.CodeBlockStartActions.IsEmpty && analyzerActions.CodeBlockActions.IsEmpty && analyzerActions.CodeBlockEndActions.IsEmpty) { continue; } if (!AnalyzerExecutor.TryExecuteCodeBlockActions( analyzerActions.CodeBlockStartActions, analyzerActions.CodeBlockActions, analyzerActions.CodeBlockEndActions, analyzerActions.Analyzer, declarationAnalysisData.TopmostNodeForAnalysis, symbol, executableCodeBlocks, semanticModel, _getKind, decl, declarationIndex, analysisScope, analysisStateOpt, isInGeneratedCode)) { success = false; } } } } private static ImmutableArray GetSyntaxNodesToAnalyze( SyntaxNode declaredNode, ISymbol declaredSymbol, ImmutableArray declarationsInNode, AnalysisScope analysisScope, bool isPartialDeclAnalysis, SemanticModel semanticModel, AnalyzerExecutor analyzerExecutor) { // Eliminate descendant member declarations within declarations. // There will be separate symbols declared for the members. HashSet descendantDeclsToSkipOpt = null; bool first = true; foreach (var declInNode in declarationsInNode) { analyzerExecutor.CancellationToken.ThrowIfCancellationRequested(); if (declInNode.DeclaredNode != declaredNode) { // Might be: // (1) A field declaration statement with multiple fields declared. // If so, we execute syntax node analysis for entire field declaration (and its descendants) // if we processing the first field and skip syntax actions for remaining fields in the declaration. // (2) A namespace declaration statement with qualified name "namespace A.B { }" if (IsEquivalentSymbol(declaredSymbol, declInNode.DeclaredSymbol)) { if (first) { break; } return ImmutableArray.Empty; } // Compute the topmost node representing the syntax declaration for the member that needs to be skipped. var declarationNodeToSkip = declInNode.DeclaredNode; var declaredSymbolOfDeclInNode = declInNode.DeclaredSymbol ?? semanticModel.GetDeclaredSymbol(declInNode.DeclaredNode, analyzerExecutor.CancellationToken); if (declaredSymbolOfDeclInNode != null) { declarationNodeToSkip = semanticModel.GetTopmostNodeForDiagnosticAnalysis(declaredSymbolOfDeclInNode, declInNode.DeclaredNode); } descendantDeclsToSkipOpt = descendantDeclsToSkipOpt ?? new HashSet(); descendantDeclsToSkipOpt.Add(declarationNodeToSkip); } first = false; } bool shouldAddNode(SyntaxNode node) => descendantDeclsToSkipOpt == null || !descendantDeclsToSkipOpt.Contains(node); var nodeBuilder = ArrayBuilder.GetInstance(); foreach (var node in declaredNode.DescendantNodesAndSelf(descendIntoChildren: shouldAddNode, descendIntoTrivia: true)) { if (shouldAddNode(node) && (!isPartialDeclAnalysis || analysisScope.ShouldAnalyze(node))) { nodeBuilder.Add(node); } } return nodeBuilder.ToImmutableAndFree(); } private static bool IsEquivalentSymbol(ISymbol declaredSymbol, ISymbol otherSymbol) { if (declaredSymbol.Equals(otherSymbol)) { return true; } // GetSymbolInfo(name syntax) for "A" in "namespace A.B { }" sometimes returns a symbol which doesn't match // the symbol declared in the compilation. So we do an equivalence check for such namespace symbols. return otherSymbol != null && declaredSymbol.Kind == SymbolKind.Namespace && otherSymbol.Kind == SymbolKind.Namespace && declaredSymbol.Name == otherSymbol.Name && declaredSymbol.ToDisplayString() == otherSymbol.ToDisplayString(); } private static ImmutableArray GetOperationBlocksToAnalyze( ImmutableArray executableBlocks, SemanticModel semanticModel, CancellationToken cancellationToken) { ArrayBuilder operationBlocksToAnalyze = ArrayBuilder.GetInstance(); foreach (SyntaxNode executableBlock in executableBlocks) { IOperation operation = semanticModel.GetOperation(executableBlock, cancellationToken); if (operation != null) { operationBlocksToAnalyze.AddRange(operation); } } return operationBlocksToAnalyze.ToImmutableAndFree(); } private static ImmutableArray GetOperationsToAnalyze( ImmutableArray operationBlocks) { ArrayBuilder operationsToAnalyze = ArrayBuilder.GetInstance(); var checkParent = true; foreach (IOperation operationBlock in operationBlocks) { if (checkParent) { // Special handling for IMethodBodyOperation and IConstructorBodyOperation. // These are newly added root operation nodes for C# method and constructor bodies. // However, to avoid a breaking change for existing operation block analyzers, // we have decided to retain the current behavior of making operation block callbacks with the contained // method body and/or constructor initializer operation nodes. // Hence we detect here if the operation block is parented by IMethodBodyOperation or IConstructorBodyOperation and // add them to 'operationsToAnalyze' so that analyzers that explicitly register for these operation kinds // can get callbacks for these nodes. if (operationBlock.Parent != null) { switch (operationBlock.Parent.Kind) { case OperationKind.MethodBody: case OperationKind.ConstructorBody: operationsToAnalyze.Add(operationBlock.Parent); break; default: Debug.Fail($"Expected operation with kind '{operationBlock.Kind}' to be the root operation with null 'Parent', but instead it has a non-null Parent with kind '{operationBlock.Parent.Kind}'"); break; } checkParent = false; } } operationsToAnalyze.AddRange(operationBlock.DescendantsAndSelf()); } Debug.Assert(operationsToAnalyze.ToImmutableHashSet().Count == operationsToAnalyze.Count); return operationsToAnalyze.ToImmutableAndFree(); } } }