// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include #include #include #include #include #include #include #include #include //TESTED_COMPONENT=src/libs/cplusplus using namespace CPlusPlus; template QMultiMap<_T2, _T1> invert(const QMap<_T1, _T2> &m) { QMultiMap<_T2, _T1> i; typename QMap<_T1, _T2>::const_iterator it = m.constBegin(); for (; it != m.constEnd(); ++it) { i.insert(it.value(), it.key()); } return i; } class ClassSymbols: protected ASTVisitor, public QMap { public: ClassSymbols(TranslationUnit *translationUnit) : ASTVisitor(translationUnit) { } QMap asMap() const { return *this; } void operator()(AST *ast) { accept(ast); } protected: virtual bool visit(ClassSpecifierAST *ast) { Class *classSymbol = ast->symbol; Q_ASSERT(classSymbol != 0); insert(ast, classSymbol); return true; } }; class tst_Lookup: public QObject { Q_OBJECT private slots: void base_class_defined_1(); void document_functionAt_data(); void document_functionAt(); // Objective-C void simple_class_1(); void class_with_baseclass(); void class_with_protocol_with_protocol(); void iface_impl_scoping(); // template instantiation: void templates_1(); void templates_2(); void templates_3(); void templates_4(); void templates_5(); void minimalname_data(); void minimalname(); }; void tst_Lookup::base_class_defined_1() { Overview overview; const QByteArray source = "\n" "class base {};\n" "class derived: public base {};\n"; Document::Ptr doc = Document::create("base_class_defined_1"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); QCOMPARE(doc->globalSymbolCount(), 2); Snapshot snapshot; snapshot.insert(doc); Class *baseClass = doc->globalSymbolAt(0)->asClass(); QVERIFY(baseClass); Class *derivedClass = doc->globalSymbolAt(1)->asClass(); QVERIFY(derivedClass); const LookupContext ctx(doc, snapshot); ClassOrNamespace *klass = ctx.lookupType(derivedClass->baseClassAt(0)->name(), derivedClass->enclosingScope()); QVERIFY(klass != 0); QCOMPARE(klass->symbols().size(), 1); QCOMPARE(klass->symbols().first(), baseClass); TranslationUnit *unit = doc->translationUnit(); QVERIFY(unit != 0); TranslationUnitAST *ast = unit->ast()->asTranslationUnit(); QVERIFY(ast != 0); ClassSymbols classSymbols(unit); classSymbols(ast); QCOMPARE(classSymbols.size(), 2); const QMultiMap classToAST = invert(classSymbols.asMap()); QVERIFY(!classToAST.values(baseClass).isEmpty()); QVERIFY(!classToAST.values(derivedClass).isEmpty()); } void tst_Lookup::document_functionAt_data() { QTest::addColumn("source"); QTest::addColumn("line"); QTest::addColumn("column"); QTest::addColumn("expectedFunction"); QTest::addColumn("expectedOpeningDeclaratorParenthesisLine"); QTest::addColumn("expectedClosingBraceLine"); QByteArray source = "\n" "void Foo::Bar() {\n" // line 1 " \n" " for (int i=0; i < 10; ++i) {\n" // line 3 " \n" " }\n" // line 5 "}\n"; QString expectedFunction = QString::fromLatin1("Foo::Bar"); QTest::newRow("nonInline1") << source << 1 << 2 << QString() << -1 << -1; QTest::newRow("nonInline2") << source << 1 << 11 << expectedFunction << 1 << 6; QTest::newRow("nonInline3") << source << 2 << 2 << expectedFunction << 1 << 6; QTest::newRow("nonInline4") << source << 3 << 10 << expectedFunction << 1 << 6; QTest::newRow("nonInline5") << source << 4 << 3 << expectedFunction << 1 << 6; QTest::newRow("nonInline6") << source << 6 << 1 << expectedFunction << 1 << 6; source = "\n" "namespace N {\n" // line 1 "class C {\n" " void f()\n" // line 3 " {\n" " }\n" // line 5 "};\n" "}\n"; // line 7 expectedFunction = QString::fromLatin1("N::C::f"); QTest::newRow("inline1") << source << 1 << 2 << QString() << -1 << -1; QTest::newRow("inline2") << source << 2 << 10 << QString() << -1 << -1; QTest::newRow("inline2") << source << 3 << 10 << expectedFunction << 3 << 5; source = "\n" "void f(Helper helper = [](){})\n" // line 1 "{\n" "}\n"; // line 3 expectedFunction = QString::fromLatin1("f"); QTest::newRow("inlineWithLambdaArg1") << source << 2 << 1 << expectedFunction << 1 << 3; source = "\n" "int g_global = 456;\n" // line 1 "int foo()\n" // line 2 "{\n" " g_global = 89;\n" // line 4 " std::function cb = [](int) { return 1; };\n" " g_global = 222;\n" "}\n"; // line 7 expectedFunction = QString::fromLatin1("foo"); QTest::newRow("inlineWithLambda1") << source << 4 << 1 << expectedFunction << 2 << 7; QTest::newRow("inlineWithLambda2") << source << 6 << 1 << expectedFunction << 2 << 7; } void tst_Lookup::document_functionAt() { QFETCH(QByteArray, source); QFETCH(int, line); QFETCH(int, column); QFETCH(QString, expectedFunction); QFETCH(int, expectedOpeningDeclaratorParenthesisLine); QFETCH(int, expectedClosingBraceLine); Document::Ptr doc = Document::create("document_functionAt"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); int actualOpeningDeclaratorParenthesisLine = -1; int actualClosingBraceLine = -1; const QString actualFunction = doc->functionAt(line, column, &actualOpeningDeclaratorParenthesisLine, &actualClosingBraceLine); QCOMPARE(actualFunction, expectedFunction); QCOMPARE(actualOpeningDeclaratorParenthesisLine, expectedOpeningDeclaratorParenthesisLine); QCOMPARE(actualClosingBraceLine, expectedClosingBraceLine); } void tst_Lookup::simple_class_1() { const QByteArray source = "\n" "@interface Zoo {} +(id)alloc; -(id)init; @end\n" "@implementation Zoo +(id)alloc{} -(id)init{} -(void)dealloc{} @end\n"; Document::Ptr doc = Document::create("simple_class_1"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); QCOMPARE(doc->globalSymbolCount(), 2); Snapshot snapshot; snapshot.insert(doc); ObjCClass *iface = doc->globalSymbolAt(0)->asObjCClass(); QVERIFY(iface); QVERIFY(iface->isInterface()); QCOMPARE(iface->memberCount(), 2); ObjCClass *impl = doc->globalSymbolAt(1)->asObjCClass(); QVERIFY(impl); QVERIFY(!impl->isInterface()); QCOMPARE(impl->memberCount(), 3); Declaration *allocMethodIface = iface->memberAt(0)->asDeclaration(); QVERIFY(allocMethodIface); QVERIFY(allocMethodIface->name() && allocMethodIface->name()->identifier()); QCOMPARE(QLatin1String(allocMethodIface->name()->identifier()->chars()), QLatin1String("alloc")); ObjCMethod *allocMethodImpl = impl->memberAt(0)->asObjCMethod(); QVERIFY(allocMethodImpl); QVERIFY(allocMethodImpl->name() && allocMethodImpl->name()->identifier()); QCOMPARE(QLatin1String(allocMethodImpl->name()->identifier()->chars()), QLatin1String("alloc")); ObjCMethod *deallocMethod = impl->memberAt(2)->asObjCMethod(); QVERIFY(deallocMethod); QVERIFY(deallocMethod->name() && deallocMethod->name()->identifier()); QCOMPARE(QLatin1String(deallocMethod->name()->identifier()->chars()), QLatin1String("dealloc")); const LookupContext context(doc, snapshot); // check class resolving: ClassOrNamespace *klass = context.lookupType(impl->name(), impl->enclosingScope()); QVERIFY(klass != 0); QCOMPARE(klass->symbols().size(), 2); QVERIFY(klass->symbols().contains(iface)); QVERIFY(klass->symbols().contains(impl)); // check method resolving: QList results = context.lookup(allocMethodImpl->name(), impl); QCOMPARE(results.size(), 2); QCOMPARE(results.at(0).declaration(), allocMethodIface); QCOMPARE(results.at(1).declaration(), allocMethodImpl); results = context.lookup(deallocMethod->name(), impl); QCOMPARE(results.size(), 1); QCOMPARE(results.at(0).declaration(), deallocMethod); } void tst_Lookup::class_with_baseclass() { const QByteArray source = "\n" "@implementation BaseZoo {} -(void)baseDecl; -(void)baseMethod{} @end\n" "@interface Zoo: BaseZoo {} +(id)alloc; -(id)init; @end\n" "@implementation Zoo +(id)alloc{} -(id)init{} -(void)dealloc{} @end\n"; Document::Ptr doc = Document::create("class_with_baseclass"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); QCOMPARE(doc->globalSymbolCount(), 3); Snapshot snapshot; snapshot.insert(doc); Document::Ptr emptyDoc = Document::create(""); ObjCClass *baseZoo = doc->globalSymbolAt(0)->asObjCClass(); QVERIFY(baseZoo); QVERIFY(!baseZoo->isInterface()); QCOMPARE(baseZoo->memberCount(), 2); ObjCClass *zooIface = doc->globalSymbolAt(1)->asObjCClass(); QVERIFY(zooIface); QVERIFY(zooIface->isInterface()); QVERIFY(zooIface->baseClass()->name() == baseZoo->name()); ObjCClass *zooImpl = doc->globalSymbolAt(2)->asObjCClass(); QVERIFY(zooImpl); QVERIFY(!zooImpl->isInterface()); QCOMPARE(zooImpl->memberCount(), 3); Declaration *baseDecl = baseZoo->memberAt(0)->asDeclaration(); QVERIFY(baseDecl); QVERIFY(baseDecl->name() && baseDecl->name()->identifier()); QCOMPARE(QLatin1String(baseDecl->name()->identifier()->chars()), QLatin1String("baseDecl")); ObjCMethod *baseMethod = baseZoo->memberAt(1)->asObjCMethod(); QVERIFY(baseMethod); QVERIFY(baseMethod->name() && baseMethod->name()->identifier()); QCOMPARE(QLatin1String(baseMethod->name()->identifier()->chars()), QLatin1String("baseMethod")); const LookupContext context(doc, snapshot); ClassOrNamespace *objClass = context.lookupType(baseZoo->name(), zooImpl->enclosingScope()); QVERIFY(objClass != 0); QVERIFY(objClass->symbols().contains(baseZoo)); QList results = context.lookup(baseDecl->name(), zooImpl); QCOMPARE(results.size(), 1); QCOMPARE(results.at(0).declaration(), baseDecl); results = context.lookup(baseMethod->name(), zooImpl); QCOMPARE(results.size(), 1); QCOMPARE(results.at(0).declaration(), baseMethod); } void tst_Lookup::class_with_protocol_with_protocol() { const QByteArray source = "\n" "@protocol P1 -(void)p1method; @end\n" "@protocol P2 -(void)p2method; @end\n" "@interface Zoo {} +(id)alloc; -(id)init; @end\n" "@implementation Zoo +(id)alloc{} -(id)init{} -(void)dealloc{} @end\n"; Document::Ptr doc = Document::create("class_with_protocol_with_protocol"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); QCOMPARE(doc->globalSymbolCount(), 4); Snapshot snapshot; snapshot.insert(doc); ObjCProtocol *P1 = doc->globalSymbolAt(0)->asObjCProtocol(); QVERIFY(P1); QCOMPARE(P1->memberCount(), 1); QCOMPARE(P1->protocolCount(), 0); Declaration *p1method = P1->memberAt(0)->asDeclaration(); QVERIFY(p1method); QCOMPARE(QLatin1String(p1method->name()->identifier()->chars()), QLatin1String("p1method")); ObjCProtocol *P2 = doc->globalSymbolAt(1)->asObjCProtocol(); QVERIFY(P2); QCOMPARE(P2->memberCount(), 1); QCOMPARE(P2->protocolCount(), 1); QCOMPARE(QLatin1String(P2->protocolAt(0)->name()->identifier()->chars()), QLatin1String("P1")); ObjCClass *zooImpl = doc->globalSymbolAt(3)->asObjCClass(); QVERIFY(zooImpl); const LookupContext context(doc, snapshot); { const QList candidates = context.lookup(P1->name(), zooImpl->enclosingScope()); QCOMPARE(candidates.size(), 1); QVERIFY(candidates.at(0).declaration() == P1); } { const QList candidates = context.lookup(P2->protocolAt(0)->name(), zooImpl->enclosingScope()); QCOMPARE(candidates.size(), 1); QVERIFY(candidates.first().declaration() == P1); } QList results = context.lookup(p1method->name(), zooImpl); QCOMPARE(results.size(), 1); QCOMPARE(results.at(0).declaration(), p1method); } void tst_Lookup::iface_impl_scoping() { const QByteArray source = "\n" "@interface Scooping{}-(int)method1:(int)arg;-(void)method2;@end\n" "@implementation Scooping-(int)method1:(int)arg{return arg;}@end\n"; Document::Ptr doc = Document::create("class_with_protocol_with_protocol"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); QCOMPARE(doc->globalSymbolCount(), 2); Snapshot snapshot; snapshot.insert(doc); ObjCClass *iface = doc->globalSymbolAt(0)->asObjCClass(); QVERIFY(iface); QVERIFY(iface->isInterface()); ObjCClass *impl = doc->globalSymbolAt(1)->asObjCClass(); QVERIFY(impl); QVERIFY(!impl->isInterface()); QCOMPARE(iface->memberCount(), 2); QCOMPARE(impl->memberCount(), 1); ObjCMethod *method1Impl = impl->memberAt(0)->asObjCMethod(); QVERIFY(method1Impl); QCOMPARE(method1Impl->identifier()->chars(), "method1"); // get the body of method1 QCOMPARE(method1Impl->memberCount(), 2); Argument *method1Arg = method1Impl->memberAt(0)->asArgument(); QVERIFY(method1Arg); QCOMPARE(method1Arg->identifier()->chars(), "arg"); QVERIFY(method1Arg->type()->asIntegerType()); Block *method1Body = method1Impl->memberAt(1)->asBlock(); QVERIFY(method1Body); const LookupContext context(doc, snapshot); { // verify if we can resolve "arg" in the body QCOMPARE(method1Impl->argumentCount(), 1); Argument *arg = method1Impl->argumentAt(0)->asArgument(); QVERIFY(arg); QVERIFY(arg->name()); QVERIFY(arg->name()->identifier()); QCOMPARE(arg->name()->identifier()->chars(), "arg"); QVERIFY(arg->type()->asIntegerType()); const QList candidates = context.lookup(arg->name(), method1Body->enclosingScope()); QCOMPARE(candidates.size(), 1); QVERIFY(candidates.at(0).declaration()->type()->asIntegerType()); } Declaration *method2 = iface->memberAt(1)->asDeclaration(); QVERIFY(method2); QCOMPARE(method2->identifier()->chars(), "method2"); { // verify if we can resolve "method2" in the body const QList candidates = context.lookup(method2->name(), method1Body->enclosingScope()); QCOMPARE(candidates.size(), 1); QCOMPARE(candidates.at(0).declaration(), method2); } } void tst_Lookup::templates_1() { const QByteArray source = "\n" "namespace std {\n" " template \n" " struct _List_iterator {\n" " T data;\n" " };\n" "\n" " template \n" " struct list {\n" " typedef _List_iterator iterator;\n" "\n" " iterator begin();\n" " _List_iterator end();\n" " };\n" "}\n" "\n" "struct Point {\n" " int x, y;\n" "};\n" "\n" "int main()\n" "{\n" " std::list l;\n" " l.begin(); // std::_List_iterator .. and not only _List_iterator\n" " l.end(); // std::_List_iterator\n" "}\n"; Document::Ptr doc = Document::create("templates_1"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); } void tst_Lookup::templates_2() { const QByteArray source = "\n" "template \n" "struct Node {\n" " T1 value;\n" " Node *next;\n" " Node *other_next;\n" "};\n" "\n" "template \n" "struct List {\n" " Node *elements;\n" "};\n" "\n" "int main()\n" "{\n" " List *e;\n" " e->elements; // Node *\n" " e->elements->next; // Node *\n" " e->elements->other_next; // Node *\n" "}\n" ; Document::Ptr doc = Document::create("templates_2"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); } void tst_Lookup::templates_3() { const QByteArray source = "\n" "struct Point {\n" " int x, y;\n" "};\n" "\n" "template \n" "struct List {\n" " const T &at(int);\n" "};\n" "\n" "int main()\n" "{\n" " List<> l;\n" " l.at(0); // const Point &\n" "}\n"; Document::Ptr doc = Document::create("templates_3"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); } void tst_Lookup::templates_4() { const QByteArray source = "\n" "template \n" "struct Allocator {\n" " typedef T *pointer_type;\n" " typedef T &reference_type;\n" "};\n" "\n" "template \n" "struct SharedPtr {\n" " typedef typename Allocator::pointer_type pointer_type;\n" " typedef typename Allocator::reference_type reference_type;\n" "\n" " pointer_type operator->();\n" " reference_type operator*();\n" "\n" " pointer_type data();\n" " reference_type get();\n" "\n" "};\n" "\n" "struct Point {\n" " int x,y;\n" "};\n" "\n" "int main()\n" "{\n" " SharedPtr l;\n" "\n" " l->x; // int\n" " (*l); // Point &\n" "}\n"; Document::Ptr doc = Document::create("templates_4"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); } void tst_Lookup::templates_5() { const QByteArray source = "\n" "struct Point {\n" " int x,y;\n" "};\n" "\n" "template \n" "struct Allocator {\n" " typedef const _Tp &const_reference;\n" "\n" " const_reference get();\n" "};\n" "\n" "int main()\n" "{\n" " Allocator::const_reference r = pt;\n" " //r.; // const Point &\n" "\n" " Allocator a;\n" " a.get(); // const Point &\n" "}\n"; Document::Ptr doc = Document::create("templates_5"); doc->setUtf8Source(source); doc->parse(); doc->check(); QVERIFY(doc->diagnosticMessages().isEmpty()); } void tst_Lookup::minimalname_data() { QTest::addColumn("source"); QTest::addColumn("index"); QTest::newRow("inlineNamespace1") << QByteArray("namespace std { inline namespace __cxx11 { class string{}; } }\n") << 0; // This case is extracted from libstdc++ 5.4.0. // The inline namespace is re-opened as non-inline, which is not standard // compliant. However, gcc does this and clang only issues a warning. QTest::newRow("inlineNamespace2") << QByteArray("namespace std { inline namespace __cxx11 {} }\n" "namespace std { namespace __cxx11 { class string{}; } }\n") << 1; } void tst_Lookup::minimalname() { QFETCH(QByteArray, source); QFETCH(int, index); Document::Ptr doc = Document::create("minimalname"); doc->setUtf8Source(source); doc->parse(); doc->check(); Snapshot snapshot; snapshot.insert(doc); LookupContext ctx(doc, snapshot); Control control; Symbol *symbol = doc->globalSymbolAt(unsigned(index)) ->asNamespace()->memberAt(0)->asNamespace()->memberAt(0); const Name *minimalName = LookupContext::minimalName(symbol, ctx.globalNamespace(), &control); Overview oo; const QString minimalNameAsString = NamePrettyPrinter(&oo)(minimalName); QCOMPARE(minimalNameAsString, QString::fromUtf8("std::string")); } QTEST_APPLESS_MAIN(tst_Lookup) #include "tst_lookup.moc"