From 3b99677b3376924e347ec4aeb0c363438146bfde Mon Sep 17 00:00:00 2001 From: Kevin Ottens Date: Wed, 1 Aug 2012 15:38:03 +0200 Subject: [PATCH] Properly handle include cycles Currently lupdate is just issuing a warning when it detects an include cycle. Since it's not a full preprocessor, most of those warnings are false positives, but they need special handling, this patch provides that. Now every result is stored as part of a cycle (so in the general case a cycle of one file). When detecting a cycle, instead instead of issuing a warning, we record the presence of the newly detected cycle for later use and merge the other relevant cycles with it. When retrieving results now it's not for a single file anymore but for a complete cycle at once (which can turn out to be a cycle of one file). Task-number: QTBUG-6587 Change-Id: Ia12bc680e645b9704a3178e8a843a554a60811bf Reviewed-by: Oswald Buddenhagen --- src/linguist/lupdate/cpp.cpp | 103 +++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 28 deletions(-) diff --git a/src/linguist/lupdate/cpp.cpp b/src/linguist/lupdate/cpp.cpp index f378f27..dd6a4ea 100644 --- a/src/linguist/lupdate/cpp.cpp +++ b/src/linguist/lupdate/cpp.cpp @@ -177,21 +177,27 @@ struct ParseResults { QSet includes; }; -typedef QHash ParseResultHash; +struct IncludeCycle { + QSet fileNames; + QSet results; +}; + +typedef QHash IncludeCycleHash; typedef QHash TranslatorHash; class CppFiles { public: - static const ParseResults *getResults(const QString &cleanFile); + static QSet getResults(const QString &cleanFile); static void setResults(const QString &cleanFile, const ParseResults *results); static const Translator *getTranslator(const QString &cleanFile); static void setTranslator(const QString &cleanFile, const Translator *results); static bool isBlacklisted(const QString &cleanFile); static void setBlacklisted(const QString &cleanFile); + static void addIncludeCycle(const QSet &fileNames); private: - static ParseResultHash &parsedFiles(); + static IncludeCycleHash &includeCycles(); static TranslatorHash &translatedFiles(); static QSet &blacklistedFiles(); }; @@ -203,8 +209,8 @@ public: void setInput(const QString &in); void setInput(QTextStream &ts, const QString &fileName); void setTranslator(Translator *_tor) { tor = _tor; } - void parse(const QString &initialContext, ConversionData &cd, QSet &inclusions); - void parseInternal(ConversionData &cd, QSet &inclusions); + void parse(const QString &initialContext, ConversionData &cd, const QStringList &includeStack, QSet &inclusions); + void parseInternal(ConversionData &cd, const QStringList &includeStack, QSet &inclusions); const ParseResults *recordResults(bool isHeader); void deleteResults() { delete results; } @@ -251,7 +257,7 @@ private: bool utf8, bool plural); void processInclude(const QString &file, ConversionData &cd, - QSet &inclusions); + const QStringList &includeStack, QSet &inclusions); void saveState(SavedState *state); void loadState(const SavedState *state); @@ -1253,11 +1259,11 @@ void CppParser::truncateNamespaces(NamespaceList *namespaces, int length) Functions for processing include files. */ -ParseResultHash &CppFiles::parsedFiles() +IncludeCycleHash &CppFiles::includeCycles() { - static ParseResultHash parsed; + static IncludeCycleHash cycles; - return parsed; + return cycles; } TranslatorHash &CppFiles::translatedFiles() @@ -1274,14 +1280,27 @@ QSet &CppFiles::blacklistedFiles() return blacklisted; } -const ParseResults *CppFiles::getResults(const QString &cleanFile) +QSet CppFiles::getResults(const QString &cleanFile) { - return parsedFiles().value(cleanFile); + IncludeCycle * const cycle = includeCycles().value(cleanFile); + + if (cycle) + return cycle->results; + else + return QSet(); } void CppFiles::setResults(const QString &cleanFile, const ParseResults *results) { - parsedFiles().insert(cleanFile, results); + IncludeCycle *cycle = includeCycles().value(cleanFile); + + if (!cycle) { + cycle = new IncludeCycle; + includeCycles().insert(cleanFile, cycle); + } + + cycle->fileNames.insert(cleanFile); + cycle->results.insert(results); } const Translator *CppFiles::getTranslator(const QString &cleanFile) @@ -1304,19 +1323,42 @@ void CppFiles::setBlacklisted(const QString &cleanFile) blacklistedFiles().insert(cleanFile); } +void CppFiles::addIncludeCycle(const QSet &fileNames) +{ + IncludeCycle * const cycle = new IncludeCycle; + cycle->fileNames = fileNames; + + QSet intersectingCycles; + foreach (const QString &fileName, fileNames) { + IncludeCycle *intersectingCycle = includeCycles().value(fileName); + + if (intersectingCycle && !intersectingCycles.contains(intersectingCycle)) { + intersectingCycles.insert(intersectingCycle); + + cycle->fileNames.unite(intersectingCycle->fileNames); + cycle->results.unite(intersectingCycle->results); + } + } + qDeleteAll(intersectingCycles); + + foreach (const QString &fileName, cycle->fileNames) + includeCycles().insert(fileName, cycle); +} + static bool isHeader(const QString &name) { QString fileExt = QFileInfo(name).suffix(); return fileExt.isEmpty() || fileExt.startsWith(QLatin1Char('h'), Qt::CaseInsensitive); } -void CppParser::processInclude(const QString &file, ConversionData &cd, +void CppParser::processInclude(const QString &file, ConversionData &cd, const QStringList &includeStack, QSet &inclusions) { QString cleanFile = QDir::cleanPath(file); - if (inclusions.contains(cleanFile)) { - yyMsg() << qPrintable(LU::tr("circular inclusion of %1\n").arg(cleanFile)); + const int index = includeStack.indexOf(cleanFile); + if (index != -1) { + CppFiles::addIncludeCycle(includeStack.mid(index).toSet()); return; } @@ -1330,8 +1372,9 @@ void CppParser::processInclude(const QString &file, ConversionData &cd, && !CppFiles::isBlacklisted(cleanFile) && isHeader(cleanFile)) { - if (const ParseResults *res = CppFiles::getResults(cleanFile)) { - results->includes.insert(res); + QSet res = CppFiles::getResults(cleanFile); + if (!res.isEmpty()) { + results->includes.unite(res); return; } @@ -1357,7 +1400,9 @@ void CppParser::processInclude(const QString &file, ConversionData &cd, break; } parser.setInput(ts, cleanFile); - parser.parse(cd.m_defaultContext, cd, inclusions); + QStringList stack = includeStack; + stack << cleanFile; + parser.parse(cd.m_defaultContext, cd, stack, inclusions); results->includes.insert(parser.recordResults(true)); } else { CppParser parser(results); @@ -1366,7 +1411,9 @@ void CppParser::processInclude(const QString &file, ConversionData &cd, parser.functionContextUnresolved = functionContextUnresolved; parser.pendingContext = pendingContext; parser.setInput(ts, cleanFile); - parser.parseInternal(cd, inclusions); + QStringList stack = includeStack; + stack << cleanFile; + parser.parseInternal(cd, stack, inclusions); // Avoid that messages obtained by direct scanning are used CppFiles::setBlacklisted(cleanFile); } @@ -1548,7 +1595,7 @@ void CppParser::recordMessage( tor->append(msg); } -void CppParser::parse(const QString &initialContext, ConversionData &cd, +void CppParser::parse(const QString &initialContext, ConversionData &cd, const QStringList &includeStack, QSet &inclusions) { if (tor) @@ -1558,10 +1605,10 @@ void CppParser::parse(const QString &initialContext, ConversionData &cd, functionContext = namespaces; functionContextUnresolved = initialContext; - parseInternal(cd, inclusions); + parseInternal(cd, includeStack, inclusions); } -void CppParser::parseInternal(ConversionData &cd, QSet &inclusions) +void CppParser::parseInternal(ConversionData &cd, const QStringList &includeStack, QSet &inclusions) { static QString strColons(QLatin1String("::")); @@ -1597,7 +1644,7 @@ void CppParser::parseInternal(ConversionData &cd, QSet &inclusions) text = QDir(QFileInfo(yyFileName).absolutePath()).absoluteFilePath(yyWord); text.detach(); if (QFileInfo(text).isFile()) { - processInclude(text, cd, inclusions); + processInclude(text, cd, includeStack, inclusions); yyTok = getToken(); break; } @@ -1607,14 +1654,14 @@ void CppParser::parseInternal(ConversionData &cd, QSet &inclusions) QStringList cSources = cd.m_allCSources.values(yyWord); if (!cSources.isEmpty()) { foreach (const QString &cSource, cSources) - processInclude(cSource, cd, inclusions); + processInclude(cSource, cd, includeStack, inclusions); goto incOk; } foreach (const QString &incPath, cd.m_includePath) { text = QDir(incPath).absoluteFilePath(yyWord); text.detach(); if (QFileInfo(text).isFile()) { - processInclude(text, cd, inclusions); + processInclude(text, cd, includeStack, inclusions); goto incOk; } } @@ -2200,7 +2247,7 @@ void fetchtrInlinedCpp(const QString &in, Translator &translator, const QString ConversionData cd; QSet inclusions; parser.setTranslator(&translator); - parser.parse(context, cd, inclusions); + parser.parse(context, cd, QStringList(), inclusions); parser.deleteResults(); } @@ -2211,7 +2258,7 @@ void loadCPP(Translator &translator, const QStringList &filenames, ConversionDat QTextCodec *codec = QTextCodec::codecForName(codecName); foreach (const QString &filename, filenames) { - if (CppFiles::getResults(filename) || CppFiles::isBlacklisted(filename)) + if (!CppFiles::getResults(filename).isEmpty() || CppFiles::isBlacklisted(filename)) continue; QFile file(filename); @@ -2231,7 +2278,7 @@ void loadCPP(Translator &translator, const QStringList &filenames, ConversionDat tor->setCodecName(translator.codecName()); parser.setTranslator(tor); QSet inclusions; - parser.parse(cd.m_defaultContext, cd, inclusions); + parser.parse(cd.m_defaultContext, cd, QStringList(), inclusions); parser.recordResults(isHeader(filename)); } -- 2.7.4