File indexing completed on 2024-05-12 04:39:11

0001 /*
0002     SPDX-FileCopyrightText: 2014 Olivier de Gaalon <olivier.jg@gmail.com>
0003     SPDX-FileCopyrightText: 2014 Milian Wolff <mail@milianw.de>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "clanghelpers.h"
0009 
0010 #include <language/duchain/duchain.h>
0011 #include <language/duchain/duchainlock.h>
0012 #include <language/duchain/declaration.h>
0013 #include <language/duchain/parsingenvironment.h>
0014 #include <language/backgroundparser/urlparselock.h>
0015 
0016 #include "builder.h"
0017 #include "parsesession.h"
0018 #include "clangparsingenvironmentfile.h"
0019 #include "clangindex.h"
0020 #include "clangducontext.h"
0021 #include "util/clangdebug.h"
0022 #include "util/clangtypes.h"
0023 
0024 #include "libclang_include_path.h"
0025 
0026 #include <QCoreApplication>
0027 #include <QDir>
0028 #include <QFileInfo>
0029 #include <QRegularExpression>
0030 
0031 #include <algorithm>
0032 
0033 #if HAVE_DLFCN
0034 #include <dlfcn.h>
0035 #endif
0036 
0037 using namespace KDevelop;
0038 
0039 namespace {
0040 
0041 CXChildVisitResult visitCursor(CXCursor cursor, CXCursor, CXClientData data)
0042 {
0043     if (cursor.kind != CXCursor_InclusionDirective) {
0044         return CXChildVisit_Continue;
0045     }
0046 
0047     auto imports = static_cast<Imports*>(data);
0048     CXFile file = clang_getIncludedFile(cursor);
0049     if(!file){
0050         return CXChildVisit_Continue;
0051     }
0052 
0053     CXSourceLocation location = clang_getCursorLocation(cursor);
0054     CXFile parentFile;
0055     uint line, column;
0056     clang_getFileLocation(location, &parentFile, &line, &column, nullptr);
0057 
0058     const auto parentFileImports = imports->values(parentFile);
0059     for (const auto& import : parentFileImports) {
0060         // clang_getInclusions doesn't include the same import twice, so we shouldn't do it too.
0061         if (import.file == file) {
0062             return CXChildVisit_Continue;
0063         }
0064     }
0065 
0066     imports->insert(parentFile, {file, CursorInRevision(line-1, column-1)});
0067 
0068     return CXChildVisit_Recurse;
0069 }
0070 
0071 ReferencedTopDUContext createTopContext(const IndexedString& path, const ClangParsingEnvironment& environment)
0072 {
0073     auto* file = new ClangParsingEnvironmentFile(path, environment);
0074     ReferencedTopDUContext context = new ClangTopDUContext(path, RangeInRevision(0, 0, INT_MAX, INT_MAX), file);
0075     DUChain::self()->addDocumentChain(context);
0076     context->updateImportsCache();
0077     return context;
0078 }
0079 
0080 }
0081 
0082 Imports ClangHelpers::tuImports(CXTranslationUnit tu)
0083 {
0084     Imports imports;
0085 
0086     // Intentionally don't use clang_getInclusions here, as it skips already visited inclusions
0087     // which makes TestDUChain::testNestedImports fail
0088     CXCursor tuCursor = clang_getTranslationUnitCursor(tu);
0089     clang_visitChildren(tuCursor, &visitCursor, &imports);
0090 
0091     return imports;
0092 }
0093 
0094 bool importLocationLessThan(const Import& lhs, const Import& rhs)
0095 {
0096     return lhs.location.line < rhs.location.line;
0097 }
0098 
0099 ReferencedTopDUContext ClangHelpers::buildDUChain(CXFile file, const Imports& imports, const ParseSession& session,
0100                                                   TopDUContext::Features features, IncludeFileContexts& includedFiles,
0101                                                   const UnsavedRevisions& unsavedRevisions,
0102                                                   const KDevelop::IndexedString& parseDocument, ClangIndex* index,
0103                                                   const std::function<bool()>& abortFunction)
0104 {
0105     if (includedFiles.contains(file)) {
0106         return {};
0107     }
0108 
0109     if (abortFunction && abortFunction()) {
0110         return {};
0111     }
0112 
0113     // prevent recursion
0114     includedFiles.insert(file, {});
0115 
0116     // ensure DUChain for imports are built properly, and in correct order
0117     QList<Import> sortedImports = imports.values(file);
0118     std::sort(sortedImports.begin(), sortedImports.end(), importLocationLessThan);
0119 
0120     for (const auto& import : qAsConst(sortedImports)) {
0121         buildDUChain(import.file, imports, session, features, includedFiles, unsavedRevisions, parseDocument, index,
0122                      abortFunction);
0123     }
0124 
0125     const QFileInfo pathInfo(ClangString(clang_getFileName(file)).toString());
0126     const IndexedString path(pathInfo.canonicalFilePath());
0127     if (path.isEmpty()) {
0128         // may happen when the file gets removed before the job is run
0129         return {};
0130     }
0131 
0132     const auto& environment = session.environment();
0133 
0134     bool update = false;
0135     UrlParseLock urlLock(path);
0136     ReferencedTopDUContext context;
0137     {
0138         DUChainWriteLocker lock;
0139         context = DUChain::self()->chainForDocument(path, &environment);
0140         if (!context) {
0141             context = ::createTopContext(path, environment);
0142         } else {
0143             update = true;
0144         }
0145 
0146         includedFiles.insert(file, context);
0147 
0148         auto envFile = dynamic_cast<ClangParsingEnvironmentFile*>(context->parsingEnvironmentFile().data());
0149         if (!envFile) {
0150             qCWarning(KDEV_CLANG) << "no suitable environment file for context" << file
0151                                   << context->parsingEnvironmentFile();
0152             return context;
0153         }
0154 
0155         if (update) {
0156             /*
0157              * The features of the parse request are meant for the parseDocument.
0158              * We don't want to apply ForceUpdate to all imports,
0159              * except when ForceUpdateRecursive is set!
0160              */
0161             const auto pathFeatures = [features, path, parseDocument]() {
0162                 if (path == parseDocument || features.testFlag(TopDUContext::ForceUpdateRecursive)) {
0163                     return features;
0164                 }
0165                 auto ret = features;
0166                 return ret.setFlag(TopDUContext::ForceUpdate, false);
0167             };
0168             if (!envFile->needsUpdate(&environment) && envFile->featuresSatisfied(pathFeatures())) {
0169                 return context;
0170             }
0171 
0172             // TODO: don't attempt to update if this environment is worse quality than the outdated one
0173             if (index && envFile->environmentQuality() < environment.quality()) {
0174                 index->pinTranslationUnitForUrl(environment.translationUnitUrl(), path);
0175             }
0176             envFile->setEnvironment(environment);
0177 
0178             envFile->clearModificationRevisions();
0179             context->clearImportedParentContexts();
0180         }
0181         context->setFeatures(features);
0182 
0183         for (const auto& import : qAsConst(sortedImports)) {
0184             auto ctx = includedFiles.value(import.file);
0185             if (!ctx) {
0186                 // happens for cyclic imports
0187                 continue;
0188             }
0189             context->addImportedParentContext(ctx, import.location);
0190             envFile->addModificationRevisions(ctx->parsingEnvironmentFile()->allModificationRevisions());
0191         }
0192         context->updateImportsCache();
0193 
0194         // prefer the editor modification revision, instead of the on-disk revision
0195         auto it = unsavedRevisions.find(path);
0196         if (it == unsavedRevisions.end()) {
0197             envFile->setModificationRevision(ModificationRevision::revisionForFile(path));
0198         } else {
0199             envFile->setModificationRevision(*it);
0200         }
0201     }
0202 
0203     const auto problems = session.problemsForFile(file);
0204     {
0205         DUChainWriteLocker lock;
0206         context->setProblems(problems);
0207     }
0208 
0209     Builder::visit(session.unit(), file, includedFiles, update);
0210 
0211     DUChain::self()->emitUpdateReady(path, context);
0212 
0213     return context;
0214 }
0215 
0216 DeclarationPointer ClangHelpers::findDeclaration(CXSourceLocation location, const QualifiedIdentifier& id, const ReferencedTopDUContext& top)
0217 {
0218     if (!top) {
0219         // may happen for cyclic includes
0220         return {};
0221     }
0222 
0223     auto cursor = CursorInRevision(ClangLocation(location));
0224     DUChainReadLocker lock;
0225 
0226     if (!id.isEmpty()) {
0227         const auto& decls = top->findDeclarations(id);
0228         for (Declaration* decl : decls) {
0229             if (decl->range().contains(cursor) ||
0230                 (decl->range().isEmpty() && decl->range().start == cursor))
0231             {
0232                 return DeclarationPointer(decl);
0233             }
0234         }
0235     }
0236 
0237     // there was no match based on the IDs, try the classical
0238     // range based search (very slow)
0239 
0240     Q_ASSERT(top);
0241     if (DUContext *local = top->findContextAt(cursor)) {
0242         if (local->owner() && local->owner()->range().contains(cursor)) {
0243            return DeclarationPointer(local->owner());
0244         }
0245         return DeclarationPointer(local->findDeclarationAt(cursor));
0246     }
0247     return {};
0248 }
0249 
0250 DeclarationPointer ClangHelpers::findDeclaration(CXCursor cursor, const IncludeFileContexts& includes)
0251 {
0252     auto location = clang_getCursorLocation(cursor);
0253     CXFile file = nullptr;
0254     clang_getFileLocation(location, &file, nullptr, nullptr, nullptr);
0255     if (!file) {
0256         return {};
0257     }
0258 
0259     // build a qualified identifier by following the chain of semantic parents
0260     QList<Identifier> ids;
0261     CXCursor currentCursor = cursor;
0262     while (currentCursor.kind != CXCursor_TranslationUnit &&
0263            currentCursor.kind != CXCursor_InvalidFile)
0264     {
0265         ids << Identifier(ClangString(clang_getCursorSpelling(currentCursor)).toString());
0266         currentCursor = clang_getCursorSemanticParent(currentCursor);
0267     }
0268     QualifiedIdentifier qid;
0269     for (int i = ids.size()-1; i >= 0; --i)
0270     {
0271         qid.push(ids[i]);
0272     }
0273 
0274     return findDeclaration(location, qid, includes.value(file));
0275 }
0276 
0277 DeclarationPointer ClangHelpers::findDeclaration(CXType type, const IncludeFileContexts& includes)
0278 {
0279     CXCursor cursor = clang_getTypeDeclaration(type);
0280     return findDeclaration(cursor, includes);
0281 }
0282 
0283 DeclarationPointer ClangHelpers::findForwardDeclaration(CXType type, DUContext* context, CXCursor cursor)
0284 {
0285     if(type.kind != CXType_Record && type.kind != CXType_ObjCInterface && type.kind != CXType_ObjCClass){
0286         return {};
0287     }
0288 
0289     auto qualifiedIdentifier = QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString());
0290 
0291     DUChainReadLocker lock;
0292     const auto decls = context->findDeclarations(qualifiedIdentifier,
0293         CursorInRevision(ClangLocation(clang_getCursorLocation(cursor)))
0294     );
0295 
0296     for (auto decl : decls) {
0297         if (decl->isForwardDeclaration()) {
0298             return DeclarationPointer(decl);
0299         }
0300     }
0301     return {};
0302 }
0303 
0304 RangeInRevision ClangHelpers::cursorSpellingNameRange(CXCursor cursor, const Identifier& id)
0305 {
0306     auto range = ClangRange(clang_Cursor_getSpellingNameRange(cursor, 0, 0)).toRangeInRevision();
0307 #if CINDEX_VERSION_MINOR < 29
0308     auto kind = clang_getCursorKind(cursor);
0309     // Clang used to report invalid ranges for destructors and methods like 'operator='
0310     if (kind == CXCursor_Destructor || kind == CXCursor_CXXMethod) {
0311         range.end.column = range.start.column + id.toString().length();
0312     }
0313 #endif
0314     Q_UNUSED(id);
0315     return range;
0316 }
0317 
0318 QStringList ClangHelpers::headerExtensions()
0319 {
0320     static const QStringList headerExtensions = {
0321             QStringLiteral("h"),
0322             QStringLiteral("H"),
0323             QStringLiteral("hh"),
0324             QStringLiteral("hxx"),
0325             QStringLiteral("hpp"),
0326             QStringLiteral("tlh"),
0327             QStringLiteral("cuh"),
0328             QStringLiteral("h++"),
0329     };
0330     return headerExtensions;
0331 }
0332 
0333 QStringList ClangHelpers::sourceExtensions()
0334 {
0335     static const QStringList sourceExtensions = {
0336         QStringLiteral("c"),
0337         QStringLiteral("cc"),
0338         QStringLiteral("cpp"),
0339         QStringLiteral("c++"),
0340         QStringLiteral("cxx"),
0341         QStringLiteral("C"),
0342         QStringLiteral("cu"),
0343         QStringLiteral("m"),
0344         QStringLiteral("mm"),
0345         QStringLiteral("M"),
0346         QStringLiteral("inl"),
0347         QStringLiteral("_impl.h"),
0348     };
0349     return sourceExtensions;
0350 }
0351 
0352 bool ClangHelpers::isSource(const QString& path)
0353 {
0354     const auto& extensions = sourceExtensions();
0355     return std::any_of(extensions.constBegin(), extensions.constEnd(),
0356                        [&](const QString& ext) { return path.endsWith(ext); });
0357 }
0358 
0359 bool ClangHelpers::isHeader(const QString& path)
0360 {
0361     const auto& extensions = headerExtensions();
0362     return std::any_of(extensions.constBegin(), extensions.constEnd(),
0363                        [&](const QString& ext) { return path.endsWith(ext); });
0364 }
0365 
0366 QString ClangHelpers::clangVersion()
0367 {
0368     static const auto clangVersion = []() -> QString {
0369         // NOTE: The apidocs for clang_getClangVersion() clearly state it shouldn't be used for parsing
0370         // but there's no other way to retrieve the Clang version at runtime at this point...
0371         const ClangString version(clang_getClangVersion());
0372         clangDebug() << "Full Clang version:" << version;
0373 
0374         // samples:
0375         //   clang version 6.0.1 (trunk 321709) (git@github.com:llvm-mirror/llvm.git 5136df4d089a086b70d452160ad5451861269498)
0376         //   clang version 7.0.0-svn341916-1~exp1~20180911115939.26 (branches/release_70)
0377         //   Ubuntu clang version 11.0.0-2
0378         QRegularExpression re(QStringLiteral("clang version (\\d+\\.\\d+\\.\\d+)"));
0379         const auto match = re.match(version.toString());
0380         if (!match.hasMatch())
0381             return {};
0382 
0383         return match.captured(1); // return e.g. 7.0.0
0384     }();
0385     return clangVersion;
0386 }
0387 
0388 static QString majorClangVersion()
0389 {
0390     const QString version = ClangHelpers::clangVersion();
0391     return version.left(version.indexOf(QLatin1Char{'.'}));
0392 }
0393 
0394 bool ClangHelpers::isValidClangBuiltingIncludePath(const QString& path)
0395 {
0396     return QFile::exists(path + QLatin1String("/cpuid.h"));
0397 }
0398 
0399 QString ClangHelpers::clangBuiltinIncludePath()
0400 {
0401     // use a lambda to store the result in a static variable which can be
0402     // returned without recomputing the string on subsequent calls.
0403     static const auto dir = []() -> QString {
0404         auto dir = QString::fromUtf8(qgetenv("KDEV_CLANG_BUILTIN_DIR"));
0405         if (!dir.isEmpty() && isValidClangBuiltingIncludePath(dir)) {
0406             clangDebug() << "Using dir from $KDEV_CLANG_BUILTIN_DIR:" << dir;
0407             return dir;
0408         }
0409 
0410         // Since https://github.com/llvm/llvm-project/commit/e1b88c8a09be25b86b13f98755a9bd744b4dbf14
0411         // Clang's resource directory includes only the major version.
0412         const QString majorVersion = majorClangVersion();
0413         const QString versionSubdir = majorVersion.toInt() >= 16 ? majorVersion : clangVersion();
0414 
0415 #ifdef Q_OS_WIN32
0416         // attempt to use the bundled copy on Windows
0417         dir = QDir::cleanPath(
0418             QStringLiteral("%1/../lib/clang/%2/include").arg(QCoreApplication::applicationDirPath(), versionSubdir));
0419         if (isValidClangBuiltingIncludePath(dir)) {
0420             clangDebug() << "Using builtin dir:" << dir;
0421             return dir;
0422         }
0423 #elif defined(Q_OS_UNIX)
0424         // a clang version upgrade since we were last built can
0425         // cause problems if the "clang/$fullversion/include" path component
0426         // changed. Try to generate the correct builtin_dir for the current
0427         // major.minor.patchlevel version: pop the last 2 components then
0428         // chdir through with the updated version directory.
0429         dir = QDir::cleanPath(QStringLiteral(KDEV_CLANG_BUILTIN_DIR "/../../%1/include").arg(versionSubdir));
0430         if (isValidClangBuiltingIncludePath(dir)) {
0431             clangDebug() << "Using builtin dir:" << dir;
0432             return dir;
0433         }
0434 #endif
0435 
0436 #if HAVE_DLFCN
0437         // maybe the location of clang changed, try to use the library path instead
0438         // we find it by pass any symbol in libclang to dladdr
0439         Dl_info info;
0440         if (dladdr(reinterpret_cast<void*>(&clang_getClangVersion), &info)) {
0441             // "../lib/" because libclang may be in lib64/ while includes are in lib/
0442             for (auto pathTemplate :
0443                  {QLatin1String{"%1/../clang/%2/include"}, QLatin1String{"%1/../../lib/clang/%2/include"}}) {
0444                 dir = QDir::cleanPath(pathTemplate.arg(QString::fromUtf8(info.dli_fname), versionSubdir));
0445                 if (isValidClangBuiltingIncludePath(dir)) {
0446                     clangDebug() << "Using builtin dir:" << dir;
0447                     return dir;
0448                 }
0449             }
0450         }
0451 #endif
0452 
0453         clangDebug() << "Using builtin dir:" << KDEV_CLANG_BUILTIN_DIR;
0454         return QString::fromUtf8(KDEV_CLANG_BUILTIN_DIR);
0455     }();
0456     return dir;
0457 }