cppmodelmanager.cpp 29.3 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 13 14
** 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.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** 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
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29

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

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

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

51 52 53 54
#include <QCoreApplication>
#include <QDebug>
#include <QMutexLocker>
#include <QTextBlock>
55
#include <QTimer>
56

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

63 64
Q_DECLARE_METATYPE(QSet<QString>)

65 66
static const bool DumpProjectInfo = qgetenv("QTC_DUMP_PROJECT_INFO") == "1";

67 68
using namespace CppTools;
using namespace CppTools::Internal;
con's avatar
con committed
69 70
using namespace CPlusPlus;

71
#ifdef QTCREATOR_WITH_DUMP_AST
72 73 74 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

#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
111
#endif // QTCREATOR_WITH_DUMP_AST
112

con's avatar
con committed
113 114
static const char pp_configuration[] =
    "# 1 \"<configuration>\"\n"
115
    "#define Q_CREATOR_RUN 1\n"
con's avatar
con committed
116 117 118 119 120 121
    "#define __cplusplus 1\n"
    "#define __extension__\n"
    "#define __context__\n"
    "#define __range__\n"
    "#define   restrict\n"
    "#define __restrict\n"
122
    "#define __restrict__\n"
con's avatar
con committed
123

124 125 126 127
    "#define __complex__\n"
    "#define __imag__\n"
    "#define __real__\n"

128 129
    "#define __builtin_va_arg(a,b) ((b)0)\n"

con's avatar
con committed
130 131
    // ### add macros for win32
    "#define __cdecl\n"
132
    "#define __stdcall\n"
133
    "#define __thiscall\n"
con's avatar
con committed
134 135 136 137 138
    "#define QT_WA(x) x\n"
    "#define CALLBACK\n"
    "#define STDMETHODCALLTYPE\n"
    "#define __RPC_FAR\n"
    "#define __declspec(a)\n"
139 140 141
    "#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method\n"
    "#define __try try\n"
    "#define __except catch\n"
142 143 144
    "#define __finally\n"
    "#define __inline inline\n"
    "#define __forceinline inline\n";
con's avatar
con committed
145

146
QSet<QString> CppModelManager::timeStampModifiedFiles(const QList<Document::Ptr> &documentsToCheck)
147
{
148
    QSet<QString> sourceFiles;
149

150
    foreach (const Document::Ptr doc, documentsToCheck) {
151 152
        const QDateTime lastModified = doc->lastModified();

153
        if (!lastModified.isNull()) {
154 155 156
            QFileInfo fileInfo(doc->fileName());

            if (fileInfo.exists() && fileInfo.lastModified() != lastModified)
157
                sourceFiles.insert(doc->fileName());
158 159 160
        }
    }

161 162 163
    return sourceFiles;
}

164 165 166 167 168 169 170 171 172 173 174 175
/*!
 * \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
176
    return new CppSourceProcessor(that->snapshot(), [that](const Document::Ptr &doc) {
177 178 179 180 181
        that->emitDocumentUpdated(doc);
        doc->releaseSourceAndAST();
    });
}

182 183 184 185 186 187 188
void CppModelManager::updateModifiedSourceFiles()
{
    const Snapshot snapshot = this->snapshot();
    QList<Document::Ptr> documentsToCheck;
    foreach (const Document::Ptr document, snapshot)
        documentsToCheck << document;

189
    updateSourceFiles(timeStampModifiedFiles(documentsToCheck));
190 191
}

con's avatar
con committed
192 193
/*!
    \class CppTools::CppModelManager
194 195 196
    \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
197

198 199 200 201
    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
202 203
*/

204 205
QMutex CppModelManager::m_instanceMutex;
CppModelManager *CppModelManager::m_instance = 0;
206 207 208

CppModelManager *CppModelManager::instance()
{
209 210 211 212 213 214 215 216
    if (m_instance)
        return m_instance;

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

    return m_instance;
217 218
}

219
CppModelManager::CppModelManager(QObject *parent)
220
    : CppModelManagerInterface(parent)
221
    , m_indexingSupporter(0)
222
    , m_enableGC(true)
con's avatar
con committed
223
{
224
    qRegisterMetaType<QSet<QString> >();
225
    connect(this, SIGNAL(sourceFilesRefreshed(QSet<QString>)),
226
            this, SLOT(onSourceFilesRefreshed()));
Erik Verbruggen's avatar
Erik Verbruggen committed
227

Roberto Raggi's avatar
Roberto Raggi committed
228
    m_findReferences = new CppFindReferences(this);
229
    m_indexerEnabled = qgetenv("QTC_NO_CODE_INDEXER") != "1";
Roberto Raggi's avatar
Roberto Raggi committed
230

231 232
    m_dirty = true;

233
    m_delayedGcTimer = new QTimer(this);
234
    m_delayedGcTimer->setObjectName(QLatin1String("CppModelManager::m_delayedGcTimer"));
235 236 237
    m_delayedGcTimer->setSingleShot(true);
    connect(m_delayedGcTimer, SIGNAL(timeout()), this, SLOT(GC()));

hjk's avatar
hjk committed
238 239
    QObject *sessionManager = ProjectExplorer::SessionManager::instance();
    connect(sessionManager, SIGNAL(projectAdded(ProjectExplorer::Project*)),
240
            this, SLOT(onProjectAdded(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
241
    connect(sessionManager, SIGNAL(aboutToRemoveProject(ProjectExplorer::Project*)),
Robert Loehning's avatar
Robert Loehning committed
242
            this, SLOT(onAboutToRemoveProject(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
243
    connect(sessionManager, SIGNAL(aboutToLoadSession(QString)),
244
            this, SLOT(onAboutToLoadSession()));
hjk's avatar
hjk committed
245
    connect(sessionManager, SIGNAL(aboutToUnloadSession(QString)),
246
            this, SLOT(onAboutToUnloadSession()));
con's avatar
con committed
247

248 249 250
    connect(Core::ICore::instance(), SIGNAL(coreAboutToClose()),
            this, SLOT(onCoreAboutToClose()));

con's avatar
con committed
251 252
    qRegisterMetaType<CPlusPlus::Document::Ptr>("CPlusPlus::Document::Ptr");

253
    m_modelManagerSupportFallback.reset(new ModelManagerSupportInternal);
254 255
    CppToolsPlugin::instance()->codeModelSettings()->setDefaultId(
                m_modelManagerSupportFallback->id());
256 257
    addModelManagerSupport(m_modelManagerSupportFallback.data());

258
    m_internalIndexingSupport = new BuiltinIndexingSupport;
con's avatar
con committed
259 260 261
}

CppModelManager::~CppModelManager()
262
{
263
    delete m_internalIndexingSupport;
264
}
con's avatar
con committed
265

266
Snapshot CppModelManager::snapshot() const
267
{
268
    QMutexLocker locker(&m_snapshotMutex);
269 270
    return m_snapshot;
}
con's avatar
con committed
271

272 273
Document::Ptr CppModelManager::document(const QString &fileName) const
{
274
    QMutexLocker locker(&m_snapshotMutex);
275 276 277 278 279 280 281 282
    return m_snapshot.document(fileName);
}

/// Replace the document in the snapshot.
///
/// \returns true if successful, false if the new document is out-dated.
bool CppModelManager::replaceDocument(Document::Ptr newDoc)
{
283
    QMutexLocker locker(&m_snapshotMutex);
284 285 286 287 288 289 290 291 292 293

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

    m_snapshot.insert(newDoc);
    return true;
}

294 295
void CppModelManager::ensureUpdated()
{
296
    QMutexLocker locker(&m_projectMutex);
297
    if (!m_dirty)
298 299
        return;

300
    m_projectFiles = internalProjectFiles();
301
    m_headerPaths = internalHeaderPaths();
302
    m_definedMacros = internalDefinedMacros();
303 304 305
    m_dirty = false;
}

306
QStringList CppModelManager::internalProjectFiles() const
con's avatar
con committed
307 308
{
    QStringList files;
309
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(m_projectToProjectsInfo);
con's avatar
con committed
310 311
    while (it.hasNext()) {
        it.next();
312
        const ProjectInfo pinfo = it.value();
313
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
314 315
            foreach (const ProjectFile &file, part->files)
                files += file.path;
316
        }
con's avatar
con committed
317
    }
318
    files.removeDuplicates();
con's avatar
con committed
319 320 321
    return files;
}

322
ProjectPart::HeaderPaths CppModelManager::internalHeaderPaths() const
con's avatar
con committed
323
{
324
    ProjectPart::HeaderPaths headerPaths;
325
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(m_projectToProjectsInfo);
con's avatar
con committed
326 327
    while (it.hasNext()) {
        it.next();
328
        const ProjectInfo pinfo = it.value();
329 330 331 332 333 334 335 336
        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
337
    }
338
    return headerPaths;
con's avatar
con committed
339 340
}

341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
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);
        }
    }
}

357
QByteArray CppModelManager::internalDefinedMacros() const
con's avatar
con committed
358 359
{
    QByteArray macros;
360
    QSet<QByteArray> alreadyIn;
361
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(m_projectToProjectsInfo);
con's avatar
con committed
362 363
    while (it.hasNext()) {
        it.next();
364
        const ProjectInfo pinfo = it.value();
365
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
366 367
            addUnique(part->toolchainDefines.split('\n'), &macros, &alreadyIn);
            addUnique(part->projectDefines.split('\n'), &macros, &alreadyIn);
368
            if (!part->projectConfigFile.isEmpty())
369
                macros += ProjectPart::readProjectConfigFile(part);
370
        }
con's avatar
con committed
371 372 373 374
    }
    return macros;
}

375
/// This function will acquire mutexes!
376
void CppModelManager::dumpModelManagerConfiguration(const QString &logFileId)
377
{
378 379 380 381
    const Snapshot globalSnapshot = snapshot();
    const QString globalSnapshotTitle
        = QString::fromLatin1("Global/Indexing Snapshot (%1 Documents)").arg(globalSnapshot.size());

382
    CppCodeModelInspector::Dumper dumper(globalSnapshot, logFileId);
383 384 385
    dumper.dumpProjectInfos(projectInfos());
    dumper.dumpSnapshot(globalSnapshot, globalSnapshotTitle, /*isGlobalSnapshot=*/ true);
    dumper.dumpWorkingCopy(workingCopy());
386
    ensureUpdated();
387
    dumper.dumpMergedEntities(m_headerPaths, m_definedMacros);
388 389
}

390
void CppModelManager::addExtraEditorSupport(AbstractEditorSupport *editorSupport)
391
{
392
    m_extraEditorSupports.insert(editorSupport);
393 394
}

395
void CppModelManager::removeExtraEditorSupport(AbstractEditorSupport *editorSupport)
396
{
397
    m_extraEditorSupports.remove(editorSupport);
398 399
}

400
EditorDocumentHandle *CppModelManager::editorDocument(const QString &filePath)
401
{
402
    QTC_ASSERT(!filePath.isEmpty(), return 0);
403

404 405
    QMutexLocker locker(&m_cppEditorsMutex);
    return m_cppEditors.value(filePath, 0);
406 407
}

408
void CppModelManager::registerEditorDocument(EditorDocumentHandle *editorDocument)
409
{
410 411 412
    QTC_ASSERT(editorDocument, return);
    const QString filePath = editorDocument->filePath();
    QTC_ASSERT(!filePath.isEmpty(), return);
413

414 415 416 417
    QMutexLocker locker(&m_cppEditorsMutex);
    QTC_ASSERT(m_cppEditors.value(filePath, 0) == 0, return);
    m_cppEditors.insert(filePath, editorDocument);
}
418

419 420 421
void CppModelManager::unregisterEditorDocument(const QString &filePath)
{
    QTC_ASSERT(!filePath.isEmpty(), return);
422

423 424
    static short closedCppDocuments = 0;
    int openCppDocuments = 0;
425

426 427 428 429 430
    {
        QMutexLocker locker(&m_cppEditorsMutex);
        QTC_ASSERT(m_cppEditors.value(filePath, 0), return);
        QTC_CHECK(m_cppEditors.remove(filePath) == 1);
        openCppDocuments = m_cppEditors.size();
431 432
    }

433 434 435
    ++closedCppDocuments;
    if (openCppDocuments == 0 || closedCppDocuments == 5) {
        closedCppDocuments = 0;
436
        delayedGC();
437 438 439
    }
}

440
QList<int> CppModelManager::references(CPlusPlus::Symbol *symbol, const LookupContext &context)
441
{
442
    return m_findReferences->references(symbol, context);
443 444
}

445
void CppModelManager::findUsages(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context)
Roberto Raggi's avatar
Roberto Raggi committed
446
{
447
    if (symbol->identifier())
448
        m_findReferences->findUsages(symbol, context);
449 450
}

451 452
void CppModelManager::renameUsages(CPlusPlus::Symbol *symbol,
                                   const CPlusPlus::LookupContext &context,
453
                                   const QString &replacement)
454 455
{
    if (symbol->identifier())
456
        m_findReferences->renameUsages(symbol, context, replacement);
Roberto Raggi's avatar
Roberto Raggi committed
457 458
}

Christian Kamm's avatar
Christian Kamm committed
459 460 461 462 463
void CppModelManager::findMacroUsages(const CPlusPlus::Macro &macro)
{
    m_findReferences->findMacroUses(macro);
}

464 465 466 467 468
void CppModelManager::renameMacroUsages(const CPlusPlus::Macro &macro, const QString &replacement)
{
    m_findReferences->renameMacroUses(macro, replacement);
}

469 470
void CppModelManager::replaceSnapshot(const CPlusPlus::Snapshot &newSnapshot)
{
471
    QMutexLocker snapshotLocker(&m_snapshotMutex);
472 473 474
    m_snapshot = newSnapshot;
}

475
WorkingCopy CppModelManager::buildWorkingCopyList()
con's avatar
con committed
476
{
477
    WorkingCopy workingCopy;
478

479 480
    foreach (const EditorDocumentHandle *cppEditor, cppEditors())
        workingCopy.insert(cppEditor->filePath(), cppEditor->contents(), cppEditor->revision());
con's avatar
con committed
481

482 483 484
    QSetIterator<AbstractEditorSupport *> it(m_extraEditorSupports);
    while (it.hasNext()) {
        AbstractEditorSupport *es = it.next();
485
        workingCopy.insert(es->fileName(), es->contents(), es->revision());
486 487
    }

488
    // Add the project configuration file
489
    QByteArray conf = codeModelConfiguration();
con's avatar
con committed
490
    conf += definedMacros();
491
    workingCopy.insert(configurationFileName(), conf);
con's avatar
con committed
492 493 494 495

    return workingCopy;
}

496
WorkingCopy CppModelManager::workingCopy() const
497 498 499 500
{
    return const_cast<CppModelManager *>(this)->buildWorkingCopyList();
}

501 502 503 504 505
QByteArray CppModelManager::codeModelConfiguration() const
{
    return QByteArray::fromRawData(pp_configuration, qstrlen(pp_configuration));
}

506
QFuture<void> CppModelManager::updateSourceFiles(const QSet<QString> &sourceFiles,
507
                                                 ProgressNotificationMode mode)
508 509 510 511
{
    if (sourceFiles.isEmpty() || !m_indexerEnabled)
        return QFuture<void>();

512
    if (m_indexingSupporter)
513 514
        m_indexingSupporter->refreshSourceFiles(sourceFiles, mode);
    return m_internalIndexingSupport->refreshSourceFiles(sourceFiles, mode);
515
}
con's avatar
con committed
516

517
QList<ProjectInfo> CppModelManager::projectInfos() const
518
{
519
    QMutexLocker locker(&m_projectMutex);
520
    return m_projectToProjectsInfo.values();
521 522
}

523
ProjectInfo CppModelManager::projectInfo(ProjectExplorer::Project *project) const
524
{
525
    QMutexLocker locker(&m_projectMutex);
526
    return m_projectToProjectsInfo.value(project, ProjectInfo(project));
527 528
}

529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
/// \brief Remove all files and their includes (recursively) of given ProjectInfo from the snapshot.
void CppModelManager::removeProjectInfoFilesAndIncludesFromSnapshot(const ProjectInfo &projectInfo)
{
    if (!projectInfo.isValid())
        return;

    QMutexLocker snapshotLocker(&m_snapshotMutex);
    foreach (const ProjectPart::Ptr &projectPart, projectInfo.projectParts()) {
        foreach (const ProjectFile &cxxFile, projectPart->files) {
            foreach (const QString &fileName, m_snapshot.allIncludesForDocument(cxxFile.path))
                m_snapshot.remove(fileName);
            m_snapshot.remove(cxxFile.path);
        }
    }
}

545
QList<EditorDocumentHandle *> CppModelManager::cppEditors() const
546
{
547 548
    QMutexLocker locker(&m_cppEditorsMutex);
    return m_cppEditors.values();
549 550
}

551 552 553 554 555 556 557 558 559 560
/// \brief Remove all given files from the snapshot.
void CppModelManager::removeFilesFromSnapshot(const QSet<QString> &filesToRemove)
{
    QMutexLocker snapshotLocker(&m_snapshotMutex);
    QSetIterator<QString> i(filesToRemove);
    while (i.hasNext())
        m_snapshot.remove(i.next());
}

class ProjectInfoComparer
561
{
562
public:
563 564
    ProjectInfoComparer(const ProjectInfo &oldProjectInfo,
                        const ProjectInfo &newProjectInfo)
565
        : m_old(oldProjectInfo)
566
        , m_oldSourceFiles(oldProjectInfo.sourceFiles())
567
        , m_new(newProjectInfo)
568
        , m_newSourceFiles(newProjectInfo.sourceFiles())
569 570 571 572 573 574 575 576 577 578
    {}

    bool definesChanged() const
    {
        return m_new.defines() != m_old.defines();
    }

    bool configurationChanged() const
    {
        return definesChanged()
579
            || m_new.headerPaths() != m_old.headerPaths();
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
    }

    bool nothingChanged() const
    {
        return !configurationChanged() && m_new.sourceFiles() == m_old.sourceFiles();
    }

    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;
        }

615
        return CppModelManager::timeStampModifiedFiles(documentsToCheck);
616 617 618
    }

private:
619
    const ProjectInfo &m_old;
620 621
    const QSet<QString> m_oldSourceFiles;

622
    const ProjectInfo &m_new;
623 624 625
    const QSet<QString> m_newSourceFiles;
};

626 627 628
/// Make sure that m_projectMutex is locked when calling this.
void CppModelManager::recalculateFileToProjectParts()
{
629
    m_projectFileToProjectPart.clear();
630 631 632
    m_fileToProjectParts.clear();
    foreach (const ProjectInfo &projectInfo, m_projectToProjectsInfo) {
        foreach (const ProjectPart::Ptr &projectPart, projectInfo.projectParts()) {
633 634
            m_projectFileToProjectPart[projectPart->projectFile] = projectPart;
            foreach (const ProjectFile &cxxFile, projectPart->files)
635
                m_fileToProjectParts[cxxFile.path].append(projectPart);
636

637 638 639 640
        }
    }
}

641 642 643
QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo &newProjectInfo)
{
    if (!newProjectInfo.isValid())
644
        return QFuture<void>();
645

646
    QSet<QString> filesToReindex;
647 648
    bool filesRemoved = false;

649
    { // Only hold the mutex for a limited scope, so the dumping afterwards does not deadlock.
650 651 652
        QMutexLocker projectLocker(&m_projectMutex);

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

655
        // Check if we can avoid a full reindexing
656
        ProjectInfo oldProjectInfo = m_projectToProjectsInfo.value(project);
657
        if (oldProjectInfo.isValid()) {
658 659
            ProjectInfoComparer comparer(oldProjectInfo, newProjectInfo);
            if (comparer.nothingChanged())
660 661
                return QFuture<void>();

662 663 664
            // If the project configuration changed, do a full reindexing
            if (comparer.configurationChanged()) {
                removeProjectInfoFilesAndIncludesFromSnapshot(oldProjectInfo);
665
                filesToReindex.unite(newSourceFiles);
666 667 668 669 670

                // The "configuration file" includes all defines and therefore should be updated
                if (comparer.definesChanged()) {
                    QMutexLocker snapshotLocker(&m_snapshotMutex);
                    m_snapshot.remove(configurationFileName());
671
                }
672 673 674 675

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

                const QSet<QString> modifiedFiles = comparer.timeStampModifiedFiles(snapshot());
679
                filesToReindex.unite(modifiedFiles);
680
            }
681 682 683 684 685 686 687 688 689 690 691

            // 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 {
692
            filesToReindex.unite(newSourceFiles);
693 694
        }

695
        // Update Project/ProjectInfo and File/ProjectPart table
696
        m_dirty = true;
697
        m_projectToProjectsInfo.insert(project, newProjectInfo);
698
        recalculateFileToProjectParts();
699

700 701 702
    } // Mutex scope

    // If requested, dump everything we got
703
    if (DumpProjectInfo)
704
        dumpModelManagerConfiguration(QLatin1String("updateProjectInfo"));
705

706 707 708 709 710
    // Remove files from snapshot that are not reachable any more
    if (filesRemoved)
        GC();

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

712 713
    // Trigger reindexing
    return updateSourceFiles(filesToReindex, ForcedProgressNotification);
714 715
}

716 717 718 719 720
ProjectPart::Ptr CppModelManager::projectPartForProjectFile(const QString &projectFile) const
{
    return m_projectFileToProjectPart.value(projectFile);
}

721
QList<ProjectPart::Ptr> CppModelManager::projectPart(const QString &fileName) const
722
{
723
    QMutexLocker locker(&m_projectMutex);
724 725
    return m_fileToProjectParts.value(fileName);
}
726

727 728 729
QList<ProjectPart::Ptr> CppModelManager::projectPartFromDependencies(const QString &fileName) const
{
    QSet<ProjectPart::Ptr> parts;
730
    const QStringList deps = snapshot().filesDependingOn(fileName);
731 732
    foreach (const QString &dep, deps)
        parts.unite(QSet<ProjectPart::Ptr>::fromList(m_fileToProjectParts.value(dep)));
733

734
    return parts.values();
735
}
con's avatar
con committed
736

737 738 739 740
ProjectPart::Ptr CppModelManager::fallbackProjectPart() const
{
    ProjectPart::Ptr part(new ProjectPart);

741
    part->projectDefines = m_definedMacros;
742
    part->headerPaths = m_headerPaths;
743 744
    part->languageVersion = ProjectPart::CXX14;
    part->languageExtensions = ProjectPart::AllExtensions;
745 746 747 748 749
    part->qtVersion = ProjectPart::Qt5;

    return part;
}

con's avatar
con committed
750 751
bool CppModelManager::isCppEditor(Core::IEditor *editor) const
{
752
    return editor->context().contains(ProjectExplorer::Constants::LANG_CXX);
con's avatar
con committed
753 754 755 756
}

void CppModelManager::emitDocumentUpdated(Document::Ptr doc)
{
757
    if (replaceDocument(doc))
758
        emit documentUpdated(doc);
Roberto Raggi's avatar
Roberto Raggi committed
759 760
}

761 762
void CppModelManager::onProjectAdded(ProjectExplorer::Project *)
{
763
    QMutexLocker locker(&m_projectMutex);
764 765 766
    m_dirty = true;
}

767 768
void CppModelManager::delayedGC()
{
769 770
    if (m_enableGC)
        m_delayedGcTimer->start(500);
771 772
}

con's avatar
con committed
773 774
void CppModelManager::onAboutToRemoveProject(ProjectExplorer::Project *project)
{
775
    do {
776
        QMutexLocker locker(&m_projectMutex);
777
        m_dirty = true;
778
        m_projectToProjectsInfo.remove(project);
779
        recalculateFileToProjectParts();
780 781
    } while (0);

782 783 784
    delayedGC();
}

785 786 787 788 789 790 791 792
void CppModelManager::onSourceFilesRefreshed() const
{
    if (BuiltinIndexingSupport::isFindErrorsIndexingActive()) {
        QTimer::singleShot(1, QCoreApplication::instance(), SLOT(quit()));
        qDebug("FindErrorsIndexing: Done, requesting Qt Creator to quit.");
    }
}

793 794 795 796
void CppModelManager::onAboutToLoadSession()
{
    if (m_delayedGcTimer->isActive())
        m_delayedGcTimer->stop();
con's avatar
con committed
797 798 799
    GC();
}

800
void CppModelManager::onAboutToUnloadSession()
con's avatar
con committed
801
{
802
    Core::ProgressManager::cancelTasks(CppTools::Constants::TASK_INDEX);
803
    do {
804
        QMutexLocker locker(&m_projectMutex);
805
        m_projectToProjectsInfo.clear();
806
        recalculateFileToProjectParts();
807 808
        m_dirty = true;
    } while (0);
con's avatar
con committed
809 810
}

811 812 813 814 815
void CppModelManager::onCoreAboutToClose()
{
    m_enableGC = false;
}

con's avatar
con committed
816 817
void CppModelManager::GC()
{
818 819 820
    if (!m_enableGC)
        return;

821 822
    // Collect files of CppEditorSupport and AbstractEditorSupport.
    QStringList filesInEditorSupports;
823 824
    foreach (const EditorDocumentHandle *cppEditor, cppEditors())
        filesInEditorSupports << cppEditor->filePath();
825 826 827 828 829 830 831 832

    QSetIterator<AbstractEditorSupport *> jt(m_extraEditorSupports);
    while (jt.hasNext()) {
        AbstractEditorSupport *abstractEditorSupport = jt.next();
        filesInEditorSupports << abstractEditorSupport->fileName();
    }

    Snapshot currentSnapshot = snapshot();
833
    QSet<QString> reachableFiles;
834 835 836 837
    // The configuration file is part of the project files, which is just fine.
    // If single files are open, without any project, then there is no need to
    // keep the configuration file around.
    QStringList todo = filesInEditorSupports + projectFiles();
con's avatar
con committed
838

839
    // Collect all files that are reachable from the project files
840
    while (!todo.isEmpty()) {
841
        const QString file = todo.last();
con's avatar
con committed
842 843
        todo.removeLast();

844
        if (reachableFiles.contains(file))
con's avatar
con committed
845
            continue;
846
        reachableFiles.insert(file);
con's avatar
con committed
847

848
        if (Document::Ptr doc = currentSnapshot.document(file))
con's avatar
con committed
849 850 851
            todo += doc->includedFiles();
    }

852 853
    // Find out the files in the current snapshot that are not reachable from the project files
    QStringList notReachableFiles;
854 855 856 857
    Snapshot newSnapshot;
    for (Snapshot::const_iterator it = currentSnapshot.begin(); it != currentSnapshot.end(); ++it) {
        const QString fileName = it.key();

858
        if (reachableFiles.contains(fileName))
859 860
            newSnapshot.insert(it.value());
        else
861
            notReachableFiles.append(fileName);
con's avatar
con committed
862 863
    }

864 865
    // Announce removing files and replace the snapshot
    emit aboutToRemoveFiles(notReachableFiles);
866
    replaceSnapshot(newSnapshot);
867
    emit gcFinished();
con's avatar
con committed
868
}
869

Erik Verbruggen's avatar