cppmodelmanager.cpp 38.2 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
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
** 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
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** 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
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company 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 "baseeditordocumentprocessor.h"
35
#include "builtinindexingsupport.h"
36
#include "cppcodemodelinspectordumper.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 "cpptoolsreuse.h"
45
#include "editordocumenthandle.h"
46
#include "symbolfinder.h"
con's avatar
con committed
47

48
#include <coreplugin/documentmanager.h>
49 50
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
51 52
#include <coreplugin/editormanager/editormanager.h>
#include <texteditor/textdocument.h>
53
#include <projectexplorer/project.h>
54 55
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
56
#include <extensionsystem/pluginmanager.h>
57
#include <utils/algorithm.h>
58
#include <utils/fileutils.h>
hjk's avatar
hjk committed
59
#include <utils/qtcassert.h>
con's avatar
con committed
60

61 62
#include <QCoreApplication>
#include <QDebug>
63
#include <QDir>
64 65
#include <QMutexLocker>
#include <QTextBlock>
66
#include <QTimer>
67

68 69
#if defined(QTCREATOR_WITH_DUMP_AST) && defined(Q_CC_GNU)
#define WITH_AST_DUMP
70 71
#include <iostream>
#include <sstream>
72
#endif
hjk's avatar
hjk committed
73

74 75
Q_DECLARE_METATYPE(QSet<QString>)

76 77
static const bool DumpProjectInfo = qgetenv("QTC_DUMP_PROJECT_INFO") == "1";

78 79
using namespace CppTools;
using namespace CppTools::Internal;
con's avatar
con committed
80 81
using namespace CPlusPlus;

82
#ifdef QTCREATOR_WITH_DUMP_AST
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 114 115 116 117 118 119 120 121

#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
122
#endif // QTCREATOR_WITH_DUMP_AST
123

124 125 126 127 128 129 130 131 132 133 134
namespace CppTools {
namespace Internal {

static QMutex m_instanceMutex;
static CppModelManager *m_instance;

class CppModelManagerPrivate
{
public:
    // Snapshot
    mutable QMutex m_snapshotMutex;
135
    Snapshot m_snapshot;
136 137 138 139

    // Project integration
    mutable QMutex m_projectMutex;
    QMap<ProjectExplorer::Project *, ProjectInfo> m_projectToProjectsInfo;
140
    QMap<Utils::FileName, QList<ProjectPart::Ptr> > m_fileToProjectParts;
141
    QMap<QString, ProjectPart::Ptr> m_projectPartIdToProjectProjectPart;
142 143 144 145 146 147 148
    // 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
149 150
    mutable QMutex m_cppEditorDocumentsMutex;
    QMap<QString, CppEditorDocumentHandle *> m_cppEditorDocuments;
151 152
    QSet<AbstractEditorSupport *> m_extraEditorSupports;

153 154 155
    // Model Manager Supports for e.g. completion and highlighting
    ModelManagerSupport::Ptr m_builtinModelManagerSupport;
    ModelManagerSupport::Ptr m_activeModelManagerSupport;
156 157 158 159 160 161 162 163

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

    CppFindReferences *m_findReferences;

164 165
    SymbolFinder m_symbolFinder;

166 167 168 169 170 171 172
    bool m_enableGC;
    QTimer m_delayedGcTimer;
};

} // namespace Internal

const char pp_configuration[] =
con's avatar
con committed
173
    "# 1 \"<configuration>\"\n"
174
    "#define Q_CREATOR_RUN 1\n"
con's avatar
con committed
175 176 177 178 179 180
    "#define __cplusplus 1\n"
    "#define __extension__\n"
    "#define __context__\n"
    "#define __range__\n"
    "#define   restrict\n"
    "#define __restrict\n"
181
    "#define __restrict__\n"
con's avatar
con committed
182

183 184 185 186
    "#define __complex__\n"
    "#define __imag__\n"
    "#define __real__\n"

187 188
    "#define __builtin_va_arg(a,b) ((b)0)\n"

189 190
    "#define _Pragma(x)\n" // C99 _Pragma operator

191 192 193 194 195 196
    "#define __func__ \"\"\n"

    // ### add macros for gcc
    "#define __PRETTY_FUNCTION__ \"\"\n"
    "#define __FUNCTION__ \"\"\n"

con's avatar
con committed
197 198
    // ### add macros for win32
    "#define __cdecl\n"
199
    "#define __stdcall\n"
200
    "#define __thiscall\n"
con's avatar
con committed
201 202 203 204 205
    "#define QT_WA(x) x\n"
    "#define CALLBACK\n"
    "#define STDMETHODCALLTYPE\n"
    "#define __RPC_FAR\n"
    "#define __declspec(a)\n"
206 207 208
    "#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method\n"
    "#define __try try\n"
    "#define __except catch\n"
209 210
    "#define __finally\n"
    "#define __inline inline\n"
211 212
    "#define __forceinline inline\n"
    "#define __pragma(x)\n";
con's avatar
con committed
213

214
QSet<QString> CppModelManager::timeStampModifiedFiles(const QList<Document::Ptr> &documentsToCheck)
215
{
216
    QSet<QString> sourceFiles;
217

218
    foreach (const Document::Ptr doc, documentsToCheck) {
219 220
        const QDateTime lastModified = doc->lastModified();

221
        if (!lastModified.isNull()) {
222 223 224
            QFileInfo fileInfo(doc->fileName());

            if (fileInfo.exists() && fileInfo.lastModified() != lastModified)
225
                sourceFiles.insert(doc->fileName());
226 227 228
        }
    }

229 230 231
    return sourceFiles;
}

232 233 234 235 236 237 238 239 240 241 242 243
/*!
 * \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
244
    return new CppSourceProcessor(that->snapshot(), [that](const Document::Ptr &doc) {
245 246 247 248 249
        const Document::Ptr previousDocument = that->document(doc->fileName());
        const unsigned newRevision = previousDocument.isNull()
                ? 1U
                : previousDocument->revision() + 1;
        doc->setRevision(newRevision);
250 251 252 253 254
        that->emitDocumentUpdated(doc);
        doc->releaseSourceAndAST();
    });
}

255 256 257 258 259 260 261
QString CppModelManager::editorConfigurationFileName()
{
    return QLatin1String("<per-editor-defines>");
}

QString CppModelManager::configurationFileName()
{
262
    return Preprocessor::configurationFileName();
263 264
}

265 266 267 268 269 270 271
void CppModelManager::updateModifiedSourceFiles()
{
    const Snapshot snapshot = this->snapshot();
    QList<Document::Ptr> documentsToCheck;
    foreach (const Document::Ptr document, snapshot)
        documentsToCheck << document;

272
    updateSourceFiles(timeStampModifiedFiles(documentsToCheck));
273 274
}

con's avatar
con committed
275 276
/*!
    \class CppTools::CppModelManager
277 278 279
    \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
280

281 282 283 284
    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
285 286
*/

287 288
CppModelManager *CppModelManager::instance()
{
289 290 291 292 293 294 295 296
    if (m_instance)
        return m_instance;

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

    return m_instance;
297 298
}

299
void CppModelManager::initializeBuiltinModelManagerSupport()
300 301 302 303 304 305
{
    d->m_builtinModelManagerSupport
            = ModelManagerSupportProviderInternal().createModelManagerSupport();
    d->m_activeModelManagerSupport = d->m_builtinModelManagerSupport;
}

306
CppModelManager::CppModelManager(QObject *parent)
307
    : CppModelManagerBase(parent), d(new CppModelManagerPrivate)
con's avatar
con committed
308
{
309 310 311
    d->m_indexingSupporter = 0;
    d->m_enableGC = true;

312
    qRegisterMetaType<QSet<QString> >();
313
    connect(this, SIGNAL(sourceFilesRefreshed(QSet<QString>)),
314
            this, SLOT(onSourceFilesRefreshed()));
Erik Verbruggen's avatar
Erik Verbruggen committed
315

316 317
    d->m_findReferences = new CppFindReferences(this);
    d->m_indexerEnabled = qgetenv("QTC_NO_CODE_INDEXER") != "1";
Roberto Raggi's avatar
Roberto Raggi committed
318

319
    d->m_dirty = true;
320

321 322 323
    d->m_delayedGcTimer.setObjectName(QLatin1String("CppModelManager::m_delayedGcTimer"));
    d->m_delayedGcTimer.setSingleShot(true);
    connect(&d->m_delayedGcTimer, SIGNAL(timeout()), this, SLOT(GC()));
324

hjk's avatar
hjk committed
325 326
    QObject *sessionManager = ProjectExplorer::SessionManager::instance();
    connect(sessionManager, SIGNAL(projectAdded(ProjectExplorer::Project*)),
327
            this, SLOT(onProjectAdded(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
328
    connect(sessionManager, SIGNAL(aboutToRemoveProject(ProjectExplorer::Project*)),
Robert Loehning's avatar
Robert Loehning committed
329
            this, SLOT(onAboutToRemoveProject(ProjectExplorer::Project*)));
hjk's avatar
hjk committed
330
    connect(sessionManager, SIGNAL(aboutToLoadSession(QString)),
331
            this, SLOT(onAboutToLoadSession()));
hjk's avatar
hjk committed
332
    connect(sessionManager, SIGNAL(aboutToUnloadSession(QString)),
333
            this, SLOT(onAboutToUnloadSession()));
334 335 336 337

    connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
            this, &CppModelManager::onCurrentEditorChanged);

338 339
    connect(Core::DocumentManager::instance(), &Core::DocumentManager::allDocumentsRenamed,
            this, &CppModelManager::renameIncludes);
con's avatar
con committed
340

341 342 343
    connect(Core::ICore::instance(), SIGNAL(coreAboutToClose()),
            this, SLOT(onCoreAboutToClose()));

con's avatar
con committed
344
    qRegisterMetaType<CPlusPlus::Document::Ptr>("CPlusPlus::Document::Ptr");
345
    qRegisterMetaType<QList<Document::DiagnosticMessage>>(
346
                "QList<CPlusPlus::Document::DiagnosticMessage>");
con's avatar
con committed
347

348
    initializeBuiltinModelManagerSupport();
349

350
    d->m_internalIndexingSupport = new BuiltinIndexingSupport;
con's avatar
con committed
351 352 353
}

CppModelManager::~CppModelManager()
354
{
355
    delete d->m_internalIndexingSupport;
356
    delete d;
357
}
con's avatar
con committed
358

359
Snapshot CppModelManager::snapshot() const
360
{
361 362
    QMutexLocker locker(&d->m_snapshotMutex);
    return d->m_snapshot;
363
}
con's avatar
con committed
364

365 366
Document::Ptr CppModelManager::document(const QString &fileName) const
{
367 368
    QMutexLocker locker(&d->m_snapshotMutex);
    return d->m_snapshot.document(fileName);
369 370 371 372 373 374 375
}

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

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

383
    d->m_snapshot.insert(newDoc);
384 385 386
    return true;
}

387
/// Make sure that m_projectMutex is locked when calling this.
388 389
void CppModelManager::ensureUpdated()
{
390
    if (!d->m_dirty)
391 392
        return;

393 394 395 396
    d->m_projectFiles = internalProjectFiles();
    d->m_headerPaths = internalHeaderPaths();
    d->m_definedMacros = internalDefinedMacros();
    d->m_dirty = false;
397 398
}

399
QStringList CppModelManager::internalProjectFiles() const
con's avatar
con committed
400 401
{
    QStringList files;
402
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
403 404
    while (it.hasNext()) {
        it.next();
405
        const ProjectInfo pinfo = it.value();
406
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
407 408
            foreach (const ProjectFile &file, part->files)
                files += file.path;
409
        }
con's avatar
con committed
410
    }
411
    files.removeDuplicates();
con's avatar
con committed
412 413 414
    return files;
}

415
ProjectPart::HeaderPaths CppModelManager::internalHeaderPaths() const
con's avatar
con committed
416
{
417
    ProjectPart::HeaderPaths headerPaths;
418
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
419 420
    while (it.hasNext()) {
        it.next();
421
        const ProjectInfo pinfo = it.value();
422 423
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
            foreach (const ProjectPart::HeaderPath &path, part->headerPaths) {
424
                const ProjectPart::HeaderPath hp(QDir::cleanPath(path.path), path.type);
425 426 427 428
                if (!headerPaths.contains(hp))
                    headerPaths += hp;
            }
        }
con's avatar
con committed
429
    }
430
    return headerPaths;
con's avatar
con committed
431 432
}

433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
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);
        }
    }
}

449
QByteArray CppModelManager::internalDefinedMacros() const
con's avatar
con committed
450 451
{
    QByteArray macros;
452
    QSet<QByteArray> alreadyIn;
453
    QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
con's avatar
con committed
454 455
    while (it.hasNext()) {
        it.next();
456
        const ProjectInfo pinfo = it.value();
457
        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
458 459
            addUnique(part->toolchainDefines.split('\n'), &macros, &alreadyIn);
            addUnique(part->projectDefines.split('\n'), &macros, &alreadyIn);
460
            if (!part->projectConfigFile.isEmpty())
461
                macros += ProjectPart::readProjectConfigFile(part);
462
        }
con's avatar
con committed
463 464 465 466
    }
    return macros;
}

467
/// This function will acquire mutexes!
468
void CppModelManager::dumpModelManagerConfiguration(const QString &logFileId)
469
{
470 471 472 473
    const Snapshot globalSnapshot = snapshot();
    const QString globalSnapshotTitle
        = QString::fromLatin1("Global/Indexing Snapshot (%1 Documents)").arg(globalSnapshot.size());

474
    CppCodeModelInspector::Dumper dumper(globalSnapshot, logFileId);
475 476 477
    dumper.dumpProjectInfos(projectInfos());
    dumper.dumpSnapshot(globalSnapshot, globalSnapshotTitle, /*isGlobalSnapshot=*/ true);
    dumper.dumpWorkingCopy(workingCopy());
478
    dumper.dumpMergedEntities(headerPaths(), definedMacros());
479 480
}

481 482 483 484 485
QSet<AbstractEditorSupport *> CppModelManager::abstractEditorSupports() const
{
    return d->m_extraEditorSupports;
}

486
void CppModelManager::addExtraEditorSupport(AbstractEditorSupport *editorSupport)
487
{
488
    d->m_extraEditorSupports.insert(editorSupport);
489 490
}

491
void CppModelManager::removeExtraEditorSupport(AbstractEditorSupport *editorSupport)
492
{
493
    d->m_extraEditorSupports.remove(editorSupport);
494 495
}

496
CppEditorDocumentHandle *CppModelManager::cppEditorDocument(const QString &filePath) const
497
{
498 499
    if (filePath.isEmpty())
        return 0;
500

501 502
    QMutexLocker locker(&d->m_cppEditorDocumentsMutex);
    return d->m_cppEditorDocuments.value(filePath, 0);
503 504
}

505
void CppModelManager::registerCppEditorDocument(CppEditorDocumentHandle *editorDocument)
506
{
507 508 509
    QTC_ASSERT(editorDocument, return);
    const QString filePath = editorDocument->filePath();
    QTC_ASSERT(!filePath.isEmpty(), return);
510

511 512 513
    QMutexLocker locker(&d->m_cppEditorDocumentsMutex);
    QTC_ASSERT(d->m_cppEditorDocuments.value(filePath, 0) == 0, return);
    d->m_cppEditorDocuments.insert(filePath, editorDocument);
514
}
515

516
void CppModelManager::unregisterCppEditorDocument(const QString &filePath)
517 518
{
    QTC_ASSERT(!filePath.isEmpty(), return);
519

520 521
    static short closedCppDocuments = 0;
    int openCppDocuments = 0;
522

523
    {
524 525 526 527
        QMutexLocker locker(&d->m_cppEditorDocumentsMutex);
        QTC_ASSERT(d->m_cppEditorDocuments.value(filePath, 0), return);
        QTC_CHECK(d->m_cppEditorDocuments.remove(filePath) == 1);
        openCppDocuments = d->m_cppEditorDocuments.size();
528 529
    }

530 531 532
    ++closedCppDocuments;
    if (openCppDocuments == 0 || closedCppDocuments == 5) {
        closedCppDocuments = 0;
533
        delayedGC();
534 535 536
    }
}

537
QList<int> CppModelManager::references(Symbol *symbol, const LookupContext &context)
538
{
539
    return d->m_findReferences->references(symbol, context);
540 541
}

542
void CppModelManager::findUsages(Symbol *symbol, const LookupContext &context)
Roberto Raggi's avatar
Roberto Raggi committed
543
{
544
    if (symbol->identifier())
545
        d->m_findReferences->findUsages(symbol, context);
546 547
}

548 549
void CppModelManager::renameUsages(Symbol *symbol,
                                   const LookupContext &context,
550
                                   const QString &replacement)
551 552
{
    if (symbol->identifier())
553
        d->m_findReferences->renameUsages(symbol, context, replacement);
Roberto Raggi's avatar
Roberto Raggi committed
554 555
}

556
void CppModelManager::findMacroUsages(const Macro &macro)
Christian Kamm's avatar
Christian Kamm committed
557
{
558
    d->m_findReferences->findMacroUses(macro);
Christian Kamm's avatar
Christian Kamm committed
559 560
}

561
void CppModelManager::renameMacroUsages(const Macro &macro, const QString &replacement)
562
{
563
    d->m_findReferences->renameMacroUses(macro, replacement);
564 565
}

566
void CppModelManager::replaceSnapshot(const Snapshot &newSnapshot)
567
{
568 569
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
    d->m_snapshot = newSnapshot;
570 571
}

572
WorkingCopy CppModelManager::buildWorkingCopyList()
con's avatar
con committed
573
{
574
    WorkingCopy workingCopy;
575

576 577 578 579 580
    foreach (const CppEditorDocumentHandle *cppEditorDocument, cppEditorDocuments()) {
        workingCopy.insert(cppEditorDocument->filePath(),
                           cppEditorDocument->contents(),
                           cppEditorDocument->revision());
    }
con's avatar
con committed
581

582
    QSetIterator<AbstractEditorSupport *> it(d->m_extraEditorSupports);
583 584
    while (it.hasNext()) {
        AbstractEditorSupport *es = it.next();
585
        workingCopy.insert(es->fileName(), es->contents(), es->revision());
586 587
    }

588
    // Add the project configuration file
589
    QByteArray conf = codeModelConfiguration();
con's avatar
con committed
590
    conf += definedMacros();
591
    workingCopy.insert(configurationFileName(), conf);
con's avatar
con committed
592 593 594 595

    return workingCopy;
}

596
WorkingCopy CppModelManager::workingCopy() const
597 598 599 600
{
    return const_cast<CppModelManager *>(this)->buildWorkingCopyList();
}

601 602 603 604 605
QByteArray CppModelManager::codeModelConfiguration() const
{
    return QByteArray::fromRawData(pp_configuration, qstrlen(pp_configuration));
}

606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
static QSet<QString> tooBigFilesRemoved(const QSet<QString> &files, int fileSizeLimit)
{
    if (fileSizeLimit == 0)
        return files;

    QSet<QString> result;
    QFileInfo fileInfo;

    QSetIterator<QString> i(files);
    while (i.hasNext()) {
        const QString filePath = i.next();
        fileInfo.setFile(filePath);
        if (skipFileDueToSizeLimit(fileInfo), fileSizeLimit)
            continue;

        result << filePath;
    }

    return result;
}

627
QFuture<void> CppModelManager::updateSourceFiles(const QSet<QString> &sourceFiles,
628
                                                 ProgressNotificationMode mode)
629
{
630
    if (sourceFiles.isEmpty() || !d->m_indexerEnabled)
631 632
        return QFuture<void>();

633 634
    const auto filteredFiles = tooBigFilesRemoved(sourceFiles, fileSizeLimit());

635
    if (d->m_indexingSupporter)
636 637
        d->m_indexingSupporter->refreshSourceFiles(filteredFiles, mode);
    return d->m_internalIndexingSupport->refreshSourceFiles(filteredFiles, mode);
638
}
con's avatar
con committed
639

640
QList<ProjectInfo> CppModelManager::projectInfos() const
641
{
642 643
    QMutexLocker locker(&d->m_projectMutex);
    return d->m_projectToProjectsInfo.values();
644 645
}

646
ProjectInfo CppModelManager::projectInfo(ProjectExplorer::Project *project) const
647
{
648
    QMutexLocker locker(&d->m_projectMutex);
649
    return d->m_projectToProjectsInfo.value(project, ProjectInfo());
650 651
}

652 653 654 655 656 657
/// \brief Remove all files and their includes (recursively) of given ProjectInfo from the snapshot.
void CppModelManager::removeProjectInfoFilesAndIncludesFromSnapshot(const ProjectInfo &projectInfo)
{
    if (!projectInfo.isValid())
        return;

658
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
659 660
    foreach (const ProjectPart::Ptr &projectPart, projectInfo.projectParts()) {
        foreach (const ProjectFile &cxxFile, projectPart->files) {
661 662 663
            foreach (const QString &fileName, d->m_snapshot.allIncludesForDocument(cxxFile.path))
                d->m_snapshot.remove(fileName);
            d->m_snapshot.remove(cxxFile.path);
664 665 666 667
        }
    }
}

668
QList<CppEditorDocumentHandle *> CppModelManager::cppEditorDocuments() const
669
{
670 671
    QMutexLocker locker(&d->m_cppEditorDocumentsMutex);
    return d->m_cppEditorDocuments.values();
672 673
}

674 675 676
/// \brief Remove all given files from the snapshot.
void CppModelManager::removeFilesFromSnapshot(const QSet<QString> &filesToRemove)
{
677
    QMutexLocker snapshotLocker(&d->m_snapshotMutex);
678 679
    QSetIterator<QString> i(filesToRemove);
    while (i.hasNext())
680
        d->m_snapshot.remove(i.next());
681 682
}

683
static QSet<QString> projectPartIds(const QSet<ProjectPart::Ptr> &projectParts)
684
{
685 686 687
    return Utils::transform(projectParts, [](const ProjectPart::Ptr &projectPart) {
        return projectPart->id();
    });
688 689
}

690
class ProjectInfoComparer
691
{
692
public:
693 694
    ProjectInfoComparer(const ProjectInfo &oldProjectInfo,
                        const ProjectInfo &newProjectInfo)
695
        : m_old(oldProjectInfo)
696
        , m_oldSourceFiles(oldProjectInfo.sourceFiles())
697
        , m_new(newProjectInfo)
698
        , m_newSourceFiles(newProjectInfo.sourceFiles())
699 700
    {}

701 702 703
    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); }
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718

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

719 720
    QStringList removedProjectParts()
    {
721 722 723
        QSet<QString> removed = projectPartIds(m_old.projectParts().toSet());
        removed.subtract(projectPartIds(m_new.projectParts().toSet()));
        return removed.toList();
724 725
    }

726 727 728 729 730 731 732 733 734 735 736 737 738 739
    /// 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;
        }

740
        return CppModelManager::timeStampModifiedFiles(documentsToCheck);
741 742 743
    }

private:
744
    const ProjectInfo &m_old;
745 746
    const QSet<QString> m_oldSourceFiles;

747
    const ProjectInfo &m_new;
748 749 750
    const QSet<QString> m_newSourceFiles;
};

751
/// Make sure that m_projectMutex is locked when calling this.
752
void CppModelManager::recalculateProjectPartMappings()
753
{
754
    d->m_projectPartIdToProjectProjectPart.clear();
755 756
    d->m_fileToProjectParts.clear();
    foreach (const ProjectInfo &projectInfo, d->m_projectToProjectsInfo) {
757
        foreach (const ProjectPart::Ptr &projectPart, projectInfo.projectParts()) {
758
            d->m_projectPartIdToProjectProjectPart[projectPart->id()] = projectPart;
759
            foreach (const ProjectFile &cxxFile, projectPart->files)
760 761
                d->m_fileToProjectParts[Utils::FileName::fromString(cxxFile.path)].append(
                            projectPart);
762

763 764
        }
    }
765 766

    d->m_symbolFinder.clearCache();
767 768
}

769
void CppModelManager::updateCppEditorDocuments() const
770
{
771 772
    // Refresh visible documents
    QSet<Core::IDocument *> visibleCppEditorDocuments;
773
    foreach (Core::IEditor *editor, Core::EditorManager::visibleEditors()) {
774
        if (Core::IDocument *document = editor->document()) {
775 776
            const QString filePath = document->filePath().toString();
            if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) {
777
                visibleCppEditorDocuments.insert(document);
778
                theCppEditorDocument->processor()->run();
779
            }
780 781 782
        }
    }

783 784 785 786 787
    // Mark invisible documents dirty
    QSet<Core::IDocument *> invisibleCppEditorDocuments
        = Core::DocumentModel::openedDocuments().toSet();
    invisibleCppEditorDocuments.subtract(visibleCppEditorDocuments);
    foreach (Core::IDocument *document, invisibleCppEditorDocuments) {
788 789 790
        const QString filePath = document->filePath().toString();
        if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath))
            theCppEditorDocument->setNeedsRefresh(true);
791 792 793
    }
}

794 795 796
QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo &newProjectInfo)
{
    if (!newProjectInfo.isValid())
797
        return QFuture<void>();
798

799
    QSet<QString> filesToReindex;
800
    QStringList removedProjectParts;
801 802
    bool filesRemoved = false;

803
    { // Only hold the mutex for a limited scope, so the dumping afterwards does not deadlock.
804
        QMutexLocker projectLocker(&d->m_projectMutex);
805 806

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

809
        // Check if we can avoid a full reindexing
810
        ProjectInfo oldProjectInfo = d->m_projectToProjectsInfo.value(project);
811
        if (oldProjectInfo.isValid()) {
812
            ProjectInfoComparer comparer(oldProjectInfo, newProjectInfo);
813

814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
            if (comparer.configurationOrFilesChanged()) {
                d->m_dirty = true;

                // If the project configuration changed, do a full reindexing
                if (comparer.configurationChanged()) {
                    removeProjectInfoFilesAndIncludesFromSnapshot(oldProjectInfo);
                    filesToReindex.unite(newSourceFiles);

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

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

                    const QSet<QString> modifiedFiles = comparer.timeStampModifiedFiles(snapshot());
                    filesToReindex.unite(modifiedFiles);
835
                }
836

837 838 839 840 841 842 843
                // 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);
                }
844 845
            }

846
            removedProjectParts = comparer.removedProjectParts();
847

848 849
        // A new project was opened/created, do a full indexing
        } else {
850
            d->m_dirty = true;
851
            filesToReindex.unite(newSourceFiles);
852 853
        }

854
        // Update Project/ProjectInfo and File/ProjectPart table
855
        d->m_projectToProjectsInfo.insert(project, newProjectInfo);
856
        recalculateProjectPartMappings();
857

858 859 860
    } // Mutex scope

    // If requested, dump everything we got
861
    if (DumpProjectInfo)
862
        dumpModelManagerConfiguration(QLatin1String("updateProjectInfo"));
863

864 865 866 867
    // Remove files from snapshot that are not reachable any more
    if (filesRemoved)
        GC();

868 869 870 871
    // Announce removed project parts
    if (!removedProjectParts.isEmpty())
        emit projectPartsRemoved(removedProjectParts);

872
    // Announce added project parts
873
    emit projectPartsUpdated(newProjectInfo.project().data());
874

875 876 877 878
    // 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.
879
    updateCppEditorDocuments();
880

881 882
    // Trigger reindexing
    return updateSourceFiles(filesToReindex, ForcedProgressNotification);
883