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

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 "test_clangutils.h"
0008 
0009 #include "../util/clangutils.h"
0010 #include "../util/clangtypes.h"
0011 #include "../util/clangdebug.h"
0012 
0013 #include <language/editor/documentrange.h>
0014 #include <tests/testcore.h>
0015 #include <tests/testhelpers.h>
0016 #include <tests/autotestshell.h>
0017 
0018 #include <clang-c/Index.h>
0019 
0020 #include <QTemporaryFile>
0021 
0022 #include <QDebug>
0023 #include <QTest>
0024 
0025 #include <memory>
0026 
0027 QTEST_MAIN(TestClangUtils)
0028 
0029 using namespace KDevelop;
0030 
0031 namespace {
0032 
0033 struct CursorCollectorVisitor
0034 {
0035     QVector<CXCursor> cursors;
0036 
0037     static CXChildVisitResult visit(CXCursor cursor, CXCursor /*parent*/, CXClientData d)
0038     {
0039         auto* thisPtr = static_cast<CursorCollectorVisitor*>(d);
0040         thisPtr->cursors << cursor;
0041 
0042         return CXChildVisit_Recurse;
0043     };
0044 };
0045 
0046 CXSourceRange toCXRange(CXTranslationUnit unit, const DocumentRange& range)
0047 {
0048     auto file = clang_getFile(unit, QString::fromUtf8(range.document.c_str(), range.document.length()).toUtf8());
0049     auto begin = clang_getLocation(unit, file, range.start().line()+1, range.start().column()+1);
0050     auto end = clang_getLocation(unit, file, range.end().line()+1, range.end().column()+1);
0051     return clang_getRange(begin, end);
0052 }
0053 
0054 struct DisposeTranslationUnit {
0055     void operator()(CXTranslationUnit unit) { clang_disposeTranslationUnit(unit); }
0056 };
0057 using TranslationUnit = std::unique_ptr<CXTranslationUnitImpl, DisposeTranslationUnit>;
0058 
0059 TranslationUnit parse(const QByteArray& code, QString* fileName = nullptr)
0060 {
0061     QTemporaryFile tempFile;
0062     QVERIFY_RETURN(tempFile.open(), {});
0063     tempFile.write(code);
0064     tempFile.flush();
0065 
0066     if (fileName) {
0067         *fileName = tempFile.fileName();
0068     }
0069 
0070     std::unique_ptr<void, void(*)(CXIndex)> index(clang_createIndex(1, 1), clang_disposeIndex);
0071     const QVector<const char*> args = {"-std=c++11", "-xc++", "-Wall", "-nostdinc", "-nostdinc++"};
0072     auto unit = TranslationUnit(clang_parseTranslationUnit(index.get(), qPrintable(tempFile.fileName()), args.data(),
0073                                                            args.size(), nullptr, 0, CXTranslationUnit_None));
0074     QVERIFY_RETURN(unit, {});
0075     return unit;
0076 }
0077 
0078 TranslationUnit runVisitor(const QByteArray& code, CXCursorVisitor visitor, CXClientData data)
0079 {
0080     auto unit = parse(code);
0081     const auto startCursor = clang_getTranslationUnitCursor(unit.get());
0082     QVERIFY_RETURN(!clang_Cursor_isNull(startCursor), {});
0083     QVERIFY_RETURN(clang_visitChildren(startCursor, visitor, data) == 0, {});
0084     return unit;
0085 }
0086 
0087 template <typename Visitor>
0088 TranslationUnit runVisitor(const QByteArray& code, Visitor& visitor)
0089 {
0090     return runVisitor(code, &Visitor::visit, &visitor);
0091 }
0092 }
0093 
0094 void TestClangUtils::initTestCase()
0095 {
0096     QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n"));
0097     AutoTestShell::init();
0098     TestCore::initialize(Core::NoUi);
0099 }
0100 
0101 void TestClangUtils::cleanupTestCase()
0102 {
0103     TestCore::shutdown();
0104 }
0105 
0106 void TestClangUtils::testGetScope()
0107 {
0108     QFETCH(QByteArray, code);
0109     QFETCH(int, cursorIndex);
0110     QFETCH(QString, expectedScope);
0111 
0112     CursorCollectorVisitor visitor;
0113     auto unit = runVisitor(code, visitor);
0114     QVERIFY(cursorIndex < visitor.cursors.size());
0115     const auto cursor = visitor.cursors[cursorIndex];
0116     clangDebug() << "Found decl:" << clang_getCursorSpelling(cursor) << "| range:" << ClangRange(clang_getCursorExtent(cursor)).toRange();
0117     const QString scope = ClangUtils::getScope(cursor);
0118     QCOMPARE(scope, expectedScope);
0119 
0120     QVERIFY(!visitor.cursors.isEmpty());
0121 }
0122 
0123 void TestClangUtils::testGetScope_data()
0124 {
0125     QTest::addColumn<QByteArray>("code");
0126     QTest::addColumn<int>("cursorIndex");
0127     QTest::addColumn<QString>("expectedScope");
0128 
0129     QTest::newRow("func-decl-inside-ns")
0130         << QByteArray("namespace ns1 { void foo(); } ns1::foo() {}")
0131         << 2
0132         << "ns1";
0133     QTest::newRow("fwd-decl-inside-ns")
0134         << QByteArray("namespace ns1 { struct foo; } struct ns1::foo {};")
0135         << 2
0136         << "ns1";
0137     QTest::newRow("fwd-decl-inside-ns-inside-ns")
0138         << QByteArray("namespace ns1 { namespace ns2 { struct foo; } } struct ns1::ns2::foo {};")
0139         << 3
0140         << "ns1::ns2";
0141     QTest::newRow("fwd-decl-inside-ns-inside-ns")
0142         << QByteArray("namespace ns1 { namespace ns2 { struct foo; } } struct ns1::ns2::foo {};")
0143         << 3
0144         << "ns1::ns2";
0145     QTest::newRow("using-namespace")
0146         << QByteArray("namespace ns1 { struct klass { struct foo; }; } using namespace ns1; struct klass::foo {};")
0147         << 5
0148         << "ns1::klass";
0149     QTest::newRow("fwd-decl-def-inside-namespace")
0150         << QByteArray("namespace ns1 { struct klass { struct foo; }; } namespace ns1 { struct klass::foo {}; }")
0151         << 4
0152         << "klass";
0153 }
0154 
0155 void TestClangUtils::testTemplateArgumentTypes()
0156 {
0157     QFETCH(QByteArray, code);
0158     QFETCH(int, cursorIndex);
0159     QFETCH(QStringList, expectedTypes);
0160 
0161     CursorCollectorVisitor visitor;
0162     auto unit = runVisitor(code, visitor);
0163     QVERIFY(cursorIndex < visitor.cursors.size());
0164     const auto cursor = visitor.cursors[cursorIndex];
0165     const QStringList types = ClangUtils::templateArgumentTypes(cursor);
0166 
0167     QCOMPARE(types, expectedTypes);
0168 }
0169 
0170 void TestClangUtils::testTemplateArgumentTypes_data()
0171 {
0172     QTest::addColumn<QByteArray>("code");
0173     QTest::addColumn<int>("cursorIndex");
0174     QTest::addColumn<QStringList>("expectedTypes");
0175 
0176     QTest::newRow("template-spec")
0177         << QByteArray(
0178             "template<typename T>\n"
0179             "struct is_void\n"
0180             "{ };\n"
0181             "template<>\n"
0182             "struct is_void<void>\n"
0183             "{ };\n")
0184         << 2
0185         << QStringList({"void"});
0186 
0187     // Partially specialize one
0188     QTest::newRow("template-partial-spec")
0189         << QByteArray(
0190             "template<typename T, unsigned N>\n"
0191             "struct is_void\n"
0192             "{ };\n"
0193             "template<typename T>\n"
0194             "struct is_void<T, 3>\n"
0195             "{ };\n")
0196         << 3
0197         << QStringList({"type-parameter-0-0", ""});
0198 
0199     // Fully specialize 3 args
0200     QTest::newRow("template-full-spec")
0201         << QByteArray(
0202             "template<typename T, typename R, unsigned N>\n"
0203             "struct is_void\n"
0204             "{ };\n"
0205             "template<>\n"
0206             "struct is_void<unsigned, void, 5>\n"
0207             "{ };\n")
0208         << 4
0209         << QStringList({"unsigned int", "void", ""});
0210 }
0211 
0212 void TestClangUtils::testGetRawContents()
0213 {
0214     QFETCH(QByteArray, code);
0215     QFETCH(KTextEditor::Range, range);
0216     QFETCH(QString, expectedContents);
0217 
0218     QString fileName;
0219     auto unit = parse(code, &fileName);
0220 
0221     DocumentRange documentRange{IndexedString(fileName), range};
0222     auto cxRange = toCXRange(unit.get(), documentRange);
0223     const QString contents = ClangUtils::getRawContents(unit.get(), cxRange);
0224     QCOMPARE(contents, expectedContents);
0225 }
0226 
0227 void TestClangUtils::testGetRawContents_data()
0228 {
0229     QTest::addColumn<QByteArray>("code");
0230     QTest::addColumn<KTextEditor::Range>("range");
0231     QTest::addColumn<QString>("expectedContents");
0232 
0233     QTest::newRow("complete")
0234         << QByteArray("void; int foo = 42; void;")
0235         << KTextEditor::Range(0, 6, 0, 19)
0236         << "int foo = 42;";
0237     QTest::newRow("cut-off-at-start")
0238         << QByteArray("void; int foo = 42; void;")
0239         << KTextEditor::Range(0, 7, 0, 19)
0240         << "nt foo = 42;";
0241     QTest::newRow("cut-off-at-end")
0242         << QByteArray("void; int foo = 42; void;")
0243         << KTextEditor::Range(0, 6, 0, 17)
0244         << "int foo = 4";
0245     QTest::newRow("whitespace")
0246         << QByteArray("void; int         ; void;")
0247         << KTextEditor::Range(0, 5, 0, 18)
0248         << " int         ";
0249     QTest::newRow("empty-range")
0250         << QByteArray("void;")
0251         << KTextEditor::Range(0, 0, 0, 0)
0252         << "";
0253 }
0254 
0255 void TestClangUtils::testRangeForIncludePathSpec()
0256 {
0257     QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include <vector>"), KTextEditor::Range(0, 10, 0, 16));
0258     QCOMPARE(ClangUtils::rangeForIncludePathSpec("# include <vector>"), KTextEditor::Range(0, 11, 0, 17));
0259     QCOMPARE(ClangUtils::rangeForIncludePathSpec("#\t include <vector>"), KTextEditor::Range(0, 12, 0, 18));
0260     QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include <foo\\\".h>"), KTextEditor::Range(0, 10, 0, 17));
0261     QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include \"foo\\\".h\""), KTextEditor::Range(0, 10, 0, 17));
0262     QCOMPARE(ClangUtils::rangeForIncludePathSpec("#include \"foo<>.h\""), KTextEditor::Range(0, 10, 0, 17));
0263 }
0264 
0265 void TestClangUtils::testGetCursorSignature()
0266 {
0267     QFETCH(QByteArray, code);
0268     QFETCH(QString, expectedSignature);
0269 
0270     QString fileName;
0271     auto unit = parse(code, &fileName);
0272     CXCursor functionCursor = clang_getNullCursor();
0273     const auto startCursor = clang_getTranslationUnitCursor(unit.get());
0274     ClangUtils::visitChildren(startCursor, [&](CXCursor cursor, CXCursor){
0275         switch (clang_getCursorKind(cursor))
0276         {
0277             case CXCursor_FunctionDecl:
0278             case CXCursor_CXXMethod:
0279             case CXCursor_FunctionTemplate:
0280                 functionCursor = cursor;
0281                 return CXChildVisit_Break;
0282             default:
0283                 return CXChildVisit_Recurse;
0284         }
0285     });
0286     QVERIFY2(!clang_Cursor_isNull(functionCursor), "function not found");
0287     auto scope = ClangUtils::getScope(functionCursor, startCursor);
0288     auto signature = ClangUtils::getCursorSignature(functionCursor, scope, {});
0289     QCOMPARE(signature, expectedSignature);
0290 }
0291 
0292 void TestClangUtils::testGetCursorSignature_data()
0293 {
0294     QTest::addColumn<QByteArray>("code");
0295     QTest::addColumn<QString>("expectedSignature");
0296 
0297     QTest::newRow("global-less")
0298         << QByteArray("class klass {}; bool operator < (const klass&, const klass&);")
0299         << "bool operator<(const klass&, const klass&)";
0300     QTest::newRow("member-less")
0301         << QByteArray("class klass { bool operator < (const klass&); };")
0302         << "bool klass::operator<(const klass&)";
0303     QTest::newRow("template-member-less")
0304         << QByteArray("class klass { template<typename T> bool operator < (const T&); };")
0305         << "bool klass::operator<(const T&)";
0306 }
0307 
0308 #include "moc_test_clangutils.cpp"