// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "cppmodelmanager.h" #include "abstracteditorsupport.h" #include "baseeditordocumentprocessor.h" #include "compileroptionsbuilder.h" #include "cppbuiltinmodelmanagersupport.h" #include "cppcanonicalsymbol.h" #include "cppcodemodelinspectordumper.h" #include "cppcodemodelsettings.h" #include "cppeditorconstants.h" #include "cppeditortr.h" #include "cppeditorwidget.h" #include "cppfindreferences.h" #include "cppincludesfilter.h" #include "cppindexingsupport.h" #include "cpplocatordata.h" #include "cpplocatorfilter.h" #include "cppprojectfile.h" #include "cppsourceprocessor.h" #include "cpptoolsjsextension.h" #include "cpptoolsreuse.h" #include "editordocumenthandle.h" #include "symbolfinder.h" #include "symbolsfindfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(QTCREATOR_WITH_DUMP_AST) && defined(Q_CC_GNU) #define WITH_AST_DUMP #include #include #endif using namespace Core; using namespace CPlusPlus; using namespace ProjectExplorer; using namespace Utils; static const bool DumpProjectInfo = qtcEnvironmentVariable("QTC_DUMP_PROJECT_INFO") == "1"; #ifdef QTCREATOR_WITH_DUMP_AST #include class DumpAST: protected ASTVisitor { public: int depth; explicit 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(QRegularExpression("\\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; } }; #endif // QTCREATOR_WITH_DUMP_AST namespace CppEditor { namespace Internal { static CppModelManager *m_instance; class ProjectData { public: ProjectInfo::ConstPtr projectInfo; QFutureWatcher *indexer = nullptr; bool fullyIndexed = false; }; class CppModelManagerPrivate { public: void setupWatcher(const QFuture &future, Project *project, ProjectData *projectData, CppModelManager *q); // Snapshot mutable QMutex m_snapshotMutex; Snapshot m_snapshot; // Project integration struct SyncedProjectData { QHash m_projectData; QMap> m_fileToProjectParts; QMap m_projectPartIdToProjectProjectPart; // The members below are cached/(re)calculated from the projects and/or their project parts bool m_dirty{true}; FilePaths m_projectFiles; HeaderPaths m_headerPaths; Macros m_definedMacros; }; Utils::SynchronizedValue m_lockedProjectData; // Editor integration mutable QMutex m_cppEditorDocumentsMutex; QMap m_cppEditorDocuments; QSet m_extraEditorSupports; // Model Manager Supports for e.g. completion and highlighting BuiltinModelManagerSupport m_builtinModelManagerSupport; std::unique_ptr m_extendedModelManagerSupport; ModelManagerSupport *m_activeModelManagerSupport = &m_builtinModelManagerSupport; // Indexing CppIndexingSupport *m_internalIndexingSupport; bool m_indexerEnabled; QMutex m_fallbackProjectPartMutex; ProjectPart::ConstPtr m_fallbackProjectPart; CppFindReferences *m_findReferences; SymbolFinder m_symbolFinder; QThreadPool m_threadPool; bool m_enableGC; QTimer m_delayedGcTimer; QTimer m_fallbackProjectPartTimer; CppLocatorData m_locatorData; std::unique_ptr m_locatorFilter; std::unique_ptr m_classesFilter; std::unique_ptr m_includesFilter; std::unique_ptr m_functionsFilter; std::unique_ptr m_symbolsFindFilter; std::unique_ptr m_currentDocumentFilter; QList m_diagnosticMessages; static void recalculateProjectPartMappings(SyncedProjectData &ld); static void ensureUpdated(SyncedProjectData &ld); static Utils::FilePaths internalProjectFiles(SyncedProjectData &ld); static ProjectExplorer::HeaderPaths internalHeaderPaths(SyncedProjectData &ld); static ProjectExplorer::Macros internalDefinedMacros(SyncedProjectData &ld); }; static CppModelManagerPrivate *d; } // namespace Internal using namespace Internal; const char pp_configuration[] = "# 1 \"\"\n" "#define Q_CREATOR_RUN 1\n" "#define __cplusplus 1\n" "#define __extension__\n" "#define __context__\n" "#define __range__\n" "#define restrict\n" "#define __restrict\n" "#define __restrict__\n" "#define __complex__\n" "#define __imag__\n" "#define __real__\n" "#define __builtin_va_arg(a,b) ((b)0)\n" "#define _Pragma(x)\n" // C99 _Pragma operator "#define __func__ \"\"\n" // ### add macros for gcc "#define __PRETTY_FUNCTION__ \"\"\n" "#define __FUNCTION__ \"\"\n" // ### add macros for win32 "#define __cdecl\n" "#define __stdcall\n" "#define __thiscall\n" "#define QT_WA(x) x\n" "#define CALLBACK\n" "#define STDMETHODCALLTYPE\n" "#define __RPC_FAR\n" "#define __declspec(a)\n" "#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method\n" "#define __try try\n" "#define __except catch\n" "#define __finally\n" "#define __inline inline\n" "#define __forceinline inline\n" "#define __pragma(x)\n" "#define __w64\n" "#define __int64 long long\n" "#define __int32 long\n" "#define __int16 short\n" "#define __int8 char\n" "#define __ptr32\n" "#define __ptr64\n"; QSet CppModelManager::timeStampModifiedFiles(const QList &documentsToCheck) { QSet sourceFiles; for (const Document::Ptr &doc : documentsToCheck) { const QDateTime lastModified = doc->lastModified(); if (!lastModified.isNull()) { const FilePath filePath = doc->filePath(); if (filePath.exists() && filePath.lastModified() != lastModified) sourceFiles.insert(filePath); } } return sourceFiles; } /*! * \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() { return new CppSourceProcessor(snapshot(), [](const Document::Ptr &doc) { const Document::Ptr previousDocument = document(doc->filePath()); const unsigned newRevision = previousDocument.isNull() ? 1U : previousDocument->revision() + 1; doc->setRevision(newRevision); emitDocumentUpdated(doc); doc->releaseSourceAndAST(); }); } const FilePath &CppModelManager::editorConfigurationFileName() { static const FilePath config = FilePath::fromPathPart(u""); return config; } ModelManagerSupport *CppModelManager::modelManagerSupport(Backend backend) { return backend == Backend::Builtin ? &d->m_builtinModelManagerSupport : d->m_activeModelManagerSupport; } void CppModelManager::startLocalRenaming(const CursorInEditor &data, const ProjectPart *projectPart, RenameCallback &&renameSymbolsCallback, Backend backend) { modelManagerSupport(backend) ->startLocalRenaming(data, projectPart, std::move(renameSymbolsCallback)); } void CppModelManager::globalRename(const CursorInEditor &data, const QString &replacement, const std::function &callback, Backend backend) { modelManagerSupport(backend)->globalRename(data, replacement, callback); } void CppModelManager::findUsages(const CursorInEditor &data, Backend backend) { modelManagerSupport(backend)->findUsages(data); } void CppModelManager::switchHeaderSource(bool inNextSplit, Backend backend) { const IDocument *currentDocument = EditorManager::currentDocument(); QTC_ASSERT(currentDocument, return); modelManagerSupport(backend)->switchHeaderSource(currentDocument->filePath(), inNextSplit); } void CppModelManager::showPreprocessedFile(bool inNextSplit) { const IDocument *doc = EditorManager::currentDocument(); QTC_ASSERT(doc, return); static const auto showError = [](const QString &reason) { MessageManager::writeFlashing(Tr::tr("Cannot show preprocessed file: %1") .arg(reason)); }; static const auto showFallbackWarning = [](const QString &reason) { MessageManager::writeSilently(Tr::tr("Falling back to built-in preprocessor: %1") .arg(reason)); }; static const auto saveAndOpen = [](const FilePath &filePath, const QByteArray &contents, bool inNextSplit) { SaveFile f(filePath); if (!f.open()) { showError(Tr::tr("Failed to open output file \"%1\".").arg(filePath.toUserOutput())); return; } f.write(contents); if (!f.commit()) { showError(Tr::tr("Failed to write output file \"%1\".").arg(filePath.toUserOutput())); return; } f.close(); openEditor(filePath, inNextSplit, Core::Constants::K_DEFAULT_TEXT_EDITOR_ID); }; const FilePath &filePath = doc->filePath(); const QString outFileName = filePath.completeBaseName() + "_preprocessed." + filePath.suffix(); const auto outFilePath = FilePath::fromString( TemporaryDirectory::masterTemporaryDirectory()->filePath(outFileName)); const auto useBuiltinPreprocessor = [filePath, outFilePath, inNextSplit, contents = doc->contents()] { const Document::Ptr preprocessedDoc = snapshot() .preprocessedDocument(contents, filePath); QByteArray content = R"(/* Created using Qt Creator's built-in preprocessor. */ /* See Tools -> Debug Qt Creator -> Inspect C++ Code Model for the parameters used. * Adapt the respective setting in Edit -> Preferences -> C++ -> Code Model to invoke * the actual compiler instead. */ )"; saveAndOpen(outFilePath, content.append(preprocessedDoc->utf8Source()), inNextSplit); }; if (CppCodeModelSettings::settingsForFile(filePath).useBuiltinPreprocessor) { useBuiltinPreprocessor(); return; } const Project * const project = ProjectTree::currentProject(); if (!project || !project->activeTarget() || !project->activeTarget()->activeBuildConfiguration()) { showFallbackWarning(Tr::tr("Could not determine which compiler to invoke.")); useBuiltinPreprocessor(); return; } const Toolchain * tc = nullptr; const ProjectFile classifier(filePath, ProjectFile::classify(filePath.toString())); if (classifier.isC()) { tc = ToolchainKitAspect::cToolchain(project->activeTarget()->kit()); } else if (classifier.isCxx() || classifier.isHeader()) { tc = ToolchainKitAspect::cxxToolchain(project->activeTarget()->kit()); } else { showFallbackWarning(Tr::tr("Could not determine which compiler to invoke.")); useBuiltinPreprocessor(); return; } const bool isGcc = dynamic_cast(tc); const bool isMsvc = !isGcc && (tc->typeId() == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID || tc->typeId() == ProjectExplorer::Constants::CLANG_CL_TOOLCHAIN_TYPEID); if (!isGcc && !isMsvc) { showFallbackWarning(Tr::tr("Could not determine compiler command line.")); useBuiltinPreprocessor(); return; } const ProjectPart::ConstPtr projectPart = Utils::findOrDefault( CppModelManager::projectPart(filePath), [](const ProjectPart::ConstPtr &pp) { return pp->belongsToProject(ProjectTree::currentProject()); }); if (!projectPart) { showFallbackWarning(Tr::tr("Could not determine compiler command line.")); useBuiltinPreprocessor(); return; } CompilerOptionsBuilder optionsBuilder(*projectPart); optionsBuilder.setNativeMode(); optionsBuilder.setClStyle(isMsvc); optionsBuilder.build(classifier.kind, UsePrecompiledHeaders::No); QStringList compilerArgs = optionsBuilder.options(); if (isGcc) compilerArgs.append({"-E", "-o", outFilePath.toUserOutput()}); else compilerArgs.append("/E"); compilerArgs.append(filePath.toUserOutput()); const CommandLine compilerCommandLine(tc->compilerCommand(), compilerArgs); const auto compiler = new Process(instance()); compiler->setCommand(compilerCommandLine); compiler->setEnvironment(project->activeTarget()->activeBuildConfiguration()->environment()); connect(compiler, &Process::done, instance(), [compiler, outFilePath, inNextSplit, useBuiltinPreprocessor, isMsvc] { compiler->deleteLater(); if (compiler->result() != ProcessResult::FinishedWithSuccess) { showFallbackWarning("Compiler failed to run"); useBuiltinPreprocessor(); return; } if (isMsvc) saveAndOpen(outFilePath, compiler->rawStdOut(), inNextSplit); else openEditor(outFilePath, inNextSplit, Core::Constants::K_DEFAULT_TEXT_EDITOR_ID); }); compiler->start(); } static void foldOrUnfoldComments(bool unfold) { IEditor * const currentEditor = EditorManager::currentEditor(); if (!currentEditor) return; const auto editorWidget = qobject_cast(currentEditor->widget()); if (!editorWidget) return; TextEditor::TextDocument * const textDoc = editorWidget->textDocument(); QTC_ASSERT(textDoc, return); const Document::Ptr cppDoc = CppModelManager::snapshot().preprocessedDocument( textDoc->contents(), textDoc->filePath()); QTC_ASSERT(cppDoc, return); cppDoc->tokenize(); TranslationUnit * const tu = cppDoc->translationUnit(); if (!tu || !tu->isTokenized()) return; for (int commentTokIndex = 0; commentTokIndex < tu->commentCount(); ++commentTokIndex) { const Token &tok = tu->commentAt(commentTokIndex); if (tok.kind() != T_COMMENT && tok.kind() != T_DOXY_COMMENT) continue; const int tokenPos = tu->getTokenPositionInDocument(tok, textDoc->document()); const int tokenEndPos = tu->getTokenEndPositionInDocument(tok, textDoc->document()); const QTextBlock tokenBlock = textDoc->document()->findBlock(tokenPos); if (!tokenBlock.isValid()) continue; const QTextBlock nextBlock = tokenBlock.next(); if (!nextBlock.isValid()) continue; if (tokenEndPos < nextBlock.position()) continue; if (TextEditor::TextDocumentLayout::foldingIndent(tokenBlock) >= TextEditor::TextDocumentLayout::foldingIndent(nextBlock)) { continue; } if (unfold) editorWidget->unfold(tokenBlock); else editorWidget->fold(tokenBlock); } } void CppModelManager::foldComments() { foldOrUnfoldComments(false); } void CppModelManager::unfoldComments() { foldOrUnfoldComments(true); } class FindUnusedActionsEnabledSwitcher { public: FindUnusedActionsEnabledSwitcher() : actions{ActionManager::command("CppTools.FindUnusedFunctions"), ActionManager::command("CppTools.FindUnusedFunctionsInSubProject")} { for (Command * const action : actions) action->action()->setEnabled(false); } ~FindUnusedActionsEnabledSwitcher() { for (Command * const action : actions) action->action()->setEnabled(true); } private: const QList actions; }; using FindUnusedActionsEnabledSwitcherPtr = std::shared_ptr; static void checkNextFunctionForUnused( const QPointer &search, const std::shared_ptr> &findRefsFuture, const FindUnusedActionsEnabledSwitcherPtr &actionsSwitcher) { if (!search || findRefsFuture->isCanceled()) return; QVariantMap data = search->userData().toMap(); QVariant &remainingLinks = data["remaining"]; QVariantList remainingLinksList = remainingLinks.toList(); QVariant &activeLinks = data["active"]; QVariantList activeLinksList = activeLinks.toList(); if (remainingLinksList.isEmpty()) { if (activeLinksList.isEmpty()) { search->finishSearch(false); findRefsFuture->reportFinished(); } return; } const auto link = qvariant_cast(remainingLinksList.takeFirst()); activeLinksList << QVariant::fromValue(link); remainingLinks = remainingLinksList; activeLinks = activeLinksList; search->setUserData(data); CppModelManager::modelManagerSupport(CppModelManager::Backend::Best) ->checkUnused(link, search, [search, link, findRefsFuture, actionsSwitcher](const Link &) { if (!search || findRefsFuture->isCanceled()) return; const int newProgress = findRefsFuture->progressValue() + 1; findRefsFuture->setProgressValueAndText(newProgress, Tr::tr("Checked %1 of %n function(s)", nullptr, findRefsFuture->progressMaximum()) .arg(newProgress)); QVariantMap data = search->userData().toMap(); QVariant &activeLinks = data["active"]; QVariantList activeLinksList = activeLinks.toList(); QTC_CHECK(activeLinksList.removeOne(QVariant::fromValue(link))); activeLinks = activeLinksList; search->setUserData(data); checkNextFunctionForUnused(search, findRefsFuture, actionsSwitcher); }); } void CppModelManager::findUnusedFunctions(const FilePath &folder) { const auto actionsSwitcher = std::make_shared(); // Step 1: Employ locator to find all functions LocatorMatcher *matcher = new LocatorMatcher; matcher->setTasks(LocatorMatcher::matchers(MatcherType::Functions)); const QPointer search = SearchResultWindow::instance()->startNewSearch(Tr::tr("Find Unused Functions"), {}, {}, SearchResultWindow::SearchOnly, SearchResultWindow::PreserveCaseDisabled, "CppEditor"); matcher->setParent(search); connect(search, &SearchResult::activated, [](const SearchResultItem &item) { EditorManager::openEditorAtSearchResult(item); }); SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus); connect(search, &SearchResult::canceled, matcher, [matcher] { delete matcher; }); connect(matcher, &LocatorMatcher::done, search, [matcher, search, folder, actionsSwitcher](bool success) { matcher->deleteLater(); if (!success) { search->finishSearch(true); return; } Links links; const LocatorFilterEntries entries = matcher->outputData(); for (const LocatorFilterEntry &entry : entries) { static const QStringList prefixBlacklist{"main(", "~", "qHash(", "begin()", "end()", "cbegin()", "cend()", "constBegin()", "constEnd()"}; if (Utils::anyOf(prefixBlacklist, [&entry](const QString &prefix) { return entry.displayName.startsWith(prefix); })) { continue; } if (!entry.linkForEditor) continue; const Link link = *entry.linkForEditor; if (link.hasValidTarget() && link.targetFilePath.isReadableFile() && (folder.isEmpty() || link.targetFilePath.isChildOf(folder)) && ProjectManager::projectForFile(link.targetFilePath)) { links << link; } } if (links.isEmpty()) { search->finishSearch(false); return; } QVariantMap remainingAndActiveLinks; remainingAndActiveLinks.insert("active", QVariantList()); remainingAndActiveLinks.insert("remaining", Utils::transform(links, [](const Link &l) { return QVariant::fromValue(l); })); search->setUserData(remainingAndActiveLinks); const auto findRefsFuture = std::make_shared>(); FutureProgress *const progress = ProgressManager::addTask(findRefsFuture->future(), Tr::tr("Finding Unused Functions"), "CppEditor.FindUnusedFunctions"); connect(progress, &FutureProgress::canceled, search, [search, future = std::weak_ptr>(findRefsFuture)] { search->finishSearch(true); if (const auto f = future.lock()) { f->cancel(); f->reportFinished(); } }); findRefsFuture->setProgressRange(0, links.size()); connect(search, &SearchResult::canceled, [findRefsFuture] { findRefsFuture->cancel(); findRefsFuture->reportFinished(); }); // Step 2: Forward search results one by one to backend to check which functions are unused. // We keep several requests in flight for decent throughput. const int inFlightCount = std::min(QThread::idealThreadCount() / 2 + 1, int(links.size())); for (int i = 0; i < inFlightCount; ++i) checkNextFunctionForUnused(search, findRefsFuture, actionsSwitcher); }); matcher->start(); } void CppModelManager::checkForUnusedSymbol(SearchResult *search, const Link &link, CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context, const LinkHandler &callback) { d->m_findReferences->checkUnused(search, link, symbol, context, callback); } int argumentPositionOf(const AST *last, const CallAST *callAst) { if (!callAst || !callAst->expression_list) return false; int num = 0; for (ExpressionListAST *it = callAst->expression_list; it; it = it->next) { ++num; const ExpressionAST *const arg = it->value; if (arg->firstToken() <= last->firstToken() && arg->lastToken() >= last->lastToken()) { return num; } } return 0; } SignalSlotType CppModelManager::getSignalSlotType(const FilePath &filePath, const QByteArray &content, int position) { if (content.isEmpty()) return SignalSlotType::None; // Insert a dummy prefix if we don't have a real one. Otherwise the AST path will not contain // anything after the CallAST. QByteArray fixedContent = content; if (position > 2 && content.mid(position - 2, 2) == "::") fixedContent.insert(position, 'x'); const Snapshot snapshot = CppModelManager::snapshot(); const Document::Ptr document = snapshot.preprocessedDocument(fixedContent, filePath); document->check(); QTextDocument textDocument(QString::fromUtf8(fixedContent)); QTextCursor cursor(&textDocument); cursor.setPosition(position); const QList path = ASTPath(document)(cursor); if (path.isEmpty()) return SignalSlotType::None; const CallAST *callAst = nullptr; for (auto it = path.crbegin(); it != path.crend(); ++it) { if ((callAst = (*it)->asCall())) break; } if (!callAst || !callAst->base_expression) return SignalSlotType::None; const int argumentPosition = argumentPositionOf(path.last(), callAst); if (argumentPosition != 2 && argumentPosition != 4) return SignalSlotType::None; const NameAST *nameAst = nullptr; if (const IdExpressionAST * const idAst = callAst->base_expression->asIdExpression()) nameAst = idAst->name; else if (const MemberAccessAST * const ast = callAst->base_expression->asMemberAccess()) nameAst = ast->member_name; if (!nameAst || !nameAst->name) return SignalSlotType::None; const Identifier * const id = nameAst->name->identifier(); if (!id) return SignalSlotType::None; const QString funcName = QString::fromUtf8(id->chars(), id->size()); if (funcName != "connect" && funcName != "disconnect") return SignalSlotType::None; Scope *scope = document->globalNamespace(); for (auto it = path.crbegin(); it != path.crend(); ++it) { if (const CompoundStatementAST * const stmtAst = (*it)->asCompoundStatement()) { scope = stmtAst->symbol; break; } } const LookupContext context(document, snapshot); if (const MemberAccessAST * const ast = callAst->base_expression->asMemberAccess()) { TypeOfExpression exprType; exprType.setExpandTemplates(true); exprType.init(document, snapshot); const QList typeMatches = exprType(ast->base_expression, document, scope); if (typeMatches.isEmpty()) return SignalSlotType::None; const std::function getNamedType = [&getNamedType](const FullySpecifiedType &type ) -> const NamedType * { Type * const t = type.type(); if (const auto namedType = t->asNamedType()) return namedType; if (const auto pointerType = t->asPointerType()) return getNamedType(pointerType->elementType()); if (const auto refType = t->asReferenceType()) return getNamedType(refType->elementType()); return nullptr; }; const NamedType *namedType = getNamedType(typeMatches.first().type()); if (!namedType && typeMatches.first().declaration()) namedType = getNamedType(typeMatches.first().declaration()->type()); if (!namedType) return SignalSlotType::None; const ClassOrNamespace * const result = context.lookupType(namedType->name(), scope); if (!result) return SignalSlotType::None; scope = result->rootClass(); if (!scope) return SignalSlotType::None; } // Is the function a member function of QObject? const QList matches = context.lookup(nameAst->name, scope); for (const LookupItem &match : matches) { if (!match.scope()) continue; const Class *klass = match.scope()->asClass(); if (!klass || !klass->name()) continue; const Identifier * const classId = klass->name()->identifier(); if (classId && QString::fromUtf8(classId->chars(), classId->size()) == "QObject") { QString expression; LanguageFeatures features = LanguageFeatures::defaultFeatures(); CPlusPlus::ExpressionUnderCursor expressionUnderCursor(features); for (int i = cursor.position(); i > 0; --i) if (textDocument.characterAt(i) == '(') { cursor.setPosition(i); break; } expression = expressionUnderCursor(cursor); if (expression.endsWith(QLatin1String("SIGNAL")) || (expression.endsWith(QLatin1String("SLOT")) && argumentPosition == 4)) return SignalSlotType::OldStyleSignal; if (argumentPosition == 2) return SignalSlotType::NewStyleSignal; } } return SignalSlotType::None; } FollowSymbolUnderCursor &CppModelManager::builtinFollowSymbol() { return d->m_builtinModelManagerSupport.followSymbolInterface(); } template static void setFilter(std::unique_ptr &filter, std::unique_ptr &&newFilter) { QTC_ASSERT(newFilter, return;); filter = std::move(newFilter); } void CppModelManager::setLocatorFilter(std::unique_ptr &&filter) { setFilter(d->m_locatorFilter, std::move(filter)); } void CppModelManager::setClassesFilter(std::unique_ptr &&filter) { setFilter(d->m_classesFilter, std::move(filter)); } void CppModelManager::setIncludesFilter(std::unique_ptr &&filter) { setFilter(d->m_includesFilter, std::move(filter)); } void CppModelManager::setFunctionsFilter(std::unique_ptr &&filter) { setFilter(d->m_functionsFilter, std::move(filter)); } void CppModelManager::setSymbolsFindFilter(std::unique_ptr &&filter) { setFilter(d->m_symbolsFindFilter, std::move(filter)); } void CppModelManager::setCurrentDocumentFilter(std::unique_ptr &&filter) { setFilter(d->m_currentDocumentFilter, std::move(filter)); } ILocatorFilter *CppModelManager::locatorFilter() { return d->m_locatorFilter.get(); } ILocatorFilter *CppModelManager::classesFilter() { return d->m_classesFilter.get(); } ILocatorFilter *CppModelManager::includesFilter() { return d->m_includesFilter.get(); } ILocatorFilter *CppModelManager::functionsFilter() { return d->m_functionsFilter.get(); } IFindFilter *CppModelManager::symbolsFindFilter() { return d->m_symbolsFindFilter.get(); } ILocatorFilter *CppModelManager::currentDocumentFilter() { return d->m_currentDocumentFilter.get(); } const FilePath &CppModelManager::configurationFileName() { return Preprocessor::configurationFileName(); } void CppModelManager::updateModifiedSourceFiles() { const Snapshot snapshot = CppModelManager::snapshot(); QList documentsToCheck; for (const Document::Ptr &document : snapshot) documentsToCheck << document; updateSourceFiles(timeStampModifiedFiles(documentsToCheck)); } /*! \class CppEditor::CppModelManager \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. 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 */ CppModelManager *CppModelManager::instance() { QTC_ASSERT(m_instance, return nullptr;); return m_instance; } void CppModelManager::registerJsExtension() { JsExpander::registerGlobalObject("Cpp", [] { return new CppToolsJsExtension(&d->m_locatorData); }); } void CppModelManager::initCppTools() { // Objects connect(VcsManager::instance(), &VcsManager::repositoryChanged, m_instance, &CppModelManager::updateModifiedSourceFiles); connect(DocumentManager::instance(), &DocumentManager::filesChangedInternally, m_instance, [](const FilePaths &filePaths) { updateSourceFiles(toSet(filePaths)); }); connect(m_instance, &CppModelManager::documentUpdated, &d->m_locatorData, &CppLocatorData::onDocumentUpdated); connect(m_instance, &CppModelManager::aboutToRemoveFiles, &d->m_locatorData, &CppLocatorData::onAboutToRemoveFiles); // Set up builtin filters setLocatorFilter(std::make_unique()); setClassesFilter(std::make_unique()); setIncludesFilter(std::make_unique()); setFunctionsFilter(std::make_unique()); setSymbolsFindFilter(std::make_unique()); setCurrentDocumentFilter(std::make_unique()); // Setup matchers LocatorMatcher::addMatcherCreator(MatcherType::AllSymbols, [] { return cppMatchers(MatcherType::AllSymbols); }); LocatorMatcher::addMatcherCreator(MatcherType::Classes, [] { return cppMatchers(MatcherType::Classes); }); LocatorMatcher::addMatcherCreator(MatcherType::Functions, [] { return cppMatchers(MatcherType::Functions); }); LocatorMatcher::addMatcherCreator(MatcherType::CurrentDocumentSymbols, [] { return cppMatchers(MatcherType::CurrentDocumentSymbols); }); } CppModelManager::CppModelManager() { d = new CppModelManagerPrivate; m_instance = this; CppModelManagerBase::registerSetExtraDiagnosticsCallback(&CppModelManager::setExtraDiagnostics); CppModelManagerBase::registerSnapshotCallback(&CppModelManager::snapshot); // Used for weak dependency in VcsBaseSubmitEditor setObjectName("CppModelManager"); ExtensionSystem::PluginManager::addObject(this); d->m_enableGC = true; // Visual C++ has 1MiB, macOSX has 512KiB if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost()) d->m_threadPool.setStackSize(2 * 1024 * 1024); qRegisterMetaType >(); connect(this, &CppModelManager::sourceFilesRefreshed, this, &CppModelManager::onSourceFilesRefreshed); d->m_findReferences = new CppFindReferences(this); d->m_indexerEnabled = qtcEnvironmentVariable("QTC_NO_CODE_INDEXER") != "1"; d->m_delayedGcTimer.setObjectName(QLatin1String("CppModelManager::m_delayedGcTimer")); d->m_delayedGcTimer.setSingleShot(true); connect(&d->m_delayedGcTimer, &QTimer::timeout, this, &CppModelManager::GC); auto projectManager = ProjectManager::instance(); connect(projectManager, &ProjectManager::projectAdded, this, &CppModelManager::onProjectAdded); connect(projectManager, &ProjectManager::aboutToRemoveProject, this, &CppModelManager::onAboutToRemoveProject); connect(SessionManager::instance(), &SessionManager::aboutToLoadSession, this, &CppModelManager::onAboutToLoadSession); connect(projectManager, &ProjectManager::startupProjectChanged, this, &CppModelManager::onActiveProjectChanged); connect(EditorManager::instance(), &EditorManager::currentEditorChanged, this, &CppModelManager::onCurrentEditorChanged); connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::filesRenamed, this, &CppModelManager::renameIncludes); connect(ICore::instance(), &ICore::coreAboutToClose, this, &CppModelManager::onCoreAboutToClose); d->m_fallbackProjectPartTimer.setSingleShot(true); d->m_fallbackProjectPartTimer.setInterval(5000); connect(&d->m_fallbackProjectPartTimer, &QTimer::timeout, this, &CppModelManager::setupFallbackProjectPart); connect(KitManager::instance(), &KitManager::kitsChanged, &d->m_fallbackProjectPartTimer, qOverload<>(&QTimer::start)); connect(this, &CppModelManager::projectPartsRemoved, &d->m_fallbackProjectPartTimer, qOverload<>(&QTimer::start)); connect(this, &CppModelManager::projectPartsUpdated, &d->m_fallbackProjectPartTimer, qOverload<>(&QTimer::start)); setupFallbackProjectPart(); qRegisterMetaType("CPlusPlus::Document::Ptr"); qRegisterMetaType>( "QList"); d->m_internalIndexingSupport = new CppIndexingSupport; initCppTools(); } CppModelManager::~CppModelManager() { ExtensionSystem::PluginManager::removeObject(this); delete d->m_internalIndexingSupport; delete d; } Snapshot CppModelManager::snapshot() { QMutexLocker locker(&d->m_snapshotMutex); return d->m_snapshot; } Document::Ptr CppModelManager::document(const FilePath &filePath) { QMutexLocker locker(&d->m_snapshotMutex); return d->m_snapshot.document(filePath); } /// Replace the document in the snapshot. /// /// Returns true if successful, false if the new document is out-dated. bool CppModelManager::replaceDocument(Document::Ptr newDoc) { QMutexLocker locker(&d->m_snapshotMutex); Document::Ptr previous = d->m_snapshot.document(newDoc->filePath()); if (previous && (newDoc->revision() != 0 && newDoc->revision() < previous->revision())) // the new document is outdated return false; d->m_snapshot.insert(newDoc); return true; } void CppModelManagerPrivate::ensureUpdated(SyncedProjectData &ld) { if (!ld.m_dirty) return; ld.m_projectFiles = internalProjectFiles(ld); ld.m_headerPaths = internalHeaderPaths(ld); ld.m_definedMacros = internalDefinedMacros(ld); ld.m_dirty = false; } FilePaths CppModelManagerPrivate::internalProjectFiles(SyncedProjectData &ld) { FilePaths files; for (const ProjectData &projectData : std::as_const(ld.m_projectData)) { for (const ProjectPart::ConstPtr &part : projectData.projectInfo->projectParts()) { for (const ProjectFile &file : part->files) files += file.path; } } FilePath::removeDuplicates(files); return files; } HeaderPaths CppModelManagerPrivate::internalHeaderPaths(SyncedProjectData &ld) { HeaderPaths headerPaths; for (const ProjectData &projectData : std::as_const(ld.m_projectData)) { for (const ProjectPart::ConstPtr &part : projectData.projectInfo->projectParts()) { for (const HeaderPath &path : part->headerPaths) { HeaderPath hp(QDir::cleanPath(path.path), path.type); if (!headerPaths.contains(hp)) headerPaths.push_back(std::move(hp)); } } } return headerPaths; } static void addUnique(const Macros &newMacros, Macros ¯os, QSet &alreadyIn) { for (const ProjectExplorer::Macro ¯o : newMacros) { if (Utils::insert(alreadyIn, macro)) macros += macro; } } Macros CppModelManagerPrivate::internalDefinedMacros(SyncedProjectData &ld) { Macros macros; QSet alreadyIn; for (const ProjectData &projectData : std::as_const(ld.m_projectData)) { for (const ProjectPart::ConstPtr &part : projectData.projectInfo->projectParts()) { addUnique(part->toolchainMacros, macros, alreadyIn); addUnique(part->projectMacros, macros, alreadyIn); } } return macros; } /// This function will acquire mutexes! void CppModelManager::dumpModelManagerConfiguration(const QString &logFileId) { const Snapshot globalSnapshot = snapshot(); const QString globalSnapshotTitle = QString::fromLatin1("Global/Indexing Snapshot (%1 Documents)").arg(globalSnapshot.size()); CppCodeModelInspector::Dumper dumper(globalSnapshot, logFileId); dumper.dumpProjectInfos(projectInfos()); dumper.dumpSnapshot(globalSnapshot, globalSnapshotTitle, /*isGlobalSnapshot=*/ true); dumper.dumpWorkingCopy(workingCopy()); dumper.dumpMergedEntities(headerPaths(), ProjectExplorer::Macro::toByteArray(definedMacros())); } QSet CppModelManager::abstractEditorSupports() { return d->m_extraEditorSupports; } void CppModelManager::addExtraEditorSupport(AbstractEditorSupport *editorSupport) { d->m_extraEditorSupports.insert(editorSupport); } void CppModelManager::removeExtraEditorSupport(AbstractEditorSupport *editorSupport) { d->m_extraEditorSupports.remove(editorSupport); } CppEditorDocumentHandle *CppModelManager::cppEditorDocument(const FilePath &filePath) { if (filePath.isEmpty()) return nullptr; QMutexLocker locker(&d->m_cppEditorDocumentsMutex); return d->m_cppEditorDocuments.value(filePath.toString(), 0); } BaseEditorDocumentProcessor *CppModelManager::cppEditorDocumentProcessor(const FilePath &filePath) { const auto document = cppEditorDocument(filePath); return document ? document->processor() : nullptr; } void CppModelManager::registerCppEditorDocument(CppEditorDocumentHandle *editorDocument) { QTC_ASSERT(editorDocument, return); const FilePath filePath = editorDocument->filePath(); QTC_ASSERT(!filePath.isEmpty(), return); QMutexLocker locker(&d->m_cppEditorDocumentsMutex); QTC_ASSERT(d->m_cppEditorDocuments.value(filePath.toString(), 0) == 0, return); d->m_cppEditorDocuments.insert(filePath.toString(), editorDocument); } void CppModelManager::unregisterCppEditorDocument(const QString &filePath) { QTC_ASSERT(!filePath.isEmpty(), return); static short closedCppDocuments = 0; int openCppDocuments = 0; { 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(); } ++closedCppDocuments; if (openCppDocuments == 0 || closedCppDocuments == 5) { closedCppDocuments = 0; delayedGC(); } } QList CppModelManager::references(Symbol *symbol, const LookupContext &context) { return d->m_findReferences->references(symbol, context); } void CppModelManager::findUsages(Symbol *symbol, const LookupContext &context) { if (symbol->identifier()) d->m_findReferences->findUsages(symbol, context); } void CppModelManager::renameUsages(Symbol *symbol, const LookupContext &context, const QString &replacement, const std::function &callback) { if (symbol->identifier()) d->m_findReferences->renameUsages(symbol, context, replacement, callback); } void CppModelManager::renameUsages(const Document::Ptr &doc, const QTextCursor &cursor, const Snapshot &snapshot, const QString &replacement, const std::function &callback) { Internal::CanonicalSymbol cs(doc, snapshot); CPlusPlus::Symbol *canonicalSymbol = cs(cursor); if (canonicalSymbol) renameUsages(canonicalSymbol, cs.context(), replacement, callback); } void CppModelManager::findMacroUsages(const CPlusPlus::Macro ¯o) { d->m_findReferences->findMacroUses(macro); } void CppModelManager::renameMacroUsages(const CPlusPlus::Macro ¯o, const QString &replacement) { d->m_findReferences->renameMacroUses(macro, replacement); } void CppModelManager::replaceSnapshot(const Snapshot &newSnapshot) { QMutexLocker snapshotLocker(&d->m_snapshotMutex); d->m_snapshot = newSnapshot; } WorkingCopy CppModelManager::buildWorkingCopyList() { WorkingCopy workingCopy; const QList cppEditorDocumentList = cppEditorDocuments(); for (const CppEditorDocumentHandle *cppEditorDocument : cppEditorDocumentList) { workingCopy.insert(cppEditorDocument->filePath(), cppEditorDocument->contents(), cppEditorDocument->revision()); } for (AbstractEditorSupport *es : std::as_const(d->m_extraEditorSupports)) workingCopy.insert(es->filePath(), es->contents(), es->revision()); // Add the project configuration file QByteArray conf = codeModelConfiguration(); conf += ProjectExplorer::Macro::toByteArray(definedMacros()); workingCopy.insert(configurationFileName(), conf); return workingCopy; } WorkingCopy CppModelManager::workingCopy() { return buildWorkingCopyList(); } QByteArray CppModelManager::codeModelConfiguration() { return QByteArray::fromRawData(pp_configuration, qstrlen(pp_configuration)); } CppLocatorData *CppModelManager::locatorData() { return &d->m_locatorData; } static QSet filteredFilesRemoved(const QSet &files, const CppCodeModelSettings &settings) { if (!settings.enableIndexing) return {}; const int fileSizeLimitInMb = settings.effectiveIndexerFileSizeLimitInMb(); if (fileSizeLimitInMb <= 0 && !settings.ignoreFiles) return files; QSet result; QList regexes; const QStringList wildcards = settings.ignorePattern.split('\n'); for (const QString &wildcard : wildcards) regexes.append(QRegularExpression::fromWildcard(wildcard, Qt::CaseInsensitive, QRegularExpression::UnanchoredWildcardConversion)); for (const QString &file : files) { const FilePath filePath = FilePath::fromString(file); if (fileSizeLimitInMb > 0 && fileSizeExceedsLimit(filePath, fileSizeLimitInMb)) continue; bool skip = false; if (settings.ignoreFiles) { for (const QRegularExpression &rx: std::as_const(regexes)) { QRegularExpressionMatch match = rx.match(filePath.absoluteFilePath().path()); if (match.hasMatch()) { const QString msg = Tr::tr("C++ Indexer: Skipping file \"%1\" " "because its path matches the ignore pattern.") .arg(filePath.displayName()); QMetaObject::invokeMethod(MessageManager::instance(), [msg] { MessageManager::writeSilently(msg); }); skip = true; break; } } } if (!skip) result << filePath.toString(); } return result; } QFuture CppModelManager::updateSourceFiles(const QSet &sourceFiles, ProgressNotificationMode mode) { if (sourceFiles.isEmpty() || !d->m_indexerEnabled) return QFuture(); QHash> sourcesPerProject; // TODO: Work with QList from here on? for (const FilePath &fp : sourceFiles) sourcesPerProject[ProjectManager::projectForFile(fp)] << fp.toString(); QSet filteredFiles; for (auto it = sourcesPerProject.cbegin(); it != sourcesPerProject.cend(); ++it) { filteredFiles.unite( filteredFilesRemoved(it.value(), CppCodeModelSettings::settingsForProject(it.key()))); } return d->m_internalIndexingSupport->refreshSourceFiles(filteredFiles, mode); } ProjectInfoList CppModelManager::projectInfos() { return Utils::transform>(d->m_lockedProjectData.readLocked() ->m_projectData, [](const ProjectData &d) { return d.projectInfo; }); } ProjectInfo::ConstPtr CppModelManager::projectInfo(Project *project) { return d->m_lockedProjectData.get( [project](const CppModelManagerPrivate::SyncedProjectData &ld) { return ld.m_projectData.value(project).projectInfo; }); } /// \brief Remove all files and their includes (recursively) of given ProjectInfo from the snapshot. void CppModelManager::removeProjectInfoFilesAndIncludesFromSnapshot(const ProjectInfo &projectInfo) { QMutexLocker snapshotLocker(&d->m_snapshotMutex); QStringList removedFiles; for (const ProjectPart::ConstPtr &projectPart : projectInfo.projectParts()) { for (const ProjectFile &cxxFile : std::as_const(projectPart->files)) { const QSet filePaths = d->m_snapshot.allIncludesForDocument(cxxFile.path); for (const FilePath &filePath : filePaths) { d->m_snapshot.remove(filePath); removedFiles << filePath.toString(); } d->m_snapshot.remove(cxxFile.path); removedFiles << cxxFile.path.toString(); } } emit m_instance->aboutToRemoveFiles(removedFiles); } const QList CppModelManager::cppEditorDocuments() { QMutexLocker locker(&d->m_cppEditorDocumentsMutex); return d->m_cppEditorDocuments.values(); } /// \brief Remove all given files from the snapshot. void CppModelManager::removeFilesFromSnapshot(const QSet &filesToRemove) { QMutexLocker snapshotLocker(&d->m_snapshotMutex); for (const FilePath &file : filesToRemove) d->m_snapshot.remove(file); } class ProjectInfoComparer { public: ProjectInfoComparer(const ProjectInfo &oldProjectInfo, const ProjectInfo &newProjectInfo) : m_old(oldProjectInfo) , m_oldSourceFiles(oldProjectInfo.sourceFiles()) , m_new(newProjectInfo) , m_newSourceFiles(newProjectInfo.sourceFiles()) {} 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); } QSet addedFiles() const { QSet addedFilesSet = m_newSourceFiles; addedFilesSet.subtract(m_oldSourceFiles); return addedFilesSet; } QSet removedFiles() const { QSet removedFilesSet = m_oldSourceFiles; removedFilesSet.subtract(m_newSourceFiles); return removedFilesSet; } QStringList removedProjectParts() { QSet removed = projectPartIds(m_old.projectParts()); removed.subtract(projectPartIds(m_new.projectParts())); return Utils::toList(removed); } /// Returns a list of common files that have a changed timestamp. QSet timeStampModifiedFiles(const Snapshot &snapshot) const { QSet commonSourceFiles = m_newSourceFiles; commonSourceFiles.intersect(m_oldSourceFiles); QList documentsToCheck; for (const FilePath &file : commonSourceFiles) { if (Document::Ptr document = snapshot.document(file)) documentsToCheck << document; } return CppModelManager::timeStampModifiedFiles(documentsToCheck); } private: static QSet projectPartIds(const QVector &projectParts) { QSet ids; for (const ProjectPart::ConstPtr &projectPart : projectParts) ids.insert(projectPart->id()); return ids; } private: const ProjectInfo &m_old; const QSet m_oldSourceFiles; const ProjectInfo &m_new; const QSet m_newSourceFiles; }; void CppModelManagerPrivate::recalculateProjectPartMappings(SyncedProjectData &ld) { ld.m_projectPartIdToProjectProjectPart.clear(); ld.m_fileToProjectParts.clear(); for (const ProjectData &projectData : std::as_const(ld.m_projectData)) { for (const ProjectPart::ConstPtr &projectPart : projectData.projectInfo->projectParts()) { ld.m_projectPartIdToProjectProjectPart[projectPart->id()] = projectPart; for (const ProjectFile &cxxFile : projectPart->files) { ld.m_fileToProjectParts[cxxFile.path].append(projectPart); if (FilePath canonical = cxxFile.path.canonicalPath(); canonical != cxxFile.path) ld.m_fileToProjectParts[canonical].append(projectPart); } } } d->m_symbolFinder.clearCache(); } void CppModelManagerPrivate::setupWatcher(const QFuture &future, Project *project, ProjectData *projectData, CppModelManager *q) { projectData->indexer = new QFutureWatcher(q); const auto handleFinished = [project, watcher = projectData->indexer, q] { d->m_lockedProjectData.write([watcher, project](auto &ld) { const auto it = ld.m_projectData.find(project); if (it != ld.m_projectData.end() && it->indexer == watcher) { it->indexer = nullptr; it->fullyIndexed = !watcher->isCanceled(); } }); watcher->disconnect(q); watcher->deleteLater(); }; q->connect(projectData->indexer, &QFutureWatcher::canceled, q, handleFinished); q->connect(projectData->indexer, &QFutureWatcher::finished, q, handleFinished); projectData->indexer->setFuture(future); } void CppModelManager::updateCppEditorDocuments(bool projectsUpdated) { // Refresh visible documents QSet visibleCppEditorDocuments; const QList editors = EditorManager::visibleEditors(); for (IEditor *editor: editors) { if (IDocument *document = editor->document()) { const FilePath filePath = document->filePath(); if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) { visibleCppEditorDocuments.insert(document); theCppEditorDocument->processor()->run(projectsUpdated); } } } // Mark invisible documents dirty QSet invisibleCppEditorDocuments = Utils::toSet(DocumentModel::openedDocuments()); invisibleCppEditorDocuments.subtract(visibleCppEditorDocuments); for (IDocument *document : std::as_const(invisibleCppEditorDocuments)) { const FilePath filePath = document->filePath(); if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) { const CppEditorDocumentHandle::RefreshReason refreshReason = projectsUpdated ? CppEditorDocumentHandle::ProjectUpdate : CppEditorDocumentHandle::Other; theCppEditorDocument->setRefreshReason(refreshReason); } } } QFuture CppModelManager::updateProjectInfo(const ProjectInfo::ConstPtr &newProjectInfo, const QSet &additionalFiles) { if (!newProjectInfo) return {}; QSet filesToReindex; QStringList removedProjectParts; bool filesRemoved = false; Project * const project = projectForProjectInfo(*newProjectInfo); if (!project) return {}; ProjectData *projectData = nullptr; d->m_lockedProjectData.write([&newProjectInfo, project, &filesToReindex, &removedProjectParts, &filesRemoved, &projectData](auto &ld) { const QSet newSourceFiles = newProjectInfo->sourceFiles(); // Check if we can avoid a full reindexing const auto it = ld.m_projectData.find(project); if (it != ld.m_projectData.end() && it->projectInfo && it->fullyIndexed) { ProjectInfoComparer comparer(*it->projectInfo, *newProjectInfo); if (comparer.configurationOrFilesChanged()) { ld.m_dirty = true; // If the project configuration changed, do a full reindexing if (comparer.configurationChanged()) { removeProjectInfoFilesAndIncludesFromSnapshot(*it->projectInfo); 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 addedFiles = comparer.addedFiles(); filesToReindex.unite(addedFiles); const QSet modifiedFiles = comparer.timeStampModifiedFiles(snapshot()); filesToReindex.unite(modifiedFiles); } // Announce and purge the removed files from the snapshot const QSet removedFiles = comparer.removedFiles(); if (!removedFiles.isEmpty()) { filesRemoved = true; emit m_instance->aboutToRemoveFiles(transform(removedFiles, &FilePath::toString)); removeFilesFromSnapshot(removedFiles); } } removedProjectParts = comparer.removedProjectParts(); // A new project was opened/created, do a full indexing } else { ld.m_dirty = true; filesToReindex.unite(newSourceFiles); } // Update Project/ProjectInfo and File/ProjectPart table if (it != ld.m_projectData.end()) { if (it->indexer) it->indexer->cancel(); it->projectInfo = newProjectInfo; it->fullyIndexed = false; } projectData = it == ld.m_projectData.end() ? &( ld.m_projectData[project] = ProjectData{newProjectInfo, nullptr, false}) : &(*it); CppModelManagerPrivate::recalculateProjectPartMappings(ld); }); // If requested, dump everything we got if (DumpProjectInfo) dumpModelManagerConfiguration(QLatin1String("updateProjectInfo")); // Remove files from snapshot that are not reachable any more if (filesRemoved) GC(); // Announce removed project parts if (!removedProjectParts.isEmpty()) emit m_instance->projectPartsRemoved(removedProjectParts); // Announce added project parts emit m_instance->projectPartsUpdated(project); // 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. updateCppEditorDocuments(/*projectsUpdated = */ true); filesToReindex.unite(additionalFiles); // Trigger reindexing const QFuture indexingFuture = updateSourceFiles(filesToReindex, ForcedProgressNotification); // It's safe to do this here, as only the UI thread writes to the map and no other thread // uses the indexer value. d->setupWatcher(indexingFuture, project, projectData, m_instance); return indexingFuture; } ProjectPart::ConstPtr CppModelManager::projectPartForId(const QString &projectPartId) { return d->m_lockedProjectData.get( [projectPartId](const CppModelManagerPrivate::SyncedProjectData &ld) { return ld.m_projectPartIdToProjectProjectPart.value(projectPartId); }); } QList CppModelManager::projectPart(const FilePath &fileName) { std::optional> result; d->m_lockedProjectData.read( [&fileName, &result](const CppModelManagerPrivate::SyncedProjectData &sd) { const auto it = sd.m_fileToProjectParts.constFind(fileName); if (it != sd.m_fileToProjectParts.constEnd()) result = *it; }); if (result) return *result; const FilePath canonicalPath = fileName.canonicalPath(); d->m_lockedProjectData.write( [&fileName, &canonicalPath, &result](CppModelManagerPrivate::SyncedProjectData &sd) { const auto it = sd.m_fileToProjectParts.constFind(canonicalPath); if (it == sd.m_fileToProjectParts.constEnd()) return; sd.m_fileToProjectParts.insert(fileName, it.value()); result = it.value(); }); return result.value_or(QList{}); } QList CppModelManager::projectPartFromDependencies( const FilePath &fileName) { QSet parts; const FilePaths deps = snapshot().filesDependingOn(fileName); for (const FilePath &dep : deps) parts.unite(Utils::toSet(projectPart(dep))); return parts.values(); } ProjectPart::ConstPtr CppModelManager::fallbackProjectPart() { QMutexLocker locker(&d->m_fallbackProjectPartMutex); return d->m_fallbackProjectPart; } bool CppModelManager::isCppEditor(IEditor *editor) { return editor->context().contains(ProjectExplorer::Constants::CXX_LANGUAGE_ID); } std::optional CppModelManager::usesClangd(const TextEditor::TextDocument *document) { return d->m_activeModelManagerSupport->usesClangd(document); } bool CppModelManager::isClangCodeModelActive() { return d->m_activeModelManagerSupport != &d->m_builtinModelManagerSupport; } void CppModelManager::emitDocumentUpdated(Document::Ptr doc) { if (replaceDocument(doc)) emit m_instance->documentUpdated(doc); } void CppModelManager::emitAbstractEditorSupportContentsUpdated(const QString &filePath, const QString &sourcePath, const QByteArray &contents) { emit m_instance->abstractEditorSupportContentsUpdated(filePath, sourcePath, contents); } void CppModelManager::emitAbstractEditorSupportRemoved(const QString &filePath) { emit m_instance->abstractEditorSupportRemoved(filePath); } void CppModelManager::onProjectAdded(Project *) { d->m_lockedProjectData.writeLocked()->m_dirty = true; } void CppModelManager::delayedGC() { if (d->m_enableGC) d->m_delayedGcTimer.start(500); } static QStringList removedProjectParts(const QStringList &before, const QStringList &after) { QSet b = Utils::toSet(before); b.subtract(Utils::toSet(after)); return Utils::toList(b); } void CppModelManager::onAboutToRemoveProject(Project *project) { QStringList idsOfRemovedProjectParts; d->m_lockedProjectData.write([project, &idsOfRemovedProjectParts]( CppModelManagerPrivate::SyncedProjectData &sd) { sd.m_dirty = true; const QStringList projectPartsIdsBefore = sd.m_projectPartIdToProjectProjectPart.keys(); sd.m_projectData.remove(project); CppModelManagerPrivate::recalculateProjectPartMappings(sd); const QStringList projectPartsIdsAfter = sd.m_projectPartIdToProjectProjectPart.keys(); idsOfRemovedProjectParts = removedProjectParts(projectPartsIdsBefore, projectPartsIdsAfter); }); if (!idsOfRemovedProjectParts.isEmpty()) emit m_instance->projectPartsRemoved(idsOfRemovedProjectParts); delayedGC(); } void CppModelManager::onActiveProjectChanged(Project *project) { if (!project) return; // Last project closed. if (!d->m_lockedProjectData.readLocked()->m_projectData.contains(project)) return; // Not yet known to us. updateCppEditorDocuments(); } void CppModelManager::onSourceFilesRefreshed() { if (CppIndexingSupport::isFindErrorsIndexingActive()) { QTimer::singleShot(1, QCoreApplication::instance(), &QCoreApplication::quit); qDebug("FindErrorsIndexing: Done, requesting Qt Creator to quit."); } } void CppModelManager::onCurrentEditorChanged(IEditor *editor) { if (!editor || !editor->document()) return; const FilePath filePath = editor->document()->filePath(); if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) { const CppEditorDocumentHandle::RefreshReason refreshReason = theCppEditorDocument->refreshReason(); if (refreshReason != CppEditorDocumentHandle::None) { const bool projectsChanged = refreshReason == CppEditorDocumentHandle::ProjectUpdate; theCppEditorDocument->setRefreshReason(CppEditorDocumentHandle::None); theCppEditorDocument->processor()->run(projectsChanged); } } } void CppModelManager::onAboutToLoadSession() { if (d->m_delayedGcTimer.isActive()) d->m_delayedGcTimer.stop(); GC(); } QSet CppModelManager::dependingInternalTargets(const FilePath &file) { QSet result; const Snapshot snapshot = CppModelManager::snapshot(); QTC_ASSERT(snapshot.contains(file), return result); bool wasHeader; const FilePath correspondingFile = correspondingHeaderOrSource(file, &wasHeader, CacheUsage::ReadOnly); const FilePaths dependingFiles = snapshot.filesDependingOn( wasHeader ? file : correspondingFile); for (const FilePath &fn : std::as_const(dependingFiles)) { for (const ProjectPart::ConstPtr &part : projectPart(fn)) result.insert(part->buildSystemTarget); } return result; } QSet CppModelManager::internalTargets(const FilePath &filePath) { QTC_ASSERT(m_instance, return {}); const QList projectParts = projectPart(filePath); // if we have no project parts it's most likely a header with declarations only and CMake based if (projectParts.isEmpty()) return dependingInternalTargets(filePath); QSet targets; for (const ProjectPart::ConstPtr &part : projectParts) { targets.insert(part->buildSystemTarget); if (part->buildTargetType != BuildTargetType::Executable) targets.unite(dependingInternalTargets(filePath)); } return targets; } // Note: This function assumes that neither the project tree nor the code model are aware of // the renamings yet, i.e. they still refer to the old file paths. void CppModelManager::renameIncludes(const QList> &oldAndNewPaths) { using IncludingFile = std::pair; // old and new path; might be identical struct RewriteCandidate { FilePath oldHeaderFilePath; FilePath newHeaderFilePath; QString oldHeaderFileName; QString newHeaderFileName; int includeLine; bool isUiHeader; }; QHash> rewriteCandidates; const Snapshot theSnapshot = snapshot(); for (const auto &[oldFilePath, newFilePath] : oldAndNewPaths) { if (oldFilePath.isEmpty() || newFilePath.isEmpty()) continue; QString oldFileName = oldFilePath.fileName(); QString newFileName = newFilePath.fileName(); const bool isUiFile = oldFilePath.suffix() == "ui" && newFilePath.suffix() == "ui"; const bool moved = oldFilePath.absolutePath() != newFilePath.absolutePath(); if (isUiFile) { if (moved) return; // This is out of scope. oldFileName = "ui_" + oldFilePath.baseName() + ".h"; newFileName = "ui_" + newFilePath.baseName() + ".h"; } static const auto getProductNode = [](const FilePath &filePath) -> const Node * { const Node * const fileNode = ProjectTree::nodeForFile(filePath); if (!fileNode) return nullptr; const ProjectNode *productNode = fileNode->parentProjectNode(); while (productNode && !productNode->isProduct()) productNode = productNode->parentProjectNode(); if (!productNode) productNode = fileNode->getProject()->rootProjectNode(); return productNode; }; const Node * const productNodeForUiFile = isUiFile ? getProductNode(oldFilePath) : nullptr; if (isUiFile && !productNodeForUiFile) continue; // Handle locations where this file is included. const QList locations = theSnapshot.includeLocationsOfDocument( isUiFile ? FilePath::fromString(oldFileName) : oldFilePath); for (const Snapshot::IncludeLocation &loc : locations) { const FilePath includingFileOld = loc.first->filePath(); FilePath includingFileNew = includingFileOld; for (const auto &pathPair : oldAndNewPaths) { if (includingFileNew == pathPair.first) { includingFileNew = pathPair.second; break; } } // Larger projects can easily have more than one ui file with the same name. // Replace only if ui file and including source file belong to the same product. if (isUiFile && getProductNode(includingFileOld) != productNodeForUiFile) continue; rewriteCandidates[std::make_pair(includingFileOld, includingFileNew)].append( {oldFilePath, newFilePath, oldFileName, newFileName, loc.second, isUiFile}); } // Handle the includes of this file: If its location changed, its own local includes // need to get adapted as well. if (isUiFile || !moved) continue; const CPlusPlus::Document::Ptr cppDoc = theSnapshot.document(oldFilePath); if (!cppDoc) continue; const auto key = std::make_pair(oldFilePath, newFilePath); for (const CPlusPlus::Document::Include &include : cppDoc->resolvedIncludes()) { const FilePath oldHeaderPath = include.resolvedFileName(); const QString oldHeaderName = oldHeaderPath.fileName(); const bool isUiHeader = oldHeaderName.startsWith("ui_") && oldHeaderName.endsWith(".h"); if (!isUiHeader && include.type() != CPlusPlus::Client::IncludeLocal) continue; FilePath newHeaderPath = oldHeaderPath; for (const auto &pathPair : oldAndNewPaths) { if (oldHeaderPath == pathPair.first) { newHeaderPath = pathPair.second; break; } } rewriteCandidates[key].append( {oldHeaderPath, newHeaderPath, oldHeaderName, newHeaderPath.fileName(), include.line(), isUiHeader}); } } const TextEditor::PlainRefactoringFileFactory changes; for (auto it = rewriteCandidates.cbegin(); it != rewriteCandidates.cend(); ++it) { const FilePath &includingFileOld = it.key().first; const FilePath &includingFileNew = it.key().second; TextEditor::RefactoringFilePtr file = changes.file(includingFileNew); ChangeSet changeSet; for (const RewriteCandidate &candidate : it.value()) { const QTextBlock &block = file->document()->findBlockByNumber( candidate.includeLine - 1); const FilePath relPathOld = FilePath::fromString(FilePath::calcRelativePath( candidate.oldHeaderFilePath.toString(), includingFileOld.parentDir().toString())); const FilePath relPathNew = FilePath::fromString(FilePath::calcRelativePath( candidate.newHeaderFilePath.toString(), includingFileNew.parentDir().toString())); int replaceStart = block.text().indexOf(relPathOld.toString()); QString oldString; QString newString; if (candidate.isUiHeader || replaceStart == -1) { replaceStart = block.text().indexOf(candidate.oldHeaderFileName); oldString = candidate.oldHeaderFileName; newString = candidate.newHeaderFileName; } else { oldString = relPathOld.toString(); newString = relPathNew.toString(); } if (replaceStart > -1 && oldString != newString) { changeSet.replace(block.position() + replaceStart, block.position() + replaceStart + oldString.length(), newString); } } file->setChangeSet(changeSet); file->apply(); } } // Return the class name which function belongs to static const char *belongingClassName(const Function *function) { if (!function) return nullptr; if (auto funcName = function->name()) { if (auto qualifiedNameId = funcName->asQualifiedNameId()) { if (const Name *funcBaseName = qualifiedNameId->base()) { if (auto identifier = funcBaseName->identifier()) return identifier->chars(); } } } return nullptr; } QSet CppModelManager::symbolsInFiles(const QSet &files) const { QSet uniqueSymbols; const Snapshot cppSnapShot = snapshot(); // Iterate over the files and get interesting symbols for (const FilePath &file : files) { // Add symbols from the C++ code model const CPlusPlus::Document::Ptr doc = cppSnapShot.document(file); if (!doc.isNull() && doc->control()) { const CPlusPlus::Control *ctrl = doc->control(); CPlusPlus::Symbol **symPtr = ctrl->firstSymbol(); // Read-only while (symPtr != ctrl->lastSymbol()) { const CPlusPlus::Symbol *sym = *symPtr; const CPlusPlus::Identifier *symId = sym->identifier(); // Add any class, function or namespace identifiers if ((sym->asClass() || sym->asFunction() || sym->asNamespace()) && symId && symId->chars()) { uniqueSymbols.insert(QString::fromUtf8(symId->chars())); } // Handle specific case : get "Foo" in "void Foo::function() {}" if (sym->asFunction() && !sym->asFunction()->asDeclaration()) { const char *className = belongingClassName(sym->asFunction()); if (className) uniqueSymbols.insert(QString::fromUtf8(className)); } ++symPtr; } } } return uniqueSymbols; } void CppModelManager::onCoreAboutToClose() { d->m_fallbackProjectPartTimer.disconnect(); d->m_fallbackProjectPartTimer.stop(); ProgressManager::cancelTasks(Constants::TASK_INDEX); d->m_enableGC = false; } void CppModelManager::handleSettingsChange(Project *project) { ProjectInfoList info; if (project) info << projectInfo(project); else info << projectInfos(); for (const ProjectInfo::ConstPtr &pi : std::as_const(info)) { const CppCodeModelSettings newSettings = CppCodeModelSettings::settingsForProject( pi->projectFilePath()); if (pi->settings() != newSettings) updateProjectInfo(ProjectInfo::cloneWithNewSettings(pi, newSettings)); } } void CppModelManager::setupFallbackProjectPart() { ToolchainInfo tcInfo; RawProjectPart rpp; rpp.setMacros(definedMacros()); rpp.setHeaderPaths(headerPaths()); rpp.setQtVersion(QtMajorVersion::Qt5); // Do not activate ObjectiveCExtensions since this will lead to the // "objective-c++" language option for a project-less *.cpp file. LanguageExtensions langExtensions = LanguageExtension::All; langExtensions &= ~LanguageExtensions(LanguageExtension::ObjectiveC); // TODO: Use different fallback toolchain for different kinds of files? const Kit * const defaultKit = KitManager::isLoaded() ? KitManager::defaultKit() : nullptr; const Toolchain * const defaultTc = defaultKit ? ToolchainKitAspect::cxxToolchain(defaultKit) : nullptr; if (defaultKit && defaultTc) { FilePath sysroot = SysRootKitAspect::sysRoot(defaultKit); if (sysroot.isEmpty()) sysroot = FilePath::fromString(defaultTc->sysRoot()); Utils::Environment env = defaultKit->buildEnvironment(); tcInfo = ToolchainInfo(defaultTc, sysroot, env); const auto macroInspectionWrapper = [runner = tcInfo.macroInspectionRunner]( const QStringList &flags) { Toolchain::MacroInspectionReport report = runner(flags); report.languageVersion = LanguageVersion::LatestCxx; return report; }; tcInfo.macroInspectionRunner = macroInspectionWrapper; } const auto part = ProjectPart::create({}, rpp, {}, {}, {}, langExtensions, {}, tcInfo); { QMutexLocker locker(&d->m_fallbackProjectPartMutex); d->m_fallbackProjectPart = part; } emit m_instance->fallbackProjectPartUpdated(); } void CppModelManager::GC() { if (!d->m_enableGC) return; // Collect files of opened editors and editor supports (e.g. ui code model) FilePaths filesInEditorSupports; const QList editorDocuments = cppEditorDocuments(); for (const CppEditorDocumentHandle *editorDocument : editorDocuments) filesInEditorSupports << editorDocument->filePath(); const QSet abstractEditorSupportList = abstractEditorSupports(); for (AbstractEditorSupport *abstractEditorSupport : abstractEditorSupportList) filesInEditorSupports << abstractEditorSupport->filePath(); Snapshot currentSnapshot = snapshot(); QSet reachableFiles; // 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. FilePaths todo = filesInEditorSupports + projectFiles(); // Collect all files that are reachable from the project files while (!todo.isEmpty()) { const FilePath filePath = todo.last(); todo.removeLast(); if (!Utils::insert(reachableFiles, filePath)) continue; if (Document::Ptr doc = currentSnapshot.document(filePath)) todo += doc->includedFiles(); } // Find out the files in the current snapshot that are not reachable from the project files QStringList notReachableFiles; Snapshot newSnapshot; for (Snapshot::const_iterator it = currentSnapshot.begin(); it != currentSnapshot.end(); ++it) { const FilePath &fileName = it.key(); if (reachableFiles.contains(fileName)) newSnapshot.insert(it.value()); else notReachableFiles.append(fileName.toString()); } // Announce removing files and replace the snapshot emit m_instance->aboutToRemoveFiles(notReachableFiles); replaceSnapshot(newSnapshot); emit m_instance->gcFinished(); } void CppModelManager::finishedRefreshingSourceFiles(const QSet &files) { emit m_instance->sourceFilesRefreshed(files); } void CppModelManager::activateClangCodeModel( std::unique_ptr &&modelManagerSupport) { d->m_extendedModelManagerSupport = std::move(modelManagerSupport); d->m_activeModelManagerSupport = d->m_extendedModelManagerSupport.get(); } CppCompletionAssistProvider *CppModelManager::completionAssistProvider() { return d->m_builtinModelManagerSupport.completionAssistProvider(); } TextEditor::BaseHoverHandler *CppModelManager::createHoverHandler() { return d->m_builtinModelManagerSupport.createHoverHandler(); } void CppModelManager::followSymbol(const CursorInEditor &data, const LinkHandler &processLinkCallback, bool resolveTarget, bool inNextSplit, FollowSymbolMode mode, Backend backend) { modelManagerSupport(backend)->followSymbol(data, processLinkCallback, mode, resolveTarget, inNextSplit); } void CppModelManager::followSymbolToType(const CursorInEditor &data, const LinkHandler &processLinkCallback, bool inNextSplit, Backend backend) { modelManagerSupport(backend)->followSymbolToType(data, processLinkCallback, inNextSplit); } void CppModelManager::switchDeclDef(const CursorInEditor &data, const LinkHandler &processLinkCallback, Backend backend) { modelManagerSupport(backend)->switchDeclDef(data, processLinkCallback); } BaseEditorDocumentProcessor *CppModelManager::createEditorDocumentProcessor( TextEditor::TextDocument *baseTextDocument) { return d->m_activeModelManagerSupport->createEditorDocumentProcessor(baseTextDocument); } CppIndexingSupport *CppModelManager::indexingSupport() { return d->m_internalIndexingSupport; } FilePaths CppModelManager::projectFiles() { return d->m_lockedProjectData.update( [](CppModelManagerPrivate::SyncedProjectData &sd) { CppModelManagerPrivate::ensureUpdated(sd); return sd.m_projectFiles; }); } HeaderPaths CppModelManager::headerPaths() { return d->m_lockedProjectData.update( [](CppModelManagerPrivate::SyncedProjectData &sd) { CppModelManagerPrivate::ensureUpdated(sd); return sd.m_headerPaths; }); } void CppModelManager::setHeaderPaths(const HeaderPaths &headerPaths) { d->m_lockedProjectData.writeLocked()->m_headerPaths = headerPaths; } Macros CppModelManager::definedMacros() { return d->m_lockedProjectData.update([](CppModelManagerPrivate::SyncedProjectData &sd) { CppModelManagerPrivate::ensureUpdated(sd); return sd.m_definedMacros; }); } void CppModelManager::enableGarbageCollector(bool enable) { d->m_delayedGcTimer.stop(); d->m_enableGC = enable; } SymbolFinder *CppModelManager::symbolFinder() { return &d->m_symbolFinder; } QThreadPool *CppModelManager::sharedThreadPool() { return &d->m_threadPool; } bool CppModelManager::setExtraDiagnostics(const FilePath &filePath, const QString &kind, const QList &diagnostics) { d->m_diagnosticMessages = diagnostics; emit m_instance->diagnosticsChanged(filePath, kind); return true; } const QList CppModelManager::diagnosticMessages() { return d->m_diagnosticMessages; } } // namespace CppEditor