Merge pull request #6972 from sdedic/adm/multiple-vulnerability-sources
Report all occurences of a vulnerability.
diff --git a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/adm/VulnerabilityWorker.java b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/adm/VulnerabilityWorker.java
index b00f423..68adc66 100644
--- a/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/adm/VulnerabilityWorker.java
+++ b/enterprise/cloud.oracle/src/org/netbeans/modules/cloud/oracle/adm/VulnerabilityWorker.java
@@ -43,12 +43,14 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
@@ -71,8 +73,8 @@
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
+import org.openide.util.Pair;
import org.openide.util.RequestProcessor;
-import org.openide.util.RequestProcessor.Task;
/**
*
@@ -131,6 +133,192 @@
// @GuardedBy(class)
private static VulnerabilityWorker instance;
+ private static String nonNull(String s) {
+ return s == null ? "" : s;
+ }
+
+ /**
+ * Holds mapping from vulnerable dependencies to the source. This is invalidated and
+ * recomputed after each source change from the same dependency result.
+ * This is computed for dependencies found in vulnerability items.
+ */
+ final static class SourceMapping {
+ /**
+ * For each reported Depdendency, its closest parent (including self) with source
+ * information.
+ */
+ final Map<Dependency, Dependency> nodesWithSource = new HashMap<>();
+
+ /**
+ * Source locations for the dependencies. Note the SourceLocation may be implied,
+ * so that it points to a different Dependnecy.
+ */
+ final Map<Dependency, SourceLocation> locations = new HashMap<>();
+
+ /**
+ * Set of files with reported locations. Usually just one.
+ */
+ final Set<FileObject> files = new HashSet<>();
+ }
+
+
+ /**
+ * Records all vulnerabilities for a certain group-artifact-version. Collects all
+ * locations (paths) in the dependency tree where the GAV appears.
+ */
+ final static class VulnerabilityItem {
+ /**
+ * GAV coordinates of the reported artifact
+ */
+ final String gav;
+ /**
+ * List of vulnerabilities found for this artifact GAV
+ */
+ final List<Vulnerability> reports;
+
+ /**
+ * All paths to the artifact through the dependency graph. Each List is a path
+ * from the project root to the identified dependency.
+ */
+ final Set<List<Dependency>> paths = new LinkedHashSet<>();
+
+ public VulnerabilityItem(String gav, List<Vulnerability> reports) {
+ this.gav = gav;
+ this.reports = reports;
+ }
+ }
+
+ private static String getDependencyId(Dependency d) {
+ if (d.getProject() != null) {
+ return d.getProject().getProjectId();
+ } else if (d.getArtifact() != null) {
+ return createGAV(d.getArtifact());
+ } else {
+ return d.toString();
+ }
+ }
+
+ static Pair<Dependency, SourceLocation> findSource(DependencyResult dependencyResult, Dependency dependency) {
+ SourceLocation sourcePath = null;
+ Dependency sd = dependency;
+ for (; sd != null; sd = sd.getParent()) {
+ try {
+ sourcePath = dependencyResult.getDeclarationRange(dependency, null);
+ if (sourcePath != null) {
+ break;
+ }
+ } catch (IOException ex) {
+ LOG.log(Level.WARNING, "Could not load dependency source", ex);
+ }
+ }
+ return Pair.of(sd, sourcePath);
+ }
+
+ static class SourceMappingBuilder {
+ private final DependencyResult dependencyResult;
+ private final Map<String, VulnerabilityItem> itemIndex;
+ private SourceMapping result = new SourceMapping();
+
+ public SourceMappingBuilder(DependencyResult dependencyResult, Map<String, VulnerabilityItem> itemIndex) {
+ this.dependencyResult = dependencyResult;
+ this.itemIndex = itemIndex;
+ }
+
+ public SourceMapping build() {
+ for (VulnerabilityItem item : itemIndex.values()) {
+ for (List<Dependency> path : item.paths) {
+ Dependency node = path.get(path.size() - 1);
+ if (result.nodesWithSource.containsKey(node)) {
+ continue;
+ }
+ Pair<Dependency, SourceLocation> s = findSource(dependencyResult, node);
+ result.nodesWithSource.put(node, s.first());
+ if (s.first() == null || s.second() == null) {
+ continue;
+ }
+ SourceLocation l = s.second();
+ result.nodesWithSource.put(node, s.first());
+ if (l != null) {
+ result.locations.putIfAbsent(s.first(), l);
+ result.files.add(l.getFile());
+ }
+ }
+ }
+ return result;
+ }
+ }
+
+
+ /**
+ * Builds the vulnerability indices from the vulnerability report and project dependencies. The outcome is "itemIdex" and
+ * "sourceMapping" which are then copied to {@link CacheItem}.
+ */
+ static class CacheDataBuilder {
+ private final DependencyResult dependencyResult;
+ private final VulnerabilityReport report;
+
+ private Map<String, VulnerabilityItem> itemIndex = new HashMap<>();
+ private SourceMapping sourceMapping;
+ private Set<String> uniqueDeps = new HashSet<>();
+
+ public CacheDataBuilder(DependencyResult dependencyResult, VulnerabilityReport report) {
+ this.dependencyResult = dependencyResult;
+ this.report = report;
+ }
+
+ private void initVulnerabilityIndex() {
+ this.itemIndex = new HashMap<>();
+ for (ApplicationDependencyVulnerabilitySummary s: report.items) {
+ VulnerabilityItem item = new VulnerabilityItem(s.getGav(), s.getVulnerabilities());
+ itemIndex.put(s.getGav(), item);
+ }
+ }
+
+ private void buildDependencyMap(Dependency dependency, List<Dependency> path, Set<String> pathToRoot) {
+ String gav = getDependencyId(dependency);
+ if (gav == null || !pathToRoot.add(gav)) {
+ return;
+ }
+ uniqueDeps.add(gav);
+
+ pathToRoot.add(gav);
+ try {
+ VulnerabilityItem item = itemIndex.get(gav);
+ if (item != null) {
+ List<Dependency> p = new ArrayList<>(path);
+ p.add(dependency);
+ item.paths.add(p);
+ }
+
+ if (dependency != dependencyResult.getRoot()) {
+ path.add(dependency);
+ }
+ dependency.getChildren().forEach((childDependency) -> {
+ buildDependencyMap(childDependency, path, pathToRoot);
+ });
+ if (!path.isEmpty()) {
+ Dependency x = path.remove(path.size() - 1);
+ assert x == dependency;
+ }
+ } finally {
+ pathToRoot.remove(gav);
+ }
+ }
+
+ Map<String, VulnerabilityItem> build() {
+ initVulnerabilityIndex();
+ SourceLocation rootLocation = null;
+ try {
+ rootLocation = dependencyResult.getDeclarationRange(dependencyResult.getRoot(), null);
+ } catch (IOException ex) {
+ LOG.log(Level.WARNING, "Could not load dependency source", ex);
+ }
+ buildDependencyMap(this.dependencyResult.getRoot(), new ArrayList<>(), new HashSet<>());
+ sourceMapping = new SourceMappingBuilder(dependencyResult, itemIndex).build();
+ return itemIndex;
+ }
+ }
+
/**
* Cached information + watcher over the project file data. Will watch for dependency change event,
* that is fired e.g. after project reload, and will REPLACE ITSELF in the cache + fire
@@ -141,17 +329,24 @@
private final DependencyResult dependencyResult;
private final VulnerabilityReport report;
- /**
- * Maps GAV -> dependency.
- */
- private Map<String, Dependency> dependencyMap;
+ // @GuardedBy(this) -- initialization only
+ private Map<String, VulnerabilityItem> itemIndex;
+ // @GuardedBy(this) -- initialization only
+ private SourceMapping sourceMapping;
+ /**
+ * Number of dependencies
+ */
+ // @GuardedBy(this) -- initialization only
+ private int uniqueDependencies;
+
// @GuardedBy(this)
private ChangeListener depChange;
+ private ChangeListener sourceChange;
// @GuardedBy(this)
private RequestProcessor.Task pendingRefresh;
-
+
public CacheItem(Project project, DependencyResult dependency, VulnerabilityReport report) {
this.project = project;
this.dependencyResult = dependency;
@@ -169,52 +364,76 @@
public List<ApplicationDependencyVulnerabilitySummary> getVulnerabilities() {
return report.items;
}
-
- public Map<String, Dependency> getDependencyMap() {
- if (dependencyMap == null) {
- dependencyMap = new HashMap<>();
- buildDependecyMap(dependencyResult.getRoot(), dependencyMap);
- }
- return dependencyMap;
- }
- private void buildDependecyMap(Dependency dependency, Map<String, Dependency> result) {
- String gav = createGAV(dependency.getArtifact());
- if (gav != null && result.putIfAbsent(gav, dependency) == null) {
- dependency.getChildren().forEach((childDependency) -> {
- buildDependecyMap(childDependency, result);
- });
+ private synchronized void initialize() {
+ if (itemIndex != null) {
+ return;
}
+ startListening();
+ CacheDataBuilder b = new CacheDataBuilder(dependencyResult, report);
+ Map<String, VulnerabilityItem> items = b.build();
+ this.itemIndex = b.itemIndex;
+ this.sourceMapping = b.sourceMapping;
+ this.uniqueDependencies = b.uniqueDeps.size();
}
public Set<FileObject> getProblematicFiles() {
if (getAudit().getIsSuccess()) {
return Collections.EMPTY_SET;
}
- Set<FileObject> result = new HashSet<>();
- for (ApplicationDependencyVulnerabilitySummary v: getVulnerabilities()){
- List<Vulnerability> vulnerabilities = v.getVulnerabilities();
- if (!vulnerabilities.isEmpty()) {
- Dependency dep = getDependencyMap().get(v.getGav());
- if (dep != null) {
- try {
- SourceLocation declarationRange = this.dependencyResult.getDeclarationRange(dep, null);
- if (declarationRange == null) {
- declarationRange = this.dependencyResult.getDeclarationRange(dep, DependencyResult.PART_CONTAINER);
- }
- if(declarationRange != null && declarationRange.getFile() != null) {
- result.add(declarationRange.getFile());
- }
- } catch (IOException ex) {
- // expected, ignore.
- }
- }
+ initialize();
+ return sourceMapping.files;
+ }
+
+ VulnerabilityItem findVulnerability(String gav) {
+ initialize();
+ return itemIndex.get(gav);
+ }
+
+ void refreshSourceMapping(RequestProcessor.Task t) {
+ SourceMapping old;
+ SourceMapping m = new SourceMappingBuilder(dependencyResult, itemIndex).build();
+ Set<FileObject> files = new HashSet<>();
+ synchronized (this) {
+ // should block on this until t[0] is assigned
+ if (pendingRefresh != t) {
+ return;
}
+ old = this.sourceMapping;
+ sourceMapping = m;
}
- return result;
+ if (old != null) {
+ files.addAll(old.files);
+ }
+ Diagnostic.ReporterControl reporter = Diagnostic.findReporterControl(Lookup.getDefault(), project.getProjectDirectory());
+ files.addAll(m.files);
+ reporter.diagnosticChanged(files, null);
}
void refreshDependencies(RequestProcessor.Task t) {
+ boolean model;
+ synchronized (cache) {
+ CacheItem registered = cache.get(project);
+ if (registered != this) {
+ return;
+ }
+ }
+ synchronized (this) {
+ // should block on this until t[0] is assigned
+ if (pendingRefresh != t) {
+ return;
+ }
+ model = modelChanged || itemIndex == null;
+ modelChanged = false;
+ }
+ if (model) {
+ refreshProjectDependencies(t);
+ } else {
+ refreshSourceMapping(t);
+ }
+ }
+
+ void refreshProjectDependencies(RequestProcessor.Task t) {
DependencyResult dr = ProjectDependencies.findDependencies(project, ProjectDependencies.newQuery(Scopes.RUNTIME));
LOG.log(Level.FINER, "{0} - dependencies refreshed", this);
synchronized (this) {
@@ -222,12 +441,13 @@
if (pendingRefresh != t) {
return;
}
+ modelChanged = false;
}
CacheItem novy = new CacheItem( project, dr, report);
if (LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, "{0} - trying to replace for {1}", new Object[] { this, novy });
}
- if (replaceCacheItem(this, novy)) {
+ if (replaceCacheItem(project, this, novy)) {
novy.startListening();
Diagnostic.ReporterControl reporter = Diagnostic.findReporterControl(Lookup.getDefault(), project.getProjectDirectory());
Set<FileObject> allFiles = new HashSet<>();
@@ -236,16 +456,31 @@
if (LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, "{0} - refreshing files: {1}", new Object[] { this, allFiles });
}
- reporter.diagnosticChanged(novy.getProblematicFiles(), null);
+ reporter.diagnosticChanged(allFiles, null);
}
}
+ /**
+ * True, if the project model changed; false, if only source has changed.
+ */
+ private boolean modelChanged;
+
+ void scheduleSourceRefresh(ChangeEvent e) {
+ // PENDING: enable when Maven and gradle dependency implementation stabilizaes on reading the
+ // actual source.
+ // scheduleRefresh(false);
+ }
+
void scheduleDependencyRefresh(ChangeEvent e) {
+ scheduleRefresh(true);
+ }
+
+ private void scheduleRefresh(boolean modelChanged) {
synchronized (this) {
if (pendingRefresh != null) {
pendingRefresh.cancel();
}
-
+ modelChanged |= modelChanged;
RequestProcessor.Task[] task = new RequestProcessor.Task[1];
if (LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, "{0} - scheduling refresh for {1}", new Object[] { this, project });
@@ -261,15 +496,12 @@
}
}
- SourceLocation getDependencyRange(Dependency d) throws IOException {
- return getDependencyRange(d, null);
- }
-
void startListening() {
synchronized (this) {
if (depChange == null) {
dependencyResult.addChangeListener(depChange = this::scheduleDependencyRefresh);
LOG.log(Level.FINER, "{0} - start listen for dependencies", this);
+ dependencyResult.addSourceChangeListener(sourceChange = this::scheduleSourceRefresh);
}
}
}
@@ -280,13 +512,38 @@
dependencyResult.removeChangeListener(depChange);
// intentionally does not clean depChange, to make a subsequent startListening no-op.
LOG.log(Level.FINER, "{0} - stop listen for dependencies", this);
+ dependencyResult.removeSourceChangeListener(sourceChange);
}
}
}
- SourceLocation getDependencyRange(Dependency d, String part) throws IOException {
- startListening();
- return dependencyResult.getDeclarationRange(d, part);
+ Diagnostic createDiagnostic(Dependency owner, Dependency dependency, List<Dependency> path, Vulnerability vulnerability, SourceLocation declarationRange) {
+ String ownerGav = owner == null ? null : getDependencyId(owner);
+ String message =
+ ownerGav == null ?
+ Bundle.MSG_Diagnostic(
+ formatCvssScore(vulnerability.getCvssV2Score()),
+ formatCvssScore(vulnerability.getCvssV3Score()),
+ getDependencyId(dependency)) :
+ Bundle.MSG_Diagnostic_Included(
+ formatCvssScore(vulnerability.getCvssV2Score()),
+ formatCvssScore(vulnerability.getCvssV3Score()),
+ getDependencyId(dependency),
+ ownerGav);
+ SourceLocation fDeclarationRange = declarationRange;
+ Diagnostic.Builder builder = Diagnostic.Builder.create(() -> fDeclarationRange.getStartOffset(), () -> fDeclarationRange.getEndOffset(), message);
+ builder.setSeverity(Diagnostic.Severity.Warning);
+ try {
+ builder.setCodeDescription(new URL(GOV_DETAIL_URL + vulnerability.getId()));
+ } catch (MalformedURLException ex) {
+ // perhaps should not happen at all
+ LOG.log(Level.INFO, "Could not link to vulnerability: {0}", vulnerability.getId());
+ }
+
+ // TODO: when Diagnostic supports (some) ID, change to report the unique id separately, now it is embedded into the diagnostic code.
+ String pathString = path.stream().map(d -> getDependencyId(d)).collect(Collectors.joining("/"));
+ builder.setCode(vulnerability.getId() + "~~" + pathString);
+ return builder.build();
}
public List<Diagnostic> getDiagnosticsForFile(FileObject file) {
@@ -297,69 +554,68 @@
return null;
}
+ initialize();
+
List<Diagnostic> result = new ArrayList<>();
- for (ApplicationDependencyVulnerabilitySummary v: getVulnerabilities()){
- List<Vulnerability> vulnerabilities = v.getVulnerabilities();
- if (!vulnerabilities.isEmpty()) {
- startListening();
- Dependency dependency = getDependencyMap().get(v.getGav());
- SourceLocation declarationRange = null;
+ SourceLocation containerLocation = null;
+
+ for (VulnerabilityItem item : this.itemIndex.values()) {
+ boolean unreported = true;
+ Set<Dependency> anchors = new HashSet<>();
+ for (List<Dependency> path : item.paths) {
+ Dependency dependency = path.get(path.size() - 1);
- if (dependency != null) {
- try {
- declarationRange = getDependencyRange(dependency);
- } catch (IOException ex) {
- Exceptions.printStackTrace(ex);
- }
- }
- // display the vulnerabilities that were never mapped on the dependency container's line.
- // also display the vulnerabilities that we KNOW about, but do can not map them back to the project file, i.e.
- // plugin-introduced dependencies (gradle).
- if (declarationRange == null && (dependency != null || !report.mappedVulnerabilities.contains(v.getGav()))) {
- try {
- if (LOG.isLoggable(Level.FINER)) {
- LOG.log(Level.FINER, "{0} getDiagnostics called for {1}", new Object[] { this, file });
- }
+ SourceLocation declarationRange = null;
- declarationRange = getDependencyRange(dependency, DependencyResult.PART_CONTAINER);
- if (declarationRange != null) {
- // discard end location, since it could span a whole section
- declarationRange = new SourceLocation(declarationRange.getFile(),
- declarationRange.getStartOffset(), declarationRange.getStartOffset(), null);
- }
- } catch (IOException ex) {
- // ignore
- }
+ String ownerGav = null;
+ Dependency withSource = sourceMapping.nodesWithSource.get(dependency);
+ if (withSource != null) {
+ declarationRange = sourceMapping.locations.get(withSource); // XXX, neprepisovat nullem
+ }
+ if (declarationRange == null) {
+ // will deal with unampped vulnerabilities later.
+ continue;
+ }
+ Dependency owner = withSource;
+ if (withSource != dependency) {
+ // the dependency result may not report implied sources, so fallback
+ // to the closest parent artifact
+ ownerGav = getDependencyId(withSource);
}
- if (declarationRange != null && declarationRange.hasPosition() && declarationRange.getFile().equals(file)) {
- final SourceLocation fDeclarationRange = declarationRange;
- String ownerGav = null;
- if (fDeclarationRange.getImpliedBy() instanceof Dependency) {
- Dependency owner = (Dependency)fDeclarationRange.getImpliedBy();
- ownerGav = createGAV(owner.getArtifact());
- }
- for(Vulnerability vulnerability: vulnerabilities) {
- String message =
- ownerGav == null ?
- Bundle.MSG_Diagnostic(
- formatCvssScore(vulnerability.getCvssV2Score()),
- formatCvssScore(vulnerability.getCvssV3Score()),
- createGAV(dependency.getArtifact())) :
- Bundle.MSG_Diagnostic_Included(
- formatCvssScore(vulnerability.getCvssV2Score()),
- formatCvssScore(vulnerability.getCvssV3Score()),
- createGAV(dependency.getArtifact()),
- ownerGav);
- Diagnostic.Builder builder = Diagnostic.Builder.create(() -> fDeclarationRange.getStartOffset(), () -> fDeclarationRange.getEndOffset(), message);
- builder.setSeverity(Diagnostic.Severity.Warning).setCode(vulnerability.getId());
+ if (declarationRange.getImpliedBy() instanceof Dependency) {
+ owner = (Dependency)declarationRange.getImpliedBy();
+ ownerGav = getDependencyId(owner);
+ }
+ if (!anchors.add(owner)) {
+ // do not report additional "foo" included from "bar", if already reported.
+ continue;
+ }
+ for (Vulnerability vulnerability : item.reports) {
+ unreported = false;
+ result.add(createDiagnostic(owner, dependency, path, vulnerability, declarationRange));
+ }
+ }
+ if (unreported && !item.paths.isEmpty()) {
+ // if the vulnerability item was matched initially and now it is not found, or mapped to a source,
+ // the user may have removed it from the project without running the analysis again - it should not be reported, it will eventually reappear
+ // after next analysis.
+ // But if it is not in the original map for some reason, report on the container location
+ if (!report.getMappedVulnerabilities().contains(item.gav)) {
+ if (containerLocation == null) {
try {
- builder.setCodeDescription(new URL(GOV_DETAIL_URL + vulnerability.getId()));
- } catch (MalformedURLException ex) {
- // perhaps should not happen at all
- LOG.log(Level.INFO, "Could not link to vulnerability: {0}", vulnerability.getId());
+ containerLocation = dependencyResult.getDeclarationRange(dependencyResult.getRoot(), DependencyResult.PART_CONTAINER);
+ } catch (IOException ex) {
+ LOG.log(Level.WARNING, "Could not load container location", ex);
}
- result.add(builder.build());
+ if (containerLocation == null) {
+ containerLocation = new SourceLocation(project.getProjectDirectory(), 0, 0, null);
+ }
+ }
+ for (Vulnerability vulnerability : item.reports) {
+ List<Dependency> path = item.paths.iterator().next();
+ Dependency dependency = path.get(path.size() - 1);
+ result.add(createDiagnostic(null, dependency, path, vulnerability, containerLocation));
}
}
}
@@ -375,18 +631,25 @@
}
}
- private static boolean replaceCacheItem(CacheItem old, CacheItem novy) {
+ private static boolean replaceCacheItem(Project p, CacheItem novy) {
+ return replaceCacheItem(p, null, novy);
+ }
+
+ private static boolean replaceCacheItem(Project p, CacheItem old, CacheItem novy) {
+ CacheItem registered;
synchronized (cache) {
- CacheItem registered = cache.get(old.project);
- if (old != null && registered != old) {
- old.stopListening();
- return false;
+ registered = cache.get(p);
+ if (old != null) {
+ if (old != registered) {
+ old.stopListening();
+ return false;
+ }
+ }
+ if (registered != null) {
+ registered.stopListening();
}
cache.put(novy.project, novy);
}
- if (old != null) {
- old.stopListening();
- }
return true;
}
@@ -549,7 +812,7 @@
if (cacheItem != null) {
synchronized (cache) {
- cache.put(project, cacheItem);
+ replaceCacheItem(project, cacheItem);
}
Set<FileObject> problematicFiles = new HashSet<>();
@@ -572,8 +835,17 @@
reporter.diagnosticChanged(problematicFiles, null);
List<ArtifactSpec> arts = new ArrayList<>();
+ Set<String> gavs = new HashSet<>();
for (ApplicationDependencyVulnerabilitySummary s : cacheItem.getVulnerabilities()) {
- Dependency d = cacheItem.getDependencyMap().get(s.getGav());
+ if (!gavs.add(s.getGav())) {
+ continue;
+ }
+ VulnerabilityItem i = cacheItem.findVulnerability(s.getGav());
+ if (i == null || i.paths.isEmpty()) {
+ continue;
+ }
+ List<Dependency> p = i.paths.iterator().next();
+ Dependency d = p.get(p.size() - 1);
if (d == null) {
continue;
}
@@ -583,7 +855,7 @@
}
}
AuditResult res = new AuditResult(project, projectDisplayName, cacheItem.report.summary.getId(),
- cacheItem.getDependencyMap().size(), cacheItem.getAudit().getVulnerableArtifactsCount(),
+ cacheItem.uniqueDependencies, cacheItem.getAudit().getVulnerableArtifactsCount(),
arts);
return res;
} else {
@@ -630,9 +902,14 @@
}
// use a random constant that could be sufficient to hold gav text (micronaut-core has max 86)
StringBuilder sb = new StringBuilder(120);
- sb.append(artifact.getGroupId()).append(':');
- sb.append(artifact.getArtifactId()).append(':');
- sb.append(artifact.getVersionSpec());
+ sb.append(String.format("%s:%s:%s",
+ nonNull(artifact.getGroupId()),
+ nonNull(artifact.getArtifactId()),
+ nonNull(artifact.getVersionSpec()))
+ );
+ if (artifact.getClassifier() != null) {
+ sb.append(':').append(artifact.getClassifier());
+ }
return sb.toString();
}
@@ -691,8 +968,8 @@
for (ApplicationDependencyVulnerabilitySummary v : items) {
List<Vulnerability> vulnerabilities = v.getVulnerabilities();
if (!vulnerabilities.isEmpty()) {
- Dependency dependency = cache.getDependencyMap().get(v.getGav());
- if (dependency != null) {
+ VulnerabilityItem item = cache.findVulnerability(v.getGav());
+ if (item != null && !item.paths.isEmpty()) {
mapped.add(v.getGav());
}
}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index 0dd12e5..782ef48 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -2055,7 +2055,14 @@
case Information: diag.setSeverity(DiagnosticSeverity.Information); break;
default: throw new IllegalStateException("Unknown severity: " + err.getSeverity());
}
- diag.setCode(id2Error.getKey());
+ // TODO: currently Diagnostic.getCode() is misused to provide an unique ID for the diagnostic. This should be changed somehow
+ // at SPI level between ErrorProvider and LSP core. For now, report just part of the (mangled) diagnostics code.
+ String realCode = id2Error.getKey();
+ int idPart = realCode == null ? -1 : realCode.indexOf("~~"); // NOI18N
+ if (idPart != -1) {
+ realCode = realCode.substring(0, idPart);
+ }
+ diag.setCode(realCode);
result.add(diag);
}
if (offset >= 0) {