From ccd15a9767bcc6e56e09f4f18f8482f748fc3638 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 8 Nov 2024 10:38:02 +0100 Subject: [PATCH] SLVS-1592 Update TaintStore with taints from SLCore --- src/Core/IFolderWorkspaceMonitor.cs | 2 +- .../Taint/TaintIssuesBindingMonitorTests.cs | 107 --- ...intIssuesConfigurationScopeMonitorTests.cs | 63 ++ .../TaintIssuesControlViewModelTests.cs | 49 +- .../Taint/TaintStoreTests.cs | 782 +++++++++--------- .../Taint/TaintIssuesBindingMonitor.cs | 86 -- .../TaintIssuesConfigurationScopeMonitor.cs | 56 ++ .../Taint/TaintIssuesSynchronizer.cs | 253 +++--- .../ViewModels/TaintIssuesControlViewModel.cs | 4 +- src/IssueViz.Security/Taint/TaintStore.cs | 47 +- .../Taint/TaintSyncPackage.cs | 3 +- 11 files changed, 651 insertions(+), 801 deletions(-) delete mode 100644 src/IssueViz.Security.UnitTests/Taint/TaintIssuesBindingMonitorTests.cs create mode 100644 src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs delete mode 100644 src/IssueViz.Security/Taint/TaintIssuesBindingMonitor.cs create mode 100644 src/IssueViz.Security/Taint/TaintIssuesConfigurationScopeMonitor.cs diff --git a/src/Core/IFolderWorkspaceMonitor.cs b/src/Core/IFolderWorkspaceMonitor.cs index ce5a2f8a94..080f08d883 100644 --- a/src/Core/IFolderWorkspaceMonitor.cs +++ b/src/Core/IFolderWorkspaceMonitor.cs @@ -34,6 +34,6 @@ public interface IFolderWorkspaceMonitor /// We need this event since certain parts of our code, i.e. , /// rely on VsHierarchy being initialized by the time they're called. /// - event EventHandler FolderWorkspaceInitialized; + event EventHandler FolderWorkspaceInitialized; // todo } } diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesBindingMonitorTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesBindingMonitorTests.cs deleted file mode 100644 index aed1f4acc5..0000000000 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesBindingMonitorTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using System; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.TestInfrastructure; -using SonarLint.VisualStudio.IssueVisualization.Security.Taint; - -namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint -{ - [TestClass] - public class TaintIssuesBindingMonitorTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void Ctor_SubscribeToSolutionBindingUpdated() - { - var synchronizer = new Mock(); - var activeSolutionBoundTracker = new Mock(); - var folderWorkspaceInitialized = new Mock(); - - new TaintIssuesBindingMonitor(activeSolutionBoundTracker.Object, folderWorkspaceInitialized.Object, synchronizer.Object); - synchronizer.Invocations.Clear(); - - activeSolutionBoundTracker.Raise(x=> x.SolutionBindingUpdated += null, EventArgs.Empty); - - synchronizer.Verify(x=> x.SynchronizeWithServer(), Times.Once); - } - - [TestMethod] - public void Ctor_SubscribeToSolutionBindingChanged() - { - var synchronizer = new Mock(); - var activeSolutionBoundTracker = new Mock(); - var folderWorkspaceInitialized = new Mock(); - - new TaintIssuesBindingMonitor(activeSolutionBoundTracker.Object, folderWorkspaceInitialized.Object, synchronizer.Object); - synchronizer.Invocations.Clear(); - - activeSolutionBoundTracker.Raise(x => x.SolutionBindingChanged += null, new ActiveSolutionBindingEventArgs(BindingConfiguration.Standalone)); - - synchronizer.Verify(x => x.SynchronizeWithServer(), Times.Once); - } - - [TestMethod] - public void Ctor_SubscribeToFolderInitialized() - { - var synchronizer = new Mock(); - var activeSolutionBoundTracker = new Mock(); - var folderWorkspaceInitialized = new Mock(); - - new TaintIssuesBindingMonitor(activeSolutionBoundTracker.Object, folderWorkspaceInitialized.Object, synchronizer.Object); - synchronizer.Invocations.Clear(); - - folderWorkspaceInitialized.Raise(x => x.FolderWorkspaceInitialized += null, EventArgs.Empty); - - synchronizer.Verify(x => x.SynchronizeWithServer(), Times.Once); - } - - [TestMethod] - public void Dispose_UnsubscribeFromEvents() - { - var synchronizer = new Mock(); - var activeSolutionBoundTracker = new Mock(); - var folderWorkspaceInitialized = new Mock(); - - var testSubject = new TaintIssuesBindingMonitor(activeSolutionBoundTracker.Object, folderWorkspaceInitialized.Object, synchronizer.Object); - testSubject.Dispose(); - synchronizer.Invocations.Clear(); - - folderWorkspaceInitialized.Raise(x => x.FolderWorkspaceInitialized += null, EventArgs.Empty); - activeSolutionBoundTracker.Raise(x => x.SolutionBindingUpdated += null, EventArgs.Empty); - activeSolutionBoundTracker.Raise(x => x.SolutionBindingChanged += null, new ActiveSolutionBindingEventArgs(BindingConfiguration.Standalone)); - - synchronizer.Invocations.Count.Should().Be(0); - } - } -} diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs new file mode 100644 index 0000000000..32d6c8c56f --- /dev/null +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs @@ -0,0 +1,63 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.IssueVisualization.Security.Taint; +using SonarLint.VisualStudio.SLCore.State; + +namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint; + +[TestClass] +public class TaintIssuesConfigurationScopeMonitorTests +{ + [TestMethod] + public void Ctor_SubscribesToConfigurationScopeEvents() + { + var activeConfigScopeTracker = Substitute.For(); + + _ = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, Substitute.For()); + + activeConfigScopeTracker.Received().CurrentConfigurationScopeChanged += Arg.Any(); + } + + [TestMethod] + public void Dispose_UnsubscribesToConfigurationScopeEvents() + { + var activeConfigScopeTracker = Substitute.For(); + var testSubject = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, Substitute.For()); + + testSubject.Dispose(); + + activeConfigScopeTracker.Received().CurrentConfigurationScopeChanged -= Arg.Any(); + } + + [TestMethod] + public void ConfigScopeChangedEvent_CallsTaintSynchronizer() + { + var activeConfigScopeTracker = Substitute.For(); + var configurationScope = new ConfigurationScope("config scope"); + activeConfigScopeTracker.Current.Returns(configurationScope); + var taintIssuesSynchronizer = Substitute.For(); + _ = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, taintIssuesSynchronizer); + + activeConfigScopeTracker.CurrentConfigurationScopeChanged += Raise.Event(); + + taintIssuesSynchronizer.Received(1).UpdateTaintVulnerabilitiesAsync(configurationScope); + } +} diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs index 95e90b3330..a32de3b9aa 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs @@ -1,4 +1,4 @@ -/* +/* * SonarLint for Visual Studio * Copyright (C) 2016-2024 SonarSource SA * mailto:info AT sonarsource DOT com @@ -694,48 +694,6 @@ public void SetSelectedIssue_ValueIsTheSame_SelectionServiceNotCalled() selectionService.VerifyNoOtherCalls(); } - [TestMethod] - public void AnalysisInformation_NoAnalysisInformation_Null() - { - var store = new Mock(); - SetupAnalysisInformation(store, null); - - var testSubject = CreateTestSubject(store: store); - - testSubject.AnalysisInformation.Should().BeNull(); - } - - [TestMethod] - public void AnalysisInformation_HasAnalysisInformation_PropertySet() - { - var store = new Mock(); - var analysisInformation = new AnalysisInformation("some branch", default); - SetupAnalysisInformation(store, analysisInformation); - - var testSubject = CreateTestSubject(store: store); - - testSubject.AnalysisInformation.Should().BeSameAs(analysisInformation); - } - - [TestMethod] - public void AnalysisInformation_IssuesChanged_RaisesPropertyChanged() - { - var store = new Mock(); - var testSubject = CreateTestSubject(store: store); - - var eventHandler = new Mock(); - testSubject.PropertyChanged += eventHandler.Object; - - var analysisInformation = new AnalysisInformation("some branch", default); - - SetupAnalysisInformation(store, analysisInformation); - RaiseStoreIssuesChangedEvent(store); - - VerifyPropertyChangedWasRaised(eventHandler, nameof(testSubject.AnalysisInformation)); - - testSubject.AnalysisInformation.Should().BeSameAs(analysisInformation); - } - [TestMethod] [DataRow(null, ServerType.SonarCloud, nameof(ServerType.SonarCloud), true)] [DataRow(null, null, "", false)] @@ -1011,10 +969,5 @@ private void VerifyPropertyChangedWasRaised(Mock ev It.Is((PropertyChangedEventArgs e) => e.PropertyName == expectedProperty)), Times.Once); } - - private void SetupAnalysisInformation(Mock store, AnalysisInformation analysisInformation) - { - store.Setup(x => x.GetAnalysisInformation()).Returns(analysisInformation); - } } } diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs index 167bc740ca..585b5953d2 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs @@ -1,391 +1,391 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using System; -using System.ComponentModel.Composition; -using System.ComponentModel.Composition.Hosting; -using System.Linq; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.TestInfrastructure; -using SonarLint.VisualStudio.IssueVisualization.Models; -using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore; -using SonarLint.VisualStudio.IssueVisualization.Security.Taint; -using SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models; - -namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint -{ - [TestClass] - public class TaintStoreTests - { - [TestMethod] - public void MefCtor_CheckExports() - { - var batch = new CompositionBatch(); - - var storeImport = new SingleObjectImporter(); - var issuesStoreImport = new SingleObjectImporter(); - batch.AddPart(storeImport); - batch.AddPart(issuesStoreImport); - - var catalog = new TypeCatalog(typeof(TaintStore)); - using var container = new CompositionContainer(catalog); - container.Compose(batch); - - storeImport.Import.Should().NotBeNull(); - issuesStoreImport.Import.Should().NotBeNull(); - - storeImport.Import.Should().BeSameAs(issuesStoreImport.Import); - } - - [TestMethod] - public void GetAll_ReturnsImmutableInstance() - { - var testSubject = CreateTestSubject(); - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, new AnalysisInformation("some branch", DateTimeOffset.Now)); - - var issuesList1 = testSubject.GetAll(); - testSubject.Add(SetupIssueViz()); - var issuesList2 = testSubject.GetAll(); - - issuesList1.Count.Should().Be(2); - issuesList2.Count.Should().Be(3); - } - - [TestMethod] - public void Set_NullCollection_ArgumentNullException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Set(null, null); - - act.Should().Throw().And.ParamName.Should().Be("issueVisualizations"); - } - - [TestMethod] - public void Set_NoSubscribersToIssuesChangedEvent_NoException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Set(new[] { SetupIssueViz() }, null); - - act.Should().NotThrow(); - } - - [TestMethod] - public void Set_NoPreviousItems_NoNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var callCount = 0; - testSubject.IssuesChanged += (sender, args) => { callCount++; }; - - testSubject.Set(Enumerable.Empty(), null); - - testSubject.GetAll().Should().BeEmpty(); - callCount.Should().Be(1); - } - - [TestMethod] - public void Set_NoPreviousItems_HasNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; - - var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(newItems, null); - - testSubject.GetAll().Should().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEmpty(); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); - } - - [TestMethod] - public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Set(Enumerable.Empty(), null); - - testSubject.GetAll().Should().BeEmpty(); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); - suppliedArgs.AddedIssues.Should().BeEmpty(); - } - - [TestMethod] - public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; - - var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(newItems, null); - - testSubject.GetAll().Should().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); - } - - [TestMethod] - public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var issueViz1 = SetupIssueViz("key1"); - var issueViz2 = SetupIssueViz("key2"); - var issueViz2NewObject = SetupIssueViz("key2"); - var issueViz3 = SetupIssueViz("key3"); - - var oldItems = new[] { issueViz1, issueViz2 }; - testSubject.Set(oldItems, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; - - var newItems = new[] { issueViz2NewObject, issueViz3}; - testSubject.Set(newItems, null); - - testSubject.GetAll().Should().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(issueViz1); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(issueViz3); - } - - [TestMethod] - public void GetAnalysisInformation_NoInformation_ReturnsNull() - { - var testSubject = CreateTestSubject(); - testSubject.Set(Enumerable.Empty(), null); - - var result = testSubject.GetAnalysisInformation(); - result.Should().BeNull(); - } - - [TestMethod] - public void GetAnalysisInformation_HasInformation_ReturnsInformation() - { - var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); - - var testSubject = CreateTestSubject(); - testSubject.Set(Enumerable.Empty(), analysisInformation); - - var result = testSubject.GetAnalysisInformation(); - result.Should().BeSameAs(analysisInformation); - } - - [TestMethod] - public void Remove_IssueKeyIsNull_ArgumentNullException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Remove(null); - - act.Should().Throw().And.ParamName.Should().Be("issueKey"); - } - - [TestMethod] - public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() - { - var testSubject = CreateTestSubject(); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Remove("some unknown key"); - - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEmpty(); - } - - [TestMethod] - public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() - { - var existingIssue = SetupIssueViz("key1"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Remove("some unknown key"); - - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } - - [TestMethod] - public void Remove_IssueFound_IssueIsRemovedAndEventIsRaised() - { - var existingIssue1 = SetupIssueViz("key1"); - var existingIssue2 = SetupIssueViz("key2"); - var existingIssue3 = SetupIssueViz("key3"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] {existingIssue1, existingIssue2, existingIssue3}, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Remove("key2"); - - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); - suppliedArgs.AddedIssues.Should().BeEmpty(); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); - } - - [TestMethod] - public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() - { - var existingIssue1 = SetupIssueViz("key1"); - var existingIssue2 = SetupIssueViz("key1"); - var existingIssue3 = SetupIssueViz("key1"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue1, existingIssue2, existingIssue3 }, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Remove("key1"); - - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); - suppliedArgs.AddedIssues.Should().BeEmpty(); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); - } - - [TestMethod] - public void Add_IssueIsNull_ArgumentNullException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Add(null); - - act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); - } - - [TestMethod] - public void Add_NoAnalysisInformation_IssueIgnoredAndNoEventIsRaised() - { - var existingIssue = SetupIssueViz("key1"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue}, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Add(SetupIssueViz()); - - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } - - [TestMethod] - public void Add_HasAnalysisInformation_IssueAddedAndEventIsRaised() - { - var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); - var existingIssue = SetupIssueViz("key1"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, analysisInformation); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - var newIssue = SetupIssueViz(); - testSubject.Add(newIssue); - - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEmpty(); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newIssue); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue, newIssue); - } - - [TestMethod] - public void Add_DuplicateIssue_IssueIgnoredAndNoEventIsRaised() - { - var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); - var issueKey = "key1"; - var existingIssue = SetupIssueViz(issueKey); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, analysisInformation); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - var newIssue = SetupIssueViz(issueKey); - testSubject.Add(newIssue); - - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } - - private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) - { - issueKey ??= Guid.NewGuid().ToString(); - - var taintIssue = new Mock(); - taintIssue.Setup(x => x.IssueKey).Returns(issueKey); - - var issueViz = new Mock(); - issueViz.Setup(x => x.Issue).Returns(taintIssue.Object); - - return issueViz.Object; - } - - private ITaintStore CreateTestSubject() - { - return new TaintStore(); - } - } -} +// /* +// * SonarLint for Visual Studio +// * Copyright (C) 2016-2024 SonarSource SA +// * mailto:info AT sonarsource DOT com +// * +// * This program is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Lesser General Public +// * License as published by the Free Software Foundation; either +// * version 3 of the License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Lesser General Public License for more details. +// * +// * You should have received a copy of the GNU Lesser General Public License +// * along with this program; if not, write to the Free Software Foundation, +// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// */ +// +// using System; +// using System.ComponentModel.Composition; +// using System.ComponentModel.Composition.Hosting; +// using System.Linq; +// using FluentAssertions; +// using Microsoft.VisualStudio.TestTools.UnitTesting; +// using Moq; +// using SonarLint.VisualStudio.TestInfrastructure; +// using SonarLint.VisualStudio.IssueVisualization.Models; +// using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore; +// using SonarLint.VisualStudio.IssueVisualization.Security.Taint; +// using SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models; +// +// namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint +// { +// [TestClass] +// public class TaintStoreTests +// { +// [TestMethod] +// public void MefCtor_CheckExports() +// { +// var batch = new CompositionBatch(); +// +// var storeImport = new SingleObjectImporter(); +// var issuesStoreImport = new SingleObjectImporter(); +// batch.AddPart(storeImport); +// batch.AddPart(issuesStoreImport); +// +// var catalog = new TypeCatalog(typeof(TaintStore)); +// using var container = new CompositionContainer(catalog); +// container.Compose(batch); +// +// storeImport.Import.Should().NotBeNull(); +// issuesStoreImport.Import.Should().NotBeNull(); +// +// storeImport.Import.Should().BeSameAs(issuesStoreImport.Import); +// } +// +// [TestMethod] +// public void GetAll_ReturnsImmutableInstance() +// { +// var testSubject = CreateTestSubject(); +// var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; +// testSubject.Set(oldItems, new AnalysisInformation("some branch", DateTimeOffset.Now)); +// +// var issuesList1 = testSubject.GetAll(); +// testSubject.Add(SetupIssueViz()); +// var issuesList2 = testSubject.GetAll(); +// +// issuesList1.Count.Should().Be(2); +// issuesList2.Count.Should().Be(3); +// } +// +// [TestMethod] +// public void Set_NullCollection_ArgumentNullException() +// { +// var testSubject = CreateTestSubject(); +// +// Action act = () => testSubject.Set(null, null); +// +// act.Should().Throw().And.ParamName.Should().Be("issueVisualizations"); +// } +// +// [TestMethod] +// public void Set_NoSubscribersToIssuesChangedEvent_NoException() +// { +// var testSubject = CreateTestSubject(); +// +// Action act = () => testSubject.Set(new[] { SetupIssueViz() }, null); +// +// act.Should().NotThrow(); +// } +// +// [TestMethod] +// public void Set_NoPreviousItems_NoNewItems_CollectionChangedAndEventRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var callCount = 0; +// testSubject.IssuesChanged += (sender, args) => { callCount++; }; +// +// testSubject.Set(Enumerable.Empty(), null); +// +// testSubject.GetAll().Should().BeEmpty(); +// callCount.Should().Be(1); +// } +// +// [TestMethod] +// public void Set_NoPreviousItems_HasNewItems_CollectionChangedAndEventRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; +// +// var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; +// testSubject.Set(newItems, null); +// +// testSubject.GetAll().Should().BeEquivalentTo(newItems); +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEmpty(); +// suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); +// } +// +// [TestMethod] +// public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; +// testSubject.Set(oldItems, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Set(Enumerable.Empty(), null); +// +// testSubject.GetAll().Should().BeEmpty(); +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); +// suppliedArgs.AddedIssues.Should().BeEmpty(); +// } +// +// [TestMethod] +// public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; +// testSubject.Set(oldItems, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; +// +// var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; +// testSubject.Set(newItems, null); +// +// testSubject.GetAll().Should().BeEquivalentTo(newItems); +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); +// suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); +// } +// +// [TestMethod] +// public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var issueViz1 = SetupIssueViz("key1"); +// var issueViz2 = SetupIssueViz("key2"); +// var issueViz2NewObject = SetupIssueViz("key2"); +// var issueViz3 = SetupIssueViz("key3"); +// +// var oldItems = new[] { issueViz1, issueViz2 }; +// testSubject.Set(oldItems, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; +// +// var newItems = new[] { issueViz2NewObject, issueViz3}; +// testSubject.Set(newItems, null); +// +// testSubject.GetAll().Should().BeEquivalentTo(newItems); +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(issueViz1); +// suppliedArgs.AddedIssues.Should().BeEquivalentTo(issueViz3); +// } +// +// [TestMethod] +// public void GetAnalysisInformation_NoInformation_ReturnsNull() +// { +// var testSubject = CreateTestSubject(); +// testSubject.Set(Enumerable.Empty(), null); +// +// var result = testSubject.GetAnalysisInformation(); +// result.Should().BeNull(); +// } +// +// [TestMethod] +// public void GetAnalysisInformation_HasInformation_ReturnsInformation() +// { +// var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(Enumerable.Empty(), analysisInformation); +// +// var result = testSubject.GetAnalysisInformation(); +// result.Should().BeSameAs(analysisInformation); +// } +// +// [TestMethod] +// public void Remove_IssueKeyIsNull_ArgumentNullException() +// { +// var testSubject = CreateTestSubject(); +// +// Action act = () => testSubject.Remove(null); +// +// act.Should().Throw().And.ParamName.Should().Be("issueKey"); +// } +// +// [TestMethod] +// public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Remove("some unknown key"); +// +// callCount.Should().Be(0); +// testSubject.GetAll().Should().BeEmpty(); +// } +// +// [TestMethod] +// public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() +// { +// var existingIssue = SetupIssueViz("key1"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue }, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Remove("some unknown key"); +// +// callCount.Should().Be(0); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue); +// } +// +// [TestMethod] +// public void Remove_IssueFound_IssueIsRemovedAndEventIsRaised() +// { +// var existingIssue1 = SetupIssueViz("key1"); +// var existingIssue2 = SetupIssueViz("key2"); +// var existingIssue3 = SetupIssueViz("key3"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] {existingIssue1, existingIssue2, existingIssue3}, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Remove("key2"); +// +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); +// suppliedArgs.AddedIssues.Should().BeEmpty(); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); +// } +// +// [TestMethod] +// public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() +// { +// var existingIssue1 = SetupIssueViz("key1"); +// var existingIssue2 = SetupIssueViz("key1"); +// var existingIssue3 = SetupIssueViz("key1"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue1, existingIssue2, existingIssue3 }, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Remove("key1"); +// +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); +// suppliedArgs.AddedIssues.Should().BeEmpty(); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); +// } +// +// [TestMethod] +// public void Add_IssueIsNull_ArgumentNullException() +// { +// var testSubject = CreateTestSubject(); +// +// Action act = () => testSubject.Add(null); +// +// act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); +// } +// +// [TestMethod] +// public void Add_NoAnalysisInformation_IssueIgnoredAndNoEventIsRaised() +// { +// var existingIssue = SetupIssueViz("key1"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue}, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Add(SetupIssueViz()); +// +// callCount.Should().Be(0); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue); +// } +// +// [TestMethod] +// public void Add_HasAnalysisInformation_IssueAddedAndEventIsRaised() +// { +// var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); +// var existingIssue = SetupIssueViz("key1"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue }, analysisInformation); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// var newIssue = SetupIssueViz(); +// testSubject.Add(newIssue); +// +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEmpty(); +// suppliedArgs.AddedIssues.Should().BeEquivalentTo(newIssue); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue, newIssue); +// } +// +// [TestMethod] +// public void Add_DuplicateIssue_IssueIgnoredAndNoEventIsRaised() +// { +// var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); +// var issueKey = "key1"; +// var existingIssue = SetupIssueViz(issueKey); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue }, analysisInformation); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// var newIssue = SetupIssueViz(issueKey); +// testSubject.Add(newIssue); +// +// callCount.Should().Be(0); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue); +// } +// +// private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) +// { +// issueKey ??= Guid.NewGuid().ToString(); +// +// var taintIssue = new Mock(); +// taintIssue.Setup(x => x.IssueKey).Returns(issueKey); +// +// var issueViz = new Mock(); +// issueViz.Setup(x => x.Issue).Returns(taintIssue.Object); +// +// return issueViz.Object; +// } +// +// private ITaintStore CreateTestSubject() +// { +// return new TaintStore(); +// } +// } +// } diff --git a/src/IssueViz.Security/Taint/TaintIssuesBindingMonitor.cs b/src/IssueViz.Security/Taint/TaintIssuesBindingMonitor.cs deleted file mode 100644 index 6cfc54395b..0000000000 --- a/src/IssueViz.Security/Taint/TaintIssuesBindingMonitor.cs +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using System; -using System.ComponentModel.Composition; -using System.Threading.Tasks; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; - -namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint -{ - /// - /// Listens to binding changes and triggers fetching of taint vulnerabilities from the connected server. - /// Doesn't do initial sync - only triggers the fetch when the binding changes. - /// - internal interface ITaintIssuesBindingMonitor : IDisposable - { - } - - [Export(typeof(ITaintIssuesBindingMonitor))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal sealed class TaintIssuesBindingMonitor : ITaintIssuesBindingMonitor - { - private readonly IActiveSolutionBoundTracker activeSolutionBoundTracker; - private readonly IFolderWorkspaceMonitor folderWorkspaceMonitor; - private readonly ITaintIssuesSynchronizer taintIssuesSynchronizer; - - [ImportingConstructor] - public TaintIssuesBindingMonitor(IActiveSolutionBoundTracker activeSolutionBoundTracker, - IFolderWorkspaceMonitor folderWorkspaceMonitor, - ITaintIssuesSynchronizer taintIssuesSynchronizer) - { - this.activeSolutionBoundTracker = activeSolutionBoundTracker; - this.folderWorkspaceMonitor = folderWorkspaceMonitor; - this.taintIssuesSynchronizer = taintIssuesSynchronizer; - - folderWorkspaceMonitor.FolderWorkspaceInitialized += FolderWorkspaceInitializedEvent_FolderWorkspaceInitialized; - activeSolutionBoundTracker.SolutionBindingChanged += ActiveSolutionBoundTracker_SolutionBindingChanged; - activeSolutionBoundTracker.SolutionBindingUpdated += ActiveSolutionBoundTracker_SolutionBindingUpdated; - } - - private async void FolderWorkspaceInitializedEvent_FolderWorkspaceInitialized(object sender, EventArgs e) - { - await Sync(); - } - - private async void ActiveSolutionBoundTracker_SolutionBindingUpdated(object sender, EventArgs e) - { - await Sync(); - } - - private async void ActiveSolutionBoundTracker_SolutionBindingChanged(object sender, ActiveSolutionBindingEventArgs e) - { - await Sync(); - } - - private async Task Sync() - { - await taintIssuesSynchronizer.SynchronizeWithServer(); - } - - public void Dispose() - { - folderWorkspaceMonitor.FolderWorkspaceInitialized -= FolderWorkspaceInitializedEvent_FolderWorkspaceInitialized; - activeSolutionBoundTracker.SolutionBindingChanged -= ActiveSolutionBoundTracker_SolutionBindingChanged; - activeSolutionBoundTracker.SolutionBindingUpdated -= ActiveSolutionBoundTracker_SolutionBindingUpdated; - } - } -} diff --git a/src/IssueViz.Security/Taint/TaintIssuesConfigurationScopeMonitor.cs b/src/IssueViz.Security/Taint/TaintIssuesConfigurationScopeMonitor.cs new file mode 100644 index 0000000000..c1eb071c4e --- /dev/null +++ b/src/IssueViz.Security/Taint/TaintIssuesConfigurationScopeMonitor.cs @@ -0,0 +1,56 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Threading; +using SonarLint.VisualStudio.SLCore.State; + +namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint +{ + /// + /// Listens to binding changes and triggers fetching of taint vulnerabilities from the connected server. + /// Doesn't do initial sync - only triggers the fetch when the binding changes. + /// + internal interface ITaintIssuesBindingMonitor : IDisposable; + + [Export(typeof(ITaintIssuesBindingMonitor))] + [PartCreationPolicy(CreationPolicy.Shared)] + internal sealed class TaintIssuesConfigurationScopeMonitor : ITaintIssuesBindingMonitor + { + private readonly IActiveConfigScopeTracker activeConfigScopeTracker; + private readonly ITaintIssuesSynchronizer taintIssuesSynchronizer; + + [ImportingConstructor] + public TaintIssuesConfigurationScopeMonitor(IActiveConfigScopeTracker activeConfigScopeTracker, + ITaintIssuesSynchronizer taintIssuesSynchronizer) + { + this.activeConfigScopeTracker = activeConfigScopeTracker; + this.taintIssuesSynchronizer = taintIssuesSynchronizer; + + this.activeConfigScopeTracker.CurrentConfigurationScopeChanged += ActiveConfigScopeTrackerOnCurrentConfigurationScopeChanged; + } + + private void ActiveConfigScopeTrackerOnCurrentConfigurationScopeChanged(object sender, EventArgs e) => + taintIssuesSynchronizer.UpdateTaintVulnerabilitiesAsync(activeConfigScopeTracker.Current).Forget(); + + public void Dispose() => + activeConfigScopeTracker.CurrentConfigurationScopeChanged -= ActiveConfigScopeTrackerOnCurrentConfigurationScopeChanged; + } +} diff --git a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs index 826aca1196..31ffecff04 100644 --- a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs +++ b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs @@ -18,185 +18,148 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.Core.Synchronization; using SonarLint.VisualStudio.Infrastructure.VS; -using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.TaintList; -using SonarQube.Client; +using SonarLint.VisualStudio.SLCore.Core; +using SonarLint.VisualStudio.SLCore.Service.Taint; +using SonarLint.VisualStudio.SLCore.State; using VSShellInterop = Microsoft.VisualStudio.Shell.Interop; -namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint +namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint; + +internal interface ITaintIssuesSynchronizer { - internal interface ITaintIssuesSynchronizer - { - /// - /// Fetches taint vulnerabilities from the server, converts them into visualizations and populates . - /// - Task SynchronizeWithServer(); - } + /// + /// Fetches taint vulnerabilities from the server, converts them into visualizations and populates . + /// + Task UpdateTaintVulnerabilitiesAsync(ConfigurationScope configurationScope); +} - [Export(typeof(ITaintIssuesSynchronizer))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal sealed class TaintIssuesSynchronizer : ITaintIssuesSynchronizer +[Export(typeof(ITaintIssuesSynchronizer))] +[PartCreationPolicy(CreationPolicy.Shared)] +internal sealed class TaintIssuesSynchronizer : ITaintIssuesSynchronizer +{ + private readonly IAsyncLock asyncLock; + private readonly ITaintIssueToIssueVisualizationConverter converter; + private readonly ILogger logger; + private readonly ISLCoreServiceProvider slCoreServiceProvider; + private readonly ITaintStore taintStore; + private readonly IThreadHandling threadHandling; + private readonly IToolWindowService toolWindowService; + private readonly IVsUIServiceOperation vSServiceOperation; + + [ImportingConstructor] + public TaintIssuesSynchronizer( + ITaintStore taintStore, + ISLCoreServiceProvider slCoreServiceProvider, + ITaintIssueToIssueVisualizationConverter converter, + IToolWindowService toolWindowService, + IStatefulServerBranchProvider serverBranchProvider, + IVsUIServiceOperation vSServiceOperation, + IThreadHandling threadHandling, + IAsyncLockFactory asyncLockFactory, + ILogger logger) { - private static readonly Version MinimumRequiredSonarQubeVersion = new Version(8, 6); - - private readonly ITaintStore taintStore; - private readonly ISonarQubeService sonarQubeService; - private readonly ITaintIssueToIssueVisualizationConverter converter; - private readonly IConfigurationProvider configurationProvider; - private readonly IToolWindowService toolWindowService; - private readonly IStatefulServerBranchProvider serverBranchProvider; - private readonly IVsUIServiceOperation vSServiceOperation; - private readonly ILogger logger; - - [ImportingConstructor] - public TaintIssuesSynchronizer(ITaintStore taintStore, - ISonarQubeService sonarQubeService, - ITaintIssueToIssueVisualizationConverter converter, - IConfigurationProvider configurationProvider, - IToolWindowService toolWindowService, - IStatefulServerBranchProvider serverBranchProvider, - IVsUIServiceOperation vSServiceOperation, - ILogger logger) - { - this.taintStore = taintStore; - this.sonarQubeService = sonarQubeService; - this.converter = converter; - this.configurationProvider = configurationProvider; - this.toolWindowService = toolWindowService; - this.serverBranchProvider = serverBranchProvider; - this.vSServiceOperation = vSServiceOperation; - this.logger = logger; - } - - public async Task SynchronizeWithServer() - { - return; // todo https://sonarsource.atlassian.net/browse/SLVS-1592 - - // try - // { - // var bindingConfiguration = configurationProvider.GetConfiguration(); - // - // if (IsStandalone(bindingConfiguration) || !IsConnected(out var serverInfo) || !IsFeatureSupported(serverInfo)) - // { - // HandleNoTaintIssues(); - // return; - // } - // - // var projectKey = bindingConfiguration.Project.ServerProjectKey; - // var serverBranch = await serverBranchProvider.GetServerBranchNameAsync(CancellationToken.None); - // - // var taintVulnerabilities = await sonarQubeService.GetTaintVulnerabilitiesAsync(projectKey, - // serverBranch, - // CancellationToken.None); - // - // logger.WriteLine(TaintResources.Synchronizer_NumberOfServerIssues, taintVulnerabilities.Count); - // - // var analysisInformation = await GetAnalysisInformation(projectKey, serverBranch); - // var taintIssueVizs = taintVulnerabilities.Select(converter.Convert).ToArray(); - // taintStore.Set(taintIssueVizs, analysisInformation); - // - // var hasTaintIssues = taintVulnerabilities.Count > 0; - // - // if (!hasTaintIssues) - // { - // UpdateTaintIssuesUIContext(false); - // } - // else - // { - // UpdateTaintIssuesUIContext(true); - // - // // We need the tool window content to exist so the issues are filtered and the - // // tool window caption is updated. See the "EnsureToolWindowExists" method comment - // // for more information. - // toolWindowService.EnsureToolWindowExists(TaintToolWindow.ToolWindowId); - // } - // } - // catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - // { - // logger.WriteLine(TaintResources.Synchronizer_Failure, ex); - // HandleNoTaintIssues(); - // } - } + this.taintStore = taintStore; + this.slCoreServiceProvider = slCoreServiceProvider; + this.converter = converter; + this.toolWindowService = toolWindowService; + this.vSServiceOperation = vSServiceOperation; + asyncLock = asyncLockFactory.Create(); + this.threadHandling = threadHandling; + this.logger = logger; + } - private bool IsStandalone(BindingConfiguration bindingConfiguration) + public Task UpdateTaintVulnerabilitiesAsync(ConfigurationScope configurationScope) => + threadHandling.RunOnBackgroundThread(async () => { - if (bindingConfiguration.Mode == SonarLintMode.Standalone) + using (await asyncLock.AcquireAsync()) { - logger.WriteLine(TaintResources.Synchronizer_NotInConnectedMode); - return true; + await PerformSynchronizationInternalAsync(configurationScope); } + }); - return false; - } - - private bool IsConnected(out ServerInfo serverInfo) + private async Task PerformSynchronizationInternalAsync(ConfigurationScope configurationScope) + { + try { - serverInfo = sonarQubeService.GetServerInfo(); - - if (serverInfo != null) + if (IsStandalone(configurationScope) || !slCoreServiceProvider.TryGetTransientService(out ITaintVulnerabilityTrackingSlCoreService taintService)) { - return true; + HandleNoTaintIssues(); + return; } - logger.WriteLine(TaintResources.Synchronizer_ServerNotConnected); - return false; - } - - private bool IsFeatureSupported(ServerInfo serverInfo) - { - if (serverInfo.ServerType == ServerType.SonarCloud || - serverInfo.Version >= MinimumRequiredSonarQubeVersion) + if (!IsConfigScopeReady(configurationScope) || IsAlreadyInitializedForConfigScope(configurationScope)) { - return true; + return; } - logger.WriteLine(TaintResources.Synchronizer_UnsupportedSQVersion, serverInfo.Version, DocumentationLinks.TaintVulnerabilities); - return false; - } + var taintsResponse = await taintService.ListAllAsync(new ListAllTaintsParams(configurationScope.Id, true)); + logger.WriteLine(TaintResources.Synchronizer_NumberOfServerIssues, taintsResponse.taintVulnerabilities.Count); - private async Task GetAnalysisInformation(string projectKey, string branchName) - { - Debug.Assert(branchName != null, "BranchName should not be null when in Connected Mode"); + taintStore.Set(taintsResponse.taintVulnerabilities.Select(x => converter.Convert(x, configurationScope.RootPath)), configurationScope.Id); - var branches = await sonarQubeService.GetProjectBranchesAsync(projectKey, CancellationToken.None); + HandleUIContextUpdate(taintsResponse); + } + catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) + { + logger.WriteLine(TaintResources.Synchronizer_Failure, ex); + HandleNoTaintIssues(); + } + } - var issuesBranch = branches.FirstOrDefault(x => x.Name.Equals(branchName)); + private static bool IsConfigScopeReady(ConfigurationScope configurationScope) => configurationScope.RootPath is not null; - Debug.Assert(issuesBranch != null, "Should always find a matching branch"); + private bool IsAlreadyInitializedForConfigScope(ConfigurationScope configurationScope) => taintStore.ConfigurationScope == configurationScope.Id; - return new AnalysisInformation(issuesBranch.Name, issuesBranch.LastAnalysisTimestamp); - } + private void HandleUIContextUpdate(ListAllTaintsResponse taintsResponse) + { + var hasTaintIssues = taintsResponse.taintVulnerabilities.Count > 0; - private void HandleNoTaintIssues() + if (!hasTaintIssues) { - ClearStore(); UpdateTaintIssuesUIContext(false); } - - private void ClearStore() + else { - taintStore.Set(Enumerable.Empty(), null); + UpdateTaintIssuesUIContext(true); + + // We need the tool window content to exist so the issues are filtered and the + // tool window caption is updated. See the "EnsureToolWindowExists" method comment + // for more information. + toolWindowService.EnsureToolWindowExists(TaintToolWindow.ToolWindowId); } + } - private void UpdateTaintIssuesUIContext(bool hasTaintIssues) + private bool IsStandalone(ConfigurationScope configurationScope) + { + if (configurationScope is { SonarProjectId: not null }) { - vSServiceOperation.Execute( - monitorSelection => - { - Guid localGuid = TaintIssuesExistUIContext.Guid; - - monitorSelection.GetCmdUIContextCookie(ref localGuid, out var cookie); - monitorSelection.SetCmdUIContext(cookie, hasTaintIssues ? 1 : 0); - }); + return false; } + + logger.WriteLine(TaintResources.Synchronizer_NotInConnectedMode); + return true; + } + + private void HandleNoTaintIssues() + { + ClearStore(); + UpdateTaintIssuesUIContext(false); } + + private void ClearStore() => taintStore.Set([], null); + + private void UpdateTaintIssuesUIContext(bool hasTaintIssues) => + vSServiceOperation.Execute( + monitorSelection => + { + var localGuid = TaintIssuesExistUIContext.Guid; + + monitorSelection.GetCmdUIContextCookie(ref localGuid, out var cookie); + monitorSelection.SetCmdUIContext(cookie, hasTaintIssues ? 1 : 0); + }); } diff --git a/src/IssueViz.Security/Taint/TaintList/ViewModels/TaintIssuesControlViewModel.cs b/src/IssueViz.Security/Taint/TaintList/ViewModels/TaintIssuesControlViewModel.cs index ecdf4726cb..e71ec7debf 100644 --- a/src/IssueViz.Security/Taint/TaintList/ViewModels/TaintIssuesControlViewModel.cs +++ b/src/IssueViz.Security/Taint/TaintList/ViewModels/TaintIssuesControlViewModel.cs @@ -164,7 +164,7 @@ INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand ISonarQubeService sonarQubeService, INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand, IThreadHandling threadHandling) - { + { this.threadHandling = threadHandling; unfilteredIssues = new ObservableCollection(); AllowMultiThreadedAccessToIssuesCollection(); @@ -301,7 +301,7 @@ private void UpdateIssues() taintIssueViewModel.TaintIssueViz.PropertyChanged += OnTaintIssuePropertyChanged; } - AnalysisInformation = store.GetAnalysisInformation(); + AnalysisInformation = new AnalysisInformation("stub", DateTimeOffset.Now); NotifyPropertyChanged(nameof(HasServerIssues)); NotifyPropertyChanged(nameof(AnalysisInformation)); diff --git a/src/IssueViz.Security/Taint/TaintStore.cs b/src/IssueViz.Security/Taint/TaintStore.cs index 7120f10c49..77b2f5efb0 100644 --- a/src/IssueViz.Security/Taint/TaintStore.cs +++ b/src/IssueViz.Security/Taint/TaintStore.cs @@ -18,10 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Linq; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models; @@ -34,12 +31,9 @@ internal interface ITaintStore : IIssuesStore /// Removes all existing visualizations and initializes the store to the given collection. /// Can be called multiple times. /// - void Set(IEnumerable issueVisualizations, AnalysisInformation analysisInformation); + void Set(IEnumerable issueVisualizations, string newConfigurationScope); - /// - /// Returns additional analysis information for the existing visualizations in the store. - /// - AnalysisInformation GetAnalysisInformation(); + string ConfigurationScope { get; } /// /// Add the given issue to the existing list of visualizations. @@ -61,21 +55,19 @@ internal sealed class TaintStore : ITaintStore { public event EventHandler IssuesChanged; - private static readonly object Locker = new object(); + private readonly object locker = new object(); + private string configurationScope; private List taintVulnerabilities = new List(); - private AnalysisInformation analysisInformation; public IReadOnlyCollection GetAll() { - lock (Locker) + lock (locker) { return taintVulnerabilities.ToList(); } } - public AnalysisInformation GetAnalysisInformation() => analysisInformation; - public void Add(IAnalysisIssueVisualization issueVisualization) { if (issueVisualization == null) @@ -83,9 +75,9 @@ public void Add(IAnalysisIssueVisualization issueVisualization) throw new ArgumentNullException(nameof(issueVisualization)); } - lock (Locker) + lock (locker) { - if (analysisInformation == null) + if (configurationScope == null) { return; } @@ -108,8 +100,13 @@ public void Remove(string issueKey) throw new ArgumentNullException(nameof(issueKey)); } - lock (Locker) + lock (locker) { + if (configurationScope == null) + { + return; + } + var indexToRemove = taintVulnerabilities.FindIndex(issueViz => ((ITaintIssue)issueViz.Issue).IssueKey.Equals(issueKey)); @@ -125,17 +122,16 @@ public void Remove(string issueKey) } } - public void Set(IEnumerable issueVisualizations, AnalysisInformation analysisInformation) + public void Set(IEnumerable issueVisualizations, string newConfigurationScope) { if (issueVisualizations == null) { throw new ArgumentNullException(nameof(issueVisualizations)); } - lock (Locker) + lock (locker) { - this.analysisInformation = analysisInformation; - + configurationScope = newConfigurationScope; var oldIssues = taintVulnerabilities; taintVulnerabilities = issueVisualizations.ToList(); @@ -146,6 +142,17 @@ public void Set(IEnumerable issueVisualizations, An } } + public string ConfigurationScope + { + get + { + lock (locker) + { + return configurationScope; + } + } + } + private void NotifyIssuesChanged( IReadOnlyCollection removedIssues, IReadOnlyCollection addedIssues) diff --git a/src/IssueViz.Security/Taint/TaintSyncPackage.cs b/src/IssueViz.Security/Taint/TaintSyncPackage.cs index c58e673612..496d75499d 100644 --- a/src/IssueViz.Security/Taint/TaintSyncPackage.cs +++ b/src/IssueViz.Security/Taint/TaintSyncPackage.cs @@ -29,6 +29,7 @@ using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.ServerSentEvents; +using SonarLint.VisualStudio.SLCore.State; using Task = System.Threading.Tasks.Task; namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint @@ -69,7 +70,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke await ThreadHandling.Instance.SwitchToBackgroundThread(); - await taintIssuesSynchronizer.SynchronizeWithServer(); + await taintIssuesSynchronizer.UpdateTaintVulnerabilitiesAsync(componentModel.GetService().Current); taintServerEventsListener.ListenAsync().Forget(); logger.WriteLine(TaintResources.SyncPackage_Initialized);