Skip to content

Commit c258e84

Browse files
sgscheffleruhafner
andauthored
Add LlvmLinkerParser for LLVM lld linker error parsing (#1245)
- Add LlvmLinkerParser class for parsing ld.lld error messages - Support versioned linkers (ld.lld-15, etc.) - Handle error/warning/note severity levels - Integrate with ClangDescriptor using composite pattern - Add comprehensive unit and integration tests Resolves JENKINS-76141 Co-authored-by: Ullrich Hafner <[email protected]>
1 parent ce27599 commit c258e84

File tree

9 files changed

+196
-6
lines changed

9 files changed

+196
-6
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package edu.hm.hafner.analysis.parser;
2+
3+
import java.io.Serial;
4+
import java.util.Optional;
5+
import java.util.regex.Matcher;
6+
7+
import edu.hm.hafner.analysis.Issue;
8+
import edu.hm.hafner.analysis.IssueBuilder;
9+
import edu.hm.hafner.analysis.LookaheadParser;
10+
import edu.hm.hafner.analysis.Severity;
11+
import edu.hm.hafner.util.LookaheadStream;
12+
13+
/**
14+
* A parser for LLVM lld linker warnings and errors.
15+
*
16+
* @author Steven Scheffler
17+
*/
18+
public class LlvmLinkerParser extends LookaheadParser {
19+
@Serial
20+
private static final long serialVersionUID = 1L;
21+
22+
// Capture optional path + program (handles /foo/bar/ld.lld, C:\foo\ld.lld-15.exe, etc.)
23+
// Named groups: linker, severity, message
24+
private static final String LLD_LINKER_PATTERN =
25+
"^(?<linker>.*[/\\\\]?ld\\.lld(?:-\\d+)?(?:\\.exe)?):\\s*(?<severity>error|warning|note):\\s*(?<message>.*)$";
26+
27+
/**
28+
* Creates a new instance of {@link LlvmLinkerParser}.
29+
*/
30+
public LlvmLinkerParser() {
31+
super(LLD_LINKER_PATTERN);
32+
}
33+
34+
@Override
35+
protected Optional<Issue> createIssue(final Matcher matcher, final LookaheadStream lookahead,
36+
final IssueBuilder builder) {
37+
final String linkerPath = matcher.group("linker");
38+
final String severity = matcher.group("severity");
39+
final String message = matcher.group("message");
40+
41+
// Strip any path prefix and prepend "/" to prevent relative path resolution
42+
final String fileName = "/" + linkerPath.replaceFirst("^.*[/\\\\]", "");
43+
44+
return builder.setFileName(fileName)
45+
.setLineStart(0)
46+
.setColumnStart(0)
47+
.setCategory("Linker")
48+
.setMessage(message.trim())
49+
.setSeverity(mapPriority(severity))
50+
.buildOptional();
51+
}
52+
53+
private Severity mapPriority(final String severity) {
54+
return switch (severity) {
55+
case "error" -> Severity.WARNING_HIGH;
56+
case "warning" -> Severity.WARNING_NORMAL;
57+
case "note" -> Severity.WARNING_LOW;
58+
default -> Severity.WARNING_NORMAL;
59+
};
60+
}
61+
}
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
package edu.hm.hafner.analysis.registry;
22

3+
import java.util.Collection;
4+
35
import edu.hm.hafner.analysis.IssueParser;
46
import edu.hm.hafner.analysis.parser.ClangParser;
7+
import edu.hm.hafner.analysis.parser.LlvmLinkerParser;
58

69
/**
7-
* A descriptor for the Clang parser.
10+
* A descriptor for the Clang parser (compiler + linker).
811
*
912
* @author Lorenz Munsch
13+
* @author Steven Scheffler
1014
*/
11-
class ClangDescriptor extends ParserDescriptor {
15+
class ClangDescriptor extends CompositeParserDescriptor {
1216
private static final String ID = "clang";
13-
private static final String NAME = "Clang";
17+
private static final String NAME = "Clang (LLVM based)";
1418

1519
ClangDescriptor() {
1620
super(ID, NAME);
1721
}
1822

1923
@Override
20-
public IssueParser create(final Option... options) {
21-
return new ClangParser();
24+
protected Collection<? extends IssueParser> createParsers() {
25+
return asList(new ClangParser(), new LlvmLinkerParser());
2226
}
23-
}
27+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package edu.hm.hafner.analysis.parser;
2+
3+
import java.util.Iterator;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import edu.hm.hafner.analysis.Issue;
8+
import edu.hm.hafner.analysis.Report;
9+
import edu.hm.hafner.analysis.Severity;
10+
import edu.hm.hafner.analysis.assertions.SoftAssertions;
11+
import edu.hm.hafner.analysis.registry.AbstractParserTest;
12+
13+
import static edu.hm.hafner.analysis.assertions.Assertions.*;
14+
15+
/**
16+
* Tests the class {@link LlvmLinkerParser}.
17+
*
18+
* @author Steven Scheffler
19+
*/
20+
class LlvmLinkerParserTest extends AbstractParserTest {
21+
LlvmLinkerParserTest() {
22+
super("llvm-linker.log");
23+
}
24+
25+
@Override
26+
protected LlvmLinkerParser createParser() {
27+
return new LlvmLinkerParser();
28+
}
29+
30+
@Override
31+
protected void assertThatIssuesArePresent(final Report report, final SoftAssertions softly) {
32+
softly.assertThat(report).hasSize(4);
33+
34+
Iterator<Issue> iterator = report.iterator();
35+
36+
softly.assertThat(iterator.next())
37+
.hasLineStart(0)
38+
.hasColumnStart(0)
39+
.hasMessage("cannot open xyz: No such file or directory")
40+
.hasFileName("/ld.lld")
41+
.hasCategory("Linker")
42+
.hasSeverity(Severity.WARNING_HIGH);
43+
44+
softly.assertThat(iterator.next())
45+
.hasLineStart(0)
46+
.hasColumnStart(0)
47+
.hasMessage("undefined symbol: lld::elf::demangle(llvm::StringRef)")
48+
.hasFileName("/ld.lld-15")
49+
.hasCategory("Linker")
50+
.hasSeverity(Severity.WARNING_HIGH);
51+
52+
softly.assertThat(iterator.next())
53+
.hasLineStart(0)
54+
.hasColumnStart(0)
55+
.hasMessage("duplicate symbol found")
56+
.hasFileName("/ld.lld")
57+
.hasCategory("Linker")
58+
.hasSeverity(Severity.WARNING_NORMAL);
59+
60+
softly.assertThat(iterator.next())
61+
.hasLineStart(0)
62+
.hasColumnStart(0)
63+
.hasMessage("creating a DT_TEXTREL in a shared object")
64+
.hasFileName("/ld.lld")
65+
.hasCategory("Linker")
66+
.hasSeverity(Severity.WARNING_LOW);
67+
}
68+
69+
@Test
70+
void shouldParseVersionedLld() {
71+
var warnings = parse("versioned-lld.log");
72+
assertThat(warnings).hasSize(3);
73+
74+
try (var softly = new SoftAssertions()) {
75+
softly.assertThat(warnings.get(0))
76+
.hasFileName("/ld.lld-15")
77+
.hasMessage("cannot find -lmylib");
78+
}
79+
}
80+
81+
@Test
82+
void shouldIgnoreNonLldLines() {
83+
var warnings = parse("mixed-output.log");
84+
assertThat(warnings).hasSize(2);
85+
assertThat(warnings.stream()).allSatisfy(issue -> assertThat(issue).hasFileName("/ld.lld"));
86+
}
87+
88+
@Test
89+
void issue76141() {
90+
var warnings = parse("issue76141.log");
91+
assertThat(warnings).hasSize(1);
92+
93+
try (var softly = new SoftAssertions()) {
94+
softly.assertThat(warnings.get(0))
95+
.hasMessage("cannot open xyz: No such file or directory")
96+
.hasFileName("/ld.lld-15")
97+
.hasSeverity(Severity.WARNING_HIGH);
98+
}
99+
}
100+
}

src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,12 @@ void shouldFindAllClangIssues() {
449449
findIssuesOfTool(9, "clang", "apple-llvm-clang.txt");
450450
}
451451

452+
/** Runs the Clang parser on mixed compiler and linker errors. */
453+
@Test
454+
void shouldFindAllClangAndLldIssues() {
455+
findIssuesOfTool(4, "clang", "mixed-clang-lld-errors.log");
456+
}
457+
452458
/** Runs the Coolflux parser on an output file that contains 1 issues. */
453459
@Test
454460
void shouldFindAllCoolfluxIssues() {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ld.lld-15: error: cannot open xyz: No such file or directory
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ld.lld: error: cannot open xyz: No such file or directory
2+
ld.lld-15: error: undefined symbol: lld::elf::demangle(llvm::StringRef)
3+
ld.lld: warning: duplicate symbol found
4+
ld.lld: note: creating a DT_TEXTREL in a shared object
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
main.cpp:42:10: error: use of undeclared identifier 'foo'
2+
main.cpp:15:5: warning: unused variable 'x' [-Wunused-variable]
3+
ld.lld: error: cannot open libbar.so: No such file or directory
4+
ld.lld: warning: creating a DT_TEXTREL in a shared object
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
make: Entering directory '/path/to/project'
2+
clang++ -c main.cpp
3+
main.cpp:42:10: error: undeclared identifier 'foo'
4+
ld.lld: error: cannot open libbar.so: No such file or directory
5+
make: *** [app] Error 1
6+
ld.lld: warning: creating a DT_TEXTREL in a shared object
7+
make: Leaving directory '/path/to/project'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ld.lld-15: error: cannot find -lmylib
2+
ld.lld-18: warning: symbol 'foo' has undefined behavior
3+
ld.lld: error: section overlap detected

0 commit comments

Comments
 (0)