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 &macro, 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