File indexing completed on 2024-04-28 04:38:07
0001 /* 0002 SPDX-FileCopyrightText: 2013 Olivier de Gaalon <olivier.jg@gmail.com> 0003 SPDX-FileCopyrightText: 2013 Milian Wolff <mail@milianw.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "clangparsejob.h" 0009 0010 #include <interfaces/icore.h> 0011 #include <interfaces/iprojectcontroller.h> 0012 #include <interfaces/iproject.h> 0013 #include <interfaces/ilanguagecontroller.h> 0014 #include <interfaces/idocumentcontroller.h> 0015 0016 #include <language/interfaces/icodehighlighting.h> 0017 0018 #include <language/backgroundparser/urlparselock.h> 0019 #include <language/backgroundparser/backgroundparser.h> 0020 0021 #include <language/duchain/duchainlock.h> 0022 #include <language/duchain/duchainutils.h> 0023 #include <language/duchain/duchain.h> 0024 #include <language/duchain/parsingenvironment.h> 0025 0026 #include <custom-definesandincludes/idefinesandincludesmanager.h> 0027 0028 #include <project/projectmodel.h> 0029 #include <project/interfaces/ibuildsystemmanager.h> 0030 0031 #include "clangsettings/clangsettingsmanager.h" 0032 #include "duchain/clanghelpers.h" 0033 #include "duchain/clangpch.h" 0034 #include "duchain/duchainutils.h" 0035 #include "duchain/parsesession.h" 0036 #include "duchain/clangindex.h" 0037 #include "duchain/clangparsingenvironmentfile.h" 0038 #include "util/clangdebug.h" 0039 #include "util/clangtypes.h" 0040 #include "util/clangutils.h" 0041 0042 #include "clangsupport.h" 0043 #include "duchain/documentfinderhelpers.h" 0044 0045 #include <QFile> 0046 #include <QStringList> 0047 #include <QFileInfo> 0048 #include <QReadLocker> 0049 #include <memory> 0050 0051 using namespace KDevelop; 0052 0053 namespace { 0054 0055 QString findConfigFile(const QString& forFile, const QString& configFileName) 0056 { 0057 QDir dir = QFileInfo(forFile).dir(); 0058 while (dir.exists()) { 0059 const QFileInfo customIncludePaths(dir, configFileName); 0060 if (customIncludePaths.exists()) { 0061 return customIncludePaths.absoluteFilePath(); 0062 } 0063 0064 if (!dir.cdUp()) { 0065 break; 0066 } 0067 } 0068 0069 return {}; 0070 } 0071 0072 Path::List readPathListFile(const QString& filepath) 0073 { 0074 if (filepath.isEmpty()) { 0075 return {}; 0076 } 0077 0078 QFile f(filepath); 0079 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { 0080 return {}; 0081 } 0082 0083 const QString text = QString::fromLocal8Bit(f.readAll()); 0084 const QStringList lines = text.split(QLatin1Char('\n'), Qt::SkipEmptyParts); 0085 Path::List paths; 0086 paths.reserve(lines.length()); 0087 for (const auto& line : lines) { 0088 paths << Path(line); 0089 } 0090 return paths; 0091 } 0092 0093 /** 0094 * File should contain the header to precompile and use while parsing 0095 * @returns the first path in the file 0096 */ 0097 Path userDefinedPchIncludeForFile(const QString& sourcefile) 0098 { 0099 const QString pchIncludeFilename = QStringLiteral(".kdev_pch_include"); 0100 const auto paths = readPathListFile(findConfigFile(sourcefile, pchIncludeFilename)); 0101 return paths.isEmpty() ? Path() : paths.first(); 0102 } 0103 0104 ProjectFileItem* findProjectFileItem(const IndexedString& url, bool* hasBuildSystemInfo) 0105 { 0106 ProjectFileItem* file = nullptr; 0107 0108 *hasBuildSystemInfo = false; 0109 const auto& projects = ICore::self()->projectController()->projects(); 0110 for (auto project : projects) { 0111 const auto files = project->filesForPath(url); 0112 if (files.isEmpty()) { 0113 continue; 0114 } 0115 0116 file = files.last(); 0117 0118 // A file might be defined in different targets. 0119 // Prefer file items defined inside a target with non-empty includes. 0120 for (auto f: files) { 0121 if (!dynamic_cast<ProjectTargetItem*>(f->parent())) { 0122 continue; 0123 } 0124 file = f; 0125 if (!IDefinesAndIncludesManager::manager()->includes(f, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) { 0126 break; 0127 } 0128 } 0129 } 0130 if (file && file->project()) { 0131 if (auto bsm = file->project()->buildSystemManager()) { 0132 *hasBuildSystemInfo = bsm->hasBuildInfo(file); 0133 } 0134 } 0135 return file; 0136 } 0137 0138 [[maybe_unused]] ClangParsingEnvironmentFile* parsingEnvironmentFile(const TopDUContext* context) 0139 { 0140 return dynamic_cast<ClangParsingEnvironmentFile*>(context->parsingEnvironmentFile().data()); 0141 } 0142 0143 DocumentChangeTracker* trackerForUrl(const IndexedString& url) 0144 { 0145 return ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); 0146 } 0147 0148 } 0149 0150 ClangParseJob::ClangParseJob(const IndexedString& url, ILanguageSupport* languageSupport) 0151 : ParseJob(url, languageSupport) 0152 , m_options(ParseSessionData::NoOption) 0153 { 0154 const auto tuUrl = clang()->index()->translationUnitForUrl(url); 0155 bool hasBuildSystemInfo; 0156 if (auto file = findProjectFileItem(tuUrl, &hasBuildSystemInfo)) { 0157 m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(file)); 0158 m_environment.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectories(file)); 0159 m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(file)); 0160 m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(file)); 0161 if (hasBuildSystemInfo) { 0162 // Assume the builder invokes the compiler in the build directory. 0163 m_environment.setWorkingDirectory(file->project()->buildSystemManager()->buildDirectory(file)); 0164 } 0165 } else { 0166 m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(tuUrl.str())); 0167 m_environment.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectories(tuUrl.str())); 0168 m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(tuUrl.str())); 0169 m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(tuUrl.str())); 0170 } 0171 const bool isSource = ClangHelpers::isSource(tuUrl.str()); 0172 m_environment.setQuality( 0173 isSource ? (hasBuildSystemInfo ? ClangParsingEnvironment::BuildSystem : ClangParsingEnvironment::Source) 0174 : ClangParsingEnvironment::Unknown 0175 ); 0176 m_environment.setTranslationUnitUrl(tuUrl); 0177 0178 Path::List projectPaths; 0179 const auto& projects = ICore::self()->projectController()->projects(); 0180 projectPaths.reserve(projects.size()); 0181 for (auto project : projects) { 0182 projectPaths.append(project->path()); 0183 if (auto* bsm = project->buildSystemManager()) { 0184 projectPaths.append(bsm->buildDirectory(project->projectItem())); 0185 } 0186 } 0187 m_environment.setProjectPaths(projectPaths); 0188 0189 m_unsavedFiles = ClangUtils::unsavedFiles(); 0190 0191 const auto documents = ICore::self()->documentController()->openDocuments(); 0192 for (auto* document : documents) { 0193 auto textDocument = document->textDocument(); 0194 if ( !textDocument ) { 0195 continue; 0196 } 0197 const IndexedString indexedUrl(textDocument->url()); 0198 if (indexedUrl == tuUrl) { 0199 m_tuDocumentIsUnsaved = true; 0200 } 0201 m_unsavedRevisions.insert(indexedUrl, ModificationRevision::revisionForFile(indexedUrl)); 0202 } 0203 0204 if (auto tracker = trackerForUrl(url)) { 0205 tracker->reset(); 0206 m_options |= ParseSessionData::OpenedInEditor; 0207 } else if (tuUrl != url && trackerForUrl(tuUrl)) { 0208 m_options |= ParseSessionData::OpenedInEditor; 0209 } 0210 } 0211 0212 ClangSupport* ClangParseJob::clang() const 0213 { 0214 return static_cast<ClangSupport*>(languageSupport()); 0215 } 0216 0217 void ClangParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) 0218 { 0219 QReadLocker parseLock(languageSupport()->parseLock()); 0220 0221 if (abortRequested()) { 0222 return; 0223 } 0224 0225 { 0226 const auto tuUrlStr = m_environment.translationUnitUrl().str(); 0227 if (!m_tuDocumentIsUnsaved && !QFile::exists(tuUrlStr)) { 0228 // maybe we requested a parse job some time ago but now the file 0229 // does not exist anymore. return early then 0230 clang()->index()->unpinTranslationUnitForUrl(document()); 0231 return; 0232 } 0233 0234 m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includesInBackground(tuUrlStr)); 0235 m_environment.addFrameworkDirectories(IDefinesAndIncludesManager::manager()->frameworkDirectoriesInBackground(tuUrlStr)); 0236 m_environment.addDefines(IDefinesAndIncludesManager::manager()->definesInBackground(tuUrlStr)); 0237 m_environment.addParserArguments(IDefinesAndIncludesManager::manager()->parserArgumentsInBackground(tuUrlStr)); 0238 m_environment.setPchInclude(userDefinedPchIncludeForFile(tuUrlStr)); 0239 } 0240 0241 if (abortRequested()) { 0242 return; 0243 } 0244 0245 // NOTE: we must have all declarations, contexts and uses available for files that are opened in the editor 0246 // it is very hard to check this for all included files of this TU, and previously lead to problems 0247 // when we tried to skip function bodies as an optimization for files that where not open in the editor. 0248 // now, we always build everything, which is correct but a tad bit slower. we can try to optimize later. 0249 setMinimumFeatures(minimumFeatures() | TopDUContext::AllDeclarationsContextsAndUses); 0250 0251 if (minimumFeatures() & AttachASTWithoutUpdating) { 0252 // The context doesn't need to be updated, but has no AST attached (restored from disk), 0253 // so attach AST to it, without updating DUChain 0254 ParseSession session(createSessionData()); 0255 0256 DUChainWriteLocker lock; 0257 auto ctx = DUChainUtils::standardContextForUrl(document().toUrl()); 0258 if (!ctx) { 0259 clangDebug() << "Lost context while attaching AST"; 0260 return; 0261 } 0262 ctx->setAst(IAstContainer::Ptr(session.data())); 0263 0264 if (minimumFeatures() & UpdateHighlighting) { 0265 lock.unlock(); 0266 languageSupport()->codeHighlighting()->highlightDUChain(ctx); 0267 } 0268 return; 0269 } 0270 0271 { 0272 UrlParseLock urlLock(document()); 0273 if (abortRequested() || !isUpdateRequired(ParseSession::languageString())) { 0274 return; 0275 } 0276 } 0277 0278 ParseSession session(ClangIntegration::DUChainUtils::findParseSessionData(document(), m_environment.translationUnitUrl())); 0279 if (abortRequested()) { 0280 return; 0281 } 0282 0283 if (!session.data() || !session.reparse(m_unsavedFiles, m_environment)) { 0284 session.setData(createSessionData()); 0285 } 0286 0287 if (!session.unit()) { 0288 // failed to parse file, unpin and don't try again 0289 clang()->index()->unpinTranslationUnitForUrl(document()); 0290 return; 0291 } 0292 0293 if (!clang_getFile(session.unit(), document().byteArray().constData())) { 0294 // this parse job's document does not exist in the pinned translation unit 0295 // so we need to unpin and re-add this document 0296 // Ideally we'd reset m_environment and session, but this is much simpler 0297 // and shouldn't be a common case 0298 clang()->index()->unpinTranslationUnitForUrl(document()); 0299 if (!(minimumFeatures() & Rescheduled)) { 0300 auto features = static_cast<TopDUContext::Features>(minimumFeatures() | Rescheduled); 0301 ICore::self()->languageController()->backgroundParser()->addDocument(document(), features, priority()); 0302 } 0303 return; 0304 } 0305 0306 Imports imports = ClangHelpers::tuImports(session.unit()); 0307 IncludeFileContexts includedFiles; 0308 if (auto pch = clang()->index()->pch(m_environment)) { 0309 auto pchFile = pch->mapFile(session.unit()); 0310 includedFiles = pch->mapIncludes(session.unit()); 0311 includedFiles.insert(pchFile, pch->context()); 0312 auto tuFile = clang_getFile(session.unit(), m_environment.translationUnitUrl().byteArray().constData()); 0313 imports.insert(tuFile, { pchFile, CursorInRevision(0, 0) } ); 0314 } 0315 0316 if (abortRequested()) { 0317 return; 0318 } 0319 0320 auto context = ClangHelpers::buildDUChain(session.mainFile(), imports, session, minimumFeatures(), includedFiles, 0321 m_unsavedRevisions, document(), clang()->index(), 0322 [this] { return abortRequested(); }); 0323 setDuChain(context); 0324 0325 if (abortRequested()) { 0326 return; 0327 } 0328 0329 if (context) { 0330 if (minimumFeatures() & TopDUContext::AST) { 0331 DUChainWriteLocker lock; 0332 context->setAst(IAstContainer::Ptr(session.data())); 0333 } 0334 #ifdef QT_DEBUG 0335 DUChainReadLocker lock; 0336 auto file = parsingEnvironmentFile(context); 0337 Q_ASSERT(file); 0338 // verify that features and environment where properly set in ClangHelpers::buildDUChain 0339 Q_ASSERT(file->featuresSatisfied(minimumFeatures() & ~TopDUContext::ForceUpdateRecursive)); 0340 if (trackerForUrl(context->url())) { 0341 Q_ASSERT(file->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses)); 0342 } 0343 #endif 0344 } 0345 0346 for (const auto& context : qAsConst(includedFiles)) { 0347 if (!context) { 0348 continue; 0349 } 0350 if (trackerForUrl(context->url())) { 0351 if (clang()->index()->translationUnitForUrl(context->url()) == m_environment.translationUnitUrl()) { 0352 // cache the parse session and the contained translation unit for this chain 0353 // this then allows us to quickly reparse the document if it is changed by 0354 // the user 0355 // otherwise no editor component is open for this document and we can dispose 0356 // the TU to save memory 0357 // share the session data with all contexts that are pinned to this TU 0358 DUChainWriteLocker lock; 0359 context->setAst(IAstContainer::Ptr(session.data())); 0360 } 0361 languageSupport()->codeHighlighting()->highlightDUChain(context); 0362 } 0363 } 0364 } 0365 0366 ParseSessionData::Ptr ClangParseJob::createSessionData() const 0367 { 0368 return ParseSessionData::Ptr(new ParseSessionData(m_unsavedFiles, clang()->index(), m_environment, m_options)); 0369 } 0370 0371 const ParsingEnvironment* ClangParseJob::environment() const 0372 { 0373 return &m_environment; 0374 } 0375 0376 #include "moc_clangparsejob.cpp"