Skip to content

Commit 19f62c4

Browse files
committed
Merge pull request #673 from mavasani/AnalyzerSpecificDiagnostics
This change addresses #259: below issues related to diagnostics generated for analyzer exceptions from third party analyzers. 1.Suppression of duplicate exception diagnostics: Current mechanism did the suppression in SuppressMessageState based on unique reported messages. This is obviously incorrect as an exception diagnostic will be reported non-suppressed and suppressed on subsequent queries to SuppressMessageState.IsDiagnosticSuppressed. 2.The IDE diagnostic service has multiple layers where document/project diagnostics are filtered and these analyzer exception diagnostics were getting dropped at various places. So this change moves the exception diagnostics generation + reporting out of the regular analyzer diagnostic pipeline and in line with analyzer load failure diagnostics reporting in VS: 1.Add an event handler to AnalyzerDriverHelper to report analyzer exception diagnostics to interested clients. 2.Listen to these diagnostic events in IDE diagnostic service and wrap them with relevant workspace/project argument and generate updated events. 3.Add an AbstractHostDiagnosticUpdateSource in Features layer to listen and report analyzer exception diagnostic events from diagnostic service. Additionally, removal of an analyzer reference in workspace will clean up the diagnostics for the analyzers belonging to that analyzer reference. 4.Listen to exception diagnostic events in command line compiler and report as regular diagnostics. Added typw AbstractHostDiagnosticUpdateSource can be extended in future to report other kind of host diagnostics which are not related to a project/document/analyzer.
2 parents 0db1e4e + 0faaab4 commit 19f62c4

File tree

40 files changed

+666
-186
lines changed

40 files changed

+666
-186
lines changed

src/Compilers/Core/AnalyzerDriver/AnalyzerDriver.projitems

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</PropertyGroup>
1111
<ItemGroup>
1212
<Compile Include="$(MSBuildThisFileDirectory)AnalyzerDriverHelper.cs" />
13+
<Compile Include="$(MSBuildThisFileDirectory)AnalyzerExceptionDiagnosticArgs.cs" />
1314
<Compile Include="$(MSBuildThisFileDirectory)AnalyzerManager.cs" />
1415
<Compile Include="$(MSBuildThisFileDirectory)DeclarationComputer.cs" />
1516
<Compile Include="$(MSBuildThisFileDirectory)DeclarationInfo.cs" />

src/Compilers/Core/AnalyzerDriver/AnalyzerDriverHelper.cs

+74-23
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,27 @@ internal class AnalyzerDriverHelper
1616
private const string DiagnosticId = "AD0001";
1717
private const string DiagnosticCategory = "Compiler";
1818

19+
private static event EventHandler<AnalyzerExceptionDiagnosticArgs> AnalyzerExceptionDiagnostic;
20+
1921
/// <summary>
2022
/// Executes the <see cref="DiagnosticAnalyzer.Initialize(AnalysisContext)"/> for the given analyzer.
2123
/// </summary>
2224
/// <param name="analyzer">Analyzer to get session wide analyzer actions.</param>
2325
/// <param name="sessionScope">Session scope to store register session wide analyzer actions.</param>
24-
/// <param name="addDiagnostic">Delegate to add diagnostics.</param>
2526
/// <param name="continueOnAnalyzerException">Predicate to decide if exceptions from the action should be handled or not.</param>
2627
/// <param name="cancellationToken">Cancellation token.</param>
2728
/// <remarks>
2829
/// Note that this API doesn't execute any <see cref="CompilationStartAnalyzerAction"/> registered by the Initialize invocation.
29-
/// Use <see cref="ExecuteCompilationStartActions(ImmutableArray{CompilationStartAnalyzerAction}, HostCompilationStartAnalysisScope, Compilation, AnalyzerOptions, Action{Diagnostic}, Func{Exception, DiagnosticAnalyzer, bool}, CancellationToken)"/> API
30+
/// Use <see cref="ExecuteCompilationStartActions(ImmutableArray{CompilationStartAnalyzerAction}, HostCompilationStartAnalysisScope, Compilation, AnalyzerOptions, Func{Exception, DiagnosticAnalyzer, bool}, CancellationToken)"/> API
3031
/// to get execute these actions to get the per-compilation analyzer actions.
3132
/// </remarks>
3233
public static void ExecuteInitializeMethod(
3334
DiagnosticAnalyzer analyzer,
3435
HostSessionStartAnalysisScope sessionScope,
35-
Action<Diagnostic> addDiagnostic,
3636
Func<Exception, DiagnosticAnalyzer, bool> continueOnAnalyzerException,
3737
CancellationToken cancellationToken)
3838
{
39-
ExecuteAndCatchIfThrows(analyzer, addDiagnostic, continueOnAnalyzerException, () =>
39+
ExecuteAndCatchIfThrows(analyzer, continueOnAnalyzerException, () =>
4040
{
4141
// The Initialize method should be run asynchronously in case it is not well behaved, e.g. does not terminate.
4242
analyzer.Initialize(new AnalyzerAnalysisContext(analyzer, sessionScope));
@@ -50,22 +50,20 @@ public static void ExecuteInitializeMethod(
5050
/// <param name="compilationScope">Compilation scope to store the analyzer actions.</param>
5151
/// <param name="compilation">Compilation to be used in the analysis.</param>
5252
/// <param name="analyzerOptions">Analyzer options.</param>
53-
/// <param name="addDiagnostic">Delegate to add diagnostics.</param>
5453
/// <param name="continueOnAnalyzerException">Predicate to decide if exceptions from the action should be handled or not.</param>
5554
/// <param name="cancellationToken">Cancellation token.</param>
5655
public static void ExecuteCompilationStartActions(
5756
ImmutableArray<CompilationStartAnalyzerAction> actions,
5857
HostCompilationStartAnalysisScope compilationScope,
5958
Compilation compilation,
6059
AnalyzerOptions analyzerOptions,
61-
Action<Diagnostic> addDiagnostic,
6260
Func<Exception, DiagnosticAnalyzer, bool> continueOnAnalyzerException,
6361
CancellationToken cancellationToken)
6462
{
6563
foreach (var startAction in actions)
6664
{
6765
cancellationToken.ThrowIfCancellationRequested();
68-
ExecuteAndCatchIfThrows(startAction.Analyzer, addDiagnostic, continueOnAnalyzerException, () =>
66+
ExecuteAndCatchIfThrows(startAction.Analyzer, continueOnAnalyzerException, () =>
6967
{
7068
startAction.Action(new AnalyzerCompilationStartAnalysisContext(startAction.Analyzer, compilationScope, compilation, analyzerOptions, cancellationToken));
7169
}, cancellationToken);
@@ -94,7 +92,7 @@ public static void ExecuteCompilationEndActions(
9492
foreach (var endAction in actions.CompilationEndActions)
9593
{
9694
cancellationToken.ThrowIfCancellationRequested();
97-
ExecuteAndCatchIfThrows(endAction.Analyzer, addDiagnostic, continueOnAnalyzerException, () =>
95+
ExecuteAndCatchIfThrows(endAction.Analyzer, continueOnAnalyzerException, () =>
9896
{
9997
var context = new CompilationEndAnalysisContext(compilation, analyzerOptions, addDiagnostic, cancellationToken);
10098
endAction.Action(context);
@@ -133,7 +131,7 @@ public static void ExecuteSymbolActions(
133131
var symbolContext = new SymbolAnalysisContext(symbol, compilation, analyzerOptions, addDiagnostic, cancellationToken);
134132

135133
// Catch Exception from action.
136-
ExecuteAndCatchIfThrows(symbolAction.Analyzer, addDiagnostic, continueOnAnalyzerException, () => action(symbolContext), cancellationToken);
134+
ExecuteAndCatchIfThrows(symbolAction.Analyzer, continueOnAnalyzerException, () => action(symbolContext), cancellationToken);
137135
}
138136
}
139137
}
@@ -161,7 +159,7 @@ public static void ExecuteSemanticModelActions(
161159
cancellationToken.ThrowIfCancellationRequested();
162160

163161
// Catch Exception from action.
164-
ExecuteAndCatchIfThrows(semanticModelAction.Analyzer, addDiagnostic, continueOnAnalyzerException, () =>
162+
ExecuteAndCatchIfThrows(semanticModelAction.Analyzer, continueOnAnalyzerException, () =>
165163
{
166164
var context = new SemanticModelAnalysisContext(semanticModel, analyzerOptions, addDiagnostic, cancellationToken);
167165
semanticModelAction.Action(context);
@@ -191,7 +189,7 @@ public static void ExecuteSyntaxTreeActions(
191189
cancellationToken.ThrowIfCancellationRequested();
192190

193191
// Catch Exception from action.
194-
ExecuteAndCatchIfThrows(syntaxTreeAction.Analyzer, addDiagnostic, continueOnAnalyzerException, () =>
192+
ExecuteAndCatchIfThrows(syntaxTreeAction.Analyzer, continueOnAnalyzerException, () =>
195193
{
196194
var context = new SyntaxTreeAnalysisContext(syntaxTree, analyzerOptions, addDiagnostic, cancellationToken);
197195
syntaxTreeAction.Action(context);
@@ -251,7 +249,7 @@ internal static void ExecuteSyntaxNodeAction(
251249
var syntaxNodeContext = new SyntaxNodeAnalysisContext(node, semanticModel, analyzerOptions, addDiagnostic, cancellationToken);
252250

253251
// Catch Exception from action.
254-
ExecuteAndCatchIfThrows(analyzer, addDiagnostic, continueOnAnalyzerException, () => syntaxNodeAction(syntaxNodeContext), cancellationToken);
252+
ExecuteAndCatchIfThrows(analyzer, continueOnAnalyzerException, () => syntaxNodeAction(syntaxNodeContext), cancellationToken);
255253
}
256254

257255
/// <summary>
@@ -329,7 +327,7 @@ internal static void ExecuteCodeBlockActions<TLanguageKindEnum>(
329327
foreach (var da in codeBlockStartActions)
330328
{
331329
// Catch Exception from the start action.
332-
ExecuteAndCatchIfThrows(da.Analyzer, addDiagnostic, continueOnAnalyzerException, () =>
330+
ExecuteAndCatchIfThrows(da.Analyzer, continueOnAnalyzerException, () =>
333331
{
334332
HostCodeBlockStartAnalysisScope<TLanguageKindEnum> codeBlockScope = new HostCodeBlockStartAnalysisScope<TLanguageKindEnum>();
335333
CodeBlockStartAnalysisContext<TLanguageKindEnum> blockStartContext = new AnalyzerCodeBlockStartAnalysisContext<TLanguageKindEnum>(da.Analyzer, codeBlockScope, declaredNode, declaredSymbol, semanticModel, analyzerOptions, cancellationToken);
@@ -353,7 +351,7 @@ internal static void ExecuteCodeBlockActions<TLanguageKindEnum>(
353351
foreach (var a in endedActions)
354352
{
355353
// Catch Exception from a.OnCodeBlockEnded
356-
ExecuteAndCatchIfThrows(a.Analyzer, addDiagnostic, continueOnAnalyzerException, () => a.Action(new CodeBlockEndAnalysisContext(declaredNode, declaredSymbol, semanticModel, analyzerOptions, addDiagnostic, cancellationToken)), cancellationToken);
354+
ExecuteAndCatchIfThrows(a.Analyzer, continueOnAnalyzerException, () => a.Action(new CodeBlockEndAnalysisContext(declaredNode, declaredSymbol, semanticModel, analyzerOptions, addDiagnostic, cancellationToken)), cancellationToken);
357355
}
358356

359357
endedActions.Free();
@@ -434,7 +432,7 @@ internal static bool CanHaveExecutableCodeBlock(ISymbol symbol)
434432
}
435433
}
436434

437-
internal static void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Action<Diagnostic> addDiagnostic, Func<Exception, DiagnosticAnalyzer, bool> continueOnAnalyzerException, Action analyze, CancellationToken cancellationToken)
435+
internal static void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Func<Exception, DiagnosticAnalyzer, bool> continueOnAnalyzerException, Action analyze, CancellationToken cancellationToken)
438436
{
439437
try
440438
{
@@ -444,14 +442,18 @@ internal static void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Action
444442
{
445443
if (oce.CancellationToken != cancellationToken)
446444
{
447-
// Create a info diagnostic saying that the analyzer failed
448-
addDiagnostic(GetAnalyzerDiagnostic(analyzer, oce));
445+
// Raise an event with a diagnostic for analyzer exception
446+
var diagnostic = GetAnalyzerDiagnostic(analyzer, oce);
447+
var args = new AnalyzerExceptionDiagnosticArgs(analyzer, diagnostic);
448+
AnalyzerExceptionDiagnostic?.Invoke(analyze, args);
449449
}
450450
}
451451
catch (Exception e) when (continueOnAnalyzerException(e, analyzer))
452452
{
453-
// Create a info diagnostic saying that the analyzer failed
454-
addDiagnostic(GetAnalyzerDiagnostic(analyzer, e));
453+
// Raise an event with a diagnostic for analyzer exception
454+
var diagnostic = GetAnalyzerDiagnostic(analyzer, e);
455+
var args = new AnalyzerExceptionDiagnosticArgs(analyzer, diagnostic);
456+
AnalyzerExceptionDiagnostic?.Invoke(analyze, args);
455457
}
456458
}
457459

@@ -471,11 +473,13 @@ internal static DiagnosticDescriptor GetDiagnosticDescriptor(string analyzerName
471473
customTags: WellKnownDiagnosticTags.AnalyzerException);
472474
}
473475

474-
internal static bool IsAnalyzerExceptionDiagnostic(string diagnosticId, IEnumerable<string> customTags)
476+
internal static bool IsAnalyzerExceptionDiagnostic(Diagnostic diagnostic)
475477
{
476-
if (diagnosticId == DiagnosticId)
478+
if (diagnostic.Id == DiagnosticId)
477479
{
478-
foreach (var tag in customTags)
480+
#pragma warning disable RS0013 // Its ok to realize the Descriptor for analyzer exception diagnostics, which are descriptor based and also rare.
481+
foreach (var tag in diagnostic.Descriptor.CustomTags)
482+
#pragma warning restore RS0013
479483
{
480484
if (tag == WellKnownDiagnosticTags.AnalyzerException)
481485
{
@@ -484,7 +488,54 @@ internal static bool IsAnalyzerExceptionDiagnostic(string diagnosticId, IEnumera
484488
}
485489
}
486490

487-
return false;
491+
return false;
492+
}
493+
494+
internal static EventHandler<AnalyzerExceptionDiagnosticArgs> RegisterAnalyzerExceptionDiagnosticHandler(ImmutableArray<DiagnosticAnalyzer> analyzers, Func<Diagnostic, bool> addAnalyzerExceptionDiagnostic)
495+
{
496+
Action<object, AnalyzerExceptionDiagnosticArgs> onAnalyzerExceptionDiagnostic =
497+
(sender, args) => addAnalyzerExceptionDiagnostic(args.Diagnostic);
498+
return RegisterAnalyzerExceptionDiagnosticHandler(analyzers, onAnalyzerExceptionDiagnostic);
499+
}
500+
501+
internal static EventHandler<AnalyzerExceptionDiagnosticArgs> RegisterAnalyzerExceptionDiagnosticHandler(ImmutableArray<DiagnosticAnalyzer> analyzers, Action<object, AnalyzerExceptionDiagnosticArgs> onAnayzerExceptionDiagnostic)
502+
{
503+
EventHandler<AnalyzerExceptionDiagnosticArgs> handler = (sender, args) =>
504+
{
505+
if (analyzers.Contains(args.FaultedAnalyzer))
506+
{
507+
onAnayzerExceptionDiagnostic(sender, args);
508+
}
509+
};
510+
511+
AnalyzerExceptionDiagnostic += handler;
512+
return handler;
513+
}
514+
515+
internal static EventHandler<AnalyzerExceptionDiagnosticArgs> RegisterAnalyzerExceptionDiagnosticHandler(DiagnosticAnalyzer analyzer, Func<Diagnostic, bool> addAnalyzerExceptionDiagnostic)
516+
{
517+
Action<object, AnalyzerExceptionDiagnosticArgs> onAnalyzerExceptionDiagnostic =
518+
(sender, args) => addAnalyzerExceptionDiagnostic(args.Diagnostic);
519+
return RegisterAnalyzerExceptionDiagnosticHandler(analyzer, onAnalyzerExceptionDiagnostic);
520+
}
521+
522+
internal static EventHandler<AnalyzerExceptionDiagnosticArgs> RegisterAnalyzerExceptionDiagnosticHandler(DiagnosticAnalyzer analyzer, Action<object, AnalyzerExceptionDiagnosticArgs> onAnayzerExceptionDiagnostic)
523+
{
524+
EventHandler<AnalyzerExceptionDiagnosticArgs> handler = (sender, args) =>
525+
{
526+
if (analyzer == args.FaultedAnalyzer)
527+
{
528+
onAnayzerExceptionDiagnostic(sender, args);
529+
}
530+
};
531+
532+
AnalyzerExceptionDiagnostic += handler;
533+
return handler;
534+
}
535+
536+
internal static void UnregisterAnalyzerExceptionDiagnosticHandler(EventHandler<AnalyzerExceptionDiagnosticArgs> handler)
537+
{
538+
AnalyzerExceptionDiagnostic -= handler;
488539
}
489540
}
490541
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
5+
namespace Microsoft.CodeAnalysis.Diagnostics
6+
{
7+
internal class AnalyzerExceptionDiagnosticArgs : EventArgs
8+
{
9+
public readonly Diagnostic Diagnostic;
10+
public readonly DiagnosticAnalyzer FaultedAnalyzer;
11+
12+
public AnalyzerExceptionDiagnosticArgs(DiagnosticAnalyzer analyzer, Diagnostic diagnostic)
13+
{
14+
this.FaultedAnalyzer = analyzer;
15+
this.Diagnostic = diagnostic;
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)