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/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 + + + + + + +