// 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.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Roslyn.Utilities; using static Microsoft.CodeAnalysis.Diagnostics.AnalyzerDriver; namespace Microsoft.CodeAnalysis.Diagnostics { /// /// Stores the partial analysis state for analyzers executed on a specific compilation. /// internal partial class AnalysisState { private readonly object _gate; /// /// Per-analyzer analysis state map. /// The integer value points to the index within the array. /// private readonly ImmutableDictionary _analyzerStateMap; /// /// Per-analyzer analysis state. /// private readonly ImmutableArray _analyzerStates; /// /// Compilation events corresponding to source tree, that are not completely processed for all analyzers. /// Events are dropped as and when they are fully processed by all analyzers. /// private readonly Dictionary> _pendingSourceEvents; /// /// Compilation events corresponding to the compilation (compilation start and completed events), that are not completely processed for all analyzers. /// private readonly HashSet _pendingNonSourceEvents; /// /// Action counts per-analyzer. /// private ImmutableDictionary _lazyAnalyzerActionCountsMap; private readonly HashSet _treesWithGeneratedSourceEvents; private readonly HashSet _partialSymbolsWithGeneratedSourceEvents; private readonly CompilationData _compilationData; private bool _compilationStartGenerated; private bool _compilationEndGenerated; /// /// Cached semantic model for the compilation trees. /// PERF: This cache enables us to re-use semantic model's bound node cache across analyzer execution and diagnostic queries. /// private readonly ConditionalWeakTable _semanticModelsMap; private readonly ObjectPool> _compilationEventsPool; private readonly HashSet _pooledEventsWithAnyActionsSet; // Create static pools for heavily allocated per-analyzer state objects - this helps in reducing allocations across CompilationWithAnalyzer instances. private const int PoolSize = 5000; private static readonly ObjectPool s_analyzerStateDataPool = new ObjectPool(() => new AnalyzerStateData(), PoolSize); private static readonly ObjectPool s_declarationAnalyzerStateDataPool = new ObjectPool(() => new DeclarationAnalyzerStateData(), PoolSize); private static readonly ObjectPool> s_currentlyAnalyzingDeclarationsMapPool = new ObjectPool>(() => new Dictionary(), PoolSize); private static readonly ObjectPool s_perAnalyzerStatePool = new ObjectPool(() => new PerAnalyzerState(s_analyzerStateDataPool, s_declarationAnalyzerStateDataPool, s_currentlyAnalyzingDeclarationsMapPool), PoolSize); public AnalysisState(ImmutableArray analyzers, CompilationData compilationData) { _gate = new object(); _analyzerStateMap = CreateAnalyzerStateMap(analyzers, out _analyzerStates); _compilationData = compilationData; _pendingSourceEvents = new Dictionary>(); _pendingNonSourceEvents = new HashSet(); _lazyAnalyzerActionCountsMap = null; _semanticModelsMap = new ConditionalWeakTable(); _treesWithGeneratedSourceEvents = new HashSet(); _partialSymbolsWithGeneratedSourceEvents = new HashSet(); _compilationStartGenerated = false; _compilationEndGenerated = false; _compilationEventsPool = new ObjectPool>(() => new HashSet()); _pooledEventsWithAnyActionsSet = new HashSet(); } ~AnalysisState() { // Free the per-analyzer state tracking objects. foreach (var analyzerState in _analyzerStates) { var shouldReturnToPool = analyzerState.Free(); // 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 (shouldReturnToPool) { s_perAnalyzerStatePool.Free(analyzerState); } else { s_perAnalyzerStatePool.ForgetTrackedObject(analyzerState); } } } private static ImmutableDictionary CreateAnalyzerStateMap(ImmutableArray analyzers, out ImmutableArray analyzerStates) { var statesBuilder = ImmutableArray.CreateBuilder(); var map = ImmutableDictionary.CreateBuilder(); var index = 0; foreach (var analyzer in analyzers) { statesBuilder.Add(s_perAnalyzerStatePool.Allocate()); map[analyzer] = index; index++; } analyzerStates = statesBuilder.ToImmutable(); return map.ToImmutable(); } private PerAnalyzerState GetAnalyzerState(DiagnosticAnalyzer analyzer) { var index = _analyzerStateMap[analyzer]; return _analyzerStates[index]; } public async Task GenerateSimulatedCompilationEventsAsync( AnalysisScope analysisScope, Compilation compilation, Func getCachedSemanticModel, AnalyzerDriver driver, CancellationToken cancellationToken) { await EnsureAnalyzerActionCountsInitializedAsync(driver, cancellationToken).ConfigureAwait(false); // Compilation started event. GenerateSimulatedCompilatioNonSourceEvent(compilation, driver, started: true, cancellationToken: cancellationToken); // Symbol declared and compilation unit completed events. foreach (var tree in analysisScope.SyntaxTrees) { GenerateSimulatedCompilationSourceEvents(tree, compilation, getCachedSemanticModel, driver, cancellationToken); } // Compilation ended event. if (analysisScope.FilterTreeOpt == null) { GenerateSimulatedCompilatioNonSourceEvent(compilation, driver, started: false, cancellationToken: cancellationToken); } } private void GenerateSimulatedCompilationSourceEvents( SyntaxTree tree, Compilation compilation, Func getCachedSemanticModel, AnalyzerDriver driver, CancellationToken cancellationToken) { lock (_gate) { if (_treesWithGeneratedSourceEvents.Contains(tree)) { return; } } var globalNs = compilation.Assembly.GlobalNamespace; var symbols = GetDeclaredSymbolsInTree(tree, compilation, getCachedSemanticModel, cancellationToken); var compilationEvents = CreateCompilationEventsForTree(symbols.Concat(globalNs), tree, compilation); lock (_gate) { if (_treesWithGeneratedSourceEvents.Contains(tree)) { return; } OnCompilationEventsGenerated_NoLock(compilationEvents, tree, driver, cancellationToken); var added = _treesWithGeneratedSourceEvents.Add(tree); Debug.Assert(added); } } private IEnumerable GetDeclaredSymbolsInTree( SyntaxTree tree, Compilation compilation, Func getCachedSemanticModel, CancellationToken cancellationToken) { var model = getCachedSemanticModel(tree, compilation, cancellationToken); var fullSpan = tree.GetRoot(cancellationToken).FullSpan; var declarationInfos = new List(); model.ComputeDeclarationsInSpan(fullSpan, getSymbol: true, builder: declarationInfos, cancellationToken: cancellationToken); return declarationInfos.Select(declInfo => declInfo.DeclaredSymbol).WhereNotNull(); } private static ImmutableArray CreateCompilationEventsForTree(IEnumerable declaredSymbols, SyntaxTree tree, Compilation compilation) { var builder = ImmutableArray.CreateBuilder(); foreach (var symbol in declaredSymbols) { Debug.Assert(symbol.ContainingAssembly == compilation.Assembly); builder.Add(new SymbolDeclaredCompilationEvent(compilation, symbol)); } builder.Add(new CompilationUnitCompletedEvent(compilation, tree)); return builder.ToImmutable(); } private void GenerateSimulatedCompilatioNonSourceEvent(Compilation compilation, AnalyzerDriver driver, bool started, CancellationToken cancellationToken) { lock (_gate) { var eventAlreadyGenerated = started ? _compilationStartGenerated : _compilationEndGenerated; if (eventAlreadyGenerated) { return; } var compilationEvent = started ? (CompilationEvent)new CompilationStartedEvent(compilation) : new CompilationCompletedEvent(compilation); var events = ImmutableArray.Create(compilationEvent); OnCompilationEventsGenerated_NoLock(events, filterTreeOpt: null, driver: driver, cancellationToken: cancellationToken); if (started) { _compilationStartGenerated = true; } else { _compilationEndGenerated = true; } } } public async Task OnCompilationEventsGeneratedAsync(ImmutableArray compilationEvents, AnalyzerDriver driver, CancellationToken cancellationToken) { await EnsureAnalyzerActionCountsInitializedAsync(driver, cancellationToken).ConfigureAwait(false); lock (_gate) { OnCompilationEventsGenerated_NoLock(compilationEvents, filterTreeOpt: null, driver: driver, cancellationToken: cancellationToken); } } private void OnCompilationEventsGenerated_NoLock(ImmutableArray compilationEvents, SyntaxTree filterTreeOpt, AnalyzerDriver driver, CancellationToken cancellationToken) { Debug.Assert(_lazyAnalyzerActionCountsMap != null); // Add the events to our global pending events map. AddToEventsMap_NoLock(compilationEvents, filterTreeOpt); // Mark the events for analysis for each analyzer. ArrayBuilder newPartialSymbols = null; Debug.Assert(_pooledEventsWithAnyActionsSet.Count == 0); foreach (var kvp in _analyzerStateMap) { var analyzer = kvp.Key; var analyzerState = _analyzerStates[kvp.Value]; var actionCounts = _lazyAnalyzerActionCountsMap[analyzer]; foreach (var compilationEvent in compilationEvents) { if (HasActionsForEvent(compilationEvent, actionCounts)) { _pooledEventsWithAnyActionsSet.Add(compilationEvent); var symbolDeclaredEvent = compilationEvent as SymbolDeclaredCompilationEvent; if (symbolDeclaredEvent?.DeclaringSyntaxReferences.Length > 1) { if (_partialSymbolsWithGeneratedSourceEvents.Contains(symbolDeclaredEvent.Symbol)) { // already processed. continue; } else { newPartialSymbols = newPartialSymbols ?? ArrayBuilder.GetInstance(); newPartialSymbols.Add(symbolDeclaredEvent.Symbol); } } analyzerState.OnCompilationEventGenerated(compilationEvent, actionCounts); } } } foreach (var compilationEvent in compilationEvents) { if (!_pooledEventsWithAnyActionsSet.Remove(compilationEvent)) { // Event has no relevant actions to execute, so mark it as complete. UpdateEventsMap_NoLock(compilationEvent, add: false); } } if (newPartialSymbols != null) { _partialSymbolsWithGeneratedSourceEvents.AddAll(newPartialSymbols); newPartialSymbols.Free(); } } private void AddToEventsMap_NoLock(ImmutableArray compilationEvents, SyntaxTree filterTreeOpt) { if (filterTreeOpt != null) { AddPendingSourceEvents_NoLock(compilationEvents, filterTreeOpt); return; } foreach (var compilationEvent in compilationEvents) { UpdateEventsMap_NoLock(compilationEvent, add: true); } } private void UpdateEventsMap_NoLock(CompilationEvent compilationEvent, bool add) { var symbolEvent = compilationEvent as SymbolDeclaredCompilationEvent; if (symbolEvent != null) { // Add/remove symbol events. // Any diagnostics request for a tree should trigger symbol and syntax node analysis for symbols with at least one declaring reference in the tree. foreach (var location in symbolEvent.Symbol.Locations) { if (location.SourceTree != null) { if (add) { AddPendingSourceEvent_NoLock(location.SourceTree, compilationEvent); } else { RemovePendingSourceEvent_NoLock(location.SourceTree, compilationEvent); } } } } else { // Add/remove compilation unit completed events. var compilationUnitCompletedEvent = compilationEvent as CompilationUnitCompletedEvent; if (compilationUnitCompletedEvent != null) { var tree = compilationUnitCompletedEvent.SemanticModel.SyntaxTree; if (add) { AddPendingSourceEvent_NoLock(tree, compilationEvent); } else { RemovePendingSourceEvent_NoLock(tree, compilationEvent); } } else if (compilationEvent is CompilationStartedEvent || compilationEvent is CompilationCompletedEvent) { // Add/remove compilation events. if (add) { _pendingNonSourceEvents.Add(compilationEvent); } else { _pendingNonSourceEvents.Remove(compilationEvent); } } else { throw new InvalidOperationException("Unexpected compilation event of type " + compilationEvent.GetType().Name); } } } private void AddPendingSourceEvents_NoLock(ImmutableArray compilationEvents, SyntaxTree tree) { HashSet currentEvents; if (!_pendingSourceEvents.TryGetValue(tree, out currentEvents)) { currentEvents = new HashSet(compilationEvents); _pendingSourceEvents[tree] = currentEvents; _compilationData.RemoveCachedSemanticModel(tree); return; } currentEvents.AddAll(compilationEvents); } private void AddPendingSourceEvent_NoLock(SyntaxTree tree, CompilationEvent compilationEvent) { HashSet currentEvents; if (!_pendingSourceEvents.TryGetValue(tree, out currentEvents)) { currentEvents = new HashSet(); _pendingSourceEvents[tree] = currentEvents; _compilationData.RemoveCachedSemanticModel(tree); } currentEvents.Add(compilationEvent); } private void RemovePendingSourceEvent_NoLock(SyntaxTree tree, CompilationEvent compilationEvent) { HashSet currentEvents; if (_pendingSourceEvents.TryGetValue(tree, out currentEvents)) { if (currentEvents.Remove(compilationEvent) && currentEvents.Count == 0) { _pendingSourceEvents.Remove(tree); _compilationData.RemoveCachedSemanticModel(tree); } } } private async Task EnsureAnalyzerActionCountsInitializedAsync(AnalyzerDriver driver, CancellationToken cancellationToken) { if (_lazyAnalyzerActionCountsMap == null) { var builder = ImmutableDictionary.CreateBuilder(); foreach (var analyzer in _analyzerStateMap.Keys) { var actionCounts = await driver.GetAnalyzerActionCountsAsync(analyzer, cancellationToken).ConfigureAwait(false); builder.Add(analyzer, actionCounts); } Interlocked.CompareExchange(ref _lazyAnalyzerActionCountsMap, builder.ToImmutable(), null); } } internal async Task GetAnalyzerActionCountsAsync(DiagnosticAnalyzer analyzer, AnalyzerDriver driver, CancellationToken cancellationToken) { await EnsureAnalyzerActionCountsInitializedAsync(driver, cancellationToken).ConfigureAwait(false); return _lazyAnalyzerActionCountsMap[analyzer]; } private static bool HasActionsForEvent(CompilationEvent compilationEvent, AnalyzerActionCounts actionCounts) { if (compilationEvent is CompilationStartedEvent) { return actionCounts.CompilationActionsCount > 0 || actionCounts.SyntaxTreeActionsCount > 0; } else if (compilationEvent is CompilationCompletedEvent) { return actionCounts.CompilationEndActionsCount > 0; } else if (compilationEvent is SymbolDeclaredCompilationEvent) { return actionCounts.SymbolActionsCount > 0 || actionCounts.HasAnyExecutableCodeActions; } else { return actionCounts.SemanticModelActionsCount > 0; } } private void OnSymbolDeclaredEventProcessed(SymbolDeclaredCompilationEvent symbolDeclaredEvent, ImmutableArray analyzers) { foreach (var analyzer in analyzers) { var analyzerState = GetAnalyzerState(analyzer); analyzerState.OnSymbolDeclaredEventProcessed(symbolDeclaredEvent); } } /// /// Invoke this method at completion of event processing for the given analysis scope. /// It updates the analysis state of this event for each analyzer and if the event has been fully processed for all analyzers, then removes it from our event cache. /// public void OnCompilationEventProcessed(CompilationEvent compilationEvent, AnalysisScope analysisScope) { // Analyze if the symbol and all its declaring syntax references are analyzed. var symbolDeclaredEvent = compilationEvent as SymbolDeclaredCompilationEvent; if (symbolDeclaredEvent != null) { OnSymbolDeclaredEventProcessed(symbolDeclaredEvent, analysisScope.Analyzers); } // Check if event is fully analyzed for all analyzers. foreach (var analyzerState in _analyzerStates) { if (!analyzerState.IsEventAnalyzed(compilationEvent)) { return; } } // Remove the event from event map. lock (_gate) { UpdateEventsMap_NoLock(compilationEvent, add: false); } } /// /// Gets pending events for given set of analyzers for the given syntax tree. /// public ImmutableArray GetPendingEvents(ImmutableArray analyzers, SyntaxTree tree) { lock (_gate) { return GetPendingEvents_NoLock(analyzers, tree); } } private HashSet GetPendingEvents_NoLock(ImmutableArray analyzers) { var uniqueEvents = _compilationEventsPool.Allocate(); foreach (var analyzer in analyzers) { var analyzerState = GetAnalyzerState(analyzer); analyzerState.AddPendingEvents(uniqueEvents); } return uniqueEvents; } /// /// Gets pending events for given set of analyzers for the given syntax tree. /// private ImmutableArray GetPendingEvents_NoLock(ImmutableArray analyzers, SyntaxTree tree) { HashSet compilationEventsForTree; if (_pendingSourceEvents.TryGetValue(tree, out compilationEventsForTree)) { if (compilationEventsForTree?.Count > 0) { HashSet pendingEvents = null; try { pendingEvents = GetPendingEvents_NoLock(analyzers); if (pendingEvents.Count > 0) { pendingEvents.IntersectWith(compilationEventsForTree); return pendingEvents.ToImmutableArray(); } } finally { Free(pendingEvents); } } } return ImmutableArray.Empty; } /// /// Gets all pending events for given set of analyzers. /// /// /// Indicates if source events (symbol declared, compilation unit completed event) should be included. /// Indicates if compilation wide events (compilation started and completed event) should be included. public ImmutableArray GetPendingEvents(ImmutableArray analyzers, bool includeSourceEvents, bool includeNonSourceEvents) { lock (_gate) { return GetPendingEvents_NoLock(analyzers, includeSourceEvents, includeNonSourceEvents); } } private ImmutableArray GetPendingEvents_NoLock(ImmutableArray analyzers, bool includeSourceEvents, bool includeNonSourceEvents) { HashSet pendingEvents = null, uniqueEvents = null; try { pendingEvents = GetPendingEvents_NoLock(analyzers); if (pendingEvents.Count == 0) { return ImmutableArray.Empty; } uniqueEvents = _compilationEventsPool.Allocate(); if (includeSourceEvents) { foreach (var compilationEvents in _pendingSourceEvents.Values) { foreach (var compilationEvent in compilationEvents) { uniqueEvents.Add(compilationEvent); } } } if (includeNonSourceEvents) { foreach (var compilationEvent in _pendingNonSourceEvents) { uniqueEvents.Add(compilationEvent); } } uniqueEvents.IntersectWith(pendingEvents); return uniqueEvents.ToImmutableArray(); } finally { Free(pendingEvents); Free(uniqueEvents); } } private void Free(HashSet events) { if (events != null) { events.Clear(); _compilationEventsPool.Free(events); } } /// /// Returns true if we have any pending syntax analysis for given analysis scope. /// public bool HasPendingSyntaxAnalysis(AnalysisScope analysisScope) { if (analysisScope.IsTreeAnalysis && !analysisScope.IsSyntaxOnlyTreeAnalysis) { return false; } foreach (var analyzer in analysisScope.Analyzers) { var analyzerState = GetAnalyzerState(analyzer); if (analyzerState.HasPendingSyntaxAnalysis(analysisScope.FilterTreeOpt)) { return true; } } return false; } /// /// Returns true if we have any pending symbol analysis for given analysis scope. /// public bool HasPendingSymbolAnalysis(AnalysisScope analysisScope) { Debug.Assert(analysisScope.FilterTreeOpt != null); var symbolDeclaredEvents = GetPendingSymbolDeclaredEvents(analysisScope.FilterTreeOpt); foreach (var symbolDeclaredEvent in symbolDeclaredEvents) { if (analysisScope.ShouldAnalyze(symbolDeclaredEvent.Symbol)) { foreach (var analyzer in analysisScope.Analyzers) { var analyzerState = GetAnalyzerState(analyzer); if (analyzerState.HasPendingSymbolAnalysis(symbolDeclaredEvent.Symbol)) { return true; } } } } return false; } private ImmutableArray GetPendingSymbolDeclaredEvents(SyntaxTree tree) { Debug.Assert(tree != null); lock (_gate) { HashSet compilationEvents; if (!_pendingSourceEvents.TryGetValue(tree, out compilationEvents)) { return ImmutableArray.Empty; } return compilationEvents.OfType().ToImmutableArray(); } } /// /// Attempts to start processing a compilation event for the given analyzer. /// /// /// Returns false if the event has already been processed for the analyzer OR is currently being processed by another task. /// If true, then it returns a non-null representing partial analysis state for the given event for the given analyzer. /// public bool TryStartProcessingEvent(CompilationEvent compilationEvent, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) { return GetAnalyzerState(analyzer).TryStartProcessingEvent(compilationEvent, out state); } /// /// Marks the given event as fully analyzed for the given analyzer. /// public void MarkEventComplete(CompilationEvent compilationEvent, DiagnosticAnalyzer analyzer) { GetAnalyzerState(analyzer).MarkEventComplete(compilationEvent); } /// /// Marks the given event as fully analyzed for the given analyzers. /// public void MarkEventComplete(CompilationEvent compilationEvent, IEnumerable analyzers) { foreach (var analyzer in analyzers) { GetAnalyzerState(analyzer).MarkEventComplete(compilationEvent); } } /// /// Attempts to start processing a symbol for the given analyzer's symbol actions. /// /// /// Returns false if the symbol has already been processed for the analyzer OR is currently being processed by another task. /// If true, then it returns a non-null representing partial analysis state for the given symbol for the given analyzer. /// public bool TryStartAnalyzingSymbol(ISymbol symbol, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) { return GetAnalyzerState(analyzer).TryStartAnalyzingSymbol(symbol, out state); } /// /// Marks the given symbol as fully analyzed for the given analyzer. /// public void MarkSymbolComplete(ISymbol symbol, DiagnosticAnalyzer analyzer) { GetAnalyzerState(analyzer).MarkSymbolComplete(symbol); } /// /// Attempts to start processing a symbol declaration for the given analyzer's syntax node and code block actions. /// /// /// Returns false if the declaration has already been processed for the analyzer OR is currently being processed by another task. /// If true, then it returns a non-null representing partial analysis state for the given declaration for the given analyzer. /// public bool TryStartAnalyzingDeclaration(ISymbol symbol, int declarationIndex, DiagnosticAnalyzer analyzer, out DeclarationAnalyzerStateData state) { return GetAnalyzerState(analyzer).TryStartAnalyzingDeclaration(symbol, declarationIndex, out state); } /// /// True if the given symbol declaration is fully analyzed. /// public bool IsDeclarationComplete(ISymbol symbol, int declarationIndex) { foreach (var analyzerState in _analyzerStates) { if (!analyzerState.IsDeclarationComplete(symbol, declarationIndex)) { return false; } } return true; } /// /// Marks the given symbol declaration as fully analyzed for the given analyzer. /// public void MarkDeclarationComplete(ISymbol symbol, int declarationIndex, DiagnosticAnalyzer analyzer) { GetAnalyzerState(analyzer).MarkDeclarationComplete(symbol, declarationIndex); } /// /// Marks the given symbol declaration as fully analyzed for the given analyzers. /// public void MarkDeclarationComplete(ISymbol symbol, int declarationIndex, IEnumerable analyzers) { foreach (var analyzer in analyzers) { GetAnalyzerState(analyzer).MarkDeclarationComplete(symbol, declarationIndex); } } /// /// Marks all the symbol declarations for the given symbol as fully analyzed for all the given analyzers. /// public void MarkDeclarationsComplete(ISymbol symbol, IEnumerable analyzers) { foreach (var analyzer in analyzers) { GetAnalyzerState(analyzer).MarkDeclarationsComplete(symbol); } } /// /// Attempts to start processing a syntax tree for the given analyzer's syntax tree actions. /// /// /// Returns false if the tree has already been processed for the analyzer OR is currently being processed by another task. /// If true, then it returns a non-null representing partial syntax analysis state for the given tree for the given analyzer. /// public bool TryStartSyntaxAnalysis(SyntaxTree tree, DiagnosticAnalyzer analyzer, out AnalyzerStateData state) { return GetAnalyzerState(analyzer).TryStartSyntaxAnalysis(tree, out state); } /// /// Marks the given tree as fully syntactically analyzed for the given analyzer. /// public void MarkSyntaxAnalysisComplete(SyntaxTree tree, DiagnosticAnalyzer analyzer) { GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(tree); } /// /// Marks the given tree as fully syntactically analyzed for the given analyzers. /// public void MarkSyntaxAnalysisComplete(SyntaxTree tree, IEnumerable analyzers) { foreach (var analyzer in analyzers) { GetAnalyzerState(analyzer).MarkSyntaxAnalysisComplete(tree); } } } }