diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs index 936ff213ef001a7b21499ed0293e45dc965dc804..18bc3f7722c72f06cd56ea0b743f7e78ae186039 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs @@ -2884,9 +2884,9 @@ public override IEnumerable GetSymbolsWithName(Func predi #endregion - internal override AnalyzerDriver AnalyzerForLanguage(ImmutableArray analyzers, AnalyzerManager analyzerManager, CancellationToken cancellationToken) + internal override AnalyzerDriver AnalyzerForLanguage(ImmutableArray analyzers, AnalyzerManager analyzerManager) { - return new AnalyzerDriver(analyzers, n => n.Kind(), analyzerManager, cancellationToken); + return new AnalyzerDriver(analyzers, n => n.Kind(), analyzerManager); } internal void SymbolDeclaredEvent(Symbol symbol) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs index 75355a27fefe580fc1b0ef8754421bdd1d5769be..3c6a1b8a11218b1eab9142b4dec4909ccbffe40c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceFieldSymbol.cs @@ -40,11 +40,6 @@ internal sealed override bool HasComplete(CompletionPart part) return state.HasComplete(part); } - internal override void ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken) - { - state.DefaultForceComplete(this); - } - public abstract override string Name { get; } protected abstract DeclarationModifiers Modifiers { get; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs index d073916ee4d36f431df380aa6f776a3782240bb3..82064d86b775aeb8d1b3b033aef9e17e65bdf322 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs @@ -439,6 +439,38 @@ private void RegisterDeclaredCorTypes() } } + internal override bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedWithinSpan, CancellationToken cancellationToken = default(CancellationToken)) + { + if (this.IsGlobalNamespace) + { + return true; + } + + // Check if any namespace declaration block intersects with the given tree/span. + foreach (var syntaxRef in this.DeclaringSyntaxReferences) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (syntaxRef.SyntaxTree != tree) + { + continue; + } + + if (!definedWithinSpan.HasValue) + { + return true; + } + + var syntax = syntaxRef.GetSyntax(cancellationToken); + if (syntax.FullSpan.IntersectsWith(definedWithinSpan.Value)) + { + return true; + } + } + + return false; + } + private struct NameToSymbolMapBuilder { private readonly Dictionary _dictionary; diff --git a/src/Compilers/CSharp/Portable/Symbols/Symbol.cs b/src/Compilers/CSharp/Portable/Symbols/Symbol.cs index c8e044f6d74b7250ec447dc244d5771c101f16b8..c073f37f7f497a5acf9788b7d41f5c27efe9b0eb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Symbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Symbol.cs @@ -679,7 +679,13 @@ internal bool Dangerous_IsFromSomeCompilation internal virtual bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedWithinSpan, CancellationToken cancellationToken = default(CancellationToken)) { - foreach (var syntaxRef in this.DeclaringSyntaxReferences) + var declaringReferences = this.DeclaringSyntaxReferences; + if (this.IsImplicitlyDeclared && declaringReferences.Length == 0) + { + return this.ContainingSymbol.IsDefinedInSourceTree(tree, definedWithinSpan, cancellationToken); + } + + foreach (var syntaxRef in declaringReferences) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Compilers/CSharp/Portable/Symbols/SymbolCompletionState.cs b/src/Compilers/CSharp/Portable/Symbols/SymbolCompletionState.cs index 39d9e0d7fd99fe59a9b2ce903a98d3f463d4f290..37b99e2f0243855f0454072a886d1fac682b72c5 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SymbolCompletionState.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SymbolCompletionState.cs @@ -45,6 +45,9 @@ internal void DefaultForceComplete(Symbol symbol) { symbol.GetAttributes(); } + + // any other values are completion parts intended for other kinds of symbols + NotePartComplete(CompletionPart.All); } internal bool HasComplete(CompletionPart part) diff --git a/src/Compilers/Core/AnalyzerDriver/AnalyzerDriver.projitems b/src/Compilers/Core/AnalyzerDriver/AnalyzerDriver.projitems index 674684072afb5a15582c19b3e81c500030ec828a..9dd0d4298e2cc2d5ba8122dc93fca40dbfa9d19a 100644 --- a/src/Compilers/Core/AnalyzerDriver/AnalyzerDriver.projitems +++ b/src/Compilers/Core/AnalyzerDriver/AnalyzerDriver.projitems @@ -9,13 +9,7 @@ AnalyzerDriver - - - - - - \ No newline at end of file diff --git a/src/Compilers/Core/AnalyzerDriver/AnalyzerExecutor.cs b/src/Compilers/Core/AnalyzerDriver/AnalyzerExecutor.cs deleted file mode 100644 index 7175bea3107f5ea078da951e274c943491e3dc38..0000000000000000000000000000000000000000 --- a/src/Compilers/Core/AnalyzerDriver/AnalyzerExecutor.cs +++ /dev/null @@ -1,603 +0,0 @@ -// 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.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis.Collections; -using Roslyn.Utilities; -using System.Collections.Concurrent; - -namespace Microsoft.CodeAnalysis.Diagnostics -{ - /// - /// Contains the core execution logic for callbacks into analyzers. - /// - internal class AnalyzerExecutor - { - private const string DiagnosticCategory = "Compiler"; - - // internal for testing purposes only. - internal const string AnalyzerExceptionDiagnosticId = "AD0001"; - - private readonly Compilation _compilation; - private readonly AnalyzerOptions _analyzerOptions; - private readonly Action _addDiagnostic; - private readonly Action _onAnalyzerException; - private readonly AnalyzerManager _analyzerManager; - private readonly Func _isCompilerAnalyzer; - private readonly Func _getAnalyzerGateOpt; - private readonly ConcurrentDictionary _analyzerExecutionTimeMapOpt; - - private readonly CancellationToken _cancellationToken; - - /// - /// Creates to execute analyzer actions with given arguments - /// - /// Compilation to be used in the analysis. - /// Analyzer options. - /// Delegate to add analyzer diagnostics. - /// - /// Optional delegate which is invoked when an analyzer throws an exception. - /// Delegate can do custom tasks such as report the given analyzer exception diagnostic, report a non-fatal watson for the exception, etc. - /// - /// Delegate to determine if the given analyzer is compiler analyzer. - /// We need to special case the compiler analyzer at few places for performance reasons. - /// Analyzer manager to fetch supported diagnostics. - /// - /// Delegate to fetch the gate object to guard all callbacks into the analyzer. - /// It should return a unique gate object for the given analyzer instance for non-concurrent analyzers, and null otherwise. - /// All analyzer callbacks for non-concurrent analyzers will be guarded with a lock on the gate. - /// - /// Flag indicating whether we need to log analyzer execution time. - /// Cancellation token. - public static AnalyzerExecutor Create( - Compilation compilation, - AnalyzerOptions analyzerOptions, - Action addDiagnostic, - Action onAnalyzerException, - Func isCompilerAnalyzer, - AnalyzerManager analyzerManager, - Func getAnalyzerGate, - bool logExecutionTime = false, - CancellationToken cancellationToken = default(CancellationToken)) - { - return new AnalyzerExecutor(compilation, analyzerOptions, addDiagnostic, onAnalyzerException, isCompilerAnalyzer, analyzerManager, getAnalyzerGate, logExecutionTime, cancellationToken); - } - - /// - /// Creates to fetch . - /// - /// - /// Optional delegate which is invoked when an analyzer throws an exception. - /// Delegate can do custom tasks such as report the given analyzer exception diagnostic, report a non-fatal watson for the exception, etc. - /// - /// Analyzer manager to fetch supported diagnostics. - /// Flag indicating whether we need to log analyzer execution time. - /// Cancellation token. - public static AnalyzerExecutor CreateForSupportedDiagnostics( - Action onAnalyzerException, - AnalyzerManager analyzerManager, - bool logExecutionTime = false, - CancellationToken cancellationToken = default(CancellationToken)) - { - return new AnalyzerExecutor( - compilation: null, - analyzerOptions: null, - addDiagnostic: null, - isCompilerAnalyzer: null, - getAnalyzerGateOpt: null, - onAnalyzerException: onAnalyzerException, - analyzerManager: analyzerManager, - logExecutionTime: logExecutionTime, - cancellationToken: cancellationToken); - } - - private AnalyzerExecutor( - Compilation compilation, - AnalyzerOptions analyzerOptions, - Action addDiagnostic, - Action onAnalyzerException, - Func isCompilerAnalyzer, - AnalyzerManager analyzerManager, - Func getAnalyzerGateOpt, - bool logExecutionTime, - CancellationToken cancellationToken) - { - _compilation = compilation; - _analyzerOptions = analyzerOptions; - _addDiagnostic = addDiagnostic; - _onAnalyzerException = onAnalyzerException; - _isCompilerAnalyzer = isCompilerAnalyzer; - _analyzerManager = analyzerManager; - _getAnalyzerGateOpt = getAnalyzerGateOpt; - _analyzerExecutionTimeMapOpt = logExecutionTime ? new ConcurrentDictionary() : null; - _cancellationToken = cancellationToken; - } - - internal Compilation Compilation => _compilation; - internal AnalyzerOptions AnalyzerOptions => _analyzerOptions; - internal CancellationToken CancellationToken => _cancellationToken; - internal Action OnAnalyzerException => _onAnalyzerException; - internal ImmutableDictionary AnalyzerExecutionTimes => _analyzerExecutionTimeMapOpt.ToImmutableDictionary(); - - /// - /// Executes the for the given analyzer. - /// - /// Analyzer to get session wide analyzer actions. - /// Session scope to store register session wide analyzer actions. - /// - /// Note that this API doesn't execute any registered by the Initialize invocation. - /// Use API - /// to get execute these actions to get the per-compilation analyzer actions. - /// - public void ExecuteInitializeMethod(DiagnosticAnalyzer analyzer, HostSessionStartAnalysisScope sessionScope) - { - // The Initialize method should be run asynchronously in case it is not well behaved, e.g. does not terminate. - ExecuteAndCatchIfThrows(analyzer, () => analyzer.Initialize(new AnalyzerAnalysisContext(analyzer, sessionScope))); - } - - /// - /// Executes the compilation start actions. - /// - /// whose compilation start actions are to be executed. - /// Compilation scope to store the analyzer actions. - public void ExecuteCompilationStartActions(ImmutableArray actions, HostCompilationStartAnalysisScope compilationScope) - { - foreach (var startAction in actions) - { - _cancellationToken.ThrowIfCancellationRequested(); - ExecuteAndCatchIfThrows(startAction.Analyzer, - () => startAction.Action(new AnalyzerCompilationStartAnalysisContext(startAction.Analyzer, compilationScope, _compilation, _analyzerOptions, _cancellationToken))); - } - } - - /// - /// Executes compilation actions or compilation end actions. - /// - /// Compilation actions to be executed. - public void ExecuteCompilationActions(ImmutableArray compilationActions) - { - foreach (var endAction in compilationActions) - { - _cancellationToken.ThrowIfCancellationRequested(); - ExecuteAndCatchIfThrows(endAction.Analyzer, - () => endAction.Action(new CompilationAnalysisContext(_compilation, _analyzerOptions, _addDiagnostic, - d => IsSupportedDiagnostic(endAction.Analyzer, d), _cancellationToken))); - } - } - - /// - /// Executes the symbol actions on the given symbols. - /// - /// whose symbol actions are to be executed. - /// Symbols to be analyzed. - public void ExecuteSymbolActions(AnalyzerActions actions, IEnumerable symbols) - { - ExecuteSymbolActions(actions.SymbolActions, symbols); - } - - /// - /// Executes the symbol actions on the given symbols. - /// - /// Symbol actions to be executed. - /// Symbols to be analyzed. - public void ExecuteSymbolActions(ImmutableArray symbolActions, IEnumerable symbols) - { - foreach (var symbol in symbols) - { - ExecuteSymbolActions(symbolActions, symbol); - } - } - - /// - /// Executes the symbol actions on the given symbol. - /// - /// Symbol actions to be executed. - /// Symbol to be analyzed. - /// Optional overridden add diagnostic delegate. - public void ExecuteSymbolActions(ImmutableArray symbolActions, ISymbol symbol, Action overriddenAddDiagnostic = null) - { - var addDiagnostic = overriddenAddDiagnostic ?? _addDiagnostic; - - foreach (var symbolAction in symbolActions) - { - var action = symbolAction.Action; - var kinds = symbolAction.Kinds; - - if (kinds.Contains(symbol.Kind)) - { - _cancellationToken.ThrowIfCancellationRequested(); - ExecuteAndCatchIfThrows(symbolAction.Analyzer, - () => action(new SymbolAnalysisContext(symbol, _compilation, _analyzerOptions, addDiagnostic, - d => IsSupportedDiagnostic(symbolAction.Analyzer, d), _cancellationToken))); - } - } - } - - /// - /// Executes the semantic model actions on the given semantic model. - /// - /// whose semantic model actions are to be executed. - /// Semantic model to analyze. - public void ExecuteSemanticModelActions(AnalyzerActions actions, SemanticModel semanticModel) - { - ExecuteSemanticModelActions(actions.SemanticModelActions, semanticModel); - } - - /// - /// Executes the semantic model actions on the given semantic model. - /// - /// Semantic model actions to be executed. - /// Semantic model to analyze. - public void ExecuteSemanticModelActions(ImmutableArray semanticModelActions, SemanticModel semanticModel) - { - foreach (var semanticModelAction in semanticModelActions) - { - _cancellationToken.ThrowIfCancellationRequested(); - - // Catch Exception from action. - ExecuteAndCatchIfThrows(semanticModelAction.Analyzer, - () => semanticModelAction.Action(new SemanticModelAnalysisContext(semanticModel, _analyzerOptions, _addDiagnostic, - d => IsSupportedDiagnostic(semanticModelAction.Analyzer, d), _cancellationToken))); - } - } - - /// - /// Executes the syntax tree actions on the given syntax tree. - /// - /// whose syntax tree actions are to be executed. - /// Syntax tree to analyze. - public void ExecuteSyntaxTreeActions(AnalyzerActions actions, SyntaxTree tree) - { - ExecuteSyntaxTreeActions(actions.SyntaxTreeActions, tree); - } - - /// - /// Executes the syntax tree actions on the given syntax tree. - /// - /// Syntax tree actions to be executed. - /// Syntax tree to analyze. - public void ExecuteSyntaxTreeActions(ImmutableArray syntaxTreeActions, SyntaxTree tree) - { - foreach (var syntaxTreeAction in syntaxTreeActions) - { - _cancellationToken.ThrowIfCancellationRequested(); - - // Catch Exception from action. - ExecuteAndCatchIfThrows(syntaxTreeAction.Analyzer, - () => syntaxTreeAction.Action(new SyntaxTreeAnalysisContext(tree, _analyzerOptions, _addDiagnostic, - d => IsSupportedDiagnostic(syntaxTreeAction.Analyzer, d), _cancellationToken))); - } - } - - /// - /// Executes the syntax node actions on the given syntax nodes. - /// - /// whose code block start actions and end actions are to be executed. - /// Syntax nodes to be analyzed. - /// SemanticModel to be used in the analysis. - /// Delegate to compute language specific syntax kind for a syntax node. - public void ExecuteSyntaxNodeActions( - AnalyzerActions actions, - IEnumerable nodes, - SemanticModel semanticModel, - Func getKind) - where TLanguageKindEnum : struct - { - var syntaxNodeActions = actions.GetSyntaxNodeActions(); - - foreach (var syntaxNodeAction in syntaxNodeActions) - { - foreach (var node in nodes) - { - if (syntaxNodeAction.Kinds.Contains(getKind(node))) - { - ExecuteSyntaxNodeAction(syntaxNodeAction, node, semanticModel); - } - } - } - } - - private void ExecuteSyntaxNodeAction( - SyntaxNodeAnalyzerAction syntaxNodeAction, - SyntaxNode node, - SemanticModel semanticModel) - where TLanguageKindEnum : struct - { - var syntaxNodeContext = new SyntaxNodeAnalysisContext(node, semanticModel, _analyzerOptions, _addDiagnostic, - d => IsSupportedDiagnostic(syntaxNodeAction.Analyzer, d), _cancellationToken); - ExecuteAndCatchIfThrows(syntaxNodeAction.Analyzer, () => syntaxNodeAction.Action(syntaxNodeContext)); - } - - /// - /// Executes the given code block actions on all the executable code blocks for each declaration info in . - /// - /// whose code block start actions and end actions are to be executed. - /// Declarations to be analyzed. - /// SemanticModel to be shared amongst all actions. - /// Delegate to compute language specific syntax kind for a syntax node. - public void ExecuteCodeBlockActions( - AnalyzerActions actions, - IEnumerable declarationsInNode, - SemanticModel semanticModel, - Func getKind) - where TLanguageKindEnum : struct - { - var codeBlockStartActions = actions.GetCodeBlockStartActions(); - var codeBlockActions = actions.CodeBlockActions; - var codeBlockEndActions = actions.CodeBlockEndActions; - - if (!codeBlockStartActions.Any() && !codeBlockActions.Any() && !codeBlockEndActions.Any()) - { - return; - } - - foreach (var declInfo in declarationsInNode) - { - var declaredNode = declInfo.DeclaredNode; - var declaredSymbol = declInfo.DeclaredSymbol; - var executableCodeBlocks = declInfo.ExecutableCodeBlocks; - - if (declaredSymbol != null && declInfo.ExecutableCodeBlocks.Any()) - { - ExecuteCodeBlockActions(codeBlockStartActions, codeBlockActions, codeBlockEndActions, - declaredNode, declaredSymbol, executableCodeBlocks, semanticModel, getKind); - } - } - } - - internal void ExecuteCodeBlockActions( - IEnumerable> codeBlockStartActions, - IEnumerable codeBlockActions, - IEnumerable codeBlockEndActions, - SyntaxNode declaredNode, - ISymbol declaredSymbol, - ImmutableArray executableCodeBlocks, - SemanticModel semanticModel, - Func getKind) - where TLanguageKindEnum : struct - { - Debug.Assert(declaredNode != null); - Debug.Assert(declaredSymbol != null); - Debug.Assert(CanHaveExecutableCodeBlock(declaredSymbol)); - Debug.Assert(codeBlockStartActions.Any() || codeBlockEndActions.Any() || codeBlockActions.Any()); - Debug.Assert(executableCodeBlocks.Any()); - - // Compute the sets of code block end, code block, and stateful syntax node actions. - var blockEndActions = PooledHashSet.GetInstance(); - var blockActions = PooledHashSet.GetInstance(); - var executableNodeActions = ArrayBuilder>.GetInstance(); - - // Include the code block actions. - blockActions.AddAll(codeBlockActions); - - // Include the initial code block end actions. - blockEndActions.AddAll(codeBlockEndActions); - - // Include the stateful actions. - foreach (var da in codeBlockStartActions) - { - // Catch Exception from the start action. - ExecuteAndCatchIfThrows(da.Analyzer, () => - { - var codeBlockScope = new HostCodeBlockStartAnalysisScope(); - var blockStartContext = new AnalyzerCodeBlockStartAnalysisContext(da.Analyzer, - codeBlockScope, declaredNode, declaredSymbol, semanticModel, _analyzerOptions, _cancellationToken); - da.Action(blockStartContext); - blockEndActions.AddAll(codeBlockScope.CodeBlockEndActions); - executableNodeActions.AddRange(codeBlockScope.SyntaxNodeActions); - }); - } - - // Execute stateful executable node analyzers, if any. - if (executableNodeActions.Any()) - { - var executableNodeActionsByKind = GetNodeActionsByKind(executableNodeActions); - - var nodesToAnalyze = executableCodeBlocks.SelectMany(cb => cb.DescendantNodesAndSelf()); - ExecuteSyntaxNodeActions(nodesToAnalyze, executableNodeActionsByKind, semanticModel, getKind); - } - - executableNodeActions.Free(); - - ExecuteCodeBlockActions(blockActions, declaredNode, declaredSymbol, semanticModel); - ExecuteCodeBlockActions(blockEndActions, declaredNode, declaredSymbol, semanticModel); - } - - private void ExecuteCodeBlockActions(PooledHashSet blockActions, SyntaxNode declaredNode, ISymbol declaredSymbol, SemanticModel semanticModel) - { - foreach (var blockAction in blockActions) - { - ExecuteAndCatchIfThrows(blockAction.Analyzer, - () => blockAction.Action(new CodeBlockAnalysisContext(declaredNode, declaredSymbol, semanticModel, _analyzerOptions, _addDiagnostic, - d => IsSupportedDiagnostic(blockAction.Analyzer, d), _cancellationToken))); - } - - blockActions.Free(); - } - - internal static ImmutableDictionary>> GetNodeActionsByKind( - IEnumerable> nodeActions) - where TLanguageKindEnum : struct - { - Debug.Assert(nodeActions != null && nodeActions.Any()); - - var nodeActionsByKind = PooledDictionary>>.GetInstance(); - foreach (var nodeAction in nodeActions) - { - foreach (var kind in nodeAction.Kinds) - { - ArrayBuilder> actionsForKind; - if (!nodeActionsByKind.TryGetValue(kind, out actionsForKind)) - { - nodeActionsByKind.Add(kind, actionsForKind = ArrayBuilder>.GetInstance()); - } - - actionsForKind.Add(nodeAction); - } - } - - var tuples = nodeActionsByKind.Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.ToImmutableAndFree())); - var map = ImmutableDictionary.CreateRange(tuples); - nodeActionsByKind.Free(); - return map; - } - - internal void ExecuteSyntaxNodeActions( - IEnumerable nodesToAnalyze, - IDictionary>> nodeActionsByKind, - SemanticModel model, - Func getKind) - where TLanguageKindEnum : struct - { - Debug.Assert(nodeActionsByKind != null); - Debug.Assert(nodeActionsByKind.Any()); - - foreach (var child in nodesToAnalyze) - { - ImmutableArray> actionsForKind; - if (nodeActionsByKind.TryGetValue(getKind(child), out actionsForKind)) - { - foreach (var action in actionsForKind) - { - ExecuteSyntaxNodeAction(action, child, model); - } - } - } - } - - internal static bool CanHaveExecutableCodeBlock(ISymbol symbol) - { - switch (symbol.Kind) - { - case SymbolKind.Method: - case SymbolKind.Event: - case SymbolKind.Property: - case SymbolKind.NamedType: - return true; - - case SymbolKind.Field: - Debug.Assert(((IFieldSymbol)symbol).AssociatedSymbol == null); - return true; - - default: - return false; - } - } - - internal void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Action analyze) - { - object gate = _getAnalyzerGateOpt?.Invoke(analyzer); - if (gate != null) - { - lock (gate) - { - ExecuteAndCatchIfThrows_NoLock(analyzer, analyze); - } - } - else - { - ExecuteAndCatchIfThrows_NoLock(analyzer, analyze); - } - } - - private void ExecuteAndCatchIfThrows_NoLock(DiagnosticAnalyzer analyzer, Action analyze) - { - try - { - Stopwatch timer = null; - if (_analyzerExecutionTimeMapOpt != null) - { - timer = Stopwatch.StartNew(); - } - - analyze(); - - if (timer != null) - { - timer.Stop(); - - _analyzerExecutionTimeMapOpt.AddOrUpdate(analyzer, timer.Elapsed, (a, accumulatedTime) => accumulatedTime + timer.Elapsed); - } - } - catch (Exception e) when (!IsCanceled(e, _cancellationToken)) - { - // Diagnostic for analyzer exception. - var diagnostic = GetAnalyzerExceptionDiagnostic(analyzer, e); - _onAnalyzerException(e, analyzer, diagnostic); - } - } - - internal static bool IsCanceled(Exception ex, CancellationToken cancellationToken) - { - return (ex as OperationCanceledException)?.CancellationToken == cancellationToken; - } - - internal static Diagnostic GetAnalyzerExceptionDiagnostic(DiagnosticAnalyzer analyzer, Exception e) - { - var analyzerName = analyzer.ToString(); - - // TODO: It is not ideal to create a new descriptor per analyzer exception diagnostic instance. - // However, until we add a LongMessage field to the Diagnostic, we are forced to park the instance specific description onto the Descriptor's Description field. - // This requires us to create a new DiagnosticDescriptor instance per diagnostic instance. - var descriptor = new DiagnosticDescriptor(AnalyzerExceptionDiagnosticId, - title: AnalyzerDriverResources.AnalyzerFailure, - messageFormat: AnalyzerDriverResources.AnalyzerThrows, - description: string.Format(AnalyzerDriverResources.AnalyzerThrowsDescription, analyzerName, e.ToString()), - category: DiagnosticCategory, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - customTags: WellKnownDiagnosticTags.AnalyzerException); - - return Diagnostic.Create(descriptor, Location.None, analyzerName, e.GetType(), e.Message); - } - - internal static bool IsAnalyzerExceptionDiagnostic(Diagnostic diagnostic) - { - if (diagnostic.Id == AnalyzerExceptionDiagnosticId) - { -#pragma warning disable RS0013 // Its ok to realize the Descriptor for analyzer exception diagnostics, which are descriptor based and also rare. - foreach (var tag in diagnostic.Descriptor.CustomTags) -#pragma warning restore RS0013 - { - if (tag == WellKnownDiagnosticTags.AnalyzerException) - { - return true; - } - } - } - - return false; - } - - internal static bool AreEquivalentAnalyzerExceptionDiagnostics(Diagnostic exceptionDiagnostic, Diagnostic other) - { - // We need to have custom de-duplication logic for diagnostics generated for analyzer exceptions. - // We create a new descriptor instance per each analyzer exception diagnostic instance (see comments in method "GetAnalyzerExceptionDiagnostic" above). - // This is primarily to allow us to embed exception stack trace in the diagnostic description. - // However, this might mean that two exception diagnostics which are equivalent in terms of ID and Message, might not have equal description strings. - // We want to classify such diagnostics as equal for de-duplication purpose to reduce the noise in output. - - Debug.Assert(IsAnalyzerExceptionDiagnostic(exceptionDiagnostic)); - - if (!IsAnalyzerExceptionDiagnostic(other)) - { - return false; - } - - return exceptionDiagnostic.Id == other.Id && - exceptionDiagnostic.Severity == other.Severity && - exceptionDiagnostic.GetMessage() == other.GetMessage(); - } - - private bool IsSupportedDiagnostic(DiagnosticAnalyzer analyzer, Diagnostic diagnostic) - { - Debug.Assert(_isCompilerAnalyzer != null); - - return _analyzerManager.IsSupportedDiagnostic(analyzer, diagnostic, _isCompilerAnalyzer, this); - } - } -} diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index a066556aa35e26d0fa5d78398b47ff15aab25c16..83fd0cd887003abb0e17a5612ae4ac968bdd9c16 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -132,6 +132,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -203,12 +225,8 @@ - - - - @@ -233,7 +251,6 @@ - diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs index 972370070cff87f639946b50f73dcd30695873fa..fc16742ac7dba478f9f4d368a7981c4c464c34d0 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs @@ -332,6 +332,15 @@ internal class CodeAnalysisResources { } } + /// + /// Looks up a localized string similar to Argument contains duplicate analyzer instances.. + /// + internal static string DuplicateAnalyzerInstances { + get { + return ResourceManager.GetString("DuplicateAnalyzerInstances", resourceCulture); + } + } + /// /// Looks up a localized string similar to Empty or invalid file name. /// @@ -611,6 +620,15 @@ internal class CodeAnalysisResources { } } + /// + /// Looks up a localized string similar to Syntax tree doesn't belong to the underlying 'Compilation'.. + /// + internal static string InvalidTree { + get { + return ResourceManager.GetString("InvalidTree", resourceCulture); + } + } + /// /// Looks up a localized string similar to Argument to '/keepalive' option is not a 32-bit integer.. /// @@ -1043,6 +1061,15 @@ internal class CodeAnalysisResources { } } + /// + /// Looks up a localized string similar to Argument contains an analyzer instance that does not belong to the 'Analyzers' for this CompilationWithAnalyzers instance.. + /// + internal static string UnsupportedAnalyzerInstance { + get { + return ResourceManager.GetString("UnsupportedAnalyzerInstance", resourceCulture); + } + } + /// /// Looks up a localized string similar to Reported diagnostic with ID '{0}' is not supported by the analyzer.. /// diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index 1d7c5fde2fa73556cd52795ab1d0078ebb17dccc..fdf5736f4a148a3bbd774dea27ad1b34e0fd3e01 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -460,4 +460,13 @@ No analyzers found + + Argument contains duplicate analyzer instances. + + + Argument contains an analyzer instance that does not belong to the 'Analyzers' for this CompilationWithAnalyzers instance. + + + Syntax tree doesn't belong to the underlying 'Compilation'. + \ No newline at end of file diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index 4d22ef9a857d4e2107a95443cc1f96aaba7c4234..216b6a16d9fb488eb76caa86514e8d2db68ebc90 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -367,7 +367,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat analyzerExceptionDiagnostics = new ConcurrentSet(); Action addExceptionDiagnostic = diagnostic => analyzerExceptionDiagnostics.Add(diagnostic); var analyzerOptions = new AnalyzerOptions(ImmutableArray.CastUp(additionalTextFiles)); - analyzerDriver = AnalyzerDriver.Create(compilation, analyzers, analyzerOptions, analyzerManager, addExceptionDiagnostic, Arguments.ReportAnalyzer, out compilation, analyzerCts.Token); + analyzerDriver = AnalyzerDriver.CreateAndAttachToCompilation(compilation, analyzers, analyzerOptions, analyzerManager, addExceptionDiagnostic, Arguments.ReportAnalyzer, out compilation, analyzerCts.Token); getAnalyzerDiagnostics = () => analyzerDriver.GetDiagnosticsAsync().Result; } diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index afda6c41bf4779aef5197219b5b26b050cc13fb4..3d9b46bc0a53ec4b86f02e1ea00027f8bf0fe10f 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -113,7 +113,7 @@ public abstract partial class Compilation return set; } - internal abstract AnalyzerDriver AnalyzerForLanguage(ImmutableArray analyzers, AnalyzerManager analyzerManager, CancellationToken cancellationToken); + internal abstract AnalyzerDriver AnalyzerForLanguage(ImmutableArray analyzers, AnalyzerManager analyzerManager); /// /// Gets the source language ("C#" or "Visual Basic"). diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..68c34c4dd5f6dc1ba27e7c858de4e5f87ba32350 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisResult.cs @@ -0,0 +1,208 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Stores the results of analyzer execution: + /// 1. Local and non-local diagnostics, per-analyzer. + /// 2. Analyzer execution times, if requested. + /// + internal partial class AnalysisResult + { + private readonly object _gate = new object(); + private readonly Dictionary _analyzerExecutionTimeOpt; + + private Dictionary>> _localSemanticDiagnosticsOpt = null; + private Dictionary>> _localSyntaxDiagnosticsOpt = null; + private Dictionary> _nonLocalDiagnosticsOpt = null; + + public AnalysisResult(bool logAnalyzerExecutionTime, ImmutableArray analyzers) + { + _analyzerExecutionTimeOpt = logAnalyzerExecutionTime ? CreateAnalyzerExecutionTimeMap(analyzers) : null; + } + + private static Dictionary CreateAnalyzerExecutionTimeMap(ImmutableArray analyzers) + { + var map = new Dictionary(analyzers.Length); + foreach (var analyzer in analyzers) + { + map[analyzer] = default(TimeSpan); + } + + return map; + } + + public TimeSpan GetAnalyzerExecutionTime(DiagnosticAnalyzer analyzer) + { + Debug.Assert(_analyzerExecutionTimeOpt != null); + + lock (_gate) + { + return _analyzerExecutionTimeOpt[analyzer]; + } + } + + public void StoreAnalysisResult(AnalysisScope analysisScope, AnalyzerDriver driver) + { + foreach (var analyzer in analysisScope.Analyzers) + { + // Dequeue reported analyzer diagnostics from the driver and store them in our maps. + var syntaxDiagnostics = driver.DequeueLocalDiagnostics(analyzer, syntax: true); + var semanticDiagnostics = driver.DequeueLocalDiagnostics(analyzer, syntax: false); + var compilationDiagnostics = driver.DequeueNonLocalDiagnostics(analyzer); + + lock (_gate) + { + if (syntaxDiagnostics.Length > 0 || semanticDiagnostics.Length > 0 || compilationDiagnostics.Length > 0) + { + UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, ref _localSyntaxDiagnosticsOpt); + UpdateLocalDiagnostics_NoLock(analyzer, semanticDiagnostics, ref _localSemanticDiagnosticsOpt); + UpdateNonLocalDiagnostics_NoLock(analyzer, compilationDiagnostics); + } + + if (_analyzerExecutionTimeOpt != null) + { + _analyzerExecutionTimeOpt[analyzer] += driver.ResetAnalyzerExecutionTime(analyzer); + } + } + } + } + + private void UpdateLocalDiagnostics_NoLock(DiagnosticAnalyzer analyzer, ImmutableArray diagnostics, ref Dictionary>> lazyLocalDiagnostics) + { + if (diagnostics.IsEmpty) + { + return; + } + + lazyLocalDiagnostics = lazyLocalDiagnostics ?? new Dictionary>>(); + + foreach (var diagsByTree in diagnostics.GroupBy(d => d.Location.SourceTree)) + { + var tree = diagsByTree.Key; + + Dictionary> allDiagnostics; + if (!lazyLocalDiagnostics.TryGetValue(tree, out allDiagnostics)) + { + allDiagnostics = new Dictionary>(); + lazyLocalDiagnostics[tree] = allDiagnostics; + } + + List analyzerDiagnostics; + if (!allDiagnostics.TryGetValue(analyzer, out analyzerDiagnostics)) + { + analyzerDiagnostics = new List(); + allDiagnostics[analyzer] = analyzerDiagnostics; + } + + analyzerDiagnostics.AddRange(diagsByTree); + } + } + + private void UpdateNonLocalDiagnostics_NoLock(DiagnosticAnalyzer analyzer, ImmutableArray diagnostics) + { + if (diagnostics.IsEmpty) + { + return; + } + + _nonLocalDiagnosticsOpt = _nonLocalDiagnosticsOpt ?? new Dictionary>(); + + List currentDiagnostics; + if (!_nonLocalDiagnosticsOpt.TryGetValue(analyzer, out currentDiagnostics)) + { + currentDiagnostics = new List(); + _nonLocalDiagnosticsOpt[analyzer] = currentDiagnostics; + } + + currentDiagnostics.AddRange(diagnostics); + } + + public ImmutableArray GetDiagnostics(AnalysisScope analysisScope, bool getLocalDiagnostics, bool getNonLocalDiagnostics) + { + lock (_gate) + { + return GetDiagnostics_NoLock(analysisScope, getLocalDiagnostics, getNonLocalDiagnostics); + } + } + + private ImmutableArray GetDiagnostics_NoLock(AnalysisScope analysisScope, bool getLocalDiagnostics, bool getNonLocalDiagnostics) + { + Debug.Assert(getLocalDiagnostics || getNonLocalDiagnostics); + + var builder = ImmutableArray.CreateBuilder(); + if (getLocalDiagnostics) + { + if (!analysisScope.IsTreeAnalysis) + { + AddAllLocalDiagnostics_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); + AddAllLocalDiagnostics_NoLock(_localSemanticDiagnosticsOpt, analysisScope, builder); + } + else if (analysisScope.IsSyntaxOnlyTreeAnalysis) + { + AddLocalDiagnosticsForPartialAnalysis_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder); + } + else + { + AddLocalDiagnosticsForPartialAnalysis_NoLock(_localSemanticDiagnosticsOpt, analysisScope, builder); + } + } + + if (getNonLocalDiagnostics && _nonLocalDiagnosticsOpt != null) + { + AddDiagnostics_NoLock(_nonLocalDiagnosticsOpt, analysisScope, builder); + } + + return builder.ToImmutableArray(); + } + + private static void AddAllLocalDiagnostics_NoLock( + Dictionary>> localDiagnostics, + AnalysisScope analysisScope, + ImmutableArray.Builder builder) + { + if (localDiagnostics != null) + { + foreach (var localDiagsByTree in localDiagnostics.Values) + { + AddDiagnostics_NoLock(localDiagsByTree, analysisScope, builder); + } + } + } + + private static void AddLocalDiagnosticsForPartialAnalysis_NoLock( + Dictionary>> localDiagnostics, + AnalysisScope analysisScope, + ImmutableArray.Builder builder) + { + Dictionary> diagnosticsForTree; + if (localDiagnostics != null && localDiagnostics.TryGetValue(analysisScope.FilterTreeOpt, out diagnosticsForTree)) + { + AddDiagnostics_NoLock(diagnosticsForTree, analysisScope, builder); + } + } + + private static void AddDiagnostics_NoLock( + Dictionary> diagnostics, + AnalysisScope analysisScope, + ImmutableArray.Builder builder) + { + Debug.Assert(diagnostics != null); + + foreach (var analyzer in analysisScope.Analyzers) + { + List diagnosticsByAnalyzer; + if (diagnostics.TryGetValue(analyzer, out diagnosticsByAnalyzer)) + { + builder.AddRange(diagnosticsByAnalyzer); + } + } + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs new file mode 100644 index 0000000000000000000000000000000000000000..3f5a23f121407fb5ad5c3687b63cd3a208725ba0 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisScope.cs @@ -0,0 +1,142 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Scope for analyzer execution. + /// This scope could either be the entire compilation for all analyzers (command line build) or + /// could be scoped to a specific tree/span and/or a subset of analyzers (CompilationWithAnalyzers). + /// + internal class AnalysisScope + { + public SyntaxTree FilterTreeOpt { get; private set; } + public TextSpan? FilterSpanOpt { get; private set; } + + public ImmutableArray Analyzers { get; private set; } + + /// + /// Syntax trees on which we need to perform syntax analysis. + /// + public IEnumerable SyntaxTrees { get; private set; } + + public bool ConcurrentAnalysis { get; private set; } + + /// + /// True if we need to categorize diagnostics into local and non-local diagnostics and track the analyzer reporting each diagnostic. + /// + public bool CategorizeDiagnostics { get; private set; } + + /// + /// True if we need to perform only syntax analysis for a single tree. + /// + public bool IsSyntaxOnlyTreeAnalysis { get; private set; } + + /// + /// True if we need to perform analysis for a single tree. + /// + public bool IsTreeAnalysis => FilterTreeOpt != null; + + public AnalysisScope(Compilation compilation, ImmutableArray analyzers, bool concurrentAnalysis, bool categorizeDiagnostics) + { + SyntaxTrees = compilation.SyntaxTrees; + Analyzers = analyzers; + ConcurrentAnalysis = concurrentAnalysis; + CategorizeDiagnostics = categorizeDiagnostics; + + FilterTreeOpt = null; + FilterSpanOpt = null; + IsSyntaxOnlyTreeAnalysis = false; + } + + public AnalysisScope(ImmutableArray analyzers, SyntaxTree filterTree, TextSpan? filterSpan, bool syntaxAnalysis, bool concurrentAnalysis, bool categorizeDiagnostics) + { + Debug.Assert(filterTree != null); + + SyntaxTrees = SpecializedCollections.SingletonEnumerable(filterTree); + Analyzers = analyzers; + FilterTreeOpt = filterTree; + FilterSpanOpt = filterSpan; + IsSyntaxOnlyTreeAnalysis = syntaxAnalysis; + ConcurrentAnalysis = concurrentAnalysis; + CategorizeDiagnostics = categorizeDiagnostics; + } + + public static bool ShouldSkipSymbolAnalysis(ISymbol symbol) + { + // Skip symbol actions for implicitly declared symbols and non-source symbols. + return symbol.IsImplicitlyDeclared || symbol.DeclaringSyntaxReferences.All(s => s.SyntaxTree == null); + } + + public static bool ShouldSkipDeclarationAnalysis(ISymbol symbol) + { + // Skip syntax actions for implicitly declared symbols, except for implicitly declared global namespace symbols. + return symbol.IsImplicitlyDeclared && + !((symbol.Kind == SymbolKind.Namespace && ((INamespaceSymbol)symbol).IsGlobalNamespace)); + } + + public bool ShouldAnalyze(SyntaxTree tree) + { + return FilterTreeOpt == null || FilterTreeOpt == tree; + } + + public bool ShouldAnalyze(ISymbol symbol) + { + if (FilterTreeOpt == null) + { + return true; + } + + foreach (var location in symbol.Locations) + { + if (location.SourceTree != null && FilterTreeOpt == location.SourceTree && ShouldInclude(location.SourceSpan)) + { + return true; + } + } + + return false; + } + + public bool ShouldAnalyze(SyntaxNode node) + { + if (FilterTreeOpt == null) + { + return true; + } + + return ShouldInclude(node.FullSpan); + } + + private bool ShouldInclude(TextSpan filterSpan) + { + return !FilterSpanOpt.HasValue || FilterSpanOpt.Value.IntersectsWith(filterSpan); + } + + public bool ContainsSpan(TextSpan filterSpan) + { + return !FilterSpanOpt.HasValue || FilterSpanOpt.Value.Contains(filterSpan); + } + + public bool ShouldInclude(Diagnostic diagnostic) + { + if (FilterTreeOpt == null) + { + return true; + } + + if (!diagnostic.Location.IsInSource || diagnostic.Location.SourceTree != FilterTreeOpt) + { + return false; + } + + return ShouldInclude(diagnostic.Location.SourceSpan); + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.AnalyzerStateData.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.AnalyzerStateData.cs new file mode 100644 index 0000000000000000000000000000000000000000..dae31bc067984347fed0e9fa785ca63c9159dd4c --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.AnalyzerStateData.cs @@ -0,0 +1,52 @@ +// 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.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + internal partial class AnalysisState + { + /// + /// Stores the partial analysis state for a specific event/symbol/tree for a specific analyzer. + /// + internal class AnalyzerStateData + { + /// + /// Current state of analysis. + /// + public StateKind StateKind { get; private set; } + + /// + /// Set of completed actions. + /// + public HashSet ProcessedActions { get; private set; } + + public AnalyzerStateData() + { + StateKind = StateKind.InProcess; + ProcessedActions = new HashSet(); + } + + public virtual void SetStateKind(StateKind stateKind) + { + StateKind = stateKind; + } + + /// + /// Resets the from to . + /// This method must be invoked after successful analysis completion AND on analysis cancellation. + /// + public void ResetToReadyState() + { + SetStateKind(StateKind.ReadyToProcess); + } + + public virtual void Free() + { + this.StateKind = StateKind.ReadyToProcess; + this.ProcessedActions.Clear(); + } + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs new file mode 100644 index 0000000000000000000000000000000000000000..971d8a32eb04e9ca901fafb1af9cfed16785bf95 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.PerAnalyzerState.cs @@ -0,0 +1,264 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Stores the current partial analysis state for an analyzer. + /// + internal partial class AnalysisState + { + private class PerAnalyzerState + { + private readonly object _gate = new object(); + private readonly Dictionary _pendingEvents = new Dictionary(); + private readonly Dictionary _pendingSymbols = new Dictionary(); + private readonly Dictionary _pendingDeclarations = new Dictionary(); + private Dictionary _lazyPendingSyntaxAnalysisTrees = null; + + private readonly ObjectPool _analyzerStateDataPool = new ObjectPool(() => new AnalyzerStateData()); + private readonly ObjectPool _declarationAnalyzerStateDataPool = new ObjectPool(() => new DeclarationAnalyzerStateData()); + + public PerAnalyzerState(ObjectPool analyzerStateDataPool, ObjectPool declarationAnalyzerStateDataPool) + { + _analyzerStateDataPool = analyzerStateDataPool; + _declarationAnalyzerStateDataPool = declarationAnalyzerStateDataPool; + } + + public IEnumerable PendingEvents_NoLock =>_pendingEvents.Keys; + + public bool HasPendingSyntaxAnalysis(SyntaxTree treeOpt) + { + lock (_gate) + { + return _lazyPendingSyntaxAnalysisTrees != null && + (treeOpt != null ? _lazyPendingSyntaxAnalysisTrees.ContainsKey(treeOpt) : _lazyPendingSyntaxAnalysisTrees.Count > 0); + } + } + + public bool HasPendingSymbolAnalysis(ISymbol symbol) + { + lock (_gate) + { + return _pendingSymbols.ContainsKey(symbol); + } + } + + private bool TryStartProcessingEntity(TAnalysisEntity analysisEntity, Dictionary pendingEntities, ObjectPool pool, out TAnalyzerStateData newState) + where TAnalyzerStateData : AnalyzerStateData, new() + { + lock (_gate) + { + return TryStartProcessingEntity_NoLock(analysisEntity, pendingEntities, pool, out newState); + } + } + + private static bool TryStartProcessingEntity_NoLock(TAnalysisEntity analysisEntity, Dictionary pendingEntities, ObjectPool pool, out TAnalyzerStateData state) + where TAnalyzerStateData : AnalyzerStateData + { + if (pendingEntities.TryGetValue(analysisEntity, out state) && + (state == null || state.StateKind == StateKind.ReadyToProcess)) + { + if (state == null) + { + state = pool.Allocate(); + } + + state.SetStateKind(StateKind.InProcess); + Debug.Assert(state.StateKind == StateKind.InProcess); + pendingEntities[analysisEntity] = state; + return true; + } + + state = null; + return false; + } + + private void MarkEntityProcessed(TAnalysisEntity analysisEntity, Dictionary pendingEntities, ObjectPool pool) + where TAnalyzerStateData : AnalyzerStateData + { + lock (_gate) + { + MarkEntityProcessed_NoLock(analysisEntity, pendingEntities, pool); + } + } + + private static void MarkEntityProcessed_NoLock(TAnalysisEntity analysisEntity, Dictionary pendingEntities, ObjectPool pool) + where TAnalyzerStateData : AnalyzerStateData + { + TAnalyzerStateData state; + if (pendingEntities.TryGetValue(analysisEntity, out state)) + { + pendingEntities.Remove(analysisEntity); + if (state != null) + { + state.Free(); + pool.Free(state); + } + } + } + + private bool IsEntityFullyProcessed(TAnalysisEntity analysisEntity, Dictionary pendingEntities) + where TAnalyzerStateData : AnalyzerStateData + { + lock (_gate) + { + return IsEntityFullyProcessed_NoLock(analysisEntity, pendingEntities); + } + } + + private static bool IsEntityFullyProcessed_NoLock(TAnalysisEntity analysisEntity, Dictionary pendingEntities) + where TAnalyzerStateData : AnalyzerStateData + { + return !pendingEntities.ContainsKey(analysisEntity); + } + + public bool TryStartProcessingEvent(CompilationEvent compilationEvent, out AnalyzerStateData state) + { + return TryStartProcessingEntity(compilationEvent, _pendingEvents, _analyzerStateDataPool, out state); + } + + public void MarkEventComplete(CompilationEvent compilationEvent) + { + MarkEntityProcessed(compilationEvent, _pendingEvents, _analyzerStateDataPool); + } + + public bool TryStartAnalyzingSymbol(ISymbol symbol, out AnalyzerStateData state) + { + return TryStartProcessingEntity(symbol, _pendingSymbols, _analyzerStateDataPool, out state); + } + + public void MarkSymbolComplete(ISymbol symbol) + { + MarkEntityProcessed(symbol, _pendingSymbols, _analyzerStateDataPool); + } + + public bool TryStartAnalyzingDeclaration(SyntaxReference decl, out DeclarationAnalyzerStateData state) + { + return TryStartProcessingEntity(decl.GetSyntax(), _pendingDeclarations, _declarationAnalyzerStateDataPool, out state); + } + + public void MarkDeclarationComplete(SyntaxReference decl) + { + MarkEntityProcessed(decl.GetSyntax(), _pendingDeclarations, _declarationAnalyzerStateDataPool); + } + + public bool TryStartSyntaxAnalysis(SyntaxTree tree, out AnalyzerStateData state) + { + Debug.Assert(_lazyPendingSyntaxAnalysisTrees != null); + return TryStartProcessingEntity(tree, _lazyPendingSyntaxAnalysisTrees, _analyzerStateDataPool, out state); + } + + public void MarkSyntaxAnalysisComplete(SyntaxTree tree) + { + if (_lazyPendingSyntaxAnalysisTrees != null) + { + MarkEntityProcessed(tree, _lazyPendingSyntaxAnalysisTrees, _analyzerStateDataPool); + } + } + + public void MarkDeclarationsComplete(ISymbol symbol) + { + lock (_gate) + { + foreach (var syntaxRef in symbol.DeclaringSyntaxReferences) + { + MarkEntityProcessed_NoLock(syntaxRef.GetSyntax(), _pendingDeclarations, _declarationAnalyzerStateDataPool); + } + } + } + + public void OnCompilationEventGenerated_NoLock(CompilationEvent compilationEvent, ActionCounts actionCounts) + { + var symbolEvent = compilationEvent as SymbolDeclaredCompilationEvent; + if (symbolEvent != null) + { + var needsAnalysis = false; + var symbol = symbolEvent.Symbol; + if (!AnalysisScope.ShouldSkipSymbolAnalysis(symbol) && actionCounts.SymbolActionsCount > 0) + { + needsAnalysis = true; + _pendingSymbols[symbol] = null; + } + + if (!AnalysisScope.ShouldSkipDeclarationAnalysis(symbol) && + (actionCounts.SyntaxNodeActionsCount > 0 || + actionCounts.CodeBlockActionsCount > 0 || + actionCounts.CodeBlockStartActionsCount > 0)) + { + foreach (var syntaxRef in symbol.DeclaringSyntaxReferences) + { + needsAnalysis = true; + _pendingDeclarations[syntaxRef.GetSyntax()]= null; + } + } + + if (!needsAnalysis) + { + return; + } + } + else if (compilationEvent is CompilationStartedEvent) + { + if (actionCounts.SyntaxTreeActionsCount > 0) + { + var map = new Dictionary(); + foreach (var tree in compilationEvent.Compilation.SyntaxTrees) + { + map[tree] = null; + } + + _lazyPendingSyntaxAnalysisTrees = map; + } + + if (actionCounts.CompilationActionsCount == 0) + { + return; + } + } + + _pendingEvents[compilationEvent] = null; + } + + public bool IsEventAnalyzed(CompilationEvent compilationEvent) + { + return IsEntityFullyProcessed(compilationEvent, _pendingEvents); + } + + public void OnSymbolDeclaredEventProcessed(SymbolDeclaredCompilationEvent symbolDeclaredEvent) + { + lock (_gate) + { + OnSymbolDeclaredEventProcessed_NoLock(symbolDeclaredEvent); + } + } + + private void OnSymbolDeclaredEventProcessed_NoLock(SymbolDeclaredCompilationEvent symbolDeclaredEvent) + { + // Check if the symbol event has been completely processed or not. + + // Have the symbol actions executed? + if (!IsEntityFullyProcessed_NoLock(symbolDeclaredEvent.Symbol, _pendingSymbols)) + { + return; + } + + // Have the node/code block actions executed for all symbol declarations? + foreach (var syntaxRef in symbolDeclaredEvent.Symbol.DeclaringSyntaxReferences) + { + if (!IsEntityFullyProcessed_NoLock(syntaxRef.GetSyntax(), _pendingDeclarations)) + { + return; + } + } + + // Mark the symbol event completely processed. + MarkEntityProcessed_NoLock(symbolDeclaredEvent, _pendingEvents, _analyzerStateDataPool); + } + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.StateKind.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.StateKind.cs new file mode 100644 index 0000000000000000000000000000000000000000..040be9b236a6b393b868001a23f1c784bc7dd956 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.StateKind.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + internal partial class AnalysisState + { + /// + /// State kind of per-analyzer tracking an analyzer's partial analysis state. + /// An analysis state object can be in one of the following states: + /// 1. Completely unprocessed: + /// 2. Currently being processed: + /// 3. Partially processed by one or more older requests that was either completed or cancelled: + /// 4. Fully processed: We don't need a state kind to represent fully processed state as the analysis state object is discarded once fully processed. + /// + internal enum StateKind + { + /// + /// Ready for processing. + /// Indicates it is either completely unprocessed or partially processed by one or more older requests that was either completed or cancelled. + /// + ReadyToProcess, + + /// + /// Currently being processed. + /// + InProcess + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.SyntaxReferenceAnalyzerStateData.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.SyntaxReferenceAnalyzerStateData.cs new file mode 100644 index 0000000000000000000000000000000000000000..81ca30f9baef3b1d0699e3a47218dd7305f1c281 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.SyntaxReferenceAnalyzerStateData.cs @@ -0,0 +1,100 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + internal partial class AnalysisState + { + /// + /// Stores the partial analysis state for a specific symbol declaration for a specific analyzer. + /// + internal sealed class DeclarationAnalyzerStateData : SyntaxNodeAnalyzerStateData + { + /// + /// Partial analysis state for code block actions executed on the declaration. + /// + public CodeBlockAnalyzerStateData CodeBlockAnalysisState { get; set; } + + public DeclarationAnalyzerStateData() + { + CodeBlockAnalysisState = new CodeBlockAnalyzerStateData(); + } + + public override void SetStateKind(StateKind stateKind) + { + CodeBlockAnalysisState.SetStateKind(stateKind); + base.SetStateKind(stateKind); + } + + public override void Free() + { + base.Free(); + CodeBlockAnalysisState.Free(); + } + } + + /// + /// Stores the partial analysis state for syntax node actions executed on the declaration. + /// + internal class SyntaxNodeAnalyzerStateData : AnalyzerStateData + { + public HashSet ProcessedNodes { get; set; } + public SyntaxNode CurrentNode { get; set; } + + public SyntaxNodeAnalyzerStateData() + { + CurrentNode = null; + ProcessedNodes = new HashSet(); + } + + public void ClearNodeAnalysisState() + { + CurrentNode = null; + ProcessedActions.Clear(); + } + + public override void Free() + { + base.Free(); + CurrentNode = null; + ProcessedNodes.Clear(); + } + } + + /// + /// Stores the partial analysis state for code block actions executed on the declaration. + /// + internal sealed class CodeBlockAnalyzerStateData : AnalyzerStateData + { + public SyntaxNodeAnalyzerStateData ExecutableNodesAnalysisState { get; private set; } + + public ImmutableHashSet CurrentCodeBlockEndActions { get; set; } + public ImmutableHashSet CurrentCodeBlockNodeActions { get; set; } + + public CodeBlockAnalyzerStateData() + { + ExecutableNodesAnalysisState = new SyntaxNodeAnalyzerStateData(); + CurrentCodeBlockEndActions = null; + CurrentCodeBlockNodeActions = null; + } + + public override void SetStateKind(StateKind stateKind) + { + ExecutableNodesAnalysisState.SetStateKind(stateKind); + base.SetStateKind(stateKind); + } + + public override void Free() + { + base.Free(); + ExecutableNodesAnalysisState.Free(); + CurrentCodeBlockEndActions = null; + CurrentCodeBlockNodeActions = null; + } + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs new file mode 100644 index 0000000000000000000000000000000000000000..85dee0cea5b09baac4e0aa158a7cf53f524a463d --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs @@ -0,0 +1,572 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Stores the partial analysis state for analyzers executed on a specific compilation. + /// + internal partial class AnalysisState + { + private readonly object _gate; + + /// + /// Per-analyzer analysis state map. + /// + private readonly ImmutableDictionary _analyzerStateMap; + + /// + /// Compilation events corresponding to source tree, that are not completely processed for all analyzers. + /// Events are dropped as and when they are fully processed by all analyzers. + /// + private readonly Dictionary> _pendingSourceEvents; + + /// + /// Compilation events corresponding to the compilation (compilation start and completed events), that are not completely processed for all analyzers. + /// + private readonly HashSet _pendingNonSourceEvents; + + /// + /// Action counts per-analyzer. + /// + private ImmutableDictionary _lazyAnalyzerActionCountsMap; + + + /// + /// Cached semantic model for the compilation trees. + /// PERF: This cache enables us to re-use semantic model's bound node cache across analyzer execution and diagnostic queries. + /// + private readonly ConditionalWeakTable _semanticModelsMap; + + private readonly ObjectPool> _compilationEventsPool; + + public AnalysisState(ImmutableArray analyzers) + { + _gate = new object(); + _analyzerStateMap = CreateAnalyzerStateMap(analyzers); + _pendingSourceEvents = new Dictionary>(); + _pendingNonSourceEvents = new HashSet(); + _lazyAnalyzerActionCountsMap = null; + _semanticModelsMap = new ConditionalWeakTable(); + _compilationEventsPool = new ObjectPool>(() => new HashSet()); + } + + private static ImmutableDictionary CreateAnalyzerStateMap(ImmutableArray analyzers) + { + var analyzerStateDataPool = new ObjectPool(() => new AnalyzerStateData()); + var declarationAnalyzerStateDataPool = new ObjectPool(() => new DeclarationAnalyzerStateData()); + + var map = ImmutableDictionary.CreateBuilder(); + foreach (var analyzer in analyzers) + { + map[analyzer] = new PerAnalyzerState(analyzerStateDataPool, declarationAnalyzerStateDataPool); + } + + return map.ToImmutable(); + } + + public SemanticModel GetOrCreateCachedSemanticModel(SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken) + { + return _semanticModelsMap.GetValue(tree, + t => + { + var mappedModel = compilation.GetSemanticModel(t); + + // Invoke GetDiagnostics to populate the compilation's CompilationEvent queue. + mappedModel.GetDiagnostics(null, cancellationToken); + + return mappedModel; + }); + } + + public async Task OnCompilationEventsGeneratedAsync(ImmutableArray compilationEvents, AnalyzerDriver driver, CancellationToken cancellationToken) + { + await EnsureAnalyzerActionCountsInitializedAsync(driver, cancellationToken).ConfigureAwait(false); + + lock (_gate) + { + OnCompilationEventsGenerated_NoLock(compilationEvents, driver, cancellationToken); + } + } + + private void OnCompilationEventsGenerated_NoLock(ImmutableArray compilationEvents, AnalyzerDriver driver, CancellationToken cancellationToken) + { + Debug.Assert(_lazyAnalyzerActionCountsMap != null); + + // Add the events to our global pending events map. + AddToEventsMap_NoLock(compilationEvents); + + // Mark the events for analysis for each analyzer. + foreach (var kvp in _analyzerStateMap) + { + var analyzer = kvp.Key; + var analyzerState = kvp.Value; + var actionCounts = _lazyAnalyzerActionCountsMap[analyzer]; + + foreach (var compilationEvent in compilationEvents) + { + if (HasActionsForEvent(compilationEvent, actionCounts)) + { + analyzerState.OnCompilationEventGenerated_NoLock(compilationEvent, actionCounts); + } + } + } + } + + private void AddToEventsMap_NoLock(ImmutableArray compilationEvents) + { + foreach (var compilationEvent in compilationEvents) + { + UpdateEventsMap_NoLock(compilationEvent, add: true); + } + } + + private void UpdateEventsMap_NoLock(CompilationEvent compilationEvent, bool add) + { + var symbolEvent = compilationEvent as SymbolDeclaredCompilationEvent; + if (symbolEvent != null) + { + // Add/remove symbol events. + // Any diagnostics request for a tree should trigger symbol and syntax node analysis for symbols with at least one declaring reference in the tree. + foreach (var location in symbolEvent.Symbol.Locations) + { + if (location.SourceTree != null) + { + if (add) + { + AddPendingSourceEvent_NoLock(location.SourceTree, compilationEvent); + } + else + { + RemovePendingSourceEvent_NoLock(location.SourceTree, compilationEvent); + } + } + } + } + else + { + // Add/remove compilation unit completed events. + var compilationUnitCompletedEvent = compilationEvent as CompilationUnitCompletedEvent; + if (compilationUnitCompletedEvent != null) + { + var tree = compilationUnitCompletedEvent.SemanticModel.SyntaxTree; + if (add) + { + AddPendingSourceEvent_NoLock(tree, compilationEvent); + } + else + { + RemovePendingSourceEvent_NoLock(tree, compilationEvent); + } + } + else if (compilationEvent is CompilationStartedEvent || compilationEvent is CompilationCompletedEvent) + { + // Add/remove compilation events. + if (add) + { + _pendingNonSourceEvents.Add(compilationEvent); + } + else + { + _pendingNonSourceEvents.Remove(compilationEvent); + } + } + else + { + throw new InvalidOperationException("Unexpected compilation event of type " + compilationEvent.GetType().Name); + } + } + } + + private void AddPendingSourceEvent_NoLock(SyntaxTree tree, CompilationEvent compilationEvent) + { + HashSet currentEvents; + if (!_pendingSourceEvents.TryGetValue(tree, out currentEvents)) + { + currentEvents = _compilationEventsPool.Allocate(); + _pendingSourceEvents[tree] = currentEvents; + } + + currentEvents.Add(compilationEvent); + } + + private void RemovePendingSourceEvent_NoLock(SyntaxTree tree, CompilationEvent compilationEvent) + { + HashSet currentEvents; + if (_pendingSourceEvents.TryGetValue(tree, out currentEvents)) + { + if (currentEvents.Remove(compilationEvent) && currentEvents.Count == 0) + { + _compilationEventsPool.Free(currentEvents); + _pendingSourceEvents.Remove(tree); + } + } + } + + private async Task EnsureAnalyzerActionCountsInitializedAsync(AnalyzerDriver driver, CancellationToken cancellationToken) + { + if (_lazyAnalyzerActionCountsMap == null) + { + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var analyzer in _analyzerStateMap.Keys) + { + var actionCounts = await driver.GetAnalyzerActionCountsAsync(analyzer, cancellationToken).ConfigureAwait(false); + builder.Add(analyzer, actionCounts); + } + + Interlocked.CompareExchange(ref _lazyAnalyzerActionCountsMap, builder.ToImmutable(), null); + } + } + + internal async Task GetAnalyzerActionCountsAsync(DiagnosticAnalyzer analyzer, AnalyzerDriver driver, CancellationToken cancellationToken) + { + await EnsureAnalyzerActionCountsInitializedAsync(driver, cancellationToken).ConfigureAwait(false); + return _lazyAnalyzerActionCountsMap[analyzer]; + } + + private static bool HasActionsForEvent(CompilationEvent compilationEvent, ActionCounts actionCounts) + { + if (compilationEvent is CompilationStartedEvent) + { + return actionCounts.CompilationActionsCount > 0 || + actionCounts.SyntaxTreeActionsCount > 0; + } + else if (compilationEvent is CompilationCompletedEvent) + { + return actionCounts.CompilationEndActionsCount > 0; + } + else if (compilationEvent is SymbolDeclaredCompilationEvent) + { + return actionCounts.CodeBlockActionsCount > 0 || + actionCounts.CodeBlockStartActionsCount > 0 || + actionCounts.SymbolActionsCount > 0 || + actionCounts.SyntaxNodeActionsCount > 0; + } + else + { + return actionCounts.SemanticModelActionsCount > 0; + } + } + + private void OnSymbolDeclaredEventProcessed(SymbolDeclaredCompilationEvent symbolDeclaredEvent, ImmutableArray analyzers) + { + foreach (var analyzer in analyzers) + { + _analyzerStateMap[analyzer].OnSymbolDeclaredEventProcessed(symbolDeclaredEvent); + } + } + + /// + /// Invoke this method at completion of event processing for the given analysis scope. + /// It updates the analysis state of this event for each analyzer and if the event has been fully processed for all analyzers, then removes it from our event cache. + /// + public void OnCompilationEventProcessed(CompilationEvent compilationEvent, AnalysisScope analysisScope) + { + // Analyze if the symbol and all its declaring syntax references are analyzed. + var symbolDeclaredEvent = compilationEvent as SymbolDeclaredCompilationEvent; + if (symbolDeclaredEvent != null) + { + OnSymbolDeclaredEventProcessed(symbolDeclaredEvent, analysisScope.Analyzers); + } + + // Check if event is fully analyzed for all analyzers. + foreach (var analyzerState in _analyzerStateMap.Values) + { + if (!analyzerState.IsEventAnalyzed(compilationEvent)) + { + return; + } + } + + // Remove the event from event map. + lock (_gate) + { + UpdateEventsMap_NoLock(compilationEvent, add: false); + } + } + + /// + /// Gets pending events for given set of analyzers for the given syntax tree. + /// + public ImmutableArray GetPendingEvents(ImmutableArray analyzers, SyntaxTree tree) + { + lock (_gate) + { + return GetPendingEvents_NoLock(analyzers, tree); + } + } + + private HashSet GetPendingEvents_NoLock(ImmutableArray analyzers) + { + var uniqueEvents = _compilationEventsPool.Allocate(); + foreach (var analyzer in analyzers) + { + var analysisState = _analyzerStateMap[analyzer]; + foreach (var pendingEvent in analysisState.PendingEvents_NoLock) + { + uniqueEvents.Add(pendingEvent); + } + } + + return uniqueEvents; + } + + /// + /// Gets pending events for given set of analyzers for the given syntax tree. + /// + private ImmutableArray GetPendingEvents_NoLock(ImmutableArray analyzers, SyntaxTree tree) + { + HashSet compilationEventsForTree; + if (_pendingSourceEvents.TryGetValue(tree, out compilationEventsForTree)) + { + if (compilationEventsForTree?.Count > 0) + { + HashSet pendingEvents = null; + try + { + pendingEvents = GetPendingEvents_NoLock(analyzers); + if (pendingEvents.Count > 0) + { + pendingEvents.IntersectWith(compilationEventsForTree); + return pendingEvents.ToImmutableArray(); + } + } + finally + { + Free(pendingEvents); + } + } + } + + return ImmutableArray.Empty; + } + + /// + /// Gets all pending events for given set of analyzers. + /// + /// + /// Indicates if source events (symbol declared, compilation unit completed event) should be included. + /// Indicates if compilation wide events (compilation started and completed event) should be included. + public ImmutableArray GetPendingEvents(ImmutableArray analyzers, bool includeSourceEvents, bool includeNonSourceEvents) + { + lock (_gate) + { + return GetPendingEvents_NoLock(analyzers, includeSourceEvents, includeNonSourceEvents); + } + } + + private ImmutableArray GetPendingEvents_NoLock(ImmutableArray analyzers, bool includeSourceEvents, bool includeNonSourceEvents) + { + HashSet pendingEvents = null, uniqueEvents = null; + try + { + pendingEvents = GetPendingEvents_NoLock(analyzers); + if (pendingEvents.Count == 0) + { + return ImmutableArray.Empty; + } + + uniqueEvents = _compilationEventsPool.Allocate(); + + if (includeSourceEvents) + { + foreach (var compilationEvents in _pendingSourceEvents.Values) + { + foreach (var compilationEvent in compilationEvents) + { + uniqueEvents.Add(compilationEvent); + } + } + } + + if (includeNonSourceEvents) + { + foreach (var compilationEvent in _pendingNonSourceEvents) + { + uniqueEvents.Add(compilationEvent); + } + } + + uniqueEvents.IntersectWith(pendingEvents); + return uniqueEvents.ToImmutableArray(); + } + finally + { + Free(pendingEvents); + Free(uniqueEvents); + } + } + + private void Free(HashSet events) + { + if (events != null) + { + events.Clear(); + _compilationEventsPool.Free(events); + } + } + + /// + /// Returns true if we have any pending syntax analysis for given analysis scope. + /// + public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) + { + if (analysisScope.IsTreeAnalysis && !analysisScope.IsSyntaxOnlyTreeAnalysis) + { + return false; + } + + foreach (var analyzer in analysisScope.Analyzers) + { + if (_analyzerStateMap[analyzer].HasPendingSyntaxAnalysis(analysisScope.FilterTreeOpt)) + { + return true; + } + } + + return false; + } + + /// + /// Returns true if we have any pending symbol analysis for given analysis scope. + /// + public bool HasPendingSymbolAnalysis(AnalysisScope analysisScope) + { + Debug.Assert(analysisScope.FilterTreeOpt != null); + + var symbolDeclaredEvents = GetPendingSymbolDeclaredEvents(analysisScope.FilterTreeOpt); + foreach (var symbolDeclaredEvent in symbolDeclaredEvents) + { + if (analysisScope.ShouldAnalyze(symbolDeclaredEvent.Symbol)) + { + foreach (var analyzer in analysisScope.Analyzers) + { + if (_analyzerStateMap[analyzer].HasPendingSymbolAnalysis(symbolDeclaredEvent.Symbol)) + { + return true; + } + } + } + } + + return false; + } + + private ImmutableArray GetPendingSymbolDeclaredEvents(SyntaxTree tree) + { + Debug.Assert(tree != null); + + lock (_gate) + { + HashSet compilationEvents; + if (!_pendingSourceEvents.TryGetValue(tree, out compilationEvents)) + { + return ImmutableArray.Empty; + } + + return compilationEvents.OfType().ToImmutableArray(); + } + } + + /// + /// Attempts to start processing a compilation event for the given analyzer. + /// + /// + /// Returns false if the event has already been processed for the analyzer OR is currently being processed by another task. + /// If true, then it returns a non-null representing partial analysis state for the given event for the given analyzer. + /// + public bool TryStartProcessingEvent(CompilationEvent compilationEvent, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) + { + return _analyzerStateMap[analyzer].TryStartProcessingEvent(compilationEvent, out state); + } + + /// + /// Marks the given event as fully analyzed for the given analyzer. + /// + public void MarkEventComplete(CompilationEvent compilationEvent, DiagnosticAnalyzer analyzer) + { + _analyzerStateMap[analyzer].MarkEventComplete(compilationEvent); + } + + /// + /// Attempts to start processing a symbol for the given analyzer's symbol actions. + /// + /// + /// Returns false if the symbol has already been processed for the analyzer OR is currently being processed by another task. + /// If true, then it returns a non-null representing partial analysis state for the given symbol for the given analyzer. + /// + public bool TryStartAnalyzingSymbol(ISymbol symbol, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) + { + return _analyzerStateMap[analyzer].TryStartAnalyzingSymbol(symbol, out state); + } + + /// + /// Marks the given symbol as fully analyzed for the given analyzer. + /// + public void MarkSymbolComplete(ISymbol symbol, DiagnosticAnalyzer analyzer) + { + _analyzerStateMap[analyzer].MarkSymbolComplete(symbol); + } + + /// + /// Attempts to start processing a symbol declaration for the given analyzer's syntax node and code block actions. + /// + /// + /// Returns false if the declaration has already been processed for the analyzer OR is currently being processed by another task. + /// If true, then it returns a non-null representing partial analysis state for the given declaration for the given analyzer. + /// + public bool TryStartAnalyzingDeclaration(SyntaxReference decl, DiagnosticAnalyzer analyzer, out DeclarationAnalyzerStateData state) + { + return _analyzerStateMap[analyzer].TryStartAnalyzingDeclaration(decl, out state); + } + + /// + /// Marks the given symbol declaration as fully analyzed for the given analyzer. + /// + public void MarkDeclarationComplete(SyntaxReference decl, DiagnosticAnalyzer analyzer) + { + _analyzerStateMap[analyzer].MarkDeclarationComplete(decl); + } + + /// + /// Marks all the symbol declarations for the given symbol as fully analyzed for all the given analyzers. + /// + public void MarkDeclarationsComplete(ISymbol symbol, IEnumerable analyzers) + { + foreach (var analyzer in analyzers) + { + _analyzerStateMap[analyzer].MarkDeclarationsComplete(symbol); + } + } + + /// + /// Attempts to start processing a syntax tree for the given analyzer's syntax tree actions. + /// + /// + /// Returns false if the tree has already been processed for the analyzer OR is currently being processed by another task. + /// If true, then it returns a non-null representing partial syntax analysis state for the given tree for the given analyzer. + /// + public bool TryStartSyntaxAnalysis(SyntaxTree tree, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) + { + return _analyzerStateMap[analyzer].TryStartSyntaxAnalysis(tree, out state); + } + + /// + /// Marks the given tree as fully syntactically analyzed for the given analyzer. + /// + public void MarkSyntaxAnalysisComplete(SyntaxTree tree, DiagnosticAnalyzer analyzer) + { + _analyzerStateMap[analyzer].MarkSyntaxAnalysisComplete(tree); + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 8202fd31b69ace310de3856207f13c9acebd16ca..46224fc463197535bc8314e0226193d0e9c264d1 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -1,7 +1,5 @@ // 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 Microsoft.CodeAnalysis.Collections; -using Roslyn.Utilities; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -11,6 +9,8 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -18,28 +18,39 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// Driver to execute diagnostic analyzers for a given compilation. /// It uses a of s to drive its analysis. /// - internal abstract class AnalyzerDriver : IDisposable + internal abstract partial class AnalyzerDriver : IDisposable { internal static readonly ConditionalWeakTable SuppressMessageStateByCompilation = new ConditionalWeakTable(); // Protect against vicious analyzers that provide large values for SymbolKind. private const int MaxSymbolKind = 100; - private readonly ImmutableArray _analyzers; - private readonly CancellationTokenRegistration _queueRegistration; + protected readonly ImmutableArray analyzers; protected readonly AnalyzerManager analyzerManager; - // Lazy fields initialized in Initialize() API - private Compilation _compilation; + // Lazy fields + private CancellationTokenRegistration _queueRegistration; protected AnalyzerExecutor analyzerExecutor; internal AnalyzerActions analyzerActions; 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; + /// + /// 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. /// @@ -51,53 +62,178 @@ internal abstract class AnalyzerDriver : IDisposable private readonly int _workerCount = Environment.ProcessorCount; /// - /// The compilation queue to create the compilation with via WithEventQueue. + /// Events queue for analyzer execution. /// - public AsyncQueue CompilationEventQueue { get; } + public AsyncQueue CompilationEventQueue { get; private set; } /// - /// An async queue that is fed the diagnostics as they are computed. + /// that is fed the diagnostics as they are computed. /// - public AsyncQueue DiagnosticQueue { get; } + public DiagnosticQueue DiagnosticQueue { get; private set; } /// - /// Initializes the compilation for the analyzer driver. - /// It also computes and initializes and . - /// Finally, it initializes and starts the for the driver. + /// Create an analyzer driver. /// - /// - /// NOTE: This method must only be invoked from . - /// - private void Initialize(Compilation comp, AnalyzerExecutor analyzerExecutor, CancellationToken cancellationToken) + /// The set of analyzers to include in the analysis + /// AnalyzerManager to manage analyzers for analyzer host's lifetime. + protected AnalyzerDriver(ImmutableArray analyzers, AnalyzerManager analyzerManager) + { + this.analyzers = analyzers; + this.analyzerManager = analyzerManager; + } + + /// + /// 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, CancellationToken cancellationToken) { try { - Debug.Assert(_compilation == null); - Debug.Assert(comp.EventQueue == this.CompilationEventQueue); + Debug.Assert(_initializeTask == null); - _compilation = comp; this.analyzerExecutor = analyzerExecutor; + this.DiagnosticQueue = diagnosticQueue; // Compute the set of effective actions based on suppression, and running the initial analyzers - var analyzerActionsTask = GetAnalyzerActionsAsync(_analyzers, analyzerManager, analyzerExecutor); - var initializeTask = analyzerActionsTask.ContinueWith(t => + var analyzerActionsTask = GetAnalyzerActionsAsync(analyzers, analyzerManager, analyzerExecutor); + _initializeTask = analyzerActionsTask.ContinueWith(t => { this.analyzerActions = t.Result; _symbolActionsByKind = MakeSymbolActionsByKind(); _semanticModelActionsMap = MakeSemanticModelActionsByAnalyzer(); + _syntaxTreeActionsMap = MakeSyntaxTreeActionsByAnalyzer(); _compilationActionsMap = MakeCompilationActionsByAnalyzer(this.analyzerActions.CompilationActions); _compilationEndActionsMap = MakeCompilationActionsByAnalyzer(this.analyzerActions.CompilationEndActions); }, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Default); // create the primary driver task. cancellationToken.ThrowIfCancellationRequested(); - _primaryTask = Task.Run(async () => + + _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, + bool categorizeDiagnostics, + CancellationToken cancellationToken) + { + Debug.Assert(_initializeTask == null); + + var diagnosticQueue = DiagnosticQueue.Create(categorizeDiagnostics); + var addDiagnostic = GetDiagnosticSinkWithSuppression(diagnosticQueue.Enqueue, compilation); + var addLocalDiagnosticOpt = categorizeDiagnostics ? GetDiagnosticSinkWithSuppression(diagnosticQueue.EnqueueLocal, compilation) : null; + var addNonLocalDiagnosticOpt = categorizeDiagnostics ? GetDiagnosticSinkWithSuppression(diagnosticQueue.EnqueueNonLocal, compilation) : null; + + Action newOnAnalyzerException; + if (analysisOptions.OnAnalyzerException != null) + { + // Wrap onAnalyzerException to pass in filtered diagnostic. + var comp = compilation; + newOnAnalyzerException = (ex, analyzer, diagnostic) => + analysisOptions.OnAnalyzerException(ex, analyzer, GetFilteredDiagnostic(diagnostic, comp)); + } + else + { + // Add exception diagnostic to regular diagnostic bag. + newOnAnalyzerException = (ex, analyzer, diagnostic) => addDiagnostic(diagnostic); + } + + // Assume all analyzers are non-thread safe. + var singleThreadedAnalyzerToGateMap = ImmutableDictionary.CreateRange(analyzers.Select(a => KeyValuePair.Create(a, new object()))); + + if (analysisOptions.LogAnalyzerExecutionTime) + { + // If we are reporting detailed analyzer performance numbers, then do a dummy invocation of Compilation.GetTypeByMetadataName API upfront. + // This API seems to cause a severe hit for the first analyzer invoking it and hence introduces lot of noise in the computed analyzer execution times. + var unused = compilation.GetTypeByMetadataName("System.Object"); + } + + Func getAnalyzerGate = analyzer => singleThreadedAnalyzerToGateMap[analyzer]; + var analyzerExecutor = AnalyzerExecutor.Create(compilation, analysisOptions.AnalyzerOptions ?? AnalyzerOptions.Empty, addDiagnostic, newOnAnalyzerException, IsCompilerAnalyzer, + analyzerManager, getAnalyzerGate, analysisOptions.LogAnalyzerExecutionTime, addLocalDiagnosticOpt, addNonLocalDiagnosticOpt, cancellationToken); + + Initialize(analyzerExecutor, diagnosticQueue, 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(() => { - await initializeTask.ConfigureAwait(false); + this.CompilationEventQueue.TryComplete(); + this.DiagnosticQueue.TryComplete(); + }); - await ProcessCompilationEventsAsync(cancellationToken).ConfigureAwait(false); - }, cancellationToken) - .ContinueWith(c => DiagnosticQueue.TryComplete(), cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + _primaryTask = ExecutePrimaryAnalysisTaskAsync(analysisScope, analysisStateOpt: null, usingPrePopulatedEventQueue: false, cancellationToken: cancellationToken) + .ContinueWith(c => DiagnosticQueue.TryComplete(), cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } } finally { @@ -114,26 +250,45 @@ private void Initialize(Compilation comp, AnalyzerExecutor analyzerExecutor, Can } } - private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken) + private async Task ExecutePrimaryAnalysisTaskAsync(AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool usingPrePopulatedEventQueue, CancellationToken cancellationToken) { - // Execute syntax tree analyzers in parallel. - var tasks = ArrayBuilder.GetInstance(); - foreach (var tree in _compilation.SyntaxTrees) + Debug.Assert(analysisScope != null); + Debug.Assert(WhenInitializedTask != null); + + await WhenInitializedTask.ConfigureAwait(false); + + if (!WhenInitializedTask.IsCanceled) + { + this.analyzerExecutor = this.analyzerExecutor.WithCancellationToken(cancellationToken); + + await ProcessCompilationEventsAsync(analysisScope, analysisStateOpt, usingPrePopulatedEventQueue, cancellationToken).ConfigureAwait(false); + } + } + + private void ExecuteSyntaxTreeActions(AnalysisScope analysisScope, AnalysisState analysisStateOpt) + { + 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 actionsByAnalyzers = this.analyzerActions.SyntaxTreeActions.GroupBy(action => action.Analyzer); - foreach (var analyzerAndActions in actionsByAnalyzers) + foreach (var analyzer in analysisScope.Analyzers) { - var task = Task.Run(() => + ImmutableArray syntaxTreeActions; + if (_syntaxTreeActionsMap.TryGetValue(analyzer, out syntaxTreeActions)) { // Execute actions for a given analyzer sequentially. - analyzerExecutor.ExecuteSyntaxTreeActions(analyzerAndActions.ToImmutableArray(), tree); - }, cancellationToken); - - tasks.Add(task); + analyzerExecutor.ExecuteSyntaxTreeActions(syntaxTreeActions, analyzer, tree, analysisScope, analysisStateOpt); + } + else + { + analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analyzer); + } } } - - return Task.WhenAll(tasks.ToArrayAndFree()); } /// @@ -152,7 +307,7 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken) /// 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 Create( + public static AnalyzerDriver CreateAndAttachToCompilation( Compilation compilation, ImmutableArray analyzers, AnalyzerOptions options, @@ -165,11 +320,11 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken) Action onAnalyzerException = (ex, analyzer, diagnostic) => addExceptionDiagnostic?.Invoke(diagnostic); - return Create(compilation, analyzers, options, analyzerManager, onAnalyzerException, reportAnalyzer, out newCompilation, cancellationToken: cancellationToken); + return CreateAndAttachToCompilation(compilation, analyzers, options, analyzerManager, onAnalyzerException, reportAnalyzer, out newCompilation, cancellationToken: cancellationToken); } // internal for testing purposes - internal static AnalyzerDriver Create( + internal static AnalyzerDriver CreateAndAttachToCompilation( Compilation compilation, ImmutableArray analyzers, AnalyzerOptions options, @@ -179,64 +334,18 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken) out Compilation newCompilation, CancellationToken cancellationToken) { - options = options ?? AnalyzerOptions.Empty; - AnalyzerDriver analyzerDriver = compilation.AnalyzerForLanguage(analyzers, analyzerManager, cancellationToken); - newCompilation = compilation.WithEventQueue(analyzerDriver.CompilationEventQueue); - - var addDiagnostic = GetDiagnosticSinkWithSuppression(analyzerDriver.DiagnosticQueue.Enqueue, newCompilation); - - Action newOnAnalyzerException; - if (onAnalyzerException != null) - { - // Wrap onAnalyzerException to pass in filtered diagnostic. - var comp = newCompilation; - newOnAnalyzerException = (ex, analyzer, diagnostic) => - onAnalyzerException(ex, analyzer, GetFilteredDiagnostic(diagnostic, comp)); - } - else - { - // Add exception diagnostic to regular diagnostic bag. - newOnAnalyzerException = (ex, analyzer, diagnostic) => addDiagnostic(diagnostic); - } + AnalyzerDriver analyzerDriver = compilation.AnalyzerForLanguage(analyzers, analyzerManager); + newCompilation = compilation.WithEventQueue(new AsyncQueue()); - // Assume all analyzers are non-thread safe. - var singleThreadedAnalyzerToGateMap = ImmutableDictionary.CreateRange(analyzers.Select(a => KeyValuePair.Create(a, new object()))); - - if (reportAnalyzer) - { - // If we are reporting detailed analyzer performance numbers, then do a dummy invocation of Compilation.GetTypeByMetadataName API upfront. - // This API seems to cause a severe hit for the first analyzer invoking it and hence introduces lot of noise in the computed analyzer execution times. - var unused = newCompilation.GetTypeByMetadataName("System.Object"); - } - - Func getAnalyzerGate = analyzer => singleThreadedAnalyzerToGateMap[analyzer]; - var analyzerExecutor = AnalyzerExecutor.Create(newCompilation, options, addDiagnostic, newOnAnalyzerException, IsCompilerAnalyzer, analyzerManager, getAnalyzerGate, reportAnalyzer, cancellationToken); - - analyzerDriver.Initialize(newCompilation, analyzerExecutor, cancellationToken); + var categorizeDiagnostics = false; + var analysisOptions = new CompilationWithAnalyzersOptions(options, onAnalyzerException, concurrentAnalysis: true, logAnalyzerExecutionTime: reportAnalyzer); + analyzerDriver.Initialize(newCompilation, analysisOptions, categorizeDiagnostics, cancellationToken); + var analysisScope = new AnalysisScope(newCompilation, analyzers, concurrentAnalysis: newCompilation.Options.ConcurrentBuild, categorizeDiagnostics: categorizeDiagnostics); + analyzerDriver.AttachQueueAndStartProcessingEvents(newCompilation.EventQueue, analysisScope, cancellationToken: cancellationToken); return analyzerDriver; } - /// - /// Create an analyzer driver. - /// - /// The set of analyzers to include in the analysis - /// AnalyzerManager to manage analyzers for analyzer host's lifetime. - /// a cancellation token that can be used to abort analysis - protected AnalyzerDriver(ImmutableArray analyzers, AnalyzerManager analyzerManager, CancellationToken cancellationToken) - { - _analyzers = analyzers; - this.analyzerManager = analyzerManager; - - this.CompilationEventQueue = new AsyncQueue(); - this.DiagnosticQueue = new AsyncQueue(); - _queueRegistration = cancellationToken.Register(() => - { - this.CompilationEventQueue.TryComplete(); - this.DiagnosticQueue.TryComplete(); - }); - } - /// /// 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 @@ -256,21 +365,31 @@ public async Task> GetDiagnosticsAsync() allDiagnostics.Add(d); } - var diagnostics = allDiagnostics.ToReadOnlyAndFree(); + return allDiagnostics.ToReadOnlyAndFree(); + } - // Verify that the diagnostics are already filtered. - Debug.Assert(_compilation == null || - diagnostics.All(diag => _compilation.Options.FilterDiagnostic(diag)?.Severity == diag.Severity)); + public ImmutableArray DequeueLocalDiagnostics(DiagnosticAnalyzer analyzer, bool syntax) + { + return syntax ? DiagnosticQueue.DequeueLocalSyntaxDiagnostics(analyzer) : DiagnosticQueue.DequeueLocalSemanticDiagnostics(analyzer); + } - return diagnostics; + public ImmutableArray DequeueNonLocalDiagnostics(DiagnosticAnalyzer analyzer) + { + return DiagnosticQueue.DequeueNonLocalDiagnostics(analyzer); } + /// + /// 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 ImmutableDictionary>> MakeSymbolActionsByKind() { @@ -301,6 +420,18 @@ public async Task> GetDiagnosticsAsync() return builder.ToImmutable(); } + private ImmutableDictionary> MakeSyntaxTreeActionsByAnalyzer() + { + var builder = ImmutableDictionary.CreateBuilder>(); + var actionsByAnalyzers = this.analyzerActions.SyntaxTreeActions.GroupBy(action => action.Analyzer); + foreach (var analyzerAndActions in actionsByAnalyzers) + { + builder.Add(analyzerAndActions.Key, analyzerAndActions.ToImmutableArray()); + } + + return builder.ToImmutable(); + } + private ImmutableDictionary> MakeSemanticModelActionsByAnalyzer() { var builder = ImmutableDictionary.CreateBuilder>(); @@ -325,48 +456,79 @@ public async Task> GetDiagnosticsAsync() return builder.ToImmutable(); } - private async Task ProcessCompilationEventsAsync(CancellationToken cancellationToken) + private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool prePopulatedEventQueue, CancellationToken cancellationToken) { CompilationCompletedEvent completedEvent = null; - // 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 workerTasks = new Task[_workerCount]; - for (int i = 0; i < _workerCount; i++) + if (analysisScope.ConcurrentAnalysis) { - workerTasks[i] = Task.Run(async () => + // 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++) + { + workerTasks[i] = ProcessCompilationEventsCoreAsync(analysisScope, analysisStateOpt, prePopulatedEventQueue, cancellationToken); + } + + cancellationToken.ThrowIfCancellationRequested(); + + // Kick off tasks to execute syntax tree actions. + var syntaxTreeActionsTask = Task.Run(() => ExecuteSyntaxTreeActions(analysisScope, analysisStateOpt)); + + // 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) { - var result = await ProcessCompilationEventsCoreAsync(cancellationToken).ConfigureAwait(false); - if (result != null) - { - completedEvent = result; - } - }, cancellationToken); + completedEvent = workerTasks[i].Result; + break; + } + } } + else + { + completedEvent = await ProcessCompilationEventsCoreAsync(analysisScope, analysisStateOpt, prePopulatedEventQueue, cancellationToken).ConfigureAwait(false); - // Kick off tasks to execute syntax tree actions. - var syntaxTreeActionsTask = ExecuteSyntaxTreeActions(cancellationToken); - - // Wait for all worker threads to complete processing events. - await Task.WhenAll(workerTasks.Concat(syntaxTreeActionsTask)).ConfigureAwait(false); + ExecuteSyntaxTreeActions(analysisScope, analysisStateOpt); + } // Finally process the compilation completed event, if any. if (completedEvent != null) { - await ProcessEventAsync(completedEvent, cancellationToken).ConfigureAwait(false); + ProcessEvent(completedEvent, analysisScope, analysisStateOpt, cancellationToken); } } - private async Task ProcessCompilationEventsCoreAsync(CancellationToken cancellationToken) + private async Task ProcessCompilationEventsCoreAsync(AnalysisScope analysisScope, AnalysisState analysisStateOpt, bool prePopulatedEventQueue, CancellationToken cancellationToken) { - while (!CompilationEventQueue.IsCompleted || CompilationEventQueue.Count > 0) + CompilationCompletedEvent completedEvent = null; + + while (true) { + if (CompilationEventQueue.Count == 0 && + (prePopulatedEventQueue || CompilationEventQueue.IsCompleted)) + { + break; + } + CompilationEvent e; try { - e = await CompilationEventQueue.DequeueAsync(cancellationToken).ConfigureAwait(false); + if (!prePopulatedEventQueue) + { + e = await CompilationEventQueue.DequeueAsync(cancellationToken).ConfigureAwait(false); + } + else if (!CompilationEventQueue.TryDequeue(out e)) + { + return completedEvent; + } } - catch (TaskCanceledException) + 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 @@ -375,187 +537,148 @@ private async Task ProcessCompilationEventsCoreAsync( break; } - if (e.Compilation != _compilation) - { - Debug.Assert(false, "CompilationEvent with a different compilation then driver's compilation?"); - continue; - } - // 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) { - return compilationCompletedEvent; + completedEvent = compilationCompletedEvent; + continue; } - await ProcessEventAsync(e, cancellationToken).ConfigureAwait(false); + ProcessEvent(e, analysisScope, analysisStateOpt, cancellationToken); } - // Another thread dequeued the compilation completed event, so we just return null. - return null; + return completedEvent; } - private async Task ProcessEventAsync(CompilationEvent e, CancellationToken cancellationToken) + private void ProcessEvent(CompilationEvent e, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { - try - { - var processEventTask = ProcessEventCoreAsync(e, cancellationToken); - if (processEventTask != null) - { - await processEventTask.ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - // when just a single operation is cancelled, we continue processing events. - // TODO: what is the desired behavior in this case? - } + ProcessEventCore(e, analysisScope, analysisStateOpt, cancellationToken); + analysisStateOpt?.OnCompilationEventProcessed(e, analysisScope); } - private Task ProcessEventCoreAsync(CompilationEvent e, CancellationToken cancellationToken) + private void ProcessEventCore(CompilationEvent e, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { var symbolEvent = e as SymbolDeclaredCompilationEvent; if (symbolEvent != null) { - return ProcessSymbolDeclaredAsync(symbolEvent, cancellationToken); + ProcessSymbolDeclared(symbolEvent, analysisScope, analysisStateOpt, cancellationToken); + return; } var completedEvent = e as CompilationUnitCompletedEvent; if (completedEvent != null) { - return ProcessCompilationUnitCompletedAsync(completedEvent, cancellationToken); + ProcessCompilationUnitCompleted(completedEvent, analysisScope, analysisStateOpt, cancellationToken); + return; } var endEvent = e as CompilationCompletedEvent; if (endEvent != null) { - return ProcessCompilationCompletedAsync(endEvent, cancellationToken); + ProcessCompilationCompleted(endEvent, analysisScope, analysisStateOpt, cancellationToken); + return; } - if (e is CompilationStartedEvent) + var startedEvent = e as CompilationStartedEvent; + if (startedEvent != null) { - // Ignore CompilationStartedEvent. - return null; + ProcessCompilationStarted(startedEvent, analysisScope, analysisStateOpt, cancellationToken); + return; } throw new InvalidOperationException("Unexpected compilation event of type " + e.GetType().Name); } - private async Task ProcessSymbolDeclaredAsync(SymbolDeclaredCompilationEvent symbolEvent, CancellationToken cancellationToken) + private void ProcessSymbolDeclared(SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { - // Collect all the analyzer action executors grouped by analyzer. - // NOTE: Right now we execute all the actions sequentially, but there is scope to fine tune this to execute certain actions in parallel. - var actionsMap = PooledDictionary>.GetInstance(); - try { + // Execute all analyzer actions. var symbol = symbolEvent.Symbol; - - // Skip symbol actions for implicitly declared symbols. - if (!symbol.IsImplicitlyDeclared) + if (!AnalysisScope.ShouldSkipSymbolAnalysis(symbol)) { - AddTasksForExecutingSymbolActions(symbolEvent, actionsMap, cancellationToken); + ExecuteSymbolActions(symbolEvent, analysisScope, analysisStateOpt, cancellationToken); } - // Skip syntax actions for implicitly declared symbols, except for implicitly declared global namespace symbols. - if (!symbol.IsImplicitlyDeclared || - (symbol.Kind == SymbolKind.Namespace && ((INamespaceSymbol)symbol).IsGlobalNamespace)) + if (!AnalysisScope.ShouldSkipDeclarationAnalysis(symbol)) { - AddTasksForExecutingDeclaringReferenceActions(symbolEvent, actionsMap, cancellationToken); + ExecuteDeclaringReferenceActions(symbolEvent, analysisScope, analysisStateOpt, cancellationToken); } - - // Execute all analyzer actions. - await Task.Run(() => - { - foreach (var builder in actionsMap.Values) - { - foreach (var action in builder) - { - action(); - } - - builder.Free(); - }; - }, cancellationToken).ConfigureAwait(false); } finally { - actionsMap.Free(); symbolEvent.FlushCache(); } } - private void AddTasksForExecutingSymbolActions(SymbolDeclaredCompilationEvent symbolEvent, IDictionary> actionsMap, CancellationToken cancellationToken) + private void ExecuteSymbolActions(SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { var symbol = symbolEvent.Symbol; - Action addDiagnosticForSymbol = GetDiagnosticSinkWithSuppression(DiagnosticQueue.Enqueue, _compilation, symbol); - - foreach (var analyzerAndActions in _symbolActionsByKind) + if (!analysisScope.ShouldAnalyze(symbol)) { - var analyzer = analyzerAndActions.Key; - var actionsByKind = analyzerAndActions.Value; - - Action executeSymbolActionsForAnalyzer = () => - ExecuteSymbolActionsForAnalyzer(symbol, analyzer, actionsByKind, addDiagnosticForSymbol, cancellationToken); - - AddAnalyzerActionsExecutor(actionsMap, analyzer, executeSymbolActionsForAnalyzer); + return; } - } - private void ExecuteSymbolActionsForAnalyzer( - ISymbol symbol, - DiagnosticAnalyzer analyzer, - ImmutableArray> actionsByKind, - Action addDiagnosticForSymbol, - CancellationToken cancellationToken) - { - // Invoke symbol analyzers only for source symbols. - var declaringSyntaxRefs = symbol.DeclaringSyntaxReferences; - if ((int)symbol.Kind < actionsByKind.Length && declaringSyntaxRefs.Any(s => s.SyntaxTree != null)) + Action addDiagnosticForSymbol = GetDiagnosticSinkWithSuppression(DiagnosticQueue.Enqueue, symbolEvent.Compilation, symbol); + Action addLocalDiagnosticForSymbol = analysisScope.CategorizeDiagnostics ? GetDiagnosticSinkWithSuppression(DiagnosticQueue.EnqueueLocal, symbolEvent.Compilation, symbol) : null; + Action addNonLocalDiagnosticForSymbol = analysisScope.CategorizeDiagnostics ? GetDiagnosticSinkWithSuppression(DiagnosticQueue.EnqueueNonLocal, symbolEvent.Compilation, symbol) : null; + + foreach (var analyzer in analysisScope.Analyzers) { - analyzerExecutor.ExecuteSymbolActions(actionsByKind[(int)symbol.Kind], symbol, addDiagnosticForSymbol); + // Invoke symbol analyzers only for source symbols. + ImmutableArray> actionsByKind; + if (_symbolActionsByKind.TryGetValue(analyzer, out actionsByKind) && (int)symbol.Kind < actionsByKind.Length) + { + analyzerExecutor.ExecuteSymbolActions(actionsByKind[(int)symbol.Kind], analyzer, symbol, addDiagnosticForSymbol, + addLocalDiagnosticForSymbol, addNonLocalDiagnosticForSymbol, GetTopmostNodeForAnalysis, analysisScope, analysisStateOpt); + } + else + { + analysisStateOpt?.MarkSymbolComplete(symbol, analyzer); + } } } - protected static void AddAnalyzerActionsExecutor(IDictionary> map, DiagnosticAnalyzer analyzer, Action executeAnalyzerActions) + private static SyntaxNode GetTopmostNodeForAnalysis(ISymbol symbol, SyntaxReference syntaxReference, Compilation compilation) { - ArrayBuilder currentActions; - if (!map.TryGetValue(analyzer, out currentActions)) - { - currentActions = ArrayBuilder.GetInstance(); - map[analyzer] = currentActions; - } - - currentActions.Add(executeAnalyzerActions); + var model = compilation.GetSemanticModel(syntaxReference.SyntaxTree); + return model.GetTopmostNodeForDiagnosticAnalysis(symbol, syntaxReference.GetSyntax()); } - protected abstract void AddTasksForExecutingDeclaringReferenceActions(SymbolDeclaredCompilationEvent symbolEvent, IDictionary> actionsMap, CancellationToken cancellationToken); + protected abstract void ExecuteDeclaringReferenceActions(SymbolDeclaredCompilationEvent symbolEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken); - private async Task ProcessCompilationUnitCompletedAsync(CompilationUnitCompletedEvent completedEvent, CancellationToken cancellationToken) + private void ProcessCompilationUnitCompleted(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. - try + var semanticModel = analysisStateOpt != null ? + analysisStateOpt.GetOrCreateCachedSemanticModel(completedEvent.CompilationUnit, completedEvent.Compilation, cancellationToken) : + completedEvent.SemanticModel; + + if (!analysisScope.ShouldAnalyze(semanticModel.SyntaxTree)) { - // Execute analyzers in parallel. - var tasks = ArrayBuilder.GetInstance(); + return; + } - var semanticModel = completedEvent.SemanticModel; - foreach (var analyzerAndActions in _semanticModelActionsMap) + try + { + foreach (var analyzer in analysisScope.Analyzers) { - var task = Task.Run(() => + ImmutableArray semanticModelActions; + if (_semanticModelActionsMap.TryGetValue(analyzer, out semanticModelActions)) { // Execute actions for a given analyzer sequentially. - analyzerExecutor.ExecuteSemanticModelActions(analyzerAndActions.Value, semanticModel); - }, cancellationToken); - - tasks.Add(task); + analyzerExecutor.ExecuteSemanticModelActions(semanticModelActions, analyzer, semanticModel, completedEvent, analysisScope, analysisStateOpt); + } + else + { + analysisStateOpt?.MarkEventComplete(completedEvent, analyzer); + } } - - await Task.WhenAll(tasks.ToArrayAndFree()).ConfigureAwait(false); } finally { @@ -563,45 +686,78 @@ private async Task ProcessCompilationUnitCompletedAsync(CompilationUnitCompleted } } - private async Task ProcessCompilationCompletedAsync(CompilationCompletedEvent endEvent, CancellationToken cancellationToken) + private void ProcessCompilationStarted(CompilationStartedEvent startedEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) + { + ExecuteCompilationActions(_compilationActionsMap, startedEvent, analysisScope, analysisStateOpt, cancellationToken); + } + + private void ProcessCompilationCompleted(CompilationCompletedEvent endEvent, AnalysisScope analysisScope, AnalysisState analysisStateOpt, CancellationToken cancellationToken) { + ExecuteCompilationActions(_compilationEndActionsMap, endEvent, analysisScope, analysisStateOpt, cancellationToken); + } + + private void ExecuteCompilationActions( + ImmutableDictionary> compilationActionsMap, + CompilationEvent compilationEvent, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt, + CancellationToken cancellationToken) + { + Debug.Assert(compilationEvent is CompilationStartedEvent || compilationEvent is CompilationCompletedEvent); + try { - await ExecuteCompilationActionsAsync(_compilationActionsMap, cancellationToken).ConfigureAwait(false); - await ExecuteCompilationActionsAsync(_compilationEndActionsMap, cancellationToken).ConfigureAwait(false); + foreach (var analyzer in analysisScope.Analyzers) + { + ImmutableArray compilationActions; + if (compilationActionsMap.TryGetValue(analyzer, out compilationActions)) + { + analyzerExecutor.ExecuteCompilationActions(compilationActions, analyzer, compilationEvent, analysisScope, analysisStateOpt); + } + else + { + analysisStateOpt?.MarkEventComplete(compilationEvent, analyzer); + } + } } finally { - endEvent.FlushCache(); + compilationEvent.FlushCache(); } } - private Task ExecuteCompilationActionsAsync(ImmutableDictionary> compilationActionsMap, CancellationToken cancellationToken) + internal static Action GetDiagnosticSinkWithSuppression(Action addDiagnosticCore, Compilation compilation, ISymbol symbolOpt = null) { - // Execute analyzers in parallel. - var tasks = ArrayBuilder.GetInstance(); - foreach (var analyzerAndActions in compilationActionsMap) + return diagnostic => { - var task = Task.Run(() => + var filteredDiagnostic = GetFilteredDiagnostic(diagnostic, compilation, symbolOpt); + if (filteredDiagnostic != null) { - // Execute actions for a given analyzer sequentially. - analyzerExecutor.ExecuteCompilationActions(analyzerAndActions.Value); - }, cancellationToken); - - tasks.Add(task); - } + addDiagnosticCore(filteredDiagnostic); + } + }; + } - return Task.WhenAll(tasks.ToArrayAndFree()); + internal static Action GetDiagnosticSinkWithSuppression(Action addLocalDiagnosticCore, Compilation compilation, ISymbol symbolOpt = null) + { + return (diagnostic, analyzer, isSyntaxDiagnostic) => + { + var filteredDiagnostic = GetFilteredDiagnostic(diagnostic, compilation, symbolOpt); + if (filteredDiagnostic != null) + { + addLocalDiagnosticCore(filteredDiagnostic, analyzer, isSyntaxDiagnostic); + } + }; } - internal static Action GetDiagnosticSinkWithSuppression(Action addDiagnosticCore, Compilation compilation, ISymbol symbolOpt = null) + internal static Action GetDiagnosticSinkWithSuppression(Action addNonLocalDiagnosticCore, Compilation compilation, ISymbol symbolOpt = null) { - return diagnostic => + return (diagnostic, analyzer) => { var filteredDiagnostic = GetFilteredDiagnostic(diagnostic, compilation, symbolOpt); if (filteredDiagnostic != null) { - addDiagnosticCore(filteredDiagnostic); + addNonLocalDiagnosticCore(filteredDiagnostic, analyzer); } }; } @@ -645,6 +801,13 @@ private static Diagnostic GetFilteredDiagnostic(Diagnostic diagnostic, Compilati }, analyzerExecutor.CancellationToken); } + internal async Task GetAnalyzerActionCountsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) + { + var executor = analyzerExecutor.WithCancellationToken(cancellationToken); + var analyzerActions = await analyzerManager.GetAnalyzerActionsAsync(analyzer, executor).ConfigureAwait(false); + return new ActionCounts(analyzerActions); + } + /// /// Returns true if all the diagnostics that can be produced by this analyzer are suppressed through options. /// @@ -684,19 +847,48 @@ internal class AnalyzerDriver : AnalyzerDriver where TLanguag private ImmutableDictionary> _lazyCodeBlockEndActionsByAnalyzer; private ImmutableDictionary> _lazyCodeBlockActionsByAnalyzer; + private readonly ConditionalWeakTable _declarationAnalysisDataCache; + private class DeclarationAnalysisData + { + /// + /// GetSyntax() for the given SyntaxReference. + /// + public SyntaxNode DeclaringReferenceSyntax { get; set; } + + /// + /// Topmost declaration node for analysis. + /// + public SyntaxNode TopmostNodeForAnalysis { get; set; } + + /// + /// All member declarations within the declaration. + /// + public ImmutableArray DeclarationsInNode { get; set; } + + /// + /// All descendant nodes for syntax node actions. + /// + public ImmutableArray DescendantNodesToAnalyze { get; set; } + + /// + /// Flag indicating if this is a partial analysis. + /// + public bool IsPartialAnalysis { get; set; } + } + /// /// 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. - /// a cancellation token that can be used to abort analysis - internal AnalyzerDriver(ImmutableArray analyzers, Func getKind, AnalyzerManager analyzerManager, CancellationToken cancellationToken) : base(analyzers, analyzerManager, cancellationToken) + internal AnalyzerDriver(ImmutableArray analyzers, Func getKind, AnalyzerManager analyzerManager) : base(analyzers, analyzerManager) { _getKind = getKind; + _declarationAnalysisDataCache = new ConditionalWeakTable(); } - private ImmutableDictionary>>> NodeActionsByKind + private ImmutableDictionary>>> NodeActionsByAnalyzerAndKind { get { @@ -807,29 +999,114 @@ internal AnalyzerDriver(ImmutableArray analyzers, Func> actionsMap, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt, CancellationToken cancellationToken) { var symbol = symbolEvent.Symbol; - var executeSyntaxNodeActions = this.NodeActionsByKind.Any(); - var executeCodeBlockActions = AnalyzerExecutor.CanHaveExecutableCodeBlock(symbol) && - (!this.CodeBlockStartActionsByAnalyzer.IsEmpty || !this.CodeBlockEndActionsByAnalyzer.IsEmpty || !this.CodeBlockActionsByAnalyzer.IsEmpty); + var executeSyntaxNodeActions = ShouldExecuteSyntaxNodeActions(analysisScope); + var executeCodeBlockActions = ShouldExecuteCodeBlockActions(analysisScope, symbol); if (executeSyntaxNodeActions || executeCodeBlockActions) { foreach (var decl in symbol.DeclaringSyntaxReferences) { - AddTasksForExecutingDeclaringReferenceActions(decl, symbolEvent, actionsMap, executeSyntaxNodeActions, executeCodeBlockActions, cancellationToken); + if (analysisScope.FilterTreeOpt == null || analysisScope.FilterTreeOpt == decl.SyntaxTree) + { + ExecuteDeclaringReferenceActions(decl, symbolEvent, analysisScope, analysisStateOpt, executeSyntaxNodeActions, executeCodeBlockActions, cancellationToken); + } } } + else + { + analysisStateOpt?.MarkDeclarationsComplete(symbol, analysisScope.Analyzers); + } + } + + private DeclarationAnalysisData GetOrComputeDeclarationAnalysisData( + SyntaxReference declaration, + Func computeDeclarationAnalysisData, + bool cacheAnalysisData) + { + if (!cacheAnalysisData) + { + return computeDeclarationAnalysisData(); + } + + return _declarationAnalysisDataCache.GetValue(declaration, decl => computeDeclarationAnalysisData()); + } + + private DeclarationAnalysisData ComputeDeclarationAnalysisData( + ISymbol symbol, + SyntaxReference declaration, + SemanticModel semanticModel, + bool shouldExecuteSyntaxNodeActions, + AnalysisScope analysisScope, + CancellationToken cancellationToken) + { + var declaringReferenceSyntax = declaration.GetSyntax(cancellationToken); + var topmostNodeForAnalysis = semanticModel.GetTopmostNodeForDiagnosticAnalysis(symbol, declaringReferenceSyntax); + var declarationsInNode = ComputeDeclarationsInNode(semanticModel, declaringReferenceSyntax, topmostNodeForAnalysis, cancellationToken); + var isPartialDeclAnalysis = analysisScope.FilterSpanOpt.HasValue && !analysisScope.ContainsSpan(topmostNodeForAnalysis.FullSpan); + var nodesToAnalyze = shouldExecuteSyntaxNodeActions ? + GetSyntaxNodesToAnalyze(topmostNodeForAnalysis, symbol, declarationsInNode, analysisScope, isPartialDeclAnalysis, semanticModel, analyzerExecutor) : + ImmutableArray.Empty; + + return new DeclarationAnalysisData() + { + DeclaringReferenceSyntax = declaringReferenceSyntax, + TopmostNodeForAnalysis = topmostNodeForAnalysis, + DeclarationsInNode = declarationsInNode, + DescendantNodesToAnalyze = nodesToAnalyze, + IsPartialAnalysis = isPartialDeclAnalysis + }; + } + + private static ImmutableArray ComputeDeclarationsInNode(SemanticModel semanticModel, SyntaxNode declaringReferenceSyntax, SyntaxNode topmostNodeForAnalysis, CancellationToken cancellationToken) + { + // We only care about the top level symbol declaration and its immediate member declarations. + int? levelsToCompute = 2; + var getSymbol = topmostNodeForAnalysis != declaringReferenceSyntax; + return semanticModel.GetDeclarationsInNode(topmostNodeForAnalysis, getSymbol, cancellationToken, levelsToCompute); } - private void AddTasksForExecutingDeclaringReferenceActions( + private void ExecuteDeclaringReferenceActions( SyntaxReference decl, SymbolDeclaredCompilationEvent symbolEvent, - IDictionary> actionsMap, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt, bool shouldExecuteSyntaxNodeActions, bool shouldExecuteCodeBlockActions, CancellationToken cancellationToken) @@ -837,26 +1114,35 @@ internal AnalyzerDriver(ImmutableArray analyzers, Func= decl.SyntaxTree.GetRoot(cancellationToken).Span.Length); + + var declarationAnalysisData = GetOrComputeDeclarationAnalysisData( + decl, + () => ComputeDeclarationAnalysisData(symbol, decl, semanticModel, shouldExecuteSyntaxNodeActions, analysisScope, cancellationToken), + cacheAnalysisData); + + if (!analysisScope.ShouldAnalyze(declarationAnalysisData.TopmostNodeForAnalysis)) + { + return; + } // Execute stateless syntax node actions. if (shouldExecuteSyntaxNodeActions) { - var nodesToAnalyze = GetSyntaxNodesToAnalyze(syntax, symbol, declarationsInNode, semanticModel, analyzerExecutor); - - foreach (var analyzerAndActions in this.NodeActionsByKind) + var nodesToAnalyze = declarationAnalysisData.DescendantNodesToAnalyze; + foreach (var analyzer in analysisScope.Analyzers) { - Action executeStatelessNodeActions = () => - analyzerExecutor.ExecuteSyntaxNodeActions(nodesToAnalyze, analyzerAndActions.Value, semanticModel, _getKind); - - AddAnalyzerActionsExecutor(actionsMap, analyzerAndActions.Key, executeStatelessNodeActions); + ImmutableDictionary>> nodeActionsByKind; + if (this.NodeActionsByAnalyzerAndKind.TryGetValue(analyzer, out nodeActionsByKind)) + { + analyzerExecutor.ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, + analyzer, semanticModel, _getKind, declarationAnalysisData.TopmostNodeForAnalysis.FullSpan, decl, analysisScope, analysisStateOpt); + } } } @@ -865,9 +1151,9 @@ internal AnalyzerDriver(ImmutableArray analyzers, Func.Empty; - foreach (var declInNode in declarationsInNode) + foreach (var declInNode in declarationAnalysisData.DeclarationsInNode) { - if (declInNode.DeclaredNode == syntax || declInNode.DeclaredNode == declaringReferenceSyntax) + if (declInNode.DeclaredNode == declarationAnalysisData.TopmostNodeForAnalysis || declInNode.DeclaredNode == declarationAnalysisData.DeclaringReferenceSyntax) { executableCodeBlocks = declInNode.ExecutableCodeBlocks; break; @@ -876,18 +1162,24 @@ internal AnalyzerDriver(ImmutableArray analyzers, Func - { - analyzerExecutor.ExecuteCodeBlockActions(analyzerActions.CodeBlockStartActions, analyzerActions.CodeBlockActions, analyzerActions.CodeBlockEndActions, - syntax, symbol, executableCodeBlocks, semanticModel, _getKind); - }; - - AddAnalyzerActionsExecutor(actionsMap, analyzerActions.Analyzer, executeCodeBlockActions); + analyzerExecutor.ExecuteCodeBlockActions( + analyzerActions.CodeBlockStartActions, analyzerActions.CodeBlockActions, + analyzerActions.CodeBlockEndActions, analyzerActions.Analyzer, declarationAnalysisData.TopmostNodeForAnalysis, symbol, + executableCodeBlocks, semanticModel, _getKind, decl, analysisScope, analysisStateOpt); } } } + + // Mark completion only if we are analyzing a span containing the entire syntax node. + if (analysisStateOpt != null && !declarationAnalysisData.IsPartialAnalysis) + { + foreach (var analyzer in analysisScope.Analyzers) + { + analysisStateOpt.MarkDeclarationComplete(decl, analyzer); + } + } } [StructLayout(LayoutKind.Auto)] @@ -899,82 +1191,48 @@ private struct CodeBlockAnalyzerActions public ImmutableArray CodeBlockEndActions; } - private IEnumerable GetCodeBlockActions() + private IEnumerable GetCodeBlockActions(AnalysisScope analysisScope) { - // Include analyzers with code block start actions. - - foreach (var analyzerAndActions in this.CodeBlockStartActionsByAnalyzer) + foreach (var analyzer in analysisScope.Analyzers) { + ImmutableArray> codeBlockStartActions; + if (!this.CodeBlockStartActionsByAnalyzer.TryGetValue(analyzer, out codeBlockStartActions)) + { + codeBlockStartActions = ImmutableArray>.Empty; + } + ImmutableArray codeBlockActions; - if (!this.CodeBlockActionsByAnalyzer.TryGetValue(analyzerAndActions.Key, out codeBlockActions)) + if (!this.CodeBlockActionsByAnalyzer.TryGetValue(analyzer, out codeBlockActions)) { codeBlockActions = ImmutableArray.Empty; } ImmutableArray codeBlockEndActions; - if (!this.CodeBlockEndActionsByAnalyzer.TryGetValue(analyzerAndActions.Key, out codeBlockEndActions)) + if (!this.CodeBlockEndActionsByAnalyzer.TryGetValue(analyzer, out codeBlockEndActions)) { codeBlockEndActions = ImmutableArray.Empty; } - yield return - new CodeBlockAnalyzerActions - { - Analyzer = analyzerAndActions.Key, - CodeBlockStartActions = analyzerAndActions.Value, - CodeBlockActions = codeBlockActions, - CodeBlockEndActions = codeBlockEndActions - }; - } - - // Include analyzers with code block actions. - - foreach (var analyzerAndActions in this.CodeBlockActionsByAnalyzer) - { - // Skip analyzers included above. - if (!CodeBlockStartActionsByAnalyzer.ContainsKey(analyzerAndActions.Key)) + if (!codeBlockStartActions.IsEmpty || !codeBlockActions.IsEmpty || !codeBlockEndActions.IsEmpty) { - ImmutableArray codeBlockEndActions; - if (!this.CodeBlockEndActionsByAnalyzer.TryGetValue(analyzerAndActions.Key, out codeBlockEndActions)) - { - codeBlockEndActions = ImmutableArray.Empty; - } - yield return new CodeBlockAnalyzerActions { - Analyzer = analyzerAndActions.Key, - CodeBlockStartActions = ImmutableArray>.Empty, - CodeBlockActions = analyzerAndActions.Value, + Analyzer = analyzer, + CodeBlockStartActions = codeBlockStartActions, + CodeBlockActions = codeBlockActions, CodeBlockEndActions = codeBlockEndActions }; } } - - // Include analyzers with code block end actions. - - foreach (var analyzerAndActions in this.CodeBlockEndActionsByAnalyzer) - { - // Skip analyzers included above. - if (!CodeBlockStartActionsByAnalyzer.ContainsKey(analyzerAndActions.Key) && !CodeBlockActionsByAnalyzer.ContainsKey(analyzerAndActions.Key)) - { - yield return - new CodeBlockAnalyzerActions - { - Analyzer = analyzerAndActions.Key, - CodeBlockStartActions = ImmutableArray>.Empty, - CodeBlockActions = ImmutableArray.Empty, - CodeBlockEndActions = analyzerAndActions.Value - }; - } - } } - private static ImmutableArray GetSyntaxNodesToAnalyze( SyntaxNode declaredNode, ISymbol declaredSymbol, IEnumerable declarationsInNode, + AnalysisScope analysisScope, + bool isPartialDeclAnalysis, SemanticModel semanticModel, AnalyzerExecutor analyzerExecutor) { @@ -1017,6 +1275,12 @@ yield return var nodesToAnalyze = descendantDeclsToSkip == null ? declaredNode.DescendantNodesAndSelf(descendIntoTrivia: true) : GetSyntaxNodesToAnalyze(declaredNode, descendantDeclsToSkip); + + if (isPartialDeclAnalysis) + { + nodesToAnalyze = nodesToAnalyze.Where(node => analysisScope.ShouldAnalyze(node)); + } + return nodesToAnalyze.ToImmutableArray(); } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs new file mode 100644 index 0000000000000000000000000000000000000000..1453357b8d525c0758937748d47a268ddc9daf0c --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -0,0 +1,985 @@ +// 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 Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +using AnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.AnalyzerStateData; +using SyntaxNodeAnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.SyntaxNodeAnalyzerStateData; +using CodeBlockAnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.CodeBlockAnalyzerStateData; +using DeclarationAnalyzerStateData = Microsoft.CodeAnalysis.Diagnostics.AnalysisState.DeclarationAnalyzerStateData; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Contains the core execution logic for callbacks into analyzers. + /// + internal class AnalyzerExecutor + { + private const string DiagnosticCategory = "Compiler"; + + // internal for testing purposes only. + internal const string AnalyzerExceptionDiagnosticId = "AD0001"; + + private readonly Compilation _compilation; + private readonly AnalyzerOptions _analyzerOptions; + private readonly Action _addDiagnostic; + private readonly Action _addLocalDiagnosticOpt; + private readonly Action _addNonLocalDiagnosticOpt; + private readonly Action _onAnalyzerException; + private readonly AnalyzerManager _analyzerManager; + private readonly Func _isCompilerAnalyzer; + private readonly Func _getAnalyzerGateOpt; + private readonly ConcurrentDictionary _analyzerExecutionTimeMapOpt; + + private readonly CancellationToken _cancellationToken; + + /// + /// Creates to execute analyzer actions with given arguments + /// + /// Compilation to be used in the analysis. + /// Analyzer options. + /// Delegate to add analyzer diagnostics. + /// + /// Optional delegate which is invoked when an analyzer throws an exception. + /// Delegate can do custom tasks such as report the given analyzer exception diagnostic, report a non-fatal watson for the exception, etc. + /// + /// Delegate to determine if the given analyzer is compiler analyzer. + /// We need to special case the compiler analyzer at few places for performance reasons. + /// Analyzer manager to fetch supported diagnostics. + /// + /// Delegate to fetch the gate object to guard all callbacks into the analyzer. + /// It should return a unique gate object for the given analyzer instance for non-concurrent analyzers, and null otherwise. + /// All analyzer callbacks for non-concurrent analyzers will be guarded with a lock on the gate. + /// + /// Flag indicating whether we need to log analyzer execution time. + /// Optional delegate to add local analyzer diagnostics. + /// Optional delegate to add non-local analyzer diagnostics. + /// Cancellation token. + public static AnalyzerExecutor Create( + Compilation compilation, + AnalyzerOptions analyzerOptions, + Action addDiagnostic, + Action onAnalyzerException, + Func isCompilerAnalyzer, + AnalyzerManager analyzerManager, + Func getAnalyzerGate, + bool logExecutionTime = false, + Action addLocalDiagnosticOpt = null, + Action addNonLocalDiagnosticOpt = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + var analyzerExecutionTimeMapOpt = logExecutionTime ? new ConcurrentDictionary() : null; + + return new AnalyzerExecutor(compilation, analyzerOptions, addDiagnostic, onAnalyzerException, isCompilerAnalyzer, + analyzerManager, getAnalyzerGate, analyzerExecutionTimeMapOpt, addLocalDiagnosticOpt, addNonLocalDiagnosticOpt, cancellationToken); + } + + /// + /// Creates to fetch . + /// + /// + /// Optional delegate which is invoked when an analyzer throws an exception. + /// Delegate can do custom tasks such as report the given analyzer exception diagnostic, report a non-fatal watson for the exception, etc. + /// + /// Analyzer manager to fetch supported diagnostics. + /// Flag indicating whether we need to log analyzer execution time. + /// Cancellation token. + public static AnalyzerExecutor CreateForSupportedDiagnostics( + Action onAnalyzerException, + AnalyzerManager analyzerManager, + bool logExecutionTime = false, + CancellationToken cancellationToken = default(CancellationToken)) + { + return new AnalyzerExecutor( + compilation: null, + analyzerOptions: null, + addDiagnostic: null, + isCompilerAnalyzer: null, + getAnalyzerGateOpt: null, + onAnalyzerException: onAnalyzerException, + analyzerManager: analyzerManager, + analyzerExecutionTimeMapOpt: null, + addLocalDiagnosticOpt: null, + addNonLocalDiagnosticOpt: null, + cancellationToken: cancellationToken); + } + + private AnalyzerExecutor( + Compilation compilation, + AnalyzerOptions analyzerOptions, + Action addDiagnostic, + Action onAnalyzerException, + Func isCompilerAnalyzer, + AnalyzerManager analyzerManager, + Func getAnalyzerGateOpt, + ConcurrentDictionary analyzerExecutionTimeMapOpt, + Action addLocalDiagnosticOpt, + Action addNonLocalDiagnosticOpt, + CancellationToken cancellationToken) + { + _compilation = compilation; + _analyzerOptions = analyzerOptions; + _addDiagnostic = addDiagnostic; + _onAnalyzerException = onAnalyzerException; + _isCompilerAnalyzer = isCompilerAnalyzer; + _analyzerManager = analyzerManager; + _getAnalyzerGateOpt = getAnalyzerGateOpt; + _analyzerExecutionTimeMapOpt = analyzerExecutionTimeMapOpt; + _addLocalDiagnosticOpt = addLocalDiagnosticOpt; + _addNonLocalDiagnosticOpt = addNonLocalDiagnosticOpt; + _cancellationToken = cancellationToken; + } + + public AnalyzerExecutor WithCancellationToken(CancellationToken cancellationToken) + { + if (cancellationToken == _cancellationToken) + { + return this; + } + + return new AnalyzerExecutor(_compilation, _analyzerOptions, _addDiagnostic, _onAnalyzerException, _isCompilerAnalyzer, + _analyzerManager, _getAnalyzerGateOpt, _analyzerExecutionTimeMapOpt, _addLocalDiagnosticOpt, _addNonLocalDiagnosticOpt, cancellationToken); + } + + internal Compilation Compilation => _compilation; + internal AnalyzerOptions AnalyzerOptions => _analyzerOptions; + internal CancellationToken CancellationToken => _cancellationToken; + internal Action OnAnalyzerException => _onAnalyzerException; + internal ImmutableDictionary AnalyzerExecutionTimes => _analyzerExecutionTimeMapOpt.ToImmutableDictionary(); + + /// + /// Executes the for the given analyzer. + /// + /// Analyzer to get session wide analyzer actions. + /// Session scope to store register session wide analyzer actions. + /// + /// Note that this API doesn't execute any registered by the Initialize invocation. + /// Use API + /// to get execute these actions to get the per-compilation analyzer actions. + /// + public void ExecuteInitializeMethod(DiagnosticAnalyzer analyzer, HostSessionStartAnalysisScope sessionScope) + { + // The Initialize method should be run asynchronously in case it is not well behaved, e.g. does not terminate. + ExecuteAndCatchIfThrows(analyzer, () => analyzer.Initialize(new AnalyzerAnalysisContext(analyzer, sessionScope))); + } + + /// + /// Executes the compilation start actions. + /// + /// whose compilation start actions are to be executed. + /// Compilation scope to store the analyzer actions. + public void ExecuteCompilationStartActions(ImmutableArray actions, HostCompilationStartAnalysisScope compilationScope) + { + foreach (var startAction in actions) + { + _cancellationToken.ThrowIfCancellationRequested(); + ExecuteAndCatchIfThrows(startAction.Analyzer, + () => startAction.Action(new AnalyzerCompilationStartAnalysisContext(startAction.Analyzer, compilationScope, _compilation, _analyzerOptions, _cancellationToken))); + } + } + + /// + /// Executes compilation actions or compilation end actions. + /// + /// Compilation actions to be executed. + /// Analyzer whose actions are to be executed. + /// Compilation event. + /// Scope for analyzer execution. + /// An optional object to track analysis state. + public void ExecuteCompilationActions( + ImmutableArray compilationActions, + DiagnosticAnalyzer analyzer, + CompilationEvent compilationEvent, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt) + { + Debug.Assert(compilationEvent is CompilationStartedEvent || compilationEvent is CompilationCompletedEvent); + + AnalyzerStateData analyzerStateOpt = null; + + try + { + if (TryStartProcessingEvent(compilationEvent, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + { + ExecuteCompilationActionsCore(compilationActions, analyzer, analyzerStateOpt); + analysisStateOpt?.MarkEventComplete(compilationEvent, analyzer); + } + } + finally + { + analyzerStateOpt?.ResetToReadyState(); + } + } + + private void ExecuteCompilationActionsCore(ImmutableArray compilationActions, DiagnosticAnalyzer analyzer, AnalyzerStateData analyzerStateOpt) + { + var addDiagnostic = GetAddCompilationDiagnostic(analyzer); + + foreach (var endAction in compilationActions) + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (ShouldExecuteAction(analyzerStateOpt, endAction)) + { + ExecuteAndCatchIfThrows(endAction.Analyzer, + () => endAction.Action(new CompilationAnalysisContext(_compilation, _analyzerOptions, addDiagnostic, + d => IsSupportedDiagnostic(endAction.Analyzer, d), _cancellationToken))); + + analyzerStateOpt?.ProcessedActions.Add(endAction); + } + } + } + + /// + /// Executes the symbol actions on the given symbol. + /// + /// Symbol actions to be executed. + /// Analyzer whose actions are to be executed. + /// Symbol to be analyzed. + /// Overridden add diagnostic delegate. + /// Overridden add local diagnostic delegate. + /// Overridden add non-local diagnostic delegate. + /// Delegate to get topmost declaration node for a symbol declaration reference. + /// Scope for analyzer execution. + /// An optional object to track analysis state. + public void ExecuteSymbolActions( + ImmutableArray symbolActions, + DiagnosticAnalyzer analyzer, + ISymbol symbol, + Action overriddenAddDiagnostic, + Action overriddenAddLocalDiagnostic, + Action overriddenAddNonLocalDiagnostic, + Func getTopMostNodeForAnalysis, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt) + { + AnalyzerStateData analyzerStateOpt = null; + + try + { + if (TryStartAnalyzingSymbol(symbol, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + { + ExecuteSymbolActionsCore(symbolActions, analyzer, symbol, overriddenAddDiagnostic, + overriddenAddLocalDiagnostic, overriddenAddNonLocalDiagnostic, getTopMostNodeForAnalysis, analyzerStateOpt); + analysisStateOpt?.MarkSymbolComplete(symbol, analyzer); + } + } + finally + { + analyzerStateOpt?.ResetToReadyState(); + } + } + + private void ExecuteSymbolActionsCore( + ImmutableArray symbolActions, + DiagnosticAnalyzer analyzer, + ISymbol symbol, + Action overriddenAddDiagnostic, + Action overriddenAddLocalDiagnostic, + Action overriddenAddNonLocalDiagnostic, + Func getTopMostNodeForAnalysis, + AnalyzerStateData analyzerStateOpt) + { + Debug.Assert(overriddenAddLocalDiagnostic == null || overriddenAddDiagnostic != null); + Debug.Assert(getTopMostNodeForAnalysis != null); + + var addDiagnostic = GetAddDiagnostic(symbol, _compilation, analyzer, overriddenAddDiagnostic ?? _addDiagnostic, overriddenAddLocalDiagnostic ?? _addLocalDiagnosticOpt, overriddenAddNonLocalDiagnostic ?? _addNonLocalDiagnosticOpt, getTopMostNodeForAnalysis); + + foreach (var symbolAction in symbolActions) + { + var action = symbolAction.Action; + var kinds = symbolAction.Kinds; + + if (kinds.Contains(symbol.Kind)) + { + if (ShouldExecuteAction(analyzerStateOpt, symbolAction)) + { + _cancellationToken.ThrowIfCancellationRequested(); + + ExecuteAndCatchIfThrows(symbolAction.Analyzer, + () => action(new SymbolAnalysisContext(symbol, _compilation, _analyzerOptions, addDiagnostic, + d => IsSupportedDiagnostic(symbolAction.Analyzer, d), _cancellationToken))); + + analyzerStateOpt?.ProcessedActions.Add(symbolAction); + } + } + } + } + + /// + /// Executes the semantic model actions on the given semantic model. + /// + /// Semantic model actions to be executed. + /// Analyzer whose actions are to be executed. + /// Semantic model to analyze. + /// Compilation event for semantic model analysis. + /// Scope for analyzer execution. + /// An optional object to track analysis state. + public void ExecuteSemanticModelActions( + ImmutableArray semanticModelActions, + DiagnosticAnalyzer analyzer, + SemanticModel semanticModel, + CompilationEvent compilationUnitCompletedEvent, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt) + { + AnalyzerStateData analyzerStateOpt = null; + + try + { + if (TryStartProcessingEvent(compilationUnitCompletedEvent, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + { + ExecuteSemanticModelActionsCore(semanticModelActions, analyzer, semanticModel, analyzerStateOpt); + analysisStateOpt?.MarkEventComplete(compilationUnitCompletedEvent, analyzer); + } + } + finally + { + analyzerStateOpt?.ResetToReadyState(); + } + } + + private void ExecuteSemanticModelActionsCore( + ImmutableArray semanticModelActions, + DiagnosticAnalyzer analyzer, + SemanticModel semanticModel, + AnalyzerStateData analyzerStateOpt) + { + var addDiagnostic = GetAddDiagnostic(semanticModel.SyntaxTree, analyzer, isSyntaxDiagnostic: false); + + foreach (var semanticModelAction in semanticModelActions) + { + if (ShouldExecuteAction(analyzerStateOpt, semanticModelAction)) + { + _cancellationToken.ThrowIfCancellationRequested(); + + // Catch Exception from action. + ExecuteAndCatchIfThrows(semanticModelAction.Analyzer, + () => semanticModelAction.Action(new SemanticModelAnalysisContext(semanticModel, _analyzerOptions, addDiagnostic, + d => IsSupportedDiagnostic(semanticModelAction.Analyzer, d), _cancellationToken))); + + analyzerStateOpt?.ProcessedActions.Add(semanticModelAction); + } + } + } + + /// + /// Executes the syntax tree actions on the given syntax tree. + /// + /// Syntax tree actions to be executed. + /// Analyzer whose actions are to be executed. + /// Syntax tree to analyze. + /// Scope for analyzer execution. + /// An optional object to track analysis state. + public void ExecuteSyntaxTreeActions( + ImmutableArray syntaxTreeActions, + DiagnosticAnalyzer analyzer, + SyntaxTree tree, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt) + { + AnalyzerStateData analyzerStateOpt = null; + + try + { + if (TryStartSyntaxAnalysis(tree, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + { + ExecuteSyntaxTreeActionsCore(syntaxTreeActions, analyzer, tree, analyzerStateOpt); + analysisStateOpt?.MarkSyntaxAnalysisComplete(tree, analyzer); + } + } + finally + { + analyzerStateOpt?.ResetToReadyState(); + } + } + + private void ExecuteSyntaxTreeActionsCore( + ImmutableArray syntaxTreeActions, + DiagnosticAnalyzer analyzer, + SyntaxTree tree, + AnalyzerStateData analyzerStateOpt) + { + var addDiagnostic = GetAddDiagnostic(tree, analyzer, isSyntaxDiagnostic: true); + + foreach (var syntaxTreeAction in syntaxTreeActions) + { + if (ShouldExecuteAction(analyzerStateOpt, syntaxTreeAction)) + { + _cancellationToken.ThrowIfCancellationRequested(); + + // Catch Exception from action. + ExecuteAndCatchIfThrows(syntaxTreeAction.Analyzer, + () => syntaxTreeAction.Action(new SyntaxTreeAnalysisContext(tree, _analyzerOptions, addDiagnostic, + d => IsSupportedDiagnostic(syntaxTreeAction.Analyzer, d), _cancellationToken))); + + analyzerStateOpt?.ProcessedActions.Add(syntaxTreeAction); + } + } + } + + private void ExecuteSyntaxNodeAction( + SyntaxNodeAnalyzerAction syntaxNodeAction, + SyntaxNode node, + SemanticModel semanticModel, + Action addDiagnostic, + SyntaxNodeAnalyzerStateData analyzerStateOpt) + where TLanguageKindEnum : struct + { + Debug.Assert(analyzerStateOpt == null || analyzerStateOpt.CurrentNode == node); + + if (ShouldExecuteAction(analyzerStateOpt, syntaxNodeAction)) + { + var syntaxNodeContext = new SyntaxNodeAnalysisContext(node, semanticModel, _analyzerOptions, addDiagnostic, + d => IsSupportedDiagnostic(syntaxNodeAction.Analyzer, d), _cancellationToken); + ExecuteAndCatchIfThrows(syntaxNodeAction.Analyzer, () => syntaxNodeAction.Action(syntaxNodeContext)); + + analyzerStateOpt?.ProcessedActions.Add(syntaxNodeAction); + } + } + + public void ExecuteCodeBlockActions( + IEnumerable> codeBlockStartActions, + IEnumerable codeBlockActions, + IEnumerable codeBlockEndActions, + DiagnosticAnalyzer analyzer, + SyntaxNode declaredNode, + ISymbol declaredSymbol, + ImmutableArray executableCodeBlocks, + SemanticModel semanticModel, + Func getKind, + SyntaxReference declaration, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt) + where TLanguageKindEnum : struct + { + DeclarationAnalyzerStateData analyzerStateOpt = null; + + try + { + if (TryStartAnalyzingSyntaxRefence(declaration, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + { + ExecuteCodeBlockActionsCore(codeBlockStartActions, codeBlockActions, codeBlockEndActions, analyzer, + declaredNode, declaredSymbol, executableCodeBlocks, semanticModel, getKind, analyzerStateOpt?.CodeBlockAnalysisState); + } + } + finally + { + analyzerStateOpt?.ResetToReadyState(); + } + } + + private void ExecuteCodeBlockActionsCore( + IEnumerable> codeBlockStartActions, + IEnumerable codeBlockActions, + IEnumerable codeBlockEndActions, + DiagnosticAnalyzer analyzer, + SyntaxNode declaredNode, + ISymbol declaredSymbol, + ImmutableArray executableCodeBlocks, + SemanticModel semanticModel, + Func getKind, + CodeBlockAnalyzerStateData analyzerStateOpt) + where TLanguageKindEnum : struct + { + Debug.Assert(declaredNode != null); + Debug.Assert(declaredSymbol != null); + Debug.Assert(CanHaveExecutableCodeBlock(declaredSymbol)); + Debug.Assert(codeBlockStartActions.Any() || codeBlockEndActions.Any() || codeBlockActions.Any()); + Debug.Assert(executableCodeBlocks.Any()); + + // Compute the sets of code block end, code block, and stateful syntax node actions. + var blockEndActions = PooledHashSet.GetInstance(); + var blockActions = PooledHashSet.GetInstance(); + var executableNodeActions = ArrayBuilder>.GetInstance(); + + // Include the code block actions. + blockActions.AddAll(codeBlockActions); + + // Include the initial code block end actions. + if (analyzerStateOpt?.CurrentCodeBlockEndActions != null) + { + // We have partially processed the code block actions. + blockEndActions.AddAll(analyzerStateOpt.CurrentCodeBlockEndActions.Cast()); + executableNodeActions.AddRange(analyzerStateOpt.CurrentCodeBlockNodeActions.Cast>()); + } + else + { + // We have beginning to process the code block actions. + blockEndActions.AddAll(codeBlockEndActions); + } + + var addDiagnostic = GetAddDiagnostic(semanticModel.SyntaxTree, declaredNode.FullSpan, analyzer, isSyntaxDiagnostic: false); + + try + { + // Include the stateful actions. + foreach (var da in codeBlockStartActions) + { + if (ShouldExecuteAction(analyzerStateOpt, da)) + { + // Catch Exception from the start action. + ExecuteAndCatchIfThrows(da.Analyzer, () => + { + var codeBlockScope = new HostCodeBlockStartAnalysisScope(); + var blockStartContext = new AnalyzerCodeBlockStartAnalysisContext(da.Analyzer, + codeBlockScope, declaredNode, declaredSymbol, semanticModel, _analyzerOptions, _cancellationToken); + da.Action(blockStartContext); + blockEndActions.AddAll(codeBlockScope.CodeBlockEndActions); + executableNodeActions.AddRange(codeBlockScope.SyntaxNodeActions); + }); + + analyzerStateOpt?.ProcessedActions.Add(da); + } + } + } + finally + { + if (analyzerStateOpt != null) + { + analyzerStateOpt.CurrentCodeBlockEndActions = blockEndActions.ToImmutableHashSet(); + analyzerStateOpt.CurrentCodeBlockNodeActions = executableNodeActions.ToImmutableHashSet(); + } + } + + // Execute stateful executable node analyzers, if any. + if (executableNodeActions.Any()) + { + var executableNodeActionsByKind = GetNodeActionsByKind(executableNodeActions); + + var nodesToAnalyze = executableCodeBlocks.SelectMany(cb => cb.DescendantNodesAndSelf()); + ExecuteSyntaxNodeActions(nodesToAnalyze, executableNodeActionsByKind, semanticModel, getKind, addDiagnostic, analyzerStateOpt?.ExecutableNodesAnalysisState); + } + + executableNodeActions.Free(); + + ExecuteCodeBlockActions(blockActions, declaredNode, declaredSymbol, semanticModel, addDiagnostic, analyzerStateOpt); + ExecuteCodeBlockActions(blockEndActions, declaredNode, declaredSymbol, semanticModel, addDiagnostic, analyzerStateOpt); + } + + private void ExecuteCodeBlockActions( + PooledHashSet blockActions, + SyntaxNode declaredNode, + ISymbol declaredSymbol, + SemanticModel semanticModel, + Action addDiagnostic, + CodeBlockAnalyzerStateData analyzerStateOpt) + { + foreach (var blockAction in blockActions) + { + if (ShouldExecuteAction(analyzerStateOpt, blockAction)) + { + ExecuteAndCatchIfThrows(blockAction.Analyzer, + () => blockAction.Action(new CodeBlockAnalysisContext(declaredNode, declaredSymbol, semanticModel, _analyzerOptions, addDiagnostic, + d => IsSupportedDiagnostic(blockAction.Analyzer, d), _cancellationToken))); + + analyzerStateOpt?.ProcessedActions.Add(blockAction); + } + } + + blockActions.Free(); + } + + internal static ImmutableDictionary>> GetNodeActionsByKind( + IEnumerable> nodeActions) + where TLanguageKindEnum : struct + { + Debug.Assert(nodeActions != null && nodeActions.Any()); + + var nodeActionsByKind = PooledDictionary>>.GetInstance(); + foreach (var nodeAction in nodeActions) + { + foreach (var kind in nodeAction.Kinds) + { + ArrayBuilder> actionsForKind; + if (!nodeActionsByKind.TryGetValue(kind, out actionsForKind)) + { + nodeActionsByKind.Add(kind, actionsForKind = ArrayBuilder>.GetInstance()); + } + + actionsForKind.Add(nodeAction); + } + } + + var tuples = nodeActionsByKind.Select(kvp => KeyValuePair.Create(kvp.Key, kvp.Value.ToImmutableAndFree())); + var map = ImmutableDictionary.CreateRange(tuples); + nodeActionsByKind.Free(); + return map; + } + + public void ExecuteSyntaxNodeActions( + IEnumerable nodesToAnalyze, + IDictionary>> nodeActionsByKind, + DiagnosticAnalyzer analyzer, + SemanticModel model, + Func getKind, + TextSpan filterSpan, + SyntaxReference declaration, + AnalysisScope analysisScope, + AnalysisState analysisStateOpt) + where TLanguageKindEnum : struct + { + DeclarationAnalyzerStateData analyzerStateOpt = null; + + try + { + if (TryStartAnalyzingSyntaxRefence(declaration, analyzer, analysisScope, analysisStateOpt, out analyzerStateOpt)) + { + ExecuteSyntaxNodeActionsCore(nodesToAnalyze, nodeActionsByKind, analyzer, model, getKind, filterSpan, analyzerStateOpt); + } + } + finally + { + analyzerStateOpt?.ResetToReadyState(); + } + } + + private void ExecuteSyntaxNodeActionsCore( + IEnumerable nodesToAnalyze, + IDictionary>> nodeActionsByKind, + DiagnosticAnalyzer analyzer, + SemanticModel model, + Func getKind, + TextSpan filterSpan, + SyntaxNodeAnalyzerStateData analyzerStateOpt) + where TLanguageKindEnum : struct + { + var addDiagnostic = GetAddDiagnostic(model.SyntaxTree, filterSpan, analyzer, isSyntaxDiagnostic: false); + ExecuteSyntaxNodeActions(nodesToAnalyze, nodeActionsByKind, model, getKind, addDiagnostic, analyzerStateOpt); + } + + private void ExecuteSyntaxNodeActions( + IEnumerable nodesToAnalyze, + IDictionary>> nodeActionsByKind, + SemanticModel model, + Func getKind, + Action addDiagnostic, + SyntaxNodeAnalyzerStateData analyzerStateOpt) + where TLanguageKindEnum : struct + { + Debug.Assert(nodeActionsByKind != null); + Debug.Assert(nodeActionsByKind.Any()); + + SyntaxNode partiallyProcessedNode = analyzerStateOpt?.CurrentNode; + if (partiallyProcessedNode != null) + { + ExecuteSyntaxNodeActions(partiallyProcessedNode, nodeActionsByKind, model, getKind, addDiagnostic, analyzerStateOpt); + } + + foreach (var child in nodesToAnalyze) + { + if (ShouldExecuteNode(analyzerStateOpt, child)) + { + SetCurrentNode(analyzerStateOpt, child); + + ExecuteSyntaxNodeActions(child, nodeActionsByKind, model, getKind, addDiagnostic, analyzerStateOpt); + } + } + } + + private void ExecuteSyntaxNodeActions( + SyntaxNode node, + IDictionary>> nodeActionsByKind, + SemanticModel model, + Func getKind, + Action addDiagnostic, + SyntaxNodeAnalyzerStateData analyzerStateOpt) + where TLanguageKindEnum : struct + { + ImmutableArray> actionsForKind; + if (nodeActionsByKind.TryGetValue(getKind(node), out actionsForKind)) + { + foreach (var action in actionsForKind) + { + ExecuteSyntaxNodeAction(action, node, model, addDiagnostic, analyzerStateOpt); + } + } + + analyzerStateOpt?.ClearNodeAnalysisState(); + } + + internal static bool CanHaveExecutableCodeBlock(ISymbol symbol) + { + switch (symbol.Kind) + { + case SymbolKind.Method: + case SymbolKind.Event: + case SymbolKind.Property: + case SymbolKind.NamedType: + return true; + + case SymbolKind.Field: + Debug.Assert(((IFieldSymbol)symbol).AssociatedSymbol == null); + return true; + + default: + return false; + } + } + + internal void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Action analyze) + { + object gate = _getAnalyzerGateOpt?.Invoke(analyzer); + if (gate != null) + { + lock (gate) + { + ExecuteAndCatchIfThrows_NoLock(analyzer, analyze); + } + } + else + { + ExecuteAndCatchIfThrows_NoLock(analyzer, analyze); + } + } + + private void ExecuteAndCatchIfThrows_NoLock(DiagnosticAnalyzer analyzer, Action analyze) + { + try + { + Stopwatch timer = null; + if (_analyzerExecutionTimeMapOpt != null) + { + timer = Stopwatch.StartNew(); + } + + analyze(); + + if (timer != null) + { + timer.Stop(); + + _analyzerExecutionTimeMapOpt.AddOrUpdate(analyzer, timer.Elapsed, (a, accumulatedTime) => accumulatedTime + timer.Elapsed); + } + } + catch (Exception e) when (!IsCanceled(e, _cancellationToken)) + { + // Diagnostic for analyzer exception. + var diagnostic = CreateAnalyzerExceptionDiagnostic(analyzer, e); + try + { + _onAnalyzerException(e, analyzer, diagnostic); + } + catch (Exception) + { + // Ignore exceptions from exception handlers. + } + } + } + + internal static bool IsCanceled(Exception ex, CancellationToken cancellationToken) + { + return (ex as OperationCanceledException)?.CancellationToken == cancellationToken; + } + + internal static Diagnostic CreateAnalyzerExceptionDiagnostic(DiagnosticAnalyzer analyzer, Exception e) + { + var analyzerName = analyzer.ToString(); + + // TODO: It is not ideal to create a new descriptor per analyzer exception diagnostic instance. + // However, until we add a LongMessage field to the Diagnostic, we are forced to park the instance specific description onto the Descriptor's Description field. + // This requires us to create a new DiagnosticDescriptor instance per diagnostic instance. + var descriptor = new DiagnosticDescriptor(AnalyzerExceptionDiagnosticId, + title: AnalyzerDriverResources.AnalyzerFailure, + messageFormat: AnalyzerDriverResources.AnalyzerThrows, + description: string.Format(AnalyzerDriverResources.AnalyzerThrowsDescription, analyzerName, e.ToString()), + category: DiagnosticCategory, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + customTags: WellKnownDiagnosticTags.AnalyzerException); + + return Diagnostic.Create(descriptor, Location.None, analyzerName, e.GetType(), e.Message); + } + + internal static bool IsAnalyzerExceptionDiagnostic(Diagnostic diagnostic) + { + if (diagnostic.Id == AnalyzerExceptionDiagnosticId) + { +#pragma warning disable RS0013 // Its ok to realize the Descriptor for analyzer exception diagnostics, which are descriptor based and also rare. + foreach (var tag in diagnostic.Descriptor.CustomTags) +#pragma warning restore RS0013 + { + if (tag == WellKnownDiagnosticTags.AnalyzerException) + { + return true; + } + } + } + + return false; + } + + internal static bool AreEquivalentAnalyzerExceptionDiagnostics(Diagnostic exceptionDiagnostic, Diagnostic other) + { + // We need to have custom de-duplication logic for diagnostics generated for analyzer exceptions. + // We create a new descriptor instance per each analyzer exception diagnostic instance (see comments in method "GetAnalyzerExceptionDiagnostic" above). + // This is primarily to allow us to embed exception stack trace in the diagnostic description. + // However, this might mean that two exception diagnostics which are equivalent in terms of ID and Message, might not have equal description strings. + // We want to classify such diagnostics as equal for de-duplication purpose to reduce the noise in output. + + Debug.Assert(IsAnalyzerExceptionDiagnostic(exceptionDiagnostic)); + + if (!IsAnalyzerExceptionDiagnostic(other)) + { + return false; + } + + return exceptionDiagnostic.Id == other.Id && + exceptionDiagnostic.Severity == other.Severity && + exceptionDiagnostic.GetMessage() == other.GetMessage(); + } + + private bool IsSupportedDiagnostic(DiagnosticAnalyzer analyzer, Diagnostic diagnostic) + { + Debug.Assert(_isCompilerAnalyzer != null); + + return _analyzerManager.IsSupportedDiagnostic(analyzer, diagnostic, _isCompilerAnalyzer, this); + } + + private static Action GetAddDiagnostic(ISymbol contextSymbol, Compilation compilation, DiagnosticAnalyzer analyzer, Action addDiagnostic, Action addLocalDiagnostic, Action addNonLocalDiagnostic, Func getTopMostNodeForAnalysis) + { + if (addLocalDiagnostic == null) + { + return addDiagnostic; + } + + return diagnostic => + { + if (diagnostic.Location.IsInSource) + { + foreach (var syntaxRef in contextSymbol.DeclaringSyntaxReferences) + { + if (syntaxRef.SyntaxTree == diagnostic.Location.SourceTree) + { + var syntax = getTopMostNodeForAnalysis(contextSymbol, syntaxRef, compilation); + if (diagnostic.Location.SourceSpan.IntersectsWith(syntax.FullSpan)) + { + addLocalDiagnostic(diagnostic, analyzer, false); + return; + } + } + } + } + + addNonLocalDiagnostic(diagnostic, analyzer); + }; + } + + private Action GetAddCompilationDiagnostic(DiagnosticAnalyzer analyzer) + { + if (_addNonLocalDiagnosticOpt == null) + { + return _addDiagnostic; + } + + return diagnostic => + { + _addNonLocalDiagnosticOpt(diagnostic, analyzer); + }; + } + + private Action GetAddDiagnostic(SyntaxTree tree, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) + { + return GetAddDiagnostic(tree, null, analyzer, isSyntaxDiagnostic, _addDiagnostic, _addLocalDiagnosticOpt, _addNonLocalDiagnosticOpt); + } + + private Action GetAddDiagnostic(SyntaxTree tree, TextSpan? span, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) + { + return GetAddDiagnostic(tree, span, analyzer, false, _addDiagnostic, _addLocalDiagnosticOpt, _addNonLocalDiagnosticOpt); + } + + private static Action GetAddDiagnostic(SyntaxTree contextTree, TextSpan? span, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic, Action addDiagnostic, Action addLocalDiagnostic, Action addNonLocalDiagnostic) + { + if (addLocalDiagnostic == null) + { + return addDiagnostic; + } + + return diagnostic => + { + if (diagnostic.Location.IsInSource && + contextTree == diagnostic.Location.SourceTree && + (!span.HasValue || span.Value.IntersectsWith(diagnostic.Location.SourceSpan))) + { + addLocalDiagnostic(diagnostic, analyzer, isSyntaxDiagnostic); + } + else + { + addNonLocalDiagnostic(diagnostic, analyzer); + } + }; + } + + private static bool ShouldExecuteAction(AnalyzerStateData analyzerStateOpt, AnalyzerAction action) + { + return analyzerStateOpt == null || !analyzerStateOpt.ProcessedActions.Contains(action); + } + + private static bool ShouldExecuteNode(SyntaxNodeAnalyzerStateData analyzerStateOpt, SyntaxNode node) + { + return analyzerStateOpt == null || !analyzerStateOpt.ProcessedNodes.Contains(node); + } + + private static void SetCurrentNode(SyntaxNodeAnalyzerStateData analyzerStateOpt, SyntaxNode node) + { + if (analyzerStateOpt != null) + { + Debug.Assert(node != null); + analyzerStateOpt.CurrentNode = node; + } + } + + private static bool TryStartProcessingEvent(CompilationEvent nonSymbolCompilationEvent, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) + { + Debug.Assert(!(nonSymbolCompilationEvent is SymbolDeclaredCompilationEvent)); + Debug.Assert(analysisScope.Analyzers.Contains(analyzer)); + + analyzerStateOpt = null; + return analysisStateOpt == null || analysisStateOpt.TryStartProcessingEvent(nonSymbolCompilationEvent, analyzer, out analyzerStateOpt); + } + + private static bool TryStartSyntaxAnalysis(SyntaxTree tree, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) + { + Debug.Assert(analysisScope.Analyzers.Contains(analyzer)); + + analyzerStateOpt = null; + return analysisStateOpt == null || analysisStateOpt.TryStartSyntaxAnalysis(tree, analyzer, out analyzerStateOpt); + } + + private static bool TryStartAnalyzingSymbol(ISymbol symbol, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out AnalyzerStateData analyzerStateOpt) + { + Debug.Assert(analysisScope.Analyzers.Contains(analyzer)); + + analyzerStateOpt = null; + return analysisStateOpt == null || analysisStateOpt.TryStartAnalyzingSymbol(symbol, analyzer, out analyzerStateOpt); + } + + private static bool TryStartAnalyzingSyntaxRefence(SyntaxReference syntaxReference, DiagnosticAnalyzer analyzer, AnalysisScope analysisScope, AnalysisState analysisStateOpt, out DeclarationAnalyzerStateData analyzerStateOpt) + { + Debug.Assert(analysisScope.Analyzers.Contains(analyzer)); + + analyzerStateOpt = null; + return analysisStateOpt == null || analysisStateOpt.TryStartAnalyzingDeclaration(syntaxReference, analyzer, out analyzerStateOpt); + } + + internal TimeSpan ResetAnalyzerExecutionTime(DiagnosticAnalyzer analyzer) + { + Debug.Assert(_analyzerExecutionTimeMapOpt != null); + TimeSpan executionTime; + if (!_analyzerExecutionTimeMapOpt.TryRemove(analyzer, out executionTime)) + { + executionTime = default(TimeSpan); + } + + return executionTime; + } + } +} diff --git a/src/Compilers/Core/AnalyzerDriver/AnalyzerManager.AnalyzerAndOptions.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.AnalyzerAndOptions.cs similarity index 100% rename from src/Compilers/Core/AnalyzerDriver/AnalyzerManager.AnalyzerAndOptions.cs rename to src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.AnalyzerAndOptions.cs diff --git a/src/Compilers/Core/AnalyzerDriver/AnalyzerManager.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs similarity index 88% rename from src/Compilers/Core/AnalyzerDriver/AnalyzerManager.cs rename to src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs index b178402d38709e174278ae7293874bc71273c5ab..91be939f37d34bde758b7b60929387864a821921 100644 --- a/src/Compilers/Core/AnalyzerDriver/AnalyzerManager.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs @@ -20,8 +20,8 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// 3) registered during Initialize are invoked only once per-compilation per- /// /// - /// TODO: Consider moving and relevant APIs and - /// out of the AnalyzerManager and into analyzer drivers. + /// TODO: Consider moving and relevant APIs + /// out of the AnalyzerManager and into analyzer drivers. /// internal partial class AnalyzerManager { @@ -148,30 +148,6 @@ public async Task GetAnalyzerActionsAsync(DiagnosticAnalyzer an return sessionScope.GetAnalyzerActions(analyzer); } - /// - /// Returns true if analyzer registered a compilation start action during - /// which registered a compilation end action and at least one other analyzer action, that the end action depends upon. - /// - public async Task GetAnalyzerHasDependentCompilationEndAsync(DiagnosticAnalyzer analyzer, AnalyzerExecutor analyzerExecutor) - { - var sessionScope = await GetSessionAnalysisScopeAsync(analyzer, analyzerExecutor).ConfigureAwait(false); - if (sessionScope.CompilationStartActions.Length > 0 && analyzerExecutor.Compilation != null) - { - var compilationScope = await GetCompilationAnalysisScopeAsync(analyzer, sessionScope, analyzerExecutor).ConfigureAwait(false); - var compilationActions = compilationScope.GetCompilationOnlyAnalyzerActions(analyzer); - return compilationActions != null && - compilationActions.CompilationEndActionsCount > 0 && - (compilationActions.CodeBlockEndActionsCount > 0 || - compilationActions.CodeBlockStartActionsCount > 0 || - compilationActions.SemanticModelActionsCount > 0 || - compilationActions.SymbolActionsCount > 0 || - compilationActions.SyntaxNodeActionsCount > 0 || - compilationActions.SyntaxTreeActionsCount > 0); - } - - return false; - } - /// /// Return of given . /// @@ -184,7 +160,14 @@ public async Task GetAnalyzerHasDependentCompilationEndAsync(DiagnosticAna var supportedDiagnostics = ImmutableArray.Empty; // Catch Exception from analyzer.SupportedDiagnostics - analyzerExecutor.ExecuteAndCatchIfThrows(analyzer, () => { supportedDiagnostics = analyzer.SupportedDiagnostics; }); + analyzerExecutor.ExecuteAndCatchIfThrows(analyzer, () => + { + var supportedDiagnosticsLocal = analyzer.SupportedDiagnostics; + if (!supportedDiagnosticsLocal.IsDefaultOrEmpty) + { + supportedDiagnostics = supportedDiagnosticsLocal; + } + }); EventHandler handler = null; Action onAnalyzerException = analyzerExecutor.OnAnalyzerException; @@ -192,7 +175,7 @@ public async Task GetAnalyzerHasDependentCompilationEndAsync(DiagnosticAna { handler = new EventHandler((sender, ex) => { - var diagnostic = AnalyzerExecutor.GetAnalyzerExceptionDiagnostic(analyzer, ex); + var diagnostic = AnalyzerExecutor.CreateAnalyzerExceptionDiagnostic(analyzer, ex); onAnalyzerException(ex, analyzer, diagnostic); }); @@ -217,10 +200,13 @@ public async Task GetAnalyzerHasDependentCompilationEndAsync(DiagnosticAna /// internal void ClearAnalyzerState(ImmutableArray analyzers) { - foreach (var analyzer in analyzers) + if (!analyzers.IsDefaultOrEmpty) { - ClearDescriptorState(analyzer); - ClearAnalysisScopeState(analyzer); + foreach (var analyzer in analyzers) + { + ClearDescriptorState(analyzer); + ClearAnalysisScopeState(analyzer); + } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.ActionCounts.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.ActionCounts.cs new file mode 100644 index 0000000000000000000000000000000000000000..49d5efe0a97131773c27f42b21958cbc6c7503ae --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.ActionCounts.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Diagnostics.Telemetry +{ + public static partial class AnalyzerTelemetry + { + /// + /// Contains the counts of registered actions for an analyzer. + /// + public class ActionCounts + { + internal ActionCounts(AnalyzerActions analyzerActions) + { + analyzerActions = analyzerActions ?? AnalyzerActions.Empty; + CompilationStartActionsCount = analyzerActions.CompilationStartActionsCount; + CompilationEndActionsCount = analyzerActions.CompilationEndActionsCount; + CompilationActionsCount = analyzerActions.CompilationActionsCount; + SyntaxTreeActionsCount = analyzerActions.SyntaxTreeActionsCount; + SemanticModelActionsCount = analyzerActions.SemanticModelActionsCount; + SymbolActionsCount = analyzerActions.SymbolActionsCount; + SyntaxNodeActionsCount = analyzerActions.SyntaxNodeActionsCount; + CodeBlockStartActionsCount = analyzerActions.CodeBlockStartActionsCount; + CodeBlockEndActionsCount = analyzerActions.CodeBlockEndActionsCount; + CodeBlockActionsCount = analyzerActions.CodeBlockActionsCount; + } + + /// + /// Count of registered compilation start actions. + /// + public int CompilationStartActionsCount { get; private set; } + + /// + /// Count of registered compilation end actions. + /// + public int CompilationEndActionsCount { get; private set; } + + /// + /// Count of registered compilation actions. + /// + public int CompilationActionsCount { get; private set; } + + /// + /// Count of registered syntax tree actions. + /// + public int SyntaxTreeActionsCount { get; private set; } + + /// + /// Count of registered semantic model actions. + /// + public int SemanticModelActionsCount { get; private set; } + + /// + /// Count of registered symbol actions. + /// + public int SymbolActionsCount { get; private set; } + + /// + /// Count of registered syntax node actions. + /// + public int SyntaxNodeActionsCount { get; private set; } + + /// + /// Count of code block start actions. + /// + public int CodeBlockStartActionsCount { get; private set; } + + /// + /// Count of code block end actions. + /// + public int CodeBlockEndActionsCount { get; private set; } + + /// + /// Count of code block actions. + /// + public int CodeBlockActionsCount { get; private set; } + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs new file mode 100644 index 0000000000000000000000000000000000000000..c190c712bb8e183ea668731212f9dda6392490f4 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerTelemetry.cs @@ -0,0 +1,27 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.Diagnostics.Telemetry +{ + public static partial class AnalyzerTelemetry + { + /// + /// Gets the count of registered actions for the analyzer for the given . + /// + public static async Task GetAnalyzerActionCountsAsync(this CompilationWithAnalyzers compilationWithAnalyzers, DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) + { + return await compilationWithAnalyzers.GetAnalyzerActionCountsAsync(analyzer, cancellationToken).ConfigureAwait(false); + } + + /// + /// Gets the execution time for the given analyzer for the given . + /// + public static TimeSpan GetAnalyzerExecutionTime(this CompilationWithAnalyzers compilationWithAnalyzers, DiagnosticAnalyzer analyzer) + { + return compilationWithAnalyzers.GetAnalyzerExecutionTime(analyzer); + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationUnitCompletedEvent.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationUnitCompletedEvent.cs index 3102551055bb075390612a5e65050f052e304b4d..b04a3a25552b7f7b6d84c60cc1fd493675bb8d35 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationUnitCompletedEvent.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationUnitCompletedEvent.cs @@ -11,6 +11,10 @@ public CompilationUnitCompletedEvent(Compilation compilation, SyntaxTree compila { this.CompilationUnit = compilationUnit; } + public CompilationUnitCompletedEvent(CompilationUnitCompletedEvent original, SemanticModel newSemanticModel) : this(original.Compilation, original.CompilationUnit) + { + SemanticModel = newSemanticModel; + } private WeakReference _weakModel; public SemanticModel SemanticModel { @@ -35,6 +39,10 @@ override public void FlushCache() } public SyntaxTree CompilationUnit { get; } + public CompilationUnitCompletedEvent WithSemanticModel(SemanticModel model) + { + return new CompilationUnitCompletedEvent(this, model); + } public override string ToString() { return "CompilationUnitCompletedEvent(" + CompilationUnit.FilePath + ")"; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index 5ce5e28b4a67588db13e9fbf75de132e3e93ef5d..7677637a2c707714ca73ff19cf7b1ffe6c5f6b0c 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -3,24 +3,94 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry; namespace Microsoft.CodeAnalysis.Diagnostics { public class CompilationWithAnalyzers { - private readonly AnalyzerDriver _driver; private readonly Compilation _compilation; + private readonly ImmutableArray _analyzers; + private readonly CompilationWithAnalyzersOptions _analysisOptions; private readonly CancellationToken _cancellationToken; - private readonly ConcurrentSet _exceptionDiagnostics; + + /// + /// Pool of s used for analyzer execution. + /// + private readonly ObjectPool _driverPool; - public Compilation Compilation - { - get { return _compilation; } - } + /// + /// Contains the partial analysis state per-analyzer. It tracks: + /// 1. Global set of pending compilation events. This is used to populate the event queue for analyzer execution. + /// 2. Per-analyzer set of pending compilation events, symbols, declarations, etc. Each of these pending entities has a state object to track partial analysis. + /// + private readonly AnalysisState _analysisState; + + /// + /// Cache of the current analysis results: + /// 1. Local and non-local diagnostics. + /// 2. Analyzer execution times, if is true. + /// + private readonly AnalysisResult _analysisResult; + + /// + /// Set of exception diagnostics reported for exceptions thrown by the analyzers. + /// + private readonly ConcurrentSet _exceptionDiagnostics = new ConcurrentSet(); + + /// + /// Lock to track the set of active tasks computing tree diagnostics and task computing compilation diagnostics. + /// + private readonly object _executingTasksLock = new object(); + private readonly Dictionary> _executingConcurrentTreeTasksOpt; + private Tuple _executingCompilationOrNonConcurrentTreeTask; + + /// + /// Used to generate a unique token for each tree diagnostics request. + /// The token is used to determine the priority of each request. + /// Each new tree diagnostic request gets an incremented token value and has higher priority over other requests for the same tree. + /// Compilation diagnostics requests always have the lowest priority. + /// + private int _currentToken = 0; + + /// + /// Map from active tasks computing tree diagnostics to their token number. + /// + private readonly Dictionary _concurrentTreeTaskTokensOpt; + + /// + /// Pool of event queues to serve each diagnostics request. + /// + private static readonly ObjectPool> s_eventQueuePool = new ObjectPool>(() => new AsyncQueue()); + private static readonly AsyncQueue s_EmptyEventQueue = new AsyncQueue(); + + + /// + /// Underlying with a non-null , used to drive analyzer execution. + /// + public Compilation Compilation => _compilation; + + /// + /// Analyzers to execute on the compilation. + /// + public ImmutableArray Analyzers => _analyzers; + + /// + /// Options to configure analyzer execution. + /// + public CompilationWithAnalyzersOptions AnalysisOptions => _analysisOptions; + + /// + /// An optional cancellation token which can be used to cancel analysis. + /// Note: This token is only used if the API invoked to get diagnostics doesn't provide a cancellation token. + /// + public CancellationToken CancellationToken => _cancellationToken; /// /// Creates a new compilation by attaching diagnostic analyzers to an existing compilation. @@ -30,23 +100,74 @@ public Compilation Compilation /// Options that are passed to analyzers. /// A cancellation token that can be used to abort analysis. public CompilationWithAnalyzers(Compilation compilation, ImmutableArray analyzers, AnalyzerOptions options, CancellationToken cancellationToken) + : this(compilation, analyzers, new CompilationWithAnalyzersOptions(options, onAnalyzerException: null, concurrentAnalysis: true, logAnalyzerExecutionTime: true), cancellationToken) + { + } + + /// + /// Creates a new compilation by attaching diagnostic analyzers to an existing compilation. + /// + /// The original compilation. + /// The set of analyzers to include in future analyses. + /// Options to configure analyzer execution. + public CompilationWithAnalyzers(Compilation compilation, ImmutableArray analyzers, CompilationWithAnalyzersOptions analysisOptions) + : this (compilation, analyzers, analysisOptions, cancellationToken: CancellationToken.None) + { + } + + private CompilationWithAnalyzers(Compilation compilation, ImmutableArray analyzers, CompilationWithAnalyzersOptions analysisOptions, CancellationToken cancellationToken) + { + VerifyArguments(compilation, analyzers, analysisOptions); + + _compilation = compilation.WithEventQueue(new AsyncQueue()); + _analyzers = analyzers; + _analysisOptions = analysisOptions; + _cancellationToken = cancellationToken; + + _analysisState = new AnalysisState(analyzers); + _analysisResult = new AnalysisResult(analysisOptions.LogAnalyzerExecutionTime, analyzers); + _driverPool = new ObjectPool(() => compilation.AnalyzerForLanguage(analyzers, AnalyzerManager.Instance)); + _executingConcurrentTreeTasksOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary>() : null; + _concurrentTreeTaskTokensOpt = analysisOptions.ConcurrentAnalysis ? new Dictionary() : null; + _executingCompilationOrNonConcurrentTreeTask = null; + } + + private void AddExceptionDiagnostic(Exception exception, DiagnosticAnalyzer analyzer, Diagnostic diagnostic) + { + if (_analysisOptions.OnAnalyzerException != null) + { + _analysisOptions.OnAnalyzerException(exception, analyzer, diagnostic); + } + + _exceptionDiagnostics.Add(diagnostic); + } + + #region Helper methods for public API argument validation + + private static void VerifyArguments(Compilation compilation, ImmutableArray analyzers, CompilationWithAnalyzersOptions analysisOptions) { if (compilation == null) { throw new ArgumentNullException(nameof(compilation)); } - VerifyAnalyzersArgument(analyzers); + if (analysisOptions == null) + { + throw new ArgumentNullException(nameof(analysisOptions)); + } - _cancellationToken = cancellationToken; - _exceptionDiagnostics = new ConcurrentSet(); - _driver = AnalyzerDriver.Create(compilation, analyzers, options, AnalyzerManager.Instance, AddExceptionDiagnostic, false, out _compilation, _cancellationToken); + VerifyAnalyzersArgumentForStaticApis(analyzers); } - private static void VerifyAnalyzersArgument(ImmutableArray analyzers) + private static void VerifyAnalyzersArgumentForStaticApis(ImmutableArray analyzers, bool allowDefaultOrEmpty = false) { if (analyzers.IsDefaultOrEmpty) { + if (allowDefaultOrEmpty) + { + return; + } + throw new ArgumentException(CodeAnalysisResources.ArgumentCannotBeEmpty, nameof(analyzers)); } @@ -54,35 +175,708 @@ private static void VerifyAnalyzersArgument(ImmutableArray a { throw new ArgumentException(CodeAnalysisResources.ArgumentElementCannotBeNull, nameof(analyzers)); } + + if (analyzers.Distinct().Length != analyzers.Length) + { + // Has duplicate analyzer instances. + throw new ArgumentException(CodeAnalysisResources.DuplicateAnalyzerInstances, nameof(analyzers)); + } } - private void AddExceptionDiagnostic(Diagnostic diagnostic) + private void VerifyAnalyzersArgument(ImmutableArray analyzers) { - _exceptionDiagnostics.Add(diagnostic); + VerifyAnalyzersArgumentForStaticApis(analyzers); + + if (analyzers.Any(a => !_analyzers.Contains(a))) + { + throw new ArgumentException(CodeAnalysisResources.UnsupportedAnalyzerInstance, nameof(analyzers)); + } + } + + private void VerifyAnalyzerArgument(DiagnosticAnalyzer analyzer) + { + VerifyAnalyzerArgumentForStaticApis(analyzer); + + if (!_analyzers.Contains(analyzer)) + { + throw new ArgumentException(CodeAnalysisResources.UnsupportedAnalyzerInstance, nameof(analyzer)); + } + } + + private static void VerifyAnalyzerArgumentForStaticApis(DiagnosticAnalyzer analyzer) + { + if (analyzer == null) + { + throw new ArgumentException(CodeAnalysisResources.ArgumentCannotBeEmpty, nameof(analyzer)); + } + } + + private void VerifyExistingAnalyzersArgument(ImmutableArray analyzers) + { + VerifyAnalyzersArgumentForStaticApis(analyzers); + + if (analyzers.Any(a => !_analyzers.Contains(a))) + { + throw new ArgumentException(CodeAnalysisResources.UnsupportedAnalyzerInstance, nameof(_analyzers)); + } + + if (analyzers.Distinct().Length != analyzers.Length) + { + // Has duplicate analyzer instances. + throw new ArgumentException(CodeAnalysisResources.DuplicateAnalyzerInstances, nameof(analyzers)); + } + } + + private void VerifyModel(SemanticModel model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (!_compilation.ContainsSyntaxTree(model.SyntaxTree)) + { + throw new ArgumentException(CodeAnalysisResources.InvalidTree, nameof(model)); + } + } + + private void VerifyTree(SyntaxTree tree) + { + if (tree == null) + { + throw new ArgumentNullException(nameof(tree)); + } + + if (!_compilation.ContainsSyntaxTree(tree)) + { + throw new ArgumentException(CodeAnalysisResources.InvalidTree, nameof(tree)); + } + } + + #endregion + + /// + /// Returns diagnostics produced by all . + /// + public Task> GetAnalyzerDiagnosticsAsync() + { + return GetAnalyzerDiagnosticsAsync(_cancellationToken); + } + + /// + /// Returns diagnostics produced by all . + /// + public async Task> GetAnalyzerDiagnosticsAsync(CancellationToken cancellationToken) + { + return await GetAnalyzerDiagnosticsAsync(Analyzers, includeCompilerDiagnostics: false, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns diagnostics produced by given . + /// + /// Analyzers whose diagnostics are required. All the given analyzers must be from the analyzers passed into the constructor of . + /// Cancellation token. + public async Task> GetAnalyzerDiagnosticsAsync(ImmutableArray analyzers, CancellationToken cancellationToken) + { + VerifyExistingAnalyzersArgument(analyzers); + + return await GetAnalyzerDiagnosticsAsync(analyzers, includeCompilerDiagnostics: false, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private async Task> GetAnalyzerDiagnosticsAsync(ImmutableArray analyzers, bool includeCompilerDiagnostics, CancellationToken cancellationToken) + { + return await GetAnalyzerDiagnosticsCoreAsync(analyzers, includeCompilerDiagnostics, includeSourceEvents: true, includeNonSourceEvents: true, forceCompleteCompilation: true, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns all diagnostics produced by compilation and by all . + /// + public Task> GetAllDiagnosticsAsync() + { + return GetAllDiagnosticsAsync(_cancellationToken); + } + + /// + /// Returns all diagnostics produced by compilation and by all . + /// + public async Task> GetAllDiagnosticsAsync(CancellationToken cancellationToken) + { + var diagnostics = await GetAnalyzerDiagnosticsAsync(Analyzers, includeCompilerDiagnostics: true, cancellationToken: cancellationToken).ConfigureAwait(false); + return diagnostics.AddRange(_exceptionDiagnostics); + } + + /// + /// Returns diagnostics produced by compilation actions of all . + /// + public async Task> GetAnalyzerCompilationDiagnosticsAsync(CancellationToken cancellationToken) + { + return await GetAnalyzerCompilationDiagnosticsCoreAsync(Analyzers, cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns diagnostics produced by compilation actions of given . + /// + /// Analyzers whose diagnostics are required. All the given analyzers must be from the analyzers passed into the constructor of . + /// Cancellation token. + public async Task> GetAnalyzerCompilationDiagnosticsAsync(ImmutableArray analyzers, CancellationToken cancellationToken) + { + VerifyExistingAnalyzersArgument(analyzers); + + return await GetAnalyzerCompilationDiagnosticsCoreAsync(analyzers, cancellationToken).ConfigureAwait(false); + } + + private async Task> GetAnalyzerCompilationDiagnosticsCoreAsync(ImmutableArray analyzers, CancellationToken cancellationToken) + { + // PERF: Don't force complete compilation diagnostics (declaration + method body diagnostics) for compiler analyzer as we want to return just the compiler declaration diagnostics. + var forceCompleteCompilation = !(analyzers.SingleOrDefault() is CompilerDiagnosticAnalyzer); + + if (forceCompleteCompilation) + { + // Force analyze entire compilation's source tree events. + await GetAnalyzerDiagnosticsCoreAsync(analyzers, includeCompilerDiagnostics: false, includeSourceEvents: true, includeNonSourceEvents: false, forceCompleteCompilation: true, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + // Now analyze the non-source events. + return await GetAnalyzerDiagnosticsCoreAsync(analyzers, includeCompilerDiagnostics: false, includeSourceEvents: false, includeNonSourceEvents: true, forceCompleteCompilation: forceCompleteCompilation, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + private async Task> GetAnalyzerDiagnosticsCoreAsync(ImmutableArray analyzers, bool includeCompilerDiagnostics, bool includeSourceEvents, bool includeNonSourceEvents, bool forceCompleteCompilation, CancellationToken cancellationToken) + { + Debug.Assert(!includeCompilerDiagnostics || forceCompleteCompilation); + + await WaitForActiveAnalysisTasksAsync(cancellationToken).ConfigureAwait(false); + + var diagnostics = ImmutableArray.Empty; + var analysisScope = new AnalysisScope(_compilation, analyzers, _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + + Action generateCompilationEvents = () => + { + if (forceCompleteCompilation) + { + // Invoke GetDiagnostics to populate the compilation's CompilationEvent queue. + // Discard the returned diagnostics. + var compDiagnostics = _compilation.GetDiagnostics(cancellationToken); + if (includeCompilerDiagnostics) + { + diagnostics = compDiagnostics; + } + } + }; + + Func> getEventQueue = () => + GetPendingEvents(analyzers, includeSourceEvents, includeNonSourceEvents); + + // Compute the analyzer diagnostics for the given analysis scope. + await ComputeAnalyzerDiagnosticsAsync(analysisScope, generateCompilationEvents, getEventQueue, newTaskToken: 0, cancellationToken: cancellationToken).ConfigureAwait(false); + + // Return computed analyzer diagnostics for the given analysis scope. + var analyzerDiagnostics = _analysisResult.GetDiagnostics(analysisScope, getLocalDiagnostics: includeSourceEvents, getNonLocalDiagnostics: includeNonSourceEvents); + return diagnostics.AddRange(analyzerDiagnostics); + } + + /// + /// Returns syntax diagnostics produced by all from analyzing the given . + /// Depending on analyzers' behavior, returned diagnostics can have locations outside the tree, + /// and some diagnostics that would be reported for the tree by an analysis of the complete compilation + /// can be absent. + /// + /// Syntax tree to analyze. + /// Cancellation token. + public async Task> GetAnalyzerSyntaxDiagnosticsAsync(SyntaxTree tree, CancellationToken cancellationToken) + { + VerifyTree(tree); + + return await GetAnalyzerSyntaxDiagnosticsCoreAsync(tree, Analyzers, cancellationToken).ConfigureAwait(false); + } + + /// + /// Returns syntax diagnostics produced by given from analyzing the given . + /// Depending on analyzers' behavior, returned diagnostics can have locations outside the tree, + /// and some diagnostics that would be reported for the tree by an analysis of the complete compilation + /// can be absent. + /// + /// Syntax tree to analyze. + /// Analyzers whose diagnostics are required. All the given analyzers must be from the analyzers passed into the constructor of . + /// Cancellation token. + public async Task> GetAnalyzerSyntaxDiagnosticsAsync(SyntaxTree tree, ImmutableArray analyzers, CancellationToken cancellationToken) + { + VerifyTree(tree); + VerifyExistingAnalyzersArgument(analyzers); + + return await GetAnalyzerSyntaxDiagnosticsCoreAsync(tree, analyzers, cancellationToken).ConfigureAwait(false); + } + + private async Task> GetAnalyzerSyntaxDiagnosticsCoreAsync(SyntaxTree tree, ImmutableArray analyzers, CancellationToken cancellationToken) + { + var taskToken = Interlocked.Increment(ref _currentToken); + + var analysisScope = new AnalysisScope(analyzers, tree, filterSpan: null, syntaxAnalysis: true, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + Action generateCompilationEvents = null; + Func> getEventQueue = () => s_EmptyEventQueue; + + // Compute the analyzer diagnostics for the given analysis scope. + await ComputeAnalyzerDiagnosticsAsync(analysisScope, generateCompilationEvents, getEventQueue, taskToken, cancellationToken).ConfigureAwait(false); + + // Return computed analyzer diagnostics for the given analysis scope. + return _analysisResult.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: false); + } + + /// + /// Returns semantic diagnostics produced by all from analyzing the given , optionally scoped to a . + /// Depending on analyzers' behavior, returned diagnostics can have locations outside the tree, + /// and some diagnostics that would be reported for the tree by an analysis of the complete compilation + /// can be absent. + /// + /// Semantic model representing the syntax tree to analyze. + /// An optional span within the tree to scope analysis. + /// Cancellation token. + public async Task> GetAnalyzerSemanticDiagnosticsAsync(SemanticModel model, TextSpan? filterSpan, CancellationToken cancellationToken) + { + VerifyModel(model); + + return await GetAnalyzerSemanticDiagnosticsCoreAsync(model, filterSpan, Analyzers, cancellationToken).ConfigureAwait(false); } /// - /// Returns diagnostics produced by diagnostic analyzers. + /// Returns semantic diagnostics produced by all from analyzing the given , optionally scoped to a . + /// Depending on analyzers' behavior, returned diagnostics can have locations outside the tree, + /// and some diagnostics that would be reported for the tree by an analysis of the complete compilation + /// can be absent. /// - public async Task> GetAnalyzerDiagnosticsAsync() + /// Semantic model representing the syntax tree to analyze. + /// An optional span within the tree to scope analysis. + /// Analyzers whose diagnostics are required. All the given analyzers must be from the analyzers passed into the constructor of . + /// Cancellation token. + public async Task> GetAnalyzerSemanticDiagnosticsAsync(SemanticModel model, TextSpan? filterSpan, ImmutableArray analyzers, CancellationToken cancellationToken) { - // Invoke GetDiagnostics to populate the compilation's CompilationEvent queue. - // Discard the returned diagnostics. - _compilation.GetDiagnostics(_cancellationToken); + VerifyModel(model); + VerifyExistingAnalyzersArgument(analyzers); - return await _driver.GetDiagnosticsAsync().ConfigureAwait(false); + return await GetAnalyzerSemanticDiagnosticsCoreAsync(model, filterSpan, analyzers, cancellationToken).ConfigureAwait(false); + } + + private async Task> GetAnalyzerSemanticDiagnosticsCoreAsync(SemanticModel model, TextSpan? filterSpan, ImmutableArray analyzers, CancellationToken cancellationToken) + { + var taskToken = Interlocked.Increment(ref _currentToken); + + var analysisScope = new AnalysisScope(analyzers, model.SyntaxTree, filterSpan, syntaxAnalysis: false, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: true); + + // Below invocation will force GetDiagnostics on the model's tree to generate compilation events. + Action generateCompilationEvents = () => + _analysisState.GetOrCreateCachedSemanticModel(model.SyntaxTree, _compilation, cancellationToken); + + Func> getEventQueue = () => GetPendingEvents(analyzers, model.SyntaxTree); + + // Compute the analyzer diagnostics for the given analysis scope. + // We need to loop till symbol analysis is complete for any partial symbols being processed for other tree diagnostic requests. + do + { + await ComputeAnalyzerDiagnosticsAsync(analysisScope, generateCompilationEvents, getEventQueue, taskToken, cancellationToken).ConfigureAwait(false); + } while (_analysisState.HasPendingSymbolAnalysis(analysisScope)); + + // Return computed analyzer diagnostics for the given analysis scope. + return _analysisResult.GetDiagnostics(analysisScope, getLocalDiagnostics: true, getNonLocalDiagnostics: false); + } + + private async Task ComputeAnalyzerDiagnosticsAsync(AnalysisScope analysisScope, Action generateCompilationEventsOpt, Func> getEventQueue, int newTaskToken, CancellationToken cancellationToken) + { + AnalyzerDriver driver = null; + Task computeTask = null; + CancellationTokenSource cts; + + // Generate compilation events, if required. + if (generateCompilationEventsOpt != null) + { + generateCompilationEventsOpt(); + } + + // Populate the events cache from the generated compilation events. + await PopulateEventsCacheAsync(cancellationToken).ConfigureAwait(false); + + try + { + // Get the analyzer driver to execute analysis. + driver = await GetAnalyzerDriverAsync(cancellationToken).ConfigureAwait(false); + + // Driver must have been initialized. + Debug.Assert(driver.WhenInitializedTask != null); + Debug.Assert(driver.WhenInitializedTask.IsCompleted); + + cancellationToken.ThrowIfCancellationRequested(); + + // Track if this task was suspended by another tree diagnostics request for the same tree. + // If so, we wait for the high priority requests to complete before restarting analysis. + bool suspendend; + do + { + suspendend = false; + + // Create a new cancellation source to allow higher priority requests to suspend our analysis. + cts = new CancellationTokenSource(); + + // Link the cancellation source with client supplied cancellation source, so the public API callee can also cancel analysis. + using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken)) + { + try + { + // Core task to compute analyzer diagnostics. + Func> getComputeTask = () => Tuple.Create( + Task.Run(async () => + { + AsyncQueue eventQueue = null; + try + { + // Get event queue with pending events to analyze. + eventQueue = getEventQueue(); + + // Execute analyzer driver on the given analysis scope with the given event queue. + await ComputeAnalyzerDiagnosticsCoreAsync(driver, eventQueue, analysisScope, cancellationToken: linkedCts.Token).ConfigureAwait(false); + } + finally + { + FreeEventQueue(eventQueue); + } + }, + linkedCts.Token), + cts); + + // Wait for higher priority tree document tasks to complete. + computeTask = await SetActiveAnalysisTaskAsync(getComputeTask, analysisScope.FilterTreeOpt, newTaskToken, cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + await computeTask.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + cancellationToken.ThrowIfCancellationRequested(); + suspendend = cts.IsCancellationRequested; + } + finally + { + cts.Dispose(); + ClearExecutingTask(computeTask, analysisScope.FilterTreeOpt); + computeTask = null; + } + } + } while (suspendend); + } + finally + { + FreeDriver(driver); + } + } + + private async Task GetAnalyzerDriverAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Get instance of analyzer driver from the driver pool. + AnalyzerDriver driver = _driverPool.Allocate(); + + try + { + // Start the initialization task, if required. + if (driver.WhenInitializedTask == null) + { + driver.Initialize(_compilation, _analysisOptions, categorizeDiagnostics: true, cancellationToken: cancellationToken); + } + + // Wait for driver initilization to complete: this executes the Initialize and CompilationStartActions to compute all registered actions per-analyzer. + await driver.WhenInitializedTask.ConfigureAwait(false); + } + finally + { + if (driver.WhenInitializedTask.IsCanceled) + { + // If the initialization task was cancelled, we retry again with our own cancellation token. + // This can happen if the task that started the initialization was cancelled by the callee, and the new request picked up this driver instance. + _driverPool.ForgetTrackedObject(driver); + driver = await GetAnalyzerDriverAsync(cancellationToken).ConfigureAwait(false); + } + } + + return driver; + } + + private void FreeDriver(AnalyzerDriver driver) + { + if (driver != null) + { + if (driver.WhenInitializedTask.IsCanceled) + { + _driverPool.ForgetTrackedObject(driver); + } + else + { + _driverPool.Free(driver); + } + } } /// - /// Returns diagnostics produced by compilation and by diagnostic analyzers. + /// Core method for executing analyzers. /// - public async Task> GetAllDiagnosticsAsync() + private async Task ComputeAnalyzerDiagnosticsCoreAsync(AnalyzerDriver driver, AsyncQueue eventQueue, AnalysisScope analysisScope, CancellationToken cancellationToken) { - // Invoke GetDiagnostics to populate the compilation's CompilationEvent queue. - ImmutableArray compilerDiagnostics = _compilation.GetDiagnostics(_cancellationToken); + Debug.Assert(driver.WhenInitializedTask.IsCompleted); - ImmutableArray analyzerDiagnostics = await _driver.GetDiagnosticsAsync().ConfigureAwait(false); - return compilerDiagnostics.AddRange(analyzerDiagnostics).AddRange(_exceptionDiagnostics); + if (eventQueue.Count > 0 || _analysisState.HasPendingSyntaxAnalysis(analysisScope)) + { + try + { + // Perform analysis to compute new diagnostics. + Debug.Assert(!eventQueue.IsCompleted); + await driver.AttachQueueAndProcessAllEventsAsync(eventQueue, analysisScope, _analysisState, cancellationToken: cancellationToken).ConfigureAwait(false); + } + finally + { + // Update the diagnostic results based on the diagnostics reported on the driver. + _analysisResult.StoreAnalysisResult(analysisScope, driver); + } + } + } + + private Task SetActiveAnalysisTaskAsync(Func> getNewAnalysisTask, SyntaxTree treeOpt, int newTaskToken, CancellationToken cancellationToken) + { + if (treeOpt != null) + { + return SetActiveTreeAnalysisTaskAsync(getNewAnalysisTask, treeOpt, newTaskToken, cancellationToken); + } + else + { + return SetActiveCompilationAnalysisTaskAsync(getNewAnalysisTask, cancellationToken); + } + } + + private async Task SetActiveCompilationAnalysisTaskAsync(Func> getNewCompilationTask, CancellationToken cancellationToken) + { + while (true) + { + // Wait for all active tasks, compilation analysis tasks have lowest priority. + await WaitForActiveAnalysisTasksAsync(cancellationToken).ConfigureAwait(false); + + lock (_executingTasksLock) + { + if ((_executingConcurrentTreeTasksOpt == null || _executingConcurrentTreeTasksOpt.Count == 0) && + _executingCompilationOrNonConcurrentTreeTask == null) + { + _executingCompilationOrNonConcurrentTreeTask = getNewCompilationTask(); + return _executingCompilationOrNonConcurrentTreeTask.Item1; + } + } + } + } + + private async Task WaitForActiveAnalysisTasksAsync(CancellationToken cancellationToken) + { + var executingTasks = ArrayBuilder>.GetInstance(); + + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + + lock (_executingTasksLock) + { + if (_executingConcurrentTreeTasksOpt?.Count > 0) + { + executingTasks.AddRange(_executingConcurrentTreeTasksOpt.Values); + } + + if (_executingCompilationOrNonConcurrentTreeTask != null) + { + executingTasks.Add(_executingCompilationOrNonConcurrentTreeTask); + } + } + + if (executingTasks.Count == 0) + { + executingTasks.Free(); + return; + } + + foreach (var task in executingTasks) + { + cancellationToken.ThrowIfCancellationRequested(); + await task.Item1.ConfigureAwait(false); + } + + executingTasks.Clear(); + } + } + + private async Task SetActiveTreeAnalysisTaskAsync(Func> getNewTreeAnalysisTask, SyntaxTree tree, int newTaskToken, CancellationToken cancellationToken) + { + while (true) + { + // For concurrent analysis, we must wait for any executing tree task with higher tokens. + Tuple executingTreeTask = null; + + lock (_executingTasksLock) + { + if (!_analysisOptions.ConcurrentAnalysis) + { + // For non-concurrent analysis, just suspend the executing task, if any. + if (_executingCompilationOrNonConcurrentTreeTask != null) + { + SuspendAnalysis_NoLock(_executingCompilationOrNonConcurrentTreeTask.Item1, _executingCompilationOrNonConcurrentTreeTask.Item2); + _executingCompilationOrNonConcurrentTreeTask = null; + } + + var newTask = getNewTreeAnalysisTask(); + _executingCompilationOrNonConcurrentTreeTask = newTask; + return newTask.Item1; + } + + Debug.Assert(_executingConcurrentTreeTasksOpt != null); + Debug.Assert(_concurrentTreeTaskTokensOpt != null); + + if (!_executingConcurrentTreeTasksOpt.TryGetValue(tree, out executingTreeTask) || + _concurrentTreeTaskTokensOpt[executingTreeTask.Item1] < newTaskToken) + { + if (executingTreeTask != null) + { + SuspendAnalysis_NoLock(executingTreeTask.Item1, executingTreeTask.Item2); + } + + if (_executingCompilationOrNonConcurrentTreeTask != null) + { + SuspendAnalysis_NoLock(_executingCompilationOrNonConcurrentTreeTask.Item1, _executingCompilationOrNonConcurrentTreeTask.Item2); + _executingCompilationOrNonConcurrentTreeTask = null; + } + + var newTask = getNewTreeAnalysisTask(); + _concurrentTreeTaskTokensOpt[newTask.Item1] = newTaskToken; + _executingConcurrentTreeTasksOpt[tree] = newTask; + return newTask.Item1; + } + } + + await executingTreeTask.Item1.ConfigureAwait(false); + } + } + + private void SuspendAnalysis_NoLock(Task computeTask, CancellationTokenSource cts) + { + if (!computeTask.IsCompleted) + { + // Suspend analysis. + cts.Cancel(); + + // Wait for cancelled task to cleanup. + computeTask.Wait(cts.Token); + } + } + + private void ClearExecutingTask(Task computeTask, SyntaxTree treeOpt) + { + if (computeTask != null) + { + lock (_executingTasksLock) + { + Tuple executingTask; + if (treeOpt != null && _analysisOptions.ConcurrentAnalysis) + { + Debug.Assert(_executingConcurrentTreeTasksOpt != null); + Debug.Assert(_concurrentTreeTaskTokensOpt != null); + + if (_executingConcurrentTreeTasksOpt.TryGetValue(treeOpt, out executingTask) && + executingTask.Item1 == computeTask) + { + _executingConcurrentTreeTasksOpt.Remove(treeOpt); + } + + if (_concurrentTreeTaskTokensOpt.ContainsKey(computeTask)) + { + _concurrentTreeTaskTokensOpt.Remove(computeTask); + } + } + else if (_executingCompilationOrNonConcurrentTreeTask?.Item1 == computeTask) + { + _executingCompilationOrNonConcurrentTreeTask = null; + } + } + } + } + + private async Task PopulateEventsCacheAsync(CancellationToken cancellationToken) + { + if (_compilation.EventQueue.Count > 0) + { + AnalyzerDriver driver = null; + try + { + driver = await GetAnalyzerDriverAsync(cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + + var compilationEvents = DequeueGeneratedCompilationEvents(); + await _analysisState.OnCompilationEventsGeneratedAsync(compilationEvents, driver, cancellationToken).ConfigureAwait(false); + } + finally + { + FreeDriver(driver); + } + } + } + + private ImmutableArray DequeueGeneratedCompilationEvents() + { + var builder = ImmutableArray.CreateBuilder(); + + CompilationEvent compilationEvent; + while (_compilation.EventQueue.TryDequeue(out compilationEvent)) + { + builder.Add(compilationEvent); + } + + return builder.ToImmutable(); + } + + private AsyncQueue GetPendingEvents(ImmutableArray analyzers, SyntaxTree tree) + { + var eventQueue = s_eventQueuePool.Allocate(); + Debug.Assert(!eventQueue.IsCompleted); + Debug.Assert(eventQueue.Count == 0); + + foreach (var compilationEvent in _analysisState.GetPendingEvents(analyzers, tree)) + { + eventQueue.Enqueue(compilationEvent); + } + + return eventQueue; + } + + private AsyncQueue GetPendingEvents(ImmutableArray analyzers, bool includeSourceEvents, bool includeNonSourceEvents) + { + Debug.Assert(includeSourceEvents || includeNonSourceEvents); + + var eventQueue = s_eventQueuePool.Allocate(); + Debug.Assert(!eventQueue.IsCompleted); + Debug.Assert(eventQueue.Count == 0); + + foreach (var compilationEvent in _analysisState.GetPendingEvents(analyzers, includeSourceEvents, includeNonSourceEvents)) + { + eventQueue.Enqueue(compilationEvent); + } + + return eventQueue; + } + + private void FreeEventQueue(AsyncQueue eventQueue) + { + if (eventQueue == null || ReferenceEquals(eventQueue, s_EmptyEventQueue)) + { + return; + } + + Debug.Assert(!eventQueue.IsCompleted); + if (eventQueue.Count > 0) + { + CompilationEvent discarded; + while (eventQueue.TryDequeue(out discarded)) ; + } + + s_eventQueuePool.Free(eventQueue); } /// @@ -129,20 +923,14 @@ public static IEnumerable GetEffectiveDiagnostics(IEnumerable public static bool IsDiagnosticAnalyzerSuppressed(DiagnosticAnalyzer analyzer, CompilationOptions options, Action onAnalyzerException = null) { - if (analyzer == null) - { - throw new ArgumentNullException(nameof(analyzer)); - } + VerifyAnalyzerArgumentForStaticApis(analyzer); if (options == null) { throw new ArgumentNullException(nameof(options)); } - Action voidHandler = (ex, a, diag) => { }; - onAnalyzerException = onAnalyzerException ?? voidHandler; var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance); - return AnalyzerDriver.IsDiagnosticAnalyzerSuppressed(analyzer, options, AnalyzerManager.Instance, analyzerExecutor); } @@ -153,9 +941,44 @@ public static bool IsDiagnosticAnalyzerSuppressed(DiagnosticAnalyzer analyzer, C /// Analyzers whose state needs to be cleared. public static void ClearAnalyzerState(ImmutableArray analyzers) { - VerifyAnalyzersArgument(analyzers); + VerifyAnalyzersArgumentForStaticApis(analyzers, allowDefaultOrEmpty: true); AnalyzerManager.Instance.ClearAnalyzerState(analyzers); } + + /// + /// Gets the count of registered actions for the analyzer. + /// + internal async Task GetAnalyzerActionCountsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) + { + VerifyAnalyzerArgument(analyzer); + + AnalyzerDriver driver = null; + try + { + driver = await GetAnalyzerDriverAsync(cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); + return await _analysisState.GetAnalyzerActionCountsAsync(analyzer, driver, cancellationToken).ConfigureAwait(false); + } + finally + { + FreeDriver(driver); + } + } + + /// + /// Gets the execution time for the given analyzer. + /// + internal TimeSpan GetAnalyzerExecutionTime(DiagnosticAnalyzer analyzer) + { + VerifyAnalyzerArgument(analyzer); + + if (!_analysisOptions.LogAnalyzerExecutionTime) + { + throw new InvalidOperationException(); + } + + return _analysisResult.GetAnalyzerExecutionTime(analyzer); + } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzersOptions.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzersOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..6205de20aa2d2e6cb432ac134a314a2ec1f167ed --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzersOptions.cs @@ -0,0 +1,53 @@ +// 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; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Options to configure analyzer execution within . + /// + public class CompilationWithAnalyzersOptions + { + private readonly AnalyzerOptions _options; + private readonly Action _onAnalyzerException; + private readonly bool _concurrentAnalysis; + private readonly bool _logAnalyzerExecutionTime; + + /// + /// Options passed to s. + /// + public AnalyzerOptions AnalyzerOptions => _options; + + + /// + /// An optional delegate to be invoked when an analyzer throws an exception. + /// + public Action OnAnalyzerException => _onAnalyzerException; + + /// + /// Flag indicating whether analysis can be performed concurrently on multiple threads. + /// + public bool ConcurrentAnalysis => _concurrentAnalysis; + + /// + /// Flag indicating whether analyzer execution time should be logged. + /// + public bool LogAnalyzerExecutionTime => _logAnalyzerExecutionTime; + + /// + /// Creates a new . + /// + /// Options that are passed to analyzers. + /// Action to invoke if an analyzer throws an exception. + /// Flag indicating whether analysis can be performed concurrently on multiple threads. + /// Flag indicating whether analyzer execution time should be logged. + public CompilationWithAnalyzersOptions(AnalyzerOptions options, Action onAnalyzerException, bool concurrentAnalysis, bool logAnalyzerExecutionTime) + { + _options = options; + _onAnalyzerException = onAnalyzerException; + _concurrentAnalysis = concurrentAnalysis; + _logAnalyzerExecutionTime = logAnalyzerExecutionTime; + } + } +} diff --git a/src/Compilers/Core/AnalyzerDriver/DiagnosticAnalysisContextHelpers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContextHelpers.cs similarity index 100% rename from src/Compilers/Core/AnalyzerDriver/DiagnosticAnalysisContextHelpers.cs rename to src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContextHelpers.cs diff --git a/src/Compilers/Core/AnalyzerDriver/DiagnosticAnalyzerAction.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs similarity index 100% rename from src/Compilers/Core/AnalyzerDriver/DiagnosticAnalyzerAction.cs rename to src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerAction.cs diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerExtensions.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerExtensions.cs index 44b3a75735c07f3dc0d53b07e59128c1b691dd76..4d86f929e0cf44b270752ac6573b247b745f32af 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerExtensions.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalyzerExtensions.cs @@ -18,5 +18,16 @@ public static CompilationWithAnalyzers WithAnalyzers(this Compilation compilatio { return new CompilationWithAnalyzers(compilation, analyzers, options, cancellationToken); } + + /// + /// Returns a new compilation with attached diagnostic analyzers. + /// + /// Compilation to which analyzers are to be added. + /// The set of analyzers to include in future analyses. + /// Options to configure analyzer execution within . + public static CompilationWithAnalyzers WithAnalyzers(this Compilation compilation, ImmutableArray analyzers, CompilationWithAnalyzersOptions analysisOptions) + { + return new CompilationWithAnalyzers(compilation, analyzers, analysisOptions); + } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs new file mode 100644 index 0000000000000000000000000000000000000000..7dd335c80f2b91b97b9ea84d74c36d18ada1ce72 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticQueue.cs @@ -0,0 +1,230 @@ +// 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.Linq; +using System.Threading; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Queue to store analyzer diagnostics on the . + /// + internal abstract class DiagnosticQueue + { + public abstract bool TryComplete(); + public abstract bool TryDequeue(out Diagnostic d); + public abstract void Enqueue(Diagnostic diagnostic); + + // Methods specific to CategorizedDiagnosticQueue + public abstract void EnqueueLocal(Diagnostic diagnostic, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic); + public abstract void EnqueueNonLocal(Diagnostic diagnostic, DiagnosticAnalyzer analyzer); + public abstract ImmutableArray DequeueLocalSyntaxDiagnostics(DiagnosticAnalyzer analyzer); + public abstract ImmutableArray DequeueLocalSemanticDiagnostics(DiagnosticAnalyzer analyzer); + public abstract ImmutableArray DequeueNonLocalDiagnostics(DiagnosticAnalyzer analyzer); + + public static DiagnosticQueue Create(bool categorized = false) + { + return categorized ? (DiagnosticQueue)new CategorizedDiagnosticQueue() : new SimpleDiagnosticQueue(); + } + + /// + /// Simple diagnostics queue: maintains all diagnostics reported by all analyzers in a single queue. + /// + private sealed class SimpleDiagnosticQueue : DiagnosticQueue + { + private readonly AsyncQueue _queue; + + public SimpleDiagnosticQueue() + { + _queue = new AsyncQueue(); + } + + public SimpleDiagnosticQueue(Diagnostic diagnostic) + { + _queue = new AsyncQueue(); + _queue.Enqueue(diagnostic); + } + + public override void Enqueue(Diagnostic diagnostic) + { + _queue.Enqueue(diagnostic); + } + + public override void EnqueueLocal(Diagnostic diagnostic, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) + { + _queue.Enqueue(diagnostic); + } + + public override void EnqueueNonLocal(Diagnostic diagnostic, DiagnosticAnalyzer analyzer) + { + _queue.Enqueue(diagnostic); + } + + public override ImmutableArray DequeueLocalSemanticDiagnostics(DiagnosticAnalyzer analyzer) + { + throw new NotImplementedException(); + } + + public override ImmutableArray DequeueLocalSyntaxDiagnostics(DiagnosticAnalyzer analyzer) + { + throw new NotImplementedException(); + } + + public override ImmutableArray DequeueNonLocalDiagnostics(DiagnosticAnalyzer analyzer) + { + throw new NotImplementedException(); + } + + public override bool TryComplete() + { + return _queue.TryComplete(); + } + + public override bool TryDequeue(out Diagnostic d) + { + return _queue.TryDequeue(out d); + } + } + + /// + /// Categorized diagnostics queue: maintains separate set of simple diagnostic queues for local semantic, local syntax and non-local diagnostics for every analyzer. + /// + private sealed class CategorizedDiagnosticQueue : DiagnosticQueue + { + private readonly object _gate = new object(); + private Dictionary _lazyLocalSemanticDiagnostics; + private Dictionary _lazyLocalSyntaxDiagnostics; + private Dictionary _lazyNonLocalDiagnostics; + + public CategorizedDiagnosticQueue() + { + _lazyLocalSemanticDiagnostics = null; + _lazyLocalSyntaxDiagnostics = null; + _lazyNonLocalDiagnostics = null; + } + + public override void Enqueue(Diagnostic diagnostic) + { + throw new InvalidOperationException(); + } + + public override void EnqueueLocal(Diagnostic diagnostic, DiagnosticAnalyzer analyzer, bool isSyntaxDiagnostic) + { + if (isSyntaxDiagnostic) + { + EnqueueCore(ref _lazyLocalSyntaxDiagnostics, diagnostic, analyzer); + } + else + { + EnqueueCore(ref _lazyLocalSemanticDiagnostics, diagnostic, analyzer); + } + } + + public override void EnqueueNonLocal(Diagnostic diagnostic, DiagnosticAnalyzer analyzer) + { + EnqueueCore(ref _lazyNonLocalDiagnostics, diagnostic, analyzer); + } + + private void EnqueueCore(ref Dictionary lazyDiagnosticsMap, Diagnostic diagnostic, DiagnosticAnalyzer analyzer) + { + lock (_gate) + { + lazyDiagnosticsMap = lazyDiagnosticsMap ?? new Dictionary(); + EnqueueCore_NoLock(lazyDiagnosticsMap, diagnostic, analyzer); + } + } + + private static void EnqueueCore_NoLock(Dictionary diagnosticsMap, Diagnostic diagnostic, DiagnosticAnalyzer analyzer) + { + SimpleDiagnosticQueue queue; + if (diagnosticsMap.TryGetValue(analyzer, out queue)) + { + queue.Enqueue(diagnostic); + } + else + { + diagnosticsMap[analyzer] = new SimpleDiagnosticQueue(diagnostic); + } + } + + public override bool TryComplete() + { + return true; + } + + public override bool TryDequeue(out Diagnostic d) + { + lock (_gate) + { + return TryDequeue_NoLock(out d); + } + } + + private bool TryDequeue_NoLock(out Diagnostic d) + { + return TryDequeue_NoLock(_lazyLocalSemanticDiagnostics, out d) || + TryDequeue_NoLock(_lazyLocalSyntaxDiagnostics, out d) || + TryDequeue_NoLock(_lazyNonLocalDiagnostics, out d); + } + + private static bool TryDequeue_NoLock(Dictionary lazyDiagnosticsMap, out Diagnostic d) + { + Diagnostic diag = null; + if (lazyDiagnosticsMap != null && lazyDiagnosticsMap.Any(kvp => kvp.Value.TryDequeue(out diag))) + { + d = diag; + return true; + } + + d = null; + return false; + } + + public override ImmutableArray DequeueLocalSyntaxDiagnostics(DiagnosticAnalyzer analyzer) + { + return DequeueDiagnosticsCore(analyzer, _lazyLocalSyntaxDiagnostics); + } + + public override ImmutableArray DequeueLocalSemanticDiagnostics(DiagnosticAnalyzer analyzer) + { + return DequeueDiagnosticsCore(analyzer, _lazyLocalSemanticDiagnostics); + } + + public override ImmutableArray DequeueNonLocalDiagnostics(DiagnosticAnalyzer analyzer) + { + return DequeueDiagnosticsCore(analyzer, _lazyNonLocalDiagnostics); + } + + private ImmutableArray DequeueDiagnosticsCore(DiagnosticAnalyzer analyzer, Dictionary diagnosticsMap) + { + SimpleDiagnosticQueue queue; + if (TryGetDiagnosticsQueue(analyzer, diagnosticsMap, out queue)) + { + var builder = ImmutableArray.CreateBuilder(); + Diagnostic d; + while (queue.TryDequeue(out d)) + { + builder.Add(d); + } + + return builder.ToImmutable(); + } + + return ImmutableArray.Empty; + } + + private bool TryGetDiagnosticsQueue(DiagnosticAnalyzer analyzer, Dictionary diagnosticsMap, out SimpleDiagnosticQueue queue) + { + queue = null; + + lock (_gate) + { + return diagnosticsMap != null && diagnosticsMap.TryGetValue(analyzer, out queue); + } + } + } + } +} diff --git a/src/Compilers/Core/AnalyzerDriver/DiagnosticStartAnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs similarity index 99% rename from src/Compilers/Core/AnalyzerDriver/DiagnosticStartAnalysisScope.cs rename to src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs index 209600c7cf51189bc14e7de109087e0bb101560e..a318c306199fd24deee198b66209c927b485d996 100644 --- a/src/Compilers/Core/AnalyzerDriver/DiagnosticStartAnalysisScope.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs @@ -476,6 +476,8 @@ internal sealed class AnalyzerActions private ImmutableArray _codeBlockActions = ImmutableArray.Empty; private ImmutableArray _syntaxNodeActions = ImmutableArray.Empty; + internal static readonly AnalyzerActions Empty = new AnalyzerActions(); + internal AnalyzerActions() { } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SymbolDeclaredCompilationEvent.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SymbolDeclaredCompilationEvent.cs index c4085346dd5d6371f8a157cd769418e7e7e2fd20..c2d263c44f5ff8b53ce78a667dfaa702dbe8a654 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/SymbolDeclaredCompilationEvent.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/SymbolDeclaredCompilationEvent.cs @@ -19,6 +19,11 @@ public SymbolDeclaredCompilationEvent(Compilation compilation, ISymbol symbol, L { _lazySemanticModel = lazySemanticModel; } + private SymbolDeclaredCompilationEvent(SymbolDeclaredCompilationEvent original, SemanticModel newSemanticModel) : this(original.Compilation, original.Symbol) + { + _semanticModel = newSemanticModel; + } + public ISymbol Symbol { get; } // At most one of these should be non-null. @@ -65,6 +70,12 @@ override public void FlushCache() _semanticModel = null; } } + + public SymbolDeclaredCompilationEvent WithSemanticModel(SemanticModel model) + { + return new SymbolDeclaredCompilationEvent(this, model); + } + private static SymbolDisplayFormat s_displayFormat = SymbolDisplayFormat.FullyQualifiedFormat; public override string ToString() { diff --git a/src/Compilers/Core/Portable/PublicAPI.txt b/src/Compilers/Core/Portable/PublicAPI.txt index 7458857f770e2f668be7fd75726b415c5317913d..0daa6d55e63fb15b2ab821fdab38f840f8662523 100644 --- a/src/Compilers/Core/Portable/PublicAPI.txt +++ b/src/Compilers/Core/Portable/PublicAPI.txt @@ -317,10 +317,29 @@ Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.Options.get - Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterSymbolAction(System.Action action, params Microsoft.CodeAnalysis.SymbolKind[] symbolKinds) -> void Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterSyntaxNodeAction(System.Action action, params TLanguageKindEnum[] syntaxKinds) -> void Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.AnalysisOptions.get -> Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.Analyzers.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.CancellationToken.get -> System.Threading.CancellationToken Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.Compilation.get -> Microsoft.CodeAnalysis.Compilation Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.CompilationWithAnalyzers(Microsoft.CodeAnalysis.Compilation compilation, System.Collections.Immutable.ImmutableArray analyzers, Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Threading.CancellationToken cancellationToken) -> void +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.CompilationWithAnalyzers(Microsoft.CodeAnalysis.Compilation compilation, System.Collections.Immutable.ImmutableArray analyzers, Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions analysisOptions) -> void Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAllDiagnosticsAsync() -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAllDiagnosticsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerCompilationDiagnosticsAsync(System.Collections.Immutable.ImmutableArray analyzers, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerCompilationDiagnosticsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerDiagnosticsAsync() -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(System.Collections.Immutable.ImmutableArray analyzers, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(Microsoft.CodeAnalysis.SemanticModel model, Microsoft.CodeAnalysis.Text.TextSpan? filterSpan, System.Collections.Immutable.ImmutableArray analyzers, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(Microsoft.CodeAnalysis.SemanticModel model, Microsoft.CodeAnalysis.Text.TextSpan? filterSpan, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerSyntaxDiagnosticsAsync(Microsoft.CodeAnalysis.SyntaxTree tree, System.Collections.Immutable.ImmutableArray analyzers, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetAnalyzerSyntaxDiagnosticsAsync(Microsoft.CodeAnalysis.SyntaxTree tree, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.AnalyzerOptions.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.CompilationWithAnalyzersOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Action onAnalyzerException, bool concurrentAnalysis, bool logAnalyzerExecutionTime) -> void +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.ConcurrentAnalysis.get -> bool +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.LogAnalyzerExecutionTime.get -> bool +Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.OnAnalyzerException.get -> System.Action Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzerAttribute Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzerAttribute.DiagnosticAnalyzerAttribute(string firstLanguage, params string[] additionalLanguages) -> void @@ -352,6 +371,18 @@ Microsoft.CodeAnalysis.Diagnostics.SyntaxTreeAnalysisContext.Options.get -> Micr Microsoft.CodeAnalysis.Diagnostics.SyntaxTreeAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> void Microsoft.CodeAnalysis.Diagnostics.SyntaxTreeAnalysisContext.SyntaxTreeAnalysisContext(Microsoft.CodeAnalysis.SyntaxTree tree, Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Action reportDiagnostic, System.Func isSupportedDiagnostic, System.Threading.CancellationToken cancellationToken) -> void Microsoft.CodeAnalysis.Diagnostics.SyntaxTreeAnalysisContext.Tree.get -> Microsoft.CodeAnalysis.SyntaxTree +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.CodeBlockActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.CodeBlockEndActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.CodeBlockStartActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.CompilationActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.CompilationEndActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.CompilationStartActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.SemanticModelActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.SymbolActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.SyntaxNodeActionsCount.get -> int +Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.ActionCounts.SyntaxTreeActionsCount.get -> int Microsoft.CodeAnalysis.Diagnostics.UnresolvedAnalyzerReference Microsoft.CodeAnalysis.Diagnostics.UnresolvedAnalyzerReference.UnresolvedAnalyzerReference(string unresolvedPath) -> void Microsoft.CodeAnalysis.DllImportData @@ -1943,6 +1974,9 @@ static Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.ClearAnalyzer static Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.GetEffectiveDiagnostics(System.Collections.Generic.IEnumerable diagnostics, Microsoft.CodeAnalysis.Compilation compilation) -> System.Collections.Generic.IEnumerable static Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers.IsDiagnosticAnalyzerSuppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer analyzer, Microsoft.CodeAnalysis.CompilationOptions options, System.Action onAnalyzerException = null) -> bool static Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzerExtensions.WithAnalyzers(this Microsoft.CodeAnalysis.Compilation compilation, System.Collections.Immutable.ImmutableArray analyzers, Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers +static Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzerExtensions.WithAnalyzers(this Microsoft.CodeAnalysis.Compilation compilation, System.Collections.Immutable.ImmutableArray analyzers, Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions analysisOptions) -> Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers +static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.GetAnalyzerActionCountsAsync(this Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers compilationWithAnalyzers, Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer analyzer, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry.GetAnalyzerExecutionTime(this Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzers compilationWithAnalyzers, Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer analyzer) -> System.TimeSpan static Microsoft.CodeAnalysis.DocumentationCommentId.CreateDeclarationId(Microsoft.CodeAnalysis.ISymbol symbol) -> string static Microsoft.CodeAnalysis.DocumentationCommentId.CreateReferenceId(Microsoft.CodeAnalysis.ISymbol symbol) -> string static Microsoft.CodeAnalysis.DocumentationCommentId.GetFirstSymbolForDeclarationId(string id, Microsoft.CodeAnalysis.Compilation compilation) -> Microsoft.CodeAnalysis.ISymbol diff --git a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb index 57ad6e7e3e9052d347ecb02dd35881e5306d4bae..3d8c578dc243f5237007b0407a59083b636a9288 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/VisualBasicCompilation.vb @@ -2073,9 +2073,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return Not hasError End Function - Friend Overrides Function AnalyzerForLanguage(analyzers As ImmutableArray(Of DiagnosticAnalyzer), analyzerManager As AnalyzerManager, cancellationToken As CancellationToken) As AnalyzerDriver + Friend Overrides Function AnalyzerForLanguage(analyzers As ImmutableArray(Of DiagnosticAnalyzer), analyzerManager As AnalyzerManager) As AnalyzerDriver Dim getKind As Func(Of SyntaxNode, SyntaxKind) = Function(node As SyntaxNode) node.Kind - Return New AnalyzerDriver(Of SyntaxKind)(analyzers, getKind, analyzerManager, cancellationToken) + Return New AnalyzerDriver(Of SyntaxKind)(analyzers, getKind, analyzerManager) End Function #End Region diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamespaceSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamespaceSymbol.vb index 8a3a12a8ffd4e2da9f3ab66a7690836e2ccbcf68..9dcf463637e6b8b2ccc14ca61ec95bb4632c294a 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamespaceSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Source/SourceNamespaceSymbol.vb @@ -418,7 +418,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols cancellationToken.ThrowIfCancellationRequested() Dim reference = decl.SyntaxReference - If reference IsNot Nothing Then + If reference IsNot Nothing AndAlso reference.SyntaxTree Is tree Then If Not reference.SyntaxTree.IsEmbeddedOrMyTemplateTree() Then Dim syntaxRef = New NamespaceDeclarationSyntaxReference(reference) Dim syntax = syntaxRef.GetSyntax(cancellationToken) diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Symbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Symbol.vb index a82b5a9a76c6e0754779481490c8f38df9802754..28a5d57ff48cc5035737864edb7ae45f6a067604 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Symbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Symbol.vb @@ -800,9 +800,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ' Returns true if some or all of the symbol is defined in the given source tree. Friend Overridable Function IsDefinedInSourceTree(tree As SyntaxTree, definedWithinSpan As TextSpan?, Optional cancellationToken As CancellationToken = Nothing) As Boolean + Dim declaringReferences = Me.DeclaringSyntaxReferences + If Me.IsImplicitlyDeclared AndAlso declaringReferences.Length = 0 Then + Return Me.ContainingSymbol.IsDefinedInSourceTree(tree, definedWithinSpan, cancellationToken) + End If + ' Default implementation: go through all locations and check for the definition. ' This is overridden for certain special cases (e.g., the implicit default constructor). - For Each syntaxRef In Me.DeclaringSyntaxReferences + For Each syntaxRef In declaringReferences cancellationToken.ThrowIfCancellationRequested() If syntaxRef.SyntaxTree Is tree AndAlso diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs index ac99a4aab26211a77d3d0b28e92a5010860e046c..04cbc723b15fb0cd30cc140236e2354f97c3cbdf 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/DiagnosticAnalyzerDriver/DiagnosticAnalyzerDriverTests.cs @@ -154,6 +154,10 @@ private void AccessSupportedDiagnostics(DiagnosticAnalyzer analyzer) private class ThrowingDoNotCatchDiagnosticAnalyzer : ThrowingDiagnosticAnalyzer, IBuiltInAnalyzer where TLanguageKindEnum : struct { + public DiagnosticAnalyzerCategory GetAnalyzerCategory() + { + return DiagnosticAnalyzerCategory.SyntaxAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis | DiagnosticAnalyzerCategory.ProjectAnalysis; + } } [Fact] diff --git a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingDiagnosticAnalyzer.cs b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingDiagnosticAnalyzer.cs index 558e438ab6f41116d3ad894409666a657305711c..07fed86f1bfb28f215fae21749da64756f8e6254 100644 --- a/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingDiagnosticAnalyzer.cs +++ b/src/EditorFeatures/Core/Implementation/RenameTracking/RenameTrackingDiagnosticAnalyzer.cs @@ -1,5 +1,6 @@ // 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.Immutable; using Microsoft.CodeAnalysis.Diagnostics; using Roslyn.Utilities; @@ -40,5 +41,10 @@ private void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context) context.ReportDiagnostic(diagnostic); } } + + public DiagnosticAnalyzerCategory GetAnalyzerCategory() + { + return DiagnosticAnalyzerCategory.SyntaxAnalysis; + } } } diff --git a/src/EditorFeatures/Test/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs b/src/EditorFeatures/Test/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs index c7acc139ba773be1cd154f831d1aae430e2aac51..6776d1609ab3bcbf6fab1e846473a1bedf8ccf68 100644 --- a/src/EditorFeatures/Test/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs +++ b/src/EditorFeatures/Test/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs @@ -13,16 +13,25 @@ using Microsoft.CodeAnalysis.UnitTests.Diagnostics; using Roslyn.Utilities; using Xunit; +using System.Collections.Concurrent; namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics { public abstract class AbstractDiagnosticProviderBasedUserDiagnosticTest : AbstractUserDiagnosticTest { + private readonly ConcurrentDictionary> _analyzerAndFixerMap = + new ConcurrentDictionary>(); + internal abstract Tuple CreateDiagnosticProviderAndFixer(Workspace workspace); + private Tuple GetOrCreateDiagnosticProviderAndFixer(Workspace workspace) + { + return _analyzerAndFixerMap.GetOrAdd(workspace, CreateDiagnosticProviderAndFixer); + } + internal override IEnumerable GetDiagnostics(TestWorkspace workspace) { - var providerAndFixer = CreateDiagnosticProviderAndFixer(workspace); + var providerAndFixer = GetOrCreateDiagnosticProviderAndFixer(workspace); var provider = providerAndFixer.Item1; TextSpan span; @@ -32,7 +41,7 @@ internal override IEnumerable GetDiagnostics(TestWorkspace workspace internal override IEnumerable> GetDiagnosticAndFixes(TestWorkspace workspace, string fixAllActionId) { - var providerAndFixer = CreateDiagnosticProviderAndFixer(workspace); + var providerAndFixer = GetOrCreateDiagnosticProviderAndFixer(workspace); var provider = providerAndFixer.Item1; Document document; @@ -43,58 +52,61 @@ internal override IEnumerable GetDiagnostics(TestWorkspace workspace document = GetDocumentAndAnnotatedSpan(workspace, out annotation, out span); } - var diagnostics = DiagnosticProviderTestUtilities.GetAllDiagnostics(provider, document, span); + using (var testDriver = new TestDiagnosticAnalyzerDriver(document.Project, provider)) + { + var diagnostics = testDriver.GetAllDiagnostics(provider, document, span); - var fixer = providerAndFixer.Item2; - var ids = new HashSet(fixer.FixableDiagnosticIds); - var dxs = diagnostics.Where(d => ids.Contains(d.Id)).ToList(); + var fixer = providerAndFixer.Item2; + var ids = new HashSet(fixer.FixableDiagnosticIds); + var dxs = diagnostics.Where(d => ids.Contains(d.Id)).ToList(); - foreach (var diagnostic in dxs) - { - if (annotation == null) + foreach (var diagnostic in dxs) { - var fixes = new List(); - var context = new CodeFixContext(document, diagnostic, (a, d) => fixes.Add(new CodeFix(a, d)), CancellationToken.None); - fixer.RegisterCodeFixesAsync(context).Wait(); - if (fixes.Any()) + if (annotation == null) { - var codeFix = new CodeFixCollection(fixer, diagnostic.Location.SourceSpan, fixes); - yield return Tuple.Create(diagnostic, codeFix); + var fixes = new List(); + var context = new CodeFixContext(document, diagnostic, (a, d) => fixes.Add(new CodeFix(a, d)), CancellationToken.None); + fixer.RegisterCodeFixesAsync(context).Wait(); + if (fixes.Any()) + { + var codeFix = new CodeFixCollection(fixer, diagnostic.Location.SourceSpan, fixes); + yield return Tuple.Create(diagnostic, codeFix); + } } - } - else - { - var fixAllProvider = fixer.GetFixAllProvider(); - Assert.NotNull(fixAllProvider); - FixAllScope scope = GetFixAllScope(annotation); + else + { + var fixAllProvider = fixer.GetFixAllProvider(); + Assert.NotNull(fixAllProvider); + FixAllScope scope = GetFixAllScope(annotation); - Func, CancellationToken, Task>> getDocumentDiagnosticsAsync = - (d, diagIds, c) => - { - var root = d.GetSyntaxRootAsync().Result; - var diags = DiagnosticProviderTestUtilities.GetDocumentDiagnostics(provider, d, root.FullSpan); - diags = diags.Where(diag => diagIds.Contains(diag.Id)); - return Task.FromResult(diags); - }; + Func, CancellationToken, Task>> getDocumentDiagnosticsAsync = + (d, diagIds, c) => + { + var root = d.GetSyntaxRootAsync().Result; + var diags = testDriver.GetDocumentDiagnostics(provider, d, root.FullSpan); + diags = diags.Where(diag => diagIds.Contains(diag.Id)); + return Task.FromResult(diags); + }; - Func, CancellationToken, Task>> getProjectDiagnosticsAsync = - (p, includeAllDocumentDiagnostics, diagIds, c) => - { - var diags = includeAllDocumentDiagnostics ? - DiagnosticProviderTestUtilities.GetAllDiagnostics(provider, p) : - DiagnosticProviderTestUtilities.GetProjectDiagnostics(provider, p); - diags = diags.Where(diag => diagIds.Contains(diag.Id)); - return Task.FromResult(diags); - }; + Func, CancellationToken, Task>> getProjectDiagnosticsAsync = + (p, includeAllDocumentDiagnostics, diagIds, c) => + { + var diags = includeAllDocumentDiagnostics ? + testDriver.GetAllDiagnostics(provider, p) : + testDriver.GetProjectDiagnostics(provider, p); + diags = diags.Where(diag => diagIds.Contains(diag.Id)); + return Task.FromResult(diags); + }; - var diagnosticIds = ImmutableHashSet.Create(diagnostic.Id); - var fixAllDiagnosticProvider = new FixAllCodeActionContext.FixAllDiagnosticProvider(diagnosticIds, getDocumentDiagnosticsAsync, getProjectDiagnosticsAsync); - var fixAllContext = new FixAllContext(document, fixer, scope, fixAllActionId, diagnosticIds, fixAllDiagnosticProvider, CancellationToken.None); - var fixAllFix = fixAllProvider.GetFixAsync(fixAllContext).WaitAndGetResult(CancellationToken.None); - if (fixAllFix != null) - { - var codeFix = new CodeFixCollection(fixAllProvider, diagnostic.Location.SourceSpan, ImmutableArray.Create(new CodeFix(fixAllFix, diagnostic))); - yield return Tuple.Create(diagnostic, codeFix); + var diagnosticIds = ImmutableHashSet.Create(diagnostic.Id); + var fixAllDiagnosticProvider = new FixAllCodeActionContext.FixAllDiagnosticProvider(diagnosticIds, getDocumentDiagnosticsAsync, getProjectDiagnosticsAsync); + var fixAllContext = new FixAllContext(document, fixer, scope, fixAllActionId, diagnosticIds, fixAllDiagnosticProvider, CancellationToken.None); + var fixAllFix = fixAllProvider.GetFixAsync(fixAllContext).WaitAndGetResult(CancellationToken.None); + if (fixAllFix != null) + { + var codeFix = new CodeFixCollection(fixAllProvider, diagnostic.Location.SourceSpan, ImmutableArray.Create(new CodeFix(fixAllFix, diagnostic))); + yield return Tuple.Create(diagnostic, codeFix); + } } } } diff --git a/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionAllCodeTests.cs b/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionAllCodeTests.cs index a64da2e6932d5a4a0dff0bc652e20dc482ff5a25..0f52d56c80975638b68b575233f65aaabc162d90 100644 --- a/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionAllCodeTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/AbstractSuppressionAllCodeTests.cs @@ -65,51 +65,46 @@ protected void TestLocalSuppression(string code, ParseOptions options, Func f.Action), pragma); - if (fix == null) - { - continue; - } + var fix = GetFix(fixes.Select(f => f.Action), pragma); + if (fix == null) + { + continue; + } - // already same fix has been tested - if (fixChecker(fix)) - { - continue; - } + // already same fix has been tested + if (fixChecker(fix)) + { + continue; + } - var operations = fix.GetOperationsAsync(CancellationToken.None).GetAwaiter().GetResult(); + var operations = fix.GetOperationsAsync(CancellationToken.None).GetAwaiter().GetResult(); - var applyChangesOperation = operations.OfType().Single(); - var newDocument = applyChangesOperation.ChangedSolution.Projects.Single().Documents.Single(); - var newTree = newDocument.GetSyntaxTreeAsync().GetAwaiter().GetResult(); + var applyChangesOperation = operations.OfType().Single(); + var newDocument = applyChangesOperation.ChangedSolution.Projects.Single().Documents.Single(); + var newTree = newDocument.GetSyntaxTreeAsync().GetAwaiter().GetResult(); - var newText = newTree.GetText().ToString(); - Assert.True(verifier(newText)); + var newText = newTree.GetText().ToString(); + Assert.True(verifier(newText)); - var newDiagnostics = newTree.GetDiagnostics(); - Assert.Equal(0, existingDiagnostics.Except(newDiagnostics, this).Count()); - } + var newDiagnostics = newTree.GetDiagnostics(); + Assert.Equal(0, existingDiagnostics.Except(newDiagnostics, this).Count()); } } } @@ -134,13 +129,13 @@ public int GetHashCode(Diagnostic obj) return Hash.Combine(obj.Id, obj.Descriptor.Category.GetHashCode()); } - internal class Analyzer : DiagnosticAnalyzer + internal class Analyzer : DiagnosticAnalyzer, IBuiltInAnalyzer { private readonly DiagnosticDescriptor _descriptor = new DiagnosticDescriptor("TestId", "Test", "Test", "Test", DiagnosticSeverity.Warning, isEnabledByDefault: true); - public SyntaxNode Node { get; set; } - + public ImmutableArray AllNodes { get; set; } + public override ImmutableArray SupportedDiagnostics { get @@ -149,12 +144,20 @@ public override ImmutableArray SupportedDiagnostics } } + public DiagnosticAnalyzerCategory GetAnalyzerCategory() + { + return DiagnosticAnalyzerCategory.SyntaxAnalysis; + } + public override void Initialize(AnalysisContext analysisContext) { analysisContext.RegisterSyntaxTreeAction( (context) => { - context.ReportDiagnostic(Diagnostic.Create(_descriptor, Node.GetLocation())); + foreach (var node in AllNodes) + { + context.ReportDiagnostic(Diagnostic.Create(_descriptor, node.GetLocation())); + } }); } } diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticProviderTestUtilities.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticProviderTestUtilities.cs index 32bbb347003815696ee2f6303d2f587e73126643..df99262fa9ce0f20186162c58fd1460738b93445 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticProviderTestUtilities.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticProviderTestUtilities.cs @@ -2,72 +2,27 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.UnitTests.Diagnostics { public static class DiagnosticProviderTestUtilities { - private static IEnumerable GetDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Document document, TextSpan span, Project project, bool getDocumentDiagnostics, bool getProjectDiagnostics, Action onAnalyzerException, bool logAnalyzerExceptionAsDiagnostics) + public static IEnumerable GetAllDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Document document, TextSpan span, Action onAnalyzerException = null, bool logAnalyzerExceptionAsDiagnostics = false) { - var documentDiagnostics = SpecializedCollections.EmptyEnumerable(); - var projectDiagnostics = SpecializedCollections.EmptyEnumerable(); - - // If no user diagnostic analyzer, then test compiler diagnostics. - var workspaceAnalyzer = workspaceAnalyzerOpt ?? DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(project.Language); - - // If the test is not configured with a custom onAnalyzerException handler AND has not requested exceptions to be handled and logged as diagnostics, then FailFast on exceptions. - if (onAnalyzerException == null && !logAnalyzerExceptionAsDiagnostics) - { - onAnalyzerException = DiagnosticExtensions.FailFastOnAnalyzerException; - } - - var exceptionDiagnosticsSource = new TestHostDiagnosticUpdateSource(project.Solution.Workspace); - var analyzerService = new TestDiagnosticAnalyzerService(project.Language, workspaceAnalyzer, exceptionDiagnosticsSource, onAnalyzerException); - var incrementalAnalyzer = analyzerService.CreateIncrementalAnalyzer(project.Solution.Workspace); - - if (getDocumentDiagnostics) - { - var tree = document.GetSyntaxTreeAsync().Result; - var root = tree.GetRoot(); - var dxs = analyzerService.GetDiagnosticsAsync(project.Solution, project.Id, document.Id).WaitAndGetResult(CancellationToken.None); - documentDiagnostics = dxs.Where(d => d.HasTextSpan && d.TextSpan.IntersectsWith(span)).Select(d => d.ToDiagnostic(tree)); - } - - if (getProjectDiagnostics) + using (var testDriver = new TestDiagnosticAnalyzerDriver(document.Project, workspaceAnalyzerOpt, onAnalyzerException, logAnalyzerExceptionAsDiagnostics)) { - var dxs = analyzerService.GetDiagnosticsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None); - projectDiagnostics = dxs.Where(d => !d.HasTextSpan).Select(d => d.ToDiagnostic(tree: null)); + return testDriver.GetAllDiagnostics(workspaceAnalyzerOpt, document, span); } - - var exceptionDiagnostics = exceptionDiagnosticsSource.TestOnly_GetReportedDiagnostics(workspaceAnalyzer).Select(d => d.ToDiagnostic(tree: null)); - - return documentDiagnostics.Concat(projectDiagnostics).Concat(exceptionDiagnostics); - } - - public static IEnumerable GetAllDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Document document, TextSpan span, Action onAnalyzerException = null, bool logAnalyzerExceptionAsDiagnostics = false) - { - return GetDiagnostics(workspaceAnalyzerOpt, document, span, document.Project, getDocumentDiagnostics: true, getProjectDiagnostics: true, onAnalyzerException: onAnalyzerException, logAnalyzerExceptionAsDiagnostics: logAnalyzerExceptionAsDiagnostics); } public static IEnumerable GetAllDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Project project, Action onAnalyzerException = null, bool logAnalyzerExceptionAsDiagnostics = false) { - var diagnostics = new List(); - foreach (var document in project.Documents) + using (var testDriver = new TestDiagnosticAnalyzerDriver(project, workspaceAnalyzerOpt, onAnalyzerException, logAnalyzerExceptionAsDiagnostics)) { - var span = document.GetSyntaxRootAsync().Result.FullSpan; - var documentDiagnostics = GetDocumentDiagnostics(workspaceAnalyzerOpt, document, span, onAnalyzerException, logAnalyzerExceptionAsDiagnostics); - diagnostics.AddRange(documentDiagnostics); + return testDriver.GetAllDiagnostics(workspaceAnalyzerOpt, project); } - - var projectDiagnostics = GetProjectDiagnostics(workspaceAnalyzerOpt, project, onAnalyzerException, logAnalyzerExceptionAsDiagnostics); - diagnostics.AddRange(projectDiagnostics); - return diagnostics; } public static IEnumerable GetAllDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Solution solution, Action onAnalyzerException = null, bool logAnalyzerExceptionAsDiagnostics = false) @@ -84,12 +39,18 @@ public static IEnumerable GetAllDiagnostics(DiagnosticAnalyzer works public static IEnumerable GetDocumentDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Document document, TextSpan span, Action onAnalyzerException = null, bool logAnalyzerExceptionAsDiagnostics = false) { - return GetDiagnostics(workspaceAnalyzerOpt, document, span, document.Project, getDocumentDiagnostics: true, getProjectDiagnostics: false, onAnalyzerException: onAnalyzerException, logAnalyzerExceptionAsDiagnostics: logAnalyzerExceptionAsDiagnostics); + using (var testDriver = new TestDiagnosticAnalyzerDriver(document.Project, workspaceAnalyzerOpt, onAnalyzerException, logAnalyzerExceptionAsDiagnostics)) + { + return testDriver.GetDocumentDiagnostics(workspaceAnalyzerOpt, document, span); + } } public static IEnumerable GetProjectDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Project project, Action onAnalyzerException = null, bool logAnalyzerExceptionAsDiagnostics = false) { - return GetDiagnostics(workspaceAnalyzerOpt, null, default(TextSpan), project, getDocumentDiagnostics: false, getProjectDiagnostics: true, onAnalyzerException: onAnalyzerException, logAnalyzerExceptionAsDiagnostics: logAnalyzerExceptionAsDiagnostics); + using (var testDriver = new TestDiagnosticAnalyzerDriver(project, workspaceAnalyzerOpt, onAnalyzerException, logAnalyzerExceptionAsDiagnostics)) + { + return testDriver.GetProjectDiagnostics(workspaceAnalyzerOpt, project); + } } } } diff --git a/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerDriver.cs new file mode 100644 index 0000000000000000000000000000000000000000..54f6f1d595e15cf0f54cd3257d6f6d337003af05 --- /dev/null +++ b/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -0,0 +1,114 @@ +// 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.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.UnitTests.Diagnostics +{ + public class TestDiagnosticAnalyzerDriver : IDisposable + { + private readonly ImmutableArray _workspaceAnalyzers; + private readonly TestDiagnosticAnalyzerService _diagnosticAnalyzerService; + private readonly TestHostDiagnosticUpdateSource _exceptionDiagnosticsSource; + private readonly SolutionCrawler.IIncrementalAnalyzer _incrementalAnalyzer; + private readonly Action _onAnalyzerException; + + public TestDiagnosticAnalyzerDriver(Project project, DiagnosticAnalyzer workspaceAnalyzerOpt = null, Action onAnalyzerException = null, bool logAnalyzerExceptionAsDiagnostics = false) + : this (project, ImmutableArray.Create(workspaceAnalyzerOpt ?? DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(project.Language)), onAnalyzerException, logAnalyzerExceptionAsDiagnostics) + { } + + public TestDiagnosticAnalyzerDriver(Project project, ImmutableArray workspaceAnalyzers, Action onAnalyzerException = null, bool logAnalyzerExceptionAsDiagnostics = false) + { + _workspaceAnalyzers = workspaceAnalyzers; + _exceptionDiagnosticsSource = new TestHostDiagnosticUpdateSource(project.Solution.Workspace); + _diagnosticAnalyzerService = new TestDiagnosticAnalyzerService(project.Language, workspaceAnalyzers, _exceptionDiagnosticsSource, onAnalyzerException); + _incrementalAnalyzer = _diagnosticAnalyzerService.CreateIncrementalAnalyzer(project.Solution.Workspace); + + // If the test is not configured with a custom onAnalyzerException handler AND has not requested exceptions to be handled and logged as diagnostics, then FailFast on exceptions. + if (onAnalyzerException == null && !logAnalyzerExceptionAsDiagnostics) + { + onAnalyzerException = DiagnosticExtensions.FailFastOnAnalyzerException; + } + + _onAnalyzerException = onAnalyzerException; + } + + private IEnumerable GetDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Document document, TextSpan span, Project project, bool getDocumentDiagnostics, bool getProjectDiagnostics) + { + var documentDiagnostics = SpecializedCollections.EmptyEnumerable(); + var projectDiagnostics = SpecializedCollections.EmptyEnumerable(); + + if (getDocumentDiagnostics) + { + var tree = document.GetSyntaxTreeAsync().Result; + var root = tree.GetRoot(); + var dxs = _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id, document.Id).WaitAndGetResult(CancellationToken.None); + documentDiagnostics = dxs.Where(d => d.HasTextSpan && d.TextSpan.IntersectsWith(span)).Select(d => d.ToDiagnostic(tree)); + } + + if (getProjectDiagnostics) + { + var dxs = _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None); + projectDiagnostics = dxs.Where(d => !d.HasTextSpan).Select(d => d.ToDiagnostic(tree: null)); + } + + var exceptionDiagnostics = _exceptionDiagnosticsSource.TestOnly_GetReportedDiagnostics().Select(d => d.ToDiagnostic(tree: null)); + + return documentDiagnostics.Concat(projectDiagnostics).Concat(exceptionDiagnostics); + } + + public IEnumerable GetAllDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Document document, TextSpan span) + { + return GetDiagnostics(workspaceAnalyzerOpt, document, span, document.Project, getDocumentDiagnostics: true, getProjectDiagnostics: true); + } + + public IEnumerable GetAllDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Project project) + { + var diagnostics = new List(); + foreach (var document in project.Documents) + { + var span = document.GetSyntaxRootAsync().Result.FullSpan; + var documentDiagnostics = GetDocumentDiagnostics(workspaceAnalyzerOpt, document, span); + diagnostics.AddRange(documentDiagnostics); + } + + var projectDiagnostics = GetProjectDiagnostics(workspaceAnalyzerOpt, project); + diagnostics.AddRange(projectDiagnostics); + return diagnostics; + } + + public IEnumerable GetAllDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Solution solution) + { + var diagnostics = new List(); + foreach (var project in solution.Projects) + { + var projectDiagnostics = GetAllDiagnostics(workspaceAnalyzerOpt, project); + diagnostics.AddRange(projectDiagnostics); + } + + return diagnostics; + } + + public IEnumerable GetDocumentDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Document document, TextSpan span) + { + return GetDiagnostics(workspaceAnalyzerOpt, document, span, document.Project, getDocumentDiagnostics: true, getProjectDiagnostics: false); + } + + public IEnumerable GetProjectDiagnostics(DiagnosticAnalyzer workspaceAnalyzerOpt, Project project) + { + return GetDiagnostics(workspaceAnalyzerOpt, null, default(TextSpan), project, getDocumentDiagnostics: false, getProjectDiagnostics: true); + } + + public void Dispose() + { + CompilationWithAnalyzers.ClearAnalyzerState(_workspaceAnalyzers); + } + } +} diff --git a/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerService.cs b/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerService.cs index 1f82aea8f2396b0d73272e4c5cf55fd8ee7a7f23..d887ddaa00b3532b9767d8658fccc2dacea00b0f 100644 --- a/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/Test/Diagnostics/TestDiagnosticAnalyzerService.cs @@ -5,6 +5,8 @@ using System.Linq; using Microsoft.CodeAnalysis.Diagnostics.Log; using Roslyn.Utilities; +using System.Runtime.CompilerServices; +using System.Collections.Generic; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -13,18 +15,27 @@ internal sealed class TestDiagnosticAnalyzerService : DiagnosticAnalyzerService private readonly Action _onAnalyzerException; internal TestDiagnosticAnalyzerService(string language, DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null, Action onAnalyzerException = null) - : this(language, ImmutableArray.Create(analyzer), hostDiagnosticUpdateSource, onAnalyzerException) + : this(CreateHostAnalyzerManager(language, analyzer, hostDiagnosticUpdateSource), hostDiagnosticUpdateSource, onAnalyzerException) { } internal TestDiagnosticAnalyzerService(string language, ImmutableArray analyzers, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null, Action onAnalyzerException = null) - : this(ImmutableDictionary.CreateRange( - SpecializedCollections.SingletonEnumerable(KeyValuePair.Create(language, analyzers))), hostDiagnosticUpdateSource, onAnalyzerException) + : this(CreateHostAnalyzerManager(language, analyzers, hostDiagnosticUpdateSource), hostDiagnosticUpdateSource, onAnalyzerException) { } internal TestDiagnosticAnalyzerService(ImmutableDictionary> analyzersMap, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null, Action onAnalyzerException = null) - : base(ImmutableArray.Create(new TestAnalyzerReferenceByLanguage(analyzersMap)), hostDiagnosticUpdateSource) + : this(CreateHostAnalyzerManager(analyzersMap, hostDiagnosticUpdateSource), hostDiagnosticUpdateSource, onAnalyzerException) + { + } + + internal TestDiagnosticAnalyzerService(ImmutableArray hostAnalyzerReferences, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null, Action onAnalyzerException = null) + : this(CreateHostAnalyzerManager(hostAnalyzerReferences, hostDiagnosticUpdateSource), hostDiagnosticUpdateSource, onAnalyzerException) + { + } + + private TestDiagnosticAnalyzerService(HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Action onAnalyzerException) + : base(hostAnalyzerManager, hostDiagnosticUpdateSource) { _onAnalyzerException = onAnalyzerException; } @@ -35,20 +46,32 @@ internal TestDiagnosticAnalyzerService(AbstractHostDiagnosticUpdateSource hostDi _onAnalyzerException = onAnalyzerException; } - internal TestDiagnosticAnalyzerService(ImmutableArray workspaceAnalyzers, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null, Action onAnalyzerException = null) - : base(workspaceAnalyzers, hostDiagnosticUpdateSource) + private static HostAnalyzerManager CreateHostAnalyzerManager(string language, DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) { - _onAnalyzerException = onAnalyzerException; + return CreateHostAnalyzerManager(language, ImmutableArray.Create(analyzer), hostDiagnosticUpdateSource); } - internal override Action GetOnAnalyzerException(ProjectId projectId, DiagnosticLogAggregator diagnosticLogAggregator) + private static HostAnalyzerManager CreateHostAnalyzerManager(string language, ImmutableArray analyzers, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) { - return _onAnalyzerException ?? base.GetOnAnalyzerException(projectId, diagnosticLogAggregator); + var map = ImmutableDictionary.CreateRange( + SpecializedCollections.SingletonEnumerable(KeyValuePair.Create(language, analyzers))); + return CreateHostAnalyzerManager(map, hostDiagnosticUpdateSource); } - internal override Action GetOnAnalyzerException_NoTelemetryLogging(ProjectId projectId) + private static HostAnalyzerManager CreateHostAnalyzerManager(ImmutableDictionary> analyzersMap, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) { - return _onAnalyzerException ?? base.GetOnAnalyzerException_NoTelemetryLogging(projectId); + var analyzerReferences = ImmutableArray.Create(new TestAnalyzerReferenceByLanguage(analyzersMap)); + return CreateHostAnalyzerManager(analyzerReferences, hostDiagnosticUpdateSource); + } + + private static HostAnalyzerManager CreateHostAnalyzerManager(ImmutableArray hostAnalyzerReferences, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) + { + return new HostAnalyzerManager(hostAnalyzerReferences, hostDiagnosticUpdateSource); + } + + internal override Action GetOnAnalyzerException(ProjectId projectId, DiagnosticLogAggregator diagnosticLogAggregator) + { + return _onAnalyzerException ?? base.GetOnAnalyzerException(projectId, diagnosticLogAggregator); } private class TestAnalyzerReferenceByLanguage : AnalyzerReference diff --git a/src/EditorFeatures/Test/Diagnostics/TestHostDiagnosticUpdateSource.cs b/src/EditorFeatures/Test/Diagnostics/TestHostDiagnosticUpdateSource.cs index 9cb9e7fec0467a86235cc3ccf061db7bc1c69909..2f4cbd82ee2a9af2072b7b13a3050399200ed90e 100644 --- a/src/EditorFeatures/Test/Diagnostics/TestHostDiagnosticUpdateSource.cs +++ b/src/EditorFeatures/Test/Diagnostics/TestHostDiagnosticUpdateSource.cs @@ -20,5 +20,10 @@ internal override Workspace Workspace return _workspace; } } + + public override int GetHashCode() + { + return _workspace.GetHashCode(); + } } } diff --git a/src/EditorFeatures/Test/EditorServicesTest.csproj b/src/EditorFeatures/Test/EditorServicesTest.csproj index ba3ae22ec6ec4f13d750042cbd7a3cd09449da6c..99905afdb570b0fd89ebfaf3f3eb3c01a20117b6 100644 --- a/src/EditorFeatures/Test/EditorServicesTest.csproj +++ b/src/EditorFeatures/Test/EditorServicesTest.csproj @@ -247,6 +247,7 @@ + diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 0ef2112e1a9277971815a4a0eed4b0658cc4858d..2aafbb6a0b2cb7767e74b5157c1e903954924d14 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -482,7 +482,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests diagnostics = exceptionDiagnosticsSource.TestOnly_GetReportedDiagnostics(analyzer) Assert.Equal(1, diagnostics.Count()) Dim diagnostic = diagnostics.First() - Assert.True(AnalyzerExecutor.IsAnalyzerExceptionDiagnostic(diagnostic.ToDiagnostic(document.GetSyntaxTreeAsync().Result))) + Assert.True(diagnostic.Id = "AD0001") Assert.Contains("CodeBlockStartedAnalyzer", diagnostic.Message, StringComparison.Ordinal) End Using End Sub diff --git a/src/Features/CSharp/CSharpFeatures.csproj b/src/Features/CSharp/CSharpFeatures.csproj index 787a6f3414259a5b9d5ebb23ae94147eab23a128..db83fdf1fedd91d1f302b949da0e811abab1aa0f 100644 --- a/src/Features/CSharp/CSharpFeatures.csproj +++ b/src/Features/CSharp/CSharpFeatures.csproj @@ -297,7 +297,6 @@ - diff --git a/src/Features/CSharp/Diagnostics/CSharpSyntaxNodeAnalyzerService.cs b/src/Features/CSharp/Diagnostics/CSharpSyntaxNodeAnalyzerService.cs deleted file mode 100644 index a1c15f0d686adb466326222012bc742e1c517986..0000000000000000000000000000000000000000 --- a/src/Features/CSharp/Diagnostics/CSharpSyntaxNodeAnalyzerService.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Collections.Generic; -using System.Composition; -using Microsoft.CodeAnalysis.Diagnostics.EngineV1; -using Microsoft.CodeAnalysis.Host.Mef; - -namespace Microsoft.CodeAnalysis.CSharp.Diagnostics -{ - [ExportLanguageService(typeof(ISyntaxNodeAnalyzerService), LanguageNames.CSharp), Shared] - internal sealed class CSharpSyntaxNodeAnalyzerService : AbstractSyntaxNodeAnalyzerService - { - public CSharpSyntaxNodeAnalyzerService() { } - - protected override IEqualityComparer GetSyntaxKindEqualityComparer() - { - return SyntaxFacts.EqualityComparer; - } - - protected override SyntaxKind GetKind(SyntaxNode node) - { - return node.Kind(); - } - } -} diff --git a/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs b/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs index 02e45107862b769a7a52093dc0381655826e81e7..7e0778630ea41bdaae3e6d4fe29d06b9b47b96b7 100644 --- a/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs +++ b/src/Features/Core/Diagnostics/AbstractHostDiagnosticUpdateSource.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal abstract class AbstractHostDiagnosticUpdateSource : IDiagnosticUpdateSource { - private static ImmutableDictionary> s_analyzerHostDiagnosticsMap = + private ImmutableDictionary> _analyzerHostDiagnosticsMap = ImmutableDictionary>.Empty; internal abstract Workspace Workspace { get; } @@ -67,7 +67,7 @@ internal void ReportAnalyzerDiagnostic(DiagnosticAnalyzer analyzer, Diagnostic d DiagnosticData.Create(project, diagnostic) : DiagnosticData.Create(this.Workspace, diagnostic); - var dxs = ImmutableInterlocked.AddOrUpdate(ref s_analyzerHostDiagnosticsMap, + var dxs = ImmutableInterlocked.AddOrUpdate(ref _analyzerHostDiagnosticsMap, analyzer, ImmutableHashSet.Create(diagnosticData), (a, existing) => @@ -87,10 +87,10 @@ public void ClearAnalyzerReferenceDiagnostics(AnalyzerFileReference analyzerRefe { var analyzers = analyzerReference.GetAnalyzers(language); ClearAnalyzerDiagnostics(analyzers, projectId); - AnalyzerManager.Instance.ClearAnalyzerState(analyzers); + CompilationWithAnalyzers.ClearAnalyzerState(analyzers); } - private void ClearAnalyzerDiagnostics(ImmutableArray analyzers, ProjectId projectId) + internal void ClearAnalyzerDiagnostics(ImmutableArray analyzers, ProjectId projectId) { foreach (var analyzer in analyzers) { @@ -101,7 +101,7 @@ private void ClearAnalyzerDiagnostics(ImmutableArray analyze private void ClearAnalyzerDiagnostics(DiagnosticAnalyzer analyzer, ProjectId projectId) { ImmutableHashSet existing; - if (!s_analyzerHostDiagnosticsMap.TryGetValue(analyzer, out existing)) + if (!_analyzerHostDiagnosticsMap.TryGetValue(analyzer, out existing)) { return; } @@ -112,13 +112,13 @@ private void ClearAnalyzerDiagnostics(DiagnosticAnalyzer analyzer, ProjectId pro { var newDiags = existing.Where(d => d.ProjectId != projectId).ToImmutableHashSet(); if (newDiags.Count < existing.Count && - ImmutableInterlocked.TryUpdate(ref s_analyzerHostDiagnosticsMap, analyzer, newDiags, existing)) + ImmutableInterlocked.TryUpdate(ref _analyzerHostDiagnosticsMap, analyzer, newDiags, existing)) { var project = this.Workspace.CurrentSolution.GetProject(projectId); RaiseDiagnosticsUpdated(MakeArgs(analyzer, ImmutableHashSet.Empty, project)); } } - else if (ImmutableInterlocked.TryRemove(ref s_analyzerHostDiagnosticsMap, analyzer, out existing)) + else if (ImmutableInterlocked.TryRemove(ref _analyzerHostDiagnosticsMap, analyzer, out existing)) { var project = this.Workspace.CurrentSolution.GetProject(projectId); RaiseDiagnosticsUpdated(MakeArgs(analyzer, ImmutableHashSet.Empty, project)); @@ -141,10 +141,15 @@ private DiagnosticsUpdatedArgs MakeArgs(DiagnosticAnalyzer analyzer, ImmutableHa diagnostics: items.ToImmutableArray()); } + internal ImmutableArray TestOnly_GetReportedDiagnostics() + { + return _analyzerHostDiagnosticsMap.Values.Flatten().ToImmutableArray(); + } + internal ImmutableHashSet TestOnly_GetReportedDiagnostics(DiagnosticAnalyzer analyzer) { ImmutableHashSet diagnostics; - if (!s_analyzerHostDiagnosticsMap.TryGetValue(analyzer, out diagnostics)) + if (!_analyzerHostDiagnosticsMap.TryGetValue(analyzer, out diagnostics)) { diagnostics = ImmutableHashSet.Empty; } diff --git a/src/Features/Core/Diagnostics/AnalyzerHelper.cs b/src/Features/Core/Diagnostics/AnalyzerHelper.cs index f280215883f85934a138f18cd12343e06080ff9f..77fd48d3f1cd729e3904538d2f9415c5c9205957 100644 --- a/src/Features/Core/Diagnostics/AnalyzerHelper.cs +++ b/src/Features/Core/Diagnostics/AnalyzerHelper.cs @@ -14,6 +14,10 @@ internal static class AnalyzerHelper private const string CSharpCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.CSharp.CSharpCompilerDiagnosticAnalyzer"; private const string VisualBasicCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.VisualBasic.VisualBasicCompilerDiagnosticAnalyzer"; + private const string AnalyzerExceptionDiagnosticId = "AD0001"; + private const string AnalyzerExceptionDiagnosticCategory = "Intellisense"; + + public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer) { return analyzer is IBuiltInAnalyzer || analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer || analyzer.IsCompilerAnalyzer(); @@ -57,26 +61,12 @@ private static string GetAssemblyQualifiedName(Type type) return type.AssemblyQualifiedName; } - internal static AnalyzerExecutor GetAnalyzerExecutorForSupportedDiagnostics( - DiagnosticAnalyzer analyzer, - AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, - Action onAnalyzerException = null, - CancellationToken cancellationToken = default(CancellationToken)) - { - // Skip telemetry logging if the exception is thrown as we are computing supported diagnostics and - // we can't determine if any descriptors support getting telemetry without having the descriptors. - Action defaultOnAnalyzerException = (ex, a, diagnostic) => - OnAnalyzerException_NoTelemetryLogging(ex, a, diagnostic, hostDiagnosticUpdateSource); - - return AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException ?? defaultOnAnalyzerException, AnalyzerManager.Instance, cancellationToken: cancellationToken); - } - internal static void OnAnalyzerException_NoTelemetryLogging( - Exception e, + Exception ex, DiagnosticAnalyzer analyzer, Diagnostic diagnostic, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, - ProjectId projectIdOpt = null) + ProjectId projectIdOpt) { if (diagnostic != null) { @@ -85,8 +75,44 @@ private static string GetAssemblyQualifiedName(Type type) if (IsBuiltInAnalyzer(analyzer)) { - FatalError.ReportWithoutCrashUnlessCanceled(e); + FatalError.ReportWithoutCrashUnlessCanceled(ex); + } + } + + internal static void OnAnalyzerExceptionForSupportedDiagnostics(DiagnosticAnalyzer analyzer, Exception exception, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) + { + if (exception is OperationCanceledException) + { + return; } + + var diagnostic = CreateAnalyzerExceptionDiagnostic(analyzer, exception); + OnAnalyzerException_NoTelemetryLogging(exception, analyzer, diagnostic, hostDiagnosticUpdateSource, projectIdOpt: null); + } + + /// + /// Create a diagnostic for exception thrown by the given analyzer. + /// + /// + /// Keep this method in sync with "AnalyzerExecutor.CreateAnalyzerExceptionDiagnostic". + /// + internal static Diagnostic CreateAnalyzerExceptionDiagnostic(DiagnosticAnalyzer analyzer, Exception e) + { + var analyzerName = analyzer.ToString(); + + // TODO: It is not ideal to create a new descriptor per analyzer exception diagnostic instance. + // However, until we add a LongMessage field to the Diagnostic, we are forced to park the instance specific description onto the Descriptor's Description field. + // This requires us to create a new DiagnosticDescriptor instance per diagnostic instance. + var descriptor = new DiagnosticDescriptor(AnalyzerExceptionDiagnosticId, + title: AnalyzerDriverResources.AnalyzerFailure, + messageFormat: AnalyzerDriverResources.AnalyzerThrows, + description: string.Format(AnalyzerDriverResources.AnalyzerThrowsDescription, analyzerName, e.ToString()), + category: AnalyzerExceptionDiagnosticCategory, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + customTags: WellKnownDiagnosticTags.AnalyzerException); + + return Diagnostic.Create(descriptor, Location.None, analyzerName, e.GetType(), e.Message); } private static VersionStamp GetAnalyzerVersion(string path) diff --git a/src/Features/Core/Diagnostics/Analyzers/RemoveUnnecessaryCastDiagnosticAnalyzerBase.cs b/src/Features/Core/Diagnostics/Analyzers/RemoveUnnecessaryCastDiagnosticAnalyzerBase.cs index 15576613e188b20e3c6b6721ee3f94090cd9a9c3..672fc982a56c193508e968f015494d28b5a562d2 100644 --- a/src/Features/Core/Diagnostics/Analyzers/RemoveUnnecessaryCastDiagnosticAnalyzerBase.cs +++ b/src/Features/Core/Diagnostics/Analyzers/RemoveUnnecessaryCastDiagnosticAnalyzerBase.cs @@ -76,5 +76,10 @@ public override void Initialize(AnalysisContext context) diagnostic = Diagnostic.Create(s_descriptor, tree.GetLocation(span)); return true; } + + public DiagnosticAnalyzerCategory GetAnalyzerCategory() + { + return DiagnosticAnalyzerCategory.SemanticSpanAnalysis; + } } } diff --git a/src/Features/Core/Diagnostics/Analyzers/RemoveUnnecessaryImportsDiagnosticAnalyzerBase.cs b/src/Features/Core/Diagnostics/Analyzers/RemoveUnnecessaryImportsDiagnosticAnalyzerBase.cs index 8839d5031416390fb0f749bbf2f63d729decf9bd..43d5c2f8cca4299d36f0b88927dc564ddcfa44fd 100644 --- a/src/Features/Core/Diagnostics/Analyzers/RemoveUnnecessaryImportsDiagnosticAnalyzerBase.cs +++ b/src/Features/Core/Diagnostics/Analyzers/RemoveUnnecessaryImportsDiagnosticAnalyzerBase.cs @@ -116,5 +116,10 @@ private IEnumerable CreateFixableDiagnostics(IEnumerable yield return Diagnostic.Create(s_fixableIdDescriptor, tree.GetLocation(span)); } } + + public DiagnosticAnalyzerCategory GetAnalyzerCategory() + { + return DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; + } } } diff --git a/src/Features/Core/Diagnostics/Analyzers/RudeEditUserDiagnosticAnalyzer.cs b/src/Features/Core/Diagnostics/Analyzers/RudeEditUserDiagnosticAnalyzer.cs index ff32c2724afd6138f51b0636ce005612a83e4e1c..19e975c1d3bc4d7610f6de93c810742cdd1dc406 100644 --- a/src/Features/Core/Diagnostics/Analyzers/RudeEditUserDiagnosticAnalyzer.cs +++ b/src/Features/Core/Diagnostics/Analyzers/RudeEditUserDiagnosticAnalyzer.cs @@ -62,5 +62,9 @@ public override async Task AnalyzeSemanticsAsync(Document document, Action GetOnAnalyzerException_NoTelemetryLogging(ProjectId projectId) - { - return Owner?.GetOnAnalyzerException_NoTelemetryLogging(projectId); - } } } diff --git a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs index eeb1311fb1546230fab69421cfd785b0bcede5c9..a9459a6148322eea16570a61a59f9fd0ad52bae3 100644 --- a/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs @@ -28,25 +28,25 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService [Import(AllowDefault = true)]IWorkspaceDiagnosticAnalyzerProviderService diagnosticAnalyzerProviderService = null, [Import(AllowDefault = true)]AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null) : this(diagnosticAnalyzerProviderService != null ? diagnosticAnalyzerProviderService.GetHostDiagnosticAnalyzerPackages() : SpecializedCollections.EmptyEnumerable(), - hostDiagnosticUpdateSource) + hostDiagnosticUpdateSource, + new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.DiagnosticService)) { - _listener = new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.DiagnosticService); } public IAsynchronousOperationListener Listener => _listener; // protected for testing purposes. - protected DiagnosticAnalyzerService(IEnumerable workspaceAnalyzerPackages, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : this() + protected DiagnosticAnalyzerService(IEnumerable workspaceAnalyzerPackages, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, IAsynchronousOperationListener listener = null) + : this (new HostAnalyzerManager(workspaceAnalyzerPackages, hostDiagnosticUpdateSource), hostDiagnosticUpdateSource, listener) { - _hostAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzerPackages, hostDiagnosticUpdateSource); - _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; } // protected for testing purposes. - protected DiagnosticAnalyzerService(ImmutableArray workspaceAnalyzers, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null) : this() + protected DiagnosticAnalyzerService(HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null, IAsynchronousOperationListener listener = null) : this() { - _hostAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzers, hostDiagnosticUpdateSource); + _hostAnalyzerManager = hostAnalyzerManager; _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; + _listener = listener ?? AggregateAsynchronousOperationListener.CreateEmptyListener(); } public ImmutableDictionary> GetDiagnosticDescriptors(Project projectOpt) @@ -103,6 +103,11 @@ public ImmutableArray GetDiagnosticDescriptors(DiagnosticA return _hostAnalyzerManager.GetDiagnosticDescriptors(analyzer); } + public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project) + { + return _hostAnalyzerManager.IsAnalyzerSuppressed(analyzer, project); + } + public void Reanalyze(Workspace workspace, IEnumerable projectIds = null, IEnumerable documentIds = null) { BaseDiagnosticIncrementalAnalyzer analyzer; @@ -227,17 +232,10 @@ public bool IsCompilerDiagnosticAnalyzer(string language, DiagnosticAnalyzer ana return (ex, analyzer, diagnostic) => { // Log telemetry, if analyzer supports telemetry. - DiagnosticAnalyzerLogger.LogAnalyzerCrashCount(analyzer, ex, diagnosticLogAggregator); + DiagnosticAnalyzerLogger.LogAnalyzerCrashCount(analyzer, ex, diagnosticLogAggregator, projectId); AnalyzerHelper.OnAnalyzerException_NoTelemetryLogging(ex, analyzer, diagnostic, _hostDiagnosticUpdateSource, projectId); }; } - - // virtual for testing purposes. - internal virtual Action GetOnAnalyzerException_NoTelemetryLogging(ProjectId projectId) - { - return (ex, analyzer, diagnostic) => - AnalyzerHelper.OnAnalyzerException_NoTelemetryLogging(ex, analyzer, diagnostic, _hostDiagnosticUpdateSource, projectId); - } } } diff --git a/src/Features/Core/Diagnostics/EngineV1/AbstractSyntaxNodeAnalyzerService.cs b/src/Features/Core/Diagnostics/EngineV1/AbstractSyntaxNodeAnalyzerService.cs deleted file mode 100644 index d1b318c25ffad68bf118163aad67f186aebbda7d..0000000000000000000000000000000000000000 --- a/src/Features/Core/Diagnostics/EngineV1/AbstractSyntaxNodeAnalyzerService.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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.Collections.Generic; -using System.Collections.Immutable; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 -{ - internal abstract class AbstractSyntaxNodeAnalyzerService : ISyntaxNodeAnalyzerService where TLanguageKindEnum : struct - { - protected abstract IEqualityComparer GetSyntaxKindEqualityComparer(); - protected abstract TLanguageKindEnum GetKind(SyntaxNode node); - - private bool ShouldAnalyze(SyntaxNode node, ImmutableArray kindsToAnalyze) - { - var kind = GetKind(node); - return kindsToAnalyze.Contains(kind); - } - - public void ExecuteSyntaxNodeActions( - AnalyzerActions actions, - IEnumerable descendantNodes, - SemanticModel semanticModel, - AnalyzerExecutor analyzerExecutor) - { - analyzerExecutor.ExecuteSyntaxNodeActions(actions, descendantNodes, semanticModel, this.GetKind); - } - - public void ExecuteCodeBlockActions( - AnalyzerActions actions, - IEnumerable declarationsInNode, - SemanticModel semanticModel, - AnalyzerExecutor analyzerExecutor) - { - analyzerExecutor.ExecuteCodeBlockActions(actions, declarationsInNode, semanticModel, this.GetKind); - } - } -} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerCategory.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerCategory.cs index 7d67228eefb6cfdbf2e2dd91625b37e3926ee156..3914c96b3b44b6a71a174fff911395df69ddb6d8 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerCategory.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerCategory.cs @@ -2,15 +2,36 @@ using System; -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 +namespace Microsoft.CodeAnalysis.Diagnostics { [Flags] internal enum DiagnosticAnalyzerCategory { + /// + /// Invalid value, analyzer must support at least one or more of the subsequent analysis categories. + /// None = 0, + + /// + /// Analyzer reports syntax diagnostics (i.e. registers a SyntaxTree action). + /// SyntaxAnalysis = 0x0001, + + /// + /// Analyzer reports semantic diagnostics and also supports incremental span based method body analysis. + /// An analyzer can support incremental method body analysis if edits within a method body only affect the diagnostics reported by the analyzer on the edited method body. + /// SemanticSpanAnalysis = 0x0010, + + /// + /// Analyzer reports semantic diagnostics but doesn't support incremental span based method body analysis. + /// It needs to re-analyze the whole document for reporting semantic diagnostics even for method body editing scenarios. + /// SemanticDocumentAnalysis = 0x0100, + + /// + /// Analyzer reports project diagnostics (i.e. registers a Compilation action and/or Compilation end action diagnostics). + /// ProjectAnalysis = 0x1000 } } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs index 7d7e10453c71db7e4759dad7d9d07f2682c44b81..0949ec94e3703ac508f8aac1191e8cb59c8b6e9b 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticAnalyzerDriver.cs @@ -4,26 +4,17 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics.Log; -using Microsoft.CodeAnalysis.GeneratedCodeRecognition; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry; namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 { internal class DiagnosticAnalyzerDriver { - /// - /// Map containing unique gate objects per-analyzer instance. - /// We need to guard against concurrent callbacks into analyzers from different diagnostic clients. - /// Additionally, certain diagnostic clients, such as FixAll occurrences, may request diagnostics for multiple documents in parallel. - /// - private static readonly ConditionalWeakTable s_analyzerGateMap = - new ConditionalWeakTable(); - private readonly Document _document; // The root of the document. May be null for documents without a root. @@ -33,26 +24,15 @@ internal class DiagnosticAnalyzerDriver // for the entire file. private readonly TextSpan? _span; private readonly Project _project; - private readonly BaseDiagnosticIncrementalAnalyzer _owner; + private readonly DiagnosticIncrementalAnalyzer _owner; private readonly CancellationToken _cancellationToken; - private readonly ISyntaxNodeAnalyzerService _syntaxNodeAnalyzerService; - private readonly IGeneratedCodeRecognitionService _generatedCodeService; - private readonly IAnalyzerDriverService _analyzerDriverService; - - private readonly Action _onAnalyzerException; - private readonly Action _onAnalyzerException_NoTelemetryLogging; - - private ImmutableArray _lazyDeclarationInfos; - private ImmutableArray _lazySymbols; - private ImmutableArray _lazyAllSyntaxNodesToAnalyze; - - private readonly AnalyzerOptions _analyzerOptions; + private readonly CompilationWithAnalyzersOptions _analysisOptions; public DiagnosticAnalyzerDriver( Document document, TextSpan? span, SyntaxNode root, - BaseDiagnosticIncrementalAnalyzer owner, + DiagnosticIncrementalAnalyzer owner, CancellationToken cancellationToken) : this(document.Project, owner, cancellationToken) { @@ -63,18 +43,17 @@ internal class DiagnosticAnalyzerDriver public DiagnosticAnalyzerDriver( Project project, - BaseDiagnosticIncrementalAnalyzer owner, + DiagnosticIncrementalAnalyzer owner, CancellationToken cancellationToken) { _project = project; _owner = owner; - _syntaxNodeAnalyzerService = project.LanguageServices.GetService(); _cancellationToken = cancellationToken; - _generatedCodeService = project.Solution.Workspace.Services.GetService(); - _analyzerDriverService = project.LanguageServices.GetService(); - _analyzerOptions = new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution.Workspace); - _onAnalyzerException = owner.GetOnAnalyzerException(project.Id); - _onAnalyzerException_NoTelemetryLogging = owner.GetOnAnalyzerException_NoTelemetryLogging(project.Id); + _analysisOptions = new CompilationWithAnalyzersOptions( + new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution.Workspace), + owner.GetOnAnalyzerException(project.Id), + concurrentAnalysis: false, + logAnalyzerExecutionTime: true); } public Document Document @@ -112,112 +91,25 @@ public CancellationToken CancellationToken } } - public ISyntaxNodeAnalyzerService SyntaxNodeAnalyzerService - { - get - { - return _syntaxNodeAnalyzerService; - } - } - - private ImmutableArray GetDeclarationInfos(SemanticModel model) - { - if (_lazyDeclarationInfos == null) - { - ImmutableArray declarations; - if (_span == null) - { - declarations = ImmutableArray.Empty; - } - else - { - declarations = _analyzerDriverService.GetDeclarationsInSpan(model, _span.Value, getSymbol: true, cancellationToken: _cancellationToken); - } - - _lazyDeclarationInfos = declarations; - } - - return _lazyDeclarationInfos; - } - - private ImmutableArray GetSymbolsToAnalyze(SemanticModel model) + private CompilationWithAnalyzers GetCompilationWithAnalyzers(Compilation compilation) { - if (_lazySymbols == null) - { - var declarationInfos = GetDeclarationInfos(model); - - // Avoid duplicate callbacks for partial symbols by analyzing symbol only for the document with first declaring syntaxref. - var builder = ImmutableHashSet.CreateBuilder(); - - for (var i = 0; i < declarationInfos.Length; i++) - { - var symbol = declarationInfos[i].DeclaredSymbol; - if (symbol == null || symbol.IsImplicitlyDeclared) - { - continue; - } - - if (symbol.DeclaringSyntaxReferences.Length > 1 && - ShouldExcludePartialSymbol(symbol)) - { - continue; - } - - builder.Add(symbol); - } - - ImmutableInterlocked.InterlockedInitialize(ref _lazySymbols, builder.ToImmutableArrayOrEmpty()); - } - - return _lazySymbols; - } - - private bool ShouldExcludePartialSymbol(ISymbol symbol) - { - // Get the first partial definition in a non-generated file. - var reference = symbol.DeclaringSyntaxReferences.FirstOrDefault(r => !IsInGeneratedCode(r)); - if (reference == null) - { - // All definitions in generated code, so just pick the first one. - reference = symbol.DeclaringSyntaxReferences[0]; - } - - return reference.SyntaxTree != _root.SyntaxTree; - } - - private bool IsInGeneratedCode(SyntaxReference reference) - { - if (_generatedCodeService == null) - { - // Test code might take this path. - return false; - } - - var tree = reference.SyntaxTree; - var document = _project.GetDocument(reference.SyntaxTree); - if (document == null) + Contract.ThrowIfFalse(_project.SupportsCompilation); + return _owner.HostAnalyzerManager.GetOrCreateCompilationWithAnalyzers(_project, p => { - return false; - } - - return _generatedCodeService.IsGeneratedCode(document); + var analyzers = _owner + .GetAnalyzers(p) + .Where(a => !CompilationWithAnalyzers.IsDiagnosticAnalyzerSuppressed(a, compilation.Options, _analysisOptions.OnAnalyzerException)) + .ToImmutableArray() + .Distinct(); + return new CompilationWithAnalyzers(compilation, analyzers, _analysisOptions); + }); } - private ImmutableArray GetSyntaxNodesToAnalyze() + public async Task GetAnalyzerActionsAsync(DiagnosticAnalyzer analyzer) { - if (_lazyAllSyntaxNodesToAnalyze == null) - { - if (_root == null || _span == null) - { - return ImmutableArray.Empty; - } - - var node = _root.FindNode(_span.Value, findInsideTrivia: true); - var descendantNodes = ImmutableArray.CreateRange(node.DescendantNodesAndSelf(descendIntoTrivia: true)); - ImmutableInterlocked.InterlockedInitialize(ref _lazyAllSyntaxNodesToAnalyze, descendantNodes); - } - - return _lazyAllSyntaxNodesToAnalyze; + var compilation = await _project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); + var compWithAnalyzers = this.GetCompilationWithAnalyzers(compilation); + return await compWithAnalyzers.GetAnalyzerActionCountsAsync(analyzer, _cancellationToken).ConfigureAwait(false); } public async Task> GetSyntaxDiagnosticsAsync(DiagnosticAnalyzer analyzer) @@ -226,116 +118,69 @@ public async Task> GetSyntaxDiagnosticsAsync(Diagnost Contract.ThrowIfNull(_document); - using (var pooledObject = SharedPools.Default>().GetPooledObject()) + var documentAnalyzer = analyzer as DocumentDiagnosticAnalyzer; + if (documentAnalyzer != null) { - var diagnostics = pooledObject.Object; - - _cancellationToken.ThrowIfCancellationRequested(); - var documentAnalyzer = analyzer as DocumentDiagnosticAnalyzer; - if (documentAnalyzer != null) + using (var pooledObject = SharedPools.Default>().GetPooledObject()) { + var diagnostics = pooledObject.Object; + _cancellationToken.ThrowIfCancellationRequested(); + try { await documentAnalyzer.AnalyzeSyntaxAsync(_document, diagnostics.Add, _cancellationToken).ConfigureAwait(false); - return diagnostics.ToImmutableArrayOrEmpty(); + return GetFilteredDocumentDiagnostics(diagnostics, compilation).ToImmutableArray(); } - catch (Exception e) when (!AnalyzerExecutor.IsCanceled(e, _cancellationToken)) + catch (Exception e) when (!IsCanceled(e, _cancellationToken)) { OnAnalyzerException(e, analyzer, compilation); return ImmutableArray.Empty; } } + } - var analyzerExecutor = GetAnalyzerExecutor(compilation, diagnostics.Add); - var analyzerActions = await this.GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false); - if (analyzerActions != null) - { - if (_document.SupportsSyntaxTree) - { - analyzerExecutor.ExecuteSyntaxTreeActions(analyzerActions, _root.SyntaxTree); - } - } - - if (diagnostics.Count == 0) - { - return ImmutableArray.Empty; - } - - return GetFilteredDocumentDiagnostics(diagnostics, compilation).ToImmutableArray(); + if (!_document.SupportsSyntaxTree) + { + return ImmutableArray.Empty; } + + var compilationWithAnalyzers = GetCompilationWithAnalyzers(compilation); + var syntaxDiagnostics = await compilationWithAnalyzers.GetAnalyzerSyntaxDiagnosticsAsync(_root.SyntaxTree, ImmutableArray.Create(analyzer), _cancellationToken).ConfigureAwait(false); + await UpdateAnalyzerTelemetryDataAsync(analyzer, compilationWithAnalyzers).ConfigureAwait(false); + return GetFilteredDocumentDiagnostics(syntaxDiagnostics, compilation, onlyLocationFiltering: true).ToImmutableArray(); } - private IEnumerable GetFilteredDocumentDiagnostics(IEnumerable diagnostics, Compilation compilation) + private IEnumerable GetFilteredDocumentDiagnostics(IEnumerable diagnostics, Compilation compilation, bool onlyLocationFiltering = false) { if (_root == null) { return diagnostics; } - return GetFilteredDocumentDiagnosticsCore(diagnostics, compilation); + return GetFilteredDocumentDiagnosticsCore(diagnostics, compilation, onlyLocationFiltering); } - private IEnumerable GetFilteredDocumentDiagnosticsCore(IEnumerable diagnostics, Compilation compilation) + private IEnumerable GetFilteredDocumentDiagnosticsCore(IEnumerable diagnostics, Compilation compilation, bool onlyLocationFiltering) { var diagsFilteredByLocation = diagnostics.Where(diagnostic => (diagnostic.Location == Location.None) || (diagnostic.Location.SourceTree == _root.SyntaxTree && (_span == null || diagnostic.Location.SourceSpan.IntersectsWith(_span.Value)))); - return compilation == null - ? diagnostics + return compilation == null || onlyLocationFiltering + ? diagsFilteredByLocation : CompilationWithAnalyzers.GetEffectiveDiagnostics(diagsFilteredByLocation, compilation); } internal void OnAnalyzerException(Exception ex, DiagnosticAnalyzer analyzer, Compilation compilation) { - var exceptionDiagnostic = AnalyzerExecutor.GetAnalyzerExceptionDiagnostic(analyzer, ex); + var exceptionDiagnostic = AnalyzerHelper.CreateAnalyzerExceptionDiagnostic(analyzer, ex); if (compilation != null) { exceptionDiagnostic = CompilationWithAnalyzers.GetEffectiveDiagnostics(ImmutableArray.Create(exceptionDiagnostic), compilation).SingleOrDefault(); } - _onAnalyzerException(ex, analyzer, exceptionDiagnostic); - } - - private AnalyzerExecutor GetAnalyzerExecutor(Compilation compilation, Action addDiagnostic) - { - return AnalyzerExecutor.Create(compilation, _analyzerOptions, addDiagnostic, _onAnalyzerException, AnalyzerHelper.IsCompilerAnalyzer, AnalyzerManager.Instance, s_analyzerGateMap.GetOrCreateValue, cancellationToken: _cancellationToken); - } - - private AnalyzerExecutor GetAnalyzerExecutorForClone(Compilation compilation, Action addDiagnostic, DiagnosticAnalyzer analyzerForExceptionDiagnostics) - { - // Report analyzer exception diagnostics on original analyzer, not the temporary cloned one. - Action onAnalyzerException = (ex, a, diag) => - _onAnalyzerException(ex, analyzerForExceptionDiagnostics, diag); - - return AnalyzerExecutor.Create(compilation, _analyzerOptions, addDiagnostic, onAnalyzerException, AnalyzerHelper.IsCompilerAnalyzer, AnalyzerManager.Instance, s_analyzerGateMap.GetOrCreateValue, cancellationToken: _cancellationToken); - } - - public async Task GetAnalyzerActionsAsync(DiagnosticAnalyzer analyzer) - { - var compilation = _project.SupportsCompilation ? await _project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false) : null; - var analyzerExecutor = GetAnalyzerExecutor(compilation, addDiagnostic: null); - return await GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false); - } - - public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer) - { - var options = this.Project.CompilationOptions; - if (options == null) - { - return false; - } - - var analyzerExecutor = AnalyzerHelper.GetAnalyzerExecutorForSupportedDiagnostics(analyzer, _owner.HostDiagnosticUpdateSource, _onAnalyzerException_NoTelemetryLogging, _cancellationToken); - return AnalyzerManager.Instance.IsDiagnosticAnalyzerSuppressed(analyzer, options, AnalyzerHelper.IsCompilerAnalyzer, analyzerExecutor); - } - - private async Task GetAnalyzerActionsAsync(DiagnosticAnalyzer analyzer, AnalyzerExecutor analyzerExecutor) - { - var analyzerActions = await AnalyzerManager.Instance.GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false); - DiagnosticAnalyzerLogger.UpdateAnalyzerTypeCount(analyzer, analyzerActions, _owner.DiagnosticLogAggregator); - return analyzerActions; + _analysisOptions.OnAnalyzerException(ex, analyzer, exceptionDiagnostic); } public async Task> GetSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer) @@ -345,71 +190,40 @@ public async Task> GetSemanticDiagnosticsAsync(Diagno Contract.ThrowIfNull(_document); - using (var pooledObject = SharedPools.Default>().GetPooledObject()) + var documentAnalyzer = analyzer as DocumentDiagnosticAnalyzer; + if (documentAnalyzer != null) { - var diagnostics = pooledObject.Object; - - // Stateless semantic analyzers: - // 1) ISemanticModelAnalyzer/IDocumentBasedDiagnosticAnalyzer - // 2) ISymbolAnalyzer - // 3) ISyntaxNodeAnalyzer - - _cancellationToken.ThrowIfCancellationRequested(); - - var documentAnalyzer = analyzer as DocumentDiagnosticAnalyzer; - if (documentAnalyzer != null) + using (var pooledObject = SharedPools.Default>().GetPooledObject()) { + var diagnostics = pooledObject.Object; + _cancellationToken.ThrowIfCancellationRequested(); + try { await documentAnalyzer.AnalyzeSemanticsAsync(_document, diagnostics.Add, _cancellationToken).ConfigureAwait(false); } - catch (Exception e) when (!AnalyzerExecutor.IsCanceled(e, _cancellationToken)) + catch (Exception e) when (!IsCanceled(e, _cancellationToken)) { OnAnalyzerException(e, analyzer, compilation); return ImmutableArray.Empty; } + + return GetFilteredDocumentDiagnostics(diagnostics, compilation).ToImmutableArray(); } - else - { - var analyzerExecutor = GetAnalyzerExecutor(compilation, diagnostics.Add); - var analyzerActions = await GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false); - if (analyzerActions != null) - { - // SemanticModel actions. - if (analyzerActions.SemanticModelActionsCount > 0) - { - analyzerExecutor.ExecuteSemanticModelActions(analyzerActions, model); - } - - // Symbol actions. - if (analyzerActions.SymbolActionsCount > 0) - { - var symbols = this.GetSymbolsToAnalyze(model); - analyzerExecutor.ExecuteSymbolActions(analyzerActions, symbols); - } - - if (this.SyntaxNodeAnalyzerService != null) - { - // SyntaxNode actions. - if (analyzerActions.SyntaxNodeActionsCount > 0) - { - this.SyntaxNodeAnalyzerService.ExecuteSyntaxNodeActions(analyzerActions, GetSyntaxNodesToAnalyze(), model, analyzerExecutor); - } - - // CodeBlockStart, CodeBlock, CodeBlockEnd, and generated SyntaxNode actions. - if (analyzerActions.CodeBlockStartActionsCount > 0 || analyzerActions.CodeBlockActionsCount > 0 || analyzerActions.CodeBlockEndActionsCount > 0) - { - this.SyntaxNodeAnalyzerService.ExecuteCodeBlockActions(analyzerActions, this.GetDeclarationInfos(model), model, analyzerExecutor); - } - } - } - } + } - return GetFilteredDocumentDiagnostics(diagnostics, compilation).ToImmutableArray(); + if (!_document.SupportsSyntaxTree) + { + return ImmutableArray.Empty; } + + var compilationWithAnalyzers = GetCompilationWithAnalyzers(compilation); + var semanticDiagnostics = await compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, _span, ImmutableArray.Create(analyzer), _cancellationToken).ConfigureAwait(false); + await UpdateAnalyzerTelemetryDataAsync(analyzer, compilationWithAnalyzers).ConfigureAwait(false); + return semanticDiagnostics; } - public async Task> GetProjectDiagnosticsAsync(DiagnosticAnalyzer analyzer, Action forceAnalyzeAllDocuments) + public async Task> GetProjectDiagnosticsAsync(DiagnosticAnalyzer analyzer) { Contract.ThrowIfNull(_project); Contract.ThrowIfFalse(_document == null); @@ -418,7 +232,7 @@ public async Task> GetProjectDiagnosticsAsync(Diagnos { if (_project.SupportsCompilation) { - await this.GetCompilationDiagnosticsAsync(analyzer, diagnostics.Object, forceAnalyzeAllDocuments).ConfigureAwait(false); + await this.GetCompilationDiagnosticsAsync(analyzer, diagnostics.Object).ConfigureAwait(false); } await this.GetProjectDiagnosticsWorkerAsync(analyzer, diagnostics.Object).ConfigureAwait(false); @@ -439,71 +253,33 @@ private async Task GetProjectDiagnosticsWorkerAsync(DiagnosticAnalyzer analyzer, { await projectAnalyzer.AnalyzeProjectAsync(_project, diagnostics.Add, _cancellationToken).ConfigureAwait(false); } - catch (Exception e) when (!AnalyzerExecutor.IsCanceled(e, _cancellationToken)) + catch (Exception e) when (!IsCanceled(e, _cancellationToken)) { var compilation = await _project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); OnAnalyzerException(e, analyzer, compilation); } } - private async Task GetCompilationDiagnosticsAsync(DiagnosticAnalyzer analyzer, List diagnostics, Action forceAnalyzeAllDocuments) + private async Task GetCompilationDiagnosticsAsync(DiagnosticAnalyzer analyzer, List diagnostics) { Contract.ThrowIfFalse(_project.SupportsCompilation); - using (var pooledObject = SharedPools.Default>().GetPooledObject()) - { - var localDiagnostics = pooledObject.Object; - var compilation = await _project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); - DiagnosticAnalyzer clonedAnalyzer = null; - - try - { - // Get all the analyzer actions, including the per-compilation actions. - var analyzerExecutor = GetAnalyzerExecutor(compilation, localDiagnostics.Add); - var analyzerActions = await this.GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false); - var hasDependentCompilationEndAction = await AnalyzerManager.Instance.GetAnalyzerHasDependentCompilationEndAsync(analyzer, analyzerExecutor).ConfigureAwait(false); - - if (hasDependentCompilationEndAction && forceAnalyzeAllDocuments != null) - { - // Analyzer registered a compilation end action and at least one other analyzer action during its compilation start action. - // We need to ensure that we have force analyzed all documents in this project for this analyzer before executing the end actions. - // Doing so on the original analyzer instance might cause duplicate callbacks into the analyzer. - // So we create a new instance of this analyzer and compute compilation diagnostics on that instance. - - try - { - clonedAnalyzer = Activator.CreateInstance(analyzer.GetType()) as DiagnosticAnalyzer; - } - catch - { - // Unable to created a new analyzer instance, bail out on reporting diagnostics. - return; - } - - // Report analyzer exception diagnostics on original analyzer, not the temporary cloned one. - analyzerExecutor = GetAnalyzerExecutorForClone(compilation, localDiagnostics.Add, analyzerForExceptionDiagnostics: analyzer); - - analyzerActions = await this.GetAnalyzerActionsAsync(clonedAnalyzer, analyzerExecutor).ConfigureAwait(false); - forceAnalyzeAllDocuments(_project, clonedAnalyzer, _cancellationToken); - } - - // Compilation actions. - analyzerExecutor.ExecuteCompilationActions(analyzerActions.CompilationActions); + var compilation = await _project.GetCompilationAsync(_cancellationToken).ConfigureAwait(false); + var compilationWithAnalyzers = GetCompilationWithAnalyzers(compilation); + var compilationDiagnostics = await compilationWithAnalyzers.GetAnalyzerCompilationDiagnosticsAsync(ImmutableArray.Create(analyzer), _cancellationToken).ConfigureAwait(false); + await UpdateAnalyzerTelemetryDataAsync(analyzer, compilationWithAnalyzers).ConfigureAwait(false); + diagnostics.AddRange(compilationDiagnostics); + } - // CompilationEnd actions. - analyzerExecutor.ExecuteCompilationActions(analyzerActions.CompilationEndActions); + private async Task UpdateAnalyzerTelemetryDataAsync(DiagnosticAnalyzer analyzer, CompilationWithAnalyzers compilationWithAnalyzers) + { + var actionCounts = await compilationWithAnalyzers.GetAnalyzerActionCountsAsync(analyzer, _cancellationToken).ConfigureAwait(false); + DiagnosticAnalyzerLogger.UpdateAnalyzerTypeCount(analyzer, actionCounts, _project, _owner.DiagnosticLogAggregator); + } - var filteredDiagnostics = CompilationWithAnalyzers.GetEffectiveDiagnostics(localDiagnostics, compilation); - diagnostics.AddRange(filteredDiagnostics); - } - finally - { - if (clonedAnalyzer != null) - { - AnalyzerManager.Instance.ClearAnalyzerState(ImmutableArray.Create(clonedAnalyzer)); - } - } - } + private static bool IsCanceled(Exception ex, CancellationToken cancellationToken) + { + return (ex as OperationCanceledException)?.CancellationToken == cancellationToken; } } } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.AnalyzerExecutor.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.AnalyzerExecutor.cs index eeee4a5cfe1a98ffbb75bc82265391a1ad796205..13370ee1a09c7899ea9eb1d705f56237afb260a8 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.AnalyzerExecutor.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.AnalyzerExecutor.cs @@ -132,7 +132,7 @@ public async Task GetProjectAnalysisDataAsync(DiagnosticAnalyzerDr return existingData; } - var diagnosticData = await GetProjectDiagnosticsAsync(analyzerDriver, stateSet.Analyzer, _owner.ForceAnalyzeAllDocuments).ConfigureAwait(false); + var diagnosticData = await GetProjectDiagnosticsAsync(analyzerDriver, stateSet.Analyzer).ConfigureAwait(false); return new AnalysisData(versions.TextVersion, versions.DataVersion, GetExistingItems(existingData), diagnosticData.AsImmutableOrEmpty()); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.SolutionCrawlerAnalysisState.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.SolutionCrawlerAnalysisState.cs new file mode 100644 index 0000000000000000000000000000000000000000..886204ecc2986463bf81470b016b98fe19d315be --- /dev/null +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.SolutionCrawlerAnalysisState.cs @@ -0,0 +1,100 @@ +// 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.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 +{ + internal partial class DiagnosticIncrementalAnalyzer + { + // PERF: Keep track of the current solution crawler analysis state for each project, so that we can reduce memory pressure by disposing off the per-project CompilationWithAnalyzers instances when appropriate. + private class SolutionCrawlerAnalysisState + { + private readonly ConditionalWeakTable _projectAnalysisStateCache; + private readonly HostAnalyzerManager _hostAnalyzerManager; + private DateTime _lastCacheCleanupTime; + + // Only keep hold CompilationWithAnalyzers instances for projects whose documents were analyzed within the last minute. + // TODO: Tune this cache cleanup interval based on performance measurements. + private const int cacheCleanupIntervalInMinutes = 1; + + public SolutionCrawlerAnalysisState(HostAnalyzerManager hostAnalyzerManager) + { + _projectAnalysisStateCache = new ConditionalWeakTable(); + _hostAnalyzerManager = hostAnalyzerManager; + _lastCacheCleanupTime = DateTime.UtcNow; + } + + private class ProjectAnalysisState + { + public HashSet PendingDocumentsOrProject { get; set; } + public DateTime LastAccessTime { get; set; } + } + + private ProjectAnalysisState CreateProjectAnalysisState(Project project) + { + return new ProjectAnalysisState + { + PendingDocumentsOrProject = new HashSet(project.Documents.Select(d => d.Id.Id).Concat(project.Id.Id)), + LastAccessTime = DateTime.UtcNow + }; + } + + public void OnDocumentAnalyzed(Document document) + { + OnDocumentOrProjectAnalyzed(document.Id.Id, document.Project); + } + + public void OnProjectAnalyzed(Project project) + { + OnDocumentOrProjectAnalyzed(project.Id.Id, project); + } + + private void OnDocumentOrProjectAnalyzed(Guid documentOrProjectGuid, Project project) + { + if (!project.SupportsCompilation) + { + return; + } + + var currentTime = DateTime.UtcNow; + var projectAnalysisState = _projectAnalysisStateCache.GetValue(project, CreateProjectAnalysisState); + + projectAnalysisState.PendingDocumentsOrProject.Remove(documentOrProjectGuid); + projectAnalysisState.LastAccessTime = currentTime; + + if (projectAnalysisState.PendingDocumentsOrProject.Count == 0) + { + // PERF: We have computed and cached all documents and project diagnostics for the given project, so drop the CompilationWithAnalyzers instance that also caches all these diagnostics. + _projectAnalysisStateCache.Remove(project); + _hostAnalyzerManager.DisposeCompilationWithAnalyzers(project); + } + + var minutesSinceCleanup = (currentTime - _lastCacheCleanupTime).TotalMinutes; + if (minutesSinceCleanup >= cacheCleanupIntervalInMinutes) + { + // PERF: For projects which haven't been analyzed recently, drop the CompilationWithAnalyzers instance to reduce memory pressure. + // Subsequent diagnostic request with instantiate a new CompilationWithAnalyzers for these projects. + foreach (var p in project.Solution.Projects) + { + ProjectAnalysisState state; + if (_projectAnalysisStateCache.TryGetValue(p, out state)) + { + var timeSinceLastAccess = currentTime - state.LastAccessTime; + if (timeSinceLastAccess.TotalMinutes >= cacheCleanupIntervalInMinutes) + { + _hostAnalyzerManager.DisposeCompilationWithAnalyzers(p); + state.LastAccessTime = currentTime; + } + } + } + + _lastCacheCleanupTime = currentTime; + } + } + } + } +} diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs index a94f465d24ee184865bc3f1936d0bca77d4b3254..75d9e08c120616921bfb895e6f6e65739ecbcf82 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.HostStates.cs @@ -36,6 +36,12 @@ public IEnumerable GetOrCreateStateSets(string language) return GetAnalyzerMap(language).GetStateSets(); } + public IEnumerable GetAnalyzers(string language) + { + var map = GetAnalyzerMap(language); + return map.GetAnalyzers(); + } + public StateSet GetOrCreateStateSet(string language, DiagnosticAnalyzer analyzer) { return GetAnalyzerMap(language).GetStateSet(analyzer); @@ -81,6 +87,21 @@ public DiagnosticAnalyzerMap(HostAnalyzerManager analyzerManager, string languag _map = analyzerMap.Remove(_compilerAnalyzer); } + public IEnumerable GetAnalyzers() + { + // always return compiler one first if it exists. + // it might not exist in test environment. + if (_compilerAnalyzer != null) + { + yield return _compilerAnalyzer; + } + + foreach (var analyzer in _map.Keys) + { + yield return analyzer; + } + } + public IEnumerable GetStateSets() { // always return compiler one first if it exists. diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs index f05a6a608bc3981df3e5b071c2b53674a9bec906..23825d7e324e584fdbea9687248d9857784ef0c7 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -33,6 +33,12 @@ public IEnumerable GetStateSets(ProjectId projectId) return map.Values; } + public IEnumerable GetAnalyzers(Project project) + { + var map = GetOrUpdateAnalyzerMap(project); + return map.Keys; + } + public IEnumerable GetOrUpdateStateSets(Project project) { var map = GetOrUpdateAnalyzerMap(project); @@ -216,7 +222,13 @@ public void RemoveStateSet(ProjectId projectId) [Conditional("DEBUG")] private void VerifyDiagnosticStates(IEnumerable stateSets) { - StateManager.VerifyDiagnosticStates(_owner._hostStates.GetStateSets().Concat(stateSets)); + // We do not de-duplicate analyzer instances across host and project analyzers. + var projectAnalyzers = stateSets.Select(state => state.Analyzer).ToImmutableHashSet(); + + var hostStates = _owner._hostStates.GetStateSets() + .Where(state => !projectAnalyzers.Contains(state.Analyzer)); + + StateManager.VerifyDiagnosticStates(hostStates.Concat(stateSets)); } private struct Entry diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs index 72315840a58178312d0f0736ec6e4d1974c49906..4f7bced381a8e40c35254ff0535a4f3a0f22edb7 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -40,6 +40,14 @@ public StateManager(HostAnalyzerManager analyzerManager) /// public event EventHandler ProjectAnalyzerReferenceChanged; + /// + /// Return s for the given . + /// + public IEnumerable GetAnalyzers(Project project) + { + return _hostStates.GetAnalyzers(project.Language).Concat(_projectStates.GetAnalyzers(project)); + } + /// /// Return s for the given . /// This will never create new but will return ones already created. @@ -170,7 +178,10 @@ private static void VerifyDiagnosticStates(IEnumerable stateSets) { var state = stateSet.GetState((StateType)i); - Contract.Requires(set.Add(ValueTuple.Create(state.Language, state.Name))); + if (!(set.Add(ValueTuple.Create(state.Language, state.Name)))) + { + Contract.Fail(); + } } } } diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs index f91cee908d779440b1a1b09fa134e75b2f347dca..069860f83c9a4797c80edcfc7b908e74484ecf5b 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -28,6 +27,7 @@ internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncremental private readonly AnalyzerExecutor _executor; private readonly StateManager _stateManager; private readonly SimpleTaskQueue _eventQueue; + private readonly SolutionCrawlerAnalysisState _solutionCrawlerAnalysisState; public DiagnosticIncrementalAnalyzer( DiagnosticAnalyzerService owner, @@ -41,9 +41,9 @@ internal partial class DiagnosticIncrementalAnalyzer : BaseDiagnosticIncremental _memberRangeMap = new MemberRangeMap(); _executor = new AnalyzerExecutor(this); _eventQueue = new SimpleTaskQueue(TaskScheduler.Default); - _stateManager = new StateManager(analyzerManager); _stateManager.ProjectAnalyzerReferenceChanged += OnProjectAnalyzerReferenceChanged; + _solutionCrawlerAnalysisState = new SolutionCrawlerAnalysisState(analyzerManager); } private void OnProjectAnalyzerReferenceChanged(object sender, ProjectAnalyzerReferenceChangedEventArgs e) @@ -116,6 +116,11 @@ private bool CheckOption(Workspace workspace, string language, bool documentOpen return false; } + internal IEnumerable GetAnalyzers(Project project) + { + return _stateManager.GetAnalyzers(project); + } + public override async Task AnalyzeSyntaxAsync(Document document, CancellationToken cancellationToken) { await AnalyzeSyntaxAsync(document, diagnosticIds: null, skipClosedFileChecks: false, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -148,7 +153,7 @@ private async Task AnalyzeSyntaxAsync(Document document, ImmutableHashSet ShouldRunAnalyzerForStateTypeAsync(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer analyzer, StateType stateTypeId, ImmutableHashSet diagnosticIds) + private bool ShouldRunAnalyzerForStateType(DiagnosticAnalyzer analyzer, StateType stateTypeId, ImmutableHashSet diagnosticIds) { - return await ShouldRunAnalyzerForStateTypeAsync(driver, analyzer, stateTypeId, diagnosticIds, Owner.GetDiagnosticDescriptors).ConfigureAwait(false); + return ShouldRunAnalyzerForStateType(analyzer, stateTypeId, diagnosticIds, Owner.GetDiagnosticDescriptors); } - private static async Task ShouldRunAnalyzerForStateTypeAsync(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer analyzer, StateType stateTypeId, + private static bool ShouldRunAnalyzerForStateType(DiagnosticAnalyzer analyzer, StateType stateTypeId, ImmutableHashSet diagnosticIds = null, Func> getDescriptors = null) { - Debug.Assert(!driver.IsAnalyzerSuppressed(analyzer)); - if (diagnosticIds != null && getDescriptors(analyzer).All(d => !diagnosticIds.Contains(d.Id))) { return false; @@ -522,26 +529,19 @@ private async Task ShouldRunAnalyzerForStateTypeAsync(DiagnosticAnalyzerDr switch (stateTypeId) { case StateType.Syntax: - return await analyzer.SupportsSyntaxDiagnosticAnalysisAsync(driver).ConfigureAwait(false); + return analyzer.SupportsSyntaxDiagnosticAnalysis(); case StateType.Document: - return await analyzer.SupportsSemanticDiagnosticAnalysisAsync(driver).ConfigureAwait(false); + return analyzer.SupportsSemanticDiagnosticAnalysis(); case StateType.Project: - return await analyzer.SupportsProjectDiagnosticAnalysisAsync(driver).ConfigureAwait(false); + return analyzer.SupportsProjectDiagnosticAnalysis(); default: throw ExceptionUtilities.Unreachable; } } - // internal for testing purposes only. - internal void ForceAnalyzeAllDocuments(Project project, DiagnosticAnalyzer analyzer, CancellationToken cancellationToken) - { - var diagnosticIds = Owner.GetDiagnosticDescriptors(analyzer).Select(d => d.Id).ToImmutableHashSet(); - ReanalyzeAllDocumentsAsync(project, analyzer, diagnosticIds, cancellationToken).Wait(cancellationToken); - } - public override void LogAnalyzerCountSummary() { DiagnosticAnalyzerLogger.LogAnalyzerCrashCountSummary(_correlationId, DiagnosticLogAggregator); @@ -850,7 +850,6 @@ private static async Task> GetSemanticDiagnosticsAsy var tree = await userDiagnosticDriver.Document.GetSyntaxTreeAsync(userDiagnosticDriver.CancellationToken).ConfigureAwait(false); var diagnostics = await userDiagnosticDriver.GetSemanticDiagnosticsAsync(analyzer).ConfigureAwait(false); - return GetDiagnosticData(userDiagnosticDriver.Document, tree, userDiagnosticDriver.Span, diagnostics); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) @@ -860,7 +859,7 @@ private static async Task> GetSemanticDiagnosticsAsy } } - private static async Task> GetProjectDiagnosticsAsync(DiagnosticAnalyzerDriver userDiagnosticDriver, DiagnosticAnalyzer analyzer, Action forceAnalyzeAllDocuments) + private static async Task> GetProjectDiagnosticsAsync(DiagnosticAnalyzerDriver userDiagnosticDriver, DiagnosticAnalyzer analyzer) { using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, userDiagnosticDriver.Project, analyzer, userDiagnosticDriver.CancellationToken)) { @@ -868,7 +867,7 @@ private static async Task> GetProjectDiagnosticsAsyn { Contract.ThrowIfNull(analyzer); - var diagnostics = await userDiagnosticDriver.GetProjectDiagnosticsAsync(analyzer, forceAnalyzeAllDocuments).ConfigureAwait(false); + var diagnostics = await userDiagnosticDriver.GetProjectDiagnosticsAsync(analyzer).ConfigureAwait(false); return GetDiagnosticData(userDiagnosticDriver.Project, diagnostics); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 193a27d1fb51af408220811d185043898454af2a..32b1ddeadfc625b4e538eb599d12ea62aeb8add0 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -46,11 +46,6 @@ public override Task> GetProjectDiagnosticsForIds return new IDELatestDiagnosticGetter(this, diagnosticIds, concurrent: projectId == null).GetProjectDiagnosticsAsync(solution, projectId, cancellationToken); } - private Task ReanalyzeAllDocumentsAsync(Project project, DiagnosticAnalyzer analyzer, ImmutableHashSet diagnosticIds, CancellationToken cancellationToken) - { - return new ReanalysisDiagnosticGetter(this, analyzer, diagnosticIds).ReanalyzeAllDocumentsAsync(project, cancellationToken); - } - private abstract class DiagnosticsGetter { protected readonly DiagnosticIncrementalAnalyzer Owner; @@ -443,8 +438,8 @@ private async Task AppendDiagnosticsOfStateTypeAsync(object documentOrProject, S { cancellationToken.ThrowIfCancellationRequested(); - if (driver.IsAnalyzerSuppressed(stateSet.Analyzer) || - !(await this.Owner.ShouldRunAnalyzerForStateTypeAsync(driver, stateSet.Analyzer, stateType, this.DiagnosticIds).ConfigureAwait(false))) + if (Owner.Owner.IsAnalyzerSuppressed(stateSet.Analyzer, project) || + !this.Owner.ShouldRunAnalyzerForStateType(stateSet.Analyzer, stateType, this.DiagnosticIds)) { continue; } @@ -471,52 +466,6 @@ private DiagnosticLogAggregator DiagnosticLogAggregator } } - private class ReanalysisDiagnosticGetter : LatestDiagnosticsGetter - { - private readonly DiagnosticAnalyzer _analyzer; - - public ReanalysisDiagnosticGetter(DiagnosticIncrementalAnalyzer owner, DiagnosticAnalyzer analyzer, ImmutableHashSet diagnosticIds) : base(owner, diagnosticIds) - { - _analyzer = analyzer; - } - - public async Task ReanalyzeAllDocumentsAsync(Project project, CancellationToken cancellationToken) - { - foreach (var document in project.Documents) - { - cancellationToken.ThrowIfCancellationRequested(); - - await AppendDiagnosticsAsync(document, cancellationToken).ConfigureAwait(false); - } - } - - protected override void FilterDiagnostics(AnalysisData analysisData, Func predicateOpt = null) - { - // we don't care about result - return; - } - - protected override async Task GetDiagnosticAnalysisDataAsync( - Solution solution, DiagnosticAnalyzerDriver analyzerDriver, StateSet stateSet, StateType stateType, VersionArgument versions) - { - // we don't care about result - switch (stateType) - { - case StateType.Syntax: - await GetSyntaxDiagnosticsAsync(analyzerDriver, _analyzer).ConfigureAwait(false); - break; - case StateType.Document: - await GetSemanticDiagnosticsAsync(analyzerDriver, _analyzer).ConfigureAwait(false); - break; - case StateType.Project: - default: - return Contract.FailWithReturn("Can't reach here"); - } - - return AnalysisData.Empty; - } - } - private class IDELatestDiagnosticGetter : LatestDiagnosticsGetter { private readonly bool _concurrent; diff --git a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetLatestDiagnosticsForSpan.cs b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetLatestDiagnosticsForSpan.cs index de3d64f60ef336bf1505bba385763da098c475cd..3a40aa83982d85306db20136c9d6030770e89e07 100644 --- a/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetLatestDiagnosticsForSpan.cs +++ b/src/Features/Core/Diagnostics/EngineV1/DiagnosticIncrementalAnalyzer_GetLatestDiagnosticsForSpan.cs @@ -96,7 +96,7 @@ public async Task TryGetAsync() } containsFullResult &= await TryGetDocumentDiagnosticsAsync( - stateSet, StateType.Project, (t, d) => t.Equals(projectTextVersion) && d.Equals(semanticVersion), GetProjectDiagnosticsWorkerAsync).ConfigureAwait(false); + stateSet, StateType.Project, (t, d) => t.Equals(projectTextVersion) && d.Equals(semanticVersion), GetProjectDiagnosticsAsync).ConfigureAwait(false); } // if we are blocked for data, then we should always have full result. @@ -238,12 +238,13 @@ private static TextSpan AdjustSpan(Document document, SyntaxNode root, TextSpan StateSet stateSet, StateType stateType, Func versionCheck, Func>> getDiagnostics) { - if (_spanBasedDriver.IsAnalyzerSuppressed(stateSet.Analyzer) || !(await ShouldRunAnalyzerForStateTypeAsync(stateSet, stateType).ConfigureAwait(false))) + if (_owner.Owner.IsAnalyzerSuppressed(stateSet.Analyzer, _document.Project) || + !ShouldRunAnalyzerForStateType(stateSet, stateType)) { return true; } - bool supportsSemanticInSpan = await stateSet.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysisAsync(_spanBasedDriver).ConfigureAwait(false); + bool supportsSemanticInSpan = stateSet.Analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis(); var analyzerDriver = GetAnalyzerDriverBasedOnStateType(stateType, supportsSemanticInSpan); return await TryGetDocumentDiagnosticsAsync(stateSet, stateType, supportsSemanticInSpan, versionCheck, getDiagnostics, analyzerDriver).ConfigureAwait(false); @@ -290,14 +291,9 @@ private static TextSpan AdjustSpan(Document document, SyntaxNode root, TextSpan return true; } - private async Task ShouldRunAnalyzerForStateTypeAsync(StateSet stateSet, StateType stateType) + private bool ShouldRunAnalyzerForStateType(StateSet stateSet, StateType stateType) { - if (stateType == StateType.Project) - { - return await DiagnosticIncrementalAnalyzer.ShouldRunAnalyzerForStateTypeAsync(_projectDriver, stateSet.Analyzer, stateType).ConfigureAwait(false); - } - - return await DiagnosticIncrementalAnalyzer.ShouldRunAnalyzerForStateTypeAsync(_spanBasedDriver, stateSet.Analyzer, stateType).ConfigureAwait(false); + return DiagnosticIncrementalAnalyzer.ShouldRunAnalyzerForStateType(stateSet.Analyzer, stateType); } private bool BlockForData(StateType stateType, bool supportsSemanticInSpan) @@ -322,11 +318,6 @@ private DiagnosticAnalyzerDriver GetAnalyzerDriverBasedOnStateType(StateType sta { return stateType == StateType.Project ? _projectDriver : supportsSemanticInSpan ? _spanBasedDriver : _documentBasedDriver; } - - private Task> GetProjectDiagnosticsWorkerAsync(DiagnosticAnalyzerDriver driver, DiagnosticAnalyzer analyzer) - { - return GetProjectDiagnosticsAsync(driver, analyzer, _owner.ForceAnalyzeAllDocuments); - } } } } diff --git a/src/Features/Core/Diagnostics/EngineV1/ISyntaxNodeAnalyzerService.cs b/src/Features/Core/Diagnostics/EngineV1/ISyntaxNodeAnalyzerService.cs deleted file mode 100644 index 963860e8635e83298df2db736f153c7e8938e3b6..0000000000000000000000000000000000000000 --- a/src/Features/Core/Diagnostics/EngineV1/ISyntaxNodeAnalyzerService.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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.Collections.Generic; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.Diagnostics.EngineV1 -{ - internal interface ISyntaxNodeAnalyzerService : ILanguageService - { - void ExecuteSyntaxNodeActions( - AnalyzerActions actions, - IEnumerable descendantNodes, - SemanticModel semanticModel, - AnalyzerExecutor analyzerExecutor); - - void ExecuteCodeBlockActions( - AnalyzerActions actions, - IEnumerable declarationsInNode, - SemanticModel semanticModel, - AnalyzerExecutor analyzerExecutor); - } -} diff --git a/src/Features/Core/Diagnostics/HostAnalyzerManager.cs b/src/Features/Core/Diagnostics/HostAnalyzerManager.cs index e4402eff8d8d476f542c8f06e126a9a78579fc4f..703aadb69cada0948a7f84b47f0fcc77755c4e8f 100644 --- a/src/Features/Core/Diagnostics/HostAnalyzerManager.cs +++ b/src/Features/Core/Diagnostics/HostAnalyzerManager.cs @@ -8,6 +8,8 @@ using System.Reflection; using Microsoft.CodeAnalysis.Diagnostics.Log; using Roslyn.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; +using System.Runtime.CompilerServices; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -71,6 +73,11 @@ internal sealed partial class HostAnalyzerManager /// private ImmutableDictionary> _compilerDiagnosticAnalyzerDescriptorMap; + /// + /// Map from project to instance to be used for computing analyzer diagnostics. + /// + private readonly ConditionalWeakTable _compilationWithAnalyzersMap; + /// /// Loader for VSIX-based analyzers. /// @@ -99,6 +106,7 @@ internal sealed partial class HostAnalyzerManager _compilerDiagnosticAnalyzerMap = ImmutableDictionary.Empty; _compilerDiagnosticAnalyzerDescriptorMap = ImmutableDictionary>.Empty; _hostDiagnosticAnalyzerPackageNameMap = ImmutableDictionary.Empty; + _compilationWithAnalyzersMap = new ConditionalWeakTable(); DiagnosticAnalyzerLogger.LogWorkspaceAnalyzers(hostAnalyzerReferences); } @@ -129,8 +137,41 @@ public object GetAnalyzerReferenceIdentity(AnalyzerReference reference) /// public ImmutableArray GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer) { - var analyzerExecutor = AnalyzerHelper.GetAnalyzerExecutorForSupportedDiagnostics(analyzer, _hostDiagnosticUpdateSource); - return AnalyzerManager.Instance.GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor); + ImmutableArray descriptors; + try + { + // SupportedDiagnostics is user code and can throw an exception. + descriptors = analyzer.SupportedDiagnostics; + if (descriptors.IsDefault) + { + descriptors = ImmutableArray.Empty; + } + } + catch (Exception ex) + { + AnalyzerHelper.OnAnalyzerExceptionForSupportedDiagnostics(analyzer, ex, _hostDiagnosticUpdateSource); + descriptors = ImmutableArray.Empty; + } + + return descriptors; + } + + /// + /// Return true if the given is suppressed for the given project. + /// + public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project) + { + var options = project.CompilationOptions; + if (options == null) + { + return false; + } + + // Skip telemetry logging for supported diagnostics, as that can cause an infinite loop. + Action onAnalyzerException = (ex, a, diagnostic) => + AnalyzerHelper.OnAnalyzerException_NoTelemetryLogging(ex, a, diagnostic, _hostDiagnosticUpdateSource, project.Id); + + return CompilationWithAnalyzers.IsDiagnosticAnalyzerSuppressed(analyzer, options, onAnalyzerException); } /// @@ -146,7 +187,7 @@ public ImmutableArray GetDiagnosticDescriptors(DiagnosticA /// public ImmutableDictionary> GetHostDiagnosticDescriptorsPerReference() { - return CreateDiagnosticDescriptorsPerReference(_lazyHostDiagnosticAnalyzersPerReferenceMap.Value); + return CreateDiagnosticDescriptorsPerReference(_lazyHostDiagnosticAnalyzersPerReferenceMap.Value, projectOpt: null); } /// @@ -154,7 +195,7 @@ public ImmutableArray GetDiagnosticDescriptors(DiagnosticA /// public ImmutableDictionary> CreateDiagnosticDescriptorsPerReference(Project project) { - return CreateDiagnosticDescriptorsPerReference(CreateDiagnosticAnalyzersPerReference(project)); + return CreateDiagnosticDescriptorsPerReference(CreateDiagnosticAnalyzersPerReference(project), project); } /// @@ -255,7 +296,8 @@ public string GetDiagnosticAnalyzerPackageName(string language, DiagnosticAnalyz } private ImmutableDictionary> CreateDiagnosticDescriptorsPerReference( - ImmutableDictionary> analyzersMap) + ImmutableDictionary> analyzersMap, + Project projectOpt) { var builder = ImmutableDictionary.CreateBuilder>(); foreach (var kv in analyzersMap) @@ -458,6 +500,20 @@ private static ImmutableArray CreateAnalyzerReferencesFromPac return current; } + internal CompilationWithAnalyzers GetOrCreateCompilationWithAnalyzers(Project project, ConditionalWeakTable.CreateValueCallback createCompilationWithAnalyzers) + { + Contract.ThrowIfFalse(project.SupportsCompilation); + return _compilationWithAnalyzersMap.GetValue(project, createCompilationWithAnalyzers); + } + + internal void DisposeCompilationWithAnalyzers(Project project) + { + if (project.SupportsCompilation) + { + _compilationWithAnalyzersMap.Remove(project); + } + } + private class LoadContextAssemblyLoader : IAnalyzerAssemblyLoader { public void AddDependencyLocation(string fullPath) diff --git a/src/Features/Core/Diagnostics/IBuiltInAnalyzer.cs b/src/Features/Core/Diagnostics/IBuiltInAnalyzer.cs index ce3dccbd9f4dd0e557856f8238512a78dfef0fb8..61ebe2eb0e956aa8b491a25ce70be3b253140701 100644 --- a/src/Features/Core/Diagnostics/IBuiltInAnalyzer.cs +++ b/src/Features/Core/Diagnostics/IBuiltInAnalyzer.cs @@ -8,5 +8,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// internal interface IBuiltInAnalyzer { + DiagnosticAnalyzerCategory GetAnalyzerCategory(); } } diff --git a/src/Features/Core/Diagnostics/Log/DiagnosticAnalyzerLogger.cs b/src/Features/Core/Diagnostics/Log/DiagnosticAnalyzerLogger.cs index 71fbdaf94536f5b4a8f7007202dd7574bd052a95..c2552ccd655b2da10e66027db4ecda952cdf34a1 100644 --- a/src/Features/Core/Diagnostics/Log/DiagnosticAnalyzerLogger.cs +++ b/src/Features/Core/Diagnostics/Log/DiagnosticAnalyzerLogger.cs @@ -7,6 +7,7 @@ using System.Text; using Microsoft.CodeAnalysis.Internal.Log; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry; namespace Microsoft.CodeAnalysis.Diagnostics.Log { @@ -38,7 +39,7 @@ public static void LogWorkspaceAnalyzers(ImmutableArray analy })); } - public static void LogAnalyzerCrashCount(DiagnosticAnalyzer analyzer, Exception ex, LogAggregator logAggregator) + public static void LogAnalyzerCrashCount(DiagnosticAnalyzer analyzer, Exception ex, LogAggregator logAggregator, ProjectId projectId) { if (logAggregator == null || analyzer == null || ex == null || ex is OperationCanceledException) { @@ -46,7 +47,7 @@ public static void LogAnalyzerCrashCount(DiagnosticAnalyzer analyzer, Exception } // TODO: once we create description manager, pass that into here. - bool telemetry = DiagnosticAnalyzerLogger.AllowsTelemetry(null, analyzer); + bool telemetry = DiagnosticAnalyzerLogger.AllowsTelemetry(null, analyzer, projectId); var tuple = ValueTuple.Create(telemetry, analyzer.GetType(), ex.GetType()); logAggregator.IncreaseCount(tuple); } @@ -86,14 +87,14 @@ public static void LogAnalyzerCrashCountSummary(int correlationId, LogAggregator } } - public static void UpdateAnalyzerTypeCount(DiagnosticAnalyzer analyzer, AnalyzerActions analyzerActions, DiagnosticLogAggregator logAggregator) + public static void UpdateAnalyzerTypeCount(DiagnosticAnalyzer analyzer, ActionCounts analyzerActions, Project projectOpt, DiagnosticLogAggregator logAggregator) { if (analyzerActions == null || analyzer == null || logAggregator == null) { return; } - logAggregator.UpdateAnalyzerTypeCount(analyzer, analyzerActions); + logAggregator.UpdateAnalyzerTypeCount(analyzer, analyzerActions, projectOpt); } public static void LogAnalyzerTypeCountSummary(int correlationId, DiagnosticLogAggregator logAggregator) @@ -131,7 +132,7 @@ public static void LogAnalyzerTypeCountSummary(int correlationId, DiagnosticLogA } } - public static bool AllowsTelemetry(DiagnosticAnalyzerService service, DiagnosticAnalyzer analyzer) + public static bool AllowsTelemetry(DiagnosticAnalyzerService service, DiagnosticAnalyzer analyzer, ProjectId projectIdOpt) { StrongBox value; if (s_telemetryCache.TryGetValue(analyzer, out value)) diff --git a/src/Features/Core/Diagnostics/Log/DiagnosticLogAggregator.cs b/src/Features/Core/Diagnostics/Log/DiagnosticLogAggregator.cs index 67d6ca845184da48274bc437a65dfe2f2f94df74..b971cbf8418d5cebcece90a097555b8ab2134173 100644 --- a/src/Features/Core/Diagnostics/Log/DiagnosticLogAggregator.cs +++ b/src/Features/Core/Diagnostics/Log/DiagnosticLogAggregator.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Internal.Log; +using static Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetry; namespace Microsoft.CodeAnalysis.Diagnostics.Log { @@ -37,9 +38,9 @@ public DiagnosticLogAggregator(DiagnosticAnalyzerService owner) get { return _analyzerInfoMap; } } - public void UpdateAnalyzerTypeCount(DiagnosticAnalyzer analyzer, AnalyzerActions analyzerActions) + public void UpdateAnalyzerTypeCount(DiagnosticAnalyzer analyzer, ActionCounts analyzerActions, Project projectOpt) { - var telemetry = DiagnosticAnalyzerLogger.AllowsTelemetry(_owner, analyzer); + var telemetry = DiagnosticAnalyzerLogger.AllowsTelemetry(_owner, analyzer, projectOpt?.Id); ImmutableInterlocked.AddOrUpdate( ref _analyzerInfoMap, @@ -56,9 +57,9 @@ public class AnalyzerInfo { public Type CLRType; public bool Telemetry; - public int[] Counts = new int[DiagnosticLogAggregator.AnalyzerTypes.Length]; + public int[] Counts = new int[AnalyzerTypes.Length]; - public AnalyzerInfo(DiagnosticAnalyzer analyzer, AnalyzerActions analyzerActions, bool telemetry) + public AnalyzerInfo(DiagnosticAnalyzer analyzer, ActionCounts analyzerActions, bool telemetry) { CLRType = analyzer.GetType(); Telemetry = telemetry; @@ -75,7 +76,7 @@ public AnalyzerInfo(DiagnosticAnalyzer analyzer, AnalyzerActions analyzerActions Counts[9] = analyzerActions.SyntaxTreeActionsCount; } - public void SetAnalyzerTypeCount(AnalyzerActions analyzerActions) + public void SetAnalyzerTypeCount(ActionCounts analyzerActions) { Counts[0] = analyzerActions.CodeBlockActionsCount; Counts[1] = analyzerActions.CodeBlockEndActionsCount; diff --git a/src/Features/Core/Features.csproj b/src/Features/Core/Features.csproj index ad121301d028e2cf9e3f24cb7061a914540e4d23..eae56c20eabd91313a93d4411ed69ebf0b55ac20 100644 --- a/src/Features/Core/Features.csproj +++ b/src/Features/Core/Features.csproj @@ -185,6 +185,7 @@ + @@ -256,7 +257,6 @@ - @@ -268,7 +268,6 @@ - diff --git a/src/Features/Core/Shared/Extensions/DiagnosticAnalyzerExtensions.cs b/src/Features/Core/Shared/Extensions/DiagnosticAnalyzerExtensions.cs index 297f2179340fc5d9b839edd5acc34591b47ffbcb..a8521ee51c182636f9173ccdeae747ef94ebd46c 100644 --- a/src/Features/Core/Shared/Extensions/DiagnosticAnalyzerExtensions.cs +++ b/src/Features/Core/Shared/Extensions/DiagnosticAnalyzerExtensions.cs @@ -1,16 +1,12 @@ // 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.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics.EngineV1; -using Roslyn.Utilities; -using System; namespace Microsoft.CodeAnalysis.Shared.Extensions { internal static class DiagnosticAnalyzerExtensions { - public static async Task GetDiagnosticAnalyzerCategoryAsync(this DiagnosticAnalyzer analyzer, DiagnosticAnalyzerDriver driver) + public static DiagnosticAnalyzerCategory GetDiagnosticAnalyzerCategory(this DiagnosticAnalyzer analyzer) { var category = DiagnosticAnalyzerCategory.None; @@ -22,82 +18,45 @@ public static async Task GetDiagnosticAnalyzerCatego { category |= DiagnosticAnalyzerCategory.ProjectAnalysis; } - else if (driver != null) + else { - // If an analyzer requires or might require the entire document, then it cannot promise - // to be able to operate on a limited span of the document. In practical terms, no analyzer - // can have both SemanticDocumentAnalysis and SemanticSpanAnalysis as categories. - bool cantSupportSemanticSpanAnalysis = false; - var analyzerActions = await driver.GetAnalyzerActionsAsync(analyzer).ConfigureAwait(false); - if (analyzerActions != null) + var builtInAnalyzer = analyzer as IBuiltInAnalyzer; + if (builtInAnalyzer != null) { - if (analyzerActions.SyntaxTreeActionsCount > 0) - { - category |= DiagnosticAnalyzerCategory.SyntaxAnalysis; - } - - if (analyzerActions.SemanticModelActionsCount > 0) - { - category |= DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; - cantSupportSemanticSpanAnalysis = true; - } - - if (analyzerActions.CompilationStartActionsCount > 0) - { - // It is not possible to know what actions a compilation start action will register without executing it, - // so return a worst-case categorization. - category |= (DiagnosticAnalyzerCategory.SyntaxAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis | DiagnosticAnalyzerCategory.ProjectAnalysis); - cantSupportSemanticSpanAnalysis = true; - } - - if (analyzerActions.CompilationActionsCount > 0 || analyzerActions.CompilationStartActionsCount > 0) - { - category |= DiagnosticAnalyzerCategory.ProjectAnalysis; - } - - if (HasSemanticDocumentActions(analyzerActions)) - { - var semanticDocumentAnalysisCategory = cantSupportSemanticSpanAnalysis ? - DiagnosticAnalyzerCategory.SemanticDocumentAnalysis : - DiagnosticAnalyzerCategory.SemanticSpanAnalysis; - category |= semanticDocumentAnalysisCategory; - } + category = builtInAnalyzer.GetAnalyzerCategory(); + } + else + { + // It is not possible to know the categorization for a public analyzer, + // so return a worst-case categorization. + category = (DiagnosticAnalyzerCategory.SyntaxAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis | DiagnosticAnalyzerCategory.ProjectAnalysis); } } return category; } - private static bool HasSemanticDocumentActions(AnalyzerActions analyzerActions) - { - return analyzerActions.SymbolActionsCount > 0 || - analyzerActions.SyntaxNodeActionsCount > 0 || - analyzerActions.SemanticModelActionsCount > 0 || - analyzerActions.CodeBlockActionsCount > 0 || - analyzerActions.CodeBlockStartActionsCount > 0; - } - - public static async Task SupportsSyntaxDiagnosticAnalysisAsync(this DiagnosticAnalyzer analyzer, DiagnosticAnalyzerDriver driver) + public static bool SupportsSyntaxDiagnosticAnalysis(this DiagnosticAnalyzer analyzer) { - var category = await analyzer.GetDiagnosticAnalyzerCategoryAsync(driver).ConfigureAwait(false); + var category = analyzer.GetDiagnosticAnalyzerCategory(); return (category & DiagnosticAnalyzerCategory.SyntaxAnalysis) != 0; } - public static async Task SupportsSemanticDiagnosticAnalysisAsync(this DiagnosticAnalyzer analyzer, DiagnosticAnalyzerDriver driver) + public static bool SupportsSemanticDiagnosticAnalysis(this DiagnosticAnalyzer analyzer) { - var category = await analyzer.GetDiagnosticAnalyzerCategoryAsync(driver).ConfigureAwait(false); + var category = analyzer.GetDiagnosticAnalyzerCategory(); return (category & (DiagnosticAnalyzerCategory.SemanticSpanAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis)) != 0; } - public static async Task SupportsSpanBasedSemanticDiagnosticAnalysisAsync(this DiagnosticAnalyzer analyzer, DiagnosticAnalyzerDriver driver) + public static bool SupportsSpanBasedSemanticDiagnosticAnalysis(this DiagnosticAnalyzer analyzer) { - var category = await analyzer.GetDiagnosticAnalyzerCategoryAsync(driver).ConfigureAwait(false); + var category = analyzer.GetDiagnosticAnalyzerCategory(); return (category & DiagnosticAnalyzerCategory.SemanticSpanAnalysis) != 0; } - public static async Task SupportsProjectDiagnosticAnalysisAsync(this DiagnosticAnalyzer analyzer, DiagnosticAnalyzerDriver driver) + public static bool SupportsProjectDiagnosticAnalysis(this DiagnosticAnalyzer analyzer) { - var category = await analyzer.GetDiagnosticAnalyzerCategoryAsync(driver).ConfigureAwait(false); + var category = analyzer.GetDiagnosticAnalyzerCategory(); return (category & DiagnosticAnalyzerCategory.ProjectAnalysis) != 0; } } diff --git a/src/Features/VisualBasic/BasicFeatures.vbproj b/src/Features/VisualBasic/BasicFeatures.vbproj index d3efbafc69c58532a7699678212db9840755e62c..c7508114c7427a1b820211a27b1e3162a4825b87 100644 --- a/src/Features/VisualBasic/BasicFeatures.vbproj +++ b/src/Features/VisualBasic/BasicFeatures.vbproj @@ -346,7 +346,6 @@ - diff --git a/src/Features/VisualBasic/Diagnostics/VisualBasicSyntaxNodeAnalyzerService.vb b/src/Features/VisualBasic/Diagnostics/VisualBasicSyntaxNodeAnalyzerService.vb deleted file mode 100644 index 546ad00deeebabd5ceec25925fa36b10fc45ea30..0000000000000000000000000000000000000000 --- a/src/Features/VisualBasic/Diagnostics/VisualBasicSyntaxNodeAnalyzerService.vb +++ /dev/null @@ -1,23 +0,0 @@ -' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -Imports System.Composition -Imports Microsoft.CodeAnalysis.Diagnostics.EngineV1 -Imports Microsoft.CodeAnalysis.Host.Mef - -Namespace Microsoft.CodeAnalysis.VisualBasic.Diagnostics - - Friend NotInheritable Class VisualBasicSyntaxNodeAnalyzerService - Inherits AbstractSyntaxNodeAnalyzerService(Of SyntaxKind) - - Public Sub New() - End Sub - - Protected Overrides Function GetSyntaxKindEqualityComparer() As IEqualityComparer(Of SyntaxKind) - Return SyntaxFacts.EqualityComparer - End Function - - Protected Overrides Function GetKind(node As SyntaxNode) As SyntaxKind - Return node.Kind - End Function - End Class -End Namespace diff --git a/src/Test/Utilities/DiagnosticExtensions.cs b/src/Test/Utilities/DiagnosticExtensions.cs index ce3ba0e790a8b6e6e6f103f8ef3adb5f62ddccc3..c9b06d1497cad55567fc98b19b4baf3e3d7a704e 100644 --- a/src/Test/Utilities/DiagnosticExtensions.cs +++ b/src/Test/Utilities/DiagnosticExtensions.cs @@ -167,7 +167,7 @@ public static TCompilation VerifyDiagnostics(this TCompilation c, } Compilation newCompilation; - var driver = AnalyzerDriver.Create(c, analyzersArray, options, AnalyzerManager.Instance, onAnalyzerException, false, out newCompilation, CancellationToken.None); + var driver = AnalyzerDriver.CreateAndAttachToCompilation(c, analyzersArray, options, AnalyzerManager.Instance, onAnalyzerException, false, out newCompilation, CancellationToken.None); var discarded = newCompilation.GetDiagnostics(); diagnostics = driver.GetDiagnosticsAsync().Result.AddRange(exceptionDiagnostics);