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- - + + + valgrind + + + - + + + Valgrind + + + - + + + + + :bulb: Use option --xml=yes + + 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 + + + + + + + +