aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs
diff options
context:
space:
mode:
authorErik Verbruggen <[email protected]>2012-12-21 17:08:21 +0100
committerErik Verbruggen <[email protected]>2013-01-18 14:58:07 +0100
commitdada2614d539136bf420b4d18c6e212be193eae6 (patch)
tree7429d54e60c4a32b3b73389c40bedb7f32470d61 /src/libs
parent4c43655cec5c117f8438f795846290e569b3727e (diff)
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: Nikolai Kosjar <[email protected]>
Diffstat (limited to 'src/libs')
-rw-r--r--src/libs/cplusplus/CppDocument.h12
-rw-r--r--src/libs/cplusplus/FastPreprocessor.h1
-rw-r--r--src/libs/cplusplus/PreprocessorClient.h3
-rw-r--r--src/libs/cplusplus/cplusplus-lib.pri2
-rw-r--r--src/libs/cplusplus/pp-engine.cpp173
-rw-r--r--src/libs/cplusplus/pp-engine.h64
6 files changed, 215 insertions, 40 deletions
diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h
index 484a1b3d9e9..3a537478ed1 100644
--- a/src/libs/cplusplus/CppDocument.h
+++ b/src/libs/cplusplus/CppDocument.h
@@ -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;
diff --git a/src/libs/cplusplus/FastPreprocessor.h b/src/libs/cplusplus/FastPreprocessor.h
index 14168ad2e6b..223acaa05f0 100644
--- a/src/libs/cplusplus/FastPreprocessor.h
+++ b/src/libs/cplusplus/FastPreprocessor.h
@@ -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) {}
diff --git a/src/libs/cplusplus/PreprocessorClient.h b/src/libs/cplusplus/PreprocessorClient.h
index f7e9932dd2d..f7f18a75cb3 100644
--- a/src/libs/cplusplus/PreprocessorClient.h
+++ b/src/libs/cplusplus/PreprocessorClient.h
@@ -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;
diff --git a/src/libs/cplusplus/cplusplus-lib.pri b/src/libs/cplusplus/cplusplus-lib.pri
index f5d1b0a5112..a2ccabdfb42 100644
--- a/src/libs/cplusplus/cplusplus-lib.pri
+++ b/src/libs/cplusplus/cplusplus-lib.pri
@@ -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 \
diff --git a/src/libs/cplusplus/pp-engine.cpp b/src/libs/cplusplus/pp-engine.cpp
index 705ea1b1a38..b1571f0c201 100644
--- a/src/libs/cplusplus/pp-engine.cpp
+++ b/src/libs/cplusplus/pp-engine.cpp
@@ -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)) {
diff --git a/src/libs/cplusplus/pp-engine.h b/src/libs/cplusplus/pp-engine.h
index 23ea7b84f9f..a3189f18183 100644
--- a/src/libs/cplusplus/pp-engine.h
+++ b/src/libs/cplusplus/pp-engine.h
@@ -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);