提交 909396b0 编写于 作者: M Manish Vasani

Merge pull request #2818 from mavasani/AnalyzerPerfReport

Add a new command line compiler switch "/reportanalyzer" to report analyzer execution times
......@@ -4407,6 +4407,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
diagnostics.
/errorlog:<file> Specify a file to log all compiler and analyzer
diagnostics.
/reportanalyzer Report additional analyzer information, such as
execution time.
- LANGUAGE -
/checked[+|-] Generate overflow checks
......
......@@ -103,6 +103,7 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
List<string> features = new List<string>();
string runtimeMetadataVersion = null;
bool errorEndLocation = false;
bool reportAnalyzer = false;
CultureInfo preferredUILang = null;
string touchedFilesPath = null;
var sqmSessionGuid = Guid.Empty;
......@@ -892,6 +893,10 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
errorEndLocation = true;
continue;
case "reportanalyzer":
reportAnalyzer = true;
continue;
case "nostdlib":
case "nostdlib+":
if (value != null)
......@@ -1111,7 +1116,8 @@ public new CSharpCommandLineArguments Parse(IEnumerable<string> args, string bas
PrintFullPaths = printFullPaths,
ShouldIncludeErrorEndLocation = errorEndLocation,
PreferredUILang = preferredUILang,
SqmSessionGuid = sqmSessionGuid
SqmSessionGuid = sqmSessionGuid,
ReportAnalyzer = reportAnalyzer
};
}
......
......@@ -6563,6 +6563,32 @@ public void ErrorLineEnd()
Assert.Equal(stringStart, text.Substring(0, stringStart.Length));
}
[Fact]
public void ReportAnalyzer()
{
var parsedArgs1 = DefaultParse(new[] { "a.cs", "/reportanalyzer" }, _baseDirectory);
Assert.True(parsedArgs1.ReportAnalyzer);
var parsedArgs2 = DefaultParse(new[] { "a.cs", "" }, _baseDirectory);
Assert.False(parsedArgs2.ReportAnalyzer);
}
[Fact]
public void ReportAnalyzerOutput()
{
var srcFile = Temp.CreateFile().WriteAllText(@"class C {}");
var srcDirectory = Path.GetDirectoryName(srcFile.Path);
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var csc = new MockCSharpCompiler(null, srcDirectory, new[] { "/reportanalyzer", "/t:library", "/a:" + Assembly.GetExecutingAssembly().Location, srcFile.Path });
var exitCode = csc.Run(outWriter);
Assert.Equal(0, exitCode);
var output = outWriter.ToString();
Assert.Contains(CodeAnalysisResources.AnalyzerExecutionTimeColumnHeader, output, StringComparison.Ordinal);
Assert.Contains(new WarningDiagnosticAnalyzer().ToString(), output, StringComparison.Ordinal);
CleanupAllGeneratedFiles(srcFile.Path);
}
[Fact]
public void ErrorPathsFromLineDirectives()
{
......
......@@ -8,6 +8,7 @@
using System.Threading;
using Microsoft.CodeAnalysis.Collections;
using Roslyn.Utilities;
using System.Collections.Concurrent;
namespace Microsoft.CodeAnalysis.Diagnostics
{
......@@ -26,7 +27,8 @@ internal class AnalyzerExecutor
private readonly AnalyzerManager _analyzerManager;
private readonly Func<DiagnosticAnalyzer, bool> _isCompilerAnalyzer;
private readonly ImmutableDictionary<DiagnosticAnalyzer, object> _singleThreadedAnalyzerToGateMap;
private readonly ConcurrentDictionary<DiagnosticAnalyzer, TimeSpan> _analyzerExecutionTimeMapOpt;
private readonly CancellationToken _cancellationToken;
/// <summary>
......@@ -45,6 +47,7 @@ internal class AnalyzerExecutor
/// <param name="singleThreadedAnalyzerToGateMap">Map from non-thread safe analyzers to unique gate objects.
/// All analyzer callbacks for these analyzers will be guarded with a lock on the gate.
/// </param>
/// <param name="logExecutionTime">Flag indicating whether we need to log analyzer execution time.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static AnalyzerExecutor Create(
Compilation compilation,
......@@ -54,9 +57,10 @@ internal class AnalyzerExecutor
Func<DiagnosticAnalyzer, bool> isCompilerAnalyzer,
AnalyzerManager analyzerManager,
ImmutableDictionary<DiagnosticAnalyzer, object> singleThreadedAnalyzerToGateMap = default(ImmutableDictionary<DiagnosticAnalyzer, object>),
bool logExecutionTime = false,
CancellationToken cancellationToken = default(CancellationToken))
{
return new AnalyzerExecutor(compilation, analyzerOptions, addDiagnostic, onAnalyzerException, isCompilerAnalyzer, analyzerManager, singleThreadedAnalyzerToGateMap, cancellationToken);
return new AnalyzerExecutor(compilation, analyzerOptions, addDiagnostic, onAnalyzerException, isCompilerAnalyzer, analyzerManager, singleThreadedAnalyzerToGateMap, logExecutionTime, cancellationToken);
}
/// <summary>
......@@ -67,11 +71,13 @@ internal class AnalyzerExecutor
/// Delegate can do custom tasks such as report the given analyzer exception diagnostic, report a non-fatal watson for the exception, etc.
/// </param>
/// <param name="analyzerManager">Analyzer manager to fetch supported diagnostics.</param>
/// <param name="logExecutionTime">Flag indicating whether we need to log analyzer execution time.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static AnalyzerExecutor CreateForSupportedDiagnostics(
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException,
AnalyzerManager analyzerManager,
CancellationToken cancellationToken)
bool logExecutionTime = false,
CancellationToken cancellationToken = default(CancellationToken))
{
return new AnalyzerExecutor(
compilation: null,
......@@ -81,6 +87,7 @@ internal class AnalyzerExecutor
singleThreadedAnalyzerToGateMap: ImmutableDictionary<DiagnosticAnalyzer, object>.Empty,
onAnalyzerException: onAnalyzerException,
analyzerManager: analyzerManager,
logExecutionTime: logExecutionTime,
cancellationToken: cancellationToken);
}
......@@ -92,6 +99,7 @@ internal class AnalyzerExecutor
Func<DiagnosticAnalyzer, bool> isCompilerAnalyzer,
AnalyzerManager analyzerManager,
ImmutableDictionary<DiagnosticAnalyzer, object> singleThreadedAnalyzerToGateMap,
bool logExecutionTime,
CancellationToken cancellationToken)
{
_compilation = compilation;
......@@ -101,6 +109,7 @@ internal class AnalyzerExecutor
_isCompilerAnalyzer = isCompilerAnalyzer;
_analyzerManager = analyzerManager;
_singleThreadedAnalyzerToGateMap = singleThreadedAnalyzerToGateMap ?? ImmutableDictionary<DiagnosticAnalyzer, object>.Empty;
_analyzerExecutionTimeMapOpt = logExecutionTime ? new ConcurrentDictionary<DiagnosticAnalyzer, TimeSpan>() : null;
_cancellationToken = cancellationToken;
}
......@@ -108,6 +117,7 @@ internal class AnalyzerExecutor
internal AnalyzerOptions AnalyzerOptions => _analyzerOptions;
internal CancellationToken CancellationToken => _cancellationToken;
internal Action<Exception, DiagnosticAnalyzer, Diagnostic> OnAnalyzerException => _onAnalyzerException;
internal ImmutableDictionary<DiagnosticAnalyzer, TimeSpan> AnalyzerExecutionTimes => _analyzerExecutionTimeMapOpt.ToImmutableDictionary();
/// <summary>
/// Executes the <see cref="DiagnosticAnalyzer.Initialize(AnalysisContext)"/> for the given analyzer.
......@@ -481,30 +491,39 @@ internal void ExecuteAndCatchIfThrows(DiagnosticAnalyzer analyzer, Action analyz
{
lock(gate)
{
ExecuteAndCatchIfThrows(analyzer, analyze, _onAnalyzerException, _cancellationToken);
ExecuteAndCatchIfThrows_NoLock(analyzer, analyze);
}
}
else
{
ExecuteAndCatchIfThrows(analyzer, analyze, _onAnalyzerException, _cancellationToken);
ExecuteAndCatchIfThrows_NoLock(analyzer, analyze);
}
}
private static void ExecuteAndCatchIfThrows(
DiagnosticAnalyzer analyzer,
Action analyze,
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException,
CancellationToken cancellationToken)
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))
catch (Exception e) when (!IsCanceled(e, _cancellationToken))
{
// Diagnostic for analyzer exception.
var diagnostic = GetAnalyzerExceptionDiagnostic(analyzer, e);
onAnalyzerException(e, analyzer, diagnostic);
_onAnalyzerException(e, analyzer, diagnostic);
}
}
......
......@@ -276,7 +276,7 @@ private static void TestDescriptorIsExceptionSafeCore(DiagnosticDescriptor descr
var analyzer = new MyAnalyzer(descriptor);
var exceptionDiagnostics = new List<Diagnostic>();
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException = (ex, a, diag) => exceptionDiagnostics.Add(diag);
var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance, CancellationToken.None);
var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance);
var descriptors = AnalyzerManager.Instance.GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor);
Assert.Equal(1, descriptors.Length);
......
......@@ -585,8 +585,7 @@ protected internal virtual void AddResponseFileCommands(CommandLineBuilderExtens
commandLine.AppendSwitchIfNotNull("/ruleset:", this.CodeAnalysisRuleSet);
commandLine.AppendSwitchIfNotNull("/errorlog:", this.ErrorLog);
commandLine.AppendSwitchIfNotNull("/subsystemversion:", this.SubsystemVersion);
// TODO: uncomment the below line once "/reportanalyzer" switch is added to compiler.
//commandLine.AppendWhenTrue("/reportanalyzer", this._store, "ReportAnalyzer");
commandLine.AppendWhenTrue("/reportanalyzer", this._store, "ReportAnalyzer");
// If the strings "LogicalName" or "Access" ever change, make sure to search/replace everywhere in vsproject.
commandLine.AppendSwitchIfNotNull("/resource:", this.Resources, new string[] { "LogicalName", "Access" });
commandLine.AppendSwitchIfNotNull("/target:", this.TargetType);
......
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.0
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
......@@ -70,6 +70,33 @@ internal class CodeAnalysisResources {
}
}
/// <summary>
/// Looks up a localized string similar to Time (s).
/// </summary>
internal static string AnalyzerExecutionTimeColumnHeader {
get {
return ResourceManager.GetString("AnalyzerExecutionTimeColumnHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Analyzer.
/// </summary>
internal static string AnalyzerNameColumnHeader {
get {
return ResourceManager.GetString("AnalyzerNameColumnHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Total analyzer execution time: {0} seconds..
/// </summary>
internal static string AnalyzerTotalExecutionTime {
get {
return ResourceManager.GetString("AnalyzerTotalExecutionTime", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Argument cannot be empty..
/// </summary>
......@@ -665,6 +692,15 @@ internal class CodeAnalysisResources {
}
}
/// <summary>
/// Looks up a localized string similar to NOTE: Elapsed time may be less than analyzer execution time because analyzers can run concurrently..
/// </summary>
internal static string MultithreadedAnalyzerExecutionNote {
get {
return ResourceManager.GetString("MultithreadedAnalyzerExecutionNote", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name cannot be empty..
/// </summary>
......
......@@ -457,4 +457,16 @@
<data name="MissingKeepAlive" xml:space="preserve">
<value>Missing argument for '/keepalive' option.</value>
</data>
<data name="AnalyzerTotalExecutionTime" xml:space="preserve">
<value>Total analyzer execution time: {0} seconds.</value>
</data>
<data name="MultithreadedAnalyzerExecutionNote" xml:space="preserve">
<value>NOTE: Elapsed time may be less than analyzer execution time because analyzers can run concurrently.</value>
</data>
<data name="AnalyzerExecutionTimeColumnHeader" xml:space="preserve">
<value>Time (s)</value>
</data>
<data name="AnalyzerNameColumnHeader" xml:space="preserve">
<value>Analyzer</value>
</data>
</root>
\ No newline at end of file
......@@ -115,6 +115,11 @@ public abstract class CommandLineArguments
/// </summary>
public ImmutableArray<CommandLineSourceFile> AdditionalFiles { get; internal set; }
/// <value>
/// Report additional information related to analyzers, such as analyzer execution time.
/// </value>
public bool ReportAnalyzer { get; internal set; }
/// <summary>
/// If true, prepend the command line header logo during
/// <see cref="CommonCompiler.Run"/>.
......
......@@ -355,6 +355,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat
CancellationTokenSource analyzerCts = null;
AnalyzerManager analyzerManager = null;
AnalyzerDriver analyzerDriver = null;
try
{
Func<ImmutableArray<Diagnostic>> getAnalyzerDiagnostics = null;
......@@ -365,7 +366,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat
var analyzerExceptionDiagnostics = new ConcurrentSet<Diagnostic>();
Action<Diagnostic> addExceptionDiagnostic = diagnostic => analyzerExceptionDiagnostics.Add(diagnostic);
var analyzerOptions = new AnalyzerOptions(ImmutableArray<AdditionalText>.CastUp(additionalTextFiles));
var analyzerDriver = AnalyzerDriver.Create(compilation, analyzers, analyzerOptions, analyzerManager, addExceptionDiagnostic, out compilation, analyzerCts.Token);
analyzerDriver = AnalyzerDriver.Create(compilation, analyzers, analyzerOptions, analyzerManager, addExceptionDiagnostic, Arguments.ReportAnalyzer, out compilation, analyzerCts.Token);
getAnalyzerDiagnostics = () =>
{
......@@ -525,8 +526,16 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat
{
analyzerCts.Cancel();
// Clear cached analyzer descriptors and unregister exception handlers hooked up to the LocalizableString fields of the associated descriptors.
analyzerManager.ClearAnalyzerState(analyzers);
if (analyzerManager != null)
{
// Clear cached analyzer descriptors and unregister exception handlers hooked up to the LocalizableString fields of the associated descriptors.
analyzerManager.ClearAnalyzerState(analyzers);
}
if (Arguments.ReportAnalyzer && analyzerDriver != null && compilation != null)
{
ReportAnalyzerExecutionTime(consoleOutput, analyzerDriver, Culture, compilation.Options.ConcurrentBuild);
}
}
}
......@@ -545,6 +554,71 @@ private ImmutableArray<AdditionalTextFile> ResolveAdditionalFilesFromArguments(L
return builder.ToImmutableArray();
}
private static void ReportAnalyzerExecutionTime(TextWriter consoleOutput, AnalyzerDriver analyzerDriver, CultureInfo culture, bool isConcurrentBuild)
{
Debug.Assert(analyzerDriver.AnalyzerExecutionTimes != null);
if (analyzerDriver.AnalyzerExecutionTimes.IsEmpty)
{
return;
}
var totalAnalyzerExecutionTime = analyzerDriver.AnalyzerExecutionTimes.Sum(kvp => kvp.Value.TotalSeconds);
Func<double, string> getFormattedTime = d => d.ToString("##0.000", culture);
consoleOutput.WriteLine();
consoleOutput.WriteLine(string.Format(CodeAnalysisResources.AnalyzerTotalExecutionTime, getFormattedTime(totalAnalyzerExecutionTime)));
if (isConcurrentBuild)
{
consoleOutput.WriteLine(CodeAnalysisResources.MultithreadedAnalyzerExecutionNote);
}
var analyzersByAssembly = analyzerDriver.AnalyzerExecutionTimes
.GroupBy(kvp => kvp.Key.GetType().GetTypeInfo().Assembly)
.OrderByDescending(kvp => kvp.Sum(entry => entry.Value.Ticks));
consoleOutput.WriteLine();
getFormattedTime = d => d < 0.001 ?
string.Format(culture, "{0,8:<0.000}", 0.001) :
string.Format(culture, "{0,8:##0.000}", d);
Func<int, string> getFormattedPercentage = i => string.Format("{0,5}", i < 1 ? "<1" : i.ToString());
Func<string, string> getFormattedAnalyzerName = s => " " + s;
// Table header
var analyzerTimeColumn = string.Format("{0,8}", CodeAnalysisResources.AnalyzerExecutionTimeColumnHeader);
var analyzerPercentageColumn = string.Format("{0,5}", "%");
var analyzerNameColumn = getFormattedAnalyzerName(CodeAnalysisResources.AnalyzerNameColumnHeader);
consoleOutput.WriteLine(analyzerTimeColumn + analyzerPercentageColumn + analyzerNameColumn);
// Table rows grouped by assembly.
foreach (var analyzerGroup in analyzersByAssembly)
{
var executionTime = analyzerGroup.Sum(kvp => kvp.Value.TotalSeconds);
var percentage = (int)(executionTime * 100 / totalAnalyzerExecutionTime);
analyzerTimeColumn = getFormattedTime(executionTime);
analyzerPercentageColumn = getFormattedPercentage(percentage);
analyzerNameColumn = getFormattedAnalyzerName(analyzerGroup.Key.FullName);
consoleOutput.WriteLine(analyzerTimeColumn + analyzerPercentageColumn + analyzerNameColumn);
// Rows for each diagnostic analyzer in the assembly.
foreach (var kvp in analyzerGroup.OrderByDescending(kvp => kvp.Value))
{
executionTime = kvp.Value.TotalSeconds;
percentage = (int)(executionTime * 100 / totalAnalyzerExecutionTime);
analyzerTimeColumn = getFormattedTime(executionTime);
analyzerPercentageColumn = getFormattedPercentage(percentage);
analyzerNameColumn = getFormattedAnalyzerName(" " + kvp.Key.ToString());
consoleOutput.WriteLine(analyzerTimeColumn + analyzerPercentageColumn + analyzerNameColumn);
}
consoleOutput.WriteLine();
}
}
private void GenerateSqmData(CompilationOptions compilationOptions, ImmutableArray<Diagnostic> diagnostics)
{
// Generate SQM data file for Compilers
......
......@@ -66,7 +66,7 @@ internal abstract class AnalyzerDriver : IDisposable
/// Finally, it initializes and starts the <see cref="_primaryTask"/> for the driver.
/// </summary>
/// <remarks>
/// NOTE: This method must only be invoked from <see cref="AnalyzerDriver.Create(Compilation, ImmutableArray{DiagnosticAnalyzer}, AnalyzerOptions, AnalyzerManager, Action{Diagnostic}, out Compilation, CancellationToken)"/>.
/// NOTE: This method must only be invoked from <see cref="AnalyzerDriver.Create(Compilation, ImmutableArray{DiagnosticAnalyzer}, AnalyzerOptions, AnalyzerManager, Action{Diagnostic}, Boolean, out Compilation, CancellationToken)"/>.
/// </remarks>
private void Initialize(Compilation comp, AnalyzerExecutor analyzerExecutor, CancellationToken cancellationToken)
{
......@@ -144,6 +144,7 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
/// <param name="options">Options that are passed to analyzers.</param>
/// <param name="analyzerManager">AnalyzerManager to manage analyzers for the lifetime of analyzer host.</param>
/// <param name="addExceptionDiagnostic">Delegate to add diagnostics generated for exceptions from third party analyzers.</param>
/// <param name="reportAnalyzer">Report additional information related to analyzers, such as analyzer execution time.</param>
/// <param name="newCompilation">The new compilation with the analyzer driver attached.</param>
/// <param name="cancellationToken">A cancellation token that can be used to abort analysis.</param>
/// <returns>A newly created analyzer driver</returns>
......@@ -156,14 +157,15 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
ImmutableArray<DiagnosticAnalyzer> analyzers,
AnalyzerOptions options,
AnalyzerManager analyzerManager,
Action<Diagnostic> addExceptionDiagnostic,
Action<Diagnostic> addExceptionDiagnostic,
bool reportAnalyzer,
out Compilation newCompilation,
CancellationToken cancellationToken)
{
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException =
(ex, analyzer, diagnostic) => addExceptionDiagnostic?.Invoke(diagnostic);
return Create(compilation, analyzers, options, analyzerManager, onAnalyzerException, out newCompilation, cancellationToken: cancellationToken);
return Create(compilation, analyzers, options, analyzerManager, onAnalyzerException, reportAnalyzer, out newCompilation, cancellationToken: cancellationToken);
}
// internal for testing purposes
......@@ -173,6 +175,7 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
AnalyzerOptions options,
AnalyzerManager analyzerManager,
Action<Exception, DiagnosticAnalyzer, Diagnostic> onAnalyzerException,
bool reportAnalyzer,
out Compilation newCompilation,
CancellationToken cancellationToken)
{
......@@ -198,8 +201,15 @@ private Task ExecuteSyntaxTreeActions(CancellationToken cancellationToken)
// 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");
}
var analyzerExecutor = AnalyzerExecutor.Create(newCompilation, options, addDiagnostic, newOnAnalyzerException, IsCompilerAnalyzer, analyzerManager, singleThreadedAnalyzerToGateMap, cancellationToken);
var analyzerExecutor = AnalyzerExecutor.Create(newCompilation, options, addDiagnostic, newOnAnalyzerException, IsCompilerAnalyzer, analyzerManager, singleThreadedAnalyzerToGateMap, reportAnalyzer, cancellationToken);
analyzerDriver.Initialize(newCompilation, analyzerExecutor, cancellationToken);
......@@ -259,6 +269,8 @@ public async Task<ImmutableArray<Diagnostic>> GetDiagnosticsAsync()
/// </summary>
public Task WhenCompletedTask => _primaryTask;
internal ImmutableDictionary<DiagnosticAnalyzer, TimeSpan> AnalyzerExecutionTimes => analyzerExecutor.AnalyzerExecutionTimes;
private ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<ImmutableArray<SymbolAnalyzerAction>>> MakeSymbolActionsByKind()
{
var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, ImmutableArray<ImmutableArray<SymbolAnalyzerAction>>>();
......
......@@ -40,7 +40,7 @@ public CompilationWithAnalyzers(Compilation compilation, ImmutableArray<Diagnost
_cancellationToken = cancellationToken;
_exceptionDiagnostics = new ConcurrentSet<Diagnostic>();
_driver = AnalyzerDriver.Create(compilation, analyzers, options, AnalyzerManager.Instance, AddExceptionDiagnostic, out _compilation, _cancellationToken);
_driver = AnalyzerDriver.Create(compilation, analyzers, options, AnalyzerManager.Instance, AddExceptionDiagnostic, false, out _compilation, _cancellationToken);
}
private static void VerifyAnalyzersArgument(ImmutableArray<DiagnosticAnalyzer> analyzers)
......@@ -141,7 +141,7 @@ public static bool IsDiagnosticAnalyzerSuppressed(DiagnosticAnalyzer analyzer, C
Action<Exception, DiagnosticAnalyzer, Diagnostic> voidHandler = (ex, a, diag) => { };
onAnalyzerException = onAnalyzerException ?? voidHandler;
var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance, CancellationToken.None);
var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, AnalyzerManager.Instance);
return AnalyzerDriver.IsDiagnosticAnalyzerSuppressed(analyzer, options, AnalyzerManager.Instance, analyzerExecutor);
}
......
......@@ -2204,6 +2204,7 @@ Microsoft.CodeAnalysis.CommandLineArguments.PdbPath.get -> string
Microsoft.CodeAnalysis.CommandLineArguments.PreferredUILang.get -> System.Globalization.CultureInfo
Microsoft.CodeAnalysis.CommandLineArguments.PrintFullPaths.get -> bool
Microsoft.CodeAnalysis.CommandLineArguments.ReferencePaths.get -> System.Collections.Immutable.ImmutableArray<string>
Microsoft.CodeAnalysis.CommandLineArguments.ReportAnalyzer.get -> bool
Microsoft.CodeAnalysis.CommandLineArguments.ResolveAnalyzerReferences(Microsoft.CodeAnalysis.IAnalyzerAssemblyLoader analyzerLoader) -> System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference>
Microsoft.CodeAnalysis.CommandLineArguments.ResolveMetadataReferences(Microsoft.CodeAnalysis.MetadataReferenceResolver metadataResolver) -> System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.MetadataReference>
Microsoft.CodeAnalysis.CommandLineArguments.ScriptArguments.get -> System.Collections.Immutable.ImmutableArray<string>
......
......@@ -2032,5 +2032,195 @@ public void OnlyStartsOneServer()
result = ProcessLauncher.Run(_csharpCompilerClientExecutable, "");
Assert.Equal(1, GetProcessesByFullPath(_compilerServerExecutable).Count);
}
// A dictionary with name and contents of all the files we want to create for the ReportAnalyzerMSBuild test.
private Dictionary<string, string> ReportAnalyzerMsBuildFiles => new Dictionary<string, string> {
{ "HelloSolution.sln",
@"
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""HelloLib"", ""HelloLib.csproj"", ""{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}""
EndProject
Project(""{F184B08F-C81C-45F6-A57F-5ABD9991F28F}"") = ""VBLib"", ""VBLib.vbproj"", ""{F21C894B-28E5-4212-8AF7-C8E0E5455737}""
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Debug|x86.ActiveCfg = Debug|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|Any CPU.Build.0 = Release|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}.Release|x86.ActiveCfg = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Debug|x86.ActiveCfg = Debug|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|Any CPU.Build.0 = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F21C894B-28E5-4212-8AF7-C8E0E5455737}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
"},
{ "HelloLib.csproj",
@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<UsingTask TaskName=""Microsoft.CodeAnalysis.BuildTasks.Csc"" AssemblyFile=""" + _buildTaskDll + @""" />
<PropertyGroup>
<Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>
<Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{C1170A4A-80CF-4B4F-AA58-2FAEA9158D31}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>HelloLib</RootNamespace>
<AssemblyName>HelloLib</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ReportAnalyzer>True</ReportAnalyzer>
</PropertyGroup>
<ItemGroup>
<Reference Include=""System"" />
<Reference Include=""System.Core"" />
<Reference Include=""System.Xml.Linq"" />
<Reference Include=""System.Xml"" />
</ItemGroup>
<ItemGroup>
<Compile Include=""HelloLib.cs"" />
</ItemGroup>
<ItemGroup>
<Folder Include=""Properties\"" />
</ItemGroup>
<Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />
<Import Project=""$(MyMSBuildToolsPath)\Microsoft.CSharp.Core.targets"" />
</Project>"},
{ "HelloLib.cs",
@"public class $P {}"},
{ "VBLib.vbproj",
@"<?xml version=""1.0"" encoding=""utf-8""?>
<Project ToolsVersion=""4.0"" DefaultTargets=""Build"" xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"">
<UsingTask TaskName=""Microsoft.CodeAnalysis.BuildTasks.Vbc"" AssemblyFile=""" + _buildTaskDll + @""" />
<PropertyGroup>
<Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>
<Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>
</SchemaVersion>
<ProjectGuid>{F21C894B-28E5-4212-8AF7-C8E0E5455737}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>VBLib</RootNamespace>
<AssemblyName>VBLib</AssemblyName>
<FileAlignment>512</FileAlignment>
<MyType>Windows</MyType>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<OutputPath>bin\Debug\</OutputPath>
<DocumentationFile>VBLib.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">
<DebugType>pdbonly</DebugType>
<DefineDebug>false</DefineDebug>
<DefineTrace>true</DefineTrace>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DocumentationFile>VBLib.xml</DocumentationFile>
<NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn>
</PropertyGroup>
<PropertyGroup>
<OptionExplicit>On</OptionExplicit>
</PropertyGroup>
<PropertyGroup>
<OptionCompare>Binary</OptionCompare>
</PropertyGroup>
<PropertyGroup>
<OptionStrict>Off</OptionStrict>
</PropertyGroup>
<PropertyGroup>
<OptionInfer>On</OptionInfer>
</PropertyGroup>
<PropertyGroup>
<ReportAnalyzer>True</ReportAnalyzer>
</PropertyGroup>
<ItemGroup>
<Reference Include=""System"" />
</ItemGroup>
<ItemGroup>
<Import Include=""Microsoft.VisualBasic"" />
<Import Include=""System"" />
<Import Include=""System.Collections.Generic"" />
</ItemGroup>
<ItemGroup>
<Compile Include=""VBLib.vb"" />
</ItemGroup>
<Import Project=""$(MSBuildToolsPath)\Microsoft.VisualBasic.targets"" />
<Import Project=""$(MyMSBuildToolsPath)\Microsoft.VisualBasic.Core.targets"" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
-->
</Project>"},
{ "VBLib.vb",
@"
Public Class $P
End Class
"}
};
[Fact]
public void ReportAnalyzerMSBuild()
{
string arguments = string.Format(@"/m /nr:false /t:Rebuild /p:UseRoslyn=1 HelloSolution.sln");
var result = RunCommandLineCompiler(MSBuildExecutable, arguments, _tempDirectory, ReportAnalyzerMsBuildFiles,
new Dictionary<string, string>
{ { "MyMSBuildToolsPath", Path.GetDirectoryName(typeof(CompilerServerUnitTests).Assembly.Location) } });
Assert.True(result.ExitCode != 0);
Assert.Contains("/reportanalyzer", result.Output);
}
}
}
......@@ -145,6 +145,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Dim sqmsessionguid As Guid = Nothing
Dim touchedFilesPath As String = Nothing
Dim features = New List(Of String)()
Dim reportAnalyzer As Boolean = False
' Process ruleset files first so that diagnostic severity settings specified on the command line via
' /nowarn and /warnaserror can override diagnostic severity settings specified in the ruleset file.
......@@ -937,6 +938,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
UnimplementedSwitch(diagnostics, name)
Continue For
Case "reportanalyzer"
reportAnalyzer = True
Continue For
Case "nostdlib"
If value IsNot Nothing Then
Exit Select
......@@ -1213,7 +1218,8 @@ lVbRuntimePlus:
.EmitPdb = emitPdb,
.DefaultCoreLibraryReference = defaultCoreLibraryReference,
.PreferredUILang = preferredUILang,
.SqmSessionGuid = sqmsessionguid
.SqmSessionGuid = sqmsessionguid,
.ReportAnalyzer = reportAnalyzer
}
End Function
......
'------------------------------------------------------------------------------
' <auto-generated>
' This code was generated by a tool.
' Runtime Version:4.0.30319.0
' Runtime Version:4.0.30319.42000
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
......
......@@ -5062,7 +5062,8 @@
diagnostics.
/errorlog:&lt;file&gt; Specify a file to log all compiler and analyzer
diagnostics.
/reportanalyzer Report additional analyzer information, such as
execution time.
- LANGUAGE -
/define:&lt;symbol_list&gt; Declare global conditional compilation
symbol(s). symbol_list:name=value,...
......
......@@ -7110,6 +7110,31 @@ out
Assert.Equal(expected:=ReportDiagnostic.Suppress, actual:=arguments.CompilationOptions.SpecificDiagnosticOptions("Test001"))
End Sub
<Fact>
Public Sub ReportAnalyzer()
Dim args1 = DefaultParse({"/reportanalyzer", "a.vb"}, _baseDirectory)
Assert.True(args1.ReportAnalyzer)
Dim args2 = DefaultParse({"", "a.vb"}, _baseDirectory)
Assert.False(args2.ReportAnalyzer)
End Sub
<Fact>
Public Sub ReportAnalyzerOutput()
Dim source As String = Temp.CreateFile().WriteAllText(<text>
Class C
End Class
</text>.Value).Path
Dim vbc = New MockVisualBasicCompiler(Nothing, _baseDirectory, {"/reportanalyzer", "/t:library", "/a:" + Assembly.GetExecutingAssembly().Location, source})
Dim outWriter = New StringWriter()
Dim exitCode = vbc.Run(outWriter, Nothing)
Assert.Equal(0, exitCode)
Dim output = outWriter.ToString()
Assert.Contains(New WarningDiagnosticAnalyzer().ToString(), output, StringComparison.Ordinal)
Assert.Contains(CodeAnalysisResources.AnalyzerExecutionTimeColumnHeader, output, StringComparison.Ordinal)
CleanupAllGeneratedFiles(source)
End Sub
End Class
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
......
......@@ -73,7 +73,7 @@ private static string GetAssemblyQualifiedNameWithoutVersion(Type type)
Action<Exception, DiagnosticAnalyzer, Diagnostic> defaultOnAnalyzerException = (ex, a, diagnostic) =>
OnAnalyzerException_NoTelemetryLogging(ex, a, diagnostic, hostDiagnosticUpdateSource);
return AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException ?? defaultOnAnalyzerException, AnalyzerManager.Instance, cancellationToken);
return AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException ?? defaultOnAnalyzerException, AnalyzerManager.Instance, cancellationToken: cancellationToken);
}
internal static void OnAnalyzerException_NoTelemetryLogging(
......
......@@ -167,7 +167,7 @@ public static TCompilation VerifyDiagnostics<TCompilation>(this TCompilation c,
}
Compilation newCompilation;
var driver = AnalyzerDriver.Create(c, analyzersArray, options, AnalyzerManager.Instance, onAnalyzerException, out newCompilation, CancellationToken.None);
var driver = AnalyzerDriver.Create(c, analyzersArray, options, AnalyzerManager.Instance, onAnalyzerException, false, out newCompilation, CancellationToken.None);
var discarded = newCompilation.GetDiagnostics();
diagnostics = driver.GetDiagnosticsAsync().Result.AddRange(exceptionDiagnostics);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册