From f27dd68045e5ae27142e3850307c8ee72d65e48a Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Wed, 13 Jan 2016 18:06:49 -0800 Subject: [PATCH] implemented analyzer exception description to contain context information --- .../Core/Portable/CodeAnalysis.csproj | 1 + .../CodeAnalysisResources.Designer.cs | 10 ++ .../Core/Portable/CodeAnalysisResources.resx | 4 + .../DiagnosticAnalyzer/AnalysisContextInfo.cs | 119 ++++++++++++++++++ .../DiagnosticAnalyzer/AnalyzerExecutor.cs | 69 ++++++---- .../DiagnosticAnalyzer/AnalyzerManager.cs | 2 +- 6 files changed, 183 insertions(+), 22 deletions(-) create mode 100644 src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index e5a9224cbcf..067e5a5742a 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -67,6 +67,7 @@ + diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs index 37be3b66281..c3b25aa6802 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs @@ -432,6 +432,16 @@ internal class CodeAnalysisResources { } } + /// + /// Looks up a localized string similar to Exception occurred with following context: + ///{0}. + /// + internal static string ExceptionContext { + get { + return ResourceManager.GetString("ExceptionContext", resourceCulture); + } + } + /// /// Looks up a localized string similar to Expected non-empty public key. /// diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index 8bda1bd8e6e..c11a923d215 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -506,4 +506,8 @@ Value for argument '/shared:' must not be empty + + Exception occurred with following context: +{0} + \ No newline at end of file diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs new file mode 100644 index 00000000000..1ded14465a3 --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContextInfo.cs @@ -0,0 +1,119 @@ +// 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.Diagnostics; +using System.Text; +using Microsoft.CodeAnalysis.Semantics; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + internal struct AnalysisContextInfo + { + private readonly Compilation _compilation; + private readonly IOperation _operation; + private readonly ISymbol _symbol; + private readonly SyntaxTree _tree; + private readonly SyntaxNode _node; + + public AnalysisContextInfo(Compilation compilation) : + this(compilation: compilation, operation: null, symbol: null, tree: null, node: null) + { + } + + public AnalysisContextInfo(SemanticModel model) : + this(model.Compilation, model.SyntaxTree) + { + } + + public AnalysisContextInfo(Compilation compilation, ISymbol symbol) : + this(compilation: compilation, operation: null, symbol: symbol, tree: null, node: null) + { + } + + public AnalysisContextInfo(Compilation compilation, SyntaxTree tree) : + this(compilation: compilation, operation: null, symbol: null, tree: tree, node: null) + { + } + + public AnalysisContextInfo(Compilation compilation, SyntaxNode node) : + this(compilation: compilation, operation: null, symbol: null, tree: node.SyntaxTree, node: node) + { + } + + public AnalysisContextInfo(Compilation compilation, IOperation operation) : + this(compilation: compilation, operation: operation, symbol: null, tree: operation.Syntax.SyntaxTree, node: operation.Syntax) + { + } + + public AnalysisContextInfo(Compilation compilation, ISymbol symbol, SyntaxNode node) : + this(compilation: compilation, operation: null, symbol: symbol, tree: node.SyntaxTree, node: node) + { + } + + public AnalysisContextInfo( + Compilation compilation, + IOperation operation, + ISymbol symbol, + SyntaxTree tree, + SyntaxNode node) + { + _compilation = compilation; + _operation = operation; + _symbol = symbol; + _tree = tree; + _node = node; + } + + public string GetContext() + { + var sb = new StringBuilder(); + + if (_compilation?.AssemblyName != null) + { + sb.AppendLine($"{nameof(Compilation)}: {_compilation.AssemblyName}"); + } + + if (_operation != null) + { + sb.AppendLine($"{nameof(IOperation)}: {_operation.Kind}"); + } + + if (_symbol?.Name != null) + { + sb.AppendLine($"{nameof(ISymbol)}: {_symbol.Name} ({_symbol.Kind})"); + } + + if (_tree?.FilePath != null) + { + sb.AppendLine($"{nameof(SyntaxTree)}: {_tree.FilePath}"); + } + + if (_node != null) + { + // can't use Kind since that is language specific. instead will output typename. + sb.AppendLine($"{nameof(SyntaxNode)}: \"{GetValueText(_node)}\" {_node.GetType().Name}@{_node.Span.ToString()}"); + } + + return sb.ToString(); + } + + private string GetValueText(SyntaxNode node) + { + const int cutoff = 30; + + if (node.Span.Length < cutoff) + { + return RemoveNewLines(node.ToString()); + } + + // get actual text without creating texts for all sub nodes. + return RemoveNewLines(node.GetText().ToString(new TextSpan(node.Span.Start - node.FullSpan.Start, cutoff))) + " ..."; + } + + private string RemoveNewLines(string text) + { + return text.Replace(Environment.NewLine, @"\r\n"); + } + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index 5d49df34320..d33158e889c 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -196,7 +196,8 @@ public AnalyzerExecutor WithCancellationToken(CancellationToken cancellationToke 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))); + ExecuteAndCatchIfThrows(analyzer, + () => analyzer.Initialize(new AnalyzerAnalysisContext(analyzer, sessionScope))); } /// @@ -209,8 +210,10 @@ public void ExecuteCompilationStartActions(ImmutableArray startAction.Action(new AnalyzerCompilationStartAnalysisContext(startAction.Analyzer, compilationScope, _compilation, _analyzerOptions, _cancellationToken))); + () => startAction.Action(new AnalyzerCompilationStartAnalysisContext(startAction.Analyzer, compilationScope, _compilation, _analyzerOptions, _cancellationToken)), + new AnalysisContextInfo(_compilation)); } } @@ -258,8 +261,10 @@ private void ExecuteCompilationActionsCore(ImmutableArray endAction.Action(new CompilationAnalysisContext(_compilation, _analyzerOptions, addDiagnostic, - d => IsSupportedDiagnostic(endAction.Analyzer, d), _cancellationToken))); + () => endAction.Action(new CompilationAnalysisContext( + _compilation, _analyzerOptions, addDiagnostic, + d => IsSupportedDiagnostic(endAction.Analyzer, d), _cancellationToken)), + new AnalysisContextInfo(_compilation)); analyzerStateOpt?.ProcessedActions.Add(endAction); } @@ -332,8 +337,9 @@ private void ExecuteCompilationActionsCore(ImmutableArray action(new SymbolAnalysisContext(symbol, _compilation, _analyzerOptions, addDiagnostic, - d => IsSupportedDiagnostic(symbolAction.Analyzer, d), _cancellationToken))); + () => action(new SymbolAnalysisContext(symbol, _compilation, _analyzerOptions, addDiagnostic, + d => IsSupportedDiagnostic(symbolAction.Analyzer, d), _cancellationToken)), + new AnalysisContextInfo(_compilation, symbol)); analyzerStateOpt?.ProcessedActions.Add(symbolAction); } @@ -399,7 +405,8 @@ private void ExecuteCompilationActionsCore(ImmutableArray semanticModelAction.Action(new SemanticModelAnalysisContext(semanticModel, _analyzerOptions, addDiagnostic, - d => IsSupportedDiagnostic(semanticModelAction.Analyzer, d), _cancellationToken))); + d => IsSupportedDiagnostic(semanticModelAction.Analyzer, d), _cancellationToken)), + new AnalysisContextInfo(semanticModel)); analyzerStateOpt?.ProcessedActions.Add(semanticModelAction); } @@ -462,7 +469,8 @@ private void ExecuteCompilationActionsCore(ImmutableArray syntaxTreeAction.Action(new SyntaxTreeAnalysisContext(tree, _analyzerOptions, addDiagnostic, - d => IsSupportedDiagnostic(syntaxTreeAction.Analyzer, d), _compilation, _cancellationToken))); + d => IsSupportedDiagnostic(syntaxTreeAction.Analyzer, d), _compilation, _cancellationToken)), + new AnalysisContextInfo(_compilation, tree)); analyzerStateOpt?.ProcessedActions.Add(syntaxTreeAction); } @@ -483,7 +491,9 @@ private void ExecuteCompilationActionsCore(ImmutableArray IsSupportedDiagnostic(syntaxNodeAction.Analyzer, d), _cancellationToken); - ExecuteAndCatchIfThrows(syntaxNodeAction.Analyzer, () => syntaxNodeAction.Action(syntaxNodeContext)); + ExecuteAndCatchIfThrows(syntaxNodeAction.Analyzer, + () => syntaxNodeAction.Action(syntaxNodeContext), + new AnalysisContextInfo(_compilation, node)); analyzerStateOpt?.ProcessedActions.Add(syntaxNodeAction); } @@ -501,7 +511,9 @@ private void ExecuteCompilationActionsCore(ImmutableArray IsSupportedDiagnostic(operationAction.Analyzer, d), semanticModel, _cancellationToken); - ExecuteAndCatchIfThrows(operationAction.Analyzer, () => operationAction.Action(operationContext)); + ExecuteAndCatchIfThrows(operationAction.Analyzer, + () => operationAction.Action(operationContext), + new AnalysisContextInfo(_compilation, operation)); analyzerStateOpt?.ProcessedActions.Add(operationAction); } @@ -658,7 +670,8 @@ private void ExecuteCompilationActionsCore(ImmutableArray codeBlockAction.Action(new CodeBlockAnalysisContext(declaredNode, declaredSymbol, semanticModel, _analyzerOptions, addDiagnostic, isSupportedDiagnostic, _cancellationToken))); + () => codeBlockAction.Action(new CodeBlockAnalysisContext(declaredNode, declaredSymbol, semanticModel, _analyzerOptions, addDiagnostic, isSupportedDiagnostic, _cancellationToken)), + new AnalysisContextInfo(_compilation, declaredSymbol, declaredNode)); } else { @@ -745,7 +760,8 @@ private void ExecuteCompilationActionsCore(ImmutableArray operationBlockAction.Action(new OperationBlockAnalysisContext(operationBlocks, declaredSymbol, _analyzerOptions, addDiagnostic, isSupportedDiagnostic, semanticModel, _cancellationToken))); + () => operationBlockAction.Action(new OperationBlockAnalysisContext(operationBlocks, declaredSymbol, _analyzerOptions, addDiagnostic, isSupportedDiagnostic, semanticModel, _cancellationToken)), + new AnalysisContextInfo(_compilation, declaredSymbol)); } } @@ -1019,23 +1035,23 @@ internal static bool CanHaveExecutableCodeBlock(ISymbol symbol) } } - internal void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Action analyze) + internal void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Action analyze, AnalysisContextInfo? info = null) { object gate = _getAnalyzerGateOpt?.Invoke(analyzer); if (gate != null) { lock (gate) { - ExecuteAndCatchIfThrows_NoLock(analyzer, analyze); + ExecuteAndCatchIfThrows_NoLock(analyzer, analyze, info); } } else { - ExecuteAndCatchIfThrows_NoLock(analyzer, analyze); + ExecuteAndCatchIfThrows_NoLock(analyzer, analyze, info); } } - private void ExecuteAndCatchIfThrows_NoLock(DiagnosticAnalyzer analyzer, Action analyze) + private void ExecuteAndCatchIfThrows_NoLock(DiagnosticAnalyzer analyzer, Action analyze, AnalysisContextInfo? info) { try { @@ -1057,7 +1073,7 @@ private void ExecuteAndCatchIfThrows_NoLock(DiagnosticAnalyzer analyzer, Action catch (Exception e) when (ExceptionFilter(e)) { // Diagnostic for analyzer exception. - var diagnostic = CreateAnalyzerExceptionDiagnostic(analyzer, e); + var diagnostic = CreateAnalyzerExceptionDiagnostic(analyzer, e, info); try { _onAnalyzerException(e, analyzer, diagnostic); @@ -1084,17 +1100,28 @@ internal bool ExceptionFilter(Exception ex) return true; } - internal static Diagnostic CreateAnalyzerExceptionDiagnostic(DiagnosticAnalyzer analyzer, Exception e) + internal static Diagnostic CreateAnalyzerExceptionDiagnostic(DiagnosticAnalyzer analyzer, Exception e, AnalysisContextInfo? info = null) { var analyzerName = analyzer.ToString(); var title = CodeAnalysisResources.CompilerAnalyzerFailure; var messageFormat = CodeAnalysisResources.CompilerAnalyzerThrows; var messageArguments = new[] { analyzerName, e.GetType().ToString(), e.Message }; - var description = string.Format(CodeAnalysisResources.CompilerAnalyzerThrowsDescription, analyzerName, e.CreateDiagnosticDescription()); + var description = string.Format(CodeAnalysisResources.CompilerAnalyzerThrowsDescription, analyzerName, CreateDiagnosticDescription(info, e)); var descriptor = GetAnalyzerExceptionDiagnosticDescriptor(AnalyzerExceptionDiagnosticId, title, description, messageFormat); return Diagnostic.Create(descriptor, Location.None, messageArguments); } + private static string CreateDiagnosticDescription(AnalysisContextInfo? info, Exception e) + { + if (info == null) + { + return e.CreateDiagnosticDescription(); + } + + return string.Join(Environment.NewLine, + string.Format(CodeAnalysisResources.ExceptionContext, info?.GetContext()), e.CreateDiagnosticDescription()); + } + internal static Diagnostic CreateDriverExceptionDiagnostic(Exception e) { var title = CodeAnalysisResources.AnalyzerDriverFailure; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs index ed77c0fee34..fa9c7fe6246 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs @@ -164,7 +164,7 @@ public async Task IsConcurrentAnalyzerAsync(DiagnosticAnalyzer analyzer, A public async Task GetGeneratedCodeAnalysisFlagsAsync(DiagnosticAnalyzer analyzer, AnalyzerExecutor analyzerExecutor) { var sessionScope = await GetSessionAnalysisScopeAsync(analyzer, analyzerExecutor).ConfigureAwait(false); - return sessionScope.GetGeneratedCodeAnalysisFlags(analyzer); + return sessionScope.GetGeneratedCodeAnalysisFlags(analyzer); } /// -- GitLab