Skip to content

Commit ac650e4

Browse files
committed
[flang] Don't create bogus tokens from token pasting (##)
When blank tokens arise from macro replacement in token sequences with token pasting (##), the preprocessor is producing some bogus tokens (e.g., "name(") that can lead to subtle bugs later when macro names are not recognized as such. The fix is to not paste tokens together when the result would not be a valid Fortran or C token in the preprocessing context.
1 parent 2910c24 commit ac650e4

File tree

2 files changed

+55
-8
lines changed

2 files changed

+55
-8
lines changed

flang/lib/Parser/preprocessor.cpp

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,23 +156,50 @@ static TokenSequence TokenPasting(TokenSequence &&text) {
156156
}
157157
TokenSequence result;
158158
std::size_t tokens{text.SizeInTokens()};
159-
bool pasting{false};
159+
std::optional<CharBlock> before; // last non-blank token before ##
160160
for (std::size_t j{0}; j < tokens; ++j) {
161-
if (IsTokenPasting(text.TokenAt(j))) {
162-
if (!pasting) {
161+
CharBlock after{text.TokenAt(j)};
162+
if (!before) {
163+
if (IsTokenPasting(after)) {
163164
while (!result.empty() &&
164165
result.TokenAt(result.SizeInTokens() - 1).IsBlank()) {
165166
result.pop_back();
166167
}
167168
if (!result.empty()) {
168-
result.ReopenLastToken();
169-
pasting = true;
169+
before = result.TokenAt(result.SizeInTokens() - 1);
170170
}
171+
} else {
172+
result.AppendRange(text, j, 1);
173+
}
174+
} else if (after.IsBlank() || IsTokenPasting(after)) {
175+
// drop it
176+
} else { // pasting before ## after
177+
bool doPaste{false};
178+
char last{before->end()[-1]};
179+
char first{after.begin()[0]};
180+
// Apply basic sanity checking to pasting so avoid constructing a bogus
181+
// token that might cause macro replacement to fail, like "macro(".
182+
if (IsLegalInIdentifier(last) && IsLegalInIdentifier(first)) {
183+
doPaste = true;
184+
} else if (IsDecimalDigit(first) &&
185+
(last == '.' || last == '+' || last == '-')) {
186+
doPaste = true; // 1. ## 0, - ## 1
187+
} else if (before->size() == 1 && after.size() == 1) {
188+
if (first == last &&
189+
(last == '<' || last == '>' || last == '*' || last == '/' ||
190+
last == '=' || last == '&' || last == '|' || last == ':')) {
191+
// Fortran **, //, ==, ::
192+
// C <<, >>, &&, || for use in #if expressions
193+
doPaste = true;
194+
} else if (first == '=' && (last == '!' || last == '/')) {
195+
doPaste = true; // != and /=
196+
}
197+
}
198+
if (doPaste) {
199+
result.ReopenLastToken();
171200
}
172-
} else if (pasting && text.TokenAt(j).IsBlank()) {
173-
} else {
174201
result.AppendRange(text, j, 1);
175-
pasting = false;
202+
before.reset();
176203
}
177204
}
178205
return result;

flang/test/Preprocessing/bug1126.F90

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
! RUN: %flang -E %s 2>&1 | FileCheck %s
2+
#define STRINGIFY(x) #x
3+
#define TOSTRING(x) STRINGIFY(x)
4+
#define PREFIX(x) prefix ## x
5+
#define NAME(x) PREFIX(foo ## x)
6+
#define AUGMENT(x) NAME(x ## suffix)
7+
8+
! CHECK: subroutine prefixfoosuffix()
9+
! CHECK: print *, "prefixfoosuffix"
10+
! CHECK: end subroutine prefixfoosuffix
11+
subroutine AUGMENT()()
12+
print *, TOSTRING(AUGMENT())
13+
end subroutine AUGMENT()
14+
15+
! CHECK: subroutine prefixfoobarsuffix()
16+
! CHECK: print *, "prefixfoobarsuffix"
17+
! CHECK: end subroutine prefixfoobarsuffix
18+
subroutine AUGMENT(bar)()
19+
print *, TOSTRING(AUGMENT(bar))
20+
end subroutine AUGMENT(bar)

0 commit comments

Comments
 (0)