Skip to content

Fix: simplecpp ## fails to expand function-like macro when '(' is not adjacent#655

Open
paulafy6 wants to merge 2 commits into
cppcheck-opensource:masterfrom
paulafy6:fix-preprocessor-concat
Open

Fix: simplecpp ## fails to expand function-like macro when '(' is not adjacent#655
paulafy6 wants to merge 2 commits into
cppcheck-opensource:masterfrom
paulafy6:fix-preprocessor-concat

Conversation

@paulafy6
Copy link
Copy Markdown

@paulafy6 paulafy6 commented May 27, 2026

When the ## operator concatenates two tokens to form a function-like macro name (e.g. PREFIX_ ## kind → PREFIX_SCALAR), simplecpp looked for the argument list '(...)' only at B->next. In PAR-style indirection patterns the '(' is separated from B by a comma or a variadic parameter token:

  #define PAR(a, ...) a __VA_ARGS__
  #define PREFIX_SCALAR(T, N) T N
  #define DISPATCH(kind, ...) PAR(PREFIX_ ## kind, (__VA_ARGS__))
  DISPATCH(SCALAR, int, x)   // was: [unknownMacro] — now: int x

Because '(' was not found, expansion was aborted and the macro was reported as unknownMacro, causing cppcheck to skip the entire translation unit.

Fix: when B->next is not '(' and we are in the appendTokens context (expandResult==false), walk forward on the same line skipping ',' separators and resolving named parameter tokens via expandArg(). The first '(' found (literally or as the head of an expanded argument) is used as lpar and passed to appendTokens() as before. The forwardScan flag ensures expandToken() is called on the result even when expandResult is false.

The forward scan is restricted to expandResult==false to avoid unintended side-effects in the main expansion loop.

… adjacent

When the ## operator concatenates two tokens to form a function-like macro
name (e.g. PREFIX_ ## kind → PREFIX_SCALAR), simplecpp looked for the
argument list '(...)' only at B->next. In PAR-style indirection patterns
the '(' is separated from B by a comma or a variadic parameter token:

  #define PAR(a, ...) a __VA_ARGS__
  #define PREFIX_SCALAR(T, N) T N
  #define DISPATCH(kind, ...) PAR(PREFIX_ ## kind, (__VA_ARGS__))
  DISPATCH(SCALAR, int, x)   // was: [unknownMacro] — now: int x

Because '(' was not found, expansion was aborted and the macro was
reported as unknownMacro, causing cppcheck to skip the entire translation
unit.

Fix: when B->next is not '(' and we are in the appendTokens context
(expandResult==false), walk forward on the same line skipping ',' separators
and resolving named parameter tokens via expandArg(). The first '(' found
(literally or as the head of an expanded argument) is used as lpar and
passed to appendTokens() as before. The forwardScan flag ensures
expandToken() is called on the result even when expandResult is false.

The forward scan is restricted to expandResult==false to avoid unintended
side-effects in the main expansion loop.
@danmar
Copy link
Copy Markdown
Collaborator

danmar commented May 27, 2026

Thanks for the contribution!

…like_par_indirection)

Covers the fix in expandHashHash(): when ## concatenation produces a
function-like macro name but '(' is not immediately adjacent in the
replacement text (hidden behind a comma/parameter), the forward scan
must locate '(' and complete the expansion.

  #define PAR(a, ...) a __VA_ARGS__
  #define PREFIX_SCALAR(T, N) T N
  #define DISPATCH(kind, ...) PAR(PREFIX_ ## kind, (__VA_ARGS__))
  DISPATCH(SCALAR, int, x)   // expected: int x
@firewave
Copy link
Copy Markdown
Collaborator

No need to repeat the whole issue description and what the fix does in the PR message as well.

I know AI loves to do that but it often gets out-of-sync if the code in the PR or the issue description changes (it did in this case) and might end up misleading (redundancies are never great).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants