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"