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/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..7322bec98
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java
@@ -0,0 +1,194 @@
+package edu.hm.hafner.analysis.parser.violations;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.text.StringEscapeUtils;
+
+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 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;
+
+ @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.setMessage(violation.getMessage());
+ issueBuilder.setDescription(generateDescriptionHtml(violation));
+ report.add(issueBuilder.buildAndClean());
+ }
+
+ return report;
+ }
+ }
+
+ private String generateDescriptionHtml(final Violation violation) {
+ final StringBuilder description = new StringBuilder(1024);
+
+ final Map specifics = violation.getSpecifics();
+ final JSONArray auxWhats = getAuxWhatsArray(specifics);
+
+ appendGeneralTable(description, violation.getSource(), violation.getGroup(), specifics.get("tid"), specifics.get("threadname"), auxWhats);
+ maybeAppendStackTraces(description, specifics.get("stacks"), violation.getMessage(), auxWhats);
+ maybeAppendSuppression(description, specifics.get("suppression"));
+
+ return description.toString();
+ }
+
+ private void appendGeneralTable(final StringBuilder html, final String executable, final String uniqueId, @CheckForNull final String threadId, @CheckForNull final String threadName, @CheckForNull final JSONArray auxWhats) {
+ html.append("");
+
+ maybeAppendTableRow(html, "Executable", executable);
+ maybeAppendTableRow(html, "Unique Id", uniqueId);
+ maybeAppendTableRow(html, "Thread Id", threadId);
+ maybeAppendTableRow(html, "Thread Name", threadName);
+
+ if (auxWhats != null && !auxWhats.isEmpty()) {
+ for (int auxwhatIndex = 0; auxwhatIndex < auxWhats.length(); ++auxwhatIndex) {
+ maybeAppendTableRow(html, "Auxiliary", auxWhats.getString(auxwhatIndex));
+ }
+ }
+
+ html.append(" ");
+ }
+
+ private void maybeAppendStackTraces(final StringBuilder html, @CheckForNull final String stacksJson, final String message, @CheckForNull final JSONArray auxWhats) {
+ if (stacksJson == null || stacksJson.isEmpty()) {
+ return;
+ }
+
+ final JSONArray stacks = new JSONArray(new JSONTokener(stacksJson));
+
+ if (!stacks.isEmpty()) {
+ appendStackTrace(html, "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 = "Auxiliary Stack Trace #" + stackIndex;
+ }
+
+ appendStackTrace(html, title, msg, stacks.getJSONArray(stackIndex));
+ }
+ }
+ }
+
+ private void appendStackTrace(final StringBuilder html, final String title, @CheckForNull final String message, final JSONArray frames) {
+ html
+ .append("
")
+ .append(title)
+ .append("");
+
+ if (message != null && !message.isEmpty()) {
+ html
+ .append("")
+ .append(message)
+ .append(" ");
+ }
+
+ for (int frameIndex = 0; frameIndex < frames.length(); ++frameIndex) {
+ final JSONObject frame = frames.getJSONObject(frameIndex);
+
+ if (frameIndex > 0) {
+ html.append(" ");
+ }
+
+ appendStackFrame(html, frame);
+ }
+ }
+
+ private void appendStackFrame(final StringBuilder html, final JSONObject frame) {
+ html.append("");
+ maybeAppendTableRow(html, "Object", frame.optString("obj"));
+ maybeAppendTableRow(html, "Function", frame.optString("fn"));
+ maybeAppendStackFrameFileTableRow(html, frame);
+ html.append(" ");
+ }
+
+ private void maybeAppendSuppression(final StringBuilder html, @CheckForNull final String suppression) {
+ if (suppression != null && !suppression.isEmpty()) {
+ html
+ .append("Suppression ")
+ .append(StringEscapeUtils.escapeHtml4(suppression))
+ .append(" | ");
+ }
+ }
+
+ private void maybeAppendTableRow(final StringBuilder html, final String name, @CheckForNull final String value) {
+ if (value != null && !value.isEmpty()) {
+ html
+ .append("")
+ .append(name)
+ .append(" | ")
+ .append(value)
+ .append(" | ");
+ }
+ }
+
+ private void maybeAppendStackFrameFileTableRow(final StringBuilder html, final JSONObject frame) throws JSONException {
+ String dir = frame.optString("dir");
+ final String file = frame.optString("file");
+ final int line = frame.optInt("line", -1);
+
+ if (!file.isEmpty()) {
+ html.append("File | ");
+
+ if (!dir.isEmpty()) {
+ html.append(dir).append('/');
+ }
+
+ html.append(file);
+
+ if (line != -1) {
+ html.append(':').append(line);
+ }
+
+ html.append(" | ");
+ }
+ }
+
+ @CheckForNull
+ private JSONArray getAuxWhatsArray(final Map specifics) {
+ final String auxWhatsJson = specifics.get("auxwhats");
+
+ if (auxWhatsJson != null && !auxWhatsJson.isEmpty()) {
+ return new JSONArray(new JSONTokener(auxWhatsJson));
+ }
+
+ return 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..82a82de46
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java
@@ -0,0 +1,26 @@
+package edu.hm.hafner.analysis.registry;
+
+import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.parser.violations.ValgrindAdapter;
+
+/**
+ * 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 "Use option --xml=yes";
+ }
+}
\ 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..87cfc9915
--- /dev/null
+++ b/src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java
@@ -0,0 +1,64 @@
+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 exit_group(status) contains uninitialised byte(s)")
+ .hasFileName("/home/buildozer/aports/main/musl/src/1.2.4/./arch/x86_64/syscall_arch.h")
+ .hasType("SyscallParam")
+ .hasLineStart(14)
+ .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(0)
+ .hasSeverity(Severity.WARNING_HIGH);
+ }
+
+ @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..2d564c100
--- /dev/null
+++ b/src/test/resources/edu/hm/hafner/analysis/parser/violations/valgrind.xml
@@ -0,0 +1,453 @@
+
+
+
+
+ 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 exit_group(status) contains uninitialised byte(s)
+
+
+ 0x401BF8E
+ /lib/ld-musl-x86_64.so.1
+ __syscall1
+ /home/buildozer/aports/main/musl/src/1.2.4/./arch/x86_64
+ syscall_arch.h
+ 14
+
+
+ 0x401BF8E
+ /lib/ld-musl-x86_64.so.1
+ _Exit
+ /home/buildozer/aports/main/musl/src/1.2.4/src/exit
+ _Exit.c
+ 7
+
+
+ 0x4014091
+ /lib/ld-musl-x86_64.so.1
+ exit
+ /home/buildozer/aports/main/musl/src/1.2.4/src/exit
+ exit.c
+ 32
+
+
+ 0x109233
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 12
+
+
+ 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
+
+
+ 0x10920C
+ /workspace/awful project/awful_program
+ make_spaghetti(int)
+ /workspace/awful project
+ spaghetti.cpp
+ 10
+
+
+ 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
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Param
+ exit_group(status)
+ __syscall1
+ _Exit
+ exit
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ main
+
+
+ Memcheck:Param
+ exit_group(status)
+ fun:__syscall1
+ fun:_Exit
+ fun:exit
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:_Z14make_spaghettii
+ fun:main
+}
+]]>
+
+
+
+
+
+ insert_a_suppression_name_here
+ Memcheck:Param
+ exit_group(status)
+ __syscall1
+ _Exit
+ exit
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ _Z14make_spaghettii
+ main
+
+
+ Memcheck:Param
+ exit_group(status)
+ fun:__syscall1
+ fun:_Exit
+ fun:exit
+ 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
+
+
+
+
+
+
+
+
|