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 }