Files
qt-creator/tests/auto/cplusplus/preprocessor/tst_preprocessor.cpp
Christian Kandeler 8eac3fba80 CPlusPlus: Fix lexer crash
At some point in the preprocessing stage, some tokens get "squeezed", so
their associated string no longer refers to the document content, but
only to their own symbol. However, there is at least one context that
operated under the assumption that the token's offset still pointed into
the "global" string. As the token's offset is zero after parsing, this
lead to the same piece of code being preprocessed in an infinite loop.

Fixes: QTCREATORBUG-19525
Change-Id: I231ba51811cfa0b5c6dfe7f75fe0384472252c6f
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2020-06-18 07:52:02 +00:00

2192 lines
73 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "../cplusplus_global.h"
#include <cplusplus/pp.h>
#include <QtTest>
#include <QFile>
#include <QHash>
#include <QSet>
//TESTED_COMPONENT=src/libs/cplusplus
using namespace CPlusPlus;
typedef QByteArray _;
#define DUMP_OUTPUT(x) {QByteArray b(x);qDebug("output: [[%s]]", b.replace("\n", "<<\n").constData());}
QByteArray loadSource(const QString &fileName)
{
QFile inf(QLatin1String(SRCDIR) + QLatin1Char('/') + fileName);
if (!inf.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug("Cannot open \"%s\"", fileName.toUtf8().constData());
return QByteArray();
}
QTextStream ins(&inf);
QString source = ins.readAll();
inf.close();
return source.toUtf8();
}
void saveData(const QByteArray &data, const QString &fileName)
{
QFile inf(QLatin1String(SRCDIR) + QLatin1Char('/') + fileName);
if (!inf.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug("Cannot open \"%s\"", fileName.toUtf8().constData());
return;
}
inf.write(data);
inf.close();
}
static QByteArray stringify(QByteArray code)
{
QByteArray res = code.replace('"', "\\\"").replace("\n", "\\n\"\n\"");
if (res.endsWith("\"\n\""))
res.chop(3);
return res;
}
static bool compare(const QByteArray &actual, const QByteArray &expected)
{
if (actual == expected)
return true;
qDebug() << "Compared strings are not the same\n"
" Actual:\n" << stringify(actual)
<< " Expected:\n" << stringify(expected);
return false;
}
struct Include
{
Include(const QString &fileName, Client::IncludeType type, int line)
: fileName(fileName), type(type), line(line)
{}
QString fileName;
Client::IncludeType type;
int line;
};
QDebug &operator<<(QDebug& d, const Include &i)
{
d << '[' << i.fileName
<< ',' << (i.type == Client::IncludeGlobal ? "Global"
: (i.type == Client::IncludeLocal ? "Local" : "Unknown"))
<< ',' << i.line
<< ']';
return d;
}
class MockClient : public Client
{
public:
struct Block {
Block() : start(0), end(0) {}
Block(int start) : start(start), end(0) {}
int start;
int end;
};
public:
MockClient(Environment *env, QByteArray *output)
: m_env(env)
, m_output(output)
, m_pp(this, env)
, m_includeDepth(0)
{}
virtual ~MockClient() {}
virtual void macroAdded(const Macro & macro)
{
m_definedMacros.append(macro.name());
m_definedMacrosLine.append(macro.line());
}
virtual void passedMacroDefinitionCheck(int /*bytesOffset*/,
int /*utf16charsOffset*/,
int line,
const Macro &macro)
{
m_definitionsResolvedFromLines[macro.name()].append(line);
}
virtual void failedMacroDefinitionCheck(int /*offset*/,
int /*utf16charsOffset*/,
const ByteArrayRef &name)
{
m_unresolvedDefines.insert(name.toByteArray());
}
virtual void notifyMacroReference(int bytesOffset, int /*utf16charsOffset*/,
int line, const Macro &macro)
{
m_macroUsesLine[macro.name()].append(line);
m_expandedMacrosOffset.append(bytesOffset);
}
virtual void startExpandingMacro(int bytesOffset,
int /*utf16charsOffset*/,
int line,
const Macro &macro,
const QVector<MacroArgumentReference> &actuals
= QVector<MacroArgumentReference>())
{
m_expandedMacros.append(macro.name());
m_expandedMacrosOffset.append(bytesOffset);
m_macroUsesLine[macro.name()].append(line);
m_macroArgsCount.append(actuals.size());
m_usedMacros.insert(macro.name(), actuals);
}
virtual void stopExpandingMacro(int /*offset*/, const Macro &/*macro*/) {}
virtual void startSkippingBlocks(int utf16charsOffset)
{ m_skippedBlocks.append(Block(utf16charsOffset)); }
virtual void stopSkippingBlocks(int utf16charsOffset)
{ m_skippedBlocks.last().end = utf16charsOffset; }
virtual void sourceNeeded(int line, const QString &includedFileName, IncludeType mode,
const QStringList &initialIncludes = QStringList())
{
Q_UNUSED(initialIncludes)
#if 1
m_recordedIncludes.append(Include(includedFileName, mode, line));
Q_UNUSED(m_env)
Q_UNUSED(m_includeDepth)
#else
Q_UNUSED(line)
QString resolvedFileName;
if (mode == IncludeLocal)
resolvedFileName = resolveLocally(m_env->currentFile, includedFileName);
else
resolvedFileName = resolveGlobally(includedFileName);
// qDebug("resolved [[%s]] to [[%s]] from [[%s]] (%s)\n",
// includedFileName.toUtf8().constData(),
// resolvedFileName.toUtf8().constData(),
// currentFileName.toUtf8().constData(),
// (mode == IncludeLocal) ? "locally" : "globally");
if (resolvedFileName.isEmpty())
return;
++m_includeDepth;
// qDebug("%5d %s %s", m_includeDepth, QByteArray(m_includeDepth, '+').constData(), resolvedFileName.toUtf8().constData());
sourceNeeded(resolvedFileName);
--m_includeDepth;
#endif
}
QString resolveLocally(const QString &currentFileName,
const QString &includedFileName) const
{
QDir dir;
if (currentFileName.isEmpty())
dir = QDir::current();
else
dir = QFileInfo(currentFileName).dir();
const QFileInfo inc(dir, includedFileName);
if (inc.exists()) {
return inc.filePath();
} else {
return QString();
}
}
QString resolveGlobally(const QString &currentFileName) const
{
foreach (const QDir &dir, m_includePaths) {
QFileInfo f(dir, currentFileName);
if (f.exists())
return f.filePath();
}
return QString();
}
void setIncludePaths(const QStringList &includePaths)
{
foreach (const QString &path, includePaths) {
QDir dir(path);
if (dir.exists())
m_includePaths.append(dir);
}
}
void sourceNeeded(const QString &fileName, bool nolines)
{
QByteArray src = loadSource(fileName);
QVERIFY(!src.isEmpty());
*m_output = m_pp.run(fileName, src, nolines, true);
}
virtual void markAsIncludeGuard(const QByteArray &macroName)
{ m_includeGuardMacro = macroName; }
QByteArray includeGuard() const
{ return m_includeGuardMacro; }
QList<Block> skippedBlocks() const
{ return m_skippedBlocks; }
QList<Include> recordedIncludes() const
{ return m_recordedIncludes; }
QList<QByteArray> expandedMacros() const
{ return m_expandedMacros; }
QList<int> expandedMacrosOffset() const
{ return m_expandedMacrosOffset; }
QList<QByteArray> definedMacros() const
{ return m_definedMacros; }
QList<int> definedMacrosLine() const
{ return m_definedMacrosLine; }
QHash<QByteArray, QList<int> > macroUsesLine() const
{ return m_macroUsesLine; }
QHash<QByteArray, QList<int> > definitionsResolvedFromLines() const
{ return m_definitionsResolvedFromLines; }
QSet<QByteArray> unresolvedDefines() const
{ return m_unresolvedDefines; }
const QList<int> macroArgsCount() const
{ return m_macroArgsCount; }
const QMap<QByteArray, QVector<MacroArgumentReference >> usedMacros() const
{ return m_usedMacros; }
private:
Environment *m_env;
QByteArray *m_output;
Preprocessor m_pp;
QList<QDir> m_includePaths;
int m_includeDepth;
QByteArray m_includeGuardMacro;
QList<Block> m_skippedBlocks;
QList<Include> m_recordedIncludes;
QList<QByteArray> m_expandedMacros;
QList<int> m_expandedMacrosOffset;
QList<QByteArray> m_definedMacros;
QList<int> m_definedMacrosLine;
QHash<QByteArray, QList<int> > m_macroUsesLine;
QHash<QByteArray, QList<int> > m_definitionsResolvedFromLines;
QSet<QByteArray> m_unresolvedDefines;
QList<int> m_macroArgsCount;
QMap<QByteArray, QVector<MacroArgumentReference >> m_usedMacros;
};
QT_BEGIN_NAMESPACE
namespace QTest {
template<> char *toString(const QList<int> &list)
{
QByteArray ba = "QList<int>(";
foreach (const int& item, list) {
ba += QTest::toString(item);
ba += ',';
}
if (!list.isEmpty())
ba[ba.size() - 1] = ')';
return qstrdup(ba.data());
}
template<> char *toString(const QList<QByteArray> &list)
{
QByteArray ba = "QList<QByteArray>(";
foreach (const QByteArray& item, list) {
ba += QTest::toString(item);
ba += ',';
}
if (!list.isEmpty())
ba[ba.size() - 1] = ')';
return qstrdup(ba.data());
}
}
QT_END_NAMESPACE
QDebug &operator<<(QDebug& d, const MockClient::Block &b)
{
d << '[' << b.start << ',' << b.end << ']';
return d;
}
class tst_Preprocessor : public QObject
{
Q_OBJECT
protected:
QByteArray preprocess(const QString &fileName, QByteArray * /*errors*/, bool nolines) {
//### TODO: hook up errors
QByteArray output;
Environment env;
MockClient client(&env, &output);
client.sourceNeeded("data/" + fileName, nolines);
return output;
}
static QByteArray simplified(const QByteArray &buf);
private:
void compare_input_output(bool keepComments = false);
private slots:
void va_args();
void named_va_args();
void extra_va_args();
void defined();
void defined_data();
void defined_usage();
void empty_macro_args();
void macro_args_count();
void macro_args_offsets();
void macro_args_offsets_data();
void invalid_param_count();
void objmacro_expanding_as_fnmacro_notification();
void macro_uses();
void macro_uses_lines();
void macro_arguments_notificatin();
void unfinished_function_like_macro_call();
void nasty_macro_expansion();
void glib_attribute();
void builtin__FILE__();
void blockSkipping();
void includes_1();
void dont_eagerly_expand();
void dont_eagerly_expand_data();
void comparisons_data();
void comparisons();
void comments_before_args();
void comments_within();
void comments_within_data();
void multitokens_argument();
void multitokens_argument_data();
void multiline_strings();
void multiline_strings_data();
void skip_unknown_directives();
void skip_unknown_directives_data();
void include_guard();
void include_guard_data();
void empty_trailing_lines();
void empty_trailing_lines_data();
void undef();
void concat();
void excessive_nesting();
void multi_byte_code_point_in_expansion();
void trigraph();
void nested_arguments_expansion();
void preprocessorSymbolsAsMacroArguments();
};
// Remove all #... lines, and 'simplify' string, to allow easily comparing the result
// Also, remove all unneeded spaces: keep only to ensure identifiers are separated.
// NOTE: may not correctly handle underscore in identifiers
QByteArray tst_Preprocessor::simplified(const QByteArray &buf)
{
QString out;
QList<QByteArray> lines = buf.split('\n');
foreach (const QByteArray &line, lines) {
if (!line.startsWith('#')) {
out.append(" ");
out.append(QString::fromUtf8(line));
}
}
out = out.simplified();
for (int i = 1; i < out.length() - 1; ) {
if (out.at(i).isSpace()
&& !(out.at(i-1).isLetterOrNumber()
&& out.at(i+1).isLetterOrNumber()))
out.remove(i,1);
else
i++;
}
return out.toUtf8();
}
void tst_Preprocessor::va_args()
{
Client *client = 0; // no client.
Environment env;
Preprocessor preprocess(client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"),
"#define foo(...) int f(__VA_ARGS__);\n"
"\nfoo( )\n"
"\nfoo(int a)\n"
"\nfoo(int a,int b)\n",
true, false);
preprocessed = preprocessed.simplified();
// DUMP_OUTPUT(preprocessed);
QVERIFY(compare(simplified(preprocessed), "int f();int f(int a);int f(int a,int b);"));
}
void tst_Preprocessor::named_va_args()
{
Client *client = 0; // no client.
Environment env;
Preprocessor preprocess(client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"),
"\n#define foo(ARGS...) int f(ARGS);"
"\nfoo( )\n"
"\nfoo(int a)\n"
"\nfoo(int a,int b)\n",
true, false);
preprocessed = preprocessed.simplified();
QVERIFY(compare(simplified(preprocessed), "int f();int f(int a);int f(int a,int b);"));
}
void tst_Preprocessor::extra_va_args()
{
Client *client = 0; // no client.
Environment env;
Preprocessor preprocess(client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"),
"#define foo(ret, ...) ret f(__VA_ARGS__);\n"
"\nfoo(int)\n"
"\nfoo(float,int b)\n"
"\nfoo(long,int b,int c)\n",
true, false);
preprocessed = preprocessed.simplified();
QVERIFY(compare(simplified(preprocessed), "int f();float f(int b);long f(int b,int c);"));
}
void tst_Preprocessor::empty_macro_args()
{
Client *client = 0; // no client.
Environment env;
Preprocessor preprocess(client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"),
"\n#define foo(a,b) a int b;"
"\nfoo(const,cVal)\n"
"\nfoo(,Val)\n"
"\nfoo( ,Val2)\n"
"\nfoo(,)\n"
"\nfoo(, )\n",
true, false);
preprocessed = preprocessed.simplified();
// DUMP_OUTPUT(preprocessed);
QVERIFY(compare(simplified(preprocessed), "const int cVal;int Val;int Val2;int;int;"));
}
void tst_Preprocessor::macro_args_count()
{
Environment env;
QByteArray output;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
preprocess.run(QLatin1String("<stdin>"),
"#define foo(a,b) a int b;\n"
"foo(const,cVal)\n"
"foo(, i)\n"
"foo(,Val)\n"
"foo( ,Val2)\n"
"foo(,)\n"
"foo(, )\n"
"#define bar(a)\n"
"bar()\n"
"bar(i)\n",
true, false);
QCOMPARE(client.macroArgsCount(),
QList<int>() << 2 // foo(const,cVal)
<< 2 // foo(, i)
<< 2 // foo(,Val)
<< 2 // foo( , Val2)
<< 2 // foo(,)
<< 2 // foo(, )
<< 1 // bar()
<< 1 // bar(i)
);
}
void tst_Preprocessor::macro_args_offsets()
{
QFETCH(QString, fileName);
QFETCH(QByteArray, source);
QFETCH(QByteArray, macroName);
QFETCH(int, bytesOffset);
QFETCH(int, bytesLength);
QFETCH(int, utf16charsOffset);
QFETCH(int, utf16charsLength);
Environment env;
QByteArray output;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
preprocess.run(fileName, source, true, false);
QMap<QByteArray, QVector<MacroArgumentReference >> usedMacros = client.usedMacros();
QCOMPARE(usedMacros.size(), 1);
QVERIFY(usedMacros.contains(macroName));
MacroArgumentReference argRef = usedMacros.value(macroName).at(0);
QCOMPARE(argRef.bytesOffset(), bytesOffset);
QCOMPARE(argRef.bytesLength(), bytesLength);
QCOMPARE(argRef.utf16charsOffset(), utf16charsOffset);
QCOMPARE(argRef.utf16charsLength(), utf16charsLength);
}
void tst_Preprocessor::macro_args_offsets_data()
{
QTest::addColumn<QString>("fileName");
QTest::addColumn<QByteArray>("source");
QTest::addColumn<QByteArray>("macroName");
QTest::addColumn<int>("bytesOffset");
QTest::addColumn<int>("bytesLength");
QTest::addColumn<int>("utf16charsOffset");
QTest::addColumn<int>("utf16charsLength");
QString fN = QLatin1String("<stdin>");
QByteArray src = QByteArray("#define SQR(a) ( a * a )\n"
"void f(){\n"
"int i = 10;\n"
"int j = SQR(10);\n"
"}");
QTest::newRow("ascii_only_before_ref") << fN << src << QByteArray("SQR")
<< 59 << 2 << 59 << 2;
src.replace("int i", "int äöü");
QTest::newRow("ascii_with_umlauts_before_ref") << fN << src << QByteArray("SQR")
<< 64 << 2 << 61 << 2;
src.clear();
src.append("#define OUT(format, ...) printf(\"%s %d: \" format, __FILE__, __LINE__)\n"
"void f(){\n"
"OUT(\"Hei verden!\\n\");\n"
"}\n");
QTest::newRow("arg_ascii_only") << fN << src << QByteArray("OUT")
<< 84 << 15 << 84 << 15;
src.replace("Hei verden", UC_U00FC);
QTest::newRow("arg_ascii_with_unicode_00fc") << fN << src << QByteArray("OUT")
<< 84 << 7 << 84 << 6;
src.replace(UC_U00FC, UC_U4E8C);
QTest::newRow("arg_ascii_with_unicode_4e8c") << fN << src << QByteArray("OUT")
<< 84 << 8 << 84 << 6;
src.replace(UC_U4E8C, UC_U10302);
QTest::newRow("arg_ascii_with_unicode_10302") << fN << src << QByteArray("OUT")
<< 84 << 9 << 84 << 7;
}
void tst_Preprocessor::invalid_param_count()
{
Environment env;
QByteArray output;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
// The following are illegal, but shouldn't crash the preprocessor.
preprocess.run(QLatin1String("<stdin>"),
"\n#define foo(a,b) int f(a,b);"
"\n#define ARGS(t) t a,t b"
"\nfoo(ARGS(int))"
"\nfoo()"
"\nfoo(int a, int b, int c)",
true, false);
// Output is not that relevant but check that nothing triggered expansion.
QCOMPARE(client.macroArgsCount(), QList<int>());
}
void tst_Preprocessor::macro_uses()
{
QByteArray buffer = QByteArray("\n#define FOO 8"
"\n#define BAR 9"
"\nvoid test(){"
"\n\tint x=FOO;"
"\n\tint y=BAR;"
"\n}");
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"), buffer);
QVERIFY(compare(simplified(preprocessed), "void test(){int x=8;int y=9;}"));
QCOMPARE(client.expandedMacros(), QList<QByteArray>() << QByteArray("FOO") << QByteArray("BAR"));
QCOMPARE(client.expandedMacrosOffset(), QList<int>() << buffer.indexOf("FOO;") << buffer.indexOf("BAR;"));
QCOMPARE(client.definedMacros(), QList<QByteArray>() << QByteArray("FOO") << QByteArray("BAR"));
QCOMPARE(client.definedMacrosLine(), QList<int>() << 2 << 3);
}
void tst_Preprocessor::macro_uses_lines()
{
QByteArray buffer("#define FOO\n"
"FOO\n"
"\n"
"#define HEADER <test>\n"
"#include HEADER\n"
"\n"
"#define DECLARE(C, V) struct C {}; C V;\n"
"#define ABC X\n"
"DECLARE(Test, test)\n"
"\n"
"int abc;\n"
"#define NOTHING(C)\n"
"NOTHING(abc)\n"
"\n"
"#define ENABLE(FEATURE) (defined ENABLE_##FEATURE && ENABLE_##FEATURE)\n"
"#define ENABLE_COOL 1\n"
"void fill();\n"
"#if ENABLE(COOL)\n"
"class Cool {};\n"
"#endif\n"
"int cool = ENABLE_COOL;\n"
"#define OTHER_ENABLE(FEATURE) ENABLE(FEATURE)\n"
"#define MORE(LESS) FOO ENABLE(LESS)\n");
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
preprocess.run(QLatin1String("<stdin>"), buffer);
QCOMPARE(client.macroUsesLine().value("FOO"), QList<int>() << 2 << 23);
QCOMPARE(client.macroUsesLine().value("HEADER"), QList<int>() << 5);
QCOMPARE(client.macroUsesLine().value("DECLARE"), QList<int>() << 9);
QCOMPARE(client.macroUsesLine().value("NOTHING"), QList<int>() << 13);
QCOMPARE(client.macroUsesLine().value("ENABLE"), QList<int>() << 18 << 22 << 23);
QCOMPARE(client.macroUsesLine().value("ENABLE_COOL"), QList<int>() << 21);
QCOMPARE(client.definitionsResolvedFromLines().value("ENABLE_COOL"), QList<int>() << 18);
QCOMPARE(client.expandedMacrosOffset(), QList<int>()
<< buffer.lastIndexOf("FOO\n")
<< buffer.lastIndexOf("HEADER")
<< buffer.lastIndexOf("DECLARE")
<< buffer.lastIndexOf("NOTHING")
<< buffer.lastIndexOf("ENABLE(COOL)")
<< buffer.lastIndexOf("ENABLE_COOL")
<< buffer.lastIndexOf("ENABLE(FEATURE)")
<< buffer.lastIndexOf("FOO ")
<< buffer.lastIndexOf("ENABLE(LESS)"));
}
void tst_Preprocessor::multitokens_argument_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QByteArray>("output");
QTest::newRow("case 1") << _(
"#define foo(ARGS) int f(ARGS)\n"
"foo(int a);\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"# expansion begin 30,3 ~3 2:4 2:8 ~1\n"
"int f(int a)\n"
"# expansion end\n"
"# 2 \"<stdin>\"\n"
" ;\n"
);
QTest::newRow("case 2") << _(
"#define foo(ARGS) int f(ARGS)\n"
"foo(int \n"
" a);\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"# expansion begin 30,3 ~3 2:4 3:4 ~1\n"
"int f(int a)\n"
"# expansion end\n"
"# 3 \"<stdin>\"\n"
" ;\n"
);
QTest::newRow("case 3") << _(
"#define foo(ARGS) int f(ARGS)\n"
"foo(int a = 0);\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"# expansion begin 30,3 ~3 2:4 2:8 2:10 2:12 ~1\n"
"int f(int a = 0)\n"
"# expansion end\n"
"# 2 \"<stdin>\"\n"
" ;\n"
);
QTest::newRow("case 4") << _(
"#define foo(X) int f(X = 0)\n"
"foo(int \n"
" a);\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"# expansion begin 28,3 ~3 2:4 3:4 ~3\n"
"int f(int a = 0)\n"
"# expansion end\n"
"# 3 \"<stdin>\"\n"
" ;\n"
);
}
void tst_Preprocessor::multitokens_argument()
{
compare_input_output();
}
void tst_Preprocessor::objmacro_expanding_as_fnmacro_notification()
{
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"),
QByteArray("\n#define bar(a,b) a + b"
"\n#define foo bar"
"\nfoo(1, 2)\n"));
QVERIFY(client.expandedMacros() == (QList<QByteArray>() << QByteArray("foo")));
}
void tst_Preprocessor::macro_arguments_notificatin()
{
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"),
QByteArray("\n#define foo(a,b) a + b"
"\n#define arg(a) a"
"\n#define value 2"
"\nfoo(arg(1), value)\n"));
QVERIFY(client.expandedMacros() == (QList<QByteArray>() << QByteArray("foo")
<< QByteArray("arg")
<< QByteArray("value")));
}
void tst_Preprocessor::unfinished_function_like_macro_call()
{
Client *client = 0; // no client.
Environment env;
Preprocessor preprocess(client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"),
QByteArray("\n"
"#define foo(a,b) a + b\n"
"foo(1, 2\n"));
QByteArray expected__("# 1 \"<stdin>\"\n"
"\n"
"\n"
"# expansion begin 24,3 3:4 ~1 3:7\n"
"1 + 2\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n");
// DUMP_OUTPUT(preprocessed);
QVERIFY(compare(preprocessed, expected__));
}
void tst_Preprocessor::nasty_macro_expansion()
{
QByteArray input("\n"
"#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))\n"
"#define is_power_of_two(x) ( !((x) & ((x)-1)) )\n"
"#define low_bit_mask(x) ( ((x)-1) & ~(x) )\n"
"#define is_valid_mask(x) is_power_of_two(1LU + (x) + low_bit_mask(x))\n"
"#define compile_ffs2(__x) \\\n"
" __builtin_choose_expr(((__x) & 0x1), 0, 1)\n"
"#define compile_ffs4(__x) \\\n"
" __builtin_choose_expr(((__x) & 0x3), \\\n"
" (compile_ffs2((__x))), \\\n"
" (compile_ffs2((__x) >> 2) + 2))\n"
"#define compile_ffs8(__x) \\\n"
" __builtin_choose_expr(((__x) & 0xf), \\\n"
" (compile_ffs4((__x))), \\\n"
" (compile_ffs4((__x) >> 4) + 4))\n"
"#define compile_ffs16(__x) \\\n"
" __builtin_choose_expr(((__x) & 0xff), \\\n"
" (compile_ffs8((__x))), \\\n"
" (compile_ffs8((__x) >> 8) + 8))\n"
"#define compile_ffs32(__x) \\\n"
" __builtin_choose_expr(((__x) & 0xffff), \\\n"
" (compile_ffs16((__x))), \\\n"
" (compile_ffs16((__x) >> 16) + 16))\n"
"#define FIELD_CHECK(__mask, __type) \\\n"
" BUILD_BUG_ON(!(__mask) || \\\n"
" !is_valid_mask(__mask) || \\\n"
" (__mask) != (__type)(__mask)) \\\n"
"\n"
"#define FIELD32(__mask) \\\n"
"({ \\\n"
" FIELD_CHECK(__mask, u32); \\\n"
" (struct rt2x00_field32) { \\\n"
" compile_ffs32(__mask), (__mask) \\\n"
" }; \\\n"
"})\n"
"#define BBPCSR 0x00f0\n"
"#define BBPCSR_BUSY FIELD32(0x00008000)\n"
"#define WAIT_FOR_BBP(__dev, __reg) \\\n"
" rt2x00pci_regbusy_read((__dev), BBPCSR, BBPCSR_BUSY, (__reg))\n"
"if (WAIT_FOR_BBP(rt2x00dev, &reg)) {}\n"
);
Client *client = 0; // no client.
Environment env;
Preprocessor preprocess(client, &env);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"), input);
QVERIFY(!preprocessed.contains("FIELD32"));
}
void tst_Preprocessor::glib_attribute()
{
Environment env;
Preprocessor preprocess(0, &env);
QByteArray preprocessed = preprocess.run(
QLatin1String("<stdin>"),
QByteArray("\n"
"# define _GLIBCXX_VISIBILITY(V) __attribute__ ((__visibility__ (#V)))\n"
"namespace std _GLIBCXX_VISIBILITY(default) {\n"
"}\n"
));
const QByteArray result____ =
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"namespace std\n"
"# expansion begin 85,19 ~9\n"
"__attribute__ ((__visibility__ (\"default\")))\n"
"# expansion end\n"
"# 3 \"<stdin>\"\n"
" {\n"
"}\n";
// DUMP_OUTPUT(preprocessed);
QVERIFY(compare(preprocessed, result____));
}
void tst_Preprocessor::builtin__FILE__()
{
Client *client = 0; // no client.
Environment env;
Preprocessor preprocess(client, &env);
QByteArray preprocessed = preprocess.run(
QLatin1String("some-file.c"),
QByteArray("const char *f = __FILE__\n"
));
const QByteArray result____ =
"# 1 \"some-file.c\"\n"
"const char *f = \"some-file.c\"\n";
QVERIFY(compare(preprocessed, result____));
}
void tst_Preprocessor::comparisons_data()
{
QTest::addColumn<QString>("infile");
QTest::addColumn<QString>("outfile");
QTest::addColumn<QString>("errorfile");
QTest::newRow("do nothing") << "noPP.1.cpp" << "noPP.1.cpp" << "";
QTest::newRow("no PP 2") << "noPP.2.cpp" << "noPP.2.cpp" << "";
QTest::newRow("identifier-expansion 1")
<< "identifier-expansion.1.cpp" << "identifier-expansion.1.out.cpp" << "";
QTest::newRow("identifier-expansion 2")
<< "identifier-expansion.2.cpp" << "identifier-expansion.2.out.cpp" << "";
QTest::newRow("identifier-expansion 3")
<< "identifier-expansion.3.cpp" << "identifier-expansion.3.out.cpp" << "";
QTest::newRow("identifier-expansion 4")
<< "identifier-expansion.4.cpp" << "identifier-expansion.4.out.cpp" << "";
QTest::newRow("identifier-expansion 5")
<< "identifier-expansion.5.cpp" << "identifier-expansion.5.out.cpp" << "";
QTest::newRow("reserved 1")
<< "reserved.1.cpp" << "reserved.1.out.cpp" << "";
QTest::newRow("recursive 1")
<< "recursive.1.cpp" << "recursive.1.out.cpp" << "";
QTest::newRow("recursive 2")
<< "recursive.2.cpp" << "recursive.2.out.cpp" << "";
QTest::newRow("macro_pounder_fn")
<< "macro_pounder_fn.c" << "" << "";
QTest::newRow("macro_expand")
<< "macro_expand.c" << "macro_expand.out.c" << "";
QTest::newRow("macro_expand_1")
<< "macro_expand_1.cpp" << "macro_expand_1.out.cpp" << "";
QTest::newRow("macro-test")
<< "macro-test.cpp" << "macro-test.out.cpp" << "";
QTest::newRow("empty-macro")
<< "empty-macro.cpp" << "empty-macro.out.cpp" << "";
QTest::newRow("empty-macro 2")
<< "empty-macro.2.cpp" << "empty-macro.2.out.cpp" << "";
QTest::newRow("poundpound 1")
<< "poundpound.1.cpp" << "poundpound.1.out.cpp" << "";
}
void tst_Preprocessor::comparisons()
{
QFETCH(QString, infile);
QFETCH(QString, outfile);
QFETCH(QString, errorfile);
QByteArray errors;
QByteArray preprocessed = preprocess(infile, &errors, infile == outfile);
// DUMP_OUTPUT(preprocessed);
if (!outfile.isEmpty()) {
// These weird underscores are here to make the name as long as
// "preprocessed", so the QCOMPARE error messages are nicely aligned.
QByteArray output____ = loadSource("data/" + outfile);
QVERIFY(compare(preprocessed, output____));
}
if (!errorfile.isEmpty()) {
QByteArray errorFileContents = loadSource("data/" + errorfile);
QVERIFY(compare(errors, errorFileContents));
}
}
void tst_Preprocessor::blockSkipping()
{
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor pp(&client, &env);
/*QByteArray preprocessed =*/ pp.run(
QLatin1String("<stdin>"),
QByteArray("#if 0\n"
"\n"
"int yes;\n"
"\n"
"#elif 0\n"
"\n"
"int no;\n"
"\n"
"#else // foobar\n"
"\n"
"void also_not;\n"
"\n"
"#endif\n"
));
QList<MockClient::Block> blocks = client.skippedBlocks();
QCOMPARE(blocks.size(), 1);
MockClient::Block b = blocks.at(0);
QCOMPARE(b.start, 6);
QCOMPARE(b.end, 34);
}
void tst_Preprocessor::includes_1()
{
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor pp(&client, &env);
/*QByteArray preprocessed =*/ pp.run(
QLatin1String("<stdin>"),
QByteArray("#define FOO <foo.h>\n"
"#define BAR \"bar.h\"\n"
"\n"
"#include FOO\n"
"#include BAR\n"
"\n"
"#include <zoo.h>\n"
"#include \"mooze.h\"\n"
));
QList<Include> incs = client.recordedIncludes();
// qDebug()<<incs;
QCOMPARE(incs.size(), 4);
QCOMPARE(incs.at(0).fileName, QLatin1String("foo.h"));
QCOMPARE(incs.at(0).type, Client::IncludeGlobal);
QCOMPARE(incs.at(0).line, 4);
QCOMPARE(incs.at(1).fileName, QLatin1String("bar.h"));
QCOMPARE(incs.at(1).type, Client::IncludeLocal);
QCOMPARE(incs.at(1).line, 5);
QCOMPARE(incs.at(2).fileName, QLatin1String("zoo.h"));
QCOMPARE(incs.at(2).type, Client::IncludeGlobal);
QCOMPARE(incs.at(2).line, 7);
QCOMPARE(incs.at(3).fileName, QLatin1String("mooze.h"));
QCOMPARE(incs.at(3).type, Client::IncludeLocal);
QCOMPARE(incs.at(3).line, 8);
}
void tst_Preprocessor::defined()
{
QFETCH(bool, xdefined);
QFETCH(bool, ydefined);
QFETCH(QString, input);
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor pp(&client, &env);
pp.run(QLatin1String("<stdin>"), input.toLatin1(), false, true);
QList<QByteArray> expected;
if (xdefined)
expected.append("X");
if (ydefined)
expected.append("Y");
if (client.definedMacros() != expected)
qWarning() << "\nSource: " << input.replace('\n', " ");
QCOMPARE(client.definedMacros(), expected);
}
void tst_Preprocessor::defined_data()
{
QTest::addColumn<bool>("xdefined");
QTest::addColumn<bool>("ydefined");
QTest::addColumn<QString>("input");
QTest::newRow("1a") << true << true <<
"#define X\n#if defined(X)\n#define Y\n#endif";
QTest::newRow("1b") << true << true <<
"#define X\n#if defined X \n#define Y\n#endif";
QTest::newRow("1c") << true << true <<
"#define X\n#ifdef X \n#define Y\n#endif";
QTest::newRow("2a") << false << false <<
"#if defined(X)\n#define Y\n#endif";
QTest::newRow("2b") << false << false <<
"#if defined X \n#define Y\n#endif";
QTest::newRow("2c") << false << false <<
"#ifdef X \n#define Y\n#endif";
QTest::newRow("3a") << true << false <<
"#define X\n#if !defined(X)\n#define Y\n#endif";
QTest::newRow("3b") << true << false <<
"#define X\n#if !defined X \n#define Y\n#endif";
QTest::newRow("3c") << true << false <<
"#define X\n#ifndef X \n#define Y\n#endif";
QTest::newRow("4a") << false << true <<
"#if !defined(X)\n#define Y\n#endif";
QTest::newRow("4b") << false << true <<
"#if !defined X \n#define Y\n#endif";
QTest::newRow("4c") << false << true <<
"#ifndef X \n#define Y\n#endif";
QTest::newRow("5a") << false << false <<
"#if !defined(X) && (defined(Y))\n"
"#define X\n"
"#endif\n";
QTest::newRow("5b") << false << false <<
"#if !defined(X) && defined(Y)\n"
"#define X\n"
"#endif\n";
QTest::newRow("5c") << false << false <<
"#if !defined(X) && 0"
"#define X\n"
"#endif\n";
QTest::newRow("5d") << false << false <<
"#if (!defined(X)) && defined(Y)\n"
"#define X\n"
"#endif\n";
QTest::newRow("5d") << false << false <<
"#if (define(Y))\n"
"#define X\n"
"#endif\n";
QTest::newRow("6a") << true << true <<
"#define X 0x040500\n"
"#if X > 0x040000\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("6b") << true << true <<
"#define X 0x040500\n"
"#if X >= 0x040000\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("6c") << true << false <<
"#define X 0x040500\n"
"#if X == 0x040000\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("6d") << true << true <<
"#define X 0x040500\n"
"#if X == 0x040500\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("6e") << true << false <<
"#define X 0x040500\n"
"#if X < 0x040000\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("6f") << true << false <<
"#define X 0x040500\n"
"#if X <= 0x040000\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("incomplete defined 1") << true << true <<
"#define X 0x040500\n"
"#if defined(X\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("incomplete defined 2") << false << false <<
"#if defined(X\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("complete defined 1") << true << true <<
"#define X 0x040500\n"
"#if defined(X )\n"
"#define Y 1\n"
"#endif\n";
QTest::newRow("complete defined 2") << true << true <<
"#define X 0x040500\n"
"#if defined(X/*xxx*/)\n"
"#define Y 1\n"
"#endif\n";
}
void tst_Preprocessor::defined_usage()
{
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor pp(&client, &env);
QByteArray source =
"#define X\n"
"#define Y\n"
"#ifdef X\n"
"#endif\n"
"#ifdef Y\n"
"#endif\n"
"#ifndef X\n"
"#endif\n"
"#ifndef Y\n"
"#endif\n"
"#ifdef ABSENT\n"
"#endif\n"
"#ifndef ABSENT2\n"
"#endif\n"
"#if defined(ABSENT3)\n"
"#endif\n"
"#if defined(X)\n"
"#endif\n"
"#if defined(X) || defined(Y)\n"
"#endif\n"
;
pp.run(QLatin1String("<stdin>"), source);
QHash<QByteArray, QList<int> > definitionsResolvedFromLines =
client.definitionsResolvedFromLines();
QCOMPARE(definitionsResolvedFromLines["X"], QList<int>() << 3 << 7 << 17 << 19);
QCOMPARE(definitionsResolvedFromLines["Y"], QList<int>() << 5 << 9 << 19);
QCOMPARE(client.unresolvedDefines(), QSet<QByteArray>() << "ABSENT" << "ABSENT2" << "ABSENT3");
}
void tst_Preprocessor::dont_eagerly_expand_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QByteArray>("output");
// Expansion must be processed upon invocation of the macro. Therefore a particular
// identifier within a define must not be expanded (in the case it matches an
// already known macro) during the processor directive handling, but only when
// it's actually "used". Naturally, if it's still not replaced after an invocation
// it should then be expanded. This is consistent with clang and gcc for example.
QTest::newRow("case 1") << _(
"#define T int\n"
"#define FOO(T) T\n"
"FOO(double)\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"# expansion begin 31,3 3:4\n"
"double\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n"
);
QTest::newRow("case 2") << _(
"#define T int\n"
"#define FOO(X) T\n"
"FOO(double)\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"# expansion begin 31,3 ~1\n"
"int\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n"
);
}
void tst_Preprocessor::dont_eagerly_expand()
{
compare_input_output();
}
void tst_Preprocessor::comments_within()
{
QFETCH(QByteArray, input);
QFETCH(QByteArray, without_comments);
QFETCH(QByteArray, with_comments);
Environment env;
Preprocessor preprocess(0, &env);
preprocess.setKeepComments(false);
QByteArray prep = preprocess.run(QLatin1String("<stdin>"), input);
QVERIFY(compare(prep, without_comments));
preprocess.setKeepComments(true);
prep = preprocess.run(QLatin1String("<stdin>"), input);
QVERIFY(compare(prep, with_comments));
}
void tst_Preprocessor::comments_within_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QByteArray>("without_comments");
QTest::addColumn<QByteArray>("with_comments");
QTest::newRow("case 1") << _(
"#define FOO int x;\n"
"\n"
" // comment\n"
" // comment\n"
" // comment\n"
" // comment\n"
"FOO\n"
"x = 10\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"# expansion begin 76,3 ~3\n"
"int x;\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
"x = 10\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
" // comment\n"
" // comment\n"
" // comment\n"
" // comment\n"
"# expansion begin 76,3 ~3\n"
"int x;\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
"x = 10\n"
);
QTest::newRow("case 2") << _(
"#define FOO int x;\n"
"\n"
" /* comment\n"
" comment\n"
" comment\n"
" comment */\n"
"FOO\n"
"x = 10\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"# expansion begin 79,3 ~3\n"
"int x;\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
"x = 10\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
" /* comment\n"
" comment\n"
" comment\n"
" comment */\n"
"# expansion begin 79,3 ~3\n"
"int x;\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
"x = 10\n"
);
QTest::newRow("case 3") << _(
"#define FOO int x;\n"
"\n"
" // comment\n"
" // comment\n"
" // comment\n"
" // comment\n"
"FOO\n"
"// test\n"
"// test again\n"
"x = 10\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"# expansion begin 76,3 ~3\n"
"int x;\n"
"# expansion end\n"
"# 10 \"<stdin>\"\n"
"x = 10\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
" // comment\n"
" // comment\n"
" // comment\n"
" // comment\n"
"# expansion begin 76,3 ~3\n"
"int x;\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
"// test\n"
"// test again\n"
"x = 10\n"
);
QTest::newRow("case 4") << _(
"#define FOO int x;\n"
"\n"
" /* comment\n"
" comment\n"
" comment\n"
" comment */\n"
"FOO\n"
"/* \n"
"*/\n"
"x = 10\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"# expansion begin 79,3 ~3\n"
"int x;\n"
"# expansion end\n"
"# 10 \"<stdin>\"\n"
"x = 10\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
" /* comment\n"
" comment\n"
" comment\n"
" comment */\n"
"# expansion begin 79,3 ~3\n"
"int x;\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
"/* \n"
"*/\n"
"x = 10\n"
);
QTest::newRow("case 5") << _(
"#define FOO(x, y) { (void)x; (void)y; }\n"
"\n"
"void foo() {\n"
" FOO(10,\n"
" //comment\n"
" 12\n"
"}\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"void foo() {\n"
"# expansion begin 57,3 ~4 4:7 ~4 6:7 7:0 ~2\n"
"{ (void)10; (void)12}; }\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"void foo() {\n"
"# expansion begin 57,3 ~4 4:7 ~5 6:7 7:0 ~2\n"
"{ (void)10; (void)/*comment*/ 12}; }\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
);
QTest::newRow("case 6") << _(
"#define FOO(x, y) { (void)x; (void)y; }\n"
"\n"
"void foo() {\n"
" FOO(10,\n"
" //tricky*/comment\n"
" 12\n"
"}\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"void foo() {\n"
"# expansion begin 57,3 ~4 4:7 ~4 6:7 7:0 ~2\n"
"{ (void)10; (void)12}; }\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"void foo() {\n"
"# expansion begin 57,3 ~4 4:7 ~5 6:7 7:0 ~2\n"
"{ (void)10; (void)/*tricky*|comment*/ 12}; }\n"
"# expansion end\n"
"# 8 \"<stdin>\"\n"
);
QTest::newRow("case 7") << _(
"#define FOO 0 //comment\n"
"#define BAR (1 == FOO)\n"
"void foo() {\n"
" if (BAR) {}\n"
"}\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"void foo() {\n"
" if (\n"
"# expansion begin 68,3 ~5\n"
"(1 == 0)\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n"
" ) {}\n"
"}\n"
) << _(
"# 1 \"<stdin>\"\n"
" //comment\n"
"\n"
"void foo() {\n"
" if (\n"
"# expansion begin 68,3 ~5\n"
"(1 == 0)\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n"
" ) {}\n"
"}\n"
);
QTest::newRow("case 8") << _(
"#define FOO /* comment */ 0\n"
"FOO\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"# expansion begin 28,3 ~1\n"
"0\n"
"# expansion end\n"
"# 3 \"<stdin>\"\n"
) << _(
"# 1 \"<stdin>\"\n"
" /* comment */\n"
"# expansion begin 28,3 ~1\n"
"0\n"
"# expansion end\n"
"# 3 \"<stdin>\"\n"
);
QTest::newRow("case 9") << _(
"#define FOO /* comment1 */ /* comment2 */ 0\n"
"FOO\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"# expansion begin 44,3 ~1\n"
"0\n"
"# expansion end\n"
"# 3 \"<stdin>\"\n"
) << _(
"# 1 \"<stdin>\"\n"
" /* comment1 */ /* comment2 */\n"
"# expansion begin 44,3 ~1\n"
"0\n"
"# expansion end\n"
"# 3 \"<stdin>\"\n"
);
QTest::newRow("case 10") << _(
"#define FOO /* comment1 */ /* comment2 */ 0 /* comment3\n"
"comment4 */\n"
"FOO\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"# expansion begin 70,3 ~1\n"
"0\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n"
) << _(
"# 1 \"<stdin>\"\n"
" /* comment1 */ /* comment2 */ /* comment3\n"
"comment4 */\n"
"# expansion begin 70,3 ~1\n"
"0\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n"
);
QTest::newRow("case 11") << _(
"#include <foo.h> // comment\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
) << _(
"# 1 \"<stdin>\"\n"
" // comment\n"
);
QTest::newRow("joined") << _(
"// comment \\\n"
"\n"
"int foo = 4;"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"int foo = 4;"
) << _(
"# 1 \"<stdin>\"\n"
"// comment \\\n"
"\n"
"int foo = 4;"
);
QTest::newRow("joined_unterminated") << _(
"// comment \\\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"int foo = 4;"
) << _(
"# 1 \"<stdin>\"\n"
"# 12 \"<stdin>\"\n"
"int foo = 4;"
) << _(
"# 1 \"<stdin>\"\n"
"// comment \\\n"
"\n"
"# 12 \"<stdin>\"\n"
"int foo = 4;"
);
QTest::newRow("inside_function_like_macro") << _(
"#define /* comment */ ASSIGN1(VAR, VALUE) VAR = VALUE\n"
"#define ASSIGN2(/* comment */ VAR, VALUE) VAR = VALUE\n"
"#define ASSIGN3(VAR /* comment */, VALUE) VAR = VALUE\n"
"#define ASSIGN4(VAR, /* comment */ VALUE) VAR = VALUE\n"
"#define ASSIGN5(VAR, VALUE /* comment */) VAR = VALUE\n"
"#define ASSIGN6(VAR, VALUE) /* comment */ VAR = VALUE\n"
"#define ASSIGN7(VAR, ... /* comment */) VAR\n"
"void func()\n"
"{\n"
" int i;\n"
" ASSIGN1(i, 3);\n"
" ASSIGN2(i, 3);\n"
" ASSIGN3(i, 3);\n"
" ASSIGN4(i, 3);\n"
" ASSIGN5(i, 3);\n"
" ASSIGN6(i, 3);\n"
" ASSIGN7(i, 3);\n"
"}\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"void func()\n"
"{\n"
" int i;\n"
"# expansion begin 397,7 11:12 ~1 11:15\n"
"i = 3\n"
"# expansion end\n"
"# 11 \"<stdin>\"\n"
" ;\n"
"# expansion begin 416,7 12:12 ~1 12:15\n"
"i = 3\n"
"# expansion end\n"
"# 12 \"<stdin>\"\n"
" ;\n"
"# expansion begin 435,7 13:12 ~1 13:15\n"
"i = 3\n"
"# expansion end\n"
"# 13 \"<stdin>\"\n"
" ;\n"
"# expansion begin 454,7 14:12 ~1 14:15\n"
"i = 3\n"
"# expansion end\n"
"# 14 \"<stdin>\"\n"
" ;\n"
"# expansion begin 473,7 15:12 ~1 15:15\n"
"i = 3\n"
"# expansion end\n"
"# 15 \"<stdin>\"\n"
" ;\n"
"# expansion begin 492,7 16:12 ~1 16:15\n"
"i = 3\n"
"# expansion end\n"
"# 16 \"<stdin>\"\n"
" ;\n"
"# expansion begin 511,7 17:12\n"
"i\n"
"# expansion end\n"
"# 17 \"<stdin>\"\n"
" ;\n"
"}\n"
) << _(
"# 1 \"<stdin>\"\n"
" /* comment */\n"
" /* comment */\n"
" /* comment */\n"
" /* comment */\n"
" /* comment */\n"
" /* comment */\n"
" /* comment */\n"
"void func()\n"
"{\n"
" int i;\n"
"# expansion begin 397,7 11:12 ~1 11:15\n"
"i = 3\n"
"# expansion end\n"
"# 11 \"<stdin>\"\n"
" ;\n"
"# expansion begin 416,7 12:12 ~1 12:15\n"
"i = 3\n"
"# expansion end\n"
"# 12 \"<stdin>\"\n"
" ;\n"
"# expansion begin 435,7 13:12 ~1 13:15\n"
"i = 3\n"
"# expansion end\n"
"# 13 \"<stdin>\"\n"
" ;\n"
"# expansion begin 454,7 14:12 ~1 14:15\n"
"i = 3\n"
"# expansion end\n"
"# 14 \"<stdin>\"\n"
" ;\n"
"# expansion begin 473,7 15:12 ~1 15:15\n"
"i = 3\n"
"# expansion end\n"
"# 15 \"<stdin>\"\n"
" ;\n"
"# expansion begin 492,7 16:12 ~1 16:15\n"
"i = 3\n"
"# expansion end\n"
"# 16 \"<stdin>\"\n"
" ;\n"
"# expansion begin 511,7 17:12\n"
"i\n"
"# expansion end\n"
"# 17 \"<stdin>\"\n"
" ;\n"
"}\n"
);
}
void tst_Preprocessor::comments_before_args()
{
Client *client = 0; // no client.
Environment env;
Preprocessor preprocess(client, &env);
preprocess.setKeepComments(true);
QByteArray preprocessed = preprocess.run(QLatin1String("<stdin>"),
"#define foo(a,b) int a = b;\n"
"foo/*C comment*/(a,1)\n"
"foo/**Doxygen comment*/(b,2)\n"
"foo//C++ comment\n"
"(c,3)\n"
"foo///Doxygen C++ comment\n"
"(d,4)\n"
"foo/*multiple*///comments\n"
"/**as well*/(e,5)\n",
true, false);
// DUMP_OUTPUT(preprocessed);
QByteArray expected =
"\n"
" /*C comment*/int a = 1;\n"
" /**Doxygen comment*/int b = 2;\n"
" //C++ comment\n"
"int\n"
"c = 3;\n"
" ///Doxygen C++ comment\n"
"int\n"
"d = 4;\n"
" /*multiple*/ //comments\n"
"/**as well*/ int\n"
"e = 5;\n";
QVERIFY(compare(preprocessed, expected));
}
void tst_Preprocessor::multiline_strings()
{
compare_input_output();
}
void tst_Preprocessor::multiline_strings_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QByteArray>("output");
QTest::newRow("case 1") << _(
"const char *s = \"abc\\\n"
"xyz\";\n"
) << _(
"# 1 \"<stdin>\"\n"
"const char *s = \"abc\\\n"
"xyz\";\n"
);
}
void tst_Preprocessor::skip_unknown_directives()
{
compare_input_output();
}
void tst_Preprocessor::skip_unknown_directives_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QByteArray>("output");
// We should skip "weird" things when preprocessing. Particularly useful when we preprocess
// a particular expression from a document which has already been processed.
QTest::newRow("case 1") << _(
"# foo\n"
"# 10 \"file.cpp\"\n"
"# ()\n"
"#\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"\n"
"\n"
);
}
void tst_Preprocessor::include_guard()
{
QFETCH(QString, includeGuard);
QFETCH(QString, input);
QByteArray output;
Environment env;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
preprocess.setKeepComments(true);
/*QByteArray prep =*/ preprocess.run(QLatin1String("<test-case>"), input);
QCOMPARE(QString::fromUtf8(client.includeGuard()), includeGuard);
}
void tst_Preprocessor::include_guard_data()
{
QTest::addColumn<QString>("includeGuard");
QTest::addColumn<QString>("input");
QTest::newRow("basic-test") << "BASIC_TEST"
<< "#ifndef BASIC_TEST\n"
"#define BASIC_TEST\n"
"\n"
"#endif // BASIC_TEST\n";
QTest::newRow("comments-1") << "GUARD"
<< "/* some\n"
" * copyright\n"
" * header.\n"
" */\n"
"#ifndef GUARD\n"
"#define GUARD\n"
"\n"
"#endif // GUARD\n";
QTest::newRow("comments-2") << "GUARD"
<< "#ifndef GUARD\n"
"#define GUARD\n"
"\n"
"#endif // GUARD\n"
"/* some\n"
" * trailing\n"
" * comments.\n"
" */\n"
;
QTest::newRow("nested-ifdef") << "GUARD"
<< "#ifndef GUARD\n"
"#define GUARD\n"
"#ifndef NOT_GUARD\n"
"#define NOT_GUARD\n"
"#endif // NOT_GUARD\n"
"\n"
"#endif // GUARD\n"
;
QTest::newRow("leading-tokens") << ""
<< "int i;\n"
"#ifndef GUARD\n"
"#define GUARD\n"
"\n"
"#endif // GUARD\n"
;
QTest::newRow("trailing-tokens") << ""
<< "#ifndef GUARD\n"
"#define GUARD\n"
"\n"
"#endif // GUARD\n"
"int i;\n"
;
QTest::newRow("surprising-but-correct") << "GUARD"
<< "#ifndef GUARD\n"
"int i;\n"
"\n"
"#define GUARD\n"
"#endif // GUARD\n"
;
QTest::newRow("incomplete-1") << ""
<< "#ifndef GUARD\n"
;
QTest::newRow("incomplete-2") << "GUARD"
<< "#ifndef GUARD\n"
"#define GUARD\n"
;
}
void tst_Preprocessor::empty_trailing_lines()
{
compare_input_output();
}
void tst_Preprocessor::empty_trailing_lines_data()
{
// Test if the number of lines at the end of a file is correct. This is important to make the
// EOF token for the end up at the correct line.
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QByteArray>("output");
const QByteArray original =
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n";
QTest::newRow("9 empty lines")
<< original
<< _("# 1 \"<stdin>\"\n") + original;
QTest::newRow("11 empty lines") << _(
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
"\n"
) << _(
"# 1 \"<stdin>\"\n"
"# 11 \"<stdin>\"\n"
);
QTest::newRow("1 include") << _(
"#include <something>\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
);
QTest::newRow("1 empty line with 1 include") << _(
"#include <something>\n"
"\n"
) << _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
);
}
void tst_Preprocessor::undef()
{
Environment env;
QByteArray output;
MockClient client(&env, &output);
Preprocessor preprocess(&client, &env);
QByteArray input =
"#define FOO\n"
"#define FOO2\n"
"#undef FOO\n"
"#undef BAR\n";
preprocess.run(QLatin1String("<stdin>"), input);
QCOMPARE(env.macroCount(), 4U);
Macro *macro = env.macroAt(0);
QCOMPARE(macro->name(), QByteArray("FOO"));
QCOMPARE(macro->bytesOffset(), 8U);
QCOMPARE(macro->line(), 1);
QVERIFY(!macro->isHidden());
macro = env.macroAt(1);
QCOMPARE(macro->name(), QByteArray("FOO2"));
QCOMPARE(macro->bytesOffset(), 20U);
QCOMPARE(macro->line(), 2);
QVERIFY(!macro->isHidden());
macro = env.macroAt(2);
QCOMPARE(macro->name(), QByteArray("FOO"));
QCOMPARE(macro->bytesOffset(), 32U);
QCOMPARE(macro->line(), 3);
QVERIFY(macro->isHidden());
macro = env.macroAt(3);
QCOMPARE(macro->name(), QByteArray("BAR"));
QCOMPARE(macro->bytesOffset(), 43U);
QCOMPARE(macro->line(), 4);
QVERIFY(macro->isHidden());
QList<QByteArray> macros = client.definedMacros();
QVERIFY(macros.contains("FOO"));
QVERIFY(macros.contains("FOO2"));
QCOMPARE(client.macroUsesLine()["FOO"], (QList<int>() << 3));
QVERIFY(client.macroUsesLine()["BAR"].isEmpty());
}
void tst_Preprocessor::concat()
{
Environment env;
Preprocessor preprocess(0, &env);
QByteArray input =
"#define concat(x,y) x ## y\n"
"#define xconcat(x, y) concat(x, y)\n"
"#define FOO 42\n"
"int var1 = concat(0x, FOO);\n"
"int var2 = xconcat(0x, FOO);\n";
QByteArray prep = preprocess.run(QLatin1String("<stdin>"), input);
const QByteArray output = _(
"# 1 \"<stdin>\"\n"
"\n"
"\n"
"\n"
"int var1 =\n"
"# expansion begin 88,6 ~1\n"
"0xFOO\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n"
" ;\n"
"int var2 =\n"
"# expansion begin 116,7 ~1\n"
"0x42\n"
"# expansion end\n"
"# 5 \"<stdin>\"\n"
" ;\n"
);
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::preprocessorSymbolsAsMacroArguments()
{
Environment env;
Preprocessor preprocess(nullptr, &env);
const QByteArray input =
"#define IFGEN(if, endif) if (1 == 0) endif\n"
"int main()\n"
"{\n"
"IFGEN(#if, #endif)\n"
"return 0;\n"
"}\n";
QVERIFY(preprocess.run(QLatin1String("<stdin>"), input).startsWith("# 1 \"<stdin>\"\n"));
}
void tst_Preprocessor::excessive_nesting()
{
Environment env;
Preprocessor preprocess(0, &env);
QByteArray input;
const QByteArray output =
"# 1 \"<stdin>\"\n"
"# 2001 \"<stdin>\"\n";
for (int i = 0; i < 1000; ++i)
input += "#if FOO\n";
for (int i = 0; i < 1000; ++i)
input += "#endif\n";
QByteArray prep = preprocess.run(QLatin1String("<stdin>"), input);
// Output cannot be precisely determined, but it shouldn't crash.
QCOMPARE(prep, output);
}
void tst_Preprocessor::multi_byte_code_point_in_expansion()
{
Environment env;
Preprocessor preprocess(0, &env);
const QByteArray input =
"#define FOO(x) x\n"
"FOO(arg" UC_U00FC "\n)\n";
const QByteArray actual = preprocess.run(QLatin1String("<stdin>"), input);
const QByteArray expected =
"# 1 \"<stdin>\"\n"
"\n"
"# expansion begin 17,3 2:4\n"
"arg" UC_U00FC "\n"
"# expansion end\n"
"# 4 \"<stdin>\"\n";
QCOMPARE(actual, expected);
}
void tst_Preprocessor::compare_input_output(bool keepComments)
{
QFETCH(QByteArray, input);
QFETCH(QByteArray, output);
Environment env;
Preprocessor preprocess(0, &env);
preprocess.setKeepComments(keepComments);
QByteArray prep = preprocess.run(QLatin1String("<stdin>"), input);
QVERIFY(compare(prep, output));
}
void tst_Preprocessor::trigraph()
{
Environment env;
Preprocessor preprocess(0, &env);
// We cannot use actual trigraphs in strings, they would be replaced by the preprocessor when
// compiling the test, so we use strings with 'j' character instead of '?', and perform a
// replacement at runtime.
// Trigraphs in source code are replaced
QByteArray prep = preprocess.run(QLatin1String("<stdin>"),
QByteArray("jj( jj) jj< jj> jj= jj=jj= jj' jj'= jj! jj!= jj- jj-=").replace('j', '?'),
true, false);
QCOMPARE(prep.constData(), "[ ] { } # ## ^ ^= | |= ~ ~=");
// Trigraphs that appear after macro expansion are not replaced
prep = preprocess.run(QLatin1String("<stdin>"),
"#define TRIGRAPH(x...) ? ## x ## ? ## x ## =\n"
"TRIGRAPH()",
true, false);
QCOMPARE(prep.constData(), QByteArray("\njj=").replace('j', '?').data());
}
QTEST_APPLESS_MAIN(tst_Preprocessor)
#include "tst_preprocessor.moc"