Commit ba2d7a4f authored by Erik Verbruggen's avatar Erik Verbruggen

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: default avatarNikolai Kosjar <nikolai.kosjar@digia.com>
parent 447c4ed3
......@@ -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;
......
......@@ -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) {
......
......@@ -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;
......
......@@ -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;
}
// -----------------
......
......@@ -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;
};
......
......@@ -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,
......
......@@ -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
......
......@@ -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;
......
......@@ -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");
}
}
......@@ -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;
......
......@@ -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();
......
......@@ -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;
......
/****************************************************************************
**
** 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.