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"