You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TrackerRawInputFactory.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.ce.task.projectanalysis.issue;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Optional;
  27. import javax.annotation.Nullable;
  28. import org.sonar.api.rule.RuleKey;
  29. import org.sonar.api.rules.RuleType;
  30. import org.sonar.api.utils.Duration;
  31. import org.sonar.api.utils.log.Loggers;
  32. import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
  33. import org.sonar.ce.task.projectanalysis.component.Component;
  34. import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
  35. import org.sonar.ce.task.projectanalysis.issue.commonrule.CommonRuleEngine;
  36. import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter;
  37. import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
  38. import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
  39. import org.sonar.core.issue.DefaultIssue;
  40. import org.sonar.core.issue.tracking.Input;
  41. import org.sonar.core.issue.tracking.LazyInput;
  42. import org.sonar.core.issue.tracking.LineHashSequence;
  43. import org.sonar.core.util.CloseableIterator;
  44. import org.sonar.db.protobuf.DbCommons;
  45. import org.sonar.db.protobuf.DbIssues;
  46. import org.sonar.scanner.protocol.Constants.Severity;
  47. import org.sonar.scanner.protocol.output.ScannerReport;
  48. import org.sonar.scanner.protocol.output.ScannerReport.IssueType;
  49. import org.sonar.server.rule.CommonRuleKeys;
  50. import static org.apache.commons.lang.StringUtils.isNotEmpty;
  51. import static org.sonar.api.issue.Issue.STATUS_OPEN;
  52. import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
  53. public class TrackerRawInputFactory {
  54. private static final long DEFAULT_EXTERNAL_ISSUE_EFFORT = 0L;
  55. private final TreeRootHolder treeRootHolder;
  56. private final BatchReportReader reportReader;
  57. private final CommonRuleEngine commonRuleEngine;
  58. private final IssueFilter issueFilter;
  59. private final SourceLinesHashRepository sourceLinesHash;
  60. private final RuleRepository ruleRepository;
  61. private final ActiveRulesHolder activeRulesHolder;
  62. public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader, SourceLinesHashRepository sourceLinesHash, CommonRuleEngine commonRuleEngine,
  63. IssueFilter issueFilter, RuleRepository ruleRepository, ActiveRulesHolder activeRulesHolder) {
  64. this.treeRootHolder = treeRootHolder;
  65. this.reportReader = reportReader;
  66. this.sourceLinesHash = sourceLinesHash;
  67. this.commonRuleEngine = commonRuleEngine;
  68. this.issueFilter = issueFilter;
  69. this.ruleRepository = ruleRepository;
  70. this.activeRulesHolder = activeRulesHolder;
  71. }
  72. public Input<DefaultIssue> create(Component component) {
  73. return new RawLazyInput(component);
  74. }
  75. private class RawLazyInput extends LazyInput<DefaultIssue> {
  76. private final Component component;
  77. private RawLazyInput(Component component) {
  78. this.component = component;
  79. }
  80. @Override
  81. protected LineHashSequence loadLineHashSequence() {
  82. if (component.getType() == Component.Type.FILE) {
  83. return new LineHashSequence(sourceLinesHash.getLineHashesMatchingDBVersion(component));
  84. } else {
  85. return new LineHashSequence(Collections.emptyList());
  86. }
  87. }
  88. @Override
  89. protected List<DefaultIssue> loadIssues() {
  90. List<DefaultIssue> result = new ArrayList<>();
  91. for (DefaultIssue commonRuleIssue : commonRuleEngine.process(component)) {
  92. if (issueFilter.accept(commonRuleIssue, component)) {
  93. result.add(init(commonRuleIssue, STATUS_OPEN));
  94. }
  95. }
  96. if (component.getReportAttributes().getRef() == null) {
  97. return result;
  98. }
  99. try (CloseableIterator<ScannerReport.Issue> reportIssues = reportReader.readComponentIssues(component.getReportAttributes().getRef())) {
  100. // optimization - do not load line hashes if there are no issues -> getLineHashSequence() is executed
  101. // as late as possible
  102. while (reportIssues.hasNext()) {
  103. ScannerReport.Issue reportIssue = reportIssues.next();
  104. if (isOnInactiveRule(reportIssue)) {
  105. continue;
  106. }
  107. if (!isIssueOnUnsupportedCommonRule(reportIssue)) {
  108. Loggers.get(getClass()).debug("Ignored issue from analysis report on rule {}:{}", reportIssue.getRuleRepository(), reportIssue.getRuleKey());
  109. continue;
  110. }
  111. DefaultIssue issue = toIssue(getLineHashSequence(), reportIssue);
  112. if (issueFilter.accept(issue, component)) {
  113. result.add(issue);
  114. }
  115. }
  116. }
  117. Map<RuleKey, ScannerReport.AdHocRule> adHocRuleMap = new HashMap<>();
  118. try (CloseableIterator<ScannerReport.AdHocRule> reportAdHocRule = reportReader.readAdHocRules()) {
  119. while (reportAdHocRule.hasNext()) {
  120. ScannerReport.AdHocRule adHocRule = reportAdHocRule.next();
  121. adHocRuleMap.put(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + adHocRule.getEngineId(), adHocRule.getRuleId()), adHocRule);
  122. }
  123. }
  124. try (CloseableIterator<ScannerReport.ExternalIssue> reportExternalIssues = reportReader.readComponentExternalIssues(component.getReportAttributes().getRef())) {
  125. // optimization - do not load line hashes if there are no issues -> getLineHashSequence() is executed
  126. // as late as possible
  127. while (reportExternalIssues.hasNext()) {
  128. ScannerReport.ExternalIssue reportExternalIssue = reportExternalIssues.next();
  129. result.add(toExternalIssue(getLineHashSequence(), reportExternalIssue, adHocRuleMap));
  130. }
  131. }
  132. return result;
  133. }
  134. private boolean isOnInactiveRule(ScannerReport.Issue reportIssue) {
  135. RuleKey ruleKey = RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey());
  136. return !activeRulesHolder.get(ruleKey).isPresent();
  137. }
  138. private boolean isIssueOnUnsupportedCommonRule(ScannerReport.Issue issue) {
  139. // issues on batch common rules are ignored. This feature
  140. // is natively supported by compute engine since 5.2.
  141. return !issue.getRuleRepository().startsWith(CommonRuleKeys.REPOSITORY_PREFIX);
  142. }
  143. private DefaultIssue toIssue(LineHashSequence lineHashSeq, ScannerReport.Issue reportIssue) {
  144. DefaultIssue issue = new DefaultIssue();
  145. init(issue, STATUS_OPEN);
  146. RuleKey ruleKey = RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey());
  147. issue.setRuleKey(ruleKey);
  148. if (reportIssue.hasTextRange()) {
  149. int startLine = reportIssue.getTextRange().getStartLine();
  150. issue.setLine(startLine);
  151. issue.setChecksum(lineHashSeq.getHashForLine(startLine));
  152. } else {
  153. issue.setChecksum("");
  154. }
  155. if (isNotEmpty(reportIssue.getMsg())) {
  156. issue.setMessage(reportIssue.getMsg());
  157. } else {
  158. Rule rule = ruleRepository.getByKey(ruleKey);
  159. issue.setMessage(rule.getName());
  160. }
  161. if (reportIssue.getSeverity() != Severity.UNSET_SEVERITY) {
  162. issue.setSeverity(reportIssue.getSeverity().name());
  163. }
  164. if (Double.compare(reportIssue.getGap(), 0D) != 0) {
  165. issue.setGap(reportIssue.getGap());
  166. }
  167. DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder();
  168. if (reportIssue.hasTextRange()) {
  169. dbLocationsBuilder.setTextRange(convertTextRange(reportIssue.getTextRange()));
  170. }
  171. for (ScannerReport.Flow flow : reportIssue.getFlowList()) {
  172. if (flow.getLocationCount() > 0) {
  173. DbIssues.Flow.Builder dbFlowBuilder = convertLocations(flow);
  174. dbLocationsBuilder.addFlow(dbFlowBuilder);
  175. }
  176. }
  177. issue.setIsFromExternalRuleEngine(false);
  178. issue.setLocations(dbLocationsBuilder.build());
  179. issue.setQuickFixAvailable(reportIssue.getQuickFixAvailable());
  180. issue.setRuleDescriptionContextKey(reportIssue.hasRuleDescriptionContextKey() ? reportIssue.getRuleDescriptionContextKey() : null);
  181. return issue;
  182. }
  183. private DbIssues.Flow.Builder convertLocations(ScannerReport.Flow flow) {
  184. DbIssues.Flow.Builder dbFlowBuilder = DbIssues.Flow.newBuilder();
  185. for (ScannerReport.IssueLocation location : flow.getLocationList()) {
  186. convertLocation(location).ifPresent(dbFlowBuilder::addLocation);
  187. }
  188. if (isNotEmpty(flow.getDescription())) {
  189. dbFlowBuilder.setDescription(flow.getDescription());
  190. }
  191. toFlowType(flow.getType()).ifPresent(dbFlowBuilder::setType);
  192. return dbFlowBuilder;
  193. }
  194. private DefaultIssue toExternalIssue(LineHashSequence lineHashSeq, ScannerReport.ExternalIssue reportExternalIssue, Map<RuleKey, ScannerReport.AdHocRule> adHocRuleMap) {
  195. DefaultIssue issue = new DefaultIssue();
  196. RuleType type = toRuleType(reportExternalIssue.getType());
  197. init(issue, type == RuleType.SECURITY_HOTSPOT ? STATUS_TO_REVIEW : STATUS_OPEN);
  198. RuleKey ruleKey = RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportExternalIssue.getEngineId(), reportExternalIssue.getRuleId());
  199. issue.setRuleKey(ruleKey);
  200. if (reportExternalIssue.hasTextRange()) {
  201. int startLine = reportExternalIssue.getTextRange().getStartLine();
  202. issue.setLine(startLine);
  203. issue.setChecksum(lineHashSeq.getHashForLine(startLine));
  204. } else {
  205. issue.setChecksum("");
  206. }
  207. if (isNotEmpty(reportExternalIssue.getMsg())) {
  208. issue.setMessage(reportExternalIssue.getMsg());
  209. }
  210. if (reportExternalIssue.getSeverity() != Severity.UNSET_SEVERITY) {
  211. issue.setSeverity(reportExternalIssue.getSeverity().name());
  212. }
  213. issue.setEffort(Duration.create(reportExternalIssue.getEffort() != 0 ? reportExternalIssue.getEffort() : DEFAULT_EXTERNAL_ISSUE_EFFORT));
  214. DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder();
  215. if (reportExternalIssue.hasTextRange()) {
  216. dbLocationsBuilder.setTextRange(convertTextRange(reportExternalIssue.getTextRange()));
  217. }
  218. for (ScannerReport.Flow flow : reportExternalIssue.getFlowList()) {
  219. if (flow.getLocationCount() > 0) {
  220. DbIssues.Flow.Builder dbFlowBuilder = convertLocations(flow);
  221. dbLocationsBuilder.addFlow(dbFlowBuilder);
  222. }
  223. }
  224. issue.setIsFromExternalRuleEngine(true);
  225. issue.setLocations(dbLocationsBuilder.build());
  226. issue.setType(type);
  227. ruleRepository.addOrUpdateAddHocRuleIfNeeded(ruleKey, () -> toAdHocRule(reportExternalIssue, adHocRuleMap.get(issue.ruleKey())));
  228. return issue;
  229. }
  230. private NewAdHocRule toAdHocRule(ScannerReport.ExternalIssue reportIssue, @Nullable ScannerReport.AdHocRule adHocRule) {
  231. if (adHocRule != null) {
  232. return new NewAdHocRule(adHocRule);
  233. }
  234. return new NewAdHocRule(reportIssue);
  235. }
  236. private Optional<DbIssues.FlowType> toFlowType(ScannerReport.FlowType flowType) {
  237. switch (flowType) {
  238. case DATA:
  239. return Optional.of(DbIssues.FlowType.DATA);
  240. case EXECUTION:
  241. return Optional.of(DbIssues.FlowType.EXECUTION);
  242. case UNDEFINED:
  243. return Optional.empty();
  244. default:
  245. throw new IllegalArgumentException("Unrecognized type: " + flowType);
  246. }
  247. }
  248. private RuleType toRuleType(IssueType type) {
  249. switch (type) {
  250. case BUG:
  251. return RuleType.BUG;
  252. case CODE_SMELL:
  253. return RuleType.CODE_SMELL;
  254. case VULNERABILITY:
  255. return RuleType.VULNERABILITY;
  256. case SECURITY_HOTSPOT:
  257. return RuleType.SECURITY_HOTSPOT;
  258. case UNRECOGNIZED:
  259. default:
  260. throw new IllegalStateException("Invalid issue type: " + type);
  261. }
  262. }
  263. private DefaultIssue init(DefaultIssue issue, String initialStatus) {
  264. issue.setStatus(initialStatus);
  265. issue.setResolution(null);
  266. issue.setComponentUuid(component.getUuid());
  267. issue.setComponentKey(component.getKey());
  268. issue.setProjectUuid(treeRootHolder.getRoot().getUuid());
  269. issue.setProjectKey(treeRootHolder.getRoot().getKey());
  270. return issue;
  271. }
  272. private Optional<DbIssues.Location> convertLocation(ScannerReport.IssueLocation source) {
  273. DbIssues.Location.Builder target = DbIssues.Location.newBuilder();
  274. if (source.getComponentRef() != 0 && source.getComponentRef() != component.getReportAttributes().getRef()) {
  275. // SONAR-10781 Component might not exist because on PR, only changed components are included in the report
  276. Optional<Component> optionalComponent = treeRootHolder.getOptionalComponentByRef(source.getComponentRef());
  277. if (!optionalComponent.isPresent()) {
  278. return Optional.empty();
  279. }
  280. target.setComponentId(optionalComponent.get().getUuid());
  281. }
  282. if (isNotEmpty(source.getMsg())) {
  283. target.setMsg(source.getMsg());
  284. }
  285. if (source.hasTextRange()) {
  286. ScannerReport.TextRange sourceRange = source.getTextRange();
  287. DbCommons.TextRange.Builder targetRange = convertTextRange(sourceRange);
  288. target.setTextRange(targetRange);
  289. }
  290. return Optional.of(target.build());
  291. }
  292. private DbCommons.TextRange.Builder convertTextRange(ScannerReport.TextRange sourceRange) {
  293. DbCommons.TextRange.Builder targetRange = DbCommons.TextRange.newBuilder();
  294. targetRange.setStartLine(sourceRange.getStartLine());
  295. targetRange.setStartOffset(sourceRange.getStartOffset());
  296. targetRange.setEndLine(sourceRange.getEndLine());
  297. targetRange.setEndOffset(sourceRange.getEndOffset());
  298. return targetRange;
  299. }
  300. }
  301. }