diff --git a/src/libs/cplusplus/pp-engine.cpp b/src/libs/cplusplus/pp-engine.cpp index 5ee8298a724..0050a2fd847 100644 --- a/src/libs/cplusplus/pp-engine.cpp +++ b/src/libs/cplusplus/pp-engine.cpp @@ -52,6 +52,7 @@ #include #include +#include #include #include @@ -161,6 +162,7 @@ namespace Internal { struct TokenBuffer { std::deque tokens; + std::vector blockedMacroNames; const Macro *macro; TokenBuffer *next; @@ -172,10 +174,14 @@ struct TokenBuffer if (!macro) return false; - for (const TokenBuffer *it = this; it; it = it->next) - if (it->macro) - if (it->macro == macro || (it->macro->name() == macro->name())) - return true; + for (const TokenBuffer *it = this; it; it = it->next) { + if (it->macro && (it->macro == macro || it->macro->name() == macro->name())) + return true; + } + for (const QByteArray &blockedMacroName : blockedMacroNames) { + if (macro->name() == blockedMacroName) + return true; + } return false; } }; @@ -956,9 +962,7 @@ bool Preprocessor::handleIdentifier(PPToken *tk) Macro *macro = m_env->resolve(macroNameRef); if (!macro - || (tk->expanded() - && m_state.m_tokenBuffer - && m_state.m_tokenBuffer->isBlocked(macro))) { + || (tk->expanded() && m_state.m_tokenBuffer && m_state.m_tokenBuffer->isBlocked(macro))) { return false; } // qDebug() << "expanding" << macro->name() << "on line" << tk->lineno; @@ -991,7 +995,7 @@ bool Preprocessor::handleIdentifier(PPToken *tk) // Collect individual tokens that form the macro arguments. QVector > allArgTks; - bool hasArgs = collectActualArguments(tk, &allArgTks); + bool hasArgs = collectActualArguments(tk, &allArgTks, macro->name()); // Check whether collecting arguments failed due to a previously added marker // that goot nested in a sequence of expansions. If so, store it and try again. @@ -1001,7 +1005,7 @@ bool Preprocessor::handleIdentifier(PPToken *tk) && (m_state.m_expansionStatus == Expanding || m_state.m_expansionStatus == ReadyForExpansion)) { oldMarkerTk = *tk; - hasArgs = collectActualArguments(tk, &allArgTks); + hasArgs = collectActualArguments(tk, &allArgTks, macro->name()); } // Check for matching parameter/argument count. @@ -1498,11 +1502,21 @@ bool Preprocessor::consumeComments(PPToken *tk) return tk->isNot(T_EOF_SYMBOL); } -bool Preprocessor::collectActualArguments(PPToken *tk, QVector > *actuals) +bool Preprocessor::collectActualArguments(PPToken *tk, QVector > *actuals, + const QByteArray &parentMacroName) { Q_ASSERT(tk); Q_ASSERT(actuals); + ExecuteOnDestruction removeBlockedName; + if (m_state.m_tokenBuffer) { + removeBlockedName.reset([this] { + if (m_state.m_tokenBuffer && !m_state.m_tokenBuffer->blockedMacroNames.empty()) + m_state.m_tokenBuffer->blockedMacroNames.pop_back(); + }); + m_state.m_tokenBuffer->blockedMacroNames.push_back(parentMacroName); + } + lex(tk); // consume the identifier bool lastCommentIsCpp = false; diff --git a/src/libs/cplusplus/pp-engine.h b/src/libs/cplusplus/pp-engine.h index 19935b2c7d9..fb9cdda2b20 100644 --- a/src/libs/cplusplus/pp-engine.h +++ b/src/libs/cplusplus/pp-engine.h @@ -220,7 +220,8 @@ private: bool scanComment(PPToken *tk); bool consumeComments(PPToken *tk); - bool collectActualArguments(PPToken *tk, QVector > *actuals); + bool collectActualArguments(PPToken *tk, QVector > *actuals, + const QByteArray &parentMacroName); void scanActualArgument(PPToken *tk, QVector *tokens); void handlePreprocessorDirective(PPToken *tk); diff --git a/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp b/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp index 2b4b6cd97a0..e00adbfe535 100644 --- a/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp +++ b/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp @@ -406,6 +406,7 @@ private slots: void excessive_nesting(); void multi_byte_code_point_in_expansion(); void trigraph(); + void nested_arguments_expansion(); }; // Remove all #... lines, and 'simplify' string, to allow easily comparing the result @@ -2044,6 +2045,62 @@ void tst_Preprocessor::concat() QVERIFY(compare(prep, output)); } +void tst_Preprocessor::nested_arguments_expansion() +{ + Environment env; + Preprocessor preprocess(nullptr, &env); + QByteArray input = "#define LPL_CAT_IMPL(x, y) x##y\n" + "#define LPL_CAT(x, y) LPL_CAT_IMPL(x, y)\n" + "#define LPL_COMPL_1 0\n" + "#define LPL_COMPL_0 1\n" + "#define LPL_COMPL(x) LPL_CAT(LPL_COMPL_, x)\n" + "#define LPL_DEC_1 0\n" + "#define LPL_DEC_2 1\n" + "#define LPL_DEC(x) LPL_CAT(LPL_DEC_, x)\n" + "#define LPL_CHECK_IMPL(unused, n, ...) n\n" + "#define LPL_CHECK(expressionToTest) LPL_CHECK_IMPL(expressionToTest, 0, 0)\n" + "#define LPL_IS_0_0 LPL_PROBE\n" + "#define LPL_IS_0(x) LPL_CHECK(LPL_CAT(LPL_IS_0_, x))\n" + "#define LPL_IS_NOT_0(x) LPL_COMPL(LPL_IS_0(x))\n" + "#define EMPTY()\n" + "#define LPL_EXPAND(id) id\n" + "#define LPL_DEFER(id) id EMPTY()\n" + "#define LPL_DEFER_TWICE(id) id EMPTY EMPTY()()\n" + "#define LPL_EVAL1(id) LPL_EXPAND(LPL_EXPAND(id))\n" + "#define LPL_EVAL2(id) LPL_EVAL1(LPL_EVAL1(id))\n" + "#define LPL_EVAL3(id) LPL_EVAL2(LPL_EVAL2(id))\n" + "#define LPL_EVAL4(id) LPL_EVAL3(LPL_EVAL3(id))\n" + "#define LPL_EVAL5(id) LPL_EVAL4(LPL_EVAL4(id))\n" + "#define LPL_EVAL6(id) LPL_EVAL5(LPL_EVAL5(id))\n" + "#define LPL_EVAL(id) LPL_EVAL6(LPL_EVAL6(id))\n" + "#define LPL_IF_0(t, f) f\n" + "#define LPL_IF_1(t, f) t\n" + "#define LPL_IF(c) LPL_CAT(LPL_IF_, LPL_IS_NOT_0(c))\n" + "#define LPL_WHEN(c) LPL_IF(c)(LPL_EXPAND, LPL_EAT)\n" + "#define LPL_WHILE_IMPL(x, predicat, macroToApply) \\\n" + " LPL_WHEN(predicat(x)) \\\n" + " (x LPL_DEFER_TWICE(LPL_WHILE_IMPL_I)()(macroToApply(x), predicat, \\\n" + " macroToApply))\n" + "#define LPL_WHILE_IMPL_I() LPL_WHILE_IMPL\n" + "#define LPL_WHILE(x, predicat, macroToApply) \\\n" + " LPL_EVAL(LPL_WHILE_IMPL(x, predicat, macroToApply))\n" + "#define LPL_AND_IMPL_TREAT_PARENTHESIS_1(x, ...) 1\n" + "#define LPL_AND_IMPL_TREAT_PARENTHESIS_0(x, ...) \\\n" + " LPL_IF(LPL_IS_0(x))(0, LPL_DEFER_TWICE(LPL_AND_IMPL_I)()(__VA_ARGS__))\n" + "#define LPL_AND_IMPL(x, ...) \\\n" + " LPL_CAT(LPL_AND_IMPL_TREAT_PARENTHESIS_, LPL_IS_PARENTHESIS(x)) \\\n" + " (x, __VA_ARGS__)\n" + "#define LPL_AND_IMPL_I() LPL_AND_IMPL\n" + "#define LPL_AND(...) LPL_EVAL(LPL_AND_IMPL(__VA_ARGS__, (), 0))\n" + "LPL_WHILE(2, LPL_IS_NOT_0, LPL_DEC);"; + + QByteArray prep = preprocess.run(QLatin1String(""), input); + qDebug() << prep; + const QByteArray output = "# 1 \"\"\n"; + // Check that it does not crash. + QVERIFY(prep.contains(output)); +} + void tst_Preprocessor::excessive_nesting() { Environment env;