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.

DefaultSensorStorage.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 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.scanner.sensor;
  21. import java.io.Serializable;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.SortedMap;
  27. import java.util.TreeMap;
  28. import java.util.stream.Collectors;
  29. import javax.annotation.Nullable;
  30. import org.sonar.api.batch.fs.InputComponent;
  31. import org.sonar.api.batch.fs.InputDir;
  32. import org.sonar.api.batch.fs.InputFile;
  33. import org.sonar.api.batch.fs.TextRange;
  34. import org.sonar.api.batch.fs.internal.DefaultInputComponent;
  35. import org.sonar.api.batch.fs.internal.DefaultInputFile;
  36. import org.sonar.api.batch.fs.internal.DefaultInputModule;
  37. import org.sonar.api.batch.measure.Metric;
  38. import org.sonar.api.batch.measure.MetricFinder;
  39. import org.sonar.api.batch.sensor.code.NewSignificantCode;
  40. import org.sonar.api.batch.sensor.code.internal.DefaultSignificantCode;
  41. import org.sonar.api.batch.sensor.coverage.NewCoverage;
  42. import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
  43. import org.sonar.api.batch.sensor.cpd.NewCpdTokens;
  44. import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens;
  45. import org.sonar.api.batch.sensor.error.AnalysisError;
  46. import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
  47. import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
  48. import org.sonar.api.batch.sensor.internal.SensorStorage;
  49. import org.sonar.api.batch.sensor.issue.ExternalIssue;
  50. import org.sonar.api.batch.sensor.issue.Issue;
  51. import org.sonar.api.batch.sensor.measure.Measure;
  52. import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
  53. import org.sonar.api.batch.sensor.rule.AdHocRule;
  54. import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
  55. import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
  56. import org.sonar.api.config.Configuration;
  57. import org.sonar.api.measures.CoreMetrics;
  58. import org.sonar.api.utils.KeyValueFormat;
  59. import org.sonar.api.utils.log.Logger;
  60. import org.sonar.api.utils.log.Loggers;
  61. import org.sonar.core.metric.ScannerMetrics;
  62. import org.sonar.core.util.CloseableIterator;
  63. import org.sonar.duplications.block.Block;
  64. import org.sonar.duplications.internal.pmd.PmdBlockChunker;
  65. import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
  66. import org.sonar.scanner.issue.IssuePublisher;
  67. import org.sonar.scanner.protocol.Constants;
  68. import org.sonar.scanner.protocol.output.FileStructure;
  69. import org.sonar.scanner.protocol.output.ScannerReport;
  70. import org.sonar.scanner.protocol.output.ScannerReportWriter;
  71. import org.sonar.scanner.report.ReportPublisher;
  72. import org.sonar.scanner.report.ScannerReportUtils;
  73. import org.sonar.scanner.repository.ContextPropertiesCache;
  74. import org.sonar.scanner.scan.branch.BranchConfiguration;
  75. import static java.lang.Math.max;
  76. import static java.util.Arrays.asList;
  77. import static java.util.Collections.unmodifiableSet;
  78. import static java.util.stream.Collectors.toList;
  79. import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_DATA_KEY;
  80. import static org.sonar.api.measures.CoreMetrics.LINES_KEY;
  81. import static org.sonar.api.measures.CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY_KEY;
  82. import static org.sonar.api.measures.CoreMetrics.TEST_SUCCESS_DENSITY_KEY;
  83. public class DefaultSensorStorage implements SensorStorage {
  84. private static final Logger LOG = Loggers.get(DefaultSensorStorage.class);
  85. private static final int DEFAULT_CPD_MIN_LINES = 10;
  86. /**
  87. * The metrics that can be computed by analyzers but that are
  88. * filtered from analysis reports. That allows analyzers to continue
  89. * providing measures that are supported only by older versions.
  90. * <p>
  91. * The metrics in this list should not be declared in {@link ScannerMetrics#ALLOWED_CORE_METRICS}.
  92. */
  93. private static final Set<String> DEPRECATED_METRICS_KEYS = unmodifiableSet(new HashSet<>(asList(
  94. COMMENT_LINES_DATA_KEY)));
  95. /**
  96. * Metrics that were computed by analyzers and that are now computed
  97. * by core
  98. */
  99. private static final Set<String> NEWLY_CORE_METRICS_KEYS = unmodifiableSet(new HashSet<>(asList(
  100. // Computed on Scanner side
  101. LINES_KEY,
  102. // Computed on CE side
  103. TEST_SUCCESS_DENSITY_KEY,
  104. PUBLIC_DOCUMENTED_API_DENSITY_KEY)));
  105. private final MetricFinder metricFinder;
  106. private final IssuePublisher moduleIssues;
  107. private final ReportPublisher reportPublisher;
  108. private final SonarCpdBlockIndex index;
  109. private final ContextPropertiesCache contextPropertiesCache;
  110. private final Configuration settings;
  111. private final ScannerMetrics scannerMetrics;
  112. private final BranchConfiguration branchConfiguration;
  113. private final Set<String> alreadyLogged = new HashSet<>();
  114. public DefaultSensorStorage(MetricFinder metricFinder, IssuePublisher moduleIssues, Configuration settings,
  115. ReportPublisher reportPublisher, SonarCpdBlockIndex index,
  116. ContextPropertiesCache contextPropertiesCache, ScannerMetrics scannerMetrics, BranchConfiguration branchConfiguration) {
  117. this.metricFinder = metricFinder;
  118. this.moduleIssues = moduleIssues;
  119. this.settings = settings;
  120. this.reportPublisher = reportPublisher;
  121. this.index = index;
  122. this.contextPropertiesCache = contextPropertiesCache;
  123. this.scannerMetrics = scannerMetrics;
  124. this.branchConfiguration = branchConfiguration;
  125. }
  126. @Override
  127. public void store(Measure newMeasure) {
  128. saveMeasure(newMeasure.inputComponent(), (DefaultMeasure<?>) newMeasure);
  129. }
  130. private void logOnce(String metricKey, String msg, Object... params) {
  131. if (alreadyLogged.add(metricKey)) {
  132. LOG.warn(msg, params);
  133. }
  134. }
  135. private void saveMeasure(InputComponent component, DefaultMeasure<?> measure) {
  136. if (component.isFile()) {
  137. DefaultInputFile defaultInputFile = (DefaultInputFile) component;
  138. defaultInputFile.setPublished(true);
  139. }
  140. if (component instanceof InputDir || (component instanceof DefaultInputModule && ((DefaultInputModule) component).definition().getParent() != null)) {
  141. logOnce(measure.metric().key(), "Storing measures on folders or modules is deprecated. Provided value of metric '{}' is ignored.", measure.metric().key());
  142. return;
  143. }
  144. if (DEPRECATED_METRICS_KEYS.contains(measure.metric().key())) {
  145. logOnce(measure.metric().key(), "Metric '{}' is deprecated. Provided value is ignored.", measure.metric().key());
  146. return;
  147. }
  148. Metric metric = metricFinder.findByKey(measure.metric().key());
  149. if (metric == null) {
  150. throw new UnsupportedOperationException("Unknown metric: " + measure.metric().key());
  151. }
  152. if (!measure.isFromCore() && NEWLY_CORE_METRICS_KEYS.contains(measure.metric().key())) {
  153. logOnce(measure.metric().key(), "Metric '{}' is an internal metric computed by SonarQube/SonarCloud. Provided value is ignored.", measure.metric().key());
  154. return;
  155. }
  156. if (!scannerMetrics.getMetrics().contains(metric)) {
  157. throw new UnsupportedOperationException("Metric '" + metric.key() + "' should not be computed by a Sensor");
  158. }
  159. if (((DefaultInputComponent) component).hasMeasureFor(metric)) {
  160. throw new UnsupportedOperationException("Can not add the same measure twice on " + component + ": " + measure);
  161. }
  162. ((DefaultInputComponent) component).setHasMeasureFor(metric);
  163. if (metric.key().equals(CoreMetrics.EXECUTABLE_LINES_DATA_KEY)) {
  164. if (component.isFile()) {
  165. ((DefaultInputFile) component).setExecutableLines(
  166. KeyValueFormat.parseIntInt((String) measure.value()).entrySet().stream().filter(e -> e.getValue() > 0).map(Map.Entry::getKey).collect(Collectors.toSet()));
  167. } else {
  168. throw new IllegalArgumentException("Executable lines can only be saved on files");
  169. }
  170. }
  171. reportPublisher.getWriter().appendComponentMeasure(((DefaultInputComponent) component).scannerId(), toReportMeasure(measure));
  172. }
  173. public static ScannerReport.Measure toReportMeasure(DefaultMeasure measureToSave) {
  174. ScannerReport.Measure.Builder builder = ScannerReport.Measure.newBuilder();
  175. builder.setMetricKey(measureToSave.metric().key());
  176. setValueAccordingToType(builder, measureToSave);
  177. return builder.build();
  178. }
  179. private static void setValueAccordingToType(ScannerReport.Measure.Builder builder, DefaultMeasure<?> measure) {
  180. Serializable value = measure.value();
  181. Metric<?> metric = measure.metric();
  182. if (Boolean.class.equals(metric.valueType())) {
  183. builder.setBooleanValue(ScannerReport.Measure.BoolValue.newBuilder().setValue((Boolean) value));
  184. } else if (Integer.class.equals(metric.valueType())) {
  185. builder.setIntValue(ScannerReport.Measure.IntValue.newBuilder().setValue(((Number) value).intValue()));
  186. } else if (Double.class.equals(metric.valueType())) {
  187. builder.setDoubleValue(ScannerReport.Measure.DoubleValue.newBuilder().setValue(((Number) value).doubleValue()));
  188. } else if (String.class.equals(metric.valueType())) {
  189. builder.setStringValue(ScannerReport.Measure.StringValue.newBuilder().setValue((String) value));
  190. } else if (Long.class.equals(metric.valueType())) {
  191. builder.setLongValue(ScannerReport.Measure.LongValue.newBuilder().setValue(((Number) value).longValue()));
  192. } else {
  193. throw new UnsupportedOperationException("Unsupported type :" + metric.valueType());
  194. }
  195. }
  196. private boolean shouldSkipStorage(DefaultInputFile defaultInputFile) {
  197. return branchConfiguration.isPullRequest() && defaultInputFile.status() == InputFile.Status.SAME;
  198. }
  199. /**
  200. * Thread safe assuming that each issues for each file are only written once.
  201. */
  202. @Override
  203. public void store(Issue issue) {
  204. if (issue.primaryLocation().inputComponent() instanceof DefaultInputFile) {
  205. DefaultInputFile defaultInputFile = (DefaultInputFile) issue.primaryLocation().inputComponent();
  206. if (shouldSkipStorage(defaultInputFile)) {
  207. return;
  208. }
  209. defaultInputFile.setPublished(true);
  210. }
  211. moduleIssues.initAndAddIssue(issue);
  212. }
  213. /**
  214. * Thread safe assuming that each issues for each file are only written once.
  215. */
  216. @Override
  217. public void store(ExternalIssue externalIssue) {
  218. if (externalIssue.primaryLocation().inputComponent() instanceof DefaultInputFile) {
  219. DefaultInputFile defaultInputFile = (DefaultInputFile) externalIssue.primaryLocation().inputComponent();
  220. defaultInputFile.setPublished(true);
  221. }
  222. moduleIssues.initAndAddExternalIssue(externalIssue);
  223. }
  224. @Override
  225. public void store(AdHocRule adHocRule) {
  226. ScannerReportWriter writer = reportPublisher.getWriter();
  227. final ScannerReport.AdHocRule.Builder builder = ScannerReport.AdHocRule.newBuilder();
  228. builder.setEngineId(adHocRule.engineId());
  229. builder.setRuleId(adHocRule.ruleId());
  230. builder.setName(adHocRule.name());
  231. String description = adHocRule.description();
  232. if (description != null) {
  233. builder.setDescription(description);
  234. }
  235. builder.setSeverity(Constants.Severity.valueOf(adHocRule.severity().name()));
  236. builder.setType(ScannerReport.IssueType.valueOf(adHocRule.type().name()));
  237. writer.appendAdHocRule(builder.build());
  238. }
  239. @Override
  240. public void store(NewHighlighting newHighlighting) {
  241. DefaultHighlighting highlighting = (DefaultHighlighting) newHighlighting;
  242. ScannerReportWriter writer = reportPublisher.getWriter();
  243. DefaultInputFile inputFile = (DefaultInputFile) highlighting.inputFile();
  244. if (shouldSkipStorage(inputFile)) {
  245. return;
  246. }
  247. inputFile.setPublished(true);
  248. int componentRef = inputFile.scannerId();
  249. if (writer.hasComponentData(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, componentRef)) {
  250. throw new UnsupportedOperationException("Trying to save highlighting twice for the same file is not supported: " + inputFile);
  251. }
  252. final ScannerReport.SyntaxHighlightingRule.Builder builder = ScannerReport.SyntaxHighlightingRule.newBuilder();
  253. final ScannerReport.TextRange.Builder rangeBuilder = ScannerReport.TextRange.newBuilder();
  254. writer.writeComponentSyntaxHighlighting(componentRef,
  255. highlighting.getSyntaxHighlightingRuleSet().stream()
  256. .map(input -> {
  257. builder.setRange(rangeBuilder.setStartLine(input.range().start().line())
  258. .setStartOffset(input.range().start().lineOffset())
  259. .setEndLine(input.range().end().line())
  260. .setEndOffset(input.range().end().lineOffset())
  261. .build());
  262. builder.setType(ScannerReportUtils.toProtocolType(input.getTextType()));
  263. return builder.build();
  264. }).collect(toList()));
  265. }
  266. @Override
  267. public void store(NewSymbolTable newSymbolTable) {
  268. DefaultSymbolTable symbolTable = (DefaultSymbolTable) newSymbolTable;
  269. ScannerReportWriter writer = reportPublisher.getWriter();
  270. DefaultInputFile inputFile = (DefaultInputFile) symbolTable.inputFile();
  271. if (shouldSkipStorage(inputFile)) {
  272. return;
  273. }
  274. inputFile.setPublished(true);
  275. int componentRef = inputFile.scannerId();
  276. if (writer.hasComponentData(FileStructure.Domain.SYMBOLS, componentRef)) {
  277. throw new UnsupportedOperationException("Trying to save symbol table twice for the same file is not supported: " + symbolTable.inputFile());
  278. }
  279. final ScannerReport.Symbol.Builder builder = ScannerReport.Symbol.newBuilder();
  280. final ScannerReport.TextRange.Builder rangeBuilder = ScannerReport.TextRange.newBuilder();
  281. writer.writeComponentSymbols(componentRef,
  282. symbolTable.getReferencesBySymbol().entrySet().stream()
  283. .map(input -> {
  284. builder.clear();
  285. rangeBuilder.clear();
  286. TextRange declaration = input.getKey();
  287. builder.setDeclaration(rangeBuilder.setStartLine(declaration.start().line())
  288. .setStartOffset(declaration.start().lineOffset())
  289. .setEndLine(declaration.end().line())
  290. .setEndOffset(declaration.end().lineOffset())
  291. .build());
  292. for (TextRange reference : input.getValue()) {
  293. builder.addReference(rangeBuilder.setStartLine(reference.start().line())
  294. .setStartOffset(reference.start().lineOffset())
  295. .setEndLine(reference.end().line())
  296. .setEndOffset(reference.end().lineOffset())
  297. .build());
  298. }
  299. return builder.build();
  300. }).collect(Collectors.toList()));
  301. }
  302. @Override
  303. public void store(NewCoverage coverage) {
  304. DefaultCoverage defaultCoverage = (DefaultCoverage) coverage;
  305. DefaultInputFile inputFile = (DefaultInputFile) defaultCoverage.inputFile();
  306. inputFile.setPublished(true);
  307. SortedMap<Integer, ScannerReport.LineCoverage.Builder> coveragePerLine = reloadExistingCoverage(inputFile);
  308. int lineCount = inputFile.lines();
  309. mergeLineCoverageValues(lineCount, defaultCoverage.hitsByLine(), coveragePerLine, (value, builder) -> builder.setHits(builder.getHits() || value > 0));
  310. mergeLineCoverageValues(lineCount, defaultCoverage.conditionsByLine(), coveragePerLine, (value, builder) -> builder.setConditions(max(value, builder.getConditions())));
  311. mergeLineCoverageValues(lineCount, defaultCoverage.coveredConditionsByLine(), coveragePerLine,
  312. (value, builder) -> builder.setCoveredConditions(max(value, builder.getCoveredConditions())));
  313. reportPublisher.getWriter().writeComponentCoverage(inputFile.scannerId(),
  314. coveragePerLine.values().stream().map(ScannerReport.LineCoverage.Builder::build).collect(Collectors.toList()));
  315. }
  316. private SortedMap<Integer, ScannerReport.LineCoverage.Builder> reloadExistingCoverage(DefaultInputFile inputFile) {
  317. SortedMap<Integer, ScannerReport.LineCoverage.Builder> coveragePerLine = new TreeMap<>();
  318. try (CloseableIterator<ScannerReport.LineCoverage> lineCoverageCloseableIterator = reportPublisher.getReader().readComponentCoverage(inputFile.scannerId())) {
  319. while (lineCoverageCloseableIterator.hasNext()) {
  320. final ScannerReport.LineCoverage lineCoverage = lineCoverageCloseableIterator.next();
  321. coveragePerLine.put(lineCoverage.getLine(), ScannerReport.LineCoverage.newBuilder(lineCoverage));
  322. }
  323. }
  324. return coveragePerLine;
  325. }
  326. interface LineCoverageOperation {
  327. void apply(Integer value, ScannerReport.LineCoverage.Builder builder);
  328. }
  329. private static void mergeLineCoverageValues(int lineCount, SortedMap<Integer, Integer> valueByLine, SortedMap<Integer, ScannerReport.LineCoverage.Builder> coveragePerLine,
  330. LineCoverageOperation op) {
  331. for (Map.Entry<Integer, Integer> lineMeasure : valueByLine.entrySet()) {
  332. int lineIdx = lineMeasure.getKey();
  333. if (lineIdx <= lineCount) {
  334. Integer value = lineMeasure.getValue();
  335. op.apply(value, coveragePerLine.computeIfAbsent(lineIdx, line -> ScannerReport.LineCoverage.newBuilder().setLine(line)));
  336. }
  337. }
  338. }
  339. @Override
  340. public void store(NewCpdTokens cpdTokens) {
  341. DefaultCpdTokens defaultCpdTokens = (DefaultCpdTokens) cpdTokens;
  342. DefaultInputFile inputFile = (DefaultInputFile) defaultCpdTokens.inputFile();
  343. inputFile.setPublished(true);
  344. PmdBlockChunker blockChunker = new PmdBlockChunker(getCpdBlockSize(inputFile.language()));
  345. List<Block> blocks = blockChunker.chunk(inputFile.key(), defaultCpdTokens.getTokenLines());
  346. index.insert(inputFile, blocks);
  347. }
  348. private int getCpdBlockSize(@Nullable String languageKey) {
  349. if (languageKey == null) {
  350. return DEFAULT_CPD_MIN_LINES;
  351. }
  352. return settings.getInt("sonar.cpd." + languageKey + ".minimumLines")
  353. .orElseGet(() -> {
  354. if ("cobol".equals(languageKey)) {
  355. return 30;
  356. }
  357. if ("abap".equals(languageKey)) {
  358. return 20;
  359. }
  360. return DEFAULT_CPD_MIN_LINES;
  361. });
  362. }
  363. @Override
  364. public void store(AnalysisError analysisError) {
  365. DefaultInputFile defaultInputFile = (DefaultInputFile) analysisError.inputFile();
  366. if (shouldSkipStorage(defaultInputFile)) {
  367. return;
  368. }
  369. defaultInputFile.setPublished(true);
  370. }
  371. @Override
  372. public void storeProperty(String key, String value) {
  373. contextPropertiesCache.put(key, value);
  374. }
  375. @Override
  376. public void store(NewSignificantCode newSignificantCode) {
  377. DefaultSignificantCode significantCode = (DefaultSignificantCode) newSignificantCode;
  378. ScannerReportWriter writer = reportPublisher.getWriter();
  379. DefaultInputFile inputFile = (DefaultInputFile) significantCode.inputFile();
  380. if (shouldSkipStorage(inputFile)) {
  381. return;
  382. }
  383. inputFile.setPublished(true);
  384. int componentRef = inputFile.scannerId();
  385. if (writer.hasComponentData(FileStructure.Domain.SGNIFICANT_CODE, componentRef)) {
  386. throw new UnsupportedOperationException(
  387. "Trying to save significant code information twice for the same file is not supported: " + significantCode.inputFile());
  388. }
  389. List<ScannerReport.LineSgnificantCode> protobuf = significantCode.significantCodePerLine().values().stream()
  390. .map(range -> ScannerReport.LineSgnificantCode.newBuilder()
  391. .setLine(range.start().line())
  392. .setStartOffset(range.start().lineOffset())
  393. .setEndOffset(range.end().lineOffset())
  394. .build())
  395. .collect(Collectors.toList());
  396. writer.writeComponentSignificantCode(componentRef, protobuf);
  397. }
  398. }