aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2021-06-24 11:13:58 -0500
committersonartech <sonartech@sonarsource.com>2021-06-28 20:03:21 +0000
commit3b0b4eb94c7028abeef6763abdf1a8b6475ad076 (patch)
treef6a4ca6568e2780f673a7b2cb13b45b546d7a100
parent205dba501eb14aae28504b71b22a7a002bd78778 (diff)
downloadsonarqube-3b0b4eb94c7028abeef6763abdf1a8b6475ad076.tar.gz
sonarqube-3b0b4eb94c7028abeef6763abdf1a8b6475ad076.zip
SONAR-15091 Compute engine fails with NPE if an external issue has an empty message
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java2
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineWithoutMessageSensor.java86
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java9
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/tracking/AbstractTrackerTest.java131
-rw-r--r--sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java19
5 files changed, 242 insertions, 5 deletions
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
index 0880d497f44..eb773fa09f6 100644
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
@@ -54,6 +54,7 @@ import org.sonar.xoo.rule.OneCodeSmellIssuePerTestLineSensor;
import org.sonar.xoo.rule.OneDayDebtPerFileSensor;
import org.sonar.xoo.rule.OneExternalIssueOnProjectSensor;
import org.sonar.xoo.rule.OneExternalIssuePerLineSensor;
+import org.sonar.xoo.rule.OneExternalIssuePerLineWithoutMessageSensor;
import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor;
import org.sonar.xoo.rule.OneIssuePerDirectorySensor;
import org.sonar.xoo.rule.OneIssuePerFileSensor;
@@ -147,6 +148,7 @@ public class XooPlugin implements Plugin {
OneIssuePerUnknownFileSensor.class,
OneExternalIssuePerLineSensor.class,
+ OneExternalIssuePerLineWithoutMessageSensor.class,
OneExternalIssueOnProjectSensor.class,
OnePredefinedRuleExternalIssuePerLineSensor.class,
OnePredefinedAndAdHocRuleExternalIssuePerLineSensor.class,
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineWithoutMessageSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineWithoutMessageSensor.java
new file mode 100644
index 00000000000..9b9b090cb5b
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneExternalIssuePerLineWithoutMessageSensor.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.
+ */
+package org.sonar.xoo.rule;
+
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.issue.NewExternalIssue;
+import org.sonar.api.rules.RuleType;
+import org.sonar.xoo.Xoo;
+
+public class OneExternalIssuePerLineWithoutMessageSensor implements Sensor {
+ public static final String RULE_ID = "oneExternalIssuePerLineWithoutMessage";
+ public static final String ENGINE_ID = "XooEngine";
+ public static final String SEVERITY = "MAJOR";
+ public static final Long EFFORT = 10L;
+ public static final RuleType TYPE = RuleType.BUG;
+ public static final String ACTIVATE = "sonar.oneExternalIssuePerLineWithoutMessage.activate";
+ public static final String REGISTER_AD_HOC_RULE = "sonar.oneExternalIssuePerLineWithoutMessage.adhocRule";
+ private static final String NAME = "One External Issue Per Line Without Message";
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name(NAME)
+ .onlyOnLanguages(Xoo.KEY)
+ .onlyWhenConfiguration(c -> c.getBoolean(ACTIVATE).orElse(false));
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ FileSystem fs = context.fileSystem();
+ FilePredicates p = fs.predicates();
+ for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(InputFile.Type.MAIN)))) {
+ createIssues(file, context);
+ }
+ if (context.config().getBoolean(REGISTER_AD_HOC_RULE).orElse(false)) {
+ context.newAdHocRule()
+ .engineId(ENGINE_ID)
+ .ruleId(RULE_ID)
+ .name("An ad hoc rule")
+ .description("blah blah")
+ .severity(Severity.BLOCKER)
+ .type(RuleType.BUG)
+ .save();
+ }
+ }
+
+ private static void createIssues(InputFile file, SensorContext context) {
+ for (int line = 1; line <= file.lines(); line++) {
+ NewExternalIssue newIssue = context.newExternalIssue();
+ newIssue
+ .engineId(ENGINE_ID)
+ .ruleId(RULE_ID)
+ .at(newIssue.newLocation()
+ .on(file)
+ .at(file.selectLine(line))
+ .message(""))
+ .severity(Severity.valueOf(SEVERITY))
+ .remediationEffortMinutes(EFFORT)
+ .type(TYPE)
+ .save();
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java b/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java
index 429017f1f05..220410a4daa 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/tracking/AbstractTracker.java
@@ -74,6 +74,9 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
if (this == o) {
return true;
}
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
LineAndLineHashKey that = (LineAndLineHashKey) o;
// start with most discriminant field
return Objects.equals(line, that.line) && lineHash.equals(that.lineHash) && ruleKey.equals(that.ruleKey);
@@ -108,7 +111,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
}
LineAndLineHashAndMessage that = (LineAndLineHashAndMessage) o;
// start with most discriminant field
- return Objects.equals(line, that.line) && lineHash.equals(that.lineHash) && message.equals(that.message) && ruleKey.equals(that.ruleKey);
+ return Objects.equals(line, that.line) && lineHash.equals(that.lineHash) && Objects.equals(message, that.message) && ruleKey.equals(that.ruleKey);
}
@Override
@@ -138,7 +141,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
}
LineHashAndMessageKey that = (LineHashAndMessageKey) o;
// start with most discriminant field
- return lineHash.equals(that.lineHash) && message.equals(that.message) && ruleKey.equals(that.ruleKey);
+ return lineHash.equals(that.lineHash) && Objects.equals(message, that.message) && ruleKey.equals(that.ruleKey);
}
@Override
@@ -168,7 +171,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> {
}
LineAndMessageKey that = (LineAndMessageKey) o;
// start with most discriminant field
- return Objects.equals(line, that.line) && message.equals(that.message) && ruleKey.equals(that.ruleKey);
+ return Objects.equals(line, that.line) && Objects.equals(message, that.message) && ruleKey.equals(that.ruleKey);
}
@Override
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/tracking/AbstractTrackerTest.java b/sonar-core/src/test/java/org/sonar/core/issue/tracking/AbstractTrackerTest.java
new file mode 100644
index 00000000000..726da91c4cc
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/issue/tracking/AbstractTrackerTest.java
@@ -0,0 +1,131 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.
+ */
+package org.sonar.core.issue.tracking;
+
+import java.util.function.Function;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.tracking.AbstractTracker.LineAndLineHashAndMessage;
+import org.sonar.core.issue.tracking.AbstractTracker.LineAndLineHashKey;
+import org.sonar.core.issue.tracking.AbstractTracker.LineAndMessageKey;
+import org.sonar.core.issue.tracking.AbstractTracker.LineHashAndMessageKey;
+import org.sonar.core.issue.tracking.AbstractTracker.LineHashKey;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AbstractTrackerTest {
+ private final Trackable base = trackable(RuleKey.of("r1", "r1"), 0, "m1", "hash1");
+ private final Trackable same = trackable(RuleKey.of("r1", "r1"), 0, "m1", "hash1");
+ private final Trackable diffRule = trackable(RuleKey.of("r1", "r2"), 0, "m1", "hash1");
+ private final Trackable diffMessage = trackable(RuleKey.of("r1", "r1"), 0, null, "hash1");
+ private final Trackable diffLineHash = trackable(RuleKey.of("r1", "r1"), 0, "m1", null);
+ private final Trackable diffLine = trackable(RuleKey.of("r1", "r1"), null, "m1", "hash1");
+
+ @Test
+ public void lineAndLineHashKey() {
+ Comparator comparator = new Comparator(LineAndLineHashKey::new);
+ comparator.sameEqualsAndHashcode(base, same);
+ comparator.sameEqualsAndHashcode(base, diffMessage);
+ comparator.differentEquals(base, diffRule);
+ comparator.differentEquals(base, diffLineHash);
+ comparator.differentEquals(base, diffLine);
+ }
+
+ @Test
+ public void lineAndLineHashAndMessage() {
+ Comparator comparator = new Comparator(LineAndLineHashAndMessage::new);
+ comparator.sameEqualsAndHashcode(base, same);
+ comparator.differentEquals(base, diffMessage);
+ comparator.differentEquals(base, diffRule);
+ comparator.differentEquals(base, diffLineHash);
+ comparator.differentEquals(base, diffLine);
+ }
+
+ @Test
+ public void lineHashAndMessageKey() {
+ Comparator comparator = new Comparator(LineHashAndMessageKey::new);
+ comparator.sameEqualsAndHashcode(base, same);
+ comparator.sameEqualsAndHashcode(base, diffLine);
+ comparator.differentEquals(base, diffMessage);
+ comparator.differentEquals(base, diffRule);
+ comparator.differentEquals(base, diffLineHash);
+ }
+
+ @Test
+ public void lineAndMessageKey() {
+ Comparator comparator = new Comparator(LineAndMessageKey::new);
+ comparator.sameEqualsAndHashcode(base, same);
+ comparator.sameEqualsAndHashcode(base, diffLineHash);
+ comparator.differentEquals(base, diffMessage);
+ comparator.differentEquals(base, diffRule);
+ comparator.differentEquals(base, diffLine);
+ }
+
+ @Test
+ public void lineHashKey() {
+ Comparator comparator = new Comparator(LineHashKey::new);
+ comparator.sameEqualsAndHashcode(base, same);
+ comparator.sameEqualsAndHashcode(base, diffLine);
+ comparator.sameEqualsAndHashcode(base, diffMessage);
+ comparator.differentEquals(base, diffRule);
+ comparator.differentEquals(base, diffLineHash);
+ }
+
+ private static Trackable trackable(RuleKey ruleKey, Integer line, String message, String lineHash) {
+ Trackable trackable = mock(Trackable.class);
+ when(trackable.getRuleKey()).thenReturn(ruleKey);
+ when(trackable.getLine()).thenReturn(line);
+ when(trackable.getMessage()).thenReturn(message);
+ when(trackable.getLineHash()).thenReturn(lineHash);
+ return trackable;
+ }
+
+ private static class Comparator {
+ private final Function<Trackable, AbstractTracker.SearchKey> searchKeyFactory;
+
+ private Comparator(Function<Trackable, AbstractTracker.SearchKey> searchKeyFactory) {
+ this.searchKeyFactory = searchKeyFactory;
+ }
+
+ private void sameEqualsAndHashcode(Trackable t1, Trackable t2) {
+ AbstractTracker.SearchKey k1 = searchKeyFactory.apply(t1);
+ AbstractTracker.SearchKey k2 = searchKeyFactory.apply(t2);
+
+ assertThat(k1).isEqualTo(k1);
+ assertThat(k1).isEqualTo(k2);
+ assertThat(k2).isEqualTo(k1);
+ assertThat(k1).hasSameHashCodeAs(k1);
+ assertThat(k1).hasSameHashCodeAs(k2);
+ assertThat(k2).hasSameHashCodeAs(k1);
+ }
+
+ private void differentEquals(Trackable t1, Trackable t2) {
+ AbstractTracker.SearchKey k1 = searchKeyFactory.apply(t1);
+ AbstractTracker.SearchKey k2 = searchKeyFactory.apply(t2);
+
+ assertThat(k1).isNotEqualTo(k2);
+ assertThat(k2).isNotEqualTo(k1);
+ assertThat(k1).isNotEqualTo(new Object());
+ assertThat(k1).isNotEqualTo(null);
+ }
+ }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java b/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
index a34fc6f6de1..61d8511f519 100644
--- a/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/issue/tracking/TrackerTest.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
+import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.Rule;
@@ -185,6 +186,19 @@ public class TrackerTest {
assertThat(tracking.getUnmatchedBases()).containsOnly(base);
}
+ // SONAR-15091
+ @Test
+ public void do_not_fail_if_message_is_null() {
+ FakeInput baseInput = new FakeInput("H1", "H2");
+ Issue base = baseInput.createIssueOnLine(1, RULE_UNUSED_LOCAL_VARIABLE, null);
+
+ FakeInput rawInput = new FakeInput("H1", "H2");
+ Issue raw = rawInput.createIssueOnLine(1, RULE_UNUSED_LOCAL_VARIABLE, null);
+
+ Tracking<Issue, Issue> tracking = tracker.trackNonClosed(rawInput, baseInput);
+ assertThat(tracking.baseFor(raw)).isNotNull();
+ }
+
@Test
public void do_not_fail_if_raw_line_does_not_exist() {
FakeInput baseInput = new FakeInput();
@@ -456,7 +470,7 @@ public class TrackerTest {
private final String status;
private final Date updateDate;
- Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status, Date updateDate) {
+ Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, @Nullable String message, String status, Date updateDate) {
this.line = line;
this.lineHash = lineHash;
this.ruleKey = ruleKey;
@@ -470,6 +484,7 @@ public class TrackerTest {
return line;
}
+ @CheckForNull
@Override
public String getMessage() {
return message;
@@ -521,7 +536,7 @@ public class TrackerTest {
/**
* No line (line 0)
*/
- Issue createIssue(RuleKey ruleKey, String message) {
+ Issue createIssue(RuleKey ruleKey, @Nullable String message) {
Issue issue = new Issue(null, "", ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN, new Date());
issues.add(issue);
return issue;