Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion run-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ def cleanup(out: str) -> str:
'macro_rescan_varargs.c',

# todo, high priority
'c99-6_10_3_4_p5.c',
'c99-6_10_3_4_p6.c',
'expr_usual_conversions.c', # condition is true: 4U - 30 >= 0
'stdint.c',
Expand Down
55 changes: 44 additions & 11 deletions simplecpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2174,24 +2174,34 @@ namespace simplecpp {
return &it->second;
}

const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap &macros, const std::set<TokenString> &expandedmacros, const std::vector<const Token*> &parametertokens) const {
// Expand while the expansion result ends with the name of a function-like
// macro whose arguments are supplied by the tokens that follow it. Each round
// consumes that macro call from the token stream, so tok always advances.
/** Expand while the expansion result in @p temp ends with the name of a
* function-like macro whose arguments are supplied by the tokens after @p tok.
* Each round consumes that macro call from the token stream, so tok always

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that "so tok always advances" will be true now. If rescanMacro returns false directly should we return tok->next?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated it to return tok->next instead relying on the callers to call tok->next.

* advances. The @p gatherCall callback copies the "( ... )" tokens that follow
* @p lpar into its output list and returns the matching ')', or nullptr when no
* complete call is available. Returns the last consumed token. */
template<class GatherCall>
const Token *expandTrailingCalls(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap &macros, const std::set<TokenString> &expandedmacros, GatherCall gatherCall) const {
while (const Macro * const calledMacro = rescanMacro(temp, tok, macros, expandedmacros)) {
TokenList temp2(files);
temp2.push_back(new Token(temp.cback()->str(), tok->location));
temp2.push_back(new Token(temp.cback()->str(), tok->next->location));

const Token * const tok2 = appendTokens(temp2, loc, tok->next, macros, expandedmacros, parametertokens);
if (!tok2)
const Token * const closingPar = gatherCall(temp2, tok->next);
if (!closingPar)
break;
output.takeTokens(temp);
output.deleteToken(output.back());
calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros);
tok = tok2;
tok = closingPar;
}
output.takeTokens(temp);
return tok->next;
return tok;
}

const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap &macros, const std::set<TokenString> &expandedmacros, const std::vector<const Token*> &parametertokens) const {
return expandTrailingCalls(output, temp, loc, tok, macros, expandedmacros, [&](TokenList &temp2, const Token *lpar) {
return appendTokens(temp2, loc, lpar, macros, expandedmacros, parametertokens);
})->next;
}

const Token *expandToken(TokenList &output, const Location &loc, const Token *tok, const MacroMap &macros, const std::set<TokenString> &expandedmacros, const std::vector<const Token*> &parametertokens) const {
Expand Down Expand Up @@ -2310,12 +2320,35 @@ namespace simplecpp {
return false;
if (variadic && argnr + 1U >= parametertokens.size()) // empty variadic parameter
return true;
for (const Token *partok = parametertokens[argnr]->next; partok != parametertokens[argnr + 1U];) {
const Token * const argEnd = parametertokens[argnr + 1U];
for (const Token *partok = parametertokens[argnr]->next; partok != argEnd;) {
const MacroMap::const_iterator it = macros.find(partok->str());
if (it != macros.end() && !partok->isExpandedFrom(&it->second) && (partok->str() == name() || expandedmacros.find(partok->str()) == expandedmacros.end())) {
std::set<TokenString> expandedmacros2(expandedmacros); // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation
expandedmacros2.erase(name());
partok = it->second.expand(output, loc, partok, macros, std::move(expandedmacros2));
TokenList temp(files);
partok = it->second.expand(temp, loc, partok, macros, std::move(expandedmacros2));
if (partok->op == '(' && temp.cback() && temp.cback()->name) {
// the expansion result may end with the name of a function-like
// macro whose arguments are supplied by the remaining argument tokens
std::set<TokenString> expandedmacros3(expandedmacros);
expandedmacros3.erase(name());
partok = expandTrailingCalls(output, temp, loc, partok->previous, macros, expandedmacros3, [&](TokenList &temp2, const Token *lpar) -> const Token * {
unsigned int par = 0;
for (const Token *tok2 = lpar; tok2 != argEnd; tok2 = tok2->next) {
temp2.push_back(new Token(*tok2));
if (tok2->op == '(') {
++par;
} else if (tok2->op == ')') {
if (--par == 0U)
return tok2;
}
}
return nullptr;
})->next;
} else {
output.takeTokens(temp);
}
} else {
output.push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok));
output.back()->macro = partok->macro;
Expand Down
17 changes: 17 additions & 0 deletions test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,22 @@ static void define26()
"f ( ) ( )", preprocess(code2));
}

static void define27()
{
// an expansion result inside a macro argument that is a function-like macro
// name must be rescanned against the remaining argument tokens
const char code[] = "#define o1(a) a()\n"
"#define b(c, d) c##d\n"
"#define l(c, d) b(c, 1)\n"
"#define m(e, f)\n"
"#define g(h, i) m(h, )\n"
"#define j(h, i) g(h, )\n"
"#define k() l(j(, ), )\n"
"#define n(a) j(l(o, )(a), )\n"
"n(k)\n";
ASSERT_EQUALS("", preprocess(code));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

The only new test is a large, fuzzer-style case whose expected output is "", so it doesn't show what behaviour it protects. Please add a few direct cases, for example with #define f(x) [x], #define ID(x) x and #define G() f:

  • ID(G()(1)) → [ 1 ]
  • ID(G()(G()(2))) → [ [ 2 ] ]
  • #define A(x) B / #define B(x) A with ID(A(1)(2)(3)(4)) → A (each round must use the right set of already-expanded macros)

I checked all three against gcc, and they pass with this PR and fail on base.

}


static void define_invalid_1()
{
Expand Down Expand Up @@ -4563,6 +4579,7 @@ static void runTests(int argc, char **argv, Input input)
TEST_CASE(define24);
TEST_CASE(define25);
TEST_CASE(define26);
TEST_CASE(define27);
TEST_CASE(define_invalid_1);
TEST_CASE(define_invalid_2);
TEST_CASE(define_invalid_3);
Expand Down
Loading