提交 c42a4119 编写于 作者: M Manish Vasani

Enable per-file state caching in analyzer. The state can be shared across...

Enable per-file state caching in analyzer. The state can be shared across analyzer actions and also across different analyzer instances.

Fixes #6324
上级 7f527b51
......@@ -895,7 +895,7 @@ public void TestReportingDiagnosticWithInvalidLocation()
var compilation = CreateCompilationWithMscorlib45(source1);
var anotherCompilation = CreateCompilationWithMscorlib45(source2);
var treeInAnotherCompilation = anotherCompilation.SyntaxTrees.Single();
string message = new ArgumentException(
string.Format(CodeAnalysisResources.InvalidDiagnosticLocationReported, AnalyzerWithInvalidDiagnosticLocation.Descriptor.Id, treeInAnotherCompilation.FilePath), "diagnostic").Message;
......@@ -1446,7 +1446,7 @@ private static DiagnosticDescription[] GetExpectedGeneratedCodeAnalyzerDiagnosti
if (compilation.Options.GeneralDiagnosticOption == ReportDiagnostic.Error)
{
for(int i = 0; i < builder.Count; i++)
for (int i = 0; i < builder.Count; i++)
{
if (((string)builder[i].Code) != GeneratedCodeAnalyzer.Error.Id)
{
......@@ -1497,7 +1497,7 @@ private static void AddExpectedDiagnostic(ArrayBuilder<DiagnosticDescription> bu
public void TestEnsureNoMergedNamespaceSymbolAnalyzer()
{
var source = @"namespace N1.N2 { }";
var metadataReference = CreateCompilationWithMscorlib(source).ToMetadataReference();
var compilation = CreateCompilationWithMscorlib(source, new[] { metadataReference });
compilation.VerifyDiagnostics();
......@@ -1506,5 +1506,44 @@ public void TestEnsureNoMergedNamespaceSymbolAnalyzer()
var analyzers = new DiagnosticAnalyzer[] { new EnsureNoMergedNamespaceSymbolAnalyzer() };
compilation.VerifyAnalyzerDiagnostics(analyzers);
}
[Fact, WorkItem(6324, "https://github.com/dotnet/roslyn/issues/6324")]
public void TestSharedStateAnalyzer()
{
string source1 = @"
public partial class C { }
";
string source2 = @"
public partial class C2 { }
";
string source3 = @"
public partial class C33 { }
";
var tree1 = CSharpSyntaxTree.ParseText(source1, path: "Source1_File1.cs");
var tree2 = CSharpSyntaxTree.ParseText(source1, path: "Source1_File2.cs");
var tree3 = CSharpSyntaxTree.ParseText(source2, path: "Source2_File3.cs");
var tree4 = CSharpSyntaxTree.ParseText(source3, path: "Source3_File4.generated.cs");
var tree5 = CSharpSyntaxTree.ParseText(source3, path: "Source3_File5.designer.cs");
var compilation = CreateCompilationWithMscorlib45(new[] { tree1, tree2, tree3, tree4, tree5 });
compilation.VerifyDiagnostics();
var analyzers = new DiagnosticAnalyzer[] { new SharedStateAnalyzer() };
compilation.VerifyAnalyzerDiagnostics(analyzers, null, null, true,
Diagnostic("UserCodeDiagnostic").WithArguments("Source1_File1.cs").WithLocation(1, 1),
Diagnostic("UniqueTextFileDiagnostic").WithArguments("Source1_File1.cs").WithLocation(1, 1),
Diagnostic("GeneratedCodeDiagnostic", "C33").WithArguments("C33").WithLocation(2, 22),
Diagnostic("UserCodeDiagnostic", "C2").WithArguments("C2").WithLocation(2, 22),
Diagnostic("UserCodeDiagnostic", "C").WithArguments("C").WithLocation(2, 22),
Diagnostic("UserCodeDiagnostic").WithArguments("Source1_File2.cs").WithLocation(1, 1),
Diagnostic("UniqueTextFileDiagnostic").WithArguments("Source1_File2.cs").WithLocation(1, 1),
Diagnostic("UserCodeDiagnostic").WithArguments("Source2_File3.cs").WithLocation(1, 1),
Diagnostic("UniqueTextFileDiagnostic").WithArguments("Source2_File3.cs").WithLocation(1, 1),
Diagnostic("GeneratedCodeDiagnostic").WithArguments("Source3_File4.generated.cs").WithLocation(1, 1),
Diagnostic("UniqueTextFileDiagnostic").WithArguments("Source3_File4.generated.cs").WithLocation(1, 1),
Diagnostic("GeneratedCodeDiagnostic").WithArguments("Source3_File5.designer.cs").WithLocation(1, 1),
Diagnostic("UniqueTextFileDiagnostic").WithArguments("Source3_File5.designer.cs").WithLocation(1, 1),
Diagnostic("NumberOfUniqueTextFileDescriptor").WithArguments("3").WithLocation(1, 1));
}
}
}
}
\ No newline at end of file
......@@ -65,9 +65,14 @@
<Compile Include="Compilation\OperationVisitor.cs" />
<Compile Include="Compilation\OperationWalker.cs" />
<Compile Include="Compilation\ScriptCompilationInfo.cs" />
<Compile Include="DiagnosticAnalyzer\SyntaxTreeValueProvider.cs" />
<Compile Include="DiagnosticAnalyzer\SourceTextValueProvider.cs" />
<Compile Include="DiagnosticAnalyzer\CompilationAnalysisValueProviderFactory.cs" />
<Compile Include="DiagnosticAnalyzer\CompilationAnalysisValueProvider.cs" />
<Compile Include="DiagnosticAnalyzer\AnalyzerDriver.GeneratedCodeUtilities.cs" />
<Compile Include="DiagnosticAnalyzer\AnalyzerDriver.CompilationData.cs" />
<Compile Include="DiagnosticAnalyzer\AnalysisContextInfo.cs" />
<Compile Include="DiagnosticAnalyzer\AnalysisValueProvider.cs" />
<Compile Include="DiagnosticAnalyzer\SuppressMessageInfo.cs" />
<Compile Include="Diagnostic\SuppressionInfo.cs" />
<Compile Include="InternalUtilities\SetWithInsertionOrder.cs" />
......@@ -76,9 +81,11 @@
<Compile Include="RealParser.cs" />
<Compile Include="ReferenceManager\MergedAliases.cs" />
<Compile Include="StrongName\CryptoBlobParser.cs" />
<Compile Include="Syntax\SyntaxTreeComparer.cs" />
<Compile Include="Syntax\ICompilationUnitSyntax.cs" />
<Compile Include="Syntax\ISkippedTokensTriviaSyntax.cs" />
<Compile Include="Text\LargeTextWriter.cs" />
<Compile Include="Text\SourceTextComparer.cs" />
<Compile Include="Text\SourceTextWriter.cs" />
<Compile Include="Text\StringTextWriter.cs" />
<Compile Include="UnicodeCharacterUtilities.cs" />
......
// 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.IO;
using System.Threading;
using Microsoft.CodeAnalysis.Text;
......
// 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.Runtime.CompilerServices;
namespace Microsoft.CodeAnalysis.Diagnostics
{
internal class AnalysisValueProvider<TKey, TValue>
where TKey : class
{
private readonly Func<TKey, TValue> _computeValue;
// This provider holds a weak reference to the key-value pairs, as AnalysisValueProvider might outlive individual compilations.
// CompilationAnalysisValueProvider, which wraps this provider and lives for the lifetime of specific compilation, holds a strong reference to the key-value pairs, providing an overall performance benefit.
private readonly ConditionalWeakTable<TKey, WrappedValue> _valueCache;
private readonly ConditionalWeakTable<TKey, WrappedValue>.CreateValueCallback _valueCacheCallback;
internal IEqualityComparer<TKey> KeyComparer { get; private set; }
public AnalysisValueProvider(Func<TKey, TValue> computeValue, IEqualityComparer<TKey> keyComparer)
{
_computeValue = computeValue;
KeyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
_valueCache = new ConditionalWeakTable<TKey, WrappedValue>();
_valueCacheCallback = new ConditionalWeakTable<TKey, WrappedValue>.CreateValueCallback(ComputeValue);
}
private sealed class WrappedValue
{
public TValue Value { get; set; }
}
private WrappedValue ComputeValue(TKey key)
{
var value = _computeValue(key);
return new WrappedValue { Value = value };
}
internal bool TryGetValue(TKey key, out TValue value)
{
// Catch any exceptions from the computeValue callback, which calls into user code.
try
{
value = _valueCache.GetValue(key, _valueCacheCallback).Value;
return true;
}
catch (Exception)
{
value = default(TValue);
return false;
}
}
}
}
......@@ -43,6 +43,7 @@ internal class AnalyzerExecutor
private readonly Func<DiagnosticAnalyzer, bool> _shouldSkipAnalysisOnGeneratedCode;
private readonly Func<Diagnostic, DiagnosticAnalyzer, Compilation, CancellationToken, bool> _shouldSuppressGeneratedCodeDiagnostic;
private readonly ConcurrentDictionary<DiagnosticAnalyzer, TimeSpan> _analyzerExecutionTimeMapOpt;
private readonly CompilationAnalysisValueProviderFactory _compilationAnalysisValueProviderFactory;
private readonly CancellationToken _cancellationToken;
/// <summary>
......@@ -163,6 +164,8 @@ internal class AnalyzerExecutor
_addCategorizedLocalDiagnosticOpt = addCategorizedLocalDiagnosticOpt;
_addCategorizedNonLocalDiagnosticOpt = addCategorizedNonLocalDiagnosticOpt;
_cancellationToken = cancellationToken;
_compilationAnalysisValueProviderFactory = new CompilationAnalysisValueProviderFactory();
}
public AnalyzerExecutor WithCancellationToken(CancellationToken cancellationToken)
......@@ -212,7 +215,8 @@ public void ExecuteCompilationStartActions(ImmutableArray<CompilationStartAnalyz
_cancellationToken.ThrowIfCancellationRequested();
ExecuteAndCatchIfThrows(startAction.Analyzer,
() => startAction.Action(new AnalyzerCompilationStartAnalysisContext(startAction.Analyzer, compilationScope, _compilation, _analyzerOptions, _cancellationToken)),
() => startAction.Action(new AnalyzerCompilationStartAnalysisContext(startAction.Analyzer, compilationScope,
_compilation, _analyzerOptions, _compilationAnalysisValueProviderFactory, _cancellationToken)),
new AnalysisContextInfo(_compilation));
}
}
......@@ -263,7 +267,7 @@ private void ExecuteCompilationActionsCore(ImmutableArray<CompilationAnalyzerAct
ExecuteAndCatchIfThrows(endAction.Analyzer,
() => endAction.Action(new CompilationAnalysisContext(
_compilation, _analyzerOptions, addDiagnostic,
d => IsSupportedDiagnostic(endAction.Analyzer, d), _cancellationToken)),
d => IsSupportedDiagnostic(endAction.Analyzer, d), _compilationAnalysisValueProviderFactory, _cancellationToken)),
new AnalysisContextInfo(_compilation));
analyzerStateOpt?.ProcessedActions.Add(endAction);
......
// 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;
namespace Microsoft.CodeAnalysis.Diagnostics
{
/// <summary>
/// Wrapper over the core <see cref="AnalysisValueProvider{TKey, TValue}"/> which holds a strong reference to key-value pairs for the lifetime of a compilation that this provider is associated with.
/// This ensures that values are never re-computed for equivalent keys while analyzing each compilation, improving overall analyzer performance.
/// </summary>
internal sealed class CompilationAnalysisValueProvider<TKey, TValue>
where TKey : class
{
private readonly AnalysisValueProvider<TKey, TValue> _analysisValueProvider;
private readonly Dictionary<TKey, TValue> _valueMap;
public CompilationAnalysisValueProvider(AnalysisValueProvider<TKey, TValue> analysisValueProvider)
{
_analysisValueProvider = analysisValueProvider;
_valueMap = new Dictionary<TKey, TValue>(analysisValueProvider.KeyComparer);
}
internal bool TryGetValue(TKey key, out TValue value)
{
// First try to get the cached value for this compilation.
lock (_valueMap)
{
if (_valueMap.TryGetValue(key, out value))
{
return true;
}
}
// Ask the core analysis value provider for the value.
if (!_analysisValueProvider.TryGetValue(key, out value))
{
value = default(TValue);
return false;
}
// Store the value for the lifetime of the compilation.
lock (_valueMap)
{
_valueMap[key] = value;
}
return true;
}
}
}
// 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.Threading;
namespace Microsoft.CodeAnalysis.Diagnostics
{
internal sealed class CompilationAnalysisValueProviderFactory
{
private Dictionary<object, object> _lazySharedStateProviderMap;
public CompilationAnalysisValueProvider<TKey, TValue> GetValueProvider<TKey, TValue>(AnalysisValueProvider<TKey, TValue> analysisSharedStateProvider)
where TKey : class
{
if (_lazySharedStateProviderMap == null)
{
Interlocked.CompareExchange(ref _lazySharedStateProviderMap, new Dictionary<object, object>(), null);
}
object value;
lock (_lazySharedStateProviderMap)
{
if (!_lazySharedStateProviderMap.TryGetValue(analysisSharedStateProvider, out value))
{
value = new CompilationAnalysisValueProvider<TKey, TValue>(analysisSharedStateProvider);
_lazySharedStateProviderMap[analysisSharedStateProvider] = value;
}
}
return value as CompilationAnalysisValueProvider<TKey, TValue>;
}
}
}
......@@ -4,6 +4,7 @@
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.Semantics;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Diagnostics
{
......@@ -196,6 +197,27 @@ public virtual void ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags an
{
throw new NotImplementedException();
}
/// <summary>
/// Attempts to compute or get the cached value provided by the given <paramref name="valueProvider"/> for the given <paramref name="key"/>.
/// Reusing the same <paramref name="valueProvider"/> instance across analyzer actions and/or analyzer instances can improve the overall analyzer performance by avoiding recomputation of the values.
/// </summary>
/// <typeparam name="TValue">The type of the value associated with the key.</typeparam>
/// <param name="key"><see cref="SourceText"/> for which the value is queried.</param>
/// <param name="valueProvider">Provider that computes the underlying value associated with the key.</param>
/// <param name="value">Value associated with the key.</param>
/// <returns>Returns true on success, false otherwise.</returns>
public bool TryGetValue<TValue>(SourceText key, SourceTextValueProvider<TValue> valueProvider, out TValue value)
{
return TryGetValue(key, valueProvider.CoreValueProvider, out value);
}
private bool TryGetValue<TKey, TValue>(TKey key, AnalysisValueProvider<TKey, TValue> valueProvider, out TValue value)
where TKey : class
{
DiagnosticAnalysisContextHelpers.VerifyArguments(key, valueProvider);
return valueProvider.TryGetValue(key, out value);
}
}
/// <summary>
......@@ -408,6 +430,47 @@ public virtual void RegisterOperationAction(Action<OperationAnalysisContext> act
{
throw new NotImplementedException();
}
/// <summary>
/// Attempts to compute or get the cached value provided by the given <paramref name="valueProvider"/> for the given <paramref name="key"/>.
/// Reusing the same <paramref name="valueProvider"/> instance across analyzer actions and/or analyzer instances can improve the overall analyzer performance by avoiding recomputation of the values.
/// </summary>
/// <typeparam name="TValue">The type of the value associated with the key.</typeparam>
/// <param name="key"><see cref="SourceText"/> for which the value is queried.</param>
/// <param name="valueProvider">Provider that computes the underlying value associated with the key.</param>
/// <param name="value">Value associated with the key.</param>
/// <returns>Returns true on success, false otherwise.</returns>
public bool TryGetValue<TValue>(SourceText key, SourceTextValueProvider<TValue> valueProvider, out TValue value)
{
return TryGetValue(key, valueProvider.CoreValueProvider, out value);
}
/// <summary>
/// Attempts to compute or get the cached value provided by the given <paramref name="valueProvider"/> for the given <paramref name="key"/>.
/// Reusing the same <paramref name="valueProvider"/> instance across analyzer actions and/or analyzer instances can improve the overall analyzer performance by avoiding recomputation of the values.
/// </summary>
/// <typeparam name="TValue">The type of the value associated with the key.</typeparam>
/// <param name="key"><see cref="SyntaxTree"/> instance for which the value is queried.</param>
/// <param name="valueProvider">Provider that computes the underlying value associated with the key.</param>
/// <param name="value">Value associated with the key.</param>
/// <returns>Returns true on success, false otherwise.</returns>
public bool TryGetValue<TValue>(SyntaxTree key, SyntaxTreeValueProvider<TValue> valueProvider, out TValue value)
{
return TryGetValue(key, valueProvider.CoreValueProvider, out value);
}
private bool TryGetValue<TKey, TValue>(TKey key, AnalysisValueProvider<TKey, TValue> valueProvider, out TValue value)
where TKey : class
{
DiagnosticAnalysisContextHelpers.VerifyArguments(key, valueProvider);
return TryGetValueCore(key, valueProvider, out value);
}
internal virtual bool TryGetValueCore<TKey, TValue>(TKey key, AnalysisValueProvider<TKey, TValue> valueProvider, out TValue value)
where TKey : class
{
throw new NotImplementedException();
}
}
/// <summary>
......@@ -420,6 +483,7 @@ public struct CompilationAnalysisContext
private readonly AnalyzerOptions _options;
private readonly Action<Diagnostic> _reportDiagnostic;
private readonly Func<Diagnostic, bool> _isSupportedDiagnostic;
private readonly CompilationAnalysisValueProviderFactory _compilationAnalysisValueProviderFactoryOpt;
private readonly CancellationToken _cancellationToken;
/// <summary>
......@@ -438,11 +502,23 @@ public struct CompilationAnalysisContext
public CancellationToken CancellationToken { get { return _cancellationToken; } }
public CompilationAnalysisContext(Compilation compilation, AnalyzerOptions options, Action<Diagnostic> reportDiagnostic, Func<Diagnostic, bool> isSupportedDiagnostic, CancellationToken cancellationToken)
: this(compilation, options, reportDiagnostic, isSupportedDiagnostic, null, cancellationToken)
{
}
internal CompilationAnalysisContext(
Compilation compilation,
AnalyzerOptions options,
Action<Diagnostic> reportDiagnostic,
Func<Diagnostic, bool> isSupportedDiagnostic,
CompilationAnalysisValueProviderFactory compilationAnalysisValueProviderFactoryOpt,
CancellationToken cancellationToken)
{
_compilation = compilation;
_options = options;
_reportDiagnostic = reportDiagnostic;
_isSupportedDiagnostic = isSupportedDiagnostic;
_compilationAnalysisValueProviderFactoryOpt = compilationAnalysisValueProviderFactoryOpt;
_cancellationToken = cancellationToken;
}
......@@ -458,6 +534,48 @@ public void ReportDiagnostic(Diagnostic diagnostic)
_reportDiagnostic(diagnostic);
}
}
/// <summary>
/// Attempts to compute or get the cached value provided by the given <paramref name="valueProvider"/> for the given <paramref name="key"/>.
/// Reusing the same <paramref name="valueProvider"/> instance across analyzer actions and/or analyzer instances can improve the overall analyzer performance by avoiding recomputation of the values.
/// </summary>
/// <typeparam name="TValue">The type of the value associated with the key.</typeparam>
/// <param name="key"><see cref="SourceText"/> for which the value is queried.</param>
/// <param name="valueProvider">Provider that computes the underlying value associated with the key.</param>
/// <param name="value">Value associated with the key.</param>
/// <returns>Returns true on success, false otherwise.</returns>
public bool TryGetValue<TValue>(SourceText key, SourceTextValueProvider<TValue> valueProvider, out TValue value)
{
return TryGetValue(key, valueProvider.CoreValueProvider, out value);
}
/// <summary>
/// Attempts to compute or get the cached value provided by the given <paramref name="valueProvider"/> for the given <paramref name="key"/>.
/// Reusing the same <paramref name="valueProvider"/> instance across analyzer actions and/or analyzer instances can improve the overall analyzer performance by avoiding recomputation of the values.
/// </summary>
/// <typeparam name="TValue">The type of the value associated with the key.</typeparam>
/// <param name="key"><see cref="SyntaxTree"/> for which the value is queried.</param>
/// <param name="valueProvider">Provider that computes the underlying value associated with the key.</param>
/// <param name="value">Value associated with the key.</param>
/// <returns>Returns true on success, false otherwise.</returns>
public bool TryGetValue<TValue>(SyntaxTree key, SyntaxTreeValueProvider<TValue> valueProvider, out TValue value)
{
return TryGetValue(key, valueProvider.CoreValueProvider, out value);
}
private bool TryGetValue<TKey, TValue>(TKey key, AnalysisValueProvider<TKey, TValue> valueProvider, out TValue value)
where TKey : class
{
DiagnosticAnalysisContextHelpers.VerifyArguments(key, valueProvider);
if (_compilationAnalysisValueProviderFactoryOpt != null)
{
var compilationAnalysisValueProvider = _compilationAnalysisValueProviderFactoryOpt.GetValueProvider(valueProvider);
return compilationAnalysisValueProvider.TryGetValue(key, out value);
}
return valueProvider.TryGetValue(key, out value);
}
}
/// <summary>
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Diagnostics
{
......@@ -113,5 +114,19 @@ private static void VerifySyntaxKinds<TLanguageKindEnum>(ImmutableArray<TLanguag
throw new ArgumentException(CodeAnalysisResources.ArgumentCannotBeEmpty, nameof(syntaxKinds));
}
}
internal static void VerifyArguments<TKey, TValue>(TKey key, AnalysisValueProvider<TKey, TValue> valueProvider)
where TKey: class
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (valueProvider == null)
{
throw new ArgumentNullException(nameof(valueProvider));
}
}
}
}
......@@ -108,12 +108,20 @@ internal sealed class AnalyzerCompilationStartAnalysisContext : CompilationStart
{
private readonly DiagnosticAnalyzer _analyzer;
private readonly HostCompilationStartAnalysisScope _scope;
public AnalyzerCompilationStartAnalysisContext(DiagnosticAnalyzer analyzer, HostCompilationStartAnalysisScope scope, Compilation compilation, AnalyzerOptions options, CancellationToken cancellationToken)
private readonly CompilationAnalysisValueProviderFactory _compilationAnalysisValueProviderFactory;
public AnalyzerCompilationStartAnalysisContext(
DiagnosticAnalyzer analyzer,
HostCompilationStartAnalysisScope scope,
Compilation compilation,
AnalyzerOptions options,
CompilationAnalysisValueProviderFactory compilationAnalysisValueProviderFactory,
CancellationToken cancellationToken)
: base(compilation, options, cancellationToken)
{
_analyzer = analyzer;
_scope = scope;
_compilationAnalysisValueProviderFactory = compilationAnalysisValueProviderFactory;
}
public override void RegisterCompilationEndAction(Action<CompilationAnalysisContext> action)
......@@ -166,6 +174,12 @@ public override void RegisterOperationAction(Action<OperationAnalysisContext> ac
{
_scope.RegisterOperationAction(this._analyzer, action, operationKinds);
}
internal override bool TryGetValueCore<TKey, TValue>(TKey key, AnalysisValueProvider<TKey, TValue> valueProvider, out TValue value)
{
var compilationAnalysisValueProvider = _compilationAnalysisValueProviderFactory.GetValueProvider(valueProvider);
return compilationAnalysisValueProvider.TryGetValue(key, out value);
}
}
/// <summary>
......
// 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 Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Diagnostics
{
/// <summary>
/// Provides custom values associated with <see cref="SourceText"/> instances using the given 'computeValue' delegate.
/// </summary>
public sealed class SourceTextValueProvider<TValue>
{
internal AnalysisValueProvider<SourceText, TValue> CoreValueProvider { get; private set; }
/// <summary>
/// Provides custom values associated with <see cref="SourceText"/> instances using the given <paramref name="computeValue"/>.
/// </summary>
/// <param name="computeValue">Delegate to compute value associated with a given <see cref="SourceText"/> instance.</param>
/// <param name="sourceTextComparer">Optional equality comparer to determine equivalent <see cref="SourceText"/> instances that have the same value.
/// If no comparer is provided, then <see cref="SourceTextComparer"/> is used by default.</param>
public SourceTextValueProvider(Func<SourceText, TValue> computeValue, IEqualityComparer<SourceText> sourceTextComparer = null)
{
CoreValueProvider = new AnalysisValueProvider<SourceText, TValue>(computeValue, sourceTextComparer ?? SourceTextComparer.Instance);
}
}
}
// 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;
namespace Microsoft.CodeAnalysis.Diagnostics
{
/// <summary>
/// Provides custom values associated with <see cref="SyntaxTree"/> instances using the given 'computeValue' delegate.
/// </summary>
public sealed class SyntaxTreeValueProvider<TValue>
{
internal AnalysisValueProvider<SyntaxTree, TValue> CoreValueProvider { get; private set; }
/// <summary>
/// Provides values associated with <see cref="SyntaxTree"/> instances using the given <paramref name="computeValue"/>.
/// </summary>
/// <param name="computeValue">Delegate to compute value associated with a given <see cref="SyntaxTree"/> instance.</param>
/// <param name="syntaxTreeComparer">Optional equality comparer to determine equivalent <see cref="SyntaxTree"/> instances that have the same value.
/// If no comparer is provided, then <see cref="SyntaxTreeComparer"/> is used by default.</param>
public SyntaxTreeValueProvider(Func<SyntaxTree, TValue> computeValue, IEqualityComparer<SyntaxTree> syntaxTreeComparer = null)
{
CoreValueProvider = new AnalysisValueProvider<SyntaxTree, TValue>(computeValue, syntaxTreeComparer ?? SyntaxTreeComparer.Instance);
}
}
}
......@@ -2,7 +2,12 @@ Microsoft.CodeAnalysis.CompilationOptions.PublicSign.get -> bool
Microsoft.CodeAnalysis.CompilationOptions.WithPublicSign(bool publicSign) -> Microsoft.CodeAnalysis.CompilationOptions
Microsoft.CodeAnalysis.CompilationReference.Equals(Microsoft.CodeAnalysis.CompilationReference other) -> bool
Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, params Microsoft.CodeAnalysis.Semantics.OperationKind[] operationKinds) -> void
Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.TryGetValue<TValue>(Microsoft.CodeAnalysis.Text.SourceText key, Microsoft.CodeAnalysis.Diagnostics.SourceTextValueProvider<TValue> valueProvider, out TValue value) -> bool
Microsoft.CodeAnalysis.Diagnostics.CompilationAnalysisContext.TryGetValue<TValue>(Microsoft.CodeAnalysis.SyntaxTree key, Microsoft.CodeAnalysis.Diagnostics.SyntaxTreeValueProvider<TValue> valueProvider, out TValue value) -> bool
Microsoft.CodeAnalysis.Diagnostics.CompilationAnalysisContext.TryGetValue<TValue>(Microsoft.CodeAnalysis.Text.SourceText key, Microsoft.CodeAnalysis.Diagnostics.SourceTextValueProvider<TValue> valueProvider, out TValue value) -> bool
Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, params Microsoft.CodeAnalysis.Semantics.OperationKind[] operationKinds) -> void
Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.TryGetValue<TValue>(Microsoft.CodeAnalysis.SyntaxTree key, Microsoft.CodeAnalysis.Diagnostics.SyntaxTreeValueProvider<TValue> valueProvider, out TValue value) -> bool
Microsoft.CodeAnalysis.Diagnostics.CompilationStartAnalysisContext.TryGetValue<TValue>(Microsoft.CodeAnalysis.Text.SourceText key, Microsoft.CodeAnalysis.Diagnostics.SourceTextValueProvider<TValue> valueProvider, out TValue value) -> bool
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.AnalyzerExceptionFilter.get -> System.Func<System.Exception, bool>
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.CompilationWithAnalyzersOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Action<System.Exception, Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, Microsoft.CodeAnalysis.Diagnostic> onAnalyzerException, System.Func<System.Exception, bool> analyzerExceptionFilter, bool concurrentAnalysis, bool logAnalyzerExecutionTime) -> void
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.CompilationWithAnalyzersOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions options, System.Action<System.Exception, Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, Microsoft.CodeAnalysis.Diagnostic> onAnalyzerException, System.Func<System.Exception, bool> analyzerExceptionFilter, bool concurrentAnalysis, bool logAnalyzerExecutionTime, bool reportSuppressedDiagnostics) -> void
......@@ -31,6 +36,10 @@ Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.OperationB
Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.Options.get -> Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions
Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.OwningSymbol.get -> Microsoft.CodeAnalysis.ISymbol
Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, params Microsoft.CodeAnalysis.Semantics.OperationKind[] operationKinds) -> void
Microsoft.CodeAnalysis.Diagnostics.SourceTextValueProvider<TValue>
Microsoft.CodeAnalysis.Diagnostics.SourceTextValueProvider<TValue>.SourceTextValueProvider(System.Func<Microsoft.CodeAnalysis.Text.SourceText, TValue> computeValue, System.Collections.Generic.IEqualityComparer<Microsoft.CodeAnalysis.Text.SourceText> sourceTextComparer = null) -> void
Microsoft.CodeAnalysis.Diagnostics.SyntaxTreeValueProvider<TValue>
Microsoft.CodeAnalysis.Diagnostics.SyntaxTreeValueProvider<TValue>.SyntaxTreeValueProvider(System.Func<Microsoft.CodeAnalysis.SyntaxTree, TValue> computeValue, System.Collections.Generic.IEqualityComparer<Microsoft.CodeAnalysis.SyntaxTree> syntaxTreeComparer = null) -> void
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationActionsCount.get -> int
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockActionsCount.get -> int
Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo.OperationBlockEndActionsCount.get -> int
......@@ -802,4 +811,4 @@ virtual Microsoft.CodeAnalysis.Semantics.OperationVisitor<TArgument, TResult>.Vi
virtual Microsoft.CodeAnalysis.Semantics.OperationVisitor<TArgument, TResult>.VisitVariableDeclarationStatement(Microsoft.CodeAnalysis.Semantics.IVariableDeclarationStatement operation, TArgument argument) -> TResult
virtual Microsoft.CodeAnalysis.Semantics.OperationVisitor<TArgument, TResult>.VisitWhileUntilLoopStatement(Microsoft.CodeAnalysis.Semantics.IWhileUntilLoopStatement operation, TArgument argument) -> TResult
virtual Microsoft.CodeAnalysis.Semantics.OperationVisitor<TArgument, TResult>.VisitWithStatement(Microsoft.CodeAnalysis.Semantics.IWithStatement operation, TArgument argument) -> TResult
virtual Microsoft.CodeAnalysis.Semantics.OperationVisitor<TArgument, TResult>.VisitYieldBreakStatement(Microsoft.CodeAnalysis.Semantics.IStatement operation, TArgument argument) -> TResult
\ No newline at end of file
virtual Microsoft.CodeAnalysis.Semantics.OperationVisitor<TArgument, TResult>.VisitYieldBreakStatement(Microsoft.CodeAnalysis.Semantics.IStatement operation, TArgument argument) -> TResult
// 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 Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
internal class SyntaxTreeComparer : IEqualityComparer<SyntaxTree>
{
public static readonly SyntaxTreeComparer Instance = new SyntaxTreeComparer();
public bool Equals(SyntaxTree x, SyntaxTree y)
{
if (x == null)
{
return y == null;
}
else if (y == null)
{
return false;
}
return string.Equals(x.FilePath, y.FilePath, StringComparison.OrdinalIgnoreCase) &&
SourceTextComparer.Instance.Equals(x.GetText(), y.GetText());
}
public int GetHashCode(SyntaxTree obj)
{
return Hash.Combine(obj.FilePath.GetHashCode(), SourceTextComparer.Instance.GetHashCode(obj.GetText()));
}
}
}
......@@ -415,14 +415,14 @@ public virtual void Write(TextWriter writer, TextSpan span, CancellationToken ca
}
}
internal ImmutableArray<byte> GetChecksum()
internal ImmutableArray<byte> GetChecksum(bool useDefaultEncodingIfNull = false)
{
if (_lazyChecksum.IsDefault)
{
// we shouldn't be asking for a checksum of encoding-less source text:
Debug.Assert(this.Encoding != null);
// we shouldn't be asking for a checksum of encoding-less source text, except for SourceText comparison.
Debug.Assert(this.Encoding != null || useDefaultEncodingIfNull);
using (var stream = new SourceTextStream(this))
using (var stream = new SourceTextStream(this, useDefaultEncodingIfNull: useDefaultEncodingIfNull))
{
ImmutableInterlocked.InterlockedInitialize(ref _lazyChecksum, CalculateChecksum(stream, _checksumAlgorithm));
}
......
// 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 Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Text
{
internal class SourceTextComparer : IEqualityComparer<SourceText>
{
public static SourceTextComparer Instance = new SourceTextComparer();
public bool Equals(SourceText x, SourceText y)
{
if (x == null)
{
return y == null;
}
else if (y == null)
{
return false;
}
return x.ContentEquals(y);
}
public int GetHashCode(SourceText obj)
{
var checksum = obj.GetChecksum(useDefaultEncodingIfNull: true);
var contentsHash = !checksum.IsDefault ? Hash.CombineValues(checksum) : 0;
var encodingHash = obj.Encoding != null ? obj.Encoding.GetHashCode() : 0;
return Hash.Combine(obj.Length,
Hash.Combine(contentsHash,
Hash.Combine(encodingHash, obj.ChecksumAlgorithm.GetHashCode())));
}
}
}
// 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.IO;
using System.Text;
......@@ -12,6 +13,7 @@ namespace Microsoft.CodeAnalysis.Text
internal sealed class SourceTextStream : Stream
{
private readonly SourceText _source;
private readonly Encoding _encoding;
private readonly Encoder _encoder;
private readonly int _minimumTargetBufferCount;
......@@ -22,11 +24,16 @@ internal sealed class SourceTextStream : Stream
private int _bufferUnreadChars;
private bool _preambleWritten;
public SourceTextStream(SourceText source, int bufferSize = 2048)
private static readonly Encoding s_utf8EncodingWithNoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
public SourceTextStream(SourceText source, int bufferSize = 2048, bool useDefaultEncodingIfNull = false)
{
Debug.Assert(source.Encoding != null || useDefaultEncodingIfNull);
_source = source;
_encoder = source.Encoding.GetEncoder();
_minimumTargetBufferCount = source.Encoding.GetMaxByteCount(charCount: 1);
_encoding = source.Encoding ?? s_utf8EncodingWithNoBOM;
_encoder = _encoding.GetEncoder();
_minimumTargetBufferCount = _encoding.GetMaxByteCount(charCount: 1);
_sourceOffset = 0;
_position = 0;
_charBuffer = new char[Math.Min(bufferSize, _source.Length)];
......@@ -109,7 +116,7 @@ public override int Read(byte[] buffer, int offset, int count)
private int WritePreamble(byte[] buffer, int offset, int count)
{
_preambleWritten = true;
byte[] preambleBytes = _source.Encoding.GetPreamble();
byte[] preambleBytes = _encoding.GetPreamble();
if (preambleBytes == null)
{
return 0;
......
......@@ -7,6 +7,7 @@
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using Xunit;
......@@ -835,5 +836,138 @@ private void ReportDiagnosticsCore(Action<Diagnostic> addDiagnostic, Location lo
addDiagnostic(diagnostic);
}
}
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public class SharedStateAnalyzer : DiagnosticAnalyzer
{
private readonly SyntaxTreeValueProvider<bool> _treeValueProvider;
private readonly HashSet<SyntaxTree> _treeCallbackSet;
private readonly SourceTextValueProvider<int> _textValueProvider;
private readonly HashSet<SourceText> _textCallbackSet;
public static readonly DiagnosticDescriptor GeneratedCodeDescriptor = new DiagnosticDescriptor(
"GeneratedCodeDiagnostic",
"Title1",
"GeneratedCodeDiagnostic {0}",
"Category",
DiagnosticSeverity.Warning,
true);
public static readonly DiagnosticDescriptor NonGeneratedCodeDescriptor = new DiagnosticDescriptor(
"UserCodeDiagnostic",
"Title2",
"UserCodeDiagnostic {0}",
"Category",
DiagnosticSeverity.Warning,
true);
public static readonly DiagnosticDescriptor UniqueTextFileDescriptor = new DiagnosticDescriptor(
"UniqueTextFileDiagnostic",
"Title3",
"UniqueTextFileDiagnostic {0}",
"Category",
DiagnosticSeverity.Warning,
true);
public static readonly DiagnosticDescriptor NumberOfUniqueTextFileDescriptor = new DiagnosticDescriptor(
"NumberOfUniqueTextFileDescriptor",
"Title4",
"NumberOfUniqueTextFileDescriptor {0}",
"Category",
DiagnosticSeverity.Warning,
true);
public SharedStateAnalyzer()
{
_treeValueProvider = new SyntaxTreeValueProvider<bool>(IsGeneratedCode);
_treeCallbackSet = new HashSet<SyntaxTree>(SyntaxTreeComparer.Instance);
_textValueProvider = new SourceTextValueProvider<int>(GetCharacterCount);
_textCallbackSet = new HashSet<SourceText>(SourceTextComparer.Instance);
}
private bool IsGeneratedCode(SyntaxTree tree)
{
lock (_treeCallbackSet)
{
if (!_treeCallbackSet.Add(tree))
{
throw new Exception("Expected driver to make a single callback per tree");
}
}
var fileNameWithoutExtension = PathUtilities.GetFileName(tree.FilePath, includeExtension: false);
return fileNameWithoutExtension.EndsWith(".designer", StringComparison.OrdinalIgnoreCase) ||
fileNameWithoutExtension.EndsWith(".generated", StringComparison.OrdinalIgnoreCase);
}
private int GetCharacterCount(SourceText text)
{
lock (_textCallbackSet)
{
if (!_textCallbackSet.Add(text))
{
throw new Exception("Expected driver to make a single callback per text");
}
}
return text.Length;
}
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(GeneratedCodeDescriptor, NonGeneratedCodeDescriptor, UniqueTextFileDescriptor, NumberOfUniqueTextFileDescriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(this.OnCompilationStart);
}
private void OnCompilationStart(CompilationStartAnalysisContext context)
{
context.RegisterSymbolAction(symbolContext =>
{
var descriptor = GeneratedCodeDescriptor;
foreach (var location in symbolContext.Symbol.Locations)
{
bool isGeneratedCode;
context.TryGetValue(location.SourceTree, _treeValueProvider, out isGeneratedCode);
if (!isGeneratedCode)
{
descriptor = NonGeneratedCodeDescriptor;
break;
}
}
var diagnostic = Diagnostic.Create(descriptor, symbolContext.Symbol.Locations[0], symbolContext.Symbol.Name);
symbolContext.ReportDiagnostic(diagnostic);
}, SymbolKind.NamedType);
context.RegisterSyntaxTreeAction(treeContext =>
{
bool isGeneratedCode;
context.TryGetValue(treeContext.Tree, _treeValueProvider, out isGeneratedCode);
var descriptor = isGeneratedCode ? GeneratedCodeDescriptor : NonGeneratedCodeDescriptor;
var diagnostic = Diagnostic.Create(descriptor, Location.None, treeContext.Tree.FilePath);
treeContext.ReportDiagnostic(diagnostic);
int length;
context.TryGetValue(treeContext.Tree.GetText(), _textValueProvider, out length);
diagnostic = Diagnostic.Create(UniqueTextFileDescriptor, Location.None, treeContext.Tree.FilePath);
treeContext.ReportDiagnostic(diagnostic);
});
context.RegisterCompilationEndAction(endContext =>
{
if (_treeCallbackSet.Count != endContext.Compilation.SyntaxTrees.Count())
{
throw new Exception("Expected driver to make a callback for every tree");
}
var diagnostic = Diagnostic.Create(NumberOfUniqueTextFileDescriptor, Location.None, _textCallbackSet.Count);
endContext.ReportDiagnostic(diagnostic);
});
}
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册