Skip to content

Commit 6f77973

Browse files
committed
Fix Gcc parser when there are multiple locations involved
When multiple locations are used, then also the primary location must be provided as a location. Fixes #1488
1 parent b843f33 commit 6f77973

3 files changed

Lines changed: 125 additions & 64 deletions

File tree

src/main/java/edu/hm/hafner/analysis/IssueBuilder.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,19 @@ public IssueBuilder guessSeverity(@CheckForNull final String severityString) {
491491
return this;
492492
}
493493

494+
/**
495+
* Sets the detailed message for this issue.
496+
*
497+
* @param message
498+
* the message
499+
*
500+
* @return this
501+
*/
502+
@CanIgnoreReturnValue
503+
public IssueBuilder setMessage(final StringBuilder message) {
504+
return setMessage(message.toString());
505+
}
506+
494507
/**
495508
* Sets the detailed message for this issue.
496509
*
@@ -588,6 +601,30 @@ public IssueBuilder addLocation(final Location location) {
588601
return this;
589602
}
590603

604+
/**
605+
* Adds another location to this issue, the first location is considered the primary location.
606+
*
607+
* @param fileName
608+
* the name of the affected file
609+
* @param lineStart
610+
* the first line of the affected code
611+
* @param lineEnd
612+
* the last line of the affected code
613+
* @param columnStart
614+
* the first column of the affected code
615+
* @param columnEnd
616+
* the last column of the affected code
617+
*
618+
* @return this
619+
*/
620+
@CanIgnoreReturnValue
621+
@SuppressWarnings("checkstyle:HiddenField")
622+
public IssueBuilder addLocation(final String fileName,
623+
final int lineStart, final int lineEnd,
624+
final int columnStart, final int columnEnd) {
625+
return addLocation(new Location(internFileName(fileName), lineStart, lineEnd, columnStart, columnEnd));
626+
}
627+
591628
/**
592629
* Adds another location to this issue, the first location is considered the primary location.
593630
*

src/main/java/edu/hm/hafner/analysis/parser/Gcc4CompilerParser.java

Lines changed: 54 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44

55
import edu.hm.hafner.analysis.Issue;
66
import edu.hm.hafner.analysis.IssueBuilder;
7-
import edu.hm.hafner.analysis.Location;
87
import edu.hm.hafner.analysis.LookaheadParser;
9-
import edu.hm.hafner.analysis.Severity;
108
import edu.hm.hafner.analysis.util.IntegerParser;
119
import edu.hm.hafner.util.LookaheadStream;
12-
import edu.hm.hafner.util.TreeStringBuilder;
1310

1411
import java.io.Serial;
12+
import java.util.ArrayList;
1513
import java.util.Optional;
1614
import java.util.regex.Matcher;
1715
import java.util.regex.Pattern;
@@ -25,12 +23,13 @@ public class Gcc4CompilerParser extends LookaheadParser {
2523
@Serial
2624
private static final long serialVersionUID = 5490211629355204910L;
2725

26+
private static final String LOCATION = "(?<file>.+?):(?<line>\\d+):(?:(?<column>\\d+):)?";
2827
private static final String GCC_WARNING_PATTERN =
29-
ANT_TASK + "(.+?):(\\d+):(?:(\\d+):)? ?([wW]arning|.*[Ee]rror|[Nn]ote): (.*)$";
28+
ANT_TASK + LOCATION + " ?(?<severity>[wW]arning|.*[Ee]rror): (?<message>.*)$";
3029
private static final Pattern CLASS_PATTERN = Pattern.compile("\\[-W(.+)]$");
3130
private static final Pattern GCC_OPTION_PATTERN = Pattern.compile("\\[-(f[^\\]]+)]$");
3231
private static final Pattern CLANG_TIDY_PATTERN = Pattern.compile("\\[[^\\s\\]]+\\]$");
33-
private static final Pattern NOTE_PATTERN = Pattern.compile("^(?<file>.+?):(?<line>\\d+):(?:(?<column>\\d+):)? ?[Nn]ote: (?<message>.*)$");
32+
private static final Pattern NOTE_PATTERN = Pattern.compile("^" + LOCATION + " ?[Nn]ote: (?<message>.*)$");
3433

3534
/**
3635
* Creates a new instance of {@link Gcc4CompilerParser}.
@@ -40,8 +39,10 @@ public Gcc4CompilerParser() {
4039
}
4140

4241
/**
43-
* Creates a new instance of {@link Gcc4CompilerParser} with specified pattern.
44-
* @param pattern a regex pattern to be used instead of the default one
42+
* Creates a new instance of {@link Gcc4CompilerParser} with the specified pattern.
43+
*
44+
* @param pattern
45+
* a regex pattern to be used instead of the default one
4546
*/
4647
Gcc4CompilerParser(final String pattern) {
4748
super(pattern);
@@ -55,64 +56,69 @@ protected boolean isLineInteresting(final String line) {
5556
@Override
5657
protected Optional<Issue> createIssue(final Matcher matcher, final LookaheadStream lookahead,
5758
final IssueBuilder builder) {
58-
if (isNoteMessage(matcher)) {
59+
var originalMessage = matcher.group("message");
60+
if (isClangTidyWarning(originalMessage)) {
5961
return Optional.empty();
6062
}
6163

62-
var message = new StringBuilder(matcher.group(5));
63-
var originalMessage = message.toString();
64-
65-
setCategory(builder, originalMessage);
64+
addLocation(matcher, builder);
6665

66+
var message = new StringBuilder(originalMessage);
6767
boolean hasCodeSnippet = false;
6868
while (lookahead.hasNext() && isMessageContinuation(lookahead, hasCodeSnippet)) {
6969
var continuation = lookahead.next();
70-
if (continuation.length() > 0 && Character.isWhitespace(continuation.charAt(0))) {
70+
if (!continuation.isEmpty() && Character.isWhitespace(continuation.charAt(0))) {
7171
hasCodeSnippet = true;
7272
}
7373
message.append('\n');
7474
message.append(continuation);
7575
}
7676

77-
var treeStringBuilder = new TreeStringBuilder();
78-
var notes = collectNotes(lookahead, builder, treeStringBuilder);
77+
var notes = collectNotes(lookahead, builder);
7978
if (!notes.isEmpty()) {
8079
message.append('\n');
8180
message.append(notes);
8281
}
8382

84-
// Reject clang-tidy warnings that have [check-name] but not [-Wwarning-name]
85-
// clang-tidy warnings should be handled by ClangTidyParser
86-
var messageStr = message.toString();
87-
if (CLANG_TIDY_PATTERN.matcher(messageStr).find() && !CLASS_PATTERN.matcher(messageStr).find()) {
88-
return Optional.empty();
89-
}
90-
91-
return builder.setFileName(matcher.group(1))
92-
.setLineStart(matcher.group(2))
93-
.setColumnStart(matcher.group(3))
94-
.setMessage(messageStr)
95-
.setSeverity(Severity.guessFromString(matcher.group(4)))
83+
setCategory(builder, originalMessage);
84+
return builder.setMessage(message)
85+
.guessSeverity(matcher.group("severity"))
9686
.buildOptional();
9787
}
9888

89+
private void addLocation(final Matcher matcher, final IssueBuilder builder) {
90+
var fileName = matcher.group("file");
91+
var line = toInt(matcher.group("line"));
92+
var column = toInt(matcher.group("column"));
93+
94+
builder.addLocation(fileName, line, line, column, column);
95+
}
96+
97+
private int toInt(final String number) {
98+
return IntegerParser.parseInt(number);
99+
}
100+
99101
/**
100-
* Checks if the matched line is a note message.
102+
* Check if the warning is from clang-tidy. Those warnings are quite similar and have [check-name] but not
103+
* [-Wwarning-name] or [-fcompiler-option]. Since a separate parser handles clang-tidy warnings, we can skip them
104+
* here.
101105
*
102-
* @param matcher the matcher for the current line
103-
* @return true if the line is a note message, false otherwise
106+
* @return {@code true} if the message is a clang-tidy warning, {@code false} otherwise
104107
*/
105-
private boolean isNoteMessage(final Matcher matcher) {
106-
var messageType = matcher.group(4);
107-
return "note".equals(messageType) || "Note".equals(messageType);
108+
private boolean isClangTidyWarning(final String message) {
109+
return CLANG_TIDY_PATTERN.matcher(message).find()
110+
&& !CLASS_PATTERN.matcher(message).find()
111+
&& !GCC_OPTION_PATTERN.matcher(message).find();
108112
}
109113

110114
/**
111-
* Sets the category based on the original message content.
112-
* Checks for GCC warning categories (e.g., [-Wunused-variable]) or compiler options (e.g., [-fpermissive]).
115+
* Sets the category based on the original message content. Checks for GCC warning categories (e.g.,
116+
* [-Wunused-variable]) or compiler options (e.g., [-fpermissive]).
113117
*
114-
* @param builder the issue builder
115-
* @param originalMessage the original message to extract the category from
118+
* @param builder
119+
* the issue builder
120+
* @param originalMessage
121+
* the original message to extract the category from
116122
*/
117123
private void setCategory(final IssueBuilder builder, final String originalMessage) {
118124
var classMatcher = CLASS_PATTERN.matcher(originalMessage);
@@ -128,38 +134,27 @@ private void setCategory(final IssueBuilder builder, final String originalMessag
128134
}
129135

130136
/**
131-
* Collects any subsequent note lines from the lookahead stream and adds them as additional locations.
137+
* Collects any further note lines from the lookahead stream and adds them as additional locations.
132138
*
133-
* @param lookahead the lookahead stream
134-
* @param builder the issue builder to add locations to
135-
* @param treeStringBuilder the tree string builder for interning file names
139+
* @param lookahead
140+
* the lookahead stream
141+
* @param builder
142+
* the issue builder to add locations to
136143
* @return a string containing all collected note lines, or an empty string if no notes were found
137144
*/
138-
private String collectNotes(final LookaheadStream lookahead, final IssueBuilder builder,
139-
final TreeStringBuilder treeStringBuilder) {
140-
var notes = new StringBuilder();
145+
private String collectNotes(final LookaheadStream lookahead, final IssueBuilder builder) {
146+
var notes = new ArrayList<String>();
141147
while (lookahead.hasNext()) {
142-
var nextLine = lookahead.peekNext();
143-
var noteMatcher = NOTE_PATTERN.matcher(nextLine);
148+
var noteMatcher = NOTE_PATTERN.matcher(lookahead.peekNext());
144149
if (noteMatcher.matches()) {
145-
if (!notes.isEmpty()) {
146-
notes.append('\n');
147-
}
148-
notes.append(lookahead.next());
149-
150-
// Add the note as an additional location
151-
var file = noteMatcher.group("file");
152-
var line = IntegerParser.parseInt(noteMatcher.group("line"));
153-
var column = IntegerParser.parseInt(noteMatcher.group("column"));
154-
155-
var fileName = treeStringBuilder.intern(file);
156-
builder.addLocation(new Location(fileName, line, line, column, column));
150+
notes.add(lookahead.next());
151+
addLocation(noteMatcher, builder);
157152
}
158153
else {
159154
break;
160155
}
161156
}
162-
return notes.toString();
157+
return String.join("\n", notes);
163158
}
164159

165160
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")

src/test/java/edu/hm/hafner/analysis/parser/Gcc4CompilerParserTest.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -622,11 +622,40 @@ void issue55221() {
622622
}
623623
}
624624

625-
/**
626-
* Parses a javac warning log.
627-
*
628-
* @see <a href="https://issues.jenkins.io/browse/JENKINS-68396">Issue 68396</a>
629-
*/
625+
@Test
626+
@org.junitpioneer.jupiter.Issue("https://github.com/jenkinsci/analysis-model/issues/1488")
627+
void shouldRegisterPrimaryLocationInTheBeginning() {
628+
var report = parseStringContent("""
629+
/workspace/foo.cpp:134:26: warning: declaration of \\'i\\' shadows a previous local [-Wshadow]
630+
134 | for (u32 i = 0; i < (cmd.Size-sizeof(TDSPCommand))/4 && (cmd.Size>sizeof(TDSPCommand))>0; i++)
631+
| ^
632+
/workspace/foo.cpp:122:18: note: shadowed declaration is here
633+
122 | for (int i = 0; i < _commandsCount; i++)
634+
| ^
635+
""");
636+
637+
assertThat(report).hasSize(1);
638+
639+
var issue = report.get(0);
640+
assertThat(issue)
641+
.hasLineStart(134)
642+
.hasColumnStart(26)
643+
.hasFileName("/workspace/foo.cpp")
644+
.hasSeverity(Severity.WARNING_NORMAL);
645+
assertThat(issue.getMessage()).containsIgnoringWhitespaces("""
646+
declaration of \\'i\\' shadows a previous local [-Wshadow]
647+
134 | for (u32 i = 0; i < (cmd.Size-sizeof(TDSPCommand))/4 && (cmd.Size>sizeof(TDSPCommand))>0; i++)
648+
| ^
649+
/workspace/foo.cpp:122:18: note: shadowed declaration is here
650+
""");
651+
assertThat(issue.getLocations()).hasSize(2);
652+
assertThat(issue.getLocations().getLast())
653+
.hasFileName("/workspace/foo.cpp")
654+
.hasLineStart(122)
655+
.hasColumnStart(18);
656+
}
657+
658+
@org.junitpioneer.jupiter.Issue("https://issues.jenkins.io/browse/JENKINS-68396")
630659
@Test
631660
void issue68396() {
632661
var warnings = parse("issue68396.txt");

0 commit comments

Comments
 (0)