提交 3f84e560 编写于 作者: J John Hamby

Merge remote-tracking branch 'upstream/master' into BadOperations

......@@ -80,8 +80,8 @@
<MicrosoftDiaSymReaderNativeVersion>1.3.3</MicrosoftDiaSymReaderNativeVersion>
<NuGetCommandLineVersion>$(NuGetCommandLineAssemblyVersion)</NuGetCommandLineVersion>
<MicrosoftCompositionVersion>$(MicrosoftCompositionAssemblyVersion)</MicrosoftCompositionVersion>
<MicrosoftCodeAnalysisAnalyzersVersionStable Condition="'$(MicrosoftCodeAnalysisAnalyzersVersionStable)' == ''">1.0.0</MicrosoftCodeAnalysisAnalyzersVersionStable>
<MicrosoftCodeAnalysisAnalyzersVersionNext Condition="'$(MicrosoftCodeAnalysisAnalyzersVersionNext)' == ''">1.2.0</MicrosoftCodeAnalysisAnalyzersVersionNext>
<!-- Versioning is independent, this should not be the Roslyn semantic version -->
<CodeAnalysisAnalyzersVersion Condition="'$(CodeAnalysisAnalyzersVersion)' == ''">1.1.0</CodeAnalysisAnalyzersVersion>
<!-- If we're on Visual Studio 2015 RTM and we still have Roslyn installed, our VSIX-producing packages are not going to install, so let's not even try.
Update 1 supports this, so we'll use csi.exe's existence as a simple proxy for whether we have Update 1 installed or not. -->
......
......@@ -331,6 +331,7 @@ private bool Includes(int val)
// Foo(x, ref x) <-- x cannot be a stack local as it is used in different contexts.
internal enum ExprContext
{
None,
Sideeffects,
Value,
Address,
......@@ -494,6 +495,7 @@ protected override BoundExpression VisitExpressionWithoutStackGuard(BoundExpress
private void PushEvalStack(BoundExpression result, ExprContext context)
{
Debug.Assert(result != null || context == ExprContext.None);
_evalStack.Add(ValueTuple.Create(result, context));
}
......@@ -879,7 +881,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
if (mayPushReceiver)
{
// push unknown value just to prevent access to stack locals.
PushEvalStack(null, ExprContext.Address);
PushEvalStack(null, ExprContext.None);
}
right = VisitExpression(node.Right, rhsContext);
......@@ -1326,7 +1328,7 @@ public override BoundNode VisitComplexConditionalReceiver(BoundComplexConditiona
var origStack = StackDepth();
PushEvalStack(null, ExprContext.Value);
PushEvalStack(null, ExprContext.None);
var cookie = GetStackStateCookie(); // implicit goto here
......@@ -1446,7 +1448,7 @@ public override BoundNode VisitCatchBlock(BoundCatchBlock node)
if (exceptionSourceOpt != null)
{
// runtime pushes the exception object
PushEvalStack(null, ExprContext.Value);
PushEvalStack(null, ExprContext.None);
_counter++;
// We consume it by writing into the exception source.
......
......@@ -4,11 +4,12 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics.CSharp;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
......@@ -1133,5 +1134,77 @@ class D
Diagnostic(CSharpCodeBlockObjectCreationAnalyzer.DiagnosticDescriptor.Id, "new C()").WithLocation(5, 18)
});
}
private static Compilation GetCompilationWithConcurrentBuildEnabled(string source)
{
var compilation = CreateCompilationWithMscorlib45(source);
// NOTE: We set the concurrentBuild option to true after creating the compilation as CreateCompilationWithMscorlib
// always sets concurrentBuild to false if debugger is attached, even if we had passed options with concurrentBuild = true to that API.
// We want the tests using GetCompilationWithConcurrentBuildEnabled to have identical behavior with and without debugger being attached.
var options = compilation.Options.WithConcurrentBuild(true);
return compilation.WithOptions(options);
}
[Fact, WorkItem(6737, "https://github.com/dotnet/roslyn/issues/6737")]
public void TestNonConcurrentAnalyzer()
{
var builder = new StringBuilder();
var typeCount = 100;
for (int i = 1; i <= typeCount; i++)
{
var typeName = $"C{i}";
builder.Append($"\r\nclass {typeName} {{ }}");
}
var source = builder.ToString();
var analyzers = new DiagnosticAnalyzer[] { new NonConcurrentAnalyzer() };
// Verify no diagnostics.
var compilation = GetCompilationWithConcurrentBuildEnabled(source);
compilation.VerifyDiagnostics();
compilation.VerifyAnalyzerDiagnostics(analyzers);
}
[Fact, WorkItem(6737, "https://github.com/dotnet/roslyn/issues/6737")]
public void TestConcurrentAnalyzer()
{
if (Environment.ProcessorCount <= 1)
{
// Don't test for non-concurrent environment.
return;
}
var builder = new StringBuilder();
var typeCount = 100;
var typeNames = new string[typeCount];
for (int i = 1; i <= typeCount; i++)
{
var typeName = $"C{i}";
typeNames[i - 1] = typeName;
builder.Append($"\r\nclass {typeName} {{ }}");
}
var source = builder.ToString();
var compilation = GetCompilationWithConcurrentBuildEnabled(source);
compilation.VerifyDiagnostics();
// Verify analyzer diagnostics for Concurrent analyzer only.
var analyzers = new DiagnosticAnalyzer[] { new ConcurrentAnalyzer(typeNames) };
var expected = new DiagnosticDescription[typeCount];
for (int i = 0; i < typeCount; i++)
{
var typeName = $"C{i + 1}";
expected[i] = Diagnostic(ConcurrentAnalyzer.Descriptor.Id, typeName)
.WithArguments(typeName)
.WithLocation(i + 2, 7);
}
compilation.VerifyAnalyzerDiagnostics(analyzers, expected: expected);
// Verify analyzer diagnostics for Concurrent and NonConcurrent analyzer together (latter reports diagnostics only for error cases).
analyzers = new DiagnosticAnalyzer[] { new ConcurrentAnalyzer(typeNames), new NonConcurrentAnalyzer() };
compilation.VerifyAnalyzerDiagnostics(analyzers, expected: expected);
}
}
}
......@@ -38,6 +38,11 @@ internal abstract partial class AnalyzerDriver : IDisposable
private ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<CompilationAnalyzerAction>> _compilationActionsMap;
private ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<CompilationAnalyzerAction>> _compilationEndActionsMap;
/// <summary>
/// Map from non-concurrent analyzers to the gate guarding callback into the analyzer.
/// </summary>
private ImmutableDictionary<DiagnosticAnalyzer, SemaphoreSlim> _analyzerGateMap = ImmutableDictionary<DiagnosticAnalyzer, SemaphoreSlim>.Empty;
/// <summary>
/// Driver task which initializes all analyzers.
/// This task is initialized and executed only once at start of analysis.
......@@ -95,16 +100,17 @@ private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagn
this.DiagnosticQueue = diagnosticQueue;
// Compute the set of effective actions based on suppression, and running the initial analyzers
var analyzerActionsTask = GetAnalyzerActionsAsync(analyzers, analyzerManager, analyzerExecutor);
_initializeTask = analyzerActionsTask.ContinueWith(t =>
_initializeTask = Task.Run(async () =>
{
this.analyzerActions = t.Result;
this.analyzerActions = await GetAnalyzerActionsAsync(analyzers, analyzerManager, analyzerExecutor).ConfigureAwait(false);
this._analyzerGateMap = await GetAnalyzerGateMapAsync(analyzers, analyzerManager, analyzerExecutor).ConfigureAwait(false);
_symbolActionsByKind = MakeSymbolActionsByKind();
_semanticModelActionsMap = MakeSemanticModelActionsByAnalyzer();
_syntaxTreeActionsMap = MakeSyntaxTreeActionsByAnalyzer();
_compilationActionsMap = MakeCompilationActionsByAnalyzer(this.analyzerActions.CompilationActions);
_compilationEndActionsMap = MakeCompilationActionsByAnalyzer(this.analyzerActions.CompilationEndActions);
}, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Default);
}, cancellationToken);
// create the primary driver task.
cancellationToken.ThrowIfCancellationRequested();
......@@ -158,9 +164,6 @@ private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagn
newOnAnalyzerException = (ex, analyzer, diagnostic) => addDiagnostic(diagnostic);
}
// Assume all analyzers are non-thread safe.
var singleThreadedAnalyzerToGateMap = ImmutableDictionary.CreateRange(analyzers.Select(a => KeyValuePair.Create(a, new SemaphoreSlim(initialCount: 1))));
if (analysisOptions.LogAnalyzerExecutionTime)
{
// If we are reporting detailed analyzer performance numbers, then do a dummy invocation of Compilation.GetTypeByMetadataName API upfront.
......@@ -168,13 +171,26 @@ private void Initialize(AnalyzerExecutor analyzerExecutor, DiagnosticQueue diagn
var unused = compilation.GetTypeByMetadataName("System.Object");
}
Func<DiagnosticAnalyzer, SemaphoreSlim> getAnalyzerGate = analyzer => singleThreadedAnalyzerToGateMap[analyzer];
var analyzerExecutor = AnalyzerExecutor.Create(compilation, analysisOptions.Options ?? AnalyzerOptions.Empty, addDiagnostic, newOnAnalyzerException, IsCompilerAnalyzer,
analyzerManager, getAnalyzerGate, analysisOptions.LogAnalyzerExecutionTime, addLocalDiagnosticOpt, addNonLocalDiagnosticOpt, cancellationToken);
analyzerManager, GetAnalyzerGate, analysisOptions.LogAnalyzerExecutionTime, addLocalDiagnosticOpt, addNonLocalDiagnosticOpt, cancellationToken);
Initialize(analyzerExecutor, diagnosticQueue, cancellationToken);
}
private SemaphoreSlim GetAnalyzerGate(DiagnosticAnalyzer analyzer)
{
SemaphoreSlim gate;
if (_analyzerGateMap.TryGetValue(analyzer, out gate))
{
// Non-concurrent analyzer, needs all the callbacks guarded by a gate.
Debug.Assert(gate != null);
return gate;
}
// Concurrent analyzer.
return null;
}
/// <summary>
/// Attaches a pre-populated event queue to the driver and processes all events in the queue.
/// </summary>
......@@ -530,7 +546,8 @@ private async Task ProcessCompilationEventsAsync(AnalysisScope analysisScope, An
var workerTasks = new Task<CompilationCompletedEvent>[workerCount];
for (int i = 0; i < workerCount; i++)
{
workerTasks[i] = ProcessCompilationEventsCoreAsync(analysisScope, analysisStateOpt, prePopulatedEventQueue, cancellationToken);
// Create separate worker tasks to process all compilation events - we do not want to process any events on the main thread.
workerTasks[i] = Task.Run(async () => await ProcessCompilationEventsCoreAsync(analysisScope, analysisStateOpt, prePopulatedEventQueue, cancellationToken).ConfigureAwait(false));
}
cancellationToken.ThrowIfCancellationRequested();
......@@ -845,28 +862,45 @@ private static Diagnostic GetFilteredDiagnostic(Diagnostic diagnostic, Compilati
return compilation.Options.FilterDiagnostic(diagnostic);
}
private static Task<AnalyzerActions> GetAnalyzerActionsAsync(
private static async Task<AnalyzerActions> GetAnalyzerActionsAsync(
ImmutableArray<DiagnosticAnalyzer> analyzers,
AnalyzerManager analyzerManager,
AnalyzerExecutor analyzerExecutor)
{
return Task.Run(async () =>
var allAnalyzerActions = new AnalyzerActions();
foreach (var analyzer in analyzers)
{
AnalyzerActions allAnalyzerActions = new AnalyzerActions();
foreach (var analyzer in analyzers)
if (!IsDiagnosticAnalyzerSuppressed(analyzer, analyzerExecutor.Compilation.Options, analyzerManager, analyzerExecutor))
{
if (!IsDiagnosticAnalyzerSuppressed(analyzer, analyzerExecutor.Compilation.Options, analyzerManager, analyzerExecutor))
var analyzerActions = await analyzerManager.GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false);
if (analyzerActions != null)
{
var analyzerActions = await analyzerManager.GetAnalyzerActionsAsync(analyzer, analyzerExecutor).ConfigureAwait(false);
if (analyzerActions != null)
{
allAnalyzerActions = allAnalyzerActions.Append(analyzerActions);
}
allAnalyzerActions = allAnalyzerActions.Append(analyzerActions);
}
}
}
return allAnalyzerActions;
}
return allAnalyzerActions;
}, analyzerExecutor.CancellationToken);
private static async Task<ImmutableDictionary<DiagnosticAnalyzer, SemaphoreSlim>> GetAnalyzerGateMapAsync(
ImmutableArray<DiagnosticAnalyzer> analyzers,
AnalyzerManager analyzerManager,
AnalyzerExecutor analyzerExecutor)
{
var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, SemaphoreSlim>();
foreach (var analyzer in analyzers)
{
var isConcurrent = await analyzerManager.IsConcurrentAnalyzerAsync(analyzer, analyzerExecutor).ConfigureAwait(false);
if (!isConcurrent)
{
// Non-concurrent analyzers need their action callbacks from the analyzer driver to be guarded by a gate.
var gate = new SemaphoreSlim(initialCount: 1);
builder.Add(analyzer, gate);
}
}
return builder.ToImmutable();
}
internal async Task<AnalyzerActionCounts> GetAnalyzerActionCountsAsync(DiagnosticAnalyzer analyzer, CancellationToken cancellationToken)
......
......@@ -148,6 +148,15 @@ public async Task<AnalyzerActions> GetAnalyzerActionsAsync(DiagnosticAnalyzer an
return sessionScope.GetAnalyzerActions(analyzer);
}
/// <summary>
/// Returns true if the given analyzer has enabled concurrent execution by invoking <see cref="AnalysisContext.EnableConcurrentExecution"/>.
/// </summary>
public async Task<bool> IsConcurrentAnalyzerAsync(DiagnosticAnalyzer analyzer, AnalyzerExecutor analyzerExecutor)
{
var sessionScope = await GetSessionAnalysisScopeAsync(analyzer, analyzerExecutor).ConfigureAwait(false);
return sessionScope.IsConcurrentAnalyzer(analyzer);
}
/// <summary>
/// Return <see cref="DiagnosticAnalyzer.SupportedDiagnostics"/> of given <paramref name="analyzer"/>.
/// </summary>
......
......@@ -162,6 +162,18 @@ public void RegisterOperationAction(Action<OperationAnalysisContext> action, par
/// <param name="action">Action to be executed at completion of semantic analysis of an <see cref="IOperation"/>.</param>
/// <param name="operationKinds">Action will be executed only if an <see cref="IOperation"/>'s Kind matches one of the operation kind values.</param>
public abstract void RegisterOperationAction(Action<OperationAnalysisContext> action, ImmutableArray<OperationKind> operationKinds);
/// <summary>
/// Enable concurrent execution of analyzer actions registered by this analyzer.
/// An analyzer that registers for concurrent execution can have better performance than a non-concurrent analyzer.
/// However, such an analyzer must ensure that its actions can execute correctly in parallel.
/// </summary>
/// <remarks>
/// Even when an analyzer registers for concurrent execution, certain related actions are *never* executed concurrently.
/// For example, end actions registered on any analysis unit (compilation, code block, operation block, etc.) are by definition semantically dependent on analysis from non-end actions registered on the same analysis unit.
/// Hence, end actions are never executed concurrently with non-end actions operating on the same analysis unit.
/// </remarks>
public abstract void EnableConcurrentExecution();
}
/// <summary>
......
......@@ -74,19 +74,24 @@ public override void RegisterSyntaxNodeAction<TLanguageKindEnum>(Action<SyntaxNo
public override void RegisterOperationAction(Action<OperationAnalysisContext> action, ImmutableArray<OperationKind> operationKinds)
{
DiagnosticAnalysisContextHelpers.VerifyArguments(action, operationKinds);
_scope.RegisterOperationAction(this._analyzer, action, operationKinds);
_scope.RegisterOperationAction(_analyzer, action, operationKinds);
}
public override void RegisterOperationBlockStartAction(Action<OperationBlockStartAnalysisContext> action)
{
DiagnosticAnalysisContextHelpers.VerifyArguments(action);
_scope.RegisterOperationBlockStartAction(this._analyzer, action);
_scope.RegisterOperationBlockStartAction(_analyzer, action);
}
public override void RegisterOperationBlockAction(Action<OperationBlockAnalysisContext> action)
{
DiagnosticAnalysisContextHelpers.VerifyArguments(action);
_scope.RegisterOperationBlockAction(this._analyzer, action);
_scope.RegisterOperationBlockAction(_analyzer, action);
}
public override void EnableConcurrentExecution()
{
_scope.EnableConcurrentExecution(_analyzer);
}
}
......@@ -226,18 +231,29 @@ public override void RegisterOperationAction(Action<OperationAnalysisContext> ac
internal sealed class HostSessionStartAnalysisScope : HostAnalysisScope
{
private ImmutableArray<CompilationStartAnalyzerAction> _compilationStartActions = ImmutableArray<CompilationStartAnalyzerAction>.Empty;
private ImmutableHashSet<DiagnosticAnalyzer> _concurrentAnalyzers = ImmutableHashSet<DiagnosticAnalyzer>.Empty;
public ImmutableArray<CompilationStartAnalyzerAction> CompilationStartActions
{
get { return _compilationStartActions; }
}
public bool IsConcurrentAnalyzer(DiagnosticAnalyzer analyzer)
{
return _concurrentAnalyzers.Contains(analyzer);
}
public void RegisterCompilationStartAction(DiagnosticAnalyzer analyzer, Action<CompilationStartAnalysisContext> action)
{
CompilationStartAnalyzerAction analyzerAction = new CompilationStartAnalyzerAction(action, analyzer);
this.GetOrCreateAnalyzerActions(analyzer).AddCompilationStartAction(analyzerAction);
_compilationStartActions = _compilationStartActions.Add(analyzerAction);
}
public void EnableConcurrentExecution(DiagnosticAnalyzer analyzer)
{
_concurrentAnalyzers = _concurrentAnalyzers.Add(analyzer);
}
}
/// <summary>
......@@ -567,6 +583,7 @@ public void RegisterOperationBlockAction(DiagnosticAnalyzer analyzer, Action<Ope
this.GetOrCreateAnalyzerActions(analyzer).AddOperationBlockAction(analyzerAction);
_operationBlockActions = _operationBlockActions.Add(analyzerAction);
}
public void RegisterOperationAction(DiagnosticAnalyzer analyzer, Action<OperationAnalysisContext> action, ImmutableArray<OperationKind> operationKinds)
{
OperationAnalyzerAction analyzerAction = new OperationAnalyzerAction(action, operationKinds, analyzer);
......@@ -766,7 +783,7 @@ internal void AddOperationBlockEndAction(OperationBlockAnalyzerAction action)
internal void AddOperationAction(OperationAnalyzerAction action)
{
this._operationActions = this._operationActions.Add(action);
_operationActions = _operationActions.Add(action);
}
/// <summary>
......
......@@ -144,6 +144,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen
End Function
Private Sub PushEvalStack(result As BoundExpression, context As ExprContext)
Debug.Assert(result IsNot Nothing OrElse context = ExprContext.None)
_evalStack.Add(ValueTuple.Create(result, context))
End Sub
......@@ -586,7 +587,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGen
If mayPushReceiver Then
'push unknown value just to prevent access to stack locals.
PushEvalStack(Nothing, ExprContext.Address)
PushEvalStack(Nothing, ExprContext.None)
End If
right = VisitExpression(node.Right, rhsContext)
......
......@@ -13470,6 +13470,98 @@ End Module
IL_002d: call "Sub System.Console.WriteLine(Integer)"
IL_0032: ret
}
]]>)
End Sub
<Fact, WorkItem(7148, "https://github.com/dotnet/roslyn/issues/7148")>
Public Sub Issue7148_1()
Dim c = CompileAndVerify(
<compilation>
<file name="a.vb">
Public Class TestClass
Private _rotation As Decimal
Private Sub CalculateDimensions()
_rotation *= 180 / System.Math.PI 'This line causes '"vbc.exe" exited with code -2146232797'
End Sub
Shared Sub Main()
Dim x as New TestClass()
x._rotation = 1
x.CalculateDimensions()
System.Console.WriteLine(x._rotation)
End Sub
End Class
</file>
</compilation>, options:=TestOptions.ReleaseExe,
expectedOutput:="57.2957795130823")
c.VerifyIL("TestClass.CalculateDimensions",
<![CDATA[
{
// Code size 40 (0x28)
.maxstack 3
.locals init (Decimal& V_0)
IL_0000: ldarg.0
IL_0001: ldflda "TestClass._rotation As Decimal"
IL_0006: dup
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldobj "Decimal"
IL_000e: call "Function System.Convert.ToDouble(Decimal) As Double"
IL_0013: ldc.r8 57.2957795130823
IL_001c: mul
IL_001d: newobj "Sub Decimal..ctor(Double)"
IL_0022: stobj "Decimal"
IL_0027: ret
}
]]>)
End Sub
<Fact, WorkItem(7148, "https://github.com/dotnet/roslyn/issues/7148")>
Public Sub Issue7148_2()
Dim c = CompileAndVerify(
<compilation>
<file name="a.vb">
Public Class TestClass
Private Shared Sub CalculateDimensions(_rotation As Decimal())
_rotation(GetIndex()) *= 180 / System.Math.PI 'This line causes '"vbc.exe" exited with code -2146232797'
End Sub
Private Shared Function GetIndex() As Integer
Return 0
End Function
Shared Sub Main()
Dim _rotation(0) as Decimal
_rotation(0) = 1
CalculateDimensions(_rotation)
System.Console.WriteLine(_rotation(0))
End Sub
End Class
</file>
</compilation>, options:=TestOptions.ReleaseExe,
expectedOutput:="57.2957795130823")
c.VerifyIL("TestClass.CalculateDimensions",
<![CDATA[
{
// Code size 45 (0x2d)
.maxstack 3
.locals init (Decimal& V_0)
IL_0000: ldarg.0
IL_0001: call "Function TestClass.GetIndex() As Integer"
IL_0006: ldelema "Decimal"
IL_000b: dup
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: ldobj "Decimal"
IL_0013: call "Function System.Convert.ToDouble(Decimal) As Double"
IL_0018: ldc.r8 57.2957795130823
IL_0021: mul
IL_0022: newobj "Sub Decimal..ctor(Double)"
IL_0027: stobj "Decimal"
IL_002c: ret
}
]]>)
End Sub
......
......@@ -4,10 +4,11 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
using System.IO;
using Xunit;
namespace Microsoft.CodeAnalysis
{
......@@ -479,5 +480,120 @@ private void OnCompilation(CompilationAnalysisContext context)
}
}
}
/// <summary>
/// This analyzer is intended to be used only when concurrent execution is enabled for analyzers.
/// This analyzer will deadlock if the driver runs analyzers on a single thread OR takes a lock around callbacks into this analyzer to prevent concurrent analyzer execution
/// Former indicates a bug in the test using this analyzer and the latter indicates a bug in the analyzer driver.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public class ConcurrentAnalyzer : DiagnosticAnalyzer
{
private readonly ImmutableHashSet<string> _symbolNames;
private int _token;
public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
"ConcurrentAnalyzerId",
"Title",
"ConcurrentAnalyzerMessage for symbol '{0}'",
"Category",
DiagnosticSeverity.Warning,
true);
public ConcurrentAnalyzer(IEnumerable<string> symbolNames)
{
Assert.True(Environment.ProcessorCount > 1, "This analyzer is intended to be used only in a concurrent environment.");
_symbolNames = symbolNames.ToImmutableHashSet();
_token = 0;
}
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(this.OnCompilationStart);
// Enable concurrent action callbacks on analyzer.
context.EnableConcurrentExecution();
}
private void OnCompilationStart(CompilationStartAnalysisContext context)
{
Assert.True(context.Compilation.Options.ConcurrentBuild, "This analyzer is intended to be used only when concurrent build is enabled.");
var pendingSymbols = new ConcurrentSet<INamedTypeSymbol>();
foreach (var type in context.Compilation.GlobalNamespace.GetTypeMembers())
{
if (_symbolNames.Contains(type.Name))
{
pendingSymbols.Add(type);
}
}
context.RegisterSymbolAction(symbolContext =>
{
if (!pendingSymbols.Remove((INamedTypeSymbol)symbolContext.Symbol))
{
return;
}
var myToken = Interlocked.Increment(ref _token);
if (myToken == 1)
{
// Wait for all symbol callbacks to execute.
// This analyzer will deadlock if the driver doesn't attempt concurrent callbacks.
while (pendingSymbols.Any())
{
Thread.Sleep(10);
}
}
// ok, now report diagnostic on the symbol.
var diagnostic = Diagnostic.Create(Descriptor, symbolContext.Symbol.Locations[0], symbolContext.Symbol.Name);
symbolContext.ReportDiagnostic(diagnostic);
}, SymbolKind.NamedType);
}
}
/// <summary>
/// This analyzer will report diagnostics only if it receives any concurrent action callbacks, which would be a
/// bug in the analyzer driver as this analyzer doesn't invoke <see cref="AnalysisContext.RegisterConcurrentExecution"/>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public class NonConcurrentAnalyzer : DiagnosticAnalyzer
{
public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
"NonConcurrentAnalyzerId",
"Title",
"Analyzer driver made concurrent action callbacks, when analyzer didn't register for concurrent execution",
"Category",
DiagnosticSeverity.Warning,
true);
private const int registeredActionCounts = 1000;
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Descriptor);
public override void Initialize(AnalysisContext context)
{
SemaphoreSlim gate = new SemaphoreSlim(initialCount: registeredActionCounts);
for (var i = 0; i < registeredActionCounts; i++)
{
context.RegisterSymbolAction(symbolContext =>
{
using (gate.DisposableWait(symbolContext.CancellationToken))
{
ReportDiagnosticIfActionInvokedConcurrently(gate, symbolContext);
}
}, SymbolKind.NamedType);
}
}
private void ReportDiagnosticIfActionInvokedConcurrently(SemaphoreSlim gate, SymbolAnalysisContext symbolContext)
{
if (gate.CurrentCount != registeredActionCounts - 1)
{
var diagnostic = Diagnostic.Create(Descriptor, symbolContext.Symbol.Locations[0]);
symbolContext.ReportDiagnostic(diagnostic);
}
}
}
}
}
\ No newline at end of file
......@@ -199,6 +199,11 @@ internal void OnCodeElementCreated(SyntaxNodeKey nodeKey, EnvDTE.CodeElement ele
_codeElementTable.Add(nodeKey, element);
}
internal void OnCodeElementDeleted(SyntaxNodeKey nodeKey)
{
_codeElementTable.Remove(nodeKey);
}
internal T GetOrCreateCodeElement<T>(SyntaxNode node)
{
var nodeKey = CodeModelService.TryGetNodeKey(node);
......@@ -612,7 +617,7 @@ public void Remove(object element)
if (codeElement == null)
{
throw new ArgumentException(ServicesVSResources.ElementIsNotValid, "element");
throw new ArgumentException(ServicesVSResources.ElementIsNotValid, nameof(element));
}
codeElement.Delete();
......
......@@ -249,6 +249,12 @@ public virtual void RenameSymbol(string newName)
CodeModelService.Rename(LookupSymbol(), newName, this.Workspace.CurrentSolution);
}
protected virtual Document DeleteCore(Document document)
{
var node = LookupNode();
return CodeModelService.Delete(document, node);
}
/// <summary>
/// Delete the element from the source file.
/// </summary>
......@@ -256,8 +262,7 @@ internal void Delete()
{
FileCodeModel.PerformEdit(document =>
{
var node = LookupNode();
return CodeModelService.Delete(document, node);
return DeleteCore(document);
});
}
......
......@@ -95,6 +95,15 @@ protected void UpdateNodeAndReacquireNodeKey<T>(Action<SyntaxNode, T> updater, T
});
}
protected override Document DeleteCore(Document document)
{
var result = base.DeleteCore(document);
FileCodeModel.OnCodeElementDeleted(_nodeKey);
return result;
}
protected override string GetName()
{
if (IsUnknown)
......
......@@ -3812,6 +3812,24 @@ class $$C : Generic&lt;string&gt;
Await TestGetBaseName(code, "N.M.Generic<string>")
End Function
<ConditionalWpfFact(GetType(x86)), Trait(Traits.Feature, Traits.Features.CodeModel)>
Public Async Function TestAddDeleteManyTimes() As Task
Dim code =
<Code>
class C$$
{
}
</Code>
Await TestElement(code,
Sub(codeClass)
For i = 1 To 100
Dim variable = codeClass.AddVariable("x", "System.Int32")
codeClass.RemoveMember(variable)
Next
End Sub)
End Function
<ConditionalWpfFact(GetType(x86)), Trait(Traits.Feature, Traits.Features.CodeModel)>
Public Async Function TestTypeDescriptor_GetProperties() As Task
Dim code =
......
......@@ -3124,6 +3124,23 @@ End Class
Await TestGetBaseName(code, "N.M.Generic(Of String)")
End Function
<ConditionalWpfFact(GetType(x86)), Trait(Traits.Feature, Traits.Features.CodeModel)>
Public Async Function TestAddDeleteManyTimes() As Task
Dim code =
<Code>
Class C$$
End Class
</Code>
Await TestElement(code,
Sub(codeClass)
For i = 1 To 100
Dim variable = codeClass.AddVariable("x", "System.Int32")
codeClass.RemoveMember(variable)
Next
End Sub)
End Function
<ConditionalWpfFact(GetType(x86)), Trait(Traits.Feature, Traits.Features.CodeModel)>
Public Async Function TestExternalClass_ImplementedInterfaces() As Task
Dim code =
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册