cppmodelmanager.cpp 34 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12
** 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
Eike Ziller's avatar
Eike Ziller committed
13 14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24 25 26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31
#include "cppmodelmanager.h"
32

33
#include "abstracteditorsupport.h"
34
#include "builtinindexingsupport.h"
35
#include "cppcodemodelinspectordumper.h"
36
#include "cppcodemodelsettings.h"
37
#include "cppfindreferences.h"
38
#include "cppindexingsupport.h"
39
#include "cppmodelmanagersupportinternal.h"
40
#include "cpprefactoringchanges.h"
41
#include "cppsourceprocessor.h"
42
#include "cpptoolsconstants.h"
43
#include "cpptoolsplugin.h"
44
#include "editordocumenthandle.h"
con's avatar
con committed
45

46
#include <coreplugin/documentmanager.h>
47 48
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
49 50
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
51
#include <extensionsystem/pluginmanager.h>
hjk's avatar
hjk committed
52
#include <utils/qtcassert.h>
con's avatar
con committed
53

54 55 56 57
#include <QCoreApplication>
#include <QDebug>
#include <QMutexLocker>
#include <QTextBlock>
58
#include <QTimer>
59

60 61
#if defined(QTCREATOR_WITH_DUMP_AST) && defined(Q_CC_GNU)
#define WITH_AST_DUMP
62 63
#include <iostream>
#include <sstream>
64
#endif
hjk's avatar
hjk committed
65

66 67
Q_DECLARE_METATYPE(QSet<QString>)

68 69
static const bool DumpProjectInfo = qgetenv("QTC_DUMP_PROJECT_INFO") == "1";

70 71
using namespace CppTools;
using namespace CppTools::Internal;
con's avatar
con committed
72 73
using namespace CPlusPlus;

74
#ifdef QTCREATOR_WITH_DUMP_AST
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

#include <cxxabi.h>

class DumpAST: protected ASTVisitor
{
public:
    int depth;

    DumpAST(Control *control)
        : ASTVisitor(control), depth(0)
    { }

    void operator()(AST *ast)
    { accept(ast); }

protected:
    virtual bool preVisit(AST *ast)
    {
        std::ostringstream s;
        PrettyPrinter pp(control(), s);
        pp(ast);
        QString code = QString::fromStdString(s.str());
        code.replace('\n', ' ');
        code.replace(QRegExp("\\s+"), " ");

        const char *name = abi::__cxa_demangle(typeid(*ast).name(), 0, 0, 0) + 11;

        QByteArray ind(depth, ' ');
        ind += name;

        printf("%-40s %s\n", ind.constData(), qPrintable(code));
        ++depth;
        return true;
    }

    virtual void postVisit(AST *)
    { --depth; }
};

Roberto Raggi's avatar
Roberto Raggi committed
114
#endif // QTCREATOR_WITH_DUMP_AST
115

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
namespace CppTools {
namespace Internal {

static QMutex m_instanceMutex;
static CppModelManager *m_instance;

class CppModelManagerPrivate
{
public:
    // Snapshot
    mutable QMutex m_snapshotMutex;
    CPlusPlus::Snapshot m_snapshot;

    // Project integration
    mutable QMutex m_projectMutex;
    QMap<ProjectExplorer::Project *, ProjectInfo> m_projectToProjectsInfo;
    QMap<QString, QList<CppTools::ProjectPart::Ptr> > m_fileToProjectParts;
    QMap<QString, CppTools::ProjectPart::Ptr> m_projectFileToProjectPart;
    // The members below are cached/(re)calculated from the projects and/or their project parts
    bool m_dirty;
    QStringList m_projectFiles;
    ProjectPart::HeaderPaths m_headerPaths;
    QByteArray m_definedMacros;

    // Editor integration
    mutable QMutex m_cppEditorsMutex;
    QMap<QString, EditorDocumentHandle *> m_cppEditors;
    QSet<AbstractEditorSupport *> m_extraEditorSupports;

    // Completion & highlighting
    QHash<QString, ModelManagerSupport *> m_idTocodeModelSupporter;
    QScopedPointer<ModelManagerSupport> m_modelManagerSupportFallback;

    // Indexing
    CppIndexingSupport *m_indexingSupporter;
    CppIndexingSupport *m_internalIndexingSupport;
    bool m_indexerEnabled;

    CppFindReferences *m_findReferences;

    bool m_enableGC;
    QTimer m_delayedGcTimer;
};

} // namespace Internal

const char pp_configuration[] =
con's avatar
con committed
163
    "# 1 \"<configuration>\"\n"
164
    "#define Q_CREATOR_RUN 1\n"
con's avatar
con committed
165 166 167 168 169 170
    "#define __cplusplus 1\n"
    "#define __extension__\n"
    "#define __context__\n"
    "#define __range__\n"
    "#define   restrict\n"
    "#define __restrict\n"
171
    "#define __restrict__\n"
con's avatar
con committed
172

173 174 175 176
    "#define __complex__\n"
    "#define __imag__\n"
    "#define __real__\n"

177 178
    "#define __builtin_va_arg(a,b) ((b)0)\n"

con's avatar
con committed
179 180
    // ### add macros for win32
    "#define __cdecl\n"
181
    "#define __stdcall\n"
182
    "#define __thiscall\n"
con's avatar
con committed
183 184 185 186 187
    "#define QT_WA(x) x\n"
    "#define CALLBACK\n"
    "#define STDMETHODCALLTYPE\n"
    "#define __RPC_FAR\n"
    "#define __declspec(a)\n"
188 189 190
    "#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method\n"
    "#define __try try\n"
    "#define __except catch\n"
191 192 193
    "#define __finally\n"
    "#define __inline inline\n"
    "#define __forceinline inline\n";
con's avatar
con committed
194

195
QSet<QString> CppModelManager::timeStampModifiedFiles(const QList<Document::Ptr> &documentsToCheck)
196
{
197
    QSet<QString> sourceFiles;
198

199
    foreach (const Document::Ptr doc, documentsToCheck) {
200 201
        const QDateTime lastModified = doc->lastModified();

202
        if (!lastModified.isNull()) {
203 204 205
            QFileInfo fileInfo(doc->fileName());

            if (fileInfo.exists() && fileInfo.lastModified() != lastModified)
206
                sourceFiles.insert(doc->fileName());
207 208 209
        }
    }

210 211 212
    return sourceFiles;
}

213 214 215 216 217 218 219 220 221 222 223 224
/*!
 * \brief createSourceProcessor Create a new source processor, which will signal the
 * model manager when a document has been processed.
 *
 * Indexed file is truncated version of fully parsed document: copy of source
 * code and full AST will be dropped when indexing is done.
 *
 * \return a new source processor object, which the caller needs to delete when finished.
 */
CppSourceProcessor *CppModelManager::createSourceProcessor()
{
    CppModelManager *that = instance();
Orgad Shaneh's avatar
Orgad Shaneh committed
225
    return new CppSourceProcessor(that->snapshot(), [that](const Document::Ptr &doc) {
226 227 228 229 230
        that->emitDocumentUpdated(doc);
        doc->releaseSourceAndAST();
    });
}

231 232 233 234 235 236 237 238 239 240
QString CppModelManager::editorConfigurationFileName()
{
    return QLatin1String("<per-editor-defines>");
}

QString CppModelManager::configurationFileName()
{
    return CPlusPlus::Preprocessor::configurationFileName;
}

241 242 243 244 245 246 247
void CppModelManager::updateModifiedSourceFiles()
{
    const Snapshot snapshot = this->snapshot();
    QList<Document::Ptr> documentsToCheck;
    foreach (const Document::Ptr document, snapshot)
        documentsToCheck << document;

248
    updateSourceFiles(timeStampModifiedFiles(documentsToCheck));
249 250
}

con's avatar
con committed
251 252
/*!
    \class CppTools::CppModelManager
253 254 255
    \brief The CppModelManager keeps tracks of the source files the code model is aware of.

    The CppModelManager manages the source files in a Snapshot object.
con's avatar
con committed
256

257 258 259 260
    The snapshot is updated in case e.g.
        * New files are opened/edited (Editor integration)
        * A project manager pushes updated project information (Project integration)
        * Files are garbage collected
con's avatar
con committed
261 262
*/

263 264
CppModelManager *CppModelManager::instance()
{
265 266 267 268 269 270 271 272
    if (m_instance)
        return m_instance;

    QMutexLocker locker(&m_instanceMutex);
    if (!m_instance)
        m_instance = new CppModelManager;

    return m_instance;
273 274
}

275
CppModelManager::CppModelManager(QObject *parent)
276
    : CppModelManagerBase(parent), d(new CppModelManagerPrivate)
con's avatar
con committed
277
{
278 279 280
    d->m_indexingSupporter = 0;
    d->m_enableGC = true;

281
    qRegisterMetaType<QSet<QString> >();
282
    connect(this, SIGNAL(sourceFilesRefreshed(QSet<QString>)),
283
            this, SLOT(onSourceFilesRefreshed()));
Erik Verbruggen's avatar
Erik Verbruggen committed
284

285 286
    d->m_findReferences = new CppFindReferences(this);
    d->m_indexerEnabled = qgetenv("QTC_NO_CODE_INDEXER") != "1";
Roberto Raggi's avatar
Roberto Raggi committed
287

288
    d->m_dirty = true;
289

290 291 292
    d->m_delayedGcTimer.setObjectName(QLatin1String("CppModelManager::m_delayedGcTimer"));
    d->m_delayedGcTimer.setSingleShot(true);
    connect(&d->m_delayedGcTimer, SIGNAL(timeout()), this, SLOT(GC()));
293

hjk's avatar
hjk committed
294 295
    QObject *sessionManager = ProjectExplorer::SessionManager::instance();
    connect(sessionManager, SIGNAL(projectAdded(ProjectExplorer::Project*)),
296
            this, SLOT(onProjectAdded(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
297
    connect(sessionManager, SIGNAL(aboutToRemoveProject(ProjectExplorer::Project*)),
Robert Loehning's avatar
Robert Loehning committed
298
            this, SLOT(onAboutToRemoveProject(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
299
    connect(sessionManager, SIGNAL(aboutToLoadSession(QString)),
300
            this, SLOT(onAboutToLoadSession()));
hjk's avatar
hjk committed
301
    connect(sessionManager, SIGNAL(aboutToUnloadSession(QString)),
302
            this, SLOT(onAboutToUnloadSession()));
303 304
    connect(Core::DocumentManager::instance(), &Core::DocumentManager::allDocumentsRenamed,
            this, &CppModelManager::renameIncludes);
con's avatar
con committed
305

306 307 308
    connect(Core::ICore::instance(), SIGNAL(coreAboutToClose()),
            this, SLOT(onCoreAboutToClose()));

con's avatar
con committed
309
    qRegisterMetaType<CPlusPlus::Document::Ptr>("CPlusPlus::Document::Ptr");
310 311
    qRegisterMetaType<QList<CPlusPlus::Document::DiagnosticMessage>>(
                "QList<CPlusPlus::Document::DiagnosticMessage>");
con's avatar
con committed
312

313
    d->m_modelManagerSupportFallback.reset(new ModelManagerSupportInternal);
314
    CppToolsPlugin::instance()->codeModelSettings()->setDefaultId(
315 316
                d->m_modelManagerSupportFallback->id());
    addModelManagerSupport(d->m_modelManagerSupportFallback.data());
317

318
    d->m_internalIndexingSupport = new BuiltinIndexingSupport;
con's avatar
con committed
319 320 321
}

CppModelManager::~CppModelManager()
322
{
323
    delete d->m_internalIndexingSupport;
324
    delete d;
325
}
con's avatar
con committed
326

327
Snapshot CppModelManager::snapshot() const
328
{
329 330
    QMutexLocker locker(&d->m_snapshotMutex);
    return d->m_snapshot;
331
}
con's avatar
con committed
332

333 334
Document::Ptr CppModelManager::document(const QString &fileName) const
{
335 336
    QMutexLocker locker(&d->m_snapshotMutex);
    return d->m_snapshot.document(fileName);
337 338 339 340 341 342 343
}

/// Replace the document in the snapshot.
///
/// \returns true if successful, false if the new document is out-dated.
bool CppModelManager::replaceDocument(Document::Ptr newDoc)
{
344
    QMutexLocker locker(&d->m_snapshotMutex);
345

346
    Document::Ptr previous = d->m_snapshot.document(newDoc->fileName());
347 348 349 350
    if (previous && (newDoc->revision() != 0 && newDoc->revision() < previous->revision()))
        // the new document is outdated
        return false;

351
    d->m_snapshot.insert(newDoc);
352 353 354
    return true;
}

355 356
void CppModelManager::ensureUpdated()
{
357 358
    QMutexLocker locker(&d->m_projectMutex);
    if (!d->m_dirty)
359 360
        return;

361 362 363 364
    d->m_projectFiles = internalProjectFiles();
    d->m_headerPaths = internalHeaderPaths();
    d->m_definedMacros = internalDefinedMacros();
    d->m_dirty = false;
365 366
}

367
QStringList CppModelManager::internalProjectFiles() const
con's avatar
con committed
368 369
{
    QStringList files;
370
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
371 372
    while (it.hasNext()) {
        it.next();
373
        const ProjectInfo pinfo = it.value();
374
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
375 376
            foreach (const ProjectFile &file, part->files)
                files += file.path;
377
        }
con's avatar
con committed
378
    }
379
    files.removeDuplicates();
con's avatar
con committed
380 381 382
    return files;
}

383
ProjectPart::HeaderPaths CppModelManager::internalHeaderPaths() const
con's avatar
con committed
384
{
385
    ProjectPart::HeaderPaths headerPaths;
386
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
387 388
    while (it.hasNext()) {
        it.next();
389
        const ProjectInfo pinfo = it.value();
390 391 392 393 394 395 396 397
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
            foreach (const ProjectPart::HeaderPath &path, part->headerPaths) {
                const ProjectPart::HeaderPath hp(CppSourceProcessor::cleanPath(path.path),
                                                 path.type);
                if (!headerPaths.contains(hp))
                    headerPaths += hp;
            }
        }
con's avatar
con committed
398
    }
399
    return headerPaths;
con's avatar
con committed
400 401
}

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
static void addUnique(const QList<QByteArray> &defs, QByteArray *macros, QSet<QByteArray> *alreadyIn)
{
    Q_ASSERT(macros);
    Q_ASSERT(alreadyIn);

    foreach (const QByteArray &def, defs) {
        if (def.trimmed().isEmpty())
            continue;
        if (!alreadyIn->contains(def)) {
            macros->append(def);
            macros->append('\n');
            alreadyIn->insert(def);
        }
    }
}

418
QByteArray CppModelManager::internalDefinedMacros() const
con's avatar
con committed
419 420
{
    QByteArray macros;
421
    QSet<QByteArray> alreadyIn;
422
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
423 424
    while (it.hasNext()) {
        it.next();
425
        const ProjectInfo pinfo = it.value();
426
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
427 428
            addUnique(part->toolchainDefines.split('\n'), &macros, &alreadyIn);
            addUnique(part->projectDefines.split('\n'), &macros, &alreadyIn);
429
            if (!part->projectConfigFile.isEmpty())
430
                macros += ProjectPart::readProjectConfigFile(part);
431
        }
con's avatar
con committed
432 433 434 435
    }
    return macros;
}

436
/// This function will acquire mutexes!
437
void CppModelManager::dumpModelManagerConfiguration(const QString &logFileId)
438
{
439 440 441 442
    const Snapshot globalSnapshot = snapshot();
    const QString globalSnapshotTitle
        = QString::fromLatin1("Global/Indexing Snapshot (%1 Documents)").arg(globalSnapshot.size());

443
    CppCodeModelInspector::Dumper dumper(globalSnapshot, logFileId);
444 445 446
    dumper.dumpProjectInfos(projectInfos());
    dumper.dumpSnapshot(globalSnapshot, globalSnapshotTitle, /*isGlobalSnapshot=*/ true);
    dumper.dumpWorkingCopy(workingCopy());
447
    ensureUpdated();
448
    dumper.dumpMergedEntities(d->m_headerPaths, d->m_definedMacros);
449 450
}

451
void CppModelManager::addExtraEditorSupport(AbstractEditorSupport *editorSupport)
452
{
453
    d->m_extraEditorSupports.insert(editorSupport);
454 455
}

456
void CppModelManager::removeExtraEditorSupport(AbstractEditorSupport *editorSupport)
457
{
458
    d->m_extraEditorSupports.remove(editorSupport);
459 460
}

461
EditorDocumentHandle *CppModelManager::editorDocument(const QString &filePath) const
462
{
463
    QTC_ASSERT(!filePath.isEmpty(), return 0);
464

465 466
    QMutexLocker locker(&d->m_cppEditorsMutex);
    return d->m_cppEditors.value(filePath, 0);
467 468
}

469
void CppModelManager::registerEditorDocument(EditorDocumentHandle *editorDocument)
470
{
471 472 473
    QTC_ASSERT(editorDocument, return);
    const QString filePath = editorDocument->filePath();
    QTC_ASSERT(!filePath.isEmpty(), return);
474

475 476 477
    QMutexLocker locker(&d->m_cppEditorsMutex);
    QTC_ASSERT(d->m_cppEditors.value(filePath, 0) == 0, return);
    d->m_cppEditors.insert(filePath, editorDocument);
478
}
479

480 481 482
void CppModelManager::unregisterEditorDocument(const QString &filePath)
{
    QTC_ASSERT(!filePath.isEmpty(), return);
483

484 485
    static short closedCppDocuments = 0;
    int openCppDocuments = 0;
486

487
    {
488 489 490 491
        QMutexLocker locker(&d->m_cppEditorsMutex);
        QTC_ASSERT(d->m_cppEditors.value(filePath, 0), return);
        QTC_CHECK(d->m_cppEditors.remove(filePath) == 1);
        openCppDocuments = d->m_cppEditors.size();
492 493
    }

494 495 496
    ++closedCppDocuments;
    if (openCppDocuments == 0 || closedCppDocuments == 5) {
        closedCppDocuments = 0;
497
        delayedGC();
498 499 500
    }
}

501
QList<int> CppModelManager::references(CPlusPlus::Symbol *symbol, const LookupContext &context)
502
{
503
    return d->m_findReferences->references(symbol, context);
504 505
}

506
void CppModelManager::findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context)
Roberto Raggi's avatar
Roberto Raggi committed
507
{
508
    if (symbol->identifier())
509
        d->m_findReferences->findUsages(symbol, context);
510 511
}

512 513
void CppModelManager::renameUsages(CPlusPlus::Symbol *symbol,
                                   const CPlusPlus::LookupContext &context,
514
                                   const QString &replacement)
515 516
{
    if (symbol->identifier())
517
        d->m_findReferences->renameUsages(symbol, context, replacement);
Roberto Raggi's avatar
Roberto Raggi committed
518 519
}

Christian Kamm's avatar
Christian Kamm committed
520 521
void CppModelManager::findMacroUsages(const CPlusPlus::Macro &macro)
{
522
    d->m_findReferences->findMacroUses(macro);
Christian Kamm's avatar
Christian Kamm committed
523 524
}

525 526
void CppModelManager::renameMacroUsages(const CPlusPlus::Macro &macro, const QString &replacement)
{
527
    d->m_findReferences->renameMacroUses(macro, replacement);
528 529
}

530 531
void CppModelManager::replaceSnapshot(const CPlusPlus::Snapshot &newSnapshot)
{
532 533
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
    d->m_snapshot = newSnapshot;
534 535
}

536
WorkingCopy CppModelManager::buildWorkingCopyList()
con's avatar
con committed
537
{
538
    WorkingCopy workingCopy;
539

540 541
    foreach (const EditorDocumentHandle *cppEditor, cppEditors())
        workingCopy.insert(cppEditor->filePath(), cppEditor->contents(), cppEditor->revision());
con's avatar
con committed
542

543
    QSetIterator<AbstractEditorSupport *> it(d->m_extraEditorSupports);
544 545
    while (it.hasNext()) {
        AbstractEditorSupport *es = it.next();
546
        workingCopy.insert(es->fileName(), es->contents(), es->revision());
547 548
    }

549
    // Add the project configuration file
550
    QByteArray conf = codeModelConfiguration();
con's avatar
con committed
551
    conf += definedMacros();
552
    workingCopy.insert(configurationFileName(), conf);
con's avatar
con committed
553 554 555 556

    return workingCopy;
}

557
WorkingCopy CppModelManager::workingCopy() const
558 559 560 561
{
    return const_cast<CppModelManager *>(this)->buildWorkingCopyList();
}

562 563 564 565 566
QByteArray CppModelManager::codeModelConfiguration() const
{
    return QByteArray::fromRawData(pp_configuration, qstrlen(pp_configuration));
}

567
QFuture<void> CppModelManager::updateSourceFiles(const QSet<QString> &sourceFiles,
568
                                                 ProgressNotificationMode mode)
569
{
570
    if (sourceFiles.isEmpty() || !d->m_indexerEnabled)
571 572
        return QFuture<void>();

573 574 575
    if (d->m_indexingSupporter)
        d->m_indexingSupporter->refreshSourceFiles(sourceFiles, mode);
    return d->m_internalIndexingSupport->refreshSourceFiles(sourceFiles, mode);
576
}
con's avatar
con committed
577

578
QList<ProjectInfo> CppModelManager::projectInfos() const
579
{
580 581
    QMutexLocker locker(&d->m_projectMutex);
    return d->m_projectToProjectsInfo.values();
582 583
}

584
ProjectInfo CppModelManager::projectInfo(ProjectExplorer::Project *project) const
585
{
586 587
    QMutexLocker locker(&d->m_projectMutex);
    return d->m_projectToProjectsInfo.value(project, ProjectInfo(project));
588 589
}

590 591 592 593 594 595
/// \brief Remove all files and their includes (recursively) of given ProjectInfo from the snapshot.
void CppModelManager::removeProjectInfoFilesAndIncludesFromSnapshot(const ProjectInfo &projectInfo)
{
    if (!projectInfo.isValid())
        return;

596
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
597 598
    foreach (const ProjectPart::Ptr &projectPart, projectInfo.projectParts()) {
        foreach (const ProjectFile &cxxFile, projectPart->files) {
599 600 601
            foreach (const QString &fileName, d->m_snapshot.allIncludesForDocument(cxxFile.path))
                d->m_snapshot.remove(fileName);
            d->m_snapshot.remove(cxxFile.path);
602 603 604 605
        }
    }
}

606
QList<EditorDocumentHandle *> CppModelManager::cppEditors() const
607
{
608 609
    QMutexLocker locker(&d->m_cppEditorsMutex);
    return d->m_cppEditors.values();
610 611
}

612 613 614
/// \brief Remove all given files from the snapshot.
void CppModelManager::removeFilesFromSnapshot(const QSet<QString> &filesToRemove)
{
615
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
616 617
    QSetIterator<QString> i(filesToRemove);
    while (i.hasNext())
618
        d->m_snapshot.remove(i.next());
619 620 621
}

class ProjectInfoComparer
622
{
623
public:
624 625
    ProjectInfoComparer(const ProjectInfo &oldProjectInfo,
                        const ProjectInfo &newProjectInfo)
626
        : m_old(oldProjectInfo)
627
        , m_oldSourceFiles(oldProjectInfo.sourceFiles())
628
        , m_new(newProjectInfo)
629
        , m_newSourceFiles(newProjectInfo.sourceFiles())
630 631
    {}

632 633 634
    bool definesChanged() const { return m_new.definesChanged(m_old); }
    bool configurationChanged() const { return m_new.configurationChanged(m_old); }
    bool configurationOrFilesChanged() const { return m_new.configurationOrFilesChanged(m_old); }
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663

    QSet<QString> addedFiles() const
    {
        QSet<QString> addedFilesSet = m_newSourceFiles;
        addedFilesSet.subtract(m_oldSourceFiles);
        return addedFilesSet;
    }

    QSet<QString> removedFiles() const
    {
        QSet<QString> removedFilesSet = m_oldSourceFiles;
        removedFilesSet.subtract(m_newSourceFiles);
        return removedFilesSet;
    }

    /// Returns a list of common files that have a changed timestamp.
    QSet<QString> timeStampModifiedFiles(const Snapshot &snapshot) const
    {
        QSet<QString> commonSourceFiles = m_newSourceFiles;
        commonSourceFiles.intersect(m_oldSourceFiles);

        QList<Document::Ptr> documentsToCheck;
        QSetIterator<QString> i(commonSourceFiles);
        while (i.hasNext()) {
            const QString file = i.next();
            if (Document::Ptr document = snapshot.document(file))
                documentsToCheck << document;
        }

664
        return CppModelManager::timeStampModifiedFiles(documentsToCheck);
665 666 667
    }

private:
668
    const ProjectInfo &m_old;
669 670
    const QSet<QString> m_oldSourceFiles;

671
    const ProjectInfo &m_new;
672 673 674
    const QSet<QString> m_newSourceFiles;
};

675 676 677
/// Make sure that m_projectMutex is locked when calling this.
void CppModelManager::recalculateFileToProjectParts()
{
678 679 680
    d->m_projectFileToProjectPart.clear();
    d->m_fileToProjectParts.clear();
    foreach (const ProjectInfo &projectInfo, d->m_projectToProjectsInfo) {
681
        foreach (const ProjectPart::Ptr &projectPart, projectInfo.projectParts()) {
682
            d->m_projectFileToProjectPart[projectPart->projectFile] = projectPart;
683
            foreach (const ProjectFile &cxxFile, projectPart->files)
684
                d->m_fileToProjectParts[cxxFile.path].append(projectPart);
685

686 687 688 689
        }
    }
}

690 691 692 693 694 695
void CppModelManager::updateVisibleEditorDocuments() const
{
    QSet<QString> visibleDocumentsInEditMode;
    foreach (Core::IEditor *editor, Core::EditorManager::visibleEditors()) {
        if (const Core::IDocument *document = editor->document()) {
            const QString filePath = document->filePath();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
696 697
            if (!filePath.isEmpty())
                visibleDocumentsInEditMode.insert(filePath);
698 699 700 701 702 703 704 705 706 707
        }
    }

    // Re-process these documents
    foreach (const QString &filePath, visibleDocumentsInEditMode) {
        if (EditorDocumentHandle *editor = editorDocument(filePath))
            editor->processor()->run();
    }
}

708 709 710
QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo &newProjectInfo)
{
    if (!newProjectInfo.isValid())
711
        return QFuture<void>();
712

713
    QSet<QString> filesToReindex;
714 715
    bool filesRemoved = false;

716
    { // Only hold the mutex for a limited scope, so the dumping afterwards does not deadlock.
717
        QMutexLocker projectLocker(&d->m_projectMutex);
718 719

        ProjectExplorer::Project *project = newProjectInfo.project().data();
720
        const QSet<QString> newSourceFiles = newProjectInfo.sourceFiles();
721

722
        // Check if we can avoid a full reindexing
723
        ProjectInfo oldProjectInfo = d->m_projectToProjectsInfo.value(project);
724
        if (oldProjectInfo.isValid()) {
725
            ProjectInfoComparer comparer(oldProjectInfo, newProjectInfo);
726 727 728 729

            if (!comparer.configurationOrFilesChanged()) {
                // Some other attached data might have changed
                d->m_projectToProjectsInfo.insert(project, newProjectInfo);
730
                return QFuture<void>();
731
            }
732

733 734 735
            // If the project configuration changed, do a full reindexing
            if (comparer.configurationChanged()) {
                removeProjectInfoFilesAndIncludesFromSnapshot(oldProjectInfo);
736
                filesToReindex.unite(newSourceFiles);
737 738 739

                // The "configuration file" includes all defines and therefore should be updated
                if (comparer.definesChanged()) {
740 741
                    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
                    d->m_snapshot.remove(configurationFileName());
742
                }
743 744 745 746

            // Otherwise check for added and modified files
            } else {
                const QSet<QString> addedFiles = comparer.addedFiles();
747
                filesToReindex.unite(addedFiles);
748 749

                const QSet<QString> modifiedFiles = comparer.timeStampModifiedFiles(snapshot());
750
                filesToReindex.unite(modifiedFiles);
751
            }
752 753 754 755 756 757 758 759 760 761 762

            // Announce and purge the removed files from the snapshot
            const QSet<QString> removedFiles = comparer.removedFiles();
            if (!removedFiles.isEmpty()) {
                filesRemoved = true;
                emit aboutToRemoveFiles(removedFiles.toList());
                removeFilesFromSnapshot(removedFiles);
            }

        // A new project was opened/created, do a full indexing
        } else {
763
            filesToReindex.unite(newSourceFiles);
764 765
        }

766
        // Update Project/ProjectInfo and File/ProjectPart table
767 768
        d->m_dirty = true;
        d->m_projectToProjectsInfo.insert(project, newProjectInfo);
769
        recalculateFileToProjectParts();
770

771 772 773
    } // Mutex scope

    // If requested, dump everything we got
774
    if (DumpProjectInfo)
775
        dumpModelManagerConfiguration(QLatin1String("updateProjectInfo"));
776

777 778 779 780 781
    // Remove files from snapshot that are not reachable any more
    if (filesRemoved)
        GC();

    emit projectPartsUpdated(newProjectInfo.project().data());
782

783 784 785 786 787 788
    // Ideally, we would update all the editor documents that depend on the 'filesToReindex'.
    // However, on e.g. a session restore first the editor documents are created and then the
    // project updates come in. That is, there are no reasonable dependency tables based on
    // resolved includes that we could rely on.
    updateVisibleEditorDocuments();

789 790
    // Trigger reindexing
    return updateSourceFiles(filesToReindex, ForcedProgressNotification);
791 792
}

793 794
ProjectPart::Ptr CppModelManager::projectPartForProjectFile(const QString &projectFile) const
{
795
    return d->m_projectFileToProjectPart.value(projectFile);
796 797
}

798
QList<ProjectPart::Ptr> CppModelManager::projectPart(const QString &fileName) const
799
{
800 801
    QMutexLocker locker(&d->m_projectMutex);
    return d->m_fileToProjectParts.value(fileName);
802
}
803

804 805 806
QList<ProjectPart::Ptr> CppModelManager::projectPartFromDependencies(const QString &fileName) const
{
    QSet<ProjectPart::Ptr> parts;
807
    const QStringList deps = snapshot().filesDependingOn(fileName);
808 809 810 811 812 813

    {
        QMutexLocker locker(&d->m_projectMutex);
        foreach (const QString &dep, deps)
            parts.unite(QSet<ProjectPart::Ptr>::fromList(d->m_fileToProjectParts.value(dep)));
    }
814

815
    return parts.values();
816
}
con's avatar
con committed
817

818 819 820 821
ProjectPart::Ptr CppModelManager::fallbackProjectPart() const
{
    ProjectPart::Ptr part(new ProjectPart);

822 823
    part->projectDefines = d->m_definedMacros;
    part->headerPaths = d->m_headerPaths;
824 825
    part->languageVersion = ProjectPart::CXX14;
    part->languageExtensions = ProjectPart::AllExtensions;
826 827 828 829 830
    part->qtVersion = ProjectPart::Qt5;

    return part;
}

con's avatar
con committed
831 832
bool CppModelManager::isCppEditor(Core::IEditor *editor) const
{
833
    return editor->context().contains(ProjectExplorer::Constants::LANG_CXX);
con's avatar
con committed
834 835 836 837
}

void CppModelManager::emitDocumentUpdated(Document::Ptr doc)
{
838
    if (replaceDocument(doc))
839
        emit documentUpdated(doc);
Roberto Raggi's avatar
Roberto Raggi committed
840 841
}

842 843
void CppModelManager::onProjectAdded(ProjectExplorer::Project *)
{
844 845
    QMutexLocker locker(&d->m_projectMutex);
    d->m_dirty = true;
846 847
}

848 849
void CppModelManager::delayedGC()
{
850 851
    if (d->m_enableGC)
        d->m_delayedGcTimer.start(500);
852 853
}

con's avatar
con committed
854 855
void CppModelManager::onAboutToRemoveProject(ProjectExplorer::Project *project)
{
856
    do {
857 858 859
        QMutexLocker locker(&d->m_projectMutex);
        d->m_dirty = true;
        d->m_projectToProjectsInfo.remove(project);
860
        recalculateFileToProjectParts();
861 862
    } while (0);

863 864 865
    delayedGC();
}

866 867 868 869 870 871 872 873
void CppModelManager::onSourceFilesRefreshed() const
{
    if (BuiltinIndexingSupport::isFindErrorsIndexingActive()) {
        QTimer::singleShot(1, QCoreApplication::instance(), SLOT(quit()));
        qDebug("FindErrorsIndexing: Done, requesting Qt Creator to quit.");
    }
}

874 875
void CppModelManager::onAboutToLoadSession()
{