diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2021-06-24 11:13:58 -0500 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-06-28 20:03:21 +0000 |
commit | 3b0b4eb94c7028abeef6763abdf1a8b6475ad076 (patch) | |
tree | f6a4ca6568e2780f673a7b2cb13b45b546d7a100 | |
parent | 205dba501eb14aae28504b71b22a7a002bd78778 (diff) | |
download | sonarqube-3b0b4eb94c7028abeef6763abdf1a8b6475ad076.tar.gz sonarqube-3b0b4eb94c7028abeef6763abdf1a8b6475ad076.zip |
SONAR-15091 Compute engine fails with NPE if an external issue has an empty message
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; |