aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Donchevskii <ivan.donchevskii@qt.io>2019-01-10 14:44:56 +0100
committerErik Verbruggen <erik.verbruggen@qt.io>2019-01-17 08:52:40 +0000
commit645bbf5dd6f96835f289aca1b628370813aac692 (patch)
tree37651c4e93ac8b1d4df24a2481124cd88461817f
parent66dc5e971399fbd7bf696344698b5bd3fb05b819 (diff)
CPlusPlus: Block function-like macro name for arguments expansion
Arguments of funstion-like macro may contain the name of this macro. The attempt to expand it results into infinite recursion. Patch solves that by saving the macro name until the arguments are collected to determine that it should not be expanded. Fixes: QTCREATORBUG-21642 Change-Id: Iafb404ecd3959a2f1011c12c1c3f1c0c54ed3547 Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io> Reviewed-by: Eike Ziller <eike.ziller@qt.io> Reviewed-by: Erik Verbruggen <erik.verbruggen@qt.io>
-rw-r--r--src/libs/cplusplus/pp-engine.cpp34
-rw-r--r--src/libs/cplusplus/pp-engine.h3
-rw-r--r--tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp57
3 files changed, 83 insertions, 11 deletions
diff --git a/src/libs/cplusplus/pp-engine.cpp b/src/libs/cplusplus/pp-engine.cpp
index 5ee8298a72..0050a2fd84 100644
--- a/src/libs/cplusplus/pp-engine.cpp
+++ b/src/libs/cplusplus/pp-engine.cpp
@@ -52,6 +52,7 @@
#include <cplusplus/Literals.h>
#include <cplusplus/cppassert.h>
+#include <utils/executeondestruction.h>
#include <utils/scopedswap.h>
#include <QDebug>
@@ -161,6 +162,7 @@ namespace Internal {
struct TokenBuffer
{
std::deque<PPToken> tokens;
+ std::vector<QByteArray> 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<QVector<PPToken> > 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<QVector<PPToken> > *actuals)
+bool Preprocessor::collectActualArguments(PPToken *tk, QVector<QVector<PPToken> > *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 19935b2c7d..fb9cdda2b2 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<QVector<PPToken> > *actuals);
+ bool collectActualArguments(PPToken *tk, QVector<QVector<PPToken> > *actuals,
+ const QByteArray &parentMacroName);
void scanActualArgument(PPToken *tk, QVector<PPToken> *tokens);
void handlePreprocessorDirective(PPToken *tk);
diff --git a/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp b/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp
index 2b4b6cd97a..e00adbfe53 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("<stdin>"), input);
+ qDebug() << prep;
+ const QByteArray output = "# 1 \"<stdin>\"\n";
+ // Check that it does not crash.
+ QVERIFY(prep.contains(output));
+}
+
void tst_Preprocessor::excessive_nesting()
{
Environment env;