diff --git a/SUPPORTED-FORMATS.md b/SUPPORTED-FORMATS.md
index a50d19731..7ddd33947 100644
--- a/SUPPORTED-FORMATS.md
+++ b/SUPPORTED-FORMATS.md
@@ -2089,6 +2089,25 @@ analyze - iccxxxxcompiler_opts cstat2.cFor details check the IAR C-
-
+
veracode-pipeline-scanner
diff --git a/etc/Jenkinsfile.valgrind b/etc/Jenkinsfile.valgrind
new file mode 100644
index 000000000..d23110205
--- /dev/null
+++ b/etc/Jenkinsfile.valgrind
@@ -0,0 +1,9 @@
+node('java11-agent') {
+ stage ('Checkout') {
+ checkout scm
+ }
+
+ stage ('Build, Test, and Static Analysis') {
+ recordIssues tool: analysisParser(analysisModelId: 'valgrind', pattern: '**/valgrind.xml')
+ }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java b/src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java
new file mode 100644
index 000000000..a9c7c789d
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java
@@ -0,0 +1,193 @@
+package edu.hm.hafner.analysis.parser.violations;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import edu.hm.hafner.analysis.IssueBuilder;
+import edu.hm.hafner.analysis.Report;
+
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+
+import j2html.tags.ContainerTag;
+import static j2html.TagCreator.*;
+
+import se.bjurr.violations.lib.model.Violation;
+import se.bjurr.violations.lib.parsers.ValgrindParser;
+
+/**
+ * Parses Valgrind XML report files.
+ *
+ * @author Tony Ciavarella
+ */
+public class ValgrindAdapter extends AbstractViolationAdapter {
+ private static final long serialVersionUID = -6117336551972081612L;
+ private static final int NUMBERED_STACK_THRESHOLD = 2;
+ private static final int NO_LINE = -1;
+
+ @Override
+ ValgrindParser createParser() {
+ return new ValgrindParser();
+ }
+
+ @Override
+ Report convertToReport(final Set violations) {
+ try (IssueBuilder issueBuilder = new IssueBuilder()) {
+ Report report = new Report();
+
+ for (Violation violation: violations) {
+ updateIssueBuilder(violation, issueBuilder);
+ issueBuilder.setCategory("valgrind:" + violation.getReporter());
+ issueBuilder.setDescription(generateDescriptionHtml(violation));
+ report.add(issueBuilder.buildAndClean());
+ }
+
+ return report;
+ }
+ }
+
+ private String generateDescriptionHtml(final Violation violation) {
+ final Map specifics = violation.getSpecifics();
+ final JSONArray auxWhats = getAuxWhatsArray(specifics);
+
+ return
+ j2html.tags.DomContentJoiner.join(
+ "",
+ false,
+ generateGeneralTableHtml(violation.getSource(), violation.getGroup(), specifics.get("tid"), specifics.get("threadname"), auxWhats),
+ maybeGenerateStackTracesHtml(specifics.get("stacks"), violation.getMessage(), auxWhats),
+ maybeGenerateSuppressionHtml(specifics.get("suppression"))
+ ).render();
+ }
+
+ private ContainerTag generateGeneralTableHtml(final String executable, final String uniqueId, @CheckForNull final String threadId, @CheckForNull final String threadName, @CheckForNull final JSONArray auxWhats) {
+ ContainerTag generalTable =
+ table(
+ attrs(".table.table-striped"),
+ maybeGenerateTableRowHtml("Executable", executable),
+ maybeGenerateTableRowHtml("Unique Id", uniqueId),
+ maybeGenerateTableRowHtml("Thread Id", threadId),
+ maybeGenerateTableRowHtml("Thread Name", threadName)
+ );
+
+ if (auxWhats != null && !auxWhats.isEmpty()) {
+ for (int auxwhatIndex = 0; auxwhatIndex < auxWhats.length(); ++auxwhatIndex) {
+ generalTable.with(maybeGenerateTableRowHtml("Auxiliary", auxWhats.getString(auxwhatIndex)));
+ }
+ }
+
+ return generalTable;
+ }
+
+ private @CheckForNull ContainerTag maybeGenerateStackTracesHtml(@CheckForNull final String stacksJson, final String message, @CheckForNull final JSONArray auxWhats) {
+ if (StringUtils.isBlank(stacksJson)) {
+ return null;
+ }
+
+ final JSONArray stacks = new JSONArray(new JSONTokener(stacksJson));
+
+ if (!stacks.isEmpty()) {
+ ContainerTag stackTraces = div();
+
+ stackTraces.with(generateStackTraceHtml("Primary Stack Trace", message, stacks.getJSONArray(0)));
+
+ for (int stackIndex = 1; stackIndex < stacks.length(); ++stackIndex) {
+ String msg = null;
+
+ if (auxWhats != null && auxWhats.length() >= stackIndex) {
+ msg = auxWhats.getString(stackIndex - 1);
+ }
+
+ String title = "Auxiliary Stack Trace";
+
+ if (stacks.length() > NUMBERED_STACK_THRESHOLD) {
+ title += " #" + stackIndex;
+ }
+
+ stackTraces.with(generateStackTraceHtml(title, msg, stacks.getJSONArray(stackIndex)));
+ }
+
+ return stackTraces;
+ }
+
+ return null;
+ }
+
+ private ContainerTag generateStackTraceHtml(final String title, @CheckForNull final String message, final JSONArray frames) {
+ ContainerTag stackTraceContainer =
+ div(
+ br(),
+ h4(title),
+ iff(StringUtils.isNotBlank(message), p(message))
+ );
+
+ for (int frameIndex = 0; frameIndex < frames.length(); ++frameIndex) {
+ final JSONObject frame = frames.getJSONObject(frameIndex);
+
+ if (frameIndex > 0) {
+ stackTraceContainer.with(br());
+ }
+
+ stackTraceContainer.with(generateStackFrameHtml(frame));
+ }
+
+ return stackTraceContainer;
+ }
+
+ private ContainerTag generateStackFrameHtml(final JSONObject frame) {
+ return
+ table(
+ maybeGenerateTableRowHtml("Object", frame.optString("obj")),
+ maybeGenerateTableRowHtml("Function", frame.optString("fn")),
+ maybeGenerateStackFrameFileTableRowHtml(frame)
+ );
+ }
+
+ private ContainerTag maybeGenerateSuppressionHtml(@CheckForNull final String suppression) {
+ return
+ iff(
+ StringUtils.isNotBlank(suppression),
+ div(br(), h4("Suppression"), table(tr(td(pre(suppression)))))
+ );
+ }
+
+ private ContainerTag maybeGenerateTableRowHtml(final String name, @CheckForNull final String value) {
+ return iff(StringUtils.isNotBlank(value), tr(td(text(name), td(text(value)))));
+ }
+
+ private @CheckForNull ContainerTag maybeGenerateStackFrameFileTableRowHtml(final JSONObject frame) throws JSONException {
+ final String file = frame.optString("file");
+
+ if (StringUtils.isNotBlank(file)) {
+ final String dir = frame.optString("dir");
+ final int line = frame.optInt("line", NO_LINE);
+ final StringBuilder fileBuilder = new StringBuilder(256);
+
+ if (StringUtils.isNotBlank(dir)) {
+ fileBuilder.append(dir).append('/');
+ }
+
+ fileBuilder.append(file);
+
+ if (line != NO_LINE) {
+ fileBuilder.append(':').append(line);
+ }
+
+ return maybeGenerateTableRowHtml("File", fileBuilder.toString());
+ }
+
+ return null;
+ }
+
+ @CheckForNull
+ private JSONArray getAuxWhatsArray(final Map specifics) {
+ final String auxWhatsJson = specifics.get("auxwhats");
+ return StringUtils.isNotBlank(auxWhatsJson) ? new JSONArray(new JSONTokener(auxWhatsJson)) : null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java b/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java
index 0b161cd2a..b4975b3b6 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java
@@ -163,6 +163,7 @@ public class ParserRegistry {
new TnsdlDescriptor(),
new TrivyDescriptor(),
new TsLintDescriptor(),
+ new ValgrindDescriptor(),
new VeraCodePipelineScannerDescriptor(),
new XlcDescriptor(),
new YamlLintDescriptor(),
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java
new file mode 100644
index 000000000..fa56d4fa7
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java
@@ -0,0 +1,42 @@
+package edu.hm.hafner.analysis.registry;
+
+import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.parser.violations.ValgrindAdapter;
+
+import static j2html.TagCreator.*;
+
+/**
+ * A descriptor for Valgrind.
+ */
+public class ValgrindDescriptor extends ParserDescriptor {
+ private static final String ID = "valgrind";
+ private static final String NAME = "Valgrind";
+
+ ValgrindDescriptor() {
+ super(ID, NAME);
+ }
+
+ @Override
+ public IssueParser createParser(final Option... options) {
+ return new ValgrindAdapter();
+ }
+
+ @Override
+ public String getHelp() {
+ return join(text("Use options"),
+ code("--xml=yes --xml-file=valgrind_report.xml --child-silent-after-fork=yes"),
+ text(", see the"),
+ a("Valgrind User Manual").withHref("https://valgrind.org/docs/manual/manual-core.html"),
+ text("for usage details.")).render();
+ }
+
+ @Override
+ public String getUrl() {
+ return "https://valgrind.org";
+ }
+
+ @Override
+ public String getIconUrl() {
+ return "https://valgrind.org/images/valgrind-link3.png";
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java b/src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java
new file mode 100644
index 000000000..aaa32c5e9
--- /dev/null
+++ b/src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java
@@ -0,0 +1,76 @@
+package edu.hm.hafner.analysis.parser.violations;
+
+import edu.hm.hafner.analysis.AbstractParserTest;
+import edu.hm.hafner.analysis.Report;
+import edu.hm.hafner.analysis.Severity;
+import edu.hm.hafner.analysis.assertions.SoftAssertions;
+
+import se.bjurr.violations.lib.model.Violation;
+
+/**
+ * Tests the class {@link ValgrindAdapter}.
+ *
+ * @author Tony Ciavarella
+ */
+class ValgrindAdapterTest extends AbstractParserTest {
+ ValgrindAdapterTest() {
+ super("valgrind.xml");
+ }
+
+ @Override
+ protected void assertThatIssuesArePresent(final Report report, final SoftAssertions softly) {
+ softly.assertThat(report).hasSize(5);
+ softly.assertThat(report.get(3))
+ .hasCategory("valgrind:memcheck")
+ .hasMessage("Conditional jump or move depends on uninitialised value(s)")
+ .hasFileName("/home/some_user/terrible_program/terrible_program.cpp")
+ .hasType("UninitCondition")
+ .hasLineStart(5)
+ .hasSeverity(Severity.WARNING_HIGH);
+ softly.assertThat(report.get(2))
+ .hasCategory("valgrind:memcheck")
+ .hasMessage("Invalid write of size 4")
+ .hasFileName("/home/some_user/terrible_program/terrible_program.cpp")
+ .hasType("InvalidWrite")
+ .hasLineStart(10)
+ .hasSeverity(Severity.WARNING_HIGH);
+ softly.assertThat(report.get(4))
+ .hasCategory("valgrind:memcheck")
+ .hasMessage("16 bytes in 1 blocks are definitely lost in loss record 1 of 1")
+ .hasFileName("/home/some_user/terrible_program/terrible_program.cpp")
+ .hasType("Leak_DefinitelyLost")
+ .hasLineStart(3)
+ .hasSeverity(Severity.WARNING_HIGH);
+ softly.assertThat(report.get(1))
+ .hasCategory("valgrind:memcheck")
+ .hasMessage("Syscall param write(buf) points to uninitialised byte(s)")
+ .hasFileName("/home/buildozer/aports/main/musl/src/1.2.4/src/thread/x86_64/syscall_cp.s")
+ .hasType("SyscallParam")
+ .hasLineStart(29)
+ .hasSeverity(Severity.WARNING_HIGH);
+ softly.assertThat(report.get(0))
+ .hasCategory("valgrind:memcheck")
+ .hasMessage("Some type of error without a stack trace")
+ .hasFileName(Violation.NO_FILE)
+ .hasType("Not_A_Real_Error")
+ .hasLineStart(Violation.NO_LINE)
+ .hasSeverity(Severity.WARNING_HIGH);
+
+ report.forEach(
+ issue -> {
+ final String description = issue.getDescription();
+ if (Violation.NO_FILE.equals(issue.getFileName())) {
+ softly.assertThat(description).doesNotContain("Primary Stack Trace");
+ }
+ else {
+ softly.assertThat(description).contains("Primary Stack Trace", "<insert_a_suppression_name_here>");
+ }
+ }
+ );
+ }
+
+ @Override
+ protected ValgrindAdapter createParser() {
+ return new ValgrindAdapter();
+ }
+}
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/violations/valgrind.xml b/src/test/resources/edu/hm/hafner/analysis/parser/violations/valgrind.xml
new file mode 100644
index 000000000..fc77bb098
--- /dev/null
+++ b/src/test/resources/edu/hm/hafner/analysis/parser/violations/valgrind.xml
@@ -0,0 +1,497 @@
+
+
+
+
+ 4
+ memcheck
+
+
+ Memcheck, a memory error detector
+ Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
+ Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
+ Command: ./terrible_program
+
+
+ 36556
+ 26175
+ memcheck
+
+
+
+ /usr/bin/valgrind.bin
+ --gen-suppressions=all
+ --xml=yes
+ --xml-file=valgrind.xml
+ --track-origins=yes
+ --leak-check=full
+ --show-leak-kinds=all
+
+
+ ./terrible_program
+
+
+
+
+ RUNNING
+
+
+
+
+ 0x0
+ 1
+ worst thread ever
+ UninitCondition
+ Conditional jump or move depends on uninitialised value(s)
+
+
+ 0x109163
+ /home/some_user/terrible_program/terrible_program
+ main
+ /home/some_user/terrible_program
+ terrible_program.cpp
+ 5
+
+
+ Uninitialised value was created by a heap allocation
+
+
+ 0x483B20F
+ /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so
+ operator new[](unsigned long)
+ ./coregrind/m_replacemalloc
+ vg_replace_malloc.c
+ 640
+
+
+ 0x109151
+ /home/some_user/terrible_program/terrible_program
+ main
+ /home/some_user/terrible_program
+ terrible_program.cpp
+ 3
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Cond
+ main
+
+
+ Memcheck:Cond
+ fun:main
+}
+]]>
+
+
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Cond
+ main
+
+
+ Memcheck:Cond
+ fun:main
+}
+]]>
+
+
+
+ 0x1
+ 1
+ InvalidWrite
+ Invalid write of size 4
+
+
+ 0x109177
+ /home/some_user/terrible_program/terrible_program
+ main
+ /home/some_user/terrible_program
+ terrible_program.cpp
+ 10
+
+
+ Address 0x4dd0c90 is 0 bytes after a block of size 16 alloc'd
+
+
+ 0x483B20F
+ /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so
+ operator new[](unsigned long)
+ ./coregrind/m_replacemalloc
+ vg_replace_malloc.c
+ 640
+
+
+ 0x109151
+ /home/some_user/terrible_program/terrible_program
+ main
+ /home/some_user/terrible_program
+ terrible_program.cpp
+ 3
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Addr4
+ main
+
+
+ Memcheck:Addr4
+ fun:main
+}
+]]>
+
+
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Addr4
+ main
+
+
+ Memcheck:Addr4
+ fun:main
+}
+]]>
+
+
+
+
+ FINISHED
+
+
+
+
+ 0x2
+ 1
+ Leak_DefinitelyLost
+
+ 16 bytes in 1 blocks are definitely lost in loss record 1 of 1
+ 16
+ 1
+
+
+
+ 0x483B20F
+ /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so
+ operator new[](unsigned long)
+ ./coregrind/m_replacemalloc
+ vg_replace_malloc.c
+ 640
+
+
+ 0x109151
+ /home/some_user/terrible_program/terrible_program
+ main
+ /home/some_user/terrible_program
+ terrible_program.cpp
+ 3
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Leak
+ match-leak-kinds: definite
+ _Znam
+ main
+
+
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:_Znam
+ fun:main
+}
+]]>
+
+
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Leak
+ match-leak-kinds: definite
+ _Znam
+ main
+
+
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:_Znam
+ fun:main
+}
+]]>
+
+
+
+ 0x3
+ 1
+ SyscallParam
+ Syscall param write(buf) points to uninitialised byte(s)
+
+
+ 0x4057F73
+ /lib/ld-musl-x86_64.so.1
+ /home/buildozer/aports/main/musl/src/1.2.4/src/thread/x86_64
+ syscall_cp.s
+ 29
+
+
+ 0x40550FD
+ /lib/ld-musl-x86_64.so.1
+ __syscall_cp_c
+ pthread_cancel.c
+ 33
+
+
+ 0x405B67F
+ /lib/ld-musl-x86_64.so.1
+ write
+ /home/buildozer/aports/main/musl/src/1.2.4/src/unistd
+ write.c
+ 6
+
+
+ 0x109226
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 11
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 6
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 6
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 6
+
+
+ 0x109283
+ /workspace/awful project/awful_program
+ main
+ /workspace/awful project
+ awful_program.cpp
+ 14
+
+
+ Address 0x4b31cd0 is 0 bytes inside a block of size 10 alloc'd
+
+
+ 0x48A5733
+ /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so
+ malloc
+
+
+ 0x1091FE
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 9
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 6
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 6
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 6
+
+
+ 0x109283
+ /workspace/awful project/awful_program
+ main
+ /workspace/awful project
+ awful_program.cpp
+ 14
+
+
+ Uninitialised value was created by a heap allocation
+
+
+ 0x48A5733
+ /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so
+ malloc
+
+
+ 0x1091FE
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 9
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 6
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+
+
+ 0x1091F2
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 6
+
+
+ 0x109283
+ /workspace/awful project/awful_program
+ main
+ /workspace/awful project
+ awful_program.cpp
+ 14
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Param
+ write(buf)
+ /lib/ld-musl-x86_64.so.1
+ __syscall_cp_c
+ write
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ main
+
+
+ Memcheck:Param
+ write(buf)
+ obj:/lib/ld-musl-x86_64.so.1
+ fun:__syscall_cp_c
+ fun:write
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:main
+}
+]]>
+
+
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Param
+ write(buf)
+ /lib/ld-musl-x86_64.so.1
+ __syscall_cp_c
+ write
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ main
+
+
+ Memcheck:Param
+ write(buf)
+ obj:/lib/ld-musl-x86_64.so.1
+ fun:__syscall_cp_c
+ fun:write
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:main
+}
+]]>
+
+
+
+ 0x4
+ 1
+ Not_A_Real_Error
+ Some type of error without a stack trace
+
+
+
+ 1
+ 0x4
+
+
+ 1
+ 0x3
+
+
+ 1
+ 0x2
+
+
+ 1
+ 0x1
+
+
+ 1
+ 0x0
+
+
+
+
+
+
+
|