diff options
author | Jacek <jacek.poreda@sonarsource.com> | 2022-08-04 10:43:44 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-08-04 20:03:08 +0000 |
commit | 05711eb23f12d08abc53e3722921f906a56ba35b (patch) | |
tree | 8004c6b5751fea188692f2d0088be5a803e2edcc | |
parent | a4f68e401d861e24eacc9f761561ad2288b9b364 (diff) | |
download | sonarqube-05711eb23f12d08abc53e3722921f906a56ba35b.tar.gz sonarqube-05711eb23f12d08abc53e3722921f906a56ba35b.zip |
SONAR-16583 Fix NPE in case location is in the same file
2 files changed, 40 insertions, 3 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComputeLocationHashesVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComputeLocationHashesVisitor.java index 7a9f4fdf220..b85ccaeff92 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComputeLocationHashesVisitor.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComputeLocationHashesVisitor.java @@ -34,6 +34,8 @@ import org.sonar.db.protobuf.DbCommons; import org.sonar.db.protobuf.DbIssues; import org.sonar.server.issue.TaintChecker; +import static org.apache.commons.lang.StringUtils.defaultIfEmpty; + /** * This visitor will update the locations field of issues, by filling the hashes for all locations. * It only applies to issues that are taint vulnerabilities and that are new or were changed. @@ -75,7 +77,7 @@ public class ComputeLocationHashesVisitor extends IssueVisitor { } DbIssues.Locations.Builder primaryLocationBuilder = ((DbIssues.Locations) issue.getLocations()).toBuilder(); - boolean hasTextRange = addLocations(component, locationsByComponent, primaryLocationBuilder); + boolean hasTextRange = addLocations(component, issue, locationsByComponent, primaryLocationBuilder); // If any location was added (because it had a text range), we'll need to update the issue at the end with the new object containing the hashes if (hasTextRange) { @@ -95,7 +97,7 @@ public class ComputeLocationHashesVisitor extends IssueVisitor { issues.clear(); } - private boolean addLocations(Component component, Map<Component, List<Location>> locationsByComponent, DbIssues.Locations.Builder primaryLocationBuilder) { + private boolean addLocations(Component component, DefaultIssue issue, Map<Component, List<Location>> locationsByComponent, DbIssues.Locations.Builder primaryLocationBuilder) { boolean hasTextRange = false; // Add primary location @@ -110,7 +112,8 @@ public class ComputeLocationHashesVisitor extends IssueVisitor { for (DbIssues.Location.Builder locationBuilder : flowBuilder.getLocationBuilderList()) { if (locationBuilder.hasTextRange()) { hasTextRange = true; - Component locationComponent = treeRootHolder.getComponentByUuid(locationBuilder.getComponentId()); + var componentUuid = defaultIfEmpty(locationBuilder.getComponentId(), issue.componentUuid()); + Component locationComponent = treeRootHolder.getComponentByUuid(componentUuid); locationsByComponent.computeIfAbsent(locationComponent, c -> new LinkedList<>()).add(new SecondaryLocation(locationBuilder)); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComputeLocationHashesVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComputeLocationHashesVisitorTest.java index 5fdb62f1238..24c3ee84534 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComputeLocationHashesVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComputeLocationHashesVisitorTest.java @@ -52,6 +52,7 @@ import static org.mockito.Mockito.when; public class ComputeLocationHashesVisitorTest { private static final String EXAMPLE_LINE_OF_CODE_FORMAT = "int example = line + of + code + %d; "; private static final String LINE_IN_THE_MAIN_FILE = "String string = 'line-in-the-main-file';"; + private static final String ANOTHER_LINE_IN_THE_MAIN_FILE = "String string = 'another-line-in-the-main-file';"; private static final String LINE_IN_ANOTHER_FILE = "String string = 'line-in-the-another-file';"; private static final RuleKey RULE_KEY = RuleKey.of("javasecurity", "S001"); @@ -215,6 +216,35 @@ public class ComputeLocationHashesVisitorTest { assertThat(locations.getFlow(0).getLocation(1).getChecksum()).isEqualTo(DigestUtils.md5Hex("Stringstring='line-in-the-another-file';")); } + @Test + public void calculates_hash_for_multiple_locations_in_same_file() { + DefaultIssue issue = createIssue() + .setComponentUuid(FILE_1.getUuid()) + .setLocations(DbIssues.Locations.newBuilder() + .setTextRange(createRange(1, 0, 1, LINE_IN_THE_MAIN_FILE.length())) + .addFlow(DbIssues.Flow.newBuilder() + .addLocation(DbIssues.Location.newBuilder() + .setComponentId(FILE_1.getUuid()) + .setTextRange(createRange(1, 0, 1, LINE_IN_THE_MAIN_FILE.length())) + .build()) + .addLocation(DbIssues.Location.newBuilder() + // component id can be empty if location is in the same file + .setTextRange(createRange(2, 0, 2, ANOTHER_LINE_IN_THE_MAIN_FILE.length())) + .build()) + .build()) + .build()); + + when(sourceLinesRepository.readLines(FILE_1)).thenReturn(manyLinesIterator(LINE_IN_THE_MAIN_FILE, ANOTHER_LINE_IN_THE_MAIN_FILE)); + + underTest.onIssue(FILE_1, issue); + underTest.afterComponent(FILE_1); + + DbIssues.Locations locations = issue.getLocations(); + + assertThat(locations.getFlow(0).getLocation(0).getChecksum()).isEqualTo(DigestUtils.md5Hex("Stringstring='line-in-the-main-file';")); + assertThat(locations.getFlow(0).getLocation(1).getChecksum()).isEqualTo(DigestUtils.md5Hex("Stringstring='another-line-in-the-main-file';")); + } + private DbCommons.TextRange createRange(int startLine, int startOffset, int endLine, int endOffset) { return DbCommons.TextRange.newBuilder() .setStartLine(startLine).setStartOffset(startOffset) @@ -240,6 +270,10 @@ public class ComputeLocationHashesVisitorTest { return CloseableIterator.from(List.of(lineContent).iterator()); } + private CloseableIterator<String> manyLinesIterator(String... lines) { + return CloseableIterator.from(List.of(lines).iterator()); + } + private static class MutableConfiguration implements Configuration { private final Map<String, String> keyValues = new HashMap<>(); |