Skip to content

Commit fbb4eb7

Browse files
fix: don't display IaC ignored errors in balloon notification (#546)
* fix: don't display balloon notification for ignored iac error * fix: only process files in file listener * fix: tests * fix: don't output amplitude connection problems as warnings/errors * fix: improve file update detection and disposal on project close * fix: add disposable/disposer to Snyk Language Client * fix: add better disposal to LanguageServerWrapper * fix: compile error * chore: improve disposals * chore: added tests for language server wrapper * refactor: make disposed property private * chore: add more tests * chore: remove test stubs * fix: actually dispose when dispose() is called * docs: added javadoc * fix: comment
1 parent 7e295e5 commit fbb4eb7

19 files changed

+576
-91
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# Snyk Security Changelog
22

3+
## [2.8.5]
4+
### Fixed
5+
- don't display balloon warnings if IaC error is ignored (e.g. no IaC files found)
6+
- don't output amplitude errors as warning, only debug
7+
38
## [2.8.4]
49
### Fixed
5-
- dont use kotlin specific convenience function that may cause errors on non kotlin IDEs
10+
- don't use kotlin specific convenience function that may cause errors on non kotlin IDEs
611

712
## [2.8.3]
813

src/main/kotlin/io/snyk/plugin/SnykBulkFileListener.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.snyk.plugin
33
import com.intellij.ide.impl.ProjectUtil
44
import com.intellij.openapi.project.Project
55
import com.intellij.openapi.vfs.VirtualFile
6+
import com.intellij.openapi.vfs.isFile
67
import com.intellij.openapi.vfs.newvfs.BulkFileListener
78
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent
89
import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
@@ -121,6 +122,7 @@ abstract class SnykBulkFileListener : BulkFileListener {
121122
.filter { eventsFilter.invoke(it) }
122123
.mapNotNull(eventToVirtualFileTransformer)
123124
.filter(VirtualFile::isValid)
125+
.filter(VirtualFile::isFile)
124126
.toSet()
125127
}
126128

src/main/kotlin/io/snyk/plugin/SnykProjectManagerListener.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@ import com.intellij.openapi.progress.Task.Backgroundable
77
import com.intellij.openapi.project.Project
88
import com.intellij.openapi.project.ProjectManagerListener
99
import snyk.common.lsp.LanguageServerWrapper
10+
import java.util.concurrent.ExecutorService
1011
import java.util.concurrent.Executors
1112
import java.util.concurrent.TimeUnit
1213

1314
private const val TIMEOUT = 1L
1415

1516
class SnykProjectManagerListener : ProjectManagerListener {
17+
val threadPool: ExecutorService = Executors.newWorkStealingPool()
18+
1619
override fun projectClosing(project: Project) {
1720
val closingTask = object : Backgroundable(project, "Project closing ${project.name}") {
1821
override fun run(indicator: ProgressIndicator) {
19-
// limit clean up to 5s
22+
// limit clean up to TIMEOUT
2023
try {
21-
Executors.newCachedThreadPool().submit {
24+
threadPool.submit {
2225
// lets all running ProgressIndicators release MUTEX first
2326
val ls = LanguageServerWrapper.getInstance()
2427
if (ls.isInitialized) {

src/main/kotlin/io/snyk/plugin/Utils.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,15 +315,16 @@ fun findPsiFileIgnoringExceptions(virtualFile: VirtualFile, project: Project): P
315315
}
316316

317317
fun refreshAnnotationsForOpenFiles(project: Project) {
318-
if (project.isDisposed) return
318+
if (project.isDisposed || ApplicationManager.getApplication().isDisposed) return
319319
VirtualFileManager.getInstance().asyncRefresh()
320320

321321
val openFiles = FileEditorManager.getInstance(project).openFiles
322322

323323
ApplicationManager.getApplication().invokeLater {
324-
project.service<CodeVisionHost>().invalidateProvider(CodeVisionHost.LensInvalidateSignal(null))
324+
if (!project.isDisposed) {
325+
project.service<CodeVisionHost>().invalidateProvider(CodeVisionHost.LensInvalidateSignal(null))
326+
}
325327
}
326-
327328
openFiles.forEach {
328329
val psiFile = findPsiFileIgnoringExceptions(it, project)
329330
if (psiFile != null) {

src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ class SnykTaskQueueService(val project: Project) {
263263

264264
fun downloadLatestRelease(force: Boolean = false) {
265265
// abort even before submitting a task
266+
if (project.isDisposed || ApplicationManager.getApplication().isDisposed) return
266267
val cliDownloader = getSnykCliDownloaderService()
267268
if (!pluginSettings().manageBinariesAutomatically) {
268269
if (!isCliInstalled()) {

src/main/kotlin/io/snyk/plugin/services/download/CliDownloaderService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class SnykCliDownloaderService {
3232

3333
private var currentProgressIndicator: ProgressIndicator? = null
3434

35-
fun isCliDownloading() = currentProgressIndicator != null
35+
fun isCliDownloading() = currentProgressIndicator != null && !ApplicationManager.getApplication().isDisposed
3636

3737
fun stopCliDownload() = currentProgressIndicator?.let {
3838
it.cancel()

src/main/kotlin/io/snyk/plugin/snykcode/SnykCodeBulkFileListener.kt

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ package io.snyk.plugin.snykcode
22

33
import com.google.common.cache.CacheBuilder
44
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
5-
import com.intellij.openapi.application.runInEdt
5+
import com.intellij.openapi.application.ApplicationManager
66
import com.intellij.openapi.diagnostic.logger
7-
import com.intellij.openapi.progress.ProgressIndicator
8-
import com.intellij.openapi.progress.ProgressManager
9-
import com.intellij.openapi.progress.Task.Backgroundable
7+
import com.intellij.openapi.project.DumbService
108
import com.intellij.openapi.project.Project
9+
import com.intellij.openapi.roots.ProjectFileIndex
1110
import com.intellij.openapi.vfs.VirtualFile
1211
import com.intellij.openapi.vfs.VirtualFileManager
1312
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
@@ -18,7 +17,9 @@ import io.snyk.plugin.toLanguageServerURL
1817
import io.snyk.plugin.toSnykFileSet
1918
import org.eclipse.lsp4j.DidSaveTextDocumentParams
2019
import org.eclipse.lsp4j.TextDocumentIdentifier
20+
import org.jetbrains.concurrency.runAsync
2121
import snyk.common.lsp.LanguageServerWrapper
22+
import java.io.File
2223
import java.time.Duration
2324

2425
class SnykCodeBulkFileListener : SnykBulkFileListener() {
@@ -28,50 +29,71 @@ class SnykCodeBulkFileListener : SnykBulkFileListener() {
2829
CacheBuilder.newBuilder()
2930
.expireAfterWrite(Duration.ofMillis(1000)).build<String, Boolean>()
3031

32+
private val blackListedDirectories =
33+
setOf(".idea", ".git", ".hg", ".svn")
34+
3135
override fun before(project: Project, virtualFilesAffected: Set<VirtualFile>) = Unit
3236

3337
override fun after(project: Project, virtualFilesAffected: Set<VirtualFile>) {
3438
if (virtualFilesAffected.isEmpty()) return
35-
ProgressManager.getInstance().run(object : Backgroundable(
36-
project,
37-
"Snyk: forwarding save event to Language Server"
38-
) {
39-
override fun run(indicator: ProgressIndicator) {
40-
val languageServerWrapper = LanguageServerWrapper.getInstance()
41-
if (!languageServerWrapper.isInitialized) return
42-
val languageServer = languageServerWrapper.languageServer
43-
val cache = getSnykCachedResults(project)?.currentSnykCodeResultsLS ?: return
44-
val filesAffected = toSnykFileSet(project, virtualFilesAffected)
45-
for (file in filesAffected) {
46-
val virtualFile = file.virtualFile
47-
if (!shouldProcess(virtualFile)) continue
48-
cache.remove(file)
49-
val param =
50-
DidSaveTextDocumentParams(
51-
TextDocumentIdentifier(virtualFile.toLanguageServerURL()),
52-
virtualFile.readText()
53-
)
54-
languageServer.textDocumentService.didSave(param)
55-
}
5639

57-
VirtualFileManager.getInstance().asyncRefresh()
58-
runInEdt { DaemonCodeAnalyzer.getInstance(project).restart() }
59-
}
60-
})
40+
runAsync {
41+
if (ApplicationManager.getApplication().isDisposed) return@runAsync
42+
if (project.isDisposed) return@runAsync
43+
if (DumbService.getInstance(project).isDumb) return@runAsync
6144

45+
val languageServerWrapper = LanguageServerWrapper.getInstance()
46+
if (languageServerWrapper.isDisposed() || !languageServerWrapper.isInitialized) return@runAsync
47+
48+
val languageServer = languageServerWrapper.languageServer
49+
val cache = getSnykCachedResults(project)?.currentSnykCodeResultsLS
50+
val filesAffected = toSnykFileSet(project, virtualFilesAffected)
51+
val index = ProjectFileIndex.getInstance(project)
52+
53+
for (file in filesAffected) {
54+
val virtualFile = file.virtualFile
55+
if (!shouldProcess(virtualFile, index, project)) continue
56+
cache?.remove(file)
57+
val param =
58+
DidSaveTextDocumentParams(
59+
TextDocumentIdentifier(virtualFile.toLanguageServerURL()),
60+
virtualFile.readText()
61+
)
62+
languageServer.textDocumentService.didSave(param)
63+
}
64+
VirtualFileManager.getInstance().asyncRefresh()
65+
DaemonCodeAnalyzer.getInstance(project).restart()
66+
}
6267
}
6368

64-
private fun shouldProcess(file: VirtualFile): Boolean {
65-
val inCache = debounceFileCache.getIfPresent(file.path)
69+
private fun shouldProcess(file: VirtualFile, index: ProjectFileIndex, project: Project): Boolean {
70+
var shouldProcess = false
71+
val application = ApplicationManager.getApplication()
72+
if (application.isDisposed) return false
73+
if (project.isDisposed) return false
74+
if (DumbService.getInstance(project).isDumb) shouldProcess = false
6675

67-
return if (inCache != null) {
68-
logger<SnykCodeBulkFileListener>().info("not forwarding file event to ls, debouncing")
69-
false
70-
} else {
71-
debounceFileCache.put(file.path, true)
72-
logger<SnykCodeBulkFileListener>().info("forwarding file event to ls, not debouncing")
73-
true
76+
application.runReadAction {
77+
val inCache = debounceFileCache.getIfPresent(file.path)
78+
if (inCache != null) {
79+
shouldProcess = false
80+
} else {
81+
debounceFileCache.put(file.path, true)
82+
if (index.isInContent(file) && !isInBlacklistedParentDir(file)) {
83+
shouldProcess = true
84+
} else {
85+
shouldProcess = false
86+
return@runReadAction
87+
}
88+
}
7489
}
90+
return shouldProcess
91+
}
92+
93+
private fun isInBlacklistedParentDir(file: VirtualFile): Boolean {
94+
val path = file.path.split(File.separatorChar)
95+
.filter { blackListedDirectories.contains(it.trimEnd(File.separatorChar)) }
96+
return path.isNotEmpty()
7597
}
7698

7799
override fun forwardEvents(events: MutableList<out VFileEvent>) = Unit

src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ import snyk.container.ui.ContainerIssueTreeNode
8585
import snyk.iac.IacError
8686
import snyk.iac.IacIssue
8787
import snyk.iac.IacResult
88+
import snyk.iac.ignorableErrorCodes
8889
import snyk.iac.ui.toolwindow.IacFileTreeNode
8990
import snyk.iac.ui.toolwindow.IacIssueTreeNode
9091
import snyk.oss.OssResult
@@ -247,7 +248,7 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {
247248
override fun scanningIacError(snykError: SnykError) {
248249
var iacResultsCount: Int? = null
249250
ApplicationManager.getApplication().invokeLater {
250-
if (snykError.code == IacError.NO_IAC_FILES_CODE) {
251+
if (snykError.code != null && ignorableErrorCodes.contains(snykError.code)) {
251252
iacResultsCount = NODE_NOT_SUPPORTED_STATE
252253
} else {
253254
SnykBalloonNotificationHelper.showError(snykError.message, project)

src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowSnykScanListenerLS.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.snyk.plugin.ui.toolwindow
22

3+
import com.intellij.openapi.Disposable
34
import com.intellij.openapi.application.ApplicationManager
45
import com.intellij.openapi.project.Project
6+
import com.intellij.openapi.util.Disposer
57
import com.intellij.openapi.util.TextRange
68
import com.intellij.openapi.vfs.VirtualFile
79
import com.intellij.util.ui.tree.TreeUtil
@@ -35,8 +37,19 @@ class SnykToolWindowSnykScanListenerLS(
3537
private val rootSecurityIssuesTreeNode: DefaultMutableTreeNode,
3638
private val rootQualityIssuesTreeNode: DefaultMutableTreeNode,
3739
private val rootOssIssuesTreeNode: DefaultMutableTreeNode,
38-
) : SnykScanListenerLS {
40+
) : SnykScanListenerLS, Disposable {
41+
private var disposed = false; get() { return project.isDisposed || ApplicationManager.getApplication().isDisposed || field }
42+
init {
43+
Disposer.register(SnykPluginDisposable.getInstance(project), this)
44+
}
45+
46+
override fun dispose() {
47+
disposed = true
48+
}
49+
fun isDisposed() = disposed
50+
3951
override fun scanningStarted(snykScan: SnykScanParams) {
52+
if (disposed) return
4053
ApplicationManager.getApplication().invokeLater {
4154
rootSecurityIssuesTreeNode.userObject = "$CODE_SECURITY_ROOT_TEXT (scanning...)"
4255
rootQualityIssuesTreeNode.userObject = "$CODE_QUALITY_ROOT_TEXT (scanning...)"
@@ -45,6 +58,7 @@ class SnykToolWindowSnykScanListenerLS(
4558
}
4659

4760
override fun scanningSnykCodeFinished(snykResults: Map<SnykFile, List<ScanIssue>>) {
61+
if (disposed) return
4862
ApplicationManager.getApplication().invokeLater {
4963
this.snykToolWindowPanel.navigateToSourceEnabled = false
5064
displaySnykCodeResults(snykResults)
@@ -54,6 +68,7 @@ class SnykToolWindowSnykScanListenerLS(
5468
}
5569

5670
override fun scanningOssFinished(snykResults: Map<SnykFile, List<ScanIssue>>) {
71+
if (disposed) return
5772
ApplicationManager.getApplication().invokeLater {
5873
this.snykToolWindowPanel.navigateToSourceEnabled = false
5974
displayOssResults(snykResults)
@@ -65,6 +80,7 @@ class SnykToolWindowSnykScanListenerLS(
6580
override fun scanningError(snykScan: SnykScanParams) = Unit
6681

6782
fun displaySnykCodeResults(snykResults: Map<SnykFile, List<ScanIssue>>) {
83+
if (disposed) return
6884
if (getSnykCachedResults(project)?.currentSnykCodeError != null) return
6985

7086
val settings = pluginSettings()
@@ -101,6 +117,7 @@ class SnykToolWindowSnykScanListenerLS(
101117
}
102118

103119
fun displayOssResults(snykResults: Map<SnykFile, List<ScanIssue>>) {
120+
if (disposed) return
104121
if (getSnykCachedResults(project)?.currentOssError != null) return
105122

106123
val settings = pluginSettings()
@@ -180,6 +197,8 @@ class SnykToolWindowSnykScanListenerLS(
180197
securityIssuesCount: Int? = null,
181198
fixableIssuesCount: Int? = null,
182199
) {
200+
if (disposed) return
201+
183202
// only add these info tree nodes to Snyk Code security vulnerabilities for now
184203
if (securityIssuesCount == null) {
185204
return

src/main/kotlin/snyk/amplitude/api/AmplitudeExperimentApiClient.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,19 @@ class AmplitudeExperimentApiClient private constructor(
2828
return try {
2929
val response = variantService().sdkVardata(user).execute()
3030
if (!response.isSuccessful) {
31-
log.warn("Error response: $response")
31+
// we don't really care
32+
log.debug("Error response: $response")
3233
return emptyMap()
3334
}
3435

3536
val variants = response.body()
3637
log.debug("Received variants: $variants")
3738
variants ?: emptyMap()
3839
} catch (e: IOException) {
39-
log.warn("Could not fetch variants because of network communication error with amplitude server", e)
40+
log.debug("Could not fetch variants because of network communication error with amplitude server. Continuing without", e)
4041
emptyMap()
4142
} catch (e: Exception) {
42-
log.warn("Could not fetch variants because of unexpected error", e)
43+
log.info("Could not fetch variants because of unexpected error. Continuing without.", e)
4344
emptyMap()
4445
}
4546
}

0 commit comments

Comments
 (0)