Skip to content

Add a macro expander that uses hide sets #742

Open
4 of 5 issues completed
Open

Description

@danmar

Part of the macro expansion rewrite (#708, step 4).

Problems with the current macro expansion

The standard describes macro expansion in translation phase 4 (C11 6.10.3.4, C++ [cpp.rescan]): the replacement list is rescanned together with the rest of the source, and a macro name found inside its own expansion is not replaced. simplecpp's Macro::expand() doesn't model these rules directly. It approximates them, and that causes bugs and makes the code hard to change:

  • No model of "painted" tokens. Recursion is prevented with an expandedmacros set that is copied down the call stack, together with per-token "expanded from" markers. A "temporary amnesia" step removes the current macro from the set again while its arguments are expanded.
  • The same rule in three places. "The expansion ends with the name of a function-like macro and its arguments follow in the source" is implemented three times: in the public Macro::expand(), in expandTrailingCalls(), and in a callback in expandArg().
  • Arguments are expanded each time they are used, not once before substitution. For example, macro usage is reported twice for B(A) with #define B(x) x A.
  • Hard to change. It is about 1000 lines, and fixes are added as special cases.

Wrong results that follow from this:

Input gcc simplecpp
#define f(x) x / f(f)(1) f(1) 1
#define R() / R at the end of the file R error: wrong number of parameters
ELLIP_FUNC(F LPAREN 'a', 'b' RPAREN) (clang macro_rescan_varargs.c) 'a' + 'b' F ( 'a' , 'b' )

Proposed solution

Use the established algorithm for this: Prosser's hide-set algorithm, as used by mcpp, ucpp and chibicc. It implements the standard's rules directly:

  • Each token has a hide set: the macros it must not be expanded into.
  • An object-like macro adds itself to the hide set of its result. For a function-like macro, the result's hide set is the intersection of the hide sets of the name and the ), plus the macro.
  • Each argument is fully expanded once, except where it is an operand of # or ##.
  • The result is pushed back onto the input and scanned again. A call whose arguments follow in the source is found by normal rescanning, so no special code is needed for it.

The new expander is written next to the old one and selected with DUI::hideSetExpansion, so both can be compared on real code and in cppcheck before the old one is removed. Functions are broken out of the old code and shared where possible (argument splitting and count check, __FILE__/__LINE__/__COUNTER__, defined, #, the ## checks).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions