]> source.dussan.org Git - sonarqube.git/blob
7b8696d3fbd079db60a45eada68737690631739c
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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 java.util.stream.Collectors;
29 import javax.annotation.Nullable;
30 import org.jetbrains.annotations.NotNull;
31 import org.slf4j.LoggerFactory;
32 import org.sonar.api.issue.impact.SoftwareQuality;
33 import org.sonar.api.rule.RuleKey;
34 import org.sonar.api.rules.RuleType;
35 import org.sonar.api.server.rule.internal.ImpactMapper;
36 import org.sonar.api.utils.Duration;
37 import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
38 import org.sonar.ce.task.projectanalysis.component.Component;
39 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
40 import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter;
41 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
42 import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
43 import org.sonar.core.issue.DefaultIssue;
44 import org.sonar.core.issue.tracking.Input;
45 import org.sonar.core.issue.tracking.LazyInput;
46 import org.sonar.core.issue.tracking.LineHashSequence;
47 import org.sonar.core.util.CloseableIterator;
48 import org.sonar.db.protobuf.DbCommons;
49 import org.sonar.db.protobuf.DbIssues;
50 import org.sonar.scanner.protocol.Constants;
51 import org.sonar.scanner.protocol.Constants.Severity;
52 import org.sonar.scanner.protocol.output.ScannerReport;
53 import org.sonar.scanner.protocol.output.ScannerReport.IssueType;
54 import org.sonar.server.rule.CommonRuleKeys;
55
56 import static org.apache.commons.lang.StringUtils.isNotEmpty;
57 import static org.sonar.api.issue.Issue.STATUS_OPEN;
58 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
59
60 public class TrackerRawInputFactory {
61   private static final long DEFAULT_EXTERNAL_ISSUE_EFFORT = 0L;
62   private final TreeRootHolder treeRootHolder;
63   private final BatchReportReader reportReader;
64   private final IssueFilter issueFilter;
65   private final SourceLinesHashRepository sourceLinesHash;
66   private final RuleRepository ruleRepository;
67   private final ActiveRulesHolder activeRulesHolder;
68
69   public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader, SourceLinesHashRepository sourceLinesHash,
70     IssueFilter issueFilter, RuleRepository ruleRepository, ActiveRulesHolder activeRulesHolder) {
71     this.treeRootHolder = treeRootHolder;
72     this.reportReader = reportReader;
73     this.sourceLinesHash = sourceLinesHash;
74     this.issueFilter = issueFilter;
75     this.ruleRepository = ruleRepository;
76     this.activeRulesHolder = activeRulesHolder;
77   }
78
79   public Input<DefaultIssue> create(Component component) {
80     return new RawLazyInput(component);
81   }
82
83   private class RawLazyInput extends LazyInput<DefaultIssue> {
84     private final Component component;
85
86     private RawLazyInput(Component component) {
87       this.component = component;
88     }
89
90     @Override
91     protected LineHashSequence loadLineHashSequence() {
92       if (component.getType() == Component.Type.FILE) {
93         return new LineHashSequence(sourceLinesHash.getLineHashesMatchingDBVersion(component));
94       } else {
95         return new LineHashSequence(Collections.emptyList());
96       }
97     }
98
99     @Override
100     protected List<DefaultIssue> loadIssues() {
101       List<DefaultIssue> result = new ArrayList<>();
102
103       if (component.getReportAttributes().getRef() == null) {
104         return result;
105       }
106
107       try (CloseableIterator<ScannerReport.Issue> reportIssues = reportReader.readComponentIssues(component.getReportAttributes().getRef())) {
108         // optimization - do not load line hashes if there are no issues -> getLineHashSequence() is executed
109         // as late as possible
110         while (reportIssues.hasNext()) {
111           ScannerReport.Issue reportIssue = reportIssues.next();
112           if (isOnInactiveRule(reportIssue)) {
113             continue;
114           }
115           if (!isIssueOnUnsupportedCommonRule(reportIssue)) {
116             LoggerFactory.getLogger(getClass()).debug("Ignored issue from analysis report on rule {}:{}", reportIssue.getRuleRepository(), reportIssue.getRuleKey());
117             continue;
118           }
119           DefaultIssue issue = toIssue(getLineHashSequence(), reportIssue);
120           if (issueFilter.accept(issue, component)) {
121             result.add(issue);
122           }
123         }
124       }
125
126       Map<RuleKey, ScannerReport.AdHocRule> adHocRuleMap = new HashMap<>();
127       try (CloseableIterator<ScannerReport.AdHocRule> reportAdHocRule = reportReader.readAdHocRules()) {
128         while (reportAdHocRule.hasNext()) {
129           ScannerReport.AdHocRule adHocRule = reportAdHocRule.next();
130           adHocRuleMap.put(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + adHocRule.getEngineId(), adHocRule.getRuleId()), adHocRule);
131         }
132       }
133
134       try (CloseableIterator<ScannerReport.ExternalIssue> reportExternalIssues = reportReader.readComponentExternalIssues(component.getReportAttributes().getRef())) {
135         // optimization - do not load line hashes if there are no issues -> getLineHashSequence() is executed
136         // as late as possible
137         while (reportExternalIssues.hasNext()) {
138           ScannerReport.ExternalIssue reportExternalIssue = reportExternalIssues.next();
139           result.add(toExternalIssue(getLineHashSequence(), reportExternalIssue, adHocRuleMap));
140         }
141       }
142
143       return result;
144     }
145
146     private boolean isOnInactiveRule(ScannerReport.Issue reportIssue) {
147       RuleKey ruleKey = RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey());
148       return !activeRulesHolder.get(ruleKey).isPresent();
149     }
150
151     private boolean isIssueOnUnsupportedCommonRule(ScannerReport.Issue issue) {
152       // issues on batch common rules are ignored. This feature
153       // is natively supported by compute engine since 5.2.
154       return !issue.getRuleRepository().startsWith(CommonRuleKeys.REPOSITORY_PREFIX);
155     }
156
157     private DefaultIssue toIssue(LineHashSequence lineHashSeq, ScannerReport.Issue reportIssue) {
158       DefaultIssue issue = new DefaultIssue();
159       init(issue, STATUS_OPEN);
160       RuleKey ruleKey = RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey());
161       issue.setRuleKey(ruleKey);
162       if (reportIssue.hasTextRange()) {
163         int startLine = reportIssue.getTextRange().getStartLine();
164         issue.setLine(startLine);
165         issue.setChecksum(lineHashSeq.getHashForLine(startLine));
166       } else {
167         issue.setChecksum("");
168       }
169       if (isNotEmpty(reportIssue.getMsg())) {
170         issue.setMessage(reportIssue.getMsg());
171         if (!reportIssue.getMsgFormattingList().isEmpty()) {
172           issue.setMessageFormattings(convertMessageFormattings(reportIssue.getMsgFormattingList()));
173         }
174       } else {
175         Rule rule = ruleRepository.getByKey(ruleKey);
176         issue.setMessage(rule.getName());
177       }
178       if (reportIssue.getSeverity() != Severity.UNSET_SEVERITY) {
179         issue.setSeverity(reportIssue.getSeverity().name());
180       }
181       if (Double.compare(reportIssue.getGap(), 0D) != 0) {
182         issue.setGap(reportIssue.getGap());
183       }
184       DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder();
185       if (reportIssue.hasTextRange()) {
186         dbLocationsBuilder.setTextRange(convertTextRange(reportIssue.getTextRange()));
187       }
188       for (ScannerReport.Flow flow : reportIssue.getFlowList()) {
189         if (flow.getLocationCount() > 0) {
190           DbIssues.Flow.Builder dbFlowBuilder = convertLocations(flow);
191           dbLocationsBuilder.addFlow(dbFlowBuilder);
192         }
193       }
194       issue.setIsFromExternalRuleEngine(false);
195       issue.setLocations(dbLocationsBuilder.build());
196       issue.setQuickFixAvailable(reportIssue.getQuickFixAvailable());
197       issue.setRuleDescriptionContextKey(reportIssue.hasRuleDescriptionContextKey() ? reportIssue.getRuleDescriptionContextKey() : null);
198       issue.setCodeVariants(reportIssue.getCodeVariantsList());
199
200       issue.replaceImpacts(convertImpacts(issue.ruleKey(), reportIssue.getOverridenImpactsList()));
201       return issue;
202     }
203
204     private Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> convertImpacts(RuleKey ruleKey, List<ScannerReport.Impact> overridenImpactsList) {
205       if (overridenImpactsList.isEmpty()) {
206         return Collections.emptyMap();
207       }
208       Rule rule = ruleRepository.getByKey(ruleKey);
209       return overridenImpactsList.stream()
210         .filter(i -> rule.getDefaultImpacts().containsKey(SoftwareQuality.valueOf(i.getSoftwareQuality())))
211         .collect(Collectors.toMap(i -> SoftwareQuality.valueOf(i.getSoftwareQuality()), i -> org.sonar.api.issue.impact.Severity.valueOf(i.getSeverity())));
212     }
213
214     private DbIssues.Flow.Builder convertLocations(ScannerReport.Flow flow) {
215       DbIssues.Flow.Builder dbFlowBuilder = DbIssues.Flow.newBuilder();
216       for (ScannerReport.IssueLocation location : flow.getLocationList()) {
217         convertLocation(location).ifPresent(dbFlowBuilder::addLocation);
218       }
219       if (isNotEmpty(flow.getDescription())) {
220         dbFlowBuilder.setDescription(flow.getDescription());
221       }
222       toFlowType(flow.getType()).ifPresent(dbFlowBuilder::setType);
223       return dbFlowBuilder;
224     }
225
226
227     private DefaultIssue toExternalIssue(LineHashSequence lineHashSeq, ScannerReport.ExternalIssue reportExternalIssue, Map<RuleKey, ScannerReport.AdHocRule> adHocRuleMap) {
228       DefaultIssue issue = new DefaultIssue();
229       RuleKey ruleKey = RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportExternalIssue.getEngineId(), reportExternalIssue.getRuleId());
230       issue.setRuleKey(ruleKey);
231       ruleRepository.addOrUpdateAddHocRuleIfNeeded(ruleKey, () -> toAdHocRule(reportExternalIssue, adHocRuleMap.get(issue.ruleKey())));
232
233       Rule existingRule = ruleRepository.getByKey(ruleKey);
234       issue.setSeverity(determineDeprecatedSeverity(reportExternalIssue, existingRule));
235       issue.setType(determineDeprecatedType(reportExternalIssue, existingRule));
236       issue.replaceImpacts(convertImpacts(issue.ruleKey(), reportExternalIssue.getImpactsList()));
237
238       init(issue, issue.type() == RuleType.SECURITY_HOTSPOT ? STATUS_TO_REVIEW : STATUS_OPEN);
239
240       if (reportExternalIssue.hasTextRange()) {
241         int startLine = reportExternalIssue.getTextRange().getStartLine();
242         issue.setLine(startLine);
243         issue.setChecksum(lineHashSeq.getHashForLine(startLine));
244       } else {
245         issue.setChecksum("");
246       }
247       if (isNotEmpty(reportExternalIssue.getMsg())) {
248         issue.setMessage(reportExternalIssue.getMsg());
249         if (!reportExternalIssue.getMsgFormattingList().isEmpty()) {
250           issue.setMessageFormattings(convertMessageFormattings(reportExternalIssue.getMsgFormattingList()));
251         }
252       }
253       issue.setEffort(Duration.create(reportExternalIssue.getEffort() != 0 ? reportExternalIssue.getEffort() : DEFAULT_EXTERNAL_ISSUE_EFFORT));
254       DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder();
255       if (reportExternalIssue.hasTextRange()) {
256         dbLocationsBuilder.setTextRange(convertTextRange(reportExternalIssue.getTextRange()));
257       }
258       for (ScannerReport.Flow flow : reportExternalIssue.getFlowList()) {
259         if (flow.getLocationCount() > 0) {
260           DbIssues.Flow.Builder dbFlowBuilder = convertLocations(flow);
261           dbLocationsBuilder.addFlow(dbFlowBuilder);
262         }
263       }
264       issue.setIsFromExternalRuleEngine(true);
265       issue.setLocations(dbLocationsBuilder.build());
266
267       return issue;
268     }
269
270     private NewAdHocRule toAdHocRule(ScannerReport.ExternalIssue reportIssue, @Nullable ScannerReport.AdHocRule adHocRule) {
271       if (adHocRule != null) {
272         return new NewAdHocRule(adHocRule);
273       }
274       return new NewAdHocRule(reportIssue);
275     }
276
277     private Optional<DbIssues.FlowType> toFlowType(ScannerReport.FlowType flowType) {
278       switch (flowType) {
279         case DATA:
280           return Optional.of(DbIssues.FlowType.DATA);
281         case EXECUTION:
282           return Optional.of(DbIssues.FlowType.EXECUTION);
283         case UNDEFINED:
284           return Optional.empty();
285         default:
286           throw new IllegalArgumentException("Unrecognized type: " + flowType);
287       }
288     }
289
290     private RuleType toRuleType(IssueType type) {
291       switch (type) {
292         case BUG:
293           return RuleType.BUG;
294         case CODE_SMELL:
295           return RuleType.CODE_SMELL;
296         case VULNERABILITY:
297           return RuleType.VULNERABILITY;
298         case SECURITY_HOTSPOT:
299           return RuleType.SECURITY_HOTSPOT;
300         case UNRECOGNIZED:
301         default:
302           throw new IllegalStateException("Invalid issue type: " + type);
303       }
304     }
305
306     private DefaultIssue init(DefaultIssue issue, String initialStatus) {
307       issue.setStatus(initialStatus);
308       issue.setResolution(null);
309       issue.setComponentUuid(component.getUuid());
310       issue.setComponentKey(component.getKey());
311       issue.setProjectUuid(treeRootHolder.getRoot().getUuid());
312       issue.setProjectKey(treeRootHolder.getRoot().getKey());
313       return issue;
314     }
315
316     private Optional<DbIssues.Location> convertLocation(ScannerReport.IssueLocation source) {
317       DbIssues.Location.Builder target = DbIssues.Location.newBuilder();
318       if (source.getComponentRef() != 0 && source.getComponentRef() != component.getReportAttributes().getRef()) {
319         // SONAR-10781 Component might not exist because on PR, only changed components are included in the report
320         Optional<Component> optionalComponent = treeRootHolder.getOptionalComponentByRef(source.getComponentRef());
321         if (!optionalComponent.isPresent()) {
322           return Optional.empty();
323         }
324         target.setComponentId(optionalComponent.get().getUuid());
325       }
326       if (isNotEmpty(source.getMsg())) {
327         target.setMsg(source.getMsg());
328         source.getMsgFormattingList()
329           .forEach(m -> target.addMsgFormatting(convertMessageFormatting(m)));
330       }
331       if (source.hasTextRange()) {
332         ScannerReport.TextRange sourceRange = source.getTextRange();
333         DbCommons.TextRange.Builder targetRange = convertTextRange(sourceRange);
334         target.setTextRange(targetRange);
335       }
336       return Optional.of(target.build());
337     }
338
339     private DbCommons.TextRange.Builder convertTextRange(ScannerReport.TextRange sourceRange) {
340       DbCommons.TextRange.Builder targetRange = DbCommons.TextRange.newBuilder();
341       targetRange.setStartLine(sourceRange.getStartLine());
342       targetRange.setStartOffset(sourceRange.getStartOffset());
343       targetRange.setEndLine(sourceRange.getEndLine());
344       targetRange.setEndOffset(sourceRange.getEndOffset());
345       return targetRange;
346     }
347
348     private RuleType determineDeprecatedType(ScannerReport.ExternalIssue reportExternalIssue, Rule rule) {
349       if (reportExternalIssue.getType() != ScannerReport.IssueType.UNSET) {
350         return toRuleType(reportExternalIssue.getType());
351       } else if (rule.getType() != null) {
352         return rule.getType();
353       } else if (!rule.getDefaultImpacts().isEmpty()) {
354         SoftwareQuality impactSoftwareQuality = ImpactMapper.getBestImpactForBackmapping(rule.getDefaultImpacts()).getKey();
355         return ImpactMapper.convertToRuleType(impactSoftwareQuality);
356       } else {
357         throw new IllegalArgumentException("Cannot determine the type for issue of rule %s".formatted(reportExternalIssue.getRuleId()));
358       }
359     }
360
361     private static String determineDeprecatedSeverity(ScannerReport.ExternalIssue reportExternalIssue, Rule rule) {
362       if (reportExternalIssue.getSeverity() != Constants.Severity.UNSET_SEVERITY) {
363         return reportExternalIssue.getSeverity().name();
364       } else if (rule.getSeverity() != null) {
365         return rule.getSeverity();
366       } else if (!rule.getDefaultImpacts().isEmpty()) {
367         org.sonar.api.issue.impact.Severity impactSeverity = ImpactMapper.getBestImpactForBackmapping(rule.getDefaultImpacts()).getValue();
368         return ImpactMapper.convertToDeprecatedSeverity(impactSeverity);
369       } else {
370         throw new IllegalArgumentException("Cannot determine the severity for issue of rule %s".formatted(reportExternalIssue.getRuleId()));
371       }
372     }
373
374   }
375
376
377   private static DbIssues.MessageFormattings convertMessageFormattings(List<ScannerReport.MessageFormatting> msgFormattings) {
378     DbIssues.MessageFormattings.Builder builder = DbIssues.MessageFormattings.newBuilder();
379     msgFormattings.stream()
380       .forEach(m -> builder.addMessageFormatting(TrackerRawInputFactory.convertMessageFormatting(m)));
381     return builder.build();
382   }
383
384   @NotNull
385   private static DbIssues.MessageFormatting convertMessageFormatting(ScannerReport.MessageFormatting m) {
386     DbIssues.MessageFormatting.Builder msgFormattingBuilder = DbIssues.MessageFormatting.newBuilder();
387     return msgFormattingBuilder
388       .setStart(m.getStart())
389       .setEnd(m.getEnd())
390       .setType(DbIssues.MessageFormattingType.valueOf(m.getType().name())).build();
391   }
392
393 }