Commit dada2614 authored by Erik Verbruggen's avatar Erik Verbruggen

C++: add include-guard tracking.

Track the typical #ifndef/#define/#endif usage in header files to see if
the macro is an include guard. If so, store it in the Document. No
behavioural change, just recording the name.

This can be used in the future to track if a file needs to be re-parsed
when a macro changes: if it was used in the file, and not defined in it
nor being the include-guard, a file should be re-preprocessed and
re-parsed.

It can also be used to check if two files have the same include guard.

Change-Id: I2715f529997a7b24a11bdbc6150652e2669f1a46
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@digia.com>
parent 4c43655c
......@@ -312,6 +312,11 @@ public:
QList<UndefinedMacroUse> undefinedMacroUses() const
{ return _undefinedMacroUses; }
void setIncludeGuardMacroName(const QByteArray &includeGuardMacroName)
{ _includeGuardMacroName = includeGuardMacroName; }
QByteArray includeGuardMacroName() const
{ return _includeGuardMacroName; }
const Macro *findMacroDefinitionAt(unsigned line) const;
const MacroUse *findMacroUseAt(unsigned offset) const;
const UndefinedMacroUse *findUndefinedMacroUseAt(unsigned offset) const;
......@@ -327,12 +332,19 @@ private:
Control *_control;
TranslationUnit *_translationUnit;
Namespace *_globalNamespace;
/// All messages generated during lexical/syntactic/semantic analysis.
QList<DiagnosticMessage> _diagnosticMessages;
QList<Include> _includes;
QList<Macro> _definedMacros;
QList<Block> _skippedBlocks;
QList<MacroUse> _macroUses;
QList<UndefinedMacroUse> _undefinedMacroUses;
/// the macro name of the include guard, if there is one.
QByteArray _includeGuardMacroName;
QByteArray _source;
QDateTime _lastModified;
QAtomicInt _keepSourceAndASTCount;
......
......@@ -71,6 +71,7 @@ public:
const Macro &,
const QVector<MacroArgumentReference> &);
virtual void stopExpandingMacro(unsigned, const Macro &) {}
virtual void markAsIncludeGuard(const QByteArray &) {}
virtual void startSkippingBlocks(unsigned) {}
virtual void stopSkippingBlocks(unsigned) {}
......
......@@ -90,6 +90,9 @@ public:
= QVector<MacroArgumentReference>()) = 0;
virtual void stopExpandingMacro(unsigned offset, const Macro &macro) = 0;
/// Mark the given macro name as the include guard for the current file.
virtual void markAsIncludeGuard(const QByteArray &macroName) = 0;
/// Start skipping from the given offset.
virtual void startSkippingBlocks(unsigned offset) = 0;
virtual void stopSkippingBlocks(unsigned offset) = 0;
......
......@@ -10,6 +10,8 @@ include(../3rdparty/cplusplus/cplusplus.pri)
greaterThan(QT_MAJOR_VERSION, 4): QT += concurrent
#DEFINES += DEBUG_INCLUDE_GUARD_TRACKING
contains(QT, gui) {
HEADERS += \
$$PWD/Icons.h \
......
......@@ -80,6 +80,7 @@ enum {
}
namespace {
/// RAII object to save a value, and restore it when the scope is left.
template<typename _T>
class ScopedSwap
{
......@@ -100,7 +101,6 @@ public:
}
};
typedef ScopedSwap<bool> ScopedBoolSwap;
typedef ScopedSwap<unsigned> ScopedUnsignedSwap;
} // anonymous namespace
namespace CPlusPlus {
......@@ -557,6 +557,7 @@ Preprocessor::State::State()
, m_offsetRef(0)
, m_lineRef(1)
, m_expansionStatus(NotExpanding)
, m_includeGuardState(IncludeGuardState_BeforeIfndef)
{
m_skipping[m_ifLevel] = false;
m_trueTest[m_ifLevel] = false;
......@@ -596,6 +597,88 @@ void Preprocessor::State::popTokenBuffer()
--m_tokenBufferDepth;
}
#ifdef DEBUG_INCLUDE_GUARD_TRACKING
QString Preprocessor::State::guardStateToString(int guardState)
{
switch (guardState) {
case IncludeGuardState_NoGuard: return QLatin1String("NoGuard");
case IncludeGuardState_BeforeIfndef: return QLatin1String("BeforeIfndef");
case IncludeGuardState_AfterIfndef: return QLatin1String("AfterIfndef");
case IncludeGuardState_AfterDefine: return QLatin1String("AfterDefine");
case IncludeGuardState_AfterEndif: return QLatin1String("AfterEndif");
default: return QLatin1String("UNKNOWN");
}
}
#endif // DEBUG_INCLUDE_GUARD_TRACKING
/**
* @brief Update the include-guard tracking state.
*
* Include guards are the #ifdef/#define/#endif sequence typically found in
* header files to prevent repeated definition of the contents of that header
* file. So, for a file to have an include guard, it must look like this:
* \code
* #ifndef SOME_ID
* ... all declarations/definitions/etc. go here ...
* #endif
* \endcode
*
* SOME_ID is an identifier, and is also the include guard. The only tokens
* allowed before the #ifndef and after the #endif are comments (in any form)
* or #line directives. The only other requirement is that a #define SOME_ID
* occurs inside the #ifndef block, but not nested inside other
* #if/#ifdef/#ifndef blocks.
*
* This method tracks the state, and is called from \c updateIncludeGuardState
* which handles the most common no-op cases.
*
* @param hint indicates what kind of token is encountered in the input
* @param idToken the identifier token that ought to be in the input
* after a #ifndef or a #define .
*/
void Preprocessor::State::updateIncludeGuardState_helper(IncludeGuardStateHint hint, PPToken *idToken)
{
#ifdef DEBUG_INCLUDE_GUARD_TRACKING
int oldIncludeGuardState = m_includeGuardState;
QByteArray oldIncludeGuardMacroName = m_includeGuardMacroName;
#endif // DEBUG_INCLUDE_GUARD_TRACKING
switch (m_includeGuardState) {
case IncludeGuardState_NoGuard:
break;
case IncludeGuardState_BeforeIfndef:
if (hint == IncludeGuardStateHint_Ifndef
&& idToken && idToken->is(T_IDENTIFIER)) {
m_includeGuardMacroName = idToken->asByteArrayRef().toByteArray();
m_includeGuardState = IncludeGuardState_AfterIfndef;
} else {
m_includeGuardState = IncludeGuardState_NoGuard;
}
break;
case IncludeGuardState_AfterIfndef:
if (hint == IncludeGuardStateHint_Define
&& idToken && idToken->is(T_IDENTIFIER)
&& idToken->asByteArrayRef() == m_includeGuardMacroName)
m_includeGuardState = IncludeGuardState_AfterDefine;
break;
case IncludeGuardState_AfterDefine:
if (hint == IncludeGuardStateHint_Endif)
m_includeGuardState = IncludeGuardState_AfterEndif;
break;
case IncludeGuardState_AfterEndif:
m_includeGuardState = IncludeGuardState_NoGuard;
m_includeGuardMacroName.clear();
break;
}
#ifdef DEBUG_INCLUDE_GUARD_TRACKING
qDebug() << "***" << guardStateToString(oldIncludeGuardState)
<< "->" << guardStateToString(m_includeGuardState)
<< "hint:" << hint
<< "guard:" << oldIncludeGuardMacroName << "->" << m_includeGuardMacroName;
#endif // DEBUG_INCLUDE_GUARD_TRACKING
}
const QString Preprocessor::configurationFileName = QLatin1String("<configuration>");
Preprocessor::Preprocessor(Client *client, Environment *env)
......@@ -618,8 +701,11 @@ QByteArray Preprocessor::run(const QString &fileName,
{
m_scratchBuffer.clear();
QByteArray preprocessed;
preprocess(fileName, source, &preprocessed, noLines, markGeneratedTokens, false);
QByteArray preprocessed, includeGuardMacroName;
preprocess(fileName, source, &preprocessed, &includeGuardMacroName, noLines,
markGeneratedTokens, false);
if (!includeGuardMacroName.isEmpty())
m_client->markAsIncludeGuard(includeGuardMacroName);
return preprocessed;
}
......@@ -736,6 +822,7 @@ _Lclassify:
} while (isContinuationToken(*tk));
goto _Lclassify;
} else if (tk->is(T_IDENTIFIER) && !isQtReservedWord(tk->asByteArrayRef())) {
m_state.updateIncludeGuardState(State::IncludeGuardStateHint_OtherToken);
if (m_state.m_inCondition && tk->asByteArrayRef() == "defined") {
handleDefined(tk);
} else {
......@@ -743,6 +830,8 @@ _Lclassify:
if (handleIdentifier(tk))
goto _Lagain;
}
} else if (tk->isNot(T_COMMENT) && tk->isNot(T_EOF_SYMBOL)) {
m_state.updateIncludeGuardState(State::IncludeGuardStateHint_OtherToken);
}
}
}
......@@ -1244,15 +1333,15 @@ void Preprocessor::enforceSpacing(const Preprocessor::PPToken &tk, bool forceSpa
/// invalid pp-tokens are used as markers to force whitespace checks.
void Preprocessor::preprocess(const QString &fileName, const QByteArray &source,
QByteArray *result, bool noLines,
QByteArray *result, QByteArray *includeGuardMacroName,
bool noLines,
bool markGeneratedTokens, bool inCondition,
unsigned offsetRef, unsigned lineRef)
{
if (source.isEmpty())
return;
const State savedState = m_state;
m_state = State();
ScopedSwap<State> savedState(m_state, State());
m_state.m_currentFileName = fileName;
m_state.m_source = source;
m_state.m_lexer = new Lexer(source.constBegin(), source.constEnd());
......@@ -1267,12 +1356,9 @@ void Preprocessor::preprocess(const QString &fileName, const QByteArray &source,
m_state.m_offsetRef = offsetRef;
m_state.m_lineRef = lineRef;
const QString previousFileName = m_env->currentFile;
m_env->currentFile = fileName;
m_env->currentFileUtf8 = fileName.toUtf8();
const unsigned previousCurrentLine = m_env->currentLine;
m_env->currentLine = 1;
ScopedSwap<QString> savedFileName(m_env->currentFile, fileName);
ScopedSwap<QByteArray> savedUtf8FileName(m_env->currentFileUtf8, fileName.toUtf8());
ScopedSwap<unsigned> savedCurrentLine(m_env->currentLine, 1);
if (!m_state.m_noLines)
generateOutputLineMarker(1);
......@@ -1313,14 +1399,14 @@ void Preprocessor::preprocess(const QString &fileName, const QByteArray &source,
removeTrailingOutputLines();
if (includeGuardMacroName) {
if (m_state.m_includeGuardState == State::IncludeGuardState_AfterDefine
|| m_state.m_includeGuardState == State::IncludeGuardState_AfterEndif)
*includeGuardMacroName = m_state.m_includeGuardMacroName;
}
delete m_state.m_lexer;
while (m_state.m_tokenBuffer)
m_state.popTokenBuffer();
m_state = savedState;
m_env->currentFile = previousFileName;
m_env->currentFileUtf8 = previousFileName.toUtf8();
m_env->currentLine = previousCurrentLine;
}
bool Preprocessor::collectActualArguments(PPToken *tk, QVector<QVector<PPToken> > *actuals)
......@@ -1426,27 +1512,31 @@ void Preprocessor::handlePreprocessorDirective(PPToken *tk)
if (tk->is(T_IDENTIFIER)) {
const ByteArrayRef directive = tk->asByteArrayRef();
if (!skipping() && directive == ppDefine)
if (!skipping() && directive == ppDefine) {
handleDefineDirective(tk);
else if (!skipping() && directive == ppUndef)
handleUndefDirective(tk);
else if (!skipping() && (directive == ppInclude
|| directive == ppImport))
handleIncludeDirective(tk, false);
else if (!skipping() && directive == ppIncludeNext)
handleIncludeDirective(tk, true);
else if (directive == ppIf)
handleIfDirective(tk);
else if (directive == ppIfDef)
handleIfDefDirective(false, tk);
else if (directive == ppIfNDef)
} else if (directive == ppIfNDef) {
handleIfDefDirective(true, tk);
else if (directive == ppEndIf)
} else if (directive == ppEndIf) {
handleEndIfDirective(tk, poundToken);
else if (directive == ppElse)
handleElseDirective(tk, poundToken);
else if (directive == ppElif)
handleElifDirective(tk, poundToken);
} else {
m_state.updateIncludeGuardState(State::IncludeGuardStateHint_OtherToken);
if (!skipping() && directive == ppUndef)
handleUndefDirective(tk);
else if (!skipping() && (directive == ppInclude
|| directive == ppImport))
handleIncludeDirective(tk, false);
else if (!skipping() && directive == ppIncludeNext)
handleIncludeDirective(tk, true);
else if (directive == ppIf)
handleIfDirective(tk);
else if (directive == ppIfDef)
handleIfDefDirective(false, tk);
else if (directive == ppElse)
handleElseDirective(tk, poundToken);
else if (directive == ppElif)
handleElifDirective(tk, poundToken);
}
}
skipPreprocesorDirective(tk);
......@@ -1506,6 +1596,8 @@ void Preprocessor::handleDefineDirective(PPToken *tk)
macro.setName(macroName);
macro.setOffset(tk->offset);
PPToken idToken(*tk);
lex(tk);
if (isContinuationToken(*tk) && tk->is(T_LPAREN) && ! tk->whitespace()) {
......@@ -1540,6 +1632,9 @@ void Preprocessor::handleDefineDirective(PPToken *tk)
}
if (isContinuationToken(*tk) && tk->is(T_RPAREN))
lex(tk); // consume ")" token
} else {
if (m_state.m_ifLevel == 1)
m_state.updateIncludeGuardState(State::IncludeGuardStateHint_Define, &idToken);
}
QVector<PPToken> bodyTokens;
......@@ -1636,7 +1731,7 @@ QByteArray Preprocessor::expand(PPToken *tk, PPToken *lastConditionToken)
// qDebug("*** Condition before: [%s]", condition.constData());
QByteArray result;
result.reserve(256);
preprocess(m_state.m_currentFileName, condition, &result, true, false, true, begin, line);
preprocess(m_state.m_currentFileName, condition, &result, 0, true, false, true, begin, line);
result.squeeze();
// qDebug("*** Condition after: [%s]", result.constData());
......@@ -1757,6 +1852,9 @@ void Preprocessor::handleEndIfDirective(PPToken *tk, const PPToken &poundToken)
--m_state.m_ifLevel;
if (m_client && wasSkipping && !m_state.m_skipping[m_state.m_ifLevel])
m_client->stopSkippingBlocks(poundToken.offset - 1);
if (m_state.m_ifLevel == 0)
m_state.updateIncludeGuardState(State::IncludeGuardStateHint_Endif);
}
lex(tk); // consume "endif" token
......@@ -1768,6 +1866,9 @@ void Preprocessor::handleIfDefDirective(bool checkUndefined, PPToken *tk)
lex(tk); // consume "ifdef" token
if (tk->is(T_IDENTIFIER)) {
if (checkUndefined && m_state.m_ifLevel == 0)
m_state.updateIncludeGuardState(State::IncludeGuardStateHint_Ifndef, tk);
bool value = false;
const ByteArrayRef macroName = tk->asByteArrayRef();
if (Macro *macro = macroDefinition(macroName, tk->offset, tk->lineno, m_env, m_client)) {
......
......@@ -81,7 +81,8 @@ public:
Preprocessor(Client *client, Environment *env);
QByteArray run(const QString &filename, const QString &source);
QByteArray run(const QString &filename, const QByteArray &source, bool noLines = false, bool markGeneratedTokens = true);
QByteArray run(const QString &filename, const QByteArray &source,
bool noLines = false, bool markGeneratedTokens = true);
bool expandFunctionlikeMacros() const;
void setExpandFunctionlikeMacros(bool expandFunctionlikeMacros);
......@@ -90,9 +91,9 @@ public:
void setKeepComments(bool keepComments);
private:
void preprocess(const QString &filename,
const QByteArray &source,
QByteArray *result, bool noLines, bool markGeneratedTokens, bool inCondition,
void preprocess(const QString &filename, const QByteArray &source,
QByteArray *result, QByteArray *includeGuardMacroName,
bool noLines, bool markGeneratedTokens, bool inCondition,
unsigned offsetRef = 0, unsigned lineRef = 1);
enum { MAX_LEVEL = 512 };
......@@ -133,6 +134,61 @@ private:
ExpansionStatus m_expansionStatus;
QByteArray m_expansionResult;
QVector<QPair<unsigned, unsigned> > m_expandedTokensInfo;
enum {
/// State to indicate that no guard is possible anymore.
IncludeGuardState_NoGuard = 0,
/// Initial state before the first non-comment token.
IncludeGuardState_BeforeIfndef,
/// State to indicate that the first interesting token was a
/// #ifndef token.
IncludeGuardState_AfterIfndef,
/// State to indicate that the only interesting tokens were
/// a #ifndef and a #define .
IncludeGuardState_AfterDefine,
/// State for after reading the #endif belonging to the #ifndef
IncludeGuardState_AfterEndif
} m_includeGuardState;
QByteArray m_includeGuardMacroName;
enum IncludeGuardStateHint {
/// anything that is not a comment, a #ifndef, a #define, or a
/// #endif
IncludeGuardStateHint_OtherToken = 0,
/// we hit a #ifndef
IncludeGuardStateHint_Ifndef,
/// we hit a #define
IncludeGuardStateHint_Define,
/// we hit a #endif
IncludeGuardStateHint_Endif
};
/// Update the include-guard state.
///
/// \param hint indicates what kind of token is encountered in the input
/// \param idToken the identifier token that ought to be in the input
/// after a #ifndef or a #define .
inline void updateIncludeGuardState(IncludeGuardStateHint hint,
PPToken *idToken = 0)
{
// some quick checks for the majority of the uninteresting cases:
if (m_includeGuardState == IncludeGuardState_NoGuard)
return; // no valid guard is possible
if (m_includeGuardState == IncludeGuardState_AfterDefine
&& hint == IncludeGuardStateHint_OtherToken)
return; // after the #define of the guard, and before the
// #endif, any non-endif token won't change the state
if (m_inCondition)
return; // include guards can never occur in pp-conditions
updateIncludeGuardState_helper(hint, idToken);
}
private:
#ifdef DEBUG_INCLUDE_GUARD_TRACKING
static QString guardStateToString(int guardState);
#endif // DEBUG_INCLUDE_GUARD_TRACKING
void updateIncludeGuardState_helper(IncludeGuardStateHint hint, PPToken *idToken);
};
void handleDefined(PPToken *tk);
......
......@@ -499,6 +499,14 @@ void CppPreprocessor::stopExpandingMacro(unsigned, const Macro &)
//qDebug() << "stop expanding:" << macro.name;
}
void CppPreprocessor::markAsIncludeGuard(const QByteArray &macroName)
{
if (!m_currentDoc)
return;
m_currentDoc->setIncludeGuardMacroName(macroName);
}
void CppPreprocessor::mergeEnvironment(Document::Ptr doc)
{
if (! doc)
......
......@@ -296,6 +296,7 @@ protected:
const CPlusPlus::Macro &macro,
const QVector<CPlusPlus::MacroArgumentReference> &actuals);
virtual void stopExpandingMacro(unsigned offset, const CPlusPlus::Macro &macro);
virtual void markAsIncludeGuard(const QByteArray &macroName);
virtual void startSkippingBlocks(unsigned offset);
virtual void stopSkippingBlocks(unsigned offset);
virtual void sourceNeeded(unsigned line, QString &fileName, IncludeType type);
......
......@@ -216,6 +216,12 @@ public:
*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; }
......@@ -246,6 +252,7 @@ private:
Preprocessor m_pp;
QList<QDir> m_includePaths;
unsigned m_includeDepth;
QByteArray m_includeGuardMacro;
QList<Block> m_skippedBlocks;
QList<Include> m_recordedIncludes;
QList<QByteArray> m_expandedMacros;
......@@ -340,6 +347,8 @@ private slots:
void multiline_strings_data();
void skip_unknown_directives();
void skip_unknown_directives_data();
void include_guard();
void include_guard_data();
};
// Remove all #... lines, and 'simplify' string, to allow easily comparing the result
......@@ -1481,6 +1490,88 @@ void tst_Preprocessor::skip_unknown_directives_data()
QTest::newRow("case 1") << original << expected;
}
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::compare_input_output(bool keepComments)
{
QFETCH(QByteArray, input);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment