Skip to content

Commit 31441ad

Browse files
authored
Fix Gcc parser when there are multiple locations involved (#1493)
1 parent 2bbb971 commit 31441ad

3 files changed

Lines changed: 130 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: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package edu.hm.hafner.analysis.parser;
22

33
import org.apache.commons.lang3.Strings;
4+
import org.dom4j.util.StringUtils;
45

56
import edu.hm.hafner.analysis.Issue;
67
import edu.hm.hafner.analysis.IssueBuilder;
7-
import edu.hm.hafner.analysis.Location;
88
import edu.hm.hafner.analysis.LookaheadParser;
9-
import edu.hm.hafner.analysis.Severity;
109
import edu.hm.hafner.analysis.util.IntegerParser;
1110
import edu.hm.hafner.util.LookaheadStream;
12-
import edu.hm.hafner.util.TreeStringBuilder;
1311

1412
import java.io.Serial;
13+
import java.util.ArrayList;
1514
import java.util.Optional;
1615
import java.util.regex.Matcher;
1716
import java.util.regex.Pattern;
@@ -25,12 +24,13 @@ public class Gcc4CompilerParser extends LookaheadParser {
2524
@Serial
2625
private static final long serialVersionUID = 5490211629355204910L;
2726

27+
private static final String LOCATION = "(?<file>.+?):(?<line>\\d+):(?:(?<column>\\d+):)?";
2828
private static final String GCC_WARNING_PATTERN =
29-
ANT_TASK + "(.+?):(\\d+):(?:(\\d+):)? ?([wW]arning|.*[Ee]rror|[Nn]ote): (.*)$";
29+
ANT_TASK + LOCATION + " ?(?<severity>[wW]arning|.*[Ee]rror): (?<message>.*)$";
3030
private static final Pattern CLASS_PATTERN = Pattern.compile("\\[-W(.+)]$");
3131
private static final Pattern GCC_OPTION_PATTERN = Pattern.compile("\\[-(f[^\\]]+)]$");
3232
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>.*)$");
33+
private static final Pattern NOTE_PATTERN = Pattern.compile("^" + LOCATION + " ?[Nn]ote: (?<message>.*)$");
3434

3535
/**
3636
* Creates a new instance of {@link Gcc4CompilerParser}.
@@ -40,8 +40,10 @@ public Gcc4CompilerParser() {
4040
}
4141

4242
/**
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
43+
* Creates a new instance of {@link Gcc4CompilerParser} with the specified pattern.
44+
*
45+
* @param pattern
46+
* a regex pattern to be used instead of the default one
4547
*/
4648
Gcc4CompilerParser(final String pattern) {
4749
super(pattern);
@@ -55,64 +57,72 @@ protected boolean isLineInteresting(final String line) {
5557
@Override
5658
protected Optional<Issue> createIssue(final Matcher matcher, final LookaheadStream lookahead,
5759
final IssueBuilder builder) {
58-
if (isNoteMessage(matcher)) {
60+
var originalMessage = matcher.group("message");
61+
if (isClangTidyWarning(originalMessage)) {
5962
return Optional.empty();
6063
}
6164

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

67+
var message = new StringBuilder(originalMessage);
6768
boolean hasCodeSnippet = false;
6869
while (lookahead.hasNext() && isMessageContinuation(lookahead, hasCodeSnippet)) {
6970
var continuation = lookahead.next();
70-
if (continuation.length() > 0 && Character.isWhitespace(continuation.charAt(0))) {
71+
if (StringUtils.startsWithWhitespace(continuation)) {
7172
hasCodeSnippet = true;
7273
}
7374
message.append('\n');
7475
message.append(continuation);
7576
}
7677

77-
var treeStringBuilder = new TreeStringBuilder();
78-
var notes = collectNotes(lookahead, builder, treeStringBuilder);
78+
var notes = collectNotes(lookahead, builder);
7979
if (!notes.isEmpty()) {
8080
message.append('\n');
8181
message.append(notes);
8282
}
8383

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)))
84+
setCategory(builder, originalMessage);
85+
return builder.setMessage(message)
86+
.guessSeverity(matcher.group("severity"))
9687
.buildOptional();
9788
}
9889

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

110118
/**
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]).
119+
* Sets the category based on the original message content. Checks for GCC warning categories (e.g.,
120+
* [-Wunused-variable]) or compiler options (e.g., [-fpermissive]).
113121
*
114-
* @param builder the issue builder
115-
* @param originalMessage the original message to extract the category from
122+
* @param builder
123+
* the issue builder
124+
* @param originalMessage
125+
* the original message to extract the category from
116126
*/
117127
private void setCategory(final IssueBuilder builder, final String originalMessage) {
118128
var classMatcher = CLASS_PATTERN.matcher(originalMessage);
@@ -128,38 +138,28 @@ private void setCategory(final IssueBuilder builder, final String originalMessag
128138
}
129139

130140
/**
131-
* Collects any subsequent note lines from the lookahead stream and adds them as additional locations.
141+
* Collects any further note lines from the lookahead stream and adds them as additional locations.
142+
*
143+
* @param lookahead
144+
* the lookahead stream
145+
* @param builder
146+
* the issue builder to add locations to
132147
*
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
136148
* @return a string containing all collected note lines, or an empty string if no notes were found
137149
*/
138-
private String collectNotes(final LookaheadStream lookahead, final IssueBuilder builder,
139-
final TreeStringBuilder treeStringBuilder) {
140-
var notes = new StringBuilder();
150+
private String collectNotes(final LookaheadStream lookahead, final IssueBuilder builder) {
151+
var notes = new ArrayList<String>();
141152
while (lookahead.hasNext()) {
142-
var nextLine = lookahead.peekNext();
143-
var noteMatcher = NOTE_PATTERN.matcher(nextLine);
153+
var noteMatcher = NOTE_PATTERN.matcher(lookahead.peekNext());
144154
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));
155+
notes.add(lookahead.next());
156+
addLocation(noteMatcher, builder);
157157
}
158158
else {
159159
break;
160160
}
161161
}
162-
return notes.toString();
162+
return String.join("\n", notes);
163163
}
164164

165165
@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)