diff --git a/simplecpp.cpp b/simplecpp.cpp index 4b24e311..c042de7d 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2453,16 +2453,61 @@ namespace simplecpp { output.deleteToken(A); 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); + // If strAB names a function-like macro, locate and append its argument list '(...)' + // from the remaining replacement tokens, then expand the whole call. + // 'forwardScan' is true when the argument list was consumed via the forward scan + // path below (not adjacent), so that expandToken() is called instead of + // the normal takeTokens() path even when expandResult is false. + 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) { + // Fast path: '(' is the very next token after B on the same line. + const Token *lpar = (sameline(B, B->next) && B->next->op == '(') ? B->next : nullptr; + if (!lpar && !expandResult) { + // Forward-scan path: '(' is not immediately adjacent to B. + // This handles PAR-style indirection patterns such as: + // #define PAR(a, ...) a __VA_ARGS__ + // #define DISPATCH(kind, ...) PAR(PREFIX_ ## kind, (__VA_ARGS__)) + // where the '(' for PREFIX_kind belongs to the __VA_ARGS__ parameter + // and is separated from B by a comma in the replacement text. + // Only active in the appendTokens context (expandResult==false) to + // avoid unintended side-effects inside the main expansion loop. + for (const Token *scan = nextTok; sameline(B, scan); scan = scan->next) { + if (scan->op == '(') { + // Found a literal '(' after skipping separators. + lpar = scan; + forwardScan = true; + break; + } + if (scan->op == ',') { + // Argument separator — skip and keep scanning. + continue; + } + if (scan->name) { + // Named token: expand it and check whether it starts with '(...)' + // (covers the case where __VA_ARGS__ expands to a parenthesised list). + 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 at any name token, whether consumed or not + } + break; // any 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.cpp b/test.cpp index 247f63e1..4e380337 100644 --- a/test.cpp +++ b/test.cpp @@ -2038,6 +2038,26 @@ static void hashhash_universal_character_2() ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\U0104' and '0104' yields universal character '\\U01040104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); } +// Regression test: ## result is a function-like macro whose '(' is not adjacent. +// PAR-style indirection hides '(' behind a comma/parameter in the replacement text. +// Previously caused [unknownMacro] and aborted TU expansion. +static void hashhash_funclike_par_indirection() +{ + // Single dispatch: PREFIX_ ## kind → PREFIX_SCALAR, '(' separated by ',' + ASSERT_EQUALS("\n\n\nint x", + preprocess("#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")); + + // Chained: two DISPATCH calls with different PREFIX_ specialisations + ASSERT_EQUALS("\n\n\n\nint x float arr [ 10 ]", + preprocess("#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")); +} static void has_include_1() { @@ -4669,6 +4689,7 @@ static void runTests(int argc, char **argv, Input input) // the behavior is undefined." TEST_CASE(hashhash_universal_character); TEST_CASE(hashhash_universal_character_2); + TEST_CASE(hashhash_funclike_par_indirection); // PAR-style ## indirection: '(' not adjacent to ## result // c++17 __has_include TEST_CASE(has_include_1);