private ProjectAnalyzersAndStates GetOrCreateProjectAnalyzersAndStates(Project project, out IEnumerable <Tuple <DiagnosticState, ProviderId, StateType> > removedStates) { removedStates = SpecializedCollections.EmptyEnumerable <Tuple <DiagnosticState, ProviderId, StateType> >(); var newAnalyzersBuilder = ImmutableArray.CreateBuilder <KeyValuePair <string, IEnumerable <DiagnosticAnalyzer> > >(); foreach (var analyzerReference in project.AnalyzerReferences) { // Filter out duplicate analyzer references. if (this.sharedAnalyzersAndStates.HasAnalyzerReference(analyzerReference, project.Language)) { continue; } // Get analyzers in the analyzer reference for the given project language. // Filter out duplicate analyzers. // NOTE: The HasAnalyzerReference check above to filter out duplicate analyzer references works only for duplicate AnalyzerFileReference with the same underlying assembly. // However, we can also have AnalyzerImageReference, which might contain the same DiagnosticAnalyzer instance across different AnalyzerImageReference instances // and we want to avoid duplicate analyzers for that case. Hence we apply the HasAnalyzer filter here. var analyzers = analyzerReference.GetAnalyzers(project.Language) .Where(a => !this.sharedAnalyzersAndStates.HasAnalyzer(a, project.Language)); if (analyzers.Any()) { newAnalyzersBuilder.Add(KeyValuePair.Create(analyzerReference.Display, analyzers)); } } var newAnalyzers = newAnalyzersBuilder.ToImmutable(); ProjectAnalyzersAndStates newProjectAnalyzersAndStates = null; ProjectAnalyzersAndStates currentProjectAnalyzersAndStates; if (this.projectAnalyzersAndStatesMap.TryGetValue(project.Id, out currentProjectAnalyzersAndStates)) { var newAnalyzersCount = newAnalyzers.Sum(kv => kv.Value.Count()); if (currentProjectAnalyzersAndStates != null && currentProjectAnalyzersAndStates.AnalyzerCount == newAnalyzersCount) { Contract.ThrowIfFalse(currentProjectAnalyzersAndStates.AnalyzerCount > 0); // Project still has the same number of analyzers, does the saved projectAnalyzersAndStates has the same set of analyzers? var hasSameAnalyzers = true; foreach (var analyzerPair in newAnalyzers) { foreach (var analyzer in analyzerPair.Value) { if (!currentProjectAnalyzersAndStates.HasAnalyzer(analyzer)) { hasSameAnalyzers = false; break; } } } if (hasSameAnalyzers) { return(currentProjectAnalyzersAndStates); } } } else { currentProjectAnalyzersAndStates = null; } if (currentProjectAnalyzersAndStates != null) { removedStates = currentProjectAnalyzersAndStates.GetAllExistingDiagnosticStates(); } var workspaceAnalyzersCount = this.sharedAnalyzersAndStates.GetAnalyzerCount(project.Language); newProjectAnalyzersAndStates = ProjectAnalyzersAndStates.CreateIfAnyAnalyzers(newAnalyzers, workspaceAnalyzersCount, project.Language); return(this.projectAnalyzersAndStatesMap.AddOrUpdate(project.Id, newProjectAnalyzersAndStates, (k, c) => newProjectAnalyzersAndStates)); }
private ProjectAnalyzersAndStates GetOrCreateProjectAnalyzersAndStates(Project project, out IEnumerable <Tuple <DiagnosticState, ProviderId, StateType> > removedStates) { removedStates = SpecializedCollections.EmptyEnumerable <Tuple <DiagnosticState, ProviderId, StateType> >(); var newAnalyzersBuilder = ImmutableArray.CreateBuilder <KeyValuePair <string, IEnumerable <DiagnosticAnalyzer> > >(); foreach (var analyzerReference in project.AnalyzerReferences) { // Filter out duplicate analyzer references. if (_sharedAnalyzersAndStates.HasAnalyzerReference(analyzerReference, project.Language)) { continue; } // Get analyzers in the analyzer reference for the given project language. // Filter out duplicate analyzers. // NOTE: The HasAnalyzerReference check above to filter out duplicate analyzer references works only for duplicate AnalyzerFileReference with the same underlying assembly. // However, we can also have AnalyzerImageReference, which might contain the same DiagnosticAnalyzer instance across different AnalyzerImageReference instances // and we want to avoid duplicate analyzers for that case. Hence we apply the HasAnalyzer filter here. var analyzers = analyzerReference.GetAnalyzers(project.Language) .Where(a => !_sharedAnalyzersAndStates.HasAnalyzer(a, project.Language)); if (analyzers.Any()) { newAnalyzersBuilder.Add(KeyValuePair.Create(analyzerReference.Display, analyzers)); } } var newAnalyzers = newAnalyzersBuilder.ToImmutable(); ProjectAnalyzersAndStates newProjectAnalyzersAndStates = null; ProjectAnalyzersAndStates currentProjectAnalyzersAndStates; if (_projectAnalyzersAndStatesMap.TryGetValue(project.Id, out currentProjectAnalyzersAndStates)) { var newAnalyzersCount = newAnalyzers.Sum(kv => kv.Value.Count()); if (currentProjectAnalyzersAndStates != null && currentProjectAnalyzersAndStates.AnalyzerCount == newAnalyzersCount) { Contract.ThrowIfFalse(currentProjectAnalyzersAndStates.AnalyzerCount > 0); // Project still has the same number of analyzers, does the saved projectAnalyzersAndStates has the same set of analyzers? var hasSameAnalyzers = true; foreach (var analyzerPair in newAnalyzers) { foreach (var analyzer in analyzerPair.Value) { if (!currentProjectAnalyzersAndStates.HasAnalyzer(analyzer)) { hasSameAnalyzers = false; break; } } } if (hasSameAnalyzers) { return(currentProjectAnalyzersAndStates); } } } else { currentProjectAnalyzersAndStates = null; } if (currentProjectAnalyzersAndStates != null) { removedStates = currentProjectAnalyzersAndStates.GetAllExistingDiagnosticStates(); } var workspaceAnalyzersCount = _sharedAnalyzersAndStates.GetAnalyzerCount(project.Language); newProjectAnalyzersAndStates = ProjectAnalyzersAndStates.CreateIfAnyAnalyzers(newAnalyzers, workspaceAnalyzersCount, project.Language); // this cache logic is completely broken. cache can't be used in this way. if we didn't have an issue before that is just purely by luck. // the reason it is broken is that, this method can be called from any place (no central call path so, can't figure out all the possible paths, but I confirmed that there // are at least more than one path) from any workspace snapshot from any thread but the cache doesn't have any versioning or snapshot check, // but regardless, this code updates the cache. which introduce a race where if code uses the cache in 2 different places in the same method, the return value might be different. // and that is causing us to crash since one gets two completely different state. // // I tried to figure out how to fix this but, eventually gave up since it is so deeply spread in the code, I don't see any way to properly fix this. // my conculusion is ripping out this code and re-write this state management code. return(_projectAnalyzersAndStatesMap.AddOrUpdate(project.Id, newProjectAnalyzersAndStates, (k, c) => newProjectAnalyzersAndStates)); }