Skip to content

Commit d3e5c20

Browse files
authored
[flang] Handle preprocessor macro expansion edge case (#73835)
When a reference to a function-like macro begins during the rescanning of the expansion of another macro but is not completed by the end of that expansion, it is necessary to abort that rescanning of that expansion and try again when more tokens can be acquired. (See the new unclosed-FLM.F90 test case.) All other Fortran preprocessors to which I have access can handle this situation.
1 parent a1db874 commit d3e5c20

File tree

3 files changed

+152
-102
lines changed

3 files changed

+152
-102
lines changed

flang/lib/Parser/preprocessor.cpp

Lines changed: 133 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -259,14 +259,15 @@ void Preprocessor::Define(std::string macro, std::string value) {
259259
void Preprocessor::Undefine(std::string macro) { definitions_.erase(macro); }
260260

261261
std::optional<TokenSequence> Preprocessor::MacroReplacement(
262-
const TokenSequence &input, Prescanner &prescanner) {
262+
const TokenSequence &input, Prescanner &prescanner,
263+
std::optional<std::size_t> *partialFunctionLikeMacro) {
263264
// Do quick scan for any use of a defined name.
264265
if (definitions_.empty()) {
265266
return std::nullopt;
266267
}
267268
std::size_t tokens{input.SizeInTokens()};
268-
std::size_t j;
269-
for (j = 0; j < tokens; ++j) {
269+
std::size_t j{0};
270+
for (; j < tokens; ++j) {
270271
CharBlock token{input.TokenAt(j)};
271272
if (!token.empty() && IsLegalIdentifierStart(token[0]) &&
272273
IsNameDefined(token)) {
@@ -277,6 +278,38 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
277278
return std::nullopt; // input contains nothing that would be replaced
278279
}
279280
TokenSequence result{input, 0, j};
281+
282+
// After rescanning after macro replacement has failed due to an unclosed
283+
// function-like macro call (no left parenthesis yet, or no closing
284+
// parenthesis), if tokens remain in the input, append them to the
285+
// replacement text and attempt to proceed. Otherwise, return, so that
286+
// the caller may try again with remaining tokens in its input.
287+
auto CompleteFunctionLikeMacro{
288+
[this, &input, &prescanner, &result, &partialFunctionLikeMacro](
289+
std::size_t after, const TokenSequence &replacement,
290+
std::size_t pFLMOffset) {
291+
if (after < input.SizeInTokens()) {
292+
result.Put(replacement, 0, pFLMOffset);
293+
TokenSequence suffix;
294+
suffix.Put(
295+
replacement, pFLMOffset, replacement.SizeInTokens() - pFLMOffset);
296+
suffix.Put(input, after, input.SizeInTokens() - after);
297+
auto further{
298+
ReplaceMacros(suffix, prescanner, partialFunctionLikeMacro)};
299+
if (partialFunctionLikeMacro && *partialFunctionLikeMacro) {
300+
// still not closed
301+
**partialFunctionLikeMacro += result.SizeInTokens();
302+
}
303+
result.Put(further);
304+
return true;
305+
} else {
306+
if (partialFunctionLikeMacro) {
307+
*partialFunctionLikeMacro = pFLMOffset + result.SizeInTokens();
308+
}
309+
return false;
310+
}
311+
}};
312+
280313
for (; j < tokens; ++j) {
281314
CharBlock token{input.TokenAt(j)};
282315
if (token.IsBlank() || !IsLegalIdentifierStart(token[0])) {
@@ -294,20 +327,17 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
294327
continue;
295328
}
296329
if (!def->isFunctionLike()) {
297-
bool isRenaming{false};
298-
if (def->isPredefined()) {
330+
if (def->isPredefined() && !def->replacement().empty()) {
299331
std::string repl;
300-
if (!def->replacement().empty()) {
301-
std::string name{def->replacement().TokenAt(0).ToString()};
302-
if (name == "__FILE__") {
303-
repl = "\""s +
304-
allSources_.GetPath(prescanner.GetCurrentProvenance()) + '"';
305-
} else if (name == "__LINE__") {
306-
std::string buf;
307-
llvm::raw_string_ostream ss{buf};
308-
ss << allSources_.GetLineNumber(prescanner.GetCurrentProvenance());
309-
repl = ss.str();
310-
}
332+
std::string name{def->replacement().TokenAt(0).ToString()};
333+
if (name == "__FILE__") {
334+
repl = "\""s +
335+
allSources_.GetPath(prescanner.GetCurrentProvenance()) + '"';
336+
} else if (name == "__LINE__") {
337+
std::string buf;
338+
llvm::raw_string_ostream ss{buf};
339+
ss << allSources_.GetLineNumber(prescanner.GetCurrentProvenance());
340+
repl = ss.str();
311341
}
312342
if (!repl.empty()) {
313343
ProvenanceRange insert{allSources_.AddCompilerInsertion(repl)};
@@ -317,105 +347,109 @@ std::optional<TokenSequence> Preprocessor::MacroReplacement(
317347
continue;
318348
}
319349
}
350+
std::optional<std::size_t> partialFLM;
320351
def->set_isDisabled(true);
321-
TokenSequence replaced{
322-
TokenPasting(ReplaceMacros(def->replacement(), prescanner))};
352+
TokenSequence replaced{TokenPasting(
353+
ReplaceMacros(def->replacement(), prescanner, &partialFLM))};
323354
def->set_isDisabled(false);
324-
// Allow a keyword-like macro replacement to be the name of
325-
// a function-like macro, possibly surrounded by blanks.
326-
std::size_t k{0}, repTokens{replaced.SizeInTokens()};
327-
for (; k < repTokens && replaced.TokenAt(k).IsBlank(); ++k) {
355+
if (partialFLM &&
356+
CompleteFunctionLikeMacro(j + 1, replaced, *partialFLM)) {
357+
return result;
358+
}
359+
if (!replaced.empty()) {
360+
ProvenanceRange from{def->replacement().GetProvenanceRange()};
361+
ProvenanceRange use{input.GetTokenProvenanceRange(j)};
362+
ProvenanceRange newRange{
363+
allSources_.AddMacroCall(from, use, replaced.ToString())};
364+
result.Put(replaced, newRange);
365+
}
366+
} else {
367+
// Possible function-like macro call. Skip spaces and newlines to see
368+
// whether '(' is next.
369+
std::size_t k{j};
370+
bool leftParen{false};
371+
while (++k < tokens) {
372+
const CharBlock &lookAhead{input.TokenAt(k)};
373+
if (!lookAhead.IsBlank() && lookAhead[0] != '\n') {
374+
leftParen = lookAhead[0] == '(' && lookAhead.size() == 1;
375+
break;
376+
}
328377
}
329-
if (k < repTokens) {
330-
token = replaced.TokenAt(k);
331-
for (++k; k < repTokens && replaced.TokenAt(k).IsBlank(); ++k) {
378+
if (!leftParen) {
379+
if (partialFunctionLikeMacro) {
380+
*partialFunctionLikeMacro = result.SizeInTokens();
381+
result.Put(input, j, tokens - j);
382+
return result;
383+
} else {
384+
result.Put(input, j);
385+
continue;
332386
}
333-
if (k == repTokens && IsLegalIdentifierStart(token[0])) {
334-
auto it{definitions_.find(token)};
335-
if (it != definitions_.end() && !it->second.isDisabled() &&
336-
it->second.isFunctionLike()) {
337-
def = &it->second;
338-
isRenaming = true;
387+
}
388+
std::vector<std::size_t> argStart{++k};
389+
for (int nesting{0}; k < tokens; ++k) {
390+
CharBlock token{input.TokenAt(k)};
391+
char ch{token.OnlyNonBlank()};
392+
if (ch == '(') {
393+
++nesting;
394+
} else if (ch == ')') {
395+
if (nesting == 0) {
396+
break;
339397
}
398+
--nesting;
399+
} else if (ch == ',' && nesting == 0) {
400+
argStart.push_back(k + 1);
340401
}
341402
}
342-
if (!isRenaming) {
343-
if (!replaced.empty()) {
344-
ProvenanceRange from{def->replacement().GetProvenanceRange()};
345-
ProvenanceRange use{input.GetTokenProvenanceRange(j)};
346-
ProvenanceRange newRange{
347-
allSources_.AddMacroCall(from, use, replaced.ToString())};
348-
result.Put(replaced, newRange);
349-
}
403+
if (argStart.size() == 1 && k == argStart[0] &&
404+
def->argumentCount() == 0) {
405+
// Subtle: () is zero arguments, not one empty argument,
406+
// unless one argument was expected.
407+
argStart.clear();
408+
}
409+
if (k >= tokens && partialFunctionLikeMacro) {
410+
*partialFunctionLikeMacro = result.SizeInTokens();
411+
result.Put(input, j, tokens - j);
412+
return result;
413+
} else if (k >= tokens || argStart.size() < def->argumentCount() ||
414+
(argStart.size() > def->argumentCount() && !def->isVariadic())) {
415+
result.Put(input, j);
350416
continue;
351417
}
352-
}
353-
// Possible function-like macro call. Skip spaces and newlines to see
354-
// whether '(' is next.
355-
std::size_t k{j};
356-
bool leftParen{false};
357-
while (++k < tokens) {
358-
const CharBlock &lookAhead{input.TokenAt(k)};
359-
if (!lookAhead.IsBlank() && lookAhead[0] != '\n') {
360-
leftParen = lookAhead[0] == '(' && lookAhead.size() == 1;
361-
break;
418+
std::vector<TokenSequence> args;
419+
for (std::size_t n{0}; n < argStart.size(); ++n) {
420+
std::size_t at{argStart[n]};
421+
std::size_t count{
422+
(n + 1 == argStart.size() ? k : argStart[n + 1] - 1) - at};
423+
args.emplace_back(TokenSequence(input, at, count));
362424
}
363-
}
364-
if (!leftParen) {
365-
result.Put(input, j);
366-
continue;
367-
}
368-
std::vector<std::size_t> argStart{++k};
369-
for (int nesting{0}; k < tokens; ++k) {
370-
CharBlock token{input.TokenAt(k)};
371-
char ch{token.OnlyNonBlank()};
372-
if (ch == '(') {
373-
++nesting;
374-
} else if (ch == ')') {
375-
if (nesting == 0) {
376-
break;
377-
}
378-
--nesting;
379-
} else if (ch == ',' && nesting == 0) {
380-
argStart.push_back(k + 1);
425+
TokenSequence applied{def->Apply(args, prescanner)};
426+
std::optional<std::size_t> partialFLM;
427+
def->set_isDisabled(true);
428+
TokenSequence replaced{
429+
ReplaceMacros(std::move(applied), prescanner, &partialFLM)};
430+
def->set_isDisabled(false);
431+
if (partialFLM &&
432+
CompleteFunctionLikeMacro(k + 1, replaced, *partialFLM)) {
433+
return result;
381434
}
435+
if (!replaced.empty()) {
436+
ProvenanceRange from{def->replacement().GetProvenanceRange()};
437+
ProvenanceRange use{input.GetIntervalProvenanceRange(j, k - j)};
438+
ProvenanceRange newRange{
439+
allSources_.AddMacroCall(from, use, replaced.ToString())};
440+
result.Put(replaced, newRange);
441+
}
442+
j = k; // advance to the terminal ')'
382443
}
383-
if (argStart.size() == 1 && k == argStart[0] && def->argumentCount() == 0) {
384-
// Subtle: () is zero arguments, not one empty argument,
385-
// unless one argument was expected.
386-
argStart.clear();
387-
}
388-
if (k >= tokens || argStart.size() < def->argumentCount() ||
389-
(argStart.size() > def->argumentCount() && !def->isVariadic())) {
390-
result.Put(input, j);
391-
continue;
392-
}
393-
std::vector<TokenSequence> args;
394-
for (std::size_t n{0}; n < argStart.size(); ++n) {
395-
std::size_t at{argStart[n]};
396-
std::size_t count{
397-
(n + 1 == argStart.size() ? k : argStart[n + 1] - 1) - at};
398-
args.emplace_back(TokenSequence(input, at, count));
399-
}
400-
TokenSequence applied{def->Apply(args, prescanner)};
401-
def->set_isDisabled(true);
402-
TokenSequence replaced{ReplaceMacros(std::move(applied), prescanner)};
403-
def->set_isDisabled(false);
404-
if (!replaced.empty()) {
405-
ProvenanceRange from{def->replacement().GetProvenanceRange()};
406-
ProvenanceRange use{input.GetIntervalProvenanceRange(j, k - j)};
407-
ProvenanceRange newRange{
408-
allSources_.AddMacroCall(from, use, replaced.ToString())};
409-
result.Put(replaced, newRange);
410-
}
411-
j = k; // advance to the terminal ')'
412444
}
413445
return result;
414446
}
415447

416-
TokenSequence Preprocessor::ReplaceMacros(
417-
const TokenSequence &tokens, Prescanner &prescanner) {
418-
if (std::optional<TokenSequence> repl{MacroReplacement(tokens, prescanner)}) {
448+
TokenSequence Preprocessor::ReplaceMacros(const TokenSequence &tokens,
449+
Prescanner &prescanner,
450+
std::optional<std::size_t> *partialFunctionLikeMacro) {
451+
if (std::optional<TokenSequence> repl{
452+
MacroReplacement(tokens, prescanner, partialFunctionLikeMacro)}) {
419453
return std::move(*repl);
420454
}
421455
return tokens;

flang/lib/Parser/preprocessor.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,16 @@ class Preprocessor {
7575
bool IsNameDefined(const CharBlock &);
7676
bool IsFunctionLikeDefinition(const CharBlock &);
7777

78-
std::optional<TokenSequence> MacroReplacement(
79-
const TokenSequence &, Prescanner &);
78+
// When called with partialFunctionLikeMacro not null, MacroReplacement()
79+
// and ReplaceMacros() handle an unclosed function-like macro reference
80+
// by terminating macro replacement at the name of the FLM and returning
81+
// its index in the result. This allows the recursive call sites in
82+
// MacroReplacement to append any remaining tokens in their inputs to
83+
// that result and try again. All other Fortran preprocessors share this
84+
// behavior.
85+
std::optional<TokenSequence> MacroReplacement(const TokenSequence &,
86+
Prescanner &,
87+
std::optional<std::size_t> *partialFunctionLikeMacro = nullptr);
8088

8189
// Implements a preprocessor directive.
8290
void Directive(const TokenSequence &, Prescanner &);
@@ -86,7 +94,8 @@ class Preprocessor {
8694
enum class CanDeadElseAppear { No, Yes };
8795

8896
CharBlock SaveTokenAsName(const CharBlock &);
89-
TokenSequence ReplaceMacros(const TokenSequence &, Prescanner &);
97+
TokenSequence ReplaceMacros(const TokenSequence &, Prescanner &,
98+
std::optional<std::size_t> *partialFunctionLikeMacro = nullptr);
9099
void SkipDisabledConditionalCode(
91100
const std::string &, IsElseActive, Prescanner &, ProvenanceRange);
92101
bool IsIfPredicateTrue(const TokenSequence &expr, std::size_t first,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
! RUN: %flang -E %s | FileCheck %s
2+
#define A B(c)
3+
#define B(d) d); call E(d
4+
#define E(f) G(f)
5+
!CHECK: call I(c); call G(c)
6+
call I(A)
7+
end

0 commit comments

Comments
 (0)