From 7c7c9aac666990649100c0abe9f1ba4fe336a40f Mon Sep 17 00:00:00 2001 From: Paula Fernandez Date: Tue, 26 May 2026 14:10:27 +0200 Subject: [PATCH] Fix: simplecpp ## fails to expand function-like macro when ( is not adjacent --- externals/simplecpp/simplecpp.cpp | 49 +++++++++++++++++++++++++++---- test/testpreprocessor.cpp | 21 +++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/externals/simplecpp/simplecpp.cpp b/externals/simplecpp/simplecpp.cpp index 7afc17ab735..4abfb1e0eac 100644 --- a/externals/simplecpp/simplecpp.cpp +++ b/externals/simplecpp/simplecpp.cpp @@ -2377,15 +2377,54 @@ namespace simplecpp { TokenList tokens(files); tokens.push_back(new Token(strAB, tok->location)); // for function like macros, push the (...) - if (tokensB.empty() && sameline(B,B->next) && B->next->op=='(') { - const MacroMap::const_iterator it = macros.find(strAB); - if (it != macros.end() && expandedmacros.find(strAB) == expandedmacros.end() && it->second.functionLike()) { - const Token * const tok2 = appendTokens(tokens, loc, B->next, macros, expandedmacros, parametertokens); + bool forwardScan = false; + const MacroMap::const_iterator it = macros.find(strAB); + const bool isFunctionLikeMacro = (it != macros.end() && expandedmacros.find(strAB) == expandedmacros.end() && it->second.functionLike()); + if (tokensB.empty() && isFunctionLikeMacro) { + // Determine where '(' for the concatenated function-like macro begins. + const Token *lpar = (sameline(B, B->next) && B->next->op == '(') ? B->next : nullptr; + if (!lpar && !expandResult) { + // Forward scan: handles patterns like PAR(PREFIX_ ## kind, (__VA_ARGS__)) + // where '(' is not immediately adjacent to B in the replacement text + // but follows comma separators or parameter tokens on the same line. + // Restricted to appendTokens context (expandResult==false) to avoid + // unintended side-effects in the main expansion loop. + const Token *scan = nextTok; + while (scan && sameline(B, scan)) { + if (scan->op == '(') { + // Literal '(' found after skipping separators + lpar = scan; + forwardScan = true; + break; + } + if (scan->op == ',') { + // Skip argument separator and keep scanning + scan = scan->next; + continue; + } + if (scan->name) { + // Named token — check if it's a parameter that expands to '(...)' + TokenList expanded(files); + if (expandArg(expanded, scan, loc, macros, expandedmacros, parametertokens) && + expanded.cfront() && expanded.cfront()->op == '(') { + for (Token *t = expanded.front(); t; t = t->next) + t->location = loc; + tokens.takeTokens(expanded); + nextTok = scan->next; + forwardScan = true; + } + break; // stop scan at any name token (consumed or not) + } + break; // other operator — stop scan + } + } + if (lpar) { + const Token * const tok2 = appendTokens(tokens, loc, lpar, macros, expandedmacros, parametertokens); if (tok2) nextTok = tok2->next; } } - if (expandResult) + if (expandResult || forwardScan) expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens); else output.takeTokens(tokens); diff --git a/test/testpreprocessor.cpp b/test/testpreprocessor.cpp index 6846d49ff30..851b026ce54 100644 --- a/test/testpreprocessor.cpp +++ b/test/testpreprocessor.cpp @@ -232,6 +232,7 @@ class TestPreprocessor : public TestFixture { TEST_CASE(preprocessor_undef); TEST_CASE(defdef); // Defined multiple times TEST_CASE(preprocessor_doublesharp); + TEST_CASE(preprocessor_doublesharp_funclike_forward_scan); // simplecpp: ## result used as function-like macro via PAR-style indirection TEST_CASE(preprocessor_include_in_str); TEST_CASE(va_args_1); //TEST_CASE(va_args_2); invalid code @@ -1355,6 +1356,26 @@ class TestPreprocessor : public TestFixture { ASSERT_EQUALS("\n\n\n\nx_y", expandMacros(filedata6, *this)); } + // Test for simplecpp forward-scan fix: when ## concatenation produces a function-like macro + // name and '(' is not immediately adjacent in the replacement text (e.g. PAR pattern), the + // forward scan must find '(' across comma separators or parameter tokens on the same line. + void preprocessor_doublesharp_funclike_forward_scan() { + // PAR pattern: PREFIX_ ## kind, (__VA_ARGS__) where '(' is separated by ',' + // Previously caused [unknownMacro] and aborted TU analysis in cppcheck. + const char filedata1[] = "#define PAR(a, ...) a __VA_ARGS__\n" + "#define PREFIX_SCALAR(T, N) T N\n" + "#define DISPATCH(kind, ...) PAR(PREFIX_ ## kind, (__VA_ARGS__))\n" + "DISPATCH(SCALAR, int, x)\n"; + ASSERT_EQUALS("\n\n\nint x", expandMacros(filedata1, *this)); + + // Chained: two DISPATCH calls in a struct context + const char filedata2[] = "#define PAR(a, ...) a __VA_ARGS__\n" + "#define PREFIX_SCALAR(T, N) T N\n" + "#define PREFIX_ARRAY(T, N, S) T N[S]\n" + "#define DISPATCH(kind, ...) PAR(PREFIX_ ## kind, (__VA_ARGS__))\n" + "DISPATCH(SCALAR, int, x) DISPATCH(ARRAY, float, arr, 10)\n"; + ASSERT_EQUALS("\n\n\n\nint x float arr [ 10 ]", expandMacros(filedata2, *this)); + } void preprocessor_include_in_str() {