Skip to content

Commit

Permalink
Merge pull request #738 from bitrunner/add-valgrind-parser
Browse files Browse the repository at this point in the history
Add valgrind parser
  • Loading branch information
uhafner authored Sep 20, 2023
2 parents 717a214 + 943e039 commit 1655139
Show file tree
Hide file tree
Showing 7 changed files with 837 additions and 0 deletions.
19 changes: 19 additions & 0 deletions SUPPORTED-FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,25 @@ analyze - iccxxxxcompiler_opts cstat2.c</pre></code>For details check the IAR C-
-
</td>
</tr>
<tr>
<td>
valgrind
</td>
<td>
-
</td>
<td>
Valgrind
</td>
<td>
-
</td>
</tr>
<tr>
<td colspan="4">
:bulb: Use option --xml=yes
</td>
</tr>
<tr>
<td>
veracode-pipeline-scanner
Expand Down
9 changes: 9 additions & 0 deletions etc/Jenkinsfile.valgrind
Original file line number Diff line number Diff line change
@@ -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')
}
}
Original file line number Diff line number Diff line change
@@ -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<Violation> 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<String, String> 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<String, String> specifics) {
final String auxWhatsJson = specifics.get("auxwhats");
return StringUtils.isNotBlank(auxWhatsJson) ? new JSONArray(new JSONTokener(auxWhatsJson)) : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ public class ParserRegistry {
new TnsdlDescriptor(),
new TrivyDescriptor(),
new TsLintDescriptor(),
new ValgrindDescriptor(),
new VeraCodePipelineScannerDescriptor(),
new XlcDescriptor(),
new YamlLintDescriptor(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
@@ -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", "&lt;insert_a_suppression_name_here&gt;");
}
}
);
}

@Override
protected ValgrindAdapter createParser() {
return new ValgrindAdapter();
}
}
Loading

0 comments on commit 1655139

Please sign in to comment.