-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #738 from bitrunner/add-valgrind-parser
Add valgrind parser
- Loading branch information
Showing
7 changed files
with
837 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
} | ||
} |
193 changes: 193 additions & 0 deletions
193
src/main/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
src/test/java/edu/hm/hafner/analysis/parser/violations/ValgrindAdapterTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", "<insert_a_suppression_name_here>"); | ||
} | ||
} | ||
); | ||
} | ||
|
||
@Override | ||
protected ValgrindAdapter createParser() { | ||
return new ValgrindAdapter(); | ||
} | ||
} |
Oops, something went wrong.