// 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.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics.Telemetry;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
{
///
/// Stores the current partial analysis state for an analyzer.
///
internal partial class AnalysisState
{
private class PerAnalyzerState
{
private const int SymbolLimitForPooling = 100;
private readonly object _gate = new object();
private readonly Dictionary _pendingEvents = new Dictionary();
private readonly Dictionary _pendingSymbols = new Dictionary();
private readonly Dictionary> _pendingDeclarations = new Dictionary>();
private Dictionary _lazySyntaxTreesWithAnalysisData = null;
private int _pendingSyntaxAnalysisTreesCount = 0;
private readonly ObjectPool _analyzerStateDataPool;
private readonly ObjectPool _declarationAnalyzerStateDataPool;
private readonly ObjectPool> _currentlyAnalyzingDeclarationsMapPool;
public PerAnalyzerState(
ObjectPool analyzerStateDataPool,
ObjectPool declarationAnalyzerStateDataPool,
ObjectPool> currentlyAnalyzingDeclarationsMapPool)
{
_analyzerStateDataPool = analyzerStateDataPool;
_declarationAnalyzerStateDataPool = declarationAnalyzerStateDataPool;
_currentlyAnalyzingDeclarationsMapPool = currentlyAnalyzingDeclarationsMapPool;
}
///
/// Returns true if the object should be returned to the pool.
///
public bool Free()
{
lock (_gate)
{
foreach (var analyzerStateData in _pendingEvents.Values)
{
FreeState_NoLock(analyzerStateData, _analyzerStateDataPool);
}
foreach (var analyzerStateData in _pendingSymbols.Values)
{
FreeState_NoLock(analyzerStateData, _analyzerStateDataPool);
}
foreach (var declarationDataMap in _pendingDeclarations.Values)
{
foreach (var declarationStateData in declarationDataMap.Values)
{
FreeDeclarationAnalyzerState_NoLock(declarationStateData);
}
FreeDeclarationDataMap_NoLock(declarationDataMap);
}
// If we have too many symbols then just discard the state object from the pool - we don't want to hold onto really large dictionaries.
if (_pendingSymbols.Count > SymbolLimitForPooling)
{
return false;
}
_pendingEvents.Clear();
_pendingSymbols.Clear();
_pendingDeclarations.Clear();
_lazySyntaxTreesWithAnalysisData = null;
_pendingSyntaxAnalysisTreesCount = 0;
return true;
}
}
public void AddPendingEvents(HashSet uniqueEvents)
{
lock (_gate)
{
foreach (var pendingEvent in _pendingEvents.Keys)
{
uniqueEvents.Add(pendingEvent);
}
}
}
public bool HasPendingSyntaxAnalysis(SyntaxTree treeOpt)
{
lock (_gate)
{
if (_pendingSyntaxAnalysisTreesCount == 0)
{
return false;
}
Debug.Assert(_lazySyntaxTreesWithAnalysisData != null);
if (treeOpt == null)
{
// We have syntax analysis pending for at least one tree.
return true;
}
AnalyzerStateData state;
if (!_lazySyntaxTreesWithAnalysisData.TryGetValue(treeOpt, out state))
{
// We haven't even started analysis for this tree.
return true;
}
// See if we have completed analysis for this tree.
return state.StateKind == StateKind.FullyProcessed;
}
}
public bool HasPendingSymbolAnalysis(ISymbol symbol)
{
lock (_gate)
{
return _pendingSymbols.ContainsKey(symbol);
}
}
private bool TryStartProcessingEntity(TAnalysisEntity analysisEntity, Dictionary pendingEntities, ObjectPool pool, out TAnalyzerStateData newState)
where TAnalyzerStateData : AnalyzerStateData, new()
{
lock (_gate)
{
return TryStartProcessingEntity_NoLock(analysisEntity, pendingEntities, pool, out newState);
}
}
private static bool TryStartProcessingEntity_NoLock(TAnalysisEntity analysisEntity, Dictionary pendingEntities, ObjectPool pool, out TAnalyzerStateData state)
where TAnalyzerStateData : AnalyzerStateData
{
if (pendingEntities.TryGetValue(analysisEntity, out state) &&
(state == null || state.StateKind == StateKind.ReadyToProcess))
{
if (state == null)
{
state = pool.Allocate();
}
state.SetStateKind(StateKind.InProcess);
Debug.Assert(state.StateKind == StateKind.InProcess);
pendingEntities[analysisEntity] = state;
return true;
}
state = null;
return false;
}
private void MarkEntityProcessed(TAnalysisEntity analysisEntity, Dictionary pendingEntities, ObjectPool pool)
where TAnalyzerStateData : AnalyzerStateData
{
lock (_gate)
{
MarkEntityProcessed_NoLock(analysisEntity, pendingEntities, pool);
}
}
private static void MarkEntityProcessed_NoLock(TAnalysisEntity analysisEntity, Dictionary pendingEntities, ObjectPool pool)
where TAnalyzerStateData : AnalyzerStateData
{
TAnalyzerStateData state;
if (pendingEntities.TryGetValue(analysisEntity, out state))
{
pendingEntities.Remove(analysisEntity);
FreeState_NoLock(state, pool);
}
}
private bool TryStartSyntaxAnalysis_NoLock(SyntaxTree tree, out AnalyzerStateData state)
{
if (_pendingSyntaxAnalysisTreesCount == 0)
{
state = null;
return false;
}
if (_lazySyntaxTreesWithAnalysisData.TryGetValue(tree, out state))
{
if (state.StateKind != StateKind.ReadyToProcess)
{
state = null;
return false;
}
}
else
{
state = _analyzerStateDataPool.Allocate();
}
state.SetStateKind(StateKind.InProcess);
Debug.Assert(state.StateKind == StateKind.InProcess);
_lazySyntaxTreesWithAnalysisData[tree] = state;
return true;
}
private void MarkSyntaxTreeProcessed_NoLock(SyntaxTree tree)
{
if (_pendingSyntaxAnalysisTreesCount == 0)
{
return;
}
Debug.Assert(_lazySyntaxTreesWithAnalysisData != null);
var wasAlreadyFullyProcessed = false;
AnalyzerStateData state;
if (_lazySyntaxTreesWithAnalysisData.TryGetValue(tree, out state))
{
if (state.StateKind != StateKind.FullyProcessed)
{
FreeState_NoLock(state, _analyzerStateDataPool);
}
else
{
wasAlreadyFullyProcessed = true;
}
}
if (!wasAlreadyFullyProcessed)
{
_pendingSyntaxAnalysisTreesCount--;
}
_lazySyntaxTreesWithAnalysisData[tree] = AnalyzerStateData.FullyProcessedInstance;
}
private bool TryStartAnalyzingDeclaration_NoLock(ISymbol symbol, int declarationIndex, out DeclarationAnalyzerStateData state)
{
Dictionary declarationDataMap;
if (!_pendingDeclarations.TryGetValue(symbol, out declarationDataMap))
{
state = null;
return false;
}
if (declarationDataMap.TryGetValue(declarationIndex, out state))
{
if (state.StateKind != StateKind.ReadyToProcess)
{
state = null;
return false;
}
}
else
{
state = _declarationAnalyzerStateDataPool.Allocate();
}
state.SetStateKind(StateKind.InProcess);
Debug.Assert(state.StateKind == StateKind.InProcess);
declarationDataMap[declarationIndex] = state;
return true;
}
private void MarkDeclarationProcessed(ISymbol symbol, int declarationIndex)
{
lock (_gate)
{
MarkDeclarationProcessed_NoLock(symbol, declarationIndex);
}
}
private void MarkDeclarationProcessed_NoLock(ISymbol symbol, int declarationIndex)
{
Dictionary declarationDataMap;
if (!_pendingDeclarations.TryGetValue(symbol, out declarationDataMap))
{
return;
}
DeclarationAnalyzerStateData state;
if (declarationDataMap.TryGetValue(declarationIndex, out state))
{
FreeDeclarationAnalyzerState_NoLock(state);
}
declarationDataMap[declarationIndex] = DeclarationAnalyzerStateData.FullyProcessedInstance;
}
private void MarkDeclarationsProcessed_NoLock(ISymbol symbol)
{
Dictionary declarationDataMap;
if (_pendingDeclarations.TryGetValue(symbol, out declarationDataMap))
{
FreeDeclarationDataMap_NoLock(declarationDataMap);
_pendingDeclarations.Remove(symbol);
}
}
private void FreeDeclarationDataMap_NoLock(Dictionary declarationDataMap)
{
declarationDataMap.Clear();
_currentlyAnalyzingDeclarationsMapPool.Free(declarationDataMap);
}
private void FreeDeclarationAnalyzerState_NoLock(DeclarationAnalyzerStateData state)
{
if (ReferenceEquals(state, DeclarationAnalyzerStateData.FullyProcessedInstance))
{
return;
}
FreeState_NoLock(state, _declarationAnalyzerStateDataPool);
}
private static void FreeState_NoLock(TAnalyzerStateData state, ObjectPool pool)
where TAnalyzerStateData : AnalyzerStateData
{
if (state != null && !ReferenceEquals(state, AnalyzerStateData.FullyProcessedInstance))
{
state.Free();
pool.Free(state);
}
}
private bool IsEntityFullyProcessed(TAnalysisEntity analysisEntity, Dictionary pendingEntities)
where TAnalyzerStateData : AnalyzerStateData
{
lock (_gate)
{
return IsEntityFullyProcessed_NoLock(analysisEntity, pendingEntities);
}
}
private static bool IsEntityFullyProcessed_NoLock(TAnalysisEntity analysisEntity, Dictionary pendingEntities)
where TAnalyzerStateData : AnalyzerStateData
{
TAnalyzerStateData state;
return !pendingEntities.TryGetValue(analysisEntity, out state) ||
state?.StateKind == StateKind.FullyProcessed;
}
private bool IsDeclarationComplete_NoLock(ISymbol symbol, int declarationIndex)
{
Dictionary declarationDataMap;
if (!_pendingDeclarations.TryGetValue(symbol, out declarationDataMap))
{
return true;
}
DeclarationAnalyzerStateData state;
if (!declarationDataMap.TryGetValue(declarationIndex, out state))
{
return false;
}
return state.StateKind == StateKind.FullyProcessed;
}
private bool AreDeclarationsProcessed_NoLock(ISymbol symbol, int declarationsCount)
{
Dictionary declarationDataMap;
if (!_pendingDeclarations.TryGetValue(symbol, out declarationDataMap))
{
return true;
}
return declarationDataMap.Count == declarationsCount &&
declarationDataMap.Values.All(state => state.StateKind == StateKind.FullyProcessed);
}
public bool TryStartProcessingEvent(CompilationEvent compilationEvent, out AnalyzerStateData state)
{
return TryStartProcessingEntity(compilationEvent, _pendingEvents, _analyzerStateDataPool, out state);
}
public void MarkEventComplete(CompilationEvent compilationEvent)
{
MarkEntityProcessed(compilationEvent, _pendingEvents, _analyzerStateDataPool);
}
public bool TryStartAnalyzingSymbol(ISymbol symbol, out AnalyzerStateData state)
{
return TryStartProcessingEntity(symbol, _pendingSymbols, _analyzerStateDataPool, out state);
}
public void MarkSymbolComplete(ISymbol symbol)
{
MarkEntityProcessed(symbol, _pendingSymbols, _analyzerStateDataPool);
}
public bool TryStartAnalyzingDeclaration(ISymbol symbol, int declarationIndex, out DeclarationAnalyzerStateData state)
{
lock (_gate)
{
return TryStartAnalyzingDeclaration_NoLock(symbol, declarationIndex, out state);
}
}
public bool IsDeclarationComplete(ISymbol symbol, int declarationIndex)
{
lock (_gate)
{
return IsDeclarationComplete_NoLock(symbol, declarationIndex);
}
}
public void MarkDeclarationComplete(ISymbol symbol, int declarationIndex)
{
lock (_gate)
{
MarkDeclarationProcessed_NoLock(symbol, declarationIndex);
}
}
public void MarkDeclarationsComplete(ISymbol symbol)
{
lock (_gate)
{
MarkDeclarationsProcessed_NoLock(symbol);
}
}
public bool TryStartSyntaxAnalysis(SyntaxTree tree, out AnalyzerStateData state)
{
lock (_gate)
{
Debug.Assert(_lazySyntaxTreesWithAnalysisData != null);
return TryStartSyntaxAnalysis_NoLock(tree, out state);
}
}
public void MarkSyntaxAnalysisComplete(SyntaxTree tree)
{
lock (_gate)
{
MarkSyntaxTreeProcessed_NoLock(tree);
}
}
public void OnCompilationEventGenerated(CompilationEvent compilationEvent, AnalyzerActionCounts actionCounts)
{
lock (_gate)
{
var symbolEvent = compilationEvent as SymbolDeclaredCompilationEvent;
if (symbolEvent != null)
{
var needsAnalysis = false;
var symbol = symbolEvent.Symbol;
if (!AnalysisScope.ShouldSkipSymbolAnalysis(symbolEvent) && actionCounts.SymbolActionsCount > 0)
{
needsAnalysis = true;
_pendingSymbols[symbol] = null;
}
if (!AnalysisScope.ShouldSkipDeclarationAnalysis(symbol) &&
actionCounts.HasAnyExecutableCodeActions)
{
needsAnalysis = true;
_pendingDeclarations[symbol] = _currentlyAnalyzingDeclarationsMapPool.Allocate();
}
if (!needsAnalysis)
{
return;
}
}
else if (compilationEvent is CompilationStartedEvent)
{
if (actionCounts.SyntaxTreeActionsCount > 0)
{
_lazySyntaxTreesWithAnalysisData = new Dictionary();
_pendingSyntaxAnalysisTreesCount = compilationEvent.Compilation.SyntaxTrees.Count();
}
if (actionCounts.CompilationActionsCount == 0)
{
return;
}
}
_pendingEvents[compilationEvent] = null;
}
}
public bool IsEventAnalyzed(CompilationEvent compilationEvent)
{
return IsEntityFullyProcessed(compilationEvent, _pendingEvents);
}
public void OnSymbolDeclaredEventProcessed(SymbolDeclaredCompilationEvent symbolDeclaredEvent)
{
lock (_gate)
{
OnSymbolDeclaredEventProcessed_NoLock(symbolDeclaredEvent);
}
}
private void OnSymbolDeclaredEventProcessed_NoLock(SymbolDeclaredCompilationEvent symbolDeclaredEvent)
{
// Check if the symbol event has been completely processed or not.
// Have the symbol actions executed?
if (!IsEntityFullyProcessed_NoLock(symbolDeclaredEvent.Symbol, _pendingSymbols))
{
return;
}
// Have the node/code block actions executed for all symbol declarations?
if (!AreDeclarationsProcessed_NoLock(symbolDeclaredEvent.Symbol, symbolDeclaredEvent.DeclaringSyntaxReferences.Length))
{
return;
}
// Mark the symbol event completely processed.
MarkEntityProcessed_NoLock(symbolDeclaredEvent, _pendingEvents, _analyzerStateDataPool);
// Mark declarations completely processed.
MarkDeclarationsProcessed_NoLock(symbolDeclaredEvent.Symbol);
}
}
}
}