Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add valgrind parser #738

Merged
merged 7 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}

uhafner marked this conversation as resolved.
Show resolved Hide resolved
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;

Check warning on line 119 in src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java#L119

Added line #L119 was not covered by tests
}

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,27 @@
package edu.hm.hafner.analysis.registry;

import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.parser.violations.ValgrindAdapter;

/**
* A descriptor for Valgrind.
* See https://valgrind.org for more information about Valgrind.
*/
public class ValgrindDescriptor extends ParserDescriptor {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you also have a URL?

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";
}
}
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