From ba2d7a4fa7c29ea2d62da3d6f6835a091f604656 Mon Sep 17 00:00:00 2001 From: Erik Verbruggen <erik.verbruggen@digia.com> Date: Mon, 19 Aug 2013 16:05:29 +0200 Subject: [PATCH] C++: Only parse with appropriate defines for open editors. If two files from different (sub-)projects include the same header file, and the defined macros differ for both files, the header file will be parsed with only the appropriate macros for the including file. Task-number: QTCREATORBUG-9802 Task-number: QTCREATORBUG-1249 Change-Id: I560490afa287b3bb1e863bce1bb4f57af36ad56e Reviewed-by: Nikolai Kosjar <nikolai.kosjar@digia.com> --- src/libs/cplusplus/CppDocument.h | 6 + src/libs/cplusplus/DependencyTable.cpp | 4 +- src/plugins/cppeditor/cppeditor.cpp | 7 +- src/plugins/cpptools/cppcompletionassist.cpp | 33 +-- src/plugins/cpptools/cppcompletionassist.h | 4 +- .../cpptools/cppcompletionassistprovider.h | 5 +- src/plugins/cpptools/cppmodelmanager.cpp | 24 ++- src/plugins/cpptools/cppmodelmanager.h | 10 +- src/plugins/cpptools/cppmodelmanager_test.cpp | 113 +++++++++- .../cpptools/cppmodelmanagerinterface.h | 2 + src/plugins/cpptools/cpppreprocessor.cpp | 47 ++++- src/plugins/cpptools/cpppreprocessor.h | 8 + src/plugins/cpptools/cppsnapshotupdater.cpp | 194 ++++++++++++++++++ src/plugins/cpptools/cppsnapshotupdater.h | 84 ++++++++ src/plugins/cpptools/cpptools.pro | 2 + src/plugins/cpptools/cpptools.qbs | 4 +- .../cpptools/cpptoolseditorsupport.cpp | 37 +++- src/plugins/cpptools/cpptoolseditorsupport.h | 8 + src/plugins/cpptools/cpptoolsplugin.h | 1 + .../cppmodelmanager/testdata_defines/header.h | 7 + .../testdata_defines/main1.cpp | 9 + .../testdata_defines/main2.cpp | 9 + 22 files changed, 570 insertions(+), 48 deletions(-) create mode 100644 src/plugins/cpptools/cppsnapshotupdater.cpp create mode 100644 src/plugins/cpptools/cppsnapshotupdater.h create mode 100644 tests/cppmodelmanager/testdata_defines/header.h create mode 100644 tests/cppmodelmanager/testdata_defines/main1.cpp create mode 100644 tests/cppmodelmanager/testdata_defines/main2.cpp diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h index 071f9485e83..886f2bed422 100644 --- a/src/libs/cplusplus/CppDocument.h +++ b/src/libs/cplusplus/CppDocument.h @@ -104,6 +104,10 @@ public: QByteArray utf8Source() const; void setUtf8Source(const QByteArray &utf8Source); + QByteArray fingerprint() const { return m_fingerprint; } + void setFingerprint(const QByteArray &fingerprint) + { m_fingerprint = fingerprint; } + void startSkippingBlocks(unsigned offset); void stopSkippingBlocks(unsigned offset); @@ -361,6 +365,8 @@ private: /// the macro name of the include guard, if there is one. QByteArray _includeGuardMacroName; + QByteArray m_fingerprint; + QByteArray _source; QDateTime _lastModified; QAtomicInt _keepSourceAndASTCount; diff --git a/src/libs/cplusplus/DependencyTable.cpp b/src/libs/cplusplus/DependencyTable.cpp index e1a4bf3b738..6d3cc0651ea 100644 --- a/src/libs/cplusplus/DependencyTable.cpp +++ b/src/libs/cplusplus/DependencyTable.cpp @@ -37,10 +37,8 @@ using namespace CPlusPlus; QStringList DependencyTable::filesDependingOn(const QString &fileName) const { int index = fileIndex.value(fileName, -1); - if (index == -1) { - qWarning() << fileName << "not in the snapshot"; + if (index == -1) return QStringList(); - } QStringList deps; for (int i = 0; i < files.size(); ++i) { diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index 8475ce10d17..f037abc8d3d 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -2107,10 +2107,11 @@ TextEditor::IAssistInterface *CPPEditorWidget::createAssistInterface( if (kind == TextEditor::Completion) { CppEditorSupport *ces = CppModelManagerInterface::instance()->cppEditorSupport(editor()); CppCompletionAssistProvider *cap = ces->completionAssistProvider(); - if (cap) + if (cap) { return cap->createAssistInterface( - ProjectExplorer::ProjectExplorerPlugin::currentProject(), - editor()->document()->filePath(), document(), position(), reason); + ProjectExplorer::ProjectExplorerPlugin::currentProject(), + editor(), document(), position(), reason); + } } else if (kind == TextEditor::QuickFix) { if (!semanticInfo().doc || isOutdated()) return 0; diff --git a/src/plugins/cpptools/cppcompletionassist.cpp b/src/plugins/cpptools/cppcompletionassist.cpp index 4a9ea061226..59d2b8aa534 100644 --- a/src/plugins/cpptools/cppcompletionassist.cpp +++ b/src/plugins/cpptools/cppcompletionassist.cpp @@ -30,6 +30,7 @@ #include "cppcompletionassist.h" #include "cppmodelmanager.h" #include "cpptoolsconstants.h" +#include "cpptoolseditorsupport.h" #include "cppdoxygen.h" #include <coreplugin/icore.h> @@ -416,24 +417,28 @@ IAssistProcessor *InternalCompletionAssistProvider::createProcessor() const } TextEditor::IAssistInterface *InternalCompletionAssistProvider::createAssistInterface( - ProjectExplorer::Project *project, const QString &filePath, QTextDocument *document, + ProjectExplorer::Project *project, BaseTextEditor *editor, QTextDocument *document, int position, TextEditor::AssistReason reason) const { + Q_UNUSED(project); + CppModelManagerInterface *modelManager = CppModelManagerInterface::instance(); - QStringList includePaths; - QStringList frameworkPaths; - if (project) { - includePaths = modelManager->projectInfo(project).includePaths(); - frameworkPaths = modelManager->projectInfo(project).frameworkPaths(); + + if (CppEditorSupport *supp = modelManager->cppEditorSupport(editor)) { + if (QSharedPointer<SnapshotUpdater> updater = supp->snapshotUpdater()) { + updater->update(modelManager->workingCopy()); + return new CppTools::Internal::CppCompletionAssistInterface( + document, + position, + editor->document()->filePath(), + reason, + updater->snapshot(), + updater->includePaths(), + updater->frameworkPaths()); + } } - return new CppTools::Internal::CppCompletionAssistInterface( - document, - position, - filePath, - reason, - modelManager->snapshot(), - includePaths, - frameworkPaths); + + return 0; } // ----------------- diff --git a/src/plugins/cpptools/cppcompletionassist.h b/src/plugins/cpptools/cppcompletionassist.h index a78777a8637..6345d895b25 100644 --- a/src/plugins/cpptools/cppcompletionassist.h +++ b/src/plugins/cpptools/cppcompletionassist.h @@ -91,8 +91,8 @@ public: virtual TextEditor::IAssistProcessor *createProcessor() const; virtual TextEditor::IAssistInterface *createAssistInterface( - ProjectExplorer::Project *project, const QString &filePath, QTextDocument *document, - int position, TextEditor::AssistReason reason) const; + ProjectExplorer::Project *project, TextEditor::BaseTextEditor *editor, + QTextDocument *document, int position, TextEditor::AssistReason reason) const; }; diff --git a/src/plugins/cpptools/cppcompletionassistprovider.h b/src/plugins/cpptools/cppcompletionassistprovider.h index e45b20946fb..e13a473e945 100644 --- a/src/plugins/cpptools/cppcompletionassistprovider.h +++ b/src/plugins/cpptools/cppcompletionassistprovider.h @@ -44,6 +44,7 @@ class Project; } namespace TextEditor { +class BaseTextEditor; class IAssistInterface; } @@ -59,8 +60,8 @@ public: virtual bool isActivationCharSequence(const QString &sequence) const; virtual TextEditor::IAssistInterface *createAssistInterface( - ProjectExplorer::Project *project, const QString &filePath, QTextDocument *document, - int position, TextEditor::AssistReason reason) const = 0; + ProjectExplorer::Project *project, TextEditor::BaseTextEditor *editor, + QTextDocument *document, int position, TextEditor::AssistReason reason) const = 0; static int activationSequenceChar(const QChar &ch, const QChar &ch2, const QChar &ch3, unsigned *kind, diff --git a/src/plugins/cpptools/cppmodelmanager.cpp b/src/plugins/cpptools/cppmodelmanager.cpp index 4fb3f381e2c..4c0c5ecba7d 100644 --- a/src/plugins/cpptools/cppmodelmanager.cpp +++ b/src/plugins/cpptools/cppmodelmanager.cpp @@ -549,7 +549,7 @@ CppModelManager::WorkingCopy CppModelManager::buildWorkingCopyList() } // Add the project configuration file - QByteArray conf = QByteArray::fromRawData(pp_configuration, qstrlen(pp_configuration)); + QByteArray conf = codeModelConfiguration(); conf += definedMacros(); workingCopy.insert(configurationFileName(), conf); @@ -561,6 +561,11 @@ CppModelManager::WorkingCopy CppModelManager::workingCopy() const return const_cast<CppModelManager *>(this)->buildWorkingCopyList(); } +QByteArray CppModelManager::codeModelConfiguration() const +{ + return QByteArray::fromRawData(pp_configuration, qstrlen(pp_configuration)); +} + QFuture<void> CppModelManager::updateSourceFiles(const QStringList &sourceFiles, ProgressNotificationMode mode) { @@ -760,20 +765,19 @@ QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo &newProjectIn QList<ProjectPart::Ptr> CppModelManager::projectPart(const QString &fileName) const { - QList<ProjectPart::Ptr> parts = m_fileToProjectParts.value(fileName); - if (!parts.isEmpty()) - return parts; + return m_fileToProjectParts.value(fileName); +} +QList<ProjectPart::Ptr> CppModelManager::projectPartFromDependencies(const QString &fileName) const +{ + QSet<ProjectPart::Ptr> parts; DependencyTable table; table.build(snapshot()); const QStringList deps = table.filesDependingOn(fileName); - foreach (const QString &dep, deps) { - parts = m_fileToProjectParts.value(dep); - if (!parts.isEmpty()) - return parts; - } + foreach (const QString &dep, deps) + parts.unite(QSet<ProjectPart::Ptr>::fromList(m_fileToProjectParts.value(dep))); - return parts; + return parts.values(); } ProjectPart::Ptr CppModelManager::fallbackProjectPart() const diff --git a/src/plugins/cpptools/cppmodelmanager.h b/src/plugins/cpptools/cppmodelmanager.h index 2ec1423e3a8..e86875a63fb 100644 --- a/src/plugins/cpptools/cppmodelmanager.h +++ b/src/plugins/cpptools/cppmodelmanager.h @@ -69,11 +69,19 @@ public: virtual QFuture<void> updateSourceFiles(const QStringList &sourceFiles, ProgressNotificationMode mode = ReservedProgressNotification); virtual WorkingCopy workingCopy() const; + virtual QByteArray codeModelConfiguration() const; virtual QList<ProjectInfo> projectInfos() const; virtual ProjectInfo projectInfo(ProjectExplorer::Project *project) const; virtual QFuture<void> updateProjectInfo(const ProjectInfo &newProjectInfo); - virtual QList<CppTools::ProjectPart::Ptr> projectPart(const QString &fileName) const; + + /// \return All project parts that mention the given file name as one of the sources/headers. + virtual QList<ProjectPart::Ptr> projectPart(const QString &fileName) const; + /// This is a fall-back function: find all files that includes the file directly or indirectly, + /// and return its \c ProjectPart list for use with this file. + virtual QList<ProjectPart::Ptr> projectPartFromDependencies(const QString &fileName) const; + /// \return A synthetic \c ProjectPart which consists of all defines/includes/frameworks from + /// all loaded projects. virtual ProjectPart::Ptr fallbackProjectPart() const; virtual CPlusPlus::Snapshot snapshot() const; diff --git a/src/plugins/cpptools/cppmodelmanager_test.cpp b/src/plugins/cpptools/cppmodelmanager_test.cpp index 7482cd32224..d9e8676ec08 100644 --- a/src/plugins/cpptools/cppmodelmanager_test.cpp +++ b/src/plugins/cpptools/cppmodelmanager_test.cpp @@ -28,8 +28,8 @@ ****************************************************************************/ #include "cpptoolsplugin.h" - #include "cpppreprocessor.h" +#include "cpptoolseditorsupport.h" #include "modelmanagertesthelper.h" #include <coreplugin/editormanager/editormanager.h> @@ -722,8 +722,8 @@ void CppToolsPlugin::test_modelmanager_gc_if_last_cppeditor_closed() QVERIFY(mm->isCppEditor(editor)); QVERIFY(mm->workingCopy().contains(file)); - // Check: File is in the snapshot - QVERIFY(mm->snapshot().contains(file)); + // Wait until the file is refreshed + helper.waitForRefreshedSourceFiles(); // Close file/editor Core::Tests::closeAndDeleteEditor(editor); @@ -751,9 +751,9 @@ void CppToolsPlugin::test_modelmanager_dont_gc_opened_files() QCOMPARE(Core::EditorManager::documentModel()->openedDocuments().size(), 1); QVERIFY(mm->isCppEditor(editor)); - // Check: File is in the working copy and snapshot + // Wait until the file is refreshed and check whether it is in the working copy + helper.waitForRefreshedSourceFiles(); QVERIFY(mm->workingCopy().contains(file)); - QVERIFY(mm->snapshot().contains(file)); // Run the garbage collector mm->GC(); @@ -767,3 +767,106 @@ void CppToolsPlugin::test_modelmanager_dont_gc_opened_files() helper.waitForFinishedGc(); QVERIFY(mm->snapshot().isEmpty()); } + +namespace { +struct EditorCloser { + Core::IEditor *editor; + EditorCloser(Core::IEditor *editor): editor(editor) {} + ~EditorCloser() + { + if (editor) + Core::EditorManager::closeEditors(QList<Core::IEditor*>() << editor); + } +}; +} + +void CppToolsPlugin::test_modelmanager_defines_per_project() +{ + ModelManagerTestHelper helper; + + MyTestDataDir testDataDirectory(QLatin1String("testdata_defines")); + const QString main1File = testDataDirectory.file(QLatin1String("main1.cpp")); + const QString main2File = testDataDirectory.file(QLatin1String("main2.cpp")); + const QString header = testDataDirectory.file(QLatin1String("header.h")); + + CppModelManager *mm = CppModelManager::instance(); + + Project *project = helper.createProject(QLatin1String("test_modelmanager_defines_per_project")); + + ProjectPart::Ptr part1(new ProjectPart); + part1->files.append(ProjectFile(main1File, ProjectFile::CXXSource)); + part1->files.append(ProjectFile(header, ProjectFile::CXXHeader)); + part1->cxxVersion = ProjectPart::CXX11; + part1->qtVersion = ProjectPart::NoQt; + part1->defines = QByteArray("#define SUB1\n"); + part1->includePaths = QStringList() << testDataDirectory.includeDir(false); + + ProjectPart::Ptr part2(new ProjectPart); + part2->files.append(ProjectFile(main2File, ProjectFile::CXXSource)); + part2->files.append(ProjectFile(header, ProjectFile::CXXHeader)); + part2->cxxVersion = ProjectPart::CXX11; + part2->qtVersion = ProjectPart::NoQt; + part2->defines = QByteArray("#define SUB2\n"); + part2->includePaths = QStringList() << testDataDirectory.includeDir(false); + + ProjectInfo pi = mm->projectInfo(project); + pi.appendProjectPart(part1); + pi.appendProjectPart(part2); + + mm->updateProjectInfo(pi); + + helper.waitForRefreshedSourceFiles(); + + QCOMPARE(mm->snapshot().size(), 4); + + // Open a file in the editor + QCOMPARE(Core::EditorManager::documentModel()->openedDocuments().size(), 0); + + { + Core::IEditor *editor = Core::EditorManager::openEditor(main1File); + EditorCloser closer(editor); + QVERIFY(editor); + QCOMPARE(Core::EditorManager::documentModel()->openedDocuments().size(), 1); + QVERIFY(mm->isCppEditor(editor)); + + CppEditorSupport *sup = mm->cppEditorSupport( + qobject_cast<TextEditor::BaseTextEditor *>(editor)); + while (sup->lastSemanticInfoDocument().isNull()) + QCoreApplication::processEvents(); + + Document::Ptr doc = mm->snapshot().document(main1File); + QVERIFY(doc); + QVERIFY(doc->globalNamespace()); + QCOMPARE(doc->globalSymbolCount(), 1U); + CPlusPlus::Symbol *s = doc->globalSymbolAt(0); + QVERIFY(s); + CPlusPlus::Declaration *decl = s->asDeclaration(); + QVERIFY(decl); + QVERIFY(decl->type()->isIntegerType()); + QCOMPARE(decl->name()->identifier()->chars(), "one"); + } + + { + Core::IEditor *editor = Core::EditorManager::openEditor(main2File); + EditorCloser closer(editor); + QVERIFY(editor); + QCOMPARE(Core::EditorManager::documentModel()->openedDocuments().size(), 1); + QVERIFY(mm->isCppEditor(editor)); + + CppEditorSupport *sup = mm->cppEditorSupport( + qobject_cast<TextEditor::BaseTextEditor *>(editor)); + while (sup->lastSemanticInfoDocument().isNull()) + QCoreApplication::processEvents(); + + Document::Ptr doc = mm->snapshot().document(main2File); + QVERIFY(doc); + QVERIFY(doc->globalNamespace()); + QCOMPARE(doc->globalSymbolCount(), 1U); + CPlusPlus::Symbol *s = doc->globalSymbolAt(0); + QVERIFY(s); + CPlusPlus::Declaration *decl = s->asDeclaration(); + QVERIFY(decl); + QVERIFY(decl->type()->isIntegerType()); + QCOMPARE(decl->name()->identifier()->chars(), "two"); + } +} diff --git a/src/plugins/cpptools/cppmodelmanagerinterface.h b/src/plugins/cpptools/cppmodelmanagerinterface.h index 4bcf1c60591..e327a9d0ae8 100644 --- a/src/plugins/cpptools/cppmodelmanagerinterface.h +++ b/src/plugins/cpptools/cppmodelmanagerinterface.h @@ -223,12 +223,14 @@ public: virtual bool isCppEditor(Core::IEditor *editor) const = 0; virtual WorkingCopy workingCopy() const = 0; + virtual QByteArray codeModelConfiguration() const = 0; virtual CPlusPlus::Snapshot snapshot() const = 0; virtual QList<ProjectInfo> projectInfos() const = 0; virtual ProjectInfo projectInfo(ProjectExplorer::Project *project) const = 0; virtual QFuture<void> updateProjectInfo(const ProjectInfo &pinfo) = 0; virtual QList<ProjectPart::Ptr> projectPart(const QString &fileName) const = 0; + virtual QList<ProjectPart::Ptr> projectPartFromDependencies(const QString &fileName) const = 0; virtual ProjectPart::Ptr fallbackProjectPart() const = 0; virtual QStringList includePaths() = 0; diff --git a/src/plugins/cpptools/cpppreprocessor.cpp b/src/plugins/cpptools/cpppreprocessor.cpp index 48cb7fc77ce..1e3f5b1bf02 100644 --- a/src/plugins/cpptools/cpppreprocessor.cpp +++ b/src/plugins/cpptools/cpppreprocessor.cpp @@ -6,6 +6,7 @@ #include <utils/textfileformat.h> #include <QCoreApplication> +#include <QCryptographicHash> /*! * \class CppTools::Internal::CppPreprocessor @@ -34,6 +35,17 @@ CppPreprocessor::CppPreprocessor(QPointer<CppModelManager> modelManager, m_preprocess.setKeepComments(true); } +CppPreprocessor::CppPreprocessor(QPointer<CppModelManager> modelManager, const Snapshot &snapshot, + bool dumpFileNameWhileParsing) + : m_snapshot(snapshot), + m_modelManager(modelManager), + m_dumpFileNameWhileParsing(dumpFileNameWhileParsing), + m_preprocess(this, &m_env), + m_revision(0) +{ + m_preprocess.setKeepComments(true); +} + CppPreprocessor::~CppPreprocessor() { } @@ -129,10 +141,10 @@ public: { _doc->check(_mode); - if (_modelManager) + if (_modelManager) { _modelManager->emitDocumentUpdated(_doc); - - _doc->releaseSourceAndAST(); + _doc->releaseSourceAndAST(); + } } }; } // end of anonymous namespace @@ -398,7 +410,7 @@ void CppPreprocessor::sourceNeeded(unsigned line, const QString &fileName, Inclu if (m_dumpFileNameWhileParsing) { qDebug() << "Parsing file:" << absoluteFileName - << "contents:" << contents.size() + << "contents:" << contents.size() << "bytes"; ; } @@ -426,6 +438,33 @@ void CppPreprocessor::sourceNeeded(unsigned line, const QString &fileName, Inclu // b.constData()); // } + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(preprocessedCode); + foreach (const Macro ¯o, doc->definedMacros()) { + if (macro.isHidden()) { + static const QByteArray undef("#undef "); + hash.addData(undef); + hash.addData(macro.name()); + } else { + static const QByteArray def("#define "); + hash.addData(macro.name()); + hash.addData(" ", 1); + hash.addData(def); + hash.addData(macro.definitionText()); + } + hash.addData("\n", 1); + } + doc->setFingerprint(hash.result()); + + Document::Ptr anotherDoc = m_globalSnapshot.document(absoluteFileName); + if (anotherDoc && anotherDoc->fingerprint() == doc->fingerprint()) { + switchDocument(previousDoc); + mergeEnvironment(anotherDoc); + m_snapshot.insert(anotherDoc); + m_todo.remove(absoluteFileName); + return; + } + doc->setUtf8Source(preprocessedCode); doc->keepSourceAndAST(); doc->tokenize(); diff --git a/src/plugins/cpptools/cpppreprocessor.h b/src/plugins/cpptools/cpppreprocessor.h index e1e3f228d1c..d0d84ac8e36 100644 --- a/src/plugins/cpptools/cpppreprocessor.h +++ b/src/plugins/cpptools/cpppreprocessor.h @@ -23,6 +23,8 @@ public: static QString cleanPath(const QString &path); CppPreprocessor(QPointer<CppModelManager> modelManager, bool dumpFileNameWhileParsing = false); + CppPreprocessor(QPointer<CppModelManager> modelManager, const CPlusPlus::Snapshot &snapshot, + bool dumpFileNameWhileParsing = false); virtual ~CppPreprocessor(); void setRevision(unsigned revision); @@ -35,12 +37,17 @@ public: void removeFromCache(const QString &fileName); void resetEnvironment(); + CPlusPlus::Snapshot snapshot() const + { return m_snapshot; } + const QSet<QString> &todo() const { return m_todo; } CppModelManager *modelManager() const { return m_modelManager.data(); } + void setGlobalSnapshot(const CPlusPlus::Snapshot &snapshot) { m_globalSnapshot = snapshot; } + protected: CPlusPlus::Document::Ptr switchDocument(CPlusPlus::Document::Ptr doc); @@ -71,6 +78,7 @@ private: void addFrameworkPath(const QString &frameworkPath); CPlusPlus::Snapshot m_snapshot; + CPlusPlus::Snapshot m_globalSnapshot; QPointer<CppModelManager> m_modelManager; bool m_dumpFileNameWhileParsing; CPlusPlus::Environment m_env; diff --git a/src/plugins/cpptools/cppsnapshotupdater.cpp b/src/plugins/cpptools/cppsnapshotupdater.cpp new file mode 100644 index 00000000000..f12f888af78 --- /dev/null +++ b/src/plugins/cpptools/cppsnapshotupdater.cpp @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "cpppreprocessor.h" +#include "cppsnapshotupdater.h" + +#include <utils/qtcassert.h> + +using namespace CPlusPlus; +using namespace CppTools; +using namespace CppTools::Internal; + +SnapshotUpdater::SnapshotUpdater(const QString &fileInEditor) + : m_mutex(QMutex::Recursive) + , m_fileInEditor(fileInEditor) +{ +} + +void SnapshotUpdater::update(CppModelManager::WorkingCopy workingCopy) +{ + QMutexLocker locker(&m_mutex); + + if (m_fileInEditor.isEmpty()) + return; + + bool invalidateSnapshot = false, invalidateConfig = false; + + CppModelManager *modelManager + = dynamic_cast<CppModelManager *>(CppModelManagerInterface::instance()); + QByteArray configFile = modelManager->codeModelConfiguration(); + QStringList includePaths; + QStringList frameworkPaths; + + updateProjectPart(); + + if (m_projectPart) { + configFile += m_projectPart->defines; + includePaths = m_projectPart->includePaths; + frameworkPaths = m_projectPart->frameworkPaths; + } + + if (configFile != m_configFile) { + m_configFile = configFile; + invalidateSnapshot = true; + invalidateConfig = true; + } + + if (includePaths != m_includePaths) { + m_includePaths = includePaths; + invalidateSnapshot = true; + } + + if (frameworkPaths != m_frameworkPaths) { + m_frameworkPaths = frameworkPaths; + invalidateSnapshot = true; + } + + unsigned rev = 0; + if (Document::Ptr doc = document()) + rev = doc->revision(); + else + invalidateSnapshot = true; + + Snapshot globalSnapshot = modelManager->snapshot(); + + if (invalidateSnapshot) { + m_snapshot = Snapshot(); + } else { + // Remove changed files from the snapshot + QSet<QString> toRemove; + foreach (const Document::Ptr &doc, m_snapshot) { + QString fileName = doc->fileName(); + if (workingCopy.contains(fileName)) { + if (workingCopy.get(fileName).second != doc->editorRevision()) + addFileAndDependencies(&toRemove, fileName); + continue; + } + Document::Ptr otherDoc = globalSnapshot.document(fileName); + if (!otherDoc.isNull() && otherDoc->revision() != doc->revision()) + addFileAndDependencies(&toRemove, fileName); + } + + if (!toRemove.isEmpty()) { + invalidateSnapshot = true; + foreach (const QString &fileName, toRemove) + m_snapshot.remove(fileName); + } + } + + // Update the snapshot + if (invalidateSnapshot) { + const QString configurationFileName = modelManager->configurationFileName(); + if (invalidateConfig) + m_snapshot.remove(configurationFileName); + if (!m_snapshot.contains(configurationFileName)) + workingCopy.insert(configurationFileName, m_configFile); + m_snapshot.remove(m_fileInEditor); + + CppPreprocessor preproc(modelManager, m_snapshot); + Snapshot globalSnapshot = modelManager->snapshot(); + globalSnapshot.remove(fileInEditor()); + preproc.setGlobalSnapshot(globalSnapshot); + preproc.setWorkingCopy(workingCopy); + preproc.setIncludePaths(m_includePaths); + preproc.setFrameworkPaths(m_frameworkPaths); + preproc.run(configurationFileName); + preproc.run(m_fileInEditor); + + m_snapshot = preproc.snapshot(); + m_snapshot = m_snapshot.simplified(document()); + m_deps.build(m_snapshot); + + foreach (Document::Ptr doc, m_snapshot) { + QString fileName = doc->fileName(); + if (doc->revision() == 0) { + Document::Ptr otherDoc = globalSnapshot.document(fileName); + doc->setRevision(otherDoc.isNull() ? 0 : otherDoc->revision()); + } + if (fileName != fileInEditor()) + doc->releaseSourceAndAST(); + } + + QTC_CHECK(document()); + if (Document::Ptr doc = document()) + doc->setRevision(rev + 1); + } +} + +Document::Ptr SnapshotUpdater::document() const +{ + QMutexLocker locker(&m_mutex); + + return m_snapshot.document(m_fileInEditor); +} + +void SnapshotUpdater::updateProjectPart() +{ + CppModelManager *cmm = dynamic_cast<CppModelManager *>(CppModelManagerInterface::instance()); + QList<ProjectPart::Ptr> pParts = cmm->projectPart(m_fileInEditor); + if (pParts.isEmpty()) { + if (m_projectPart) + // File is not directly part of any project, but we got one before. We will re-use it, + // because re-calculating this can be expensive when the dependency table is big. + return; + + // Fall-back step 1: Get some parts through the dependency table: + pParts = cmm->projectPartFromDependencies(m_fileInEditor); + if (pParts.isEmpty()) + // Fall-back step 2: Use fall-back part from the model manager: + m_projectPart = cmm->fallbackProjectPart(); + else + m_projectPart = pParts.first(); + } else { + if (!pParts.contains(m_projectPart)) + // Apparently the project file changed, so update our project part. + m_projectPart = pParts.first(); + } +} + +void SnapshotUpdater::addFileAndDependencies(QSet<QString> *toRemove, const QString &fileName) const +{ + toRemove->insert(fileName); + if (fileName != m_fileInEditor) { + QStringList deps = m_deps.filesDependingOn(fileName); + toRemove->unite(QSet<QString>::fromList(deps)); + } +} + diff --git a/src/plugins/cpptools/cppsnapshotupdater.h b/src/plugins/cpptools/cppsnapshotupdater.h new file mode 100644 index 00000000000..8db54def7e1 --- /dev/null +++ b/src/plugins/cpptools/cppsnapshotupdater.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef CPPTOOLS_INTERNAL_SNAPSHOTUPDATER_H +#define CPPTOOLS_INTERNAL_SNAPSHOTUPDATER_H + +#include "cpptools_global.h" +#include "cppmodelmanager.h" + +#include <cplusplus/CppDocument.h> +#include <cplusplus/DependencyTable.h> + +#include <QMutex> +#include <QString> + +namespace CppTools { + +class CPPTOOLS_EXPORT SnapshotUpdater +{ + Q_DISABLE_COPY(SnapshotUpdater) + +public: + SnapshotUpdater(const QString &fileInEditor = QString()); + + QString fileInEditor() const + { return m_fileInEditor; } + + void update(CppModelManagerInterface::WorkingCopy workingCopy); + + CPlusPlus::Document::Ptr document() const; + + CPlusPlus::Snapshot snapshot() const + { return m_snapshot; } + + QStringList includePaths() const + { return m_includePaths; } + + QStringList frameworkPaths() const + { return m_frameworkPaths; } + +private: + void updateProjectPart(); + void addFileAndDependencies(QSet<QString> *toRemove, const QString &fileName) const; + +private: + mutable QMutex m_mutex; + QString m_fileInEditor; + ProjectPart::Ptr m_projectPart; + QByteArray m_configFile; + QStringList m_includePaths; + QStringList m_frameworkPaths; + CPlusPlus::Snapshot m_snapshot; + CPlusPlus::DependencyTable m_deps; +}; + +} // namespace CppTools + +#endif // CPPTOOLS_INTERNAL_SNAPSHOTUPDATER_H diff --git a/src/plugins/cpptools/cpptools.pro b/src/plugins/cpptools/cpptools.pro index b27d4f46fed..7c3d94fc910 100644 --- a/src/plugins/cpptools/cpptools.pro +++ b/src/plugins/cpptools/cpptools.pro @@ -12,6 +12,7 @@ HEADERS += completionsettingspage.h \ cpptools_global.h \ cpptoolsconstants.h \ cpptoolseditorsupport.h \ + cppsnapshotupdater.h \ cpptoolsplugin.h \ cppqtstyleindenter.h \ searchsymbols.h \ @@ -60,6 +61,7 @@ SOURCES += completionsettingspage.cpp \ cppmodelmanagerinterface.cpp \ cpplocatorfilter.cpp \ cpptoolseditorsupport.cpp \ + cppsnapshotupdater.cpp \ cpptoolsplugin.cpp \ cppqtstyleindenter.cpp \ searchsymbols.cpp \ diff --git a/src/plugins/cpptools/cpptools.qbs b/src/plugins/cpptools/cpptools.qbs index e78d40ee199..16584b457f0 100644 --- a/src/plugins/cpptools/cpptools.qbs +++ b/src/plugins/cpptools/cpptools.qbs @@ -121,7 +121,9 @@ QtcPlugin { "cppcodemodelsettings.h", "cppcodemodelsettingspage.cpp", "cppcodemodelsettingspage.h", - "cppcodemodelsettingspage.ui" + "cppcodemodelsettingspage.ui", + "cppsnapshotupdater.cpp", + "cppsnapshotupdater.h", ] Group { diff --git a/src/plugins/cpptools/cpptoolseditorsupport.cpp b/src/plugins/cpptools/cpptoolseditorsupport.cpp index fc4d64b3c92..c33a4241b99 100644 --- a/src/plugins/cpptools/cpptoolseditorsupport.cpp +++ b/src/plugins/cpptools/cpptoolseditorsupport.cpp @@ -169,6 +169,8 @@ QString CppEditorSupport::fileName() const QByteArray CppEditorSupport::contents() const { + QMutexLocker locker(&m_cachedContentsLock); + const int editorRev = editorRevision(); if (m_cachedContentsEditorRevision != editorRev && !m_fileIsBeingReloaded) { m_cachedContentsEditorRevision = editorRev; @@ -215,6 +217,13 @@ SemanticInfo CppEditorSupport::recalculateSemanticInfo(bool emitSignalWhenFinish return m_lastSemanticInfo; } +Document::Ptr CppEditorSupport::lastSemanticInfoDocument() const +{ + QMutexLocker locker(&m_lastSemanticInfoLock); + + return m_lastSemanticInfo.doc; +} + void CppEditorSupport::recalculateSemanticInfoDetached(bool force) { // Block premature calculation caused by CppEditorPlugin::currentEditorChanged @@ -236,6 +245,16 @@ CppCompletionAssistProvider *CppEditorSupport::completionAssistProvider() const return m_completionAssistProvider; } +QSharedPointer<SnapshotUpdater> CppEditorSupport::snapshotUpdater() +{ + QSharedPointer<SnapshotUpdater> updater = m_snapshotUpdater; + if (!updater) { + updater.reset(new SnapshotUpdater(fileName())); + m_snapshotUpdater = updater; + } + return updater; +} + void CppEditorSupport::updateDocument() { m_revision = editorRevision(); @@ -246,6 +265,19 @@ void CppEditorSupport::updateDocument() m_updateDocumentTimer->start(m_updateDocumentInterval); } +static void parse(QFutureInterface<void> &future, CppEditorSupport *support) +{ + future.setProgressRange(0, 1); + + CppModelManager *cmm = qobject_cast<CppModelManager *>(CppModelManager::instance()); + QSharedPointer<SnapshotUpdater> updater = support->snapshotUpdater(); + + updater->update(cmm->workingCopy()); + cmm->finishedRefreshingSourceFiles(QStringList(updater->document()->fileName())); + + future.setProgressValue(1); +} + void CppEditorSupport::updateDocumentNow() { if (m_documentParser.isRunning() || m_revision != editorRevision()) { @@ -259,8 +291,7 @@ void CppEditorSupport::updateDocumentNow() if (m_highlightingSupport && !m_highlightingSupport->requiresSemanticInfo()) startHighlighting(); - const QStringList sourceFiles(m_textEditor->document()->filePath()); - m_documentParser = m_modelManager->updateSourceFiles(sourceFiles); + m_documentParser = QtConcurrent::run(&parse, this); } } @@ -429,7 +460,7 @@ SemanticInfo::Source CppEditorSupport::currentSource(bool force) int line = 0, column = 0; m_textEditor->convertPosition(m_textEditor->editorWidget()->position(), &line, &column); - const Snapshot snapshot = m_modelManager->snapshot(); + const Snapshot snapshot = m_snapshotUpdater->snapshot(); QByteArray code; if (force || m_lastSemanticInfo.revision != editorRevision()) diff --git a/src/plugins/cpptools/cpptoolseditorsupport.h b/src/plugins/cpptools/cpptoolseditorsupport.h index 46e9ee3d512..838b8cab4f3 100644 --- a/src/plugins/cpptools/cpptoolseditorsupport.h +++ b/src/plugins/cpptools/cpptoolseditorsupport.h @@ -33,12 +33,14 @@ #include "cpphighlightingsupport.h" #include "cppmodelmanager.h" #include "cppsemanticinfo.h" +#include "cppsnapshotupdater.h" #include <cplusplus/CppDocument.h> #include <QFuture> #include <QObject> #include <QPointer> +#include <QSharedPointer> #include <QTimer> namespace CPlusPlus { class AST; } @@ -111,6 +113,8 @@ public: /// thread if it is outdate. SemanticInfo recalculateSemanticInfo(bool emitSignalWhenFinished = true); + CPlusPlus::Document::Ptr lastSemanticInfoDocument() const; + /// Recalculates the semantic info in a future, and will emit the /// semanticInfoUpdated() signal when finished. /// Requires that initialized() is true. @@ -119,6 +123,8 @@ public: CppCompletionAssistProvider *completionAssistProvider() const; + QSharedPointer<SnapshotUpdater> snapshotUpdater(); + signals: void documentUpdated(); void diagnosticsChanged(); @@ -173,6 +179,7 @@ private: QFuture<void> m_documentParser; // content caching + mutable QMutex m_cachedContentsLock; mutable QByteArray m_cachedContents; mutable int m_cachedContentsEditorRevision; bool m_fileIsBeingReloaded; @@ -188,6 +195,7 @@ private: mutable QMutex m_lastSemanticInfoLock; SemanticInfo m_lastSemanticInfo; QFuture<void> m_futureSemanticInfo; + QSharedPointer<SnapshotUpdater> m_snapshotUpdater; // Highlighting: unsigned m_lastHighlightRevision; diff --git a/src/plugins/cpptools/cpptoolsplugin.h b/src/plugins/cpptools/cpptoolsplugin.h index 4f7653ddaf2..b45631ab8d0 100644 --- a/src/plugins/cpptools/cpptoolsplugin.h +++ b/src/plugins/cpptools/cpptoolsplugin.h @@ -211,6 +211,7 @@ private slots: void test_modelmanager_extraeditorsupport_uiFiles(); void test_modelmanager_gc_if_last_cppeditor_closed(); void test_modelmanager_dont_gc_opened_files(); + void test_modelmanager_defines_per_project(); void test_cpplocatorfilters_CppLocatorFilter(); void test_cpplocatorfilters_CppLocatorFilter_data(); diff --git a/tests/cppmodelmanager/testdata_defines/header.h b/tests/cppmodelmanager/testdata_defines/header.h new file mode 100644 index 00000000000..aecbed15c4d --- /dev/null +++ b/tests/cppmodelmanager/testdata_defines/header.h @@ -0,0 +1,7 @@ +#ifdef SUB1 +# define BUS_ONE +#endif + +#ifdef SUB2 +# define BUS_TWO +#endif diff --git a/tests/cppmodelmanager/testdata_defines/main1.cpp b/tests/cppmodelmanager/testdata_defines/main1.cpp new file mode 100644 index 00000000000..569fc023d2e --- /dev/null +++ b/tests/cppmodelmanager/testdata_defines/main1.cpp @@ -0,0 +1,9 @@ +#include "header.h" + +#ifdef BUS_ONE +int one; +#endif + +#ifdef BUS_TWO +int two; +#endif diff --git a/tests/cppmodelmanager/testdata_defines/main2.cpp b/tests/cppmodelmanager/testdata_defines/main2.cpp new file mode 100644 index 00000000000..569fc023d2e --- /dev/null +++ b/tests/cppmodelmanager/testdata_defines/main2.cpp @@ -0,0 +1,9 @@ +#include "header.h" + +#ifdef BUS_ONE +int one; +#endif + +#ifdef BUS_TWO +int two; +#endif -- GitLab