diff --git a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/ignore/IgnoreEntry.java b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/ignore/IgnoreEntry.java index 5d27748c3..0d11c595a 100644 --- a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/ignore/IgnoreEntry.java +++ b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/ignore/IgnoreEntry.java @@ -40,14 +40,19 @@ public static final class FileReference { public String path; public boolean active; public Integer line; + public String problematicLine; public FileReference() { } - public FileReference(String relativePath, boolean b, int line) { + public FileReference(String relativePath, boolean b, int line, String problematicLine) { this.path = relativePath; this.active = b; this.line = line; + if (!problematicLine.isEmpty()) { + this.problematicLine = problematicLine; + } + } } } diff --git a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/ignore/IgnoreManager.java b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/ignore/IgnoreManager.java index a728920f0..27f4ac04a 100644 --- a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/ignore/IgnoreManager.java +++ b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/ignore/IgnoreManager.java @@ -126,7 +126,7 @@ public void addAllIgnoredEntry(ScanIssue issueToIgnore, String clickId) { fileRefs.add(new IgnoreEntry.FileReference( ignoreFileManager.normalizePath(issue.getFilePath()), true, - issue.getLocations().get(0).getLine())); + issue.getLocations().get(0).getLine(), "")); scanFileAndUpdateResults(issue.getFilePath(), issue.getScanEngine()); return true; }); @@ -343,7 +343,7 @@ public IgnoreEntry convertToIgnoredEntryIac(ScanIssue detail, String clickId) { ignoreEntry.setSimilarityId(vulnerability.getSimilarityId()); ignoreEntry.setSeverity(vulnerability.getSeverity()); ignoreEntry.setDescription(vulnerability.getDescription()); - ignoreEntry.setFiles(List.of(new IgnoreEntry.FileReference(relativePath, true, line))); + ignoreEntry.setFiles(List.of(new IgnoreEntry.FileReference(relativePath, true, line, ""))); return ignoreEntry; }); ifExistingIssue(detail, entry); @@ -383,7 +383,7 @@ public IgnoreEntry convertToIgnoredEntryAsca(ScanIssue detail, String clickId) { ignoreEntry.setRuleId(detail.getRuleId()); ignoreEntry.setSeverity(vulnerability.getSeverity()); ignoreEntry.setDescription(vulnerability.getDescription()); - ignoreEntry.setFiles(List.of(new IgnoreEntry.FileReference(relativePath, true, line))); + ignoreEntry.setFiles(List.of(new IgnoreEntry.FileReference(relativePath, true, line, vulnerability.getProblematicLine()))); return ignoreEntry; }); ifExistingIssue(detail, entry); @@ -404,7 +404,7 @@ public IgnoreEntry convertToIgnoredEntry(ScanIssue detail, String clickId) { String vulnerabilityKey = createJsonKeyForIgnoreEntry(detail, clickId); int line = detail.getLocations().get(0).getLine(); IgnoreEntry entry = ignoreFileManager.getIgnoreData().computeIfAbsent(vulnerabilityKey, k -> { - IgnoreEntry.FileReference fileRef = new IgnoreEntry.FileReference(relativePath, true, line); + IgnoreEntry.FileReference fileRef = new IgnoreEntry.FileReference(relativePath, true, line, ""); ArrayList fileReference = new ArrayList<>(); fileReference.add(fileRef); IgnoreEntry ignoreEntry = new IgnoreEntry(); @@ -444,6 +444,15 @@ public IgnoreEntry convertToIgnoredEntry(ScanIssue detail, String clickId) { private void ifExistingIssue(ScanIssue detail, IgnoreEntry entry) { String relativePath = ignoreFileManager.normalizePath(detail.getFilePath()); int line = detail.getLocations().get(0).getLine(); + String problematicLine = null; + if (detail.getScanEngine() == ScanEngine.ASCA && detail.getVulnerabilities() != null && !detail.getVulnerabilities().isEmpty()) { + // Find the matching vulnerability for this line + for (Vulnerability v : detail.getVulnerabilities()) { + // You may want to match by line or other criteria if needed + problematicLine = v.getProblematicLine(); + break; // Use the first for now, or improve as needed + } + } // Ensure files list is mutable if (!(entry.getFiles() instanceof ArrayList)) { entry.setFiles(new ArrayList<>(entry.getFiles())); @@ -453,8 +462,13 @@ private void ifExistingIssue(ScanIssue detail, IgnoreEntry entry) { .findFirst(); if (existing.isPresent()) { existing.get().setActive(true); + // Update problematicLine if ASCA and not set or different + if (detail.getScanEngine() == ScanEngine.ASCA && problematicLine != null && + (existing.get().getProblematicLine() == null || !existing.get().getProblematicLine().equals(problematicLine))) { + existing.get().setProblematicLine(problematicLine); + } } else { - entry.getFiles().add(new IgnoreEntry.FileReference(relativePath, true, line)); + entry.getFiles().add(new IgnoreEntry.FileReference(relativePath, true, line, problematicLine)); } } @@ -721,4 +735,165 @@ private List createIgnoreKeysForScanIssue(ScanIssue scanIssue) { } return keys; } + + /** + * Updates line numbers for ignored ASCA entries based on new scan results, using problematicLine content for matching. + * For each ignored file reference, if a scan result with the same problematicLine is found, update the line number in the ignore entry. + * Removes file references and ignore entries that are no longer present in the scan result. + * + * @param fullScanResults The scan results containing updated line numbers and issues + * @param filePath The path of the file that was scanned and needs line number updates + */ + public void updateLineNumbersForIgnoredEntriesByProblematicLine(ScanResult fullScanResults, String filePath) { + List allIssuesForFile = fullScanResults.getIssues(); + if (allIssuesForFile == null || allIssuesForFile.isEmpty()) { + LOGGER.debug(String.format("ASCA-Ignore: No issues found in scan results for file: %s", filePath)); + return; + } + String relativePath = ignoreFileManager.normalizePath(filePath); + boolean hasChanges = false; + // Build a list of all vulnerabilities with their problematicLine and line number + List vulnerabilitiesWithLine = new ArrayList<>(); + Set presentProblematicLines = new HashSet<>(); + for (ScanIssue scanIssue : allIssuesForFile) { + if (scanIssue.getVulnerabilities() != null) { + for (Vulnerability v : scanIssue.getVulnerabilities()) { + int line = (scanIssue.getLocations() != null && !scanIssue.getLocations().isEmpty()) + ? scanIssue.getLocations().get(0).getLine() : 0; + vulnerabilitiesWithLine.add(new VulnerabilityWithLine(v.getProblematicLine(), line)); + if (v.getProblematicLine() != null) { + presentProblematicLines.add(v.getProblematicLine()); + } + } + } + } + // Collect keys to remove + List keysToRemove = new ArrayList<>(); + // Iterate through all ignore entries using normal for-each + for (Map.Entry mapEntry : ignoreFileManager.getIgnoreData().entrySet()) { + IgnoreEntry ignoreEntry = mapEntry.getValue(); + if (!ScanEngine.ASCA.toString().equalsIgnoreCase(String.valueOf(ignoreEntry.getType()))) { + continue; // Only process ASCA entries + } + // Remove file references that are not present in the scan result + List fileRefs = ignoreEntry.getFiles(); + List fileRefsToRemove = new ArrayList<>(); + for (IgnoreEntry.FileReference fileRef : fileRefs) { + if (fileRef.getPath().equals(relativePath) && fileRef.isActive()) { + String ignoredProblematicLine = fileRef.getProblematicLine(); + // Find a matching vulnerability by problematicLine (null-safe) + VulnerabilityWithLine match = vulnerabilitiesWithLine.stream() + .filter(vwl -> Objects.equals(vwl.problematicLine, ignoredProblematicLine)) + .findFirst().orElse(null); + if (match != null && match.line > 0 && fileRef.getLine() != match.line) { + fileRef.setLine(match.line); + hasChanges = true; + } + // If problematicLine is not present in the scan result, mark this file reference for removal + if (ignoredProblematicLine == null || !presentProblematicLines.contains(ignoredProblematicLine)) { + fileRefsToRemove.add(fileRef); + hasChanges = true; + } + } + } + // Remove marked file references + if (!fileRefsToRemove.isEmpty()) { + fileRefs.removeAll(fileRefsToRemove); + } + // If no file references left, mark the key for removal + if (ignoreEntry.getFiles().isEmpty()) { + keysToRemove.add(mapEntry.getKey()); + } + } + // Remove keys from ignore data + for (String keyToRemove : keysToRemove) { + ignoreFileManager.getIgnoreData().remove(keyToRemove); + hasChanges = true; + } + if (hasChanges) { + ignoreFileManager.saveIgnoreDataToDisk(); + LOGGER.info(String.format("ASCA-Ignore: Line numbers and obsolete entries updated by problematicLine and saved for file: %s", relativePath)); + } else { + LOGGER.debug(String.format("ASCA-Ignore: No line number or entry changes detected by problematicLine for file: %s", relativePath)); + } + } + + // Helper class for matching problematicLine and line together + private static class VulnerabilityWithLine { + String problematicLine; + int line; + VulnerabilityWithLine(String problematicLine, int line) { + this.problematicLine = problematicLine; + this.line = line; + } + } + + /** + * Removes all ASCA ignore entries and file references for a file when there are no issues in the scan result. + * + * @param filePath The path of the file for which ignore entries should be removed + * @return true if any ignore entries were removed, false otherwise + */ + public void removeIgnoreEntriesForFileIfEmpty(String filePath) { + String relativePath = ignoreFileManager.normalizePath(filePath); + List keysToRemove = new ArrayList<>(); + boolean removed = false; + for (Map.Entry mapEntry : ignoreFileManager.getIgnoreData().entrySet()) { + IgnoreEntry ignoreEntry = mapEntry.getValue(); + if (!ScanEngine.ASCA.toString().equalsIgnoreCase(String.valueOf(ignoreEntry.getType()))) { + continue; + } + // Remove file references for this file + List fileRefs = ignoreEntry.getFiles(); + fileRefs.removeIf(fileRef -> fileRef.getPath().equals(relativePath)); + // If no file references left, mark the key for removal + if (ignoreEntry.getFiles().isEmpty()) { + keysToRemove.add(mapEntry.getKey()); + } + } + // Remove keys from ignore data + for (String keyToRemove : keysToRemove) { + ignoreFileManager.getIgnoreData().remove(keyToRemove); + removed = true; + } + if (removed) { + ignoreFileManager.saveIgnoreDataToDisk(); + LOGGER.info(String.format("ASCA-Ignore: Removed ignore entries for file with no issues: %s", relativePath)); + } + } + + public boolean isIgnored(ScanIssue issue, List ignoreEntries, String filePath) { + String normalizedPath = ignoreFileManager.normalizePath(filePath); + boolean isAsca = issue.getScanEngine() == ScanEngine.ASCA; + // For ASCA, check problematicLine for all vulnerabilities + if (isAsca && issue.getVulnerabilities() != null && !issue.getVulnerabilities().isEmpty()) { + for (Vulnerability vuln : issue.getVulnerabilities()) { + String issueProblematicLine = vuln.getProblematicLine(); + for (IgnoreEntry entry : ignoreEntries) { + for (IgnoreEntry.FileReference ref : entry.getFiles()) { + boolean pathMatch = ref.isActive() && ref.getPath().equals(normalizedPath); + boolean problematicLineMatch = (issueProblematicLine == null && ref.getProblematicLine() == null) + || (issueProblematicLine != null && issueProblematicLine.equals(ref.getProblematicLine())); + if (pathMatch && problematicLineMatch) { + return true; + } + } + } + } + return false; + } + // Default: match by path and line + int issueLine = issue.getLocations() != null && !issue.getLocations().isEmpty() + ? issue.getLocations().get(0).getLine() + : -1; + for (IgnoreEntry entry : ignoreEntries) { + for (IgnoreEntry.FileReference ref : entry.getFiles()) { + if (ref.isActive() && ref.getPath().equals(normalizedPath) && ref.getLine() == issueLine) { + return true; + } + } + } + return false; + } + } diff --git a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/model/Vulnerability.java b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/model/Vulnerability.java index 8999a79f0..6681e3afa 100644 --- a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/model/Vulnerability.java +++ b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/model/Vulnerability.java @@ -33,5 +33,6 @@ public Vulnerability() { private String expectedValue; private String title; private String SimilarityId; + private String problematicLine; } diff --git a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/scanners/asca/AscaScanResultAdaptor.java b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/scanners/asca/AscaScanResultAdaptor.java index 7e8cf3e99..631cf9dd2 100644 --- a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/scanners/asca/AscaScanResultAdaptor.java +++ b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/scanners/asca/AscaScanResultAdaptor.java @@ -28,6 +28,7 @@ public class AscaScanResultAdaptor implements com.checkmarx.intellij.devassist.c private final String filePath; private final List scanIssues; + /** * Constructs an instance of {@code AscaScanResultAdaptor} with the specified ASCA scan results. * This adapter allows conversion and processing of ASCA scan results into a standardized format. @@ -80,7 +81,6 @@ private List buildIssues() { if (scanDetails.isEmpty()) { return Collections.emptyList(); } - // Group scan details by line number, then sort by severity precedence Map> groupedIssues = scanDetails.stream() .filter(Objects::nonNull) @@ -192,6 +192,7 @@ private Vulnerability createVulnerability(ScanDetail scanDetail, String override vulnerability.setSeverity(mapSeverity(scanDetail.getSeverity())); vulnerability.setRemediationAdvise(scanDetail.getRemediationAdvise()); vulnerability.setTitle(scanDetail.getRuleName()); + vulnerability.setProblematicLine(scanDetail.getProblematicLine()); return vulnerability; } @@ -225,4 +226,7 @@ private String getUniqueId(ScanDetail scanIssue) { } return ScanEngine.ASCA.name(); } + + + } diff --git a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/scanners/asca/AscaScannerService.java b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/scanners/asca/AscaScannerService.java index f0046b9f0..f54fbfe2a 100644 --- a/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/scanners/asca/AscaScannerService.java +++ b/devassist-lib/src/main/java/com/checkmarx/intellij/devassist/scanners/asca/AscaScannerService.java @@ -7,6 +7,8 @@ import com.checkmarx.intellij.common.wrapper.CxWrapperFactory; import com.checkmarx.intellij.devassist.basescanner.BaseScannerService; import com.checkmarx.intellij.devassist.configuration.ScannerConfig; +import com.checkmarx.intellij.devassist.ignore.IgnoreEntry; +import com.checkmarx.intellij.devassist.ignore.IgnoreFileManager; import com.checkmarx.intellij.devassist.ignore.IgnoreManager; import com.checkmarx.intellij.devassist.telemetry.TelemetryService; import com.checkmarx.intellij.devassist.utils.DevAssistConstants; @@ -28,6 +30,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; /** * Realtime ASCA scanner service that integrates with the realtime scanner system. @@ -36,6 +40,7 @@ public class AscaScannerService extends BaseScannerService { private static final Logger LOGGER = Utils.getLogger(AscaScannerService.class); private static final String ASCA_DIR = "CxASCA"; + private static final Object SCAN_LOCK = new Object(); /** * Creates an ASCA scanner service with the default ASCA realtime configuration. @@ -145,8 +150,26 @@ public com.checkmarx.intellij.devassist.common.ScanResult scan(@NotN LOGGER.debug("ASCA scanner: scan completed - " + uri + " (" + issueCount + " issues found)"); AscaScanResultAdaptor scanResultAdaptor = new AscaScanResultAdaptor(ascaResult, uri); - TelemetryService.logScanResults(scanResultAdaptor, ScanEngine.ASCA); - return scanResultAdaptor; + + // Filter out ignored issues based on problematicLine + IgnoreManager ignoreManager = new IgnoreManager(psiFile.getProject()); + IgnoreFileManager ignoreFileManager = IgnoreFileManager.getInstance(psiFile.getProject()); + List ignoreEntries = ignoreFileManager.getAllIgnoreEntries(); + List filteredIssues = new ArrayList<>(); + for (com.checkmarx.intellij.devassist.model.ScanIssue issue : scanResultAdaptor.getIssues()) { + if (!ignoreManager.isIgnored(issue, ignoreEntries, uri)) { + filteredIssues.add(issue); + } + } + // Return a new adaptor with only non-ignored issues + AscaScanResultAdaptor filteredAdaptor = new AscaScanResultAdaptor(ascaResult, uri) { + @Override + public List getIssues() { + return filteredIssues; + } + }; + TelemetryService.logScanResults(filteredAdaptor, ScanEngine.ASCA); + return filteredAdaptor; } catch (Exception e) { LOGGER.warn("ASCA scanner: scan error for file: " + uri, e); @@ -181,12 +204,12 @@ private ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestV return null; } + synchronized (SCAN_LOCK) { String tempFilePath = saveTempFile(file.getName(), fileContent); if (tempFilePath == null) { LOGGER.warn("Failed to create temporary file for ASCA scan."); return null; } - try { LOGGER.info(Strings.join("Starting ASCA scan on file: ", virtualFile.getPath())); ScanResult scanResult = scanAscaFile(tempFilePath, ascLatestVersion, agent, DevAssistUtils.getIgnoreFilePath(project)); @@ -200,6 +223,7 @@ private ScanResult runAscaScan(PsiFile file, Project project, boolean ascLatestV } finally { deleteFile(tempFilePath); } + } } /** @@ -441,7 +465,9 @@ private void updateIgnoredFileDataOnLatestResult(String tempFilePath, Project pr ScanResult fullScanResult = scanAscaFile(tempFilePath, ascLatestVersion, agent, ""); if (fullScanResult.getScanDetails() != null && !fullScanResult.getScanDetails().isEmpty()) { AscaScanResultAdaptor fullScanResultAdaptor = new AscaScanResultAdaptor(fullScanResult, filePath); - ignoreManager.updateLineNumbersForIgnoredEntries(fullScanResultAdaptor, filePath); + ignoreManager.updateLineNumbersForIgnoredEntriesByProblematicLine(fullScanResultAdaptor, filePath); + }else{ + ignoreManager.removeIgnoreEntriesForFileIfEmpty(filePath); } } } catch (Exception e) {