File indexing completed on 2024-05-19 15:44:54

0001 /*
0002     SPDX-FileCopyrightText: 2013 Olivier de Gaalon <olivier.jg@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "clangindex.h"
0008 
0009 #include "clangpch.h"
0010 #include "clangparsingenvironment.h"
0011 #include "documentfinderhelpers.h"
0012 
0013 #include <interfaces/icompletionsettings.h>
0014 #include <interfaces/icore.h>
0015 #include <interfaces/ilanguagecontroller.h>
0016 
0017 #include <util/path.h>
0018 #include <util/clangtypes.h>
0019 #include <util/clangdebug.h>
0020 #include <language/backgroundparser/urlparselock.h>
0021 #include <language/duchain/duchainlock.h>
0022 #include <language/duchain/duchain.h>
0023 
0024 #include <clang-c/Index.h>
0025 
0026 using namespace KDevelop;
0027 
0028 namespace {
0029 
0030 CXIndex createIndex()
0031 {
0032     // NOTE: We don't exclude PCH declarations. That way we could retrieve imports manually,
0033     // as clang_getInclusions returns nothing on reparse with CXTranslationUnit_PrecompiledPreamble flag.
0034     const auto excludeDeclarationsFromPCH = false;
0035     const auto displayDiagnostics = qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DIAGS");
0036 
0037     CXIndex index;
0038 #if CINDEX_VERSION_MINOR >= 64
0039     const bool storePreamblesInMemory =
0040         ICore::self()->languageController()->completionSettings()->precompiledPreambleStorage()
0041         == ICompletionSettings::PrecompiledPreambleStorage::Memory;
0042     // When KDevelop crashes, libclang leaves preamble-*.pch files in its temporary directory.
0043     // These files occupy from tens of megabytes to gigabytes and are not automatically removed
0044     // until system restart. Set the preamble storage path to the active session's temporary
0045     // directory in order to remove all PCH files for the active session on KDevelop start.
0046     // See also https://github.com/llvm/llvm-project/issues/51847
0047     const auto preambleStoragePath = ICore::self()->sessionTemporaryDirectoryPath().toUtf8();
0048 
0049     CXIndexOptions options = {sizeof(CXIndexOptions)};
0050     // Demote the priority of the clang parse threads to reduce potential UI lockups.
0051     // The code completion threads still retain their normal priority to return the results as quickly as possible.
0052     options.ThreadBackgroundPriorityForIndexing = CXChoice_Enabled;
0053     options.ExcludeDeclarationsFromPCH = excludeDeclarationsFromPCH;
0054     options.DisplayDiagnostics = displayDiagnostics;
0055     options.StorePreamblesInMemory = storePreamblesInMemory;
0056     options.PreambleStoragePath = preambleStoragePath.constData();
0057 
0058     index = clang_createIndexWithOptions(&options);
0059     if (index) {
0060         return index;
0061     }
0062     qCWarning(KDEV_CLANG) << "clang_createIndexWithOptions() failed. CINDEX_VERSION_MINOR =" << CINDEX_VERSION_MINOR
0063                           << ", sizeof(CXIndexOptions) =" << options.Size;
0064     // Fall back to using older API. Configuring the preamble storage is not essential.
0065 #endif
0066     index = clang_createIndex(excludeDeclarationsFromPCH, displayDiagnostics);
0067     // Demote the priority of the clang parse threads to reduce potential UI lockups.
0068     // The code completion threads still retain their normal priority to return the results as quickly as possible.
0069     clang_CXIndex_setGlobalOptions(
0070         index, clang_CXIndex_getGlobalOptions(index) | CXGlobalOpt_ThreadBackgroundPriorityForIndexing);
0071     return index;
0072 }
0073 
0074 } // unnamed namespace
0075 
0076 ClangIndex::ClangIndex()
0077     : m_index(createIndex())
0078 {
0079 }
0080 
0081 CXIndex ClangIndex::index() const
0082 {
0083     return m_index;
0084 }
0085 
0086 QSharedPointer<const ClangPCH> ClangIndex::pch(const ClangParsingEnvironment& environment)
0087 {
0088     const auto& pchInclude = environment.pchInclude();
0089     if (!pchInclude.isValid()) {
0090         return {};
0091     }
0092 
0093     UrlParseLock pchLock(IndexedString(pchInclude.pathOrUrl()));
0094 
0095     if (QFile::exists(pchInclude.toLocalFile() + QLatin1String(".pch"))) {
0096         QReadLocker lock(&m_pchLock);
0097         auto pch = m_pch.constFind(pchInclude);
0098         if (pch != m_pch.constEnd()) {
0099             return pch.value();
0100         }
0101     }
0102 
0103     auto pch = QSharedPointer<ClangPCH>::create(environment, this);
0104     QWriteLocker lock(&m_pchLock);
0105     m_pch.insert(pchInclude, pch);
0106     return pch;
0107 }
0108 
0109 ClangIndex::~ClangIndex()
0110 {
0111     clang_disposeIndex(m_index);
0112 }
0113 
0114 IndexedString ClangIndex::translationUnitForUrl(const IndexedString& url)
0115 {
0116     { // try explicit pin data first
0117         QMutexLocker lock(&m_mappingMutex);
0118         auto tu = m_tuForUrl.find(url);
0119         if (tu != m_tuForUrl.end()) {
0120             if (!QFile::exists(tu.value().str())) {
0121                 // TU doesn't exist, unpin
0122                 m_tuForUrl.erase(tu);
0123                 return url;
0124             }
0125             return tu.value();
0126         }
0127     }
0128     // if no explicit pin data is available, follow back the duchain import chain
0129     {
0130         DUChainReadLocker lock;
0131         TopDUContext* top = DUChain::self()->chainForDocument(url);
0132         if (top) {
0133             TopDUContext* tuTop = top;
0134             QSet<TopDUContext*> visited;
0135             while(true) {
0136                 visited.insert(tuTop);
0137                 TopDUContext* next = nullptr;
0138                 const auto importers = tuTop->indexedImporters();
0139                 for (IndexedDUContext ctx : importers) {
0140                     if (ctx.data()) {
0141                         next = ctx.data()->topContext();
0142                         break;
0143                     }
0144                 }
0145                 if (!next || visited.contains(next)) {
0146                     break;
0147                 }
0148                 tuTop = next;
0149             }
0150             if (tuTop != top) {
0151                 return tuTop->url();
0152             }
0153         }
0154     }
0155 
0156     // otherwise, fallback to a simple buddy search for headers
0157     if (ClangHelpers::isHeader(url.str())) {
0158         const auto buddies = DocumentFinderHelpers::potentialBuddies(url.toUrl(), false);
0159         for (const QUrl& buddy : buddies) {
0160             const QString buddyPath = buddy.toLocalFile();
0161             if (QFile::exists(buddyPath)) {
0162                 return IndexedString(buddyPath);
0163             }
0164         }
0165     }
0166     return url;
0167 }
0168 
0169 void ClangIndex::pinTranslationUnitForUrl(const IndexedString& tu, const IndexedString& url)
0170 {
0171     QMutexLocker lock(&m_mappingMutex);
0172     m_tuForUrl.insert(url, tu);
0173 }
0174 
0175 void ClangIndex::unpinTranslationUnitForUrl(const IndexedString& url)
0176 {
0177     QMutexLocker lock(&m_mappingMutex);
0178     m_tuForUrl.remove(url);
0179 }