diff --git a/Changelog.md b/Changelog.md index a4555014..cb0f3130 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.19] - 2023-08-22 +### VS Extension +Fix concurrent access issue with cache storage for fixes. Fix #480 + ## [1.0.18] - 2023-08-09 ### Rules Fix language filtering on random number generator rules. Fix #468 diff --git a/DevSkim-DotNet/Microsoft.DevSkim.LanguageProtoInterop/CodeFixMapping.cs b/DevSkim-DotNet/Microsoft.DevSkim.LanguageProtoInterop/CodeFixMapping.cs index b9902942..83e2d0d1 100644 --- a/DevSkim-DotNet/Microsoft.DevSkim.LanguageProtoInterop/CodeFixMapping.cs +++ b/DevSkim-DotNet/Microsoft.DevSkim.LanguageProtoInterop/CodeFixMapping.cs @@ -16,7 +16,7 @@ public class MappingsVersion public Uri fileName; } - public class CodeFixMapping + public class CodeFixMapping : IEquatable { /// /// Reported version of the document the diagnostic applies to @@ -73,5 +73,36 @@ public CodeFixMapping(Diagnostic diagnostic, string replacement, Uri fileName, s this.matchEnd = matchEnd; this.isSuppression = isSuppression; } + + public bool Equals(CodeFixMapping other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return version == other.version && Equals(diagnostic, other.diagnostic) && replacement == other.replacement && Equals(fileName, other.fileName) && friendlyString == other.friendlyString && matchStart == other.matchStart && matchEnd == other.matchEnd && isSuppression == other.isSuppression; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CodeFixMapping)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = version.GetHashCode(); + hashCode = (hashCode * 397) ^ (diagnostic != null ? diagnostic.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (replacement != null ? replacement.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (fileName != null ? fileName.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (friendlyString != null ? friendlyString.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ matchStart; + hashCode = (hashCode * 397) ^ matchEnd; + hashCode = (hashCode * 397) ^ isSuppression.GetHashCode(); + return hashCode; + } + } } } diff --git a/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/DevSkimFixMessageTarget.cs b/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/DevSkimFixMessageTarget.cs index ffe28960..d7b42022 100644 --- a/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/DevSkimFixMessageTarget.cs +++ b/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/DevSkimFixMessageTarget.cs @@ -58,15 +58,22 @@ await Task.Run(() => { StaticData.FileToCodeFixMap.AddOrUpdate(mapping.fileName, // Add New Nested Dictionary - (Uri _) => new ConcurrentDictionary>(new Dictionary>() { { mapping.version ?? -1, new HashSet() { mapping } } }), + (Uri _) => new (new Dictionary> + { { mapping.version ?? -1, new (new Dictionary() + { {mapping, true } }) } }), // Update Nested Dictionary (key, oldValue) => { oldValue.AddOrUpdate(mapping.version ?? -1, - // Add new HashSet - (int _) => new HashSet() { mapping }, - // Update HashSet of CodeFixMappings - (versionKey, oldSet) => { oldSet.Add(mapping); return oldSet; }); + // Add new Set of mappings + (int _) => + { + var addedMapping = new ConcurrentDictionary(); + addedMapping.TryAdd(mapping, true); + return addedMapping; + }, + // Update Set of CodeFixMappings + (versionKey, oldSet) => { oldSet.TryAdd(mapping, true); return oldSet; }); return oldValue; }); } diff --git a/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/StaticData.cs b/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/StaticData.cs index da61b7f8..8e940ad6 100644 --- a/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/StaticData.cs +++ b/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/StaticData.cs @@ -8,6 +8,6 @@ internal static class StaticData { // Maps file name to a dictionary of file versions to a deduplicated set of CodeFixMappings - internal static ConcurrentDictionary>> FileToCodeFixMap { get; } = new ConcurrentDictionary>>(); + internal static ConcurrentDictionary>> FileToCodeFixMap { get; } = new(); } } diff --git a/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/SuggestionActionsSource.cs b/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/SuggestionActionsSource.cs index 22f85d16..de726528 100644 --- a/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/SuggestionActionsSource.cs +++ b/DevSkim-DotNet/Microsoft.DevSkim.VisualStudio/SuggestionActionsSource.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -43,11 +44,11 @@ public IEnumerable GetSuggestedActions(ISuggestedActionCateg List suggestedActions = new List(); if (TryGetWordUnderCaret(out TextExtent wordExtent) && wordExtent.IsSignificant) { - if (StaticData.FileToCodeFixMap.TryGetValue(new Uri(_fileName), out System.Collections.Concurrent.ConcurrentDictionary> dictForFile)) + if (StaticData.FileToCodeFixMap.TryGetValue(new Uri(_fileName), out ConcurrentDictionary> dictForFile)) { - if (dictForFile.TryGetValue(wordExtent.Span.Snapshot.Version.VersionNumber, out HashSet fixes)) + if (dictForFile.TryGetValue(wordExtent.Span.Snapshot.Version.VersionNumber, out ConcurrentDictionary fixes)) { - suggestedActions.AddRange(fixes.Where(codeFixMapping => Intersects(codeFixMapping, wordExtent)).Select(intersectedMapping => new DevSkimSuggestedAction(wordExtent.Span, intersectedMapping))); + suggestedActions.AddRange(fixes.Where(codeFixMapping => Intersects(codeFixMapping.Key, wordExtent)).Select(intersectedMapping => new DevSkimSuggestedAction(wordExtent.Span, intersectedMapping.Key))); } } yield return new SuggestedActionSet(suggestedActions, wordExtent.Span); @@ -79,11 +80,11 @@ public Task HasSuggestedActionsAsync(ISuggestedActionCategorySet requested bool res = TryGetWordUnderCaret(out TextExtent wordExtent); if (res && wordExtent.IsSignificant) { - if (StaticData.FileToCodeFixMap.TryGetValue(new Uri(_fileName), out System.Collections.Concurrent.ConcurrentDictionary> dictForFile)) + if (StaticData.FileToCodeFixMap.TryGetValue(new Uri(_fileName), out ConcurrentDictionary> dictForFile)) { - if (dictForFile.TryGetValue(wordExtent.Span.Snapshot.Version.VersionNumber, out HashSet fixes)) + if (dictForFile.TryGetValue(wordExtent.Span.Snapshot.Version.VersionNumber, out ConcurrentDictionary fixes)) { - return fixes.Any(codeFixMapping => Intersects(codeFixMapping, wordExtent)); + return fixes.Any(codeFixMapping => Intersects(codeFixMapping.Key, wordExtent)); } } }