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