File indexing completed on 2024-06-16 04:23:15

0001 /*
0002     SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "usescollector.h"
0008 #include <interfaces/icore.h>
0009 #include <interfaces/ilanguagecontroller.h>
0010 #include <language/duchain/parsingenvironment.h>
0011 #include <language/duchain/duchainlock.h>
0012 #include <language/duchain/duchain.h>
0013 #include <interfaces/iprojectcontroller.h>
0014 #include <interfaces/idocumentcontroller.h>
0015 #include <language/duchain/duchainutils.h>
0016 #include <language/duchain/types/indexedtype.h>
0017 #include <language/duchain/classfunctiondeclaration.h>
0018 #include <backgroundparser/parsejob.h>
0019 #include <backgroundparser/backgroundparser.h>
0020 #include "../classmemberdeclaration.h"
0021 #include "../abstractfunctiondeclaration.h"
0022 #include "../functiondefinition.h"
0023 #include <debug.h>
0024 #include <interfaces/iuicontroller.h>
0025 #include <codegen/coderepresentation.h>
0026 #include <sublime/message.h>
0027 #include <KLocalizedString>
0028 
0029 using namespace KDevelop;
0030 
0031 ///@todo make this language-neutral
0032 static Identifier destructorForName(const Identifier& name)
0033 {
0034     QString str = name.identifier().str();
0035     if (str.startsWith(QLatin1Char('~')))
0036         return Identifier(str);
0037     return Identifier(QLatin1Char('~') + str);
0038 }
0039 
0040 ///@todo Only collect uses within currently loaded projects
0041 
0042 template <class ImportanceChecker>
0043 void collectImporters(ImportanceChecker& checker, ParsingEnvironmentFile* current,
0044                       QSet<ParsingEnvironmentFile*>& visited, QSet<ParsingEnvironmentFile*>& collected)
0045 {
0046     //Ignore proxy-contexts while collecting. Those build a parallel and much more complicated structure.
0047     if (current->isProxyContext())
0048         return;
0049 
0050     if (visited.contains(current))
0051         return;
0052 
0053     visited.insert(current);
0054     if (checker(current))
0055         collected.insert(current);
0056 
0057     const auto importers = current->importers();
0058     for (const ParsingEnvironmentFilePointer& importer : importers) {
0059         if (importer.data())
0060             collectImporters(checker, importer.data(), visited, collected);
0061         else
0062             qCDebug(LANGUAGE) << "missing environment-file, strange";
0063     }
0064 }
0065 
0066 ///The returned set does not include the file itself
0067 ///@param visited should be empty on each call, used to prevent endless recursion
0068 void allImportedFiles(ParsingEnvironmentFilePointer file, QSet<IndexedString>& set,
0069                       QSet<ParsingEnvironmentFilePointer>& visited)
0070 {
0071     const auto imports = file->imports();
0072     for (const ParsingEnvironmentFilePointer& import : imports) {
0073         if (!import) {
0074             qCDebug(LANGUAGE) << "warning: missing import";
0075             continue;
0076         }
0077         if (!visited.contains(import)) {
0078             visited.insert(import);
0079             set.insert(import->url());
0080             allImportedFiles(import, set, visited);
0081         }
0082     }
0083 }
0084 
0085 void UsesCollector::setCollectConstructors(bool process)
0086 {
0087     m_collectConstructors = process;
0088 }
0089 
0090 void UsesCollector::setProcessDeclarations(bool process)
0091 {
0092     m_processDeclarations = process;
0093 }
0094 
0095 void UsesCollector::setCollectOverloads(bool collect)
0096 {
0097     m_collectOverloads = collect;
0098 }
0099 
0100 void UsesCollector::setCollectDefinitions(bool collect)
0101 {
0102     m_collectDefinitions = collect;
0103 }
0104 
0105 QList<IndexedDeclaration> UsesCollector::declarations()
0106 {
0107     return m_declarations;
0108 }
0109 
0110 bool UsesCollector::isReady() const
0111 {
0112     return m_waitForUpdate.size() == m_updateReady.size();
0113 }
0114 
0115 bool UsesCollector::shouldRespectFile(const IndexedString& document)
0116 {
0117     return ( bool )ICore::self()->projectController()->findProjectForUrl(document.toUrl()) ||
0118            ( bool )ICore::self()->documentController()->documentForUrl(document.toUrl());
0119 }
0120 
0121 struct ImportanceChecker
0122 {
0123     explicit ImportanceChecker(UsesCollector& collector) : m_collector(collector)
0124     {
0125     }
0126     bool operator ()(ParsingEnvironmentFile* file)
0127     {
0128         return m_collector.shouldRespectFile(file->url());
0129     }
0130     UsesCollector& m_collector;
0131 };
0132 
0133 void UsesCollector::startCollecting()
0134 {
0135     DUChainReadLocker lock(DUChain::lock());
0136 
0137     if (Declaration* decl = m_declaration.data()) {
0138         if (m_collectDefinitions) {
0139             if (auto* def = dynamic_cast<FunctionDefinition*>(decl)) {
0140                 //Jump from definition to declaration
0141                 Declaration* declaration = def->declaration();
0142                 if (declaration)
0143                     decl = declaration;
0144             }
0145         }
0146 
0147         ///Collect all overloads into "decls"
0148         QList<Declaration*> decls;
0149 
0150         if (m_collectOverloads && decl->context()->owner() && decl->context()->type() == DUContext::Class) {
0151             //First find the overridden base, and then all overriders of that base.
0152             while (Declaration* overridden = DUChainUtils::overridden(decl))
0153                 decl = overridden;
0154             uint maxAllowedSteps = 10000;
0155             decls += DUChainUtils::overriders(decl->context()->owner(), decl, maxAllowedSteps);
0156             if (maxAllowedSteps == 10000) {
0157                 ///@todo Fail!
0158             }
0159         }
0160 
0161         decls << decl;
0162 
0163         ///Collect all "parsed versions" or forward-declarations etc. here, into allDeclarations
0164         QSet<IndexedDeclaration> allDeclarations;
0165 
0166         for (Declaration* overload : qAsConst(decls)) {
0167             m_declarations = DUChainUtils::collectAllVersions(overload);
0168             for (const IndexedDeclaration& d : qAsConst(m_declarations)) {
0169                 if (!d.data() || d.data()->id() != overload->id())
0170                     continue;
0171                 allDeclarations.insert(d);
0172 
0173                 if (m_collectConstructors && d.data() && d.data()->internalContext() &&
0174                     d.data()->internalContext()->type() == DUContext::Class) {
0175                     const QList<Declaration*> constructors = d.data()->internalContext()->findLocalDeclarations(
0176                         d.data()->identifier(), CursorInRevision::invalid(), nullptr,
0177                         AbstractType::Ptr(), DUContext::OnlyFunctions);
0178                     for (Declaration* constructor : constructors) {
0179                         auto* classFun = dynamic_cast<ClassFunctionDeclaration*>(constructor);
0180                         if (classFun && classFun->isConstructor())
0181                             allDeclarations.insert(IndexedDeclaration(constructor));
0182                     }
0183 
0184                     Identifier destructorId = destructorForName(d.data()->identifier());
0185 
0186                     const QList<Declaration*> destructors = d.data()->internalContext()->findLocalDeclarations(
0187                         destructorId, CursorInRevision::invalid(), nullptr,
0188                         AbstractType::Ptr(), DUContext::OnlyFunctions);
0189                     for (Declaration* destructor : destructors) {
0190                         auto* classFun = dynamic_cast<ClassFunctionDeclaration*>(destructor);
0191                         if (classFun && classFun->isDestructor())
0192                             allDeclarations.insert(IndexedDeclaration(destructor));
0193                     }
0194                 }
0195             }
0196         }
0197 
0198         ///Collect definitions for declarations
0199         if (m_collectDefinitions) {
0200             for (const IndexedDeclaration d : qAsConst(allDeclarations)) {
0201                 Declaration* definition = FunctionDefinition::definition(d.data());
0202                 if (definition) {
0203                     qCDebug(LANGUAGE) << "adding definition";
0204                     allDeclarations.insert(IndexedDeclaration(definition));
0205                 }
0206             }
0207         }
0208 
0209         m_declarations.clear();
0210 
0211         ///Step 4: Copy allDeclarations into m_declarations, build top-context list, etc.
0212         QList<ReferencedTopDUContext> candidateTopContexts;
0213         candidateTopContexts.reserve(allDeclarations.size());
0214         m_declarations.reserve(allDeclarations.size());
0215         for (const IndexedDeclaration d : qAsConst(allDeclarations)) {
0216             m_declarations << d;
0217             m_declarationTopContexts.insert(d.indexedTopContext());
0218             //We only collect declarations with the same type here..
0219             candidateTopContexts << d.indexedTopContext().data();
0220         }
0221 
0222         ImportanceChecker checker(*this);
0223 
0224         QSet<ParsingEnvironmentFile*> visited;
0225         QSet<ParsingEnvironmentFile*> collected;
0226 
0227         qCDebug(LANGUAGE) << "count of source candidate top-contexts:" << candidateTopContexts.size();
0228 
0229         ///We use ParsingEnvironmentFile to collect all the relevant importers, because loading those is very cheap, compared
0230         ///to loading a whole TopDUContext.
0231         if (decl->inSymbolTable()) {
0232             //The declaration can only be used from other contexts if it is in the symbol table
0233             for (const ReferencedTopDUContext& top : qAsConst(candidateTopContexts)) {
0234                 if (top->parsingEnvironmentFile()) {
0235                     collectImporters(checker, top->parsingEnvironmentFile().data(), visited, collected);
0236                     //In C++, visibility is not handled strictly through the import-structure.
0237                     //It may happen that an object is visible because of an earlier include.
0238                     //We can not perfectly handle that, but we can at least handle it if the header includes
0239                     //the header that contains the declaration. That header may be parsed empty due to header-guards,
0240                     //but we still need to pick it up here.
0241                     const QList<ParsingEnvironmentFilePointer> allVersions = DUChain::self()->allEnvironmentFiles(
0242                         top->url());
0243                     for (const ParsingEnvironmentFilePointer& version : allVersions)
0244                         collectImporters(checker, version.data(), visited, collected);
0245                 }
0246             }
0247         }
0248         KDevelop::ParsingEnvironmentFile* file = decl->topContext()->parsingEnvironmentFile().data();
0249         if (!file)
0250             return;
0251 
0252         if (checker(file))
0253             collected.insert(file);
0254 
0255         {
0256             QSet<ParsingEnvironmentFile*> filteredCollected;
0257             QMap<IndexedString, bool> grepCache;
0258             // Filter the collected files by performing a grep
0259             for (ParsingEnvironmentFile* file : qAsConst(collected)) {
0260                 IndexedString url = file->url();
0261                 QMap<IndexedString, bool>::iterator grepCacheIt = grepCache.find(url);
0262                 if (grepCacheIt == grepCache.end()) {
0263                     CodeRepresentation::Ptr repr = KDevelop::createCodeRepresentation(url);
0264                     if (repr) {
0265                         QVector<KTextEditor::Range> found = repr->grep(decl->identifier().identifier().str());
0266                         grepCacheIt = grepCache.insert(url, !found.isEmpty());
0267                     }
0268                 }
0269                 if (grepCacheIt.value())
0270                     filteredCollected << file;
0271             }
0272 
0273             qCDebug(LANGUAGE) << "Collected contexts for full re-parse, before filtering: " << collected.size() <<
0274                 " after filtering: " << filteredCollected.size();
0275             collected = filteredCollected;
0276         }
0277 
0278         ///We have all importers now. However since we can tell parse-jobs to also update all their importers, we only need to
0279         ///update the "root" top-contexts that open the whole set with their imports.
0280         QSet<IndexedString> rootFiles;
0281         QSet<IndexedString> allFiles;
0282         for (ParsingEnvironmentFile* importer : qAsConst(collected)) {
0283             QSet<IndexedString> allImports;
0284             QSet<ParsingEnvironmentFilePointer> visited;
0285             allImportedFiles(ParsingEnvironmentFilePointer(importer), allImports, visited);
0286             //Remove all files from the "root" set that are imported by this one
0287             ///@todo more intelligent
0288             rootFiles -= allImports;
0289             allFiles += allImports;
0290             allFiles.insert(importer->url());
0291             rootFiles.insert(importer->url());
0292         }
0293 
0294         emit maximumProgressSignal(rootFiles.size());
0295         maximumProgress(rootFiles.size());
0296 
0297         //If we used the AllDeclarationsContextsAndUsesRecursive flag here, we would compute way too much. This way we only
0298         //set the minimum-features selectively on the files we really require them on.
0299         for (ParsingEnvironmentFile* file : qAsConst(collected)) {
0300             m_staticFeaturesManipulated.insert(file->url());
0301         }
0302 
0303         m_staticFeaturesManipulated.insert(decl->url());
0304 
0305         const auto currentFeaturesManipulated = m_staticFeaturesManipulated;
0306         for (const IndexedString& file : currentFeaturesManipulated) {
0307             ParseJob::setStaticMinimumFeatures(file, TopDUContext::AllDeclarationsContextsAndUses);
0308         }
0309 
0310         m_waitForUpdate = rootFiles;
0311 
0312         for (const IndexedString& file : qAsConst(rootFiles)) {
0313             qCDebug(LANGUAGE) << "updating root file:" << file.str();
0314             DUChain::self()->updateContextForUrl(file, TopDUContext::AllDeclarationsContextsAndUses, this);
0315         }
0316     } else {
0317         emit maximumProgressSignal(0);
0318         maximumProgress(0);
0319     }
0320 }
0321 
0322 void UsesCollector::maximumProgress(uint max)
0323 {
0324     Q_UNUSED(max);
0325 }
0326 
0327 UsesCollector::UsesCollector(IndexedDeclaration declaration) : m_declaration(declaration)
0328     , m_collectOverloads(true)
0329     , m_collectDefinitions(true)
0330     , m_collectConstructors(false)
0331     , m_processDeclarations(true)
0332 {
0333 }
0334 
0335 UsesCollector::~UsesCollector()
0336 {
0337     ICore::self()->languageController()->backgroundParser()->revertAllRequests(this);
0338 
0339     const auto currentFeaturesManipulated = m_staticFeaturesManipulated;
0340     for (const IndexedString& file : currentFeaturesManipulated) {
0341         ParseJob::unsetStaticMinimumFeatures(file, TopDUContext::AllDeclarationsContextsAndUses);
0342     }
0343 }
0344 
0345 void UsesCollector::progress(uint processed, uint total)
0346 {
0347     Q_UNUSED(processed);
0348     Q_UNUSED(total);
0349 }
0350 
0351 void UsesCollector::updateReady(const KDevelop::IndexedString& url, KDevelop::ReferencedTopDUContext topContext)
0352 {
0353     DUChainReadLocker lock(DUChain::lock());
0354 
0355     if (!topContext) {
0356         qCDebug(LANGUAGE) << "failed updating" << url.str();
0357     } else {
0358         if (topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) {
0359             ///Use the attached content-context instead
0360             const auto importedParentContexts = topContext->importedParentContexts();
0361             for (const DUContext::Import& import : importedParentContexts) {
0362                 if (import.context(nullptr) && import.context(nullptr)->topContext()->parsingEnvironmentFile() &&
0363                     !import.context(nullptr)->topContext()->parsingEnvironmentFile()->isProxyContext()) {
0364                     if ((import.context(nullptr)->topContext()->features() &
0365                          TopDUContext::AllDeclarationsContextsAndUses)) {
0366                         ReferencedTopDUContext newTop(import.context(nullptr)->topContext());
0367                         topContext = newTop;
0368                         break;
0369                     }
0370                 }
0371             }
0372 
0373             if (topContext->parsingEnvironmentFile() && topContext->parsingEnvironmentFile()->isProxyContext()) {
0374                 qCDebug(LANGUAGE) << "got bad proxy-context for" << url.str();
0375                 topContext = nullptr;
0376             }
0377         }
0378     }
0379 
0380     if (m_waitForUpdate.contains(url) && !m_updateReady.contains(url)) {
0381         m_updateReady << url;
0382         m_checked.clear();
0383 
0384         emit progressSignal(m_updateReady.size(), m_waitForUpdate.size());
0385         progress(m_updateReady.size(), m_waitForUpdate.size());
0386     }
0387 
0388     if (!topContext || !topContext->parsingEnvironmentFile()) {
0389         qCDebug(LANGUAGE) << "bad top-context";
0390         return;
0391     }
0392 
0393     if (!m_staticFeaturesManipulated.contains(url))
0394         return; //Not interesting
0395 
0396     if (!(topContext->features() & TopDUContext::AllDeclarationsContextsAndUses)) {
0397         ///@todo With simplified environment-matching, the same file may have been imported multiple times,
0398         ///while only one of  those was updated. We have to check here whether this file is just such an import,
0399         ///or whether we work on with it.
0400         ///@todo We will lose files that were edited right after their update here.
0401         qCWarning(LANGUAGE) << "WARNING: context" << topContext->url().str() << "does not have the required features!!";
0402         // TODO no i18n?
0403         const QString messageText = QLatin1String("Updating ") +
0404             ICore::self()->projectController()->prettyFileName(topContext->url().toUrl(), KDevelop::IProjectController::FormatPlain) + QLatin1String(" failed!");
0405         auto* message = new Sublime::Message(messageText, Sublime::Message::Warning);
0406         message->setAutoHide(0);
0407         ICore::self()->uiController()->postMessage(message);
0408         return;
0409     }
0410 
0411     if (topContext->parsingEnvironmentFile()->needsUpdate()) {
0412         qCWarning(LANGUAGE) << "WARNING: context" << topContext->url().str() << "is not up to date!";
0413         const auto prettyFileName = ICore::self()->projectController()->prettyFileName(topContext->url().toUrl(), KDevelop::IProjectController::FormatPlain);
0414         const auto messageText = i18n("%1 still needs an update!", prettyFileName);
0415         auto* message = new Sublime::Message(messageText, Sublime::Message::Warning);
0416         message->setAutoHide(0);
0417         ICore::self()->uiController()->postMessage(message);
0418 //       return;
0419     }
0420 
0421     IndexedTopDUContext indexed(topContext.data());
0422     if (m_checked.contains(indexed))
0423         return;
0424 
0425     if (!topContext.data()) {
0426         qCDebug(LANGUAGE) << "updated top-context is zero:" << url.str();
0427         return;
0428     }
0429 
0430     m_checked.insert(indexed);
0431 
0432     if (m_declaration.data() && ((m_processDeclarations && m_declarationTopContexts.contains(indexed)) ||
0433                                  DUChainUtils::contextHasUse(topContext.data(), m_declaration.data()))) {
0434         if (!m_processed.contains(topContext->url())) {
0435             m_processed.insert(topContext->url());
0436             lock.unlock();
0437             emit processUsesSignal(topContext);
0438             processUses(topContext);
0439             lock.lock();
0440         }
0441     } else {
0442         if (!m_declaration.data()) {
0443             qCDebug(LANGUAGE) << "declaration has become invalid";
0444         }
0445     }
0446 
0447     QList<KDevelop::ReferencedTopDUContext> imports;
0448 
0449     const auto importedParentContexts = topContext->importedParentContexts();
0450     for (const DUContext::Import& imported : importedParentContexts) {
0451         if (imported.context(nullptr) && imported.context(nullptr)->topContext())
0452             imports << KDevelop::ReferencedTopDUContext(imported.context(nullptr)->topContext());
0453     }
0454 
0455     for (const KDevelop::ReferencedTopDUContext& import : qAsConst(imports)) {
0456         IndexedString url = import->url();
0457         lock.unlock();
0458         updateReady(url, import);
0459         lock.lock();
0460     }
0461 }
0462 
0463 IndexedDeclaration UsesCollector::declaration() const
0464 {
0465     return m_declaration;
0466 }
0467 
0468 #include "moc_usescollector.cpp"