File indexing completed on 2024-05-12 04:39:19
0001 /* 0002 SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "clangutils.h" 0008 0009 #include "../util/clangdebug.h" 0010 #include "../util/clangtypes.h" 0011 #include "../duchain/cursorkindtraits.h" 0012 #include "../duchain/documentfinderhelpers.h" 0013 0014 #include <language/duchain/stringhelpers.h> 0015 #include <interfaces/icore.h> 0016 #include <interfaces/idocumentcontroller.h> 0017 0018 #include <clang-c/Index.h> 0019 0020 #include <QTextStream> 0021 #include <QRegularExpression> 0022 0023 #include <memory> 0024 #include <functional> 0025 0026 using namespace KDevelop; 0027 0028 CXCursor ClangUtils::getCXCursor(int line, int column, const CXTranslationUnit& unit, const CXFile& file) 0029 { 0030 if (!file) { 0031 clangDebug() << "getCXCursor couldn't find file: " << clang_getFileName(file); 0032 return clang_getNullCursor(); 0033 } 0034 0035 CXSourceLocation location = clang_getLocation(unit, file, line + 1, column + 1); 0036 0037 if (clang_equalLocations(clang_getNullLocation(), location)) { 0038 clangDebug() << "getCXCursor given invalid position " << line << ", " << column 0039 << " for file " << clang_getFileName(file); 0040 return clang_getNullCursor(); 0041 } 0042 0043 return clang_getCursor(unit, location); 0044 } 0045 0046 QVector<UnsavedFile> ClangUtils::unsavedFiles() 0047 { 0048 QVector<UnsavedFile> ret; 0049 const auto documents = ICore::self()->documentController()->openDocuments(); 0050 for (auto* document : documents) { 0051 auto textDocument = document->textDocument(); 0052 // TODO: Introduce a cache so we don't have to re-read all the open documents 0053 // which were not changed since the last run 0054 if (!textDocument || !textDocument->url().isLocalFile() || !textDocument->isModified()) { 0055 continue; 0056 } 0057 if (!DocumentFinderHelpers::mimeTypesList().contains(textDocument->mimeType())) { 0058 continue; 0059 } 0060 ret << UnsavedFile(textDocument->url().toLocalFile(), 0061 textDocument->textLines(textDocument->documentRange())); 0062 } 0063 return ret; 0064 } 0065 0066 KTextEditor::Range ClangUtils::rangeForIncludePathSpec(const QString& line, const KTextEditor::Range& originalRange) 0067 { 0068 static const QRegularExpression pattern(QStringLiteral("^\\s*(#\\s*include|#\\s*import)")); 0069 if (!line.contains(pattern)) { 0070 return KTextEditor::Range::invalid(); 0071 } 0072 0073 KTextEditor::Range range = originalRange; 0074 int pos = 0; 0075 char term_char = 0; 0076 for (; pos < line.size(); ++pos) { 0077 if (line[pos] == QLatin1Char('"') || line[pos] == QLatin1Char('<')) { 0078 term_char = line[pos] == QLatin1Char('"') ? '"' : '>'; 0079 range.setStart({ range.start().line(), ++pos }); 0080 break; 0081 } 0082 } 0083 0084 for (; pos < line.size(); ++pos) { 0085 if (line[pos] == QLatin1Char('\\')) { 0086 ++pos; 0087 continue; 0088 } else if(line[pos] == QLatin1Char(term_char)) { 0089 range.setEnd({ range.start().line(), pos }); 0090 break; 0091 } 0092 } 0093 0094 return range; 0095 } 0096 0097 namespace { 0098 0099 struct FunctionInfo { 0100 KTextEditor::Range range; 0101 QString fileName; 0102 CXTranslationUnit unit; 0103 QStringList stringParts; 0104 }; 0105 0106 CXChildVisitResult paramVisitor(CXCursor cursor, CXCursor /*parent*/, CXClientData data) 0107 { 0108 //Ignore the type of the parameter 0109 CXCursorKind kind = clang_getCursorKind(cursor); 0110 if (kind == CXCursor_TypeRef || kind == CXCursor_TemplateRef || kind == CXCursor_NamespaceRef) { 0111 return CXChildVisit_Continue; 0112 } 0113 0114 auto *info = static_cast<FunctionInfo*>(data); 0115 ClangRange range(clang_getCursorExtent(cursor)); 0116 0117 CXFile file; 0118 clang_getFileLocation(clang_getCursorLocation(cursor),&file,nullptr,nullptr,nullptr); 0119 if (!file) { 0120 clangDebug() << "Couldn't find file associated with default parameter cursor!"; 0121 //We keep going, because getting an error because we accidentally duplicated 0122 //a default parameter is better than deleting a default parameter 0123 } 0124 QString fileName = ClangString(clang_getFileName(file)).toString(); 0125 0126 //Clang doesn't make a distinction between the default arguments being in 0127 //the declaration or definition, and the default arguments don't have lexical 0128 //parents. So this range check is the only thing that really works. 0129 if ((info->fileName.isEmpty() || fileName == info->fileName) && info->range.contains(range.toRange())) { 0130 info->stringParts.append(ClangUtils::getRawContents(info->unit, range.range())); 0131 } 0132 return CXChildVisit_Continue; 0133 } 0134 0135 } 0136 0137 QVector<QString> ClangUtils::getDefaultArguments(CXCursor cursor, DefaultArgumentsMode mode) 0138 { 0139 if (!CursorKindTraits::isFunction(clang_getCursorKind(cursor))) { 0140 return QVector<QString>(); 0141 } 0142 0143 int numArgs = clang_Cursor_getNumArguments(cursor); 0144 QVector<QString> arguments(mode == FixedSize ? numArgs : 0); 0145 QString fileName; 0146 CXFile file; 0147 clang_getFileLocation(clang_getCursorLocation(cursor),&file,nullptr,nullptr,nullptr); 0148 if (!file) { 0149 clangDebug() << "Couldn't find file associated with default parameter cursor!"; 0150 //The empty string serves as a wildcard string, because it's better to 0151 //duplicate a default parameter than delete one 0152 } else { 0153 fileName = ClangString(clang_getFileName(file)).toString(); 0154 } 0155 0156 FunctionInfo info{ClangRange(clang_getCursorExtent(cursor)).toRange(), fileName, 0157 clang_Cursor_getTranslationUnit(cursor), QStringList()}; 0158 0159 for (int i = 0; i < numArgs; i++) { 0160 CXCursor arg = clang_Cursor_getArgument(cursor, i); 0161 info.stringParts.clear(); 0162 clang_visitChildren(arg, paramVisitor, &info); 0163 0164 const auto hasDefault = !info.stringParts.isEmpty(); 0165 0166 //Clang includes the equal sign sometimes, but not other times. 0167 if (!info.stringParts.isEmpty() && info.stringParts.first() == QLatin1String("=")) { 0168 info.stringParts.removeFirst(); 0169 } 0170 //Clang seems to include the , or ) at the end of the param, so delete that 0171 if (!info.stringParts.isEmpty() && 0172 ((info.stringParts.last() == QLatin1String(",")) || 0173 (info.stringParts.last() == QLatin1String(")") && 0174 // assuming otherwise matching "(" & ")" tokens 0175 info.stringParts.count(QStringLiteral("(")) != info.stringParts.count(QStringLiteral(")"))))) { 0176 info.stringParts.removeLast(); 0177 } 0178 0179 const QString result = info.stringParts.join(QString()); 0180 if (mode == FixedSize) { 0181 arguments.replace(i, result); 0182 } else if (!result.isEmpty()) { 0183 arguments << result; 0184 } else if (hasDefault) { 0185 // no string obtained, probably due to a parse error... 0186 // we have to include some argument, otherwise it's even more confusing to our users 0187 // furthermore, we cannot even do getRawContents on the arg's cursor, as it's cursor 0188 // extent stops at the first error... 0189 arguments << i18n("<parse error>"); 0190 } 0191 } 0192 return arguments; 0193 } 0194 0195 bool ClangUtils::isScopeKind(CXCursorKind kind) 0196 { 0197 return kind == CXCursor_Namespace || kind == CXCursor_StructDecl || 0198 kind == CXCursor_UnionDecl || kind == CXCursor_ClassDecl || 0199 kind == CXCursor_ClassTemplate || kind == CXCursor_ClassTemplatePartialSpecialization; 0200 } 0201 0202 QString ClangUtils::getScope(CXCursor cursor, CXCursor context) 0203 { 0204 QStringList scope; 0205 if (clang_Cursor_isNull(context)) { 0206 context = clang_getCursorLexicalParent(cursor); 0207 } 0208 context = clang_getCanonicalCursor(context); 0209 CXCursor search = clang_getCursorSemanticParent(cursor); 0210 while (isScopeKind(clang_getCursorKind(search)) && !clang_equalCursors(search, context)) { 0211 scope.prepend(ClangString(clang_getCursorDisplayName(search)).toString()); 0212 search = clang_getCursorSemanticParent(search); 0213 } 0214 return scope.join(QLatin1String("::")); 0215 } 0216 0217 QString ClangUtils::getCursorSignature(CXCursor cursor, const QString& scope, const QVector<QString>& defaultArgs) 0218 { 0219 CXCursorKind kind = clang_getCursorKind(cursor); 0220 //Get the return type 0221 QString ret; 0222 ret.reserve(128); 0223 QTextStream stream(&ret); 0224 if (kind != CXCursor_Constructor && kind != CXCursor_Destructor) { 0225 stream << ClangString(clang_getTypeSpelling(clang_getCursorResultType(cursor))).toString() 0226 << ' '; 0227 } 0228 0229 //Build the function name, with scope and parameters 0230 if (!scope.isEmpty()) { 0231 stream << scope << "::"; 0232 } 0233 0234 QString functionName = ClangString(clang_getCursorSpelling(cursor)).toString(); 0235 if (functionName.contains(QLatin1Char('<')) && !functionName.startsWith(QStringLiteral("operator<"))) { 0236 stream << functionName.leftRef(functionName.indexOf(QLatin1Char('<'))); 0237 } else { 0238 stream << functionName; 0239 } 0240 0241 //Add the parameters and such 0242 stream << '('; 0243 int numArgs ; 0244 QVector<CXCursor> args; 0245 0246 // SEE https://bugs.kde.org/show_bug.cgi?id=368544 0247 // clang_Cursor_getNumArguments returns -1 for FunctionTemplate 0248 // clang checks if cursor's Decl is ObjCMethodDecl or FunctionDecl 0249 // CXCursor_FunctionTemplate is neither of them instead it has a FunctionTemplateDecl 0250 // HACK Get function template arguments by visiting children 0251 if (kind == CXCursor_FunctionTemplate) { 0252 clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor /*parent*/, CXClientData data) { 0253 if (clang_getCursorKind(cursor) == CXCursor_ParmDecl) { 0254 (static_cast<QVector<CXCursor>*>(data))->push_back(cursor); 0255 } 0256 return CXChildVisit_Continue; 0257 }, &args); 0258 numArgs = args.size(); 0259 } else { 0260 numArgs = clang_Cursor_getNumArguments(cursor); 0261 args.reserve(numArgs); 0262 for (int i = 0; i < numArgs; i++) { 0263 CXCursor arg = clang_Cursor_getArgument(cursor, i); 0264 args.push_back(arg); 0265 } 0266 } 0267 0268 for (int i = 0; i < numArgs; i++) { 0269 CXCursor arg = args[i]; 0270 0271 //Clang formats pointer types as "t *x" and reference types as "t &x", while 0272 //KDevelop formats them as "t* x" and "t& x". Make that adjustment. 0273 const QString type = ClangString(clang_getTypeSpelling(clang_getCursorType(arg))).toString(); 0274 if (type.endsWith(QLatin1String(" *")) || type.endsWith(QLatin1String(" &"))) { 0275 stream << type.leftRef(type.length() - 2) << type.at(type.length() - 1); 0276 } else { 0277 stream << type; 0278 } 0279 0280 const QString id = ClangString(clang_getCursorDisplayName(arg)).toString(); 0281 if (!id.isEmpty()) { 0282 stream << ' ' << id; 0283 } 0284 0285 if (i < defaultArgs.count() && !defaultArgs.at(i).isEmpty()) { 0286 stream << " = " << defaultArgs.at(i); 0287 } 0288 0289 if (i < numArgs - 1) { 0290 stream << ", "; 0291 } 0292 } 0293 0294 if (clang_Cursor_isVariadic(cursor)) { 0295 if (numArgs > 0) { 0296 stream << ", "; 0297 } 0298 stream << "..."; 0299 } 0300 0301 stream << ')'; 0302 0303 if (clang_CXXMethod_isConst(cursor)) { 0304 stream << " const"; 0305 } 0306 0307 switch (clang_getCursorExceptionSpecificationType(cursor)) { 0308 case CXCursor_ExceptionSpecificationKind_DynamicNone: 0309 stream << " throw()"; 0310 break; 0311 case CXCursor_ExceptionSpecificationKind_BasicNoexcept: 0312 stream << " noexcept"; 0313 break; 0314 default: 0315 break; 0316 } 0317 0318 return ret; 0319 } 0320 0321 QStringList ClangUtils::templateArgumentTypes(CXCursor cursor) 0322 { 0323 CXType typeList = clang_getCursorType(cursor); 0324 int templateArgCount = clang_Type_getNumTemplateArguments(typeList); 0325 QStringList types; 0326 types.reserve(templateArgCount); 0327 for (int i = 0; i < templateArgCount; ++i) { 0328 ClangString clangString(clang_getTypeSpelling(clang_Type_getTemplateArgumentAsType(typeList, i))); 0329 types.append(clangString.toString()); 0330 } 0331 0332 return types; 0333 } 0334 0335 QString ClangUtils::getRawContents(CXTranslationUnit unit, CXSourceRange range) 0336 { 0337 const auto rangeStart = clang_getRangeStart(range); 0338 const auto rangeEnd = clang_getRangeEnd(range); 0339 CXFile rangeFile; 0340 unsigned int start, end; 0341 clang_getFileLocation(rangeStart, &rangeFile, nullptr, nullptr, &start); 0342 clang_getFileLocation(rangeEnd, nullptr, nullptr, nullptr, &end); 0343 0344 std::size_t fileSize; 0345 const char* fileBuffer = clang_getFileContents(unit, rangeFile, &fileSize); 0346 if (fileBuffer && start < fileSize && end <= fileSize && start < end) { 0347 return QString::fromUtf8(fileBuffer + start, end - start); 0348 } 0349 return QString(); 0350 } 0351 0352 bool ClangUtils::isExplicitlyDefaultedOrDeleted(CXCursor cursor) 0353 { 0354 if (clang_getCursorAvailability(cursor) == CXAvailability_NotAvailable) { 0355 return true; 0356 } 0357 0358 if (clang_CXXMethod_isDefaulted(cursor)) { 0359 return true; 0360 } 0361 return false; 0362 } 0363 0364 void ClangUtils::visitChildren(CXCursor parent, std::function<CXChildVisitResult(CXCursor, CXCursor)> visitor) 0365 { 0366 static constexpr CXCursorVisitor cVisitor = [](CXCursor cursor, CXCursor parent, CXClientData client_data) 0367 { 0368 return (*static_cast<std::function<CXChildVisitResult(CXCursor, CXCursor)>*>(client_data))(cursor, parent); 0369 }; 0370 clang_visitChildren(parent, cVisitor, &visitor); 0371 } 0372 0373 KDevelop::ClassFunctionFlags ClangUtils::specialAttributes(CXCursor cursor) 0374 { 0375 // check for our injected attributes to detect Qt signals and slots 0376 // see also the contents of wrappedQtHeaders/QtCore/qobjectdefs.h 0377 ClassFunctionFlags flags = {}; 0378 if (cursor.kind == CXCursor_CXXMethod) { 0379 clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor /*parent*/, CXClientData data) -> CXChildVisitResult { 0380 auto& flags = *static_cast<ClassFunctionFlags*>(data); 0381 switch (cursor.kind) { 0382 case CXCursor_AnnotateAttr: { 0383 ClangString attribute(clang_getCursorDisplayName(cursor)); 0384 if (attribute.c_str() == QByteArrayLiteral("qt_signal")) { 0385 flags |= FunctionSignalFlag; 0386 } else if (attribute.c_str() == QByteArrayLiteral("qt_slot")) { 0387 flags |= FunctionSlotFlag; 0388 } 0389 break; 0390 } 0391 case CXCursor_CXXFinalAttr: 0392 flags |= FinalFunctionFlag; 0393 break; 0394 default: 0395 break; 0396 } 0397 0398 return CXChildVisit_Break; 0399 }, &flags); 0400 } 0401 return flags; 0402 } 0403 0404 unsigned int ClangUtils::skipTopCommentBlock(CXTranslationUnit unit, CXFile file) 0405 { 0406 const auto fileRange = clang_getRange(clang_getLocation(unit, file, 1, 1), 0407 clang_getLocation(unit, file, std::numeric_limits<unsigned>::max(), 1)); 0408 const ClangTokens tokens (unit, fileRange); 0409 const auto nonCommentToken = std::find_if(tokens.begin(), tokens.end(), 0410 [&](CXToken token) { return clang_getTokenKind(token) != CXToken_Comment; }); 0411 0412 // explicitly handle this case, otherwise we skip the preceding whitespace 0413 if (nonCommentToken == tokens.begin()) { 0414 return 1; 0415 } 0416 0417 const auto location = (nonCommentToken != tokens.end()) ? clang_getTokenExtent(unit, *nonCommentToken) : fileRange; 0418 return KTextEditor::Cursor(ClangRange(location).end()).line() + 1; 0419 }