Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Add SARIF parser #9609

Merged
merged 21 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ plugins {
id "com.gorylenko.gradle-git-properties" version "2.4.2"
id "org.owasp.dependencycheck" version "11.1.0"
id "com.adarshr.test-logger" version "4.0.0"
id "org.jsonschema2pojo" version "1.2.2"
}

group = "de.tum.cit.aet.artemis"
Expand Down Expand Up @@ -154,6 +155,7 @@ private excludedClassFilesForReport(classDirectories) {
exclude: [
"**/de/tum/cit/aet/artemis/**/domain/**/*_*",
"**/de/tum/cit/aet/artemis/core/config/migration/entries/**",
"**/de/tum/cit/aet/artemis/programming/service/localci/scaparser/format/sarif/**",
"**/gradle-wrapper.jar/**"
]
)
Expand Down Expand Up @@ -194,6 +196,12 @@ jacocoTestCoverageVerification {
}
check.dependsOn jacocoTestCoverageVerification

jsonSchema2Pojo {
targetDirectory = file(layout.buildDirectory.dir("generated/sources/js2p"))
useOptionalForGetters = true
inclusionLevel = "NON_EMPTY"
}
magaupp marked this conversation as resolved.
Show resolved Hide resolved

configurations {
providedRuntime
}
Expand Down Expand Up @@ -644,6 +652,14 @@ checkstyle {
maxErrors = 0
}

tasks.withType(Checkstyle).configureEach {
// Exclude generated sources
exclude {
it.file.toPath().startsWith(jsonSchema2Pojo.targetDirectory.toPath())
}
}


def isNonStable = { String version ->
def stableKeyword = ["RELEASE", "FINAL", "GA"].any { it -> version.toUpperCase().contains(it) }
def regex = /^[0-9,.v-]+(-r)?$/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,18 +378,18 @@ private BuildResult parseTestResults(TarArchiveInputStream testResultsTarInputSt
}

// Read the contents of the tar entry as a string.
String xmlString = readTarEntryContent(testResultsTarInputStream);
String fileString = readTarEntryContent(testResultsTarInputStream);
// Get the file name of the tar entry.
String fileName = getFileName(tarEntry);

try {
// Check if the file is a static code analysis report file
if (StaticCodeAnalysisTool.getToolByFilePattern(fileName).isPresent()) {
processStaticCodeAnalysisReportFile(fileName, xmlString, staticCodeAnalysisReports, buildJobId);
processStaticCodeAnalysisReportFile(fileName, fileString, staticCodeAnalysisReports, buildJobId);
}
else {
// ugly workaround because in swift result files \n\t breaks the parsing
var testResultFileString = xmlString.replace("\n\t", "");
var testResultFileString = fileString.replace("\n\t", "");
if (!testResultFileString.isBlank()) {
processTestResultFile(testResultFileString, failedTests, successfulTests);
}
Expand Down Expand Up @@ -418,7 +418,7 @@ private boolean isValidTestResultFile(TarArchiveEntry tarArchiveEntry) {
String result = (lastIndexOfSlash != -1 && lastIndexOfSlash + 1 < name.length()) ? name.substring(lastIndexOfSlash + 1) : name;

// Java test result files are named "TEST-*.xml", Python test result files are named "*results.xml".
return !tarArchiveEntry.isDirectory() && result.endsWith(".xml") && !result.equals("pom.xml");
return !tarArchiveEntry.isDirectory() && (result.endsWith(".xml") && !result.equals("pom.xml") || result.endsWith(".sarif"));
magaupp marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -444,12 +444,12 @@ private String getFileName(TarArchiveEntry tarEntry) {
* Processes a static code analysis report file and adds the report to the corresponding list.
*
* @param fileName the file name of the static code analysis report file
* @param xmlString the content of the static code analysis report file
* @param reportContent the content of the static code analysis report file
* @param staticCodeAnalysisReports the list of static code analysis reports
*/
private void processStaticCodeAnalysisReportFile(String fileName, String xmlString, List<StaticCodeAnalysisReportDTO> staticCodeAnalysisReports, String buildJobId) {
private void processStaticCodeAnalysisReportFile(String fileName, String reportContent, List<StaticCodeAnalysisReportDTO> staticCodeAnalysisReports, String buildJobId) {
try {
staticCodeAnalysisReports.add(ReportParser.getReport(xmlString, fileName));
staticCodeAnalysisReports.add(ReportParser.getReport(reportContent, fileName));
}
catch (UnsupportedToolException e) {
String msg = "Failed to parse static code analysis report for " + fileName;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package de.tum.cit.aet.artemis.programming.domain;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

Expand All @@ -10,28 +11,29 @@
*/
public enum StaticCodeAnalysisTool {

SPOTBUGS(ProgrammingLanguage.JAVA, "spotbugs:spotbugs", "spotbugsXml.xml"), CHECKSTYLE(ProgrammingLanguage.JAVA, "checkstyle:checkstyle", "checkstyle-result.xml"),
PMD(ProgrammingLanguage.JAVA, "pmd:pmd", "pmd.xml"), PMD_CPD(ProgrammingLanguage.JAVA, "pmd:cpd", "cpd.xml"), SWIFTLINT(ProgrammingLanguage.SWIFT, "", "swiftlint-result.xml"),
GCC(ProgrammingLanguage.C, "", "gcc.xml");
// @formatter:off
SPOTBUGS("spotbugsXml.xml"),
CHECKSTYLE("checkstyle-result.xml"),
PMD("pmd.xml"),
PMD_CPD("cpd.xml"),
SWIFTLINT("swiftlint-result.xml"),
GCC("gcc.xml"),
OTHER(null),
;
// @formatter:on

private final ProgrammingLanguage language;
// @formatter:off
private static final Map<ProgrammingLanguage, List<StaticCodeAnalysisTool>> TOOLS_OF_PROGRAMMING_LANGUAGE = new EnumMap<>(Map.of(
ProgrammingLanguage.JAVA, List.of(SPOTBUGS, CHECKSTYLE, PMD, PMD_CPD),
ProgrammingLanguage.SWIFT, List.of(SWIFTLINT),
ProgrammingLanguage.C, List.of(GCC)
));
// @formatter:on

private final String command;
private final String fileName;

private final String filePattern;

StaticCodeAnalysisTool(ProgrammingLanguage language, String command, String filePattern) {
this.language = language;
this.command = command;
this.filePattern = filePattern;
}

public String getTask() {
return this.command;
}

public String getFilePattern() {
return this.filePattern;
StaticCodeAnalysisTool(String fileName) {
this.fileName = fileName;
magaupp marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand All @@ -41,13 +43,7 @@ public String getFilePattern() {
* @return List of static code analysis
*/
public static List<StaticCodeAnalysisTool> getToolsForProgrammingLanguage(ProgrammingLanguage language) {
List<StaticCodeAnalysisTool> tools = new ArrayList<>();
for (var tool : StaticCodeAnalysisTool.values()) {
if (tool.language == language) {
tools.add(tool);
}
}
return tools;
return TOOLS_OF_PROGRAMMING_LANGUAGE.getOrDefault(language, List.of());
}

/**
Expand All @@ -58,7 +54,7 @@ public static List<StaticCodeAnalysisTool> getToolsForProgrammingLanguage(Progra
*/
public static Optional<StaticCodeAnalysisTool> getToolByFilePattern(String fileName) {
for (StaticCodeAnalysisTool tool : StaticCodeAnalysisTool.values()) {
if (Objects.equals(fileName, tool.filePattern)) {
if (Objects.equals(fileName, tool.fileName)) {
return Optional.of(tool);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,94 +1,27 @@
package de.tum.cit.aet.artemis.programming.service.localci.scaparser;

import static de.tum.cit.aet.artemis.programming.service.localci.scaparser.utils.ReportUtils.createErrorReport;
import static de.tum.cit.aet.artemis.programming.service.localci.scaparser.utils.ReportUtils.createFileTooLargeReport;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import com.fasterxml.jackson.databind.ObjectMapper;

import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO;
import de.tum.cit.aet.artemis.programming.service.localci.scaparser.exception.ParserException;
import de.tum.cit.aet.artemis.programming.service.localci.scaparser.exception.UnsupportedToolException;
import de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.ParserPolicy;
import de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.ParserStrategy;
import de.tum.cit.aet.artemis.programming.service.localci.scaparser.utils.FileUtils;

/**
* Public API for parsing of static code analysis reports
*/
public class ReportParser {

private final ObjectMapper mapper = new ObjectMapper();

// Reports that are bigger then the threshold will not be parsed
// and an issue will be generated. The unit is in megabytes.
private static final int STATIC_CODE_ANALYSIS_REPORT_FILESIZE_LIMIT_IN_MB = 1;

/**
* Transform a given static code analysis report into a JSON representation.
* All supported tools share the same JSON format.
*
* @param file Reference to the static code analysis report
* @return Static code analysis report represented as a JSON String
* @throws ParserException - If an exception occurs that is not already handled by the parser itself, e.g. caused by the json-parsing
*/
public String transformToJSONReport(File file) throws ParserException {
try {
StaticCodeAnalysisReportDTO report = transformToReport(file);
return mapper.writeValueAsString(report);
}
catch (Exception e) {
throw new ParserException(e.getMessage(), e);
}
}

/**
* Transform a given static code analysis report given as a file into a plain Java object.
*
* @param file Reference to the static code analysis report
* @return Static code analysis report represented as a plain Java object
*/
public StaticCodeAnalysisReportDTO transformToReport(File file) {
if (file == null) {
throw new IllegalArgumentException("File must not be null");
}

// The static code analysis parser only supports xml files.
if (!FileUtils.getExtension(file).equals("xml")) {
throw new IllegalArgumentException("File must be xml format");
}
try {
// Reject any file larger than the given threshold
if (FileUtils.isFilesizeGreaterThan(file, STATIC_CODE_ANALYSIS_REPORT_FILESIZE_LIMIT_IN_MB)) {
return createFileTooLargeReport(file.getName());
}

return getReport(file);
}
catch (Exception e) {
return createErrorReport(file.getName(), e);
}
}
private static final ParserPolicy parserPolicy = new ParserPolicy();

/**
* Builds the document using the provided file and parses it to a Report object using ObjectMapper.
* Builds the document using the provided string and parses it to a Report object.
*
* @param file File referencing the static code analysis report
* @param reportContent String containing the static code analysis report
* @param fileName filename of the report used for configuring a parser
* @return Report containing the static code analysis issues
* @throws UnsupportedToolException if the static code analysis tool which created the report is not supported
* @throws IOException if the file could not be read
*/
public static StaticCodeAnalysisReportDTO getReport(File file) throws IOException {
String xmlContent = Files.readString(file.toPath());
return getReport(xmlContent, file.getName());
}

public static StaticCodeAnalysisReportDTO getReport(String xmlContent, String fileName) {
ParserPolicy parserPolicy = new ParserPolicy();
public static StaticCodeAnalysisReportDTO getReport(String reportContent, String fileName) {
magaupp marked this conversation as resolved.
Show resolved Hide resolved
ParserStrategy parserStrategy = parserPolicy.configure(fileName);
return parserStrategy.parse(xmlContent);
return parserStrategy.parse(reportContent);
magaupp marked this conversation as resolved.
Show resolved Hide resolved
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public class CheckstyleParser implements ParserStrategy {
private final XmlMapper xmlMapper = new XmlMapper();

@Override
public StaticCodeAnalysisReportDTO parse(String xmlContent) {
public StaticCodeAnalysisReportDTO parse(String reportContent) {
try {
List<CheckstyleFile> files = xmlMapper.readValue(xmlContent, new com.fasterxml.jackson.core.type.TypeReference<List<CheckstyleFile>>() {
List<CheckstyleFile> files = xmlMapper.readValue(reportContent, new com.fasterxml.jackson.core.type.TypeReference<List<CheckstyleFile>>() {
});
return createReportFromFiles(files);
}
magaupp marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ class PMDCPDParser implements ParserStrategy {
private final XmlMapper xmlMapper = new XmlMapper();

@Override
public StaticCodeAnalysisReportDTO parse(String xmlContent) {
public StaticCodeAnalysisReportDTO parse(String reportContent) {
try {
PmdCpc duplication = xmlMapper.readValue(xmlContent, PmdCpc.class);
PmdCpc duplication = xmlMapper.readValue(reportContent, PmdCpc.class);
magaupp marked this conversation as resolved.
Show resolved Hide resolved
return createReportFromDuplication(duplication);
}
catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ class PMDParser implements ParserStrategy {
private final XmlMapper xmlMapper = new XmlMapper();

@Override
magaupp marked this conversation as resolved.
Show resolved Hide resolved
public StaticCodeAnalysisReportDTO parse(String xmlContent) {
public StaticCodeAnalysisReportDTO parse(String reportContent) {
try {
PMDReport pmdReport = xmlMapper.readValue(xmlContent, PMDReport.class);
PMDReport pmdReport = xmlMapper.readValue(reportContent, PMDReport.class);
return createReportFromPMDReport(pmdReport);
magaupp marked this conversation as resolved.
Show resolved Hide resolved
}
catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ static String transformToUnixPath(String path) {
}

/**
* Parse a static code analysis report from an XML string into a common Java representation.
* Parse a static code analysis report from a serialized string into a common Java representation.
*
* @param xmlContent The XML content as a String
* @param reportContent The serialized content as a String
* @return Report object containing the parsed report information
*/
StaticCodeAnalysisReportDTO parse(String xmlContent);
StaticCodeAnalysisReportDTO parse(String reportContent);
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ class SpotbugsParser implements ParserStrategy {
private final XmlMapper xmlMapper = new XmlMapper();

@Override
public StaticCodeAnalysisReportDTO parse(String xmlContent) {
public StaticCodeAnalysisReportDTO parse(String reportContent) {
try {
BugCollection bugCollection = xmlMapper.readValue(xmlContent, BugCollection.class);
BugCollection bugCollection = xmlMapper.readValue(reportContent, BugCollection.class);
return createReportFromBugCollection(bugCollection);
}
catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif;

import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ReportingDescriptor;

class IdCategorizer implements RuleCategorizer {

@Override
public String categorizeRule(ReportingDescriptor rule) {
return rule.getId();
}
magaupp marked this conversation as resolved.
Show resolved Hide resolved
}
magaupp marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.tum.cit.aet.artemis.programming.service.localci.scaparser.strategy.sarif;

import de.tum.cit.aet.artemis.programming.service.localci.scaparser.format.sarif.ReportingDescriptor;

public interface RuleCategorizer {

/**
* Categorizes a SARIF rule using a tool specific strategy.
*
* @param rule The reporting descriptor containing the rule details
* @return The identifier of the resulting category
*/
String categorizeRule(ReportingDescriptor rule);
}
Loading
Loading