Skip to content

Commit 850d42f

Browse files
authored
[flang] Handle "defined" in macro expansions (#114844)
The preprocessor implements "defined(X)" and "defined X" in if/elif directive expressions in such a way that they only work at the top level, not when they appear in macro expansions. Fix that, which is a little tricky due to the need to detect the "defined" keyword before applying any macro expansion to its argument, and add a bunch of tests. Fixes #114064.
1 parent 07e053f commit 850d42f

File tree

5 files changed

+195
-58
lines changed

5 files changed

+195
-58
lines changed

flang/include/flang/Parser/preprocessor.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ class Definition {
4848

4949
bool set_isDisabled(bool disable);
5050

51-
TokenSequence Apply(const std::vector<TokenSequence> &args, Prescanner &);
51+
TokenSequence Apply(const std::vector<TokenSequence> &args, Prescanner &,
52+
bool inIfExpression = false);
5253

5354
void Print(llvm::raw_ostream &out, const char *macroName = "") const;
5455

@@ -93,7 +94,8 @@ class Preprocessor {
9394
// behavior.
9495
std::optional<TokenSequence> MacroReplacement(const TokenSequence &,
9596
Prescanner &,
96-
std::optional<std::size_t> *partialFunctionLikeMacro = nullptr);
97+
std::optional<std::size_t> *partialFunctionLikeMacro = nullptr,
98+
bool inIfExpression = false);
9799

98100
// Implements a preprocessor directive.
99101
void Directive(const TokenSequence &, Prescanner &);
@@ -106,7 +108,8 @@ class Preprocessor {
106108

107109
CharBlock SaveTokenAsName(const CharBlock &);
108110
TokenSequence ReplaceMacros(const TokenSequence &, Prescanner &,
109-
std::optional<std::size_t> *partialFunctionLikeMacro = nullptr);
111+
std::optional<std::size_t> *partialFunctionLikeMacro = nullptr,
112+
bool inIfExpression = false);
110113
void SkipDisabledConditionalCode(
111114
const std::string &, IsElseActive, Prescanner &, ProvenanceRange);
112115
bool IsIfPredicateTrue(const TokenSequence &expr, std::size_t first,

flang/include/flang/Parser/token-sequence.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class TokenSequence {
7979
}
8080

8181
std::size_t SkipBlanks(std::size_t) const;
82+
std::optional<std::size_t> SkipBlanksBackwards(std::size_t) const;
8283

8384
// True if anything remains in the sequence at & after the given offset
8485
// except blanks and line-ending C++ and Fortran free-form comments.

flang/lib/Parser/preprocessor.cpp

Lines changed: 61 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,13 @@ static TokenSequence TokenPasting(TokenSequence &&text) {
177177
return result;
178178
}
179179

180-
TokenSequence Definition::Apply(
181-
const std::vector<TokenSequence> &args, Prescanner &prescanner) {
180+
constexpr bool IsDefinedKeyword(CharBlock token) {
181+
return token.size() == 7 && (token[0] == 'd' || token[0] == 'D') &&
182+
ToLowerCaseLetters(token.ToString()) == "defined";
183+
}
184+
185+
TokenSequence Definition::Apply(const std::vector<TokenSequence> &args,
186+
Prescanner &prescanner, bool inIfExpression) {
182187
TokenSequence result;
183188
bool skipping{false};
184189
int parenthesesNesting{0};
@@ -223,13 +228,16 @@ TokenSequence Definition::Apply(
223228
const TokenSequence *arg{&args[index]};
224229
std::optional<TokenSequence> replaced;
225230
// Don't replace macros in the actual argument if it is preceded or
226-
// followed by the token-pasting operator ## in the replacement text.
227-
if (prev == 0 || !IsTokenPasting(replacement_.TokenAt(prev - 1))) {
231+
// followed by the token-pasting operator ## in the replacement text,
232+
// or if we have to worry about "defined(X)"/"defined X" in an
233+
// #if/#elif expression.
234+
if (!inIfExpression &&
235+
(prev == 0 || !IsTokenPasting(replacement_.TokenAt(prev - 1)))) {
228236
auto next{replacement_.SkipBlanks(j + 1)};
229237
if (next >= tokens || !IsTokenPasting(replacement_.TokenAt(next))) {
230238
// Apply macro replacement to the actual argument
231-
replaced =
232-
prescanner.preprocessor().MacroReplacement(*arg, prescanner);
239+
replaced = prescanner.preprocessor().MacroReplacement(
240+
*arg, prescanner, nullptr, inIfExpression);
233241
if (replaced) {
234242
arg = &*replaced;
235243
}
@@ -301,7 +309,7 @@ void Preprocessor::Undefine(std::string macro) { definitions_.erase(macro); }
301309

302310
std::optional<TokenSequence> Preprocessor::MacroReplacement(
303311
const TokenSequence &input, Prescanner &prescanner,
304-
std::optional<std::size_t> *partialFunctionLikeMacro) {
312+
std::optional<std::size_t> *partialFunctionLikeMacro, bool inIfExpression) {
305313
// Do quick scan for any use of a defined name.
306314
if (definitions_.empty()) {
307315
return std::nullopt;
@@ -311,7 +319,7 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
311319
for (; j < tokens; ++j) {
312320
CharBlock token{input.TokenAt(j)};
313321
if (!token.empty() && IsLegalIdentifierStart(token[0]) &&
314-
IsNameDefined(token)) {
322+
(IsNameDefined(token) || (inIfExpression && IsDefinedKeyword(token)))) {
315323
break;
316324
}
317325
}
@@ -326,17 +334,17 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
326334
// replacement text and attempt to proceed. Otherwise, return, so that
327335
// the caller may try again with remaining tokens in its input.
328336
auto CompleteFunctionLikeMacro{
329-
[this, &input, &prescanner, &result, &partialFunctionLikeMacro](
330-
std::size_t after, const TokenSequence &replacement,
337+
[this, &input, &prescanner, &result, &partialFunctionLikeMacro,
338+
inIfExpression](std::size_t after, const TokenSequence &replacement,
331339
std::size_t pFLMOffset) {
332340
if (after < input.SizeInTokens()) {
333341
result.Put(replacement, 0, pFLMOffset);
334342
TokenSequence suffix;
335343
suffix.Put(
336344
replacement, pFLMOffset, replacement.SizeInTokens() - pFLMOffset);
337345
suffix.Put(input, after, input.SizeInTokens() - after);
338-
auto further{
339-
ReplaceMacros(suffix, prescanner, partialFunctionLikeMacro)};
346+
auto further{ReplaceMacros(
347+
suffix, prescanner, partialFunctionLikeMacro, inIfExpression)};
340348
if (partialFunctionLikeMacro && *partialFunctionLikeMacro) {
341349
// still not closed
342350
**partialFunctionLikeMacro += result.SizeInTokens();
@@ -357,7 +365,28 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
357365
result.Put(input, j);
358366
continue;
359367
}
368+
// Process identifier in replacement text.
360369
auto it{definitions_.find(token)};
370+
// Is in the X in "defined(X)" or "defined X" in an #if/#elif expression?
371+
if (inIfExpression) {
372+
if (auto prev{result.SkipBlanksBackwards(result.SizeInTokens())}) {
373+
bool ok{true};
374+
std::optional<std::size_t> rightParenthesis;
375+
if (result.TokenAt(*prev).OnlyNonBlank() == '(') {
376+
prev = result.SkipBlanksBackwards(*prev);
377+
rightParenthesis = input.SkipBlanks(j + 1);
378+
ok = *rightParenthesis < tokens &&
379+
input.TokenAt(*rightParenthesis).OnlyNonBlank() == ')';
380+
}
381+
if (ok && prev && IsDefinedKeyword(result.TokenAt(*prev))) {
382+
result = TokenSequence{result, 0, *prev}; // trims off "defined ("
383+
char truth{it != definitions_.end() ? '1' : '0'};
384+
result.Put(&truth, 1, allSources_.CompilerInsertionProvenance(truth));
385+
j = rightParenthesis.value_or(j);
386+
continue;
387+
}
388+
}
389+
}
361390
if (it == definitions_.end()) {
362391
result.Put(input, j);
363392
continue;
@@ -403,8 +432,8 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
403432
}
404433
std::optional<std::size_t> partialFLM;
405434
def->set_isDisabled(true);
406-
TokenSequence replaced{TokenPasting(
407-
ReplaceMacros(def->replacement(), prescanner, &partialFLM))};
435+
TokenSequence replaced{TokenPasting(ReplaceMacros(
436+
def->replacement(), prescanner, &partialFLM, inIfExpression))};
408437
def->set_isDisabled(false);
409438
if (partialFLM &&
410439
CompleteFunctionLikeMacro(j + 1, replaced, *partialFLM)) {
@@ -476,11 +505,11 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
476505
(n + 1 == argStart.size() ? k : argStart[n + 1] - 1) - at};
477506
args.emplace_back(TokenSequence(input, at, count));
478507
}
479-
TokenSequence applied{def->Apply(args, prescanner)};
508+
TokenSequence applied{def->Apply(args, prescanner, inIfExpression)};
480509
std::optional<std::size_t> partialFLM;
481510
def->set_isDisabled(true);
482-
TokenSequence replaced{
483-
ReplaceMacros(std::move(applied), prescanner, &partialFLM)};
511+
TokenSequence replaced{ReplaceMacros(
512+
std::move(applied), prescanner, &partialFLM, inIfExpression)};
484513
def->set_isDisabled(false);
485514
if (partialFLM &&
486515
CompleteFunctionLikeMacro(k + 1, replaced, *partialFLM)) {
@@ -501,9 +530,9 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
501530

502531
TokenSequence Preprocessor::ReplaceMacros(const TokenSequence &tokens,
503532
Prescanner &prescanner,
504-
std::optional<std::size_t> *partialFunctionLikeMacro) {
505-
if (std::optional<TokenSequence> repl{
506-
MacroReplacement(tokens, prescanner, partialFunctionLikeMacro)}) {
533+
std::optional<std::size_t> *partialFunctionLikeMacro, bool inIfExpression) {
534+
if (std::optional<TokenSequence> repl{MacroReplacement(
535+
tokens, prescanner, partialFunctionLikeMacro, inIfExpression)}) {
507536
return std::move(*repl);
508537
}
509538
return tokens;
@@ -1215,50 +1244,27 @@ static std::int64_t ExpressionValue(const TokenSequence &token,
12151244
return left;
12161245
}
12171246

1218-
bool Preprocessor::IsIfPredicateTrue(const TokenSequence &expr,
1247+
bool Preprocessor::IsIfPredicateTrue(const TokenSequence &directive,
12191248
std::size_t first, std::size_t exprTokens, Prescanner &prescanner) {
1220-
TokenSequence expr1{expr, first, exprTokens};
1221-
if (expr1.HasBlanks()) {
1222-
expr1.RemoveBlanks();
1223-
}
1224-
TokenSequence expr2;
1225-
for (std::size_t j{0}; j < expr1.SizeInTokens(); ++j) {
1226-
if (ToLowerCaseLetters(expr1.TokenAt(j).ToString()) == "defined") {
1227-
CharBlock name;
1228-
if (j + 3 < expr1.SizeInTokens() &&
1229-
expr1.TokenAt(j + 1).OnlyNonBlank() == '(' &&
1230-
expr1.TokenAt(j + 3).OnlyNonBlank() == ')') {
1231-
name = expr1.TokenAt(j + 2);
1232-
j += 3;
1233-
} else if (j + 1 < expr1.SizeInTokens() &&
1234-
IsLegalIdentifierStart(expr1.TokenAt(j + 1))) {
1235-
name = expr1.TokenAt(++j);
1236-
}
1237-
if (!name.empty()) {
1238-
char truth{IsNameDefined(name) ? '1' : '0'};
1239-
expr2.Put(&truth, 1, allSources_.CompilerInsertionProvenance(truth));
1240-
continue;
1241-
}
1242-
}
1243-
expr2.Put(expr1, j);
1244-
}
1245-
TokenSequence expr3{ReplaceMacros(expr2, prescanner)};
1246-
if (expr3.HasBlanks()) {
1247-
expr3.RemoveBlanks();
1249+
TokenSequence expr{directive, first, exprTokens};
1250+
TokenSequence replaced{
1251+
ReplaceMacros(expr, prescanner, nullptr, /*inIfExpression=*/true)};
1252+
if (replaced.HasBlanks()) {
1253+
replaced.RemoveBlanks();
12481254
}
1249-
if (expr3.empty()) {
1255+
if (replaced.empty()) {
12501256
prescanner.Say(expr.GetProvenanceRange(), "empty expression"_err_en_US);
12511257
return false;
12521258
}
12531259
std::size_t atToken{0};
12541260
std::optional<Message> error;
1255-
bool result{ExpressionValue(expr3, 0, &atToken, &error) != 0};
1261+
bool result{ExpressionValue(replaced, 0, &atToken, &error) != 0};
12561262
if (error) {
12571263
prescanner.Say(std::move(*error));
1258-
} else if (atToken < expr3.SizeInTokens() &&
1259-
expr3.TokenAt(atToken).ToString() != "!") {
1260-
prescanner.Say(expr3.GetIntervalProvenanceRange(
1261-
atToken, expr3.SizeInTokens() - atToken),
1264+
} else if (atToken < replaced.SizeInTokens() &&
1265+
replaced.TokenAt(atToken).ToString() != "!") {
1266+
prescanner.Say(replaced.GetIntervalProvenanceRange(
1267+
atToken, replaced.SizeInTokens() - atToken),
12621268
atToken == 0 ? "could not parse any expression"_err_en_US
12631269
: "excess characters after expression"_err_en_US);
12641270
}

flang/lib/Parser/token-sequence.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ std::size_t TokenSequence::SkipBlanks(std::size_t at) const {
6161
return tokens; // even if at > tokens
6262
}
6363

64+
std::optional<std::size_t> TokenSequence::SkipBlanksBackwards(
65+
std::size_t at) const {
66+
while (at-- > 0) {
67+
if (!TokenAt(at).IsBlank()) {
68+
return at;
69+
}
70+
}
71+
return std::nullopt;
72+
}
73+
6474
// C-style /*comments*/ are removed from preprocessing directive
6575
// token sequences by the prescanner, but not C++ or Fortran
6676
// free-form line-ending comments (//... and !...) because
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
! RUN: %flang -E %s 2>&1 | FileCheck %s
2+
3+
! CHECK: print *, 'pass 1'
4+
#define IS_DEFINED
5+
#define M1 defined(IS_DEFINED)
6+
#if M1
7+
print *, 'pass 1'
8+
#else
9+
print *, 'fail 1'
10+
#endif
11+
12+
! CHECK: print *, 'pass 2'
13+
#define M2 defined IS_DEFINED
14+
#if M2
15+
print *, 'pass 2'
16+
#else
17+
print *, 'fail 2'
18+
#endif
19+
20+
! CHECK: print *, 'pass 3'
21+
#define M3 defined(IS_UNDEFINED)
22+
#if M3
23+
print *, 'fail 3'
24+
#else
25+
print *, 'pass 3'
26+
#endif
27+
28+
! CHECK: print *, 'pass 4'
29+
#define M4 defined IS_UNDEFINED
30+
#if M4
31+
print *, 'fail 4'
32+
#else
33+
print *, 'pass 4'
34+
#endif
35+
36+
! CHECK: print *, 'pass 5'
37+
#define DEFINED_KEYWORD defined
38+
#define M5(x) DEFINED_KEYWORD(x)
39+
#define KWM1 1
40+
#if M5(KWM1)
41+
print *, 'pass 5'
42+
#else
43+
print *, 'fail 5'
44+
#endif
45+
46+
! CHECK: print *, 'pass 6'
47+
#define KWM2 KWM1
48+
#if M5(KWM2)
49+
print *, 'pass 6'
50+
#else
51+
print *, 'fail 6'
52+
#endif
53+
54+
! CHECK: print *, 'pass 7'
55+
#if M5(IS_UNDEFINED)
56+
print *, 'fail 7'
57+
#else
58+
print *, 'pass 7'
59+
#endif
60+
61+
! CHECK: print *, 'pass 8'
62+
#define KWM3 IS_UNDEFINED
63+
#if M5(KWM3)
64+
print *, 'pass 8'
65+
#else
66+
print *, 'fail 8'
67+
#endif
68+
69+
! CHECK: print *, 'pass 9'
70+
#define M6(x) defined(x)
71+
#if M6(KWM1)
72+
print *, 'pass 9'
73+
#else
74+
print *, 'fail 9'
75+
#endif
76+
77+
! CHECK: print *, 'pass 10'
78+
#if M6(KWM2)
79+
print *, 'pass 10'
80+
#else
81+
print *, 'fail 10'
82+
#endif
83+
84+
! CHECK: print *, 'pass 11'
85+
#if M6(IS_UNDEFINED)
86+
print *, 'fail 11'
87+
#else
88+
print *, 'pass 11'
89+
#endif
90+
91+
! CHECK: print *, 'pass 12'
92+
#if M6(KWM3)
93+
print *, 'pass 12'
94+
#else
95+
print *, 'fail 12'
96+
#endif
97+
98+
! CHECK: print *, 'pass 13'
99+
#define M7(A, B) ((A) * 10000 + (B) * 100)
100+
#define M8(A, B, C, AA, BB) ( \
101+
(defined(AA) && defined(BB)) && \
102+
(M7(A, B) C M7(AA, BB)))
103+
#if M8(9, 5, >, BAZ, FUX)
104+
print *, 'fail 13'
105+
#else
106+
print *, 'pass 13'
107+
#endif
108+
109+
! CHECK: print *, 'pass 14'
110+
#define M9() (defined(IS_UNDEFINED))
111+
#if M9()
112+
print *, 'fail 14'
113+
#else
114+
print *, 'pass 14'
115+
#endif
116+
117+
end

0 commit comments

Comments
 (0)