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 }