1 /*
2     SPDX-FileCopyrightText: 2014 Milian Wolff <mail@milianw.de>
3     SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
4     SPDX-FileCopyrightText: 2015 Sergey Kalinichev <kalinichev.so.0@gmail.com>
5 
6     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
7 */
8 
9 #include "test_duchain.h"
10 
11 #include <tests/testcore.h>
12 #include <tests/autotestshell.h>
13 #include <tests/testfile.h>
14 #include <tests/testproject.h>
15 #include <language/duchain/duchainlock.h>
16 #include <language/duchain/duchain.h>
17 #include <language/duchain/declaration.h>
18 #include <language/duchain/parsingenvironment.h>
19 #include <language/duchain/problem.h>
20 #include <language/duchain/types/integraltype.h>
21 #include <language/duchain/types/structuretype.h>
22 #include <language/duchain/types/functiontype.h>
23 #include <language/duchain/types/typealiastype.h>
24 #include <language/duchain/types/typeutils.h>
25 #include <language/duchain/duchainutils.h>
26 #include <language/duchain/classdeclaration.h>
27 #include <language/duchain/abstractfunctiondeclaration.h>
28 #include <language/duchain/functiondefinition.h>
29 #include <language/duchain/classfunctiondeclaration.h>
30 #include <language/duchain/forwarddeclaration.h>
31 #include <language/duchain/use.h>
32 #include <language/duchain/duchaindumper.h>
33 #include <language/backgroundparser/backgroundparser.h>
34 #include <interfaces/ilanguagecontroller.h>
35 #include <interfaces/idocumentcontroller.h>
36 #include <util/kdevstringhandler.h>
37 
38 #include "duchain/clangparsingenvironmentfile.h"
39 #include "duchain/clangparsingenvironment.h"
40 #include "duchain/parsesession.h"
41 #include "duchain/clanghelpers.h"
42 
43 #include "testprovider.h"
44 
45 #include <KConfigGroup>
46 
47 #include <QTest>
48 #include <QSignalSpy>
49 #include <QLoggingCategory>
50 #include <QThread>
51 #include <QVersionNumber>
52 
53 QTEST_MAIN(TestDUChain)
54 
55 using namespace KDevelop;
56 
57 TestDUChain::~TestDUChain() = default;
58 
initTestCase()59 void TestDUChain::initTestCase()
60 {
61     QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n"));
62     QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1"));
63     AutoTestShell::init({QStringLiteral("kdevclangsupport")});
64     auto core = TestCore::initialize();
65     delete core->projectController();
66     m_projectController = new TestProjectController(core);
67     core->setProjectController(m_projectController);
68 }
69 
cleanupTestCase()70 void TestDUChain::cleanupTestCase()
71 {
72     TestCore::shutdown();
73 }
74 
cleanup()75 void TestDUChain::cleanup()
76 {
77     if (m_provider) {
78         IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data());
79     }
80 }
81 
init()82 void TestDUChain::init()
83 {
84     m_provider.reset(new TestEnvironmentProvider);
85     IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data());
86 }
87 
88 struct ExpectedComment
89 {
90     QString identifier;
91     QString comment;
92 };
93 Q_DECLARE_METATYPE(ExpectedComment)
Q_DECLARE_METATYPE(AbstractType::WhichType)94 Q_DECLARE_METATYPE(AbstractType::WhichType)
95 
96 void TestDUChain::testComments()
97 {
98     QFETCH(QString, code);
99     QFETCH(ExpectedComment, expectedComment);
100 
101     TestFile file(code, QStringLiteral("cpp"));
102     QVERIFY(file.parseAndWait());
103 
104     DUChainReadLocker lock;
105     auto top = file.topContext();
106     QVERIFY(top);
107     auto candidates = top->findDeclarations(QualifiedIdentifier(expectedComment.identifier));
108     QVERIFY(!candidates.isEmpty());
109     auto decl = candidates.first();
110     QString comment = QString::fromLocal8Bit(decl->comment());
111     const auto plainText = KDevelop::htmlToPlainText(comment, KDevelop::CompleteMode);
112     // if comment is e.g. "<this is bar", htmlToPlainText(comment) would just return an empty string; avoid this
113     if (!plainText.isEmpty()) {
114         comment = plainText;
115     }
116     QCOMPARE(comment, expectedComment.comment);
117 }
118 
testComments_data()119 void TestDUChain::testComments_data()
120 {
121     QTest::addColumn<QString>("code");
122     QTest::addColumn<ExpectedComment>("expectedComment");
123 
124     // note: Clang only retrieves the comments when in doxygen-style format (i.e. '///', '/**', '///<')
125     QTest::newRow("invalid1")
126         << "//this is foo\nint foo;"
127         << ExpectedComment{"foo", QString()};
128     QTest::newRow("invalid2")
129         << "/*this is foo*/\nint foo;"
130         << ExpectedComment{"foo", QString()};
131     QTest::newRow("basic1")
132         << "///this is foo\nint foo;"
133         << ExpectedComment{"foo", "this is foo"};
134     QTest::newRow("basic2")
135         << "/**this is foo*/\nint foo;"
136         << ExpectedComment{"foo", "this is foo"};
137 
138     // as long as https://bugs.llvm.org/show_bug.cgi?id=35333 is not fixed, we don't fully parse and render
139     // doxygen-style comments properly (cf. `makeComment` in builder.cpp)
140 #define PARSE_COMMENTS 0
141     QTest::newRow("enumerator")
142         << "enum Foo { bar1, ///<this is bar1\nbar2 ///<this is bar2\n };"
143         << ExpectedComment{"Foo::bar1", "this is bar1"};
144 
145     QTest::newRow("comment-formatting")
146         << "/** a\n * multiline\n *\n * comment\n */ int foo;"
147 #if PARSE_COMMENTS
148         << ExpectedComment{"foo", "a multiline\ncomment"};
149 #else
150         << ExpectedComment{"foo", "a multiline comment"};
151 #endif
152     QTest::newRow("comment-doxygen-tags")
153         << "/** @see bar()\n@param a foo\n*/\nvoid foo(int a);\nvoid bar();"
154 #if PARSE_COMMENTS
155         << ExpectedComment{"foo", "bar()\na\nfoo"};
156 #else
157         << ExpectedComment{"foo", "@see bar() @param a foo"};
158 #endif
159 }
160 
testElaboratedType()161 void TestDUChain::testElaboratedType()
162 {
163     QFETCH(QString, code);
164     QFETCH(AbstractType::WhichType, type);
165 
166     TestFile file(code, QStringLiteral("cpp"));
167     QVERIFY(file.parseAndWait());
168 
169     DUChainReadLocker lock;
170     auto top = file.topContext();
171     QVERIFY(top);
172     QCOMPARE(file.topContext()->localDeclarations().size(), 2);
173 
174     auto decl = file.topContext()->localDeclarations()[1];
175     QVERIFY(decl);
176 
177     auto function = dynamic_cast<FunctionDeclaration*>(decl);
178     QVERIFY(function);
179 
180     auto functionType = function->type<FunctionType>();
181     QVERIFY(functionType);
182 
183 #if CINDEX_VERSION_MINOR < 34
184     QEXPECT_FAIL("namespace", "The ElaboratedType is not exposed through the libclang interface, not much we can do here", Abort);
185 #endif
186     QVERIFY(functionType->returnType()->whichType() != AbstractType::TypeDelayed);
187 #if CINDEX_VERSION_MINOR < 34
188     QEXPECT_FAIL("typedef", "After using clang_getCanonicalType on ElaboratedType all typedef information get's stripped away", Continue);
189 #endif
190     QCOMPARE(functionType->returnType()->whichType(), type);
191 }
192 
testElaboratedType_data()193 void TestDUChain::testElaboratedType_data()
194 {
195     QTest::addColumn<QString>("code");
196     QTest::addColumn<AbstractType::WhichType>("type");
197 
198     QTest::newRow("namespace")
199         << "namespace NS{struct Type{};} struct NS::Type foo();"
200         << AbstractType::TypeStructure;
201     QTest::newRow("enum")
202         << "enum Enum{}; enum Enum foo();"
203         << AbstractType::TypeEnumeration;
204     QTest::newRow("typedef")
205         << "namespace NS{typedef int type;} NS::type foo();"
206         << AbstractType::TypeAlias;
207 }
208 
testInclude()209 void TestDUChain::testInclude()
210 {
211     TestFile header(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h"));
212     // NOTE: header is _not_ explicitly being parsed, instead the impl job does that
213 
214     TestFile impl("#include \"" + header.url().str() + "\"\n"
215                   "int main() { return foo(); }", QStringLiteral("cpp"), &header);
216     impl.parse(TopDUContext::AllDeclarationsContextsAndUses);
217 
218     auto implCtx = impl.topContext();
219     QVERIFY(implCtx);
220 
221     DUChainReadLocker lock;
222     QCOMPARE(implCtx->localDeclarations().size(), 1);
223 
224     auto headerCtx = DUChain::self()->chainForDocument(header.url());
225     QVERIFY(headerCtx);
226     QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate());
227     QCOMPARE(headerCtx->localDeclarations().size(), 1);
228 
229     QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10)));
230 
231     Declaration* foo = headerCtx->localDeclarations().first();
232     QCOMPARE(foo->uses().size(), 1);
233     QCOMPARE(foo->uses().begin().key(), impl.url());
234     QCOMPARE(foo->uses().begin()->size(), 1);
235     QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23));
236 }
237 
testMissingInclude()238 void TestDUChain::testMissingInclude()
239 {
240     auto code = R"(
241 #pragma once
242 #include "missing1.h"
243 
244 template<class T>
245 class A
246 {
247     T a;
248 };
249 
250 #include "missing2.h"
251 
252 class B : public A<int>
253 {
254 };
255     )";
256 
257     TestFile header(code, QStringLiteral("h"));
258     TestFile impl("#include \"" + header.url().str() + "\"\n", QStringLiteral("cpp"), &header);
259     QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses));
260 
261     DUChainReadLocker lock;
262 
263     auto top = impl.topContext();
264     QVERIFY(top);
265 
266     QCOMPARE(top->importedParentContexts().count(), 1);
267 
268     TopDUContext* headerCtx = dynamic_cast<TopDUContext*>(top->importedParentContexts().first().context(top));
269     QVERIFY(headerCtx);
270     QCOMPARE(headerCtx->url(), header.url());
271 
272 #if CINDEX_VERSION_MINOR < 34
273     QEXPECT_FAIL("", "Second missing header isn't reported", Continue);
274 #endif
275     QCOMPARE(headerCtx->problems().count(), 2);
276 
277     QCOMPARE(headerCtx->localDeclarations().count(), 2);
278 
279     auto a = dynamic_cast<ClassDeclaration*>(headerCtx->localDeclarations().first());
280     QVERIFY(a);
281 
282     auto b = dynamic_cast<ClassDeclaration*>(headerCtx->localDeclarations().last());
283     QVERIFY(b);
284 
285     // NOTE: This fails and needs fixing. If the include of "missing2.h"
286     //       above is commented out, then it doesn't fail. Maybe
287     //       clang stops processing when it encounters the second missing
288     //       header, or similar.
289     // XFAIL this check until https://bugs.llvm.org/show_bug.cgi?id=38155 is fixed
290     if (QVersionNumber::fromString(ClangHelpers::clangVersion()) < QVersionNumber(9, 0, 0))
291         QEXPECT_FAIL("", "Base class isn't assigned correctly", Continue);
292     QCOMPARE(b->baseClassesSize(), 1u);
293 
294 #if CINDEX_VERSION_MINOR < 34
295     // at least the one problem we have should have been propagated
296     QCOMPARE(top->problems().count(), 1);
297 #else
298     // two errors:
299     // /tmp/testfile_f32415.h:3:10: error: 'missing1.h' file not found
300     // /tmp/testfile_f32415.h:11:10: error: 'missing2.h' file not found
301     QCOMPARE(top->problems().count(), 2);
302 #endif
303 }
304 
createCode(const QByteArray & prefix,const int functions)305 QByteArray createCode(const QByteArray& prefix, const int functions)
306 {
307     QByteArray code;
308     code += "#ifndef " + prefix + "_H\n";
309     code += "#define " + prefix + "_H\n";
310     for (int i = 0; i < functions; ++i) {
311         code += "void myFunc_" + prefix + "(int arg1, char arg2, const char* arg3);\n";
312     }
313     code += "#endif\n";
314     return code;
315 }
316 
testIncludeLocking()317 void TestDUChain::testIncludeLocking()
318 {
319     TestFile header1(createCode("Header1", 1000), QStringLiteral("h"));
320     TestFile header2(createCode("Header2", 1000), QStringLiteral("h"));
321     TestFile header3(createCode("Header3", 1000), QStringLiteral("h"));
322 
323     ICore::self()->languageController()->backgroundParser()->setThreadCount(3);
324 
325     TestFile impl1("#include \"" + header1.url().str() + "\"\n"
326                    "#include \"" + header2.url().str() + "\"\n"
327                    "#include \"" + header3.url().str() + "\"\n"
328                    "int main() { return 0; }", QStringLiteral("cpp"));
329 
330     TestFile impl2("#include \"" + header2.url().str() + "\"\n"
331                    "#include \"" + header1.url().str() + "\"\n"
332                    "#include \"" + header3.url().str() + "\"\n"
333                    "int main() { return 0; }", QStringLiteral("cpp"));
334 
335     TestFile impl3("#include \"" + header3.url().str() + "\"\n"
336                    "#include \"" + header1.url().str() + "\"\n"
337                    "#include \"" + header2.url().str() + "\"\n"
338                    "int main() { return 0; }", QStringLiteral("cpp"));
339 
340     impl1.parse(TopDUContext::AllDeclarationsContextsAndUses);
341     impl2.parse(TopDUContext::AllDeclarationsContextsAndUses);
342     impl3.parse(TopDUContext::AllDeclarationsContextsAndUses);
343 
344     QVERIFY(impl1.waitForParsed(5000));
345     QVERIFY(impl2.waitForParsed(5000));
346     QVERIFY(impl3.waitForParsed(5000));
347 
348     DUChainReadLocker lock;
349     QVERIFY(DUChain::self()->chainForDocument(header1.url()));
350     QVERIFY(DUChain::self()->chainForDocument(header2.url()));
351     QVERIFY(DUChain::self()->chainForDocument(header3.url()));
352 }
353 
testReparse()354 void TestDUChain::testReparse()
355 {
356     TestFile file(QStringLiteral("int main() { int i = 42; return i; }"), QStringLiteral("cpp"));
357     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
358 
359     DeclarationPointer mainDecl;
360     DeclarationPointer iDecl;
361     for (int i = 0; i < 3; ++i) {
362         QVERIFY(file.waitForParsed(500));
363         DUChainReadLocker lock;
364         QVERIFY(file.topContext());
365         QCOMPARE(file.topContext()->childContexts().size(), 1);
366         QCOMPARE(file.topContext()->localDeclarations().size(), 1);
367         DUContext *exprContext = file.topContext()->childContexts().first()->childContexts().first();
368         QCOMPARE(exprContext->localDeclarations().size(), 1);
369 
370         if (i) {
371             QVERIFY(mainDecl);
372             QCOMPARE(mainDecl.data(), file.topContext()->localDeclarations().first());
373 
374             QVERIFY(iDecl);
375             QCOMPARE(iDecl.data(), exprContext->localDeclarations().first());
376         }
377         mainDecl = file.topContext()->localDeclarations().first();
378         iDecl = exprContext->localDeclarations().first();
379 
380         QVERIFY(mainDecl->uses().isEmpty());
381         QCOMPARE(iDecl->uses().size(), 1);
382         QCOMPARE(iDecl->uses().begin()->size(), 1);
383 
384         if (i == 1) {
385             file.setFileContents(QStringLiteral("int main()\n{\nfloat i = 13; return i - 5;\n}\n"));
386         }
387 
388         file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive);
389     }
390 }
391 
testReparseError()392 void TestDUChain::testReparseError()
393 {
394     TestFile file(QStringLiteral("int i = 1 / 0;\n"), QStringLiteral("cpp"));
395     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
396 
397     for (int i = 0; i < 2; ++i) {
398         QVERIFY(file.waitForParsed(500));
399         DUChainReadLocker lock;
400         QVERIFY(file.topContext());
401         if (!i) {
402             QCOMPARE(file.topContext()->problems().size(), 1);
403             file.setFileContents(QStringLiteral("int i = 0;\n"));
404         } else {
405             QCOMPARE(file.topContext()->problems().size(), 0);
406         }
407 
408         file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive);
409     }
410 }
411 
testTemplate()412 void TestDUChain::testTemplate()
413 {
414     TestFile file("template<typename T> struct foo { T bar; };\n"
415                   "int main() { foo<int> myFoo; return myFoo.bar; }\n", QStringLiteral("cpp"));
416     QVERIFY(file.parseAndWait());
417 
418     DUChainReadLocker lock;
419     QVERIFY(file.topContext());
420     QCOMPARE(file.topContext()->localDeclarations().size(), 2);
421     auto fooDecl = file.topContext()->localDeclarations().first();
422     QVERIFY(fooDecl->internalContext());
423     QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 2);
424 
425     QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo< T >")).size(), 1);
426     QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo< T >::bar")).size(), 1);
427 
428     auto mainCtx = file.topContext()->localDeclarations().last()->internalContext()->childContexts().first();
429     QVERIFY(mainCtx);
430     auto myFoo = mainCtx->localDeclarations().first();
431     QVERIFY(myFoo);
432     QCOMPARE(myFoo->abstractType()->toString().remove(' '), QStringLiteral("foo<int>"));
433 }
434 
testNamespace()435 void TestDUChain::testNamespace()
436 {
437     TestFile file("namespace foo { struct bar { int baz; }; }\n"
438                   "int main() { foo::bar myBar; }\n", QStringLiteral("cpp"));
439     QVERIFY(file.parseAndWait());
440 
441     DUChainReadLocker lock;
442     QVERIFY(file.topContext());
443     QCOMPARE(file.topContext()->localDeclarations().size(), 2);
444     auto fooDecl = file.topContext()->localDeclarations().first();
445     QVERIFY(fooDecl->internalContext());
446     QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 1);
447 
448     DUContext* top = file.topContext().data();
449     DUContext* mainCtx = file.topContext()->childContexts().last();
450 
451     auto foo = top->localDeclarations().first();
452     QCOMPARE(foo->qualifiedIdentifier().toString(), QString("foo"));
453 
454     DUContext* fooCtx = file.topContext()->childContexts().first();
455     QCOMPARE(fooCtx->localScopeIdentifier().toString(), QString("foo"));
456     QCOMPARE(fooCtx->scopeIdentifier(true).toString(), QString("foo"));
457     QCOMPARE(fooCtx->localDeclarations().size(), 1);
458     auto bar = fooCtx->localDeclarations().first();
459     QCOMPARE(bar->qualifiedIdentifier().toString(), QString("foo::bar"));
460     QCOMPARE(fooCtx->childContexts().size(), 1);
461 
462     DUContext* barCtx = fooCtx->childContexts().first();
463     QCOMPARE(barCtx->localScopeIdentifier().toString(), QString("bar"));
464     QCOMPARE(barCtx->scopeIdentifier(true).toString(), QString("foo::bar"));
465     QCOMPARE(barCtx->localDeclarations().size(), 1);
466     auto baz = barCtx->localDeclarations().first();
467     QCOMPARE(baz->qualifiedIdentifier().toString(), QString("foo::bar::baz"));
468 
469     for (auto ctx : {top, mainCtx}) {
470         QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1);
471         QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1);
472         QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar::baz")).size(), 1);
473     }
474 }
475 
testAutoTypeDeduction()476 void TestDUChain::testAutoTypeDeduction()
477 {
478     TestFile file(QStringLiteral(R"(
479         const volatile auto foo = 5;
480         template<class T> struct myTemplate {};
481         myTemplate<myTemplate<int>& > templRefParam;
482         auto autoTemplRefParam = templRefParam;
483     )"), QStringLiteral("cpp"));
484     QVERIFY(file.parseAndWait());
485 
486     DUChainReadLocker lock;
487 
488     DUContext* ctx = file.topContext().data();
489     QVERIFY(ctx);
490     QCOMPARE(ctx->localDeclarations().size(), 4);
491     QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1);
492     Declaration* decl = ctx->findDeclarations(QualifiedIdentifier(QStringLiteral("foo")))[0];
493     QCOMPARE(decl->identifier(), Identifier("foo"));
494 #if CINDEX_VERSION_MINOR < 31
495     QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue);
496 #endif
497     QVERIFY(decl->type<IntegralType>());
498 #if CINDEX_VERSION_MINOR < 31
499     QCOMPARE(decl->toString(), QStringLiteral("const volatile auto foo"));
500 #else
501     QCOMPARE(decl->toString(), QStringLiteral("const volatile int foo"));
502 #endif
503 
504     decl = ctx->findDeclarations(QualifiedIdentifier(QStringLiteral("autoTemplRefParam")))[0];
505     QVERIFY(decl);
506     QVERIFY(decl->abstractType());
507 #if CINDEX_VERSION_MINOR < 31
508     QEXPECT_FAIL("", "Auto type is not exposed via LibClang", Continue);
509 #endif
510     QCOMPARE(decl->abstractType()->toString(), QStringLiteral("myTemplate< myTemplate< int >& >"));
511 }
512 
testTypeDeductionInTemplateInstantiation()513 void TestDUChain::testTypeDeductionInTemplateInstantiation()
514 {
515     // see: http://clang-developers.42468.n3.nabble.com/RFC-missing-libclang-query-functions-features-td2504253.html
516     TestFile file(QStringLiteral("template<typename T> struct foo { T member; } foo<int> f; auto i = f.member;"), QStringLiteral("cpp"));
517     QVERIFY(file.parseAndWait());
518 
519     DUChainReadLocker lock;
520 
521     DUContext* ctx = file.topContext().data();
522     QVERIFY(ctx);
523     QCOMPARE(ctx->localDeclarations().size(), 3);
524     Declaration* decl = nullptr;
525 
526     // check 'foo' declaration
527     decl = ctx->localDeclarations()[0];
528     QVERIFY(decl);
529     QCOMPARE(decl->identifier(), Identifier("foo< T >"));
530 
531     // check type of 'member' inside declaration-scope
532     QCOMPARE(ctx->childContexts().size(), 1);
533     DUContext* fooCtx = ctx->childContexts().first();
534     QVERIFY(fooCtx);
535     // Should there really be two declarations?
536     QCOMPARE(fooCtx->localDeclarations().size(), 2);
537     decl = fooCtx->localDeclarations()[1];
538     QCOMPARE(decl->identifier(), Identifier("member"));
539 
540     // check type of 'member' in definition of 'f'
541     decl = ctx->localDeclarations()[1];
542     QCOMPARE(decl->identifier(), Identifier("f"));
543     decl = ctx->localDeclarations()[2];
544     QCOMPARE(decl->identifier(), Identifier("i"));
545 #if CINDEX_VERSION_MINOR < 31
546     QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue);
547 #endif
548     QVERIFY(decl->type<IntegralType>());
549 }
550 
testVirtualMemberFunction()551 void TestDUChain::testVirtualMemberFunction()
552 {
553     //Forward-declarations with "struct" or "class" are considered equal, so make sure the override is detected correctly.
554     TestFile file(QStringLiteral("struct S {}; struct A { virtual S* ret(); }; struct B : public A { virtual S* ret(); };"), QStringLiteral("cpp"));
555     QVERIFY(file.parseAndWait());
556 
557     DUChainReadLocker lock;
558     DUContext* top = file.topContext().data();
559     QVERIFY(top);
560 
561     QCOMPARE(top->childContexts().count(), 3);
562     QCOMPARE(top->localDeclarations().count(), 3);
563     QCOMPARE(top->childContexts()[2]->localDeclarations().count(), 1);
564     Declaration* decl = top->childContexts()[2]->localDeclarations()[0];
565     QCOMPARE(decl->identifier(), Identifier("ret"));
566     QVERIFY(DUChainUtils::overridden(decl));
567 }
568 
testBaseClasses()569 void TestDUChain::testBaseClasses()
570 {
571     TestFile file(QStringLiteral("class Base {}; class Inherited : public Base {};"), QStringLiteral("cpp"));
572     QVERIFY(file.parseAndWait());
573 
574     DUChainReadLocker lock;
575     DUContext* top = file.topContext().data();
576     QVERIFY(top);
577 
578     QCOMPARE(top->localDeclarations().count(), 2);
579     Declaration* baseDecl = top->localDeclarations().first();
580     QCOMPARE(baseDecl->identifier(), Identifier("Base"));
581 
582     ClassDeclaration* inheritedDecl = dynamic_cast<ClassDeclaration*>(top->localDeclarations()[1]);
583     QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited"));
584 
585     QVERIFY(inheritedDecl);
586     QCOMPARE(inheritedDecl->baseClassesSize(), 1u);
587 
588     QCOMPARE(baseDecl->uses().count(), 1);
589     QCOMPARE(baseDecl->uses().first().count(), 1);
590     QCOMPARE(baseDecl->uses().first().first(), RangeInRevision(0, 40, 0, 44));
591 }
592 
testReparseBaseClasses()593 void TestDUChain::testReparseBaseClasses()
594 {
595     TestFile file(QStringLiteral("struct a{}; struct b : a {};\n"), QStringLiteral("cpp"));
596     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
597 
598     for (int i = 0; i < 2; ++i) {
599         qDebug() << "run: " << i;
600         QVERIFY(file.waitForParsed(500));
601         DUChainWriteLocker lock;
602         QVERIFY(file.topContext());
603         QCOMPARE(file.topContext()->childContexts().size(), 2);
604         QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1);
605         QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1);
606 
607         QCOMPARE(file.topContext()->localDeclarations().size(), 2);
608         auto aDecl = dynamic_cast<ClassDeclaration*>(file.topContext()->localDeclarations().first());
609         QVERIFY(aDecl);
610         QCOMPARE(aDecl->baseClassesSize(), 0u);
611         auto bDecl = dynamic_cast<ClassDeclaration*>(file.topContext()->localDeclarations().last());
612         QVERIFY(bDecl);
613         QCOMPARE(bDecl->baseClassesSize(), 1u);
614         int distance = 0;
615         QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance));
616         QCOMPARE(distance, 1);
617 
618         file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive);
619     }
620 }
621 
testReparseBaseClassesTemplates()622 void TestDUChain::testReparseBaseClassesTemplates()
623 {
624     TestFile file(QStringLiteral("template<typename T> struct a{}; struct b : a<int> {};\n"), QStringLiteral("cpp"));
625     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
626 
627     for (int i = 0; i < 2; ++i) {
628         qDebug() << "run: " << i;
629         QVERIFY(file.waitForParsed(500));
630         DUChainWriteLocker lock;
631         QVERIFY(file.topContext());
632         QCOMPARE(file.topContext()->childContexts().size(), 2);
633         QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1);
634         QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1);
635 
636         QCOMPARE(file.topContext()->localDeclarations().size(), 2);
637         auto aDecl = dynamic_cast<ClassDeclaration*>(file.topContext()->localDeclarations().first());
638         QVERIFY(aDecl);
639         QCOMPARE(aDecl->baseClassesSize(), 0u);
640         auto bDecl = dynamic_cast<ClassDeclaration*>(file.topContext()->localDeclarations().last());
641         QVERIFY(bDecl);
642         QCOMPARE(bDecl->baseClassesSize(), 1u);
643         int distance = 0;
644         QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance));
645         QCOMPARE(distance, 1);
646 
647         file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive);
648     }
649 }
650 
testGetInheriters_data()651 void TestDUChain::testGetInheriters_data()
652 {
653     QTest::addColumn<QString>("code");
654 
655     QTest::newRow("inline") << "struct Base { struct Inner {}; }; struct Inherited : Base, Base::Inner {};";
656     QTest::newRow("outline") << "struct Base { struct Inner; }; struct Base::Inner {}; struct Inherited : Base, Base::Inner {};";
657 }
658 
testGetInheriters()659 void TestDUChain::testGetInheriters()
660 {
661     QFETCH(QString, code);
662     TestFile file(code, QStringLiteral("cpp"));
663     QVERIFY(file.parseAndWait());
664 
665     DUChainReadLocker lock;
666     auto top = file.topContext();
667     QVERIFY(top);
668     QVERIFY(top->problems().isEmpty());
669 
670     QCOMPARE(top->localDeclarations().count(), 2);
671     Declaration* baseDecl = top->localDeclarations().first();
672     QCOMPARE(baseDecl->identifier(), Identifier("Base"));
673 
674     DUContext* baseCtx = baseDecl->internalContext();
675     QVERIFY(baseCtx);
676     QCOMPARE(baseCtx->localDeclarations().count(), 1);
677 
678     Declaration* innerDecl = baseCtx->localDeclarations().first();
679     QCOMPARE(innerDecl->identifier(), Identifier("Inner"));
680     if (auto forward = dynamic_cast<ForwardDeclaration*>(innerDecl)) {
681         innerDecl = forward->resolve(top);
682     }
683     QVERIFY(dynamic_cast<ClassDeclaration*>(innerDecl));
684 
685     Declaration* inheritedDecl = top->localDeclarations().last();
686     QVERIFY(inheritedDecl);
687     QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited"));
688 
689     uint maxAllowedSteps = uint(-1);
690     auto baseInheriters = DUChainUtils::inheriters(baseDecl, maxAllowedSteps);
691     QCOMPARE(baseInheriters, QList<Declaration*>() << inheritedDecl);
692 
693     maxAllowedSteps = uint(-1);
694     auto innerInheriters = DUChainUtils::inheriters(innerDecl, maxAllowedSteps);
695     QCOMPARE(innerInheriters, QList<Declaration*>() << inheritedDecl);
696 
697     maxAllowedSteps = uint(-1);
698     auto inheritedInheriters = DUChainUtils::inheriters(inheritedDecl, maxAllowedSteps);
699     QCOMPARE(inheritedInheriters.count(), 0);
700 }
701 
testGlobalFunctionDeclaration()702 void TestDUChain::testGlobalFunctionDeclaration()
703 {
704     TestFile file(QStringLiteral("void foo(int arg1, char arg2);\n"), QStringLiteral("cpp"));
705     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
706     file.waitForParsed();
707 
708     DUChainReadLocker lock;
709     QVERIFY(file.topContext());
710     QCOMPARE(file.topContext()->localDeclarations().size(), 1);
711     QCOMPARE(file.topContext()->childContexts().size(), 1);
712     QVERIFY(!file.topContext()->childContexts().first()->inSymbolTable());
713 }
714 
testFunctionDefinitionVsDeclaration()715 void TestDUChain::testFunctionDefinitionVsDeclaration()
716 {
717     TestFile file(QStringLiteral("void func(); void func() {}\n"), QStringLiteral("cpp"));
718     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
719     QVERIFY(file.waitForParsed());
720 
721     DUChainReadLocker lock;
722     QVERIFY(file.topContext());
723     QCOMPARE(file.topContext()->localDeclarations().size(), 2);
724     auto funcDecl = file.topContext()->localDeclarations()[0];
725     QVERIFY(!funcDecl->isDefinition());
726     QVERIFY(!dynamic_cast<FunctionDefinition*>(funcDecl));
727     auto funcDef = file.topContext()->localDeclarations()[1];
728     QVERIFY(dynamic_cast<FunctionDefinition*>(funcDef));
729     QVERIFY(funcDef->isDefinition());
730 }
731 
testEnsureNoDoubleVisit()732 void TestDUChain::testEnsureNoDoubleVisit()
733 {
734     // On some language construct, we may up visiting the same cursor multiple times
735     // Example: "struct SomeStruct {} s;"
736     // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)]
737     // decl: "struct SomeStruct s " of kind VarDecl (9) in main.cpp@[(1,1),(1,19)]
738     // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)]
739     //
740     // => We end up visiting the StructDecl twice (or more)
741     //    That's because we use clang_visitChildren not just on the translation unit cursor.
742     //    Apparently just "recursing" vs. "visiting children explicitly"
743     //    results in a different AST traversal
744 
745     TestFile file(QStringLiteral("struct SomeStruct {} s;\n"), QStringLiteral("cpp"));
746     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
747     QVERIFY(file.waitForParsed());
748 
749     DUChainReadLocker lock;
750     auto top = file.topContext();
751     QVERIFY(top);
752 
753     // there should only be one declaration for "SomeStruct"
754     auto candidates = top->findDeclarations(QualifiedIdentifier(QStringLiteral("SomeStruct")));
755     QCOMPARE(candidates.size(), 1);
756 }
757 
testParsingEnvironment()758 void TestDUChain::testParsingEnvironment()
759 {
760     const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses;
761 
762     IndexedTopDUContext indexed;
763     ClangParsingEnvironment lastEnv;
764     {
765         TestFile file(QStringLiteral("int main() {}\n"), QStringLiteral("cpp"));
766         const auto astFeatures = features | TopDUContext::AST;
767         file.parse(astFeatures);
768         file.setKeepDUChainData(true);
769         QVERIFY(file.waitForParsed());
770 
771         DUChainWriteLocker lock;
772         auto top = file.topContext();
773         QVERIFY(top);
774         auto sessionData = ParseSessionData::Ptr(dynamic_cast<ParseSessionData*>(top->ast().data()));
775         lock.unlock();
776         ParseSession session(sessionData);
777         lock.lock();
778         QVERIFY(session.data());
779         QVERIFY(top);
780 
781         auto envFile = QExplicitlySharedDataPointer<ClangParsingEnvironmentFile>(
782             dynamic_cast<ClangParsingEnvironmentFile*>(file.topContext()->parsingEnvironmentFile().data()));
783 
784         QCOMPARE(envFile->features(), astFeatures);
785         QVERIFY(envFile->featuresSatisfied(astFeatures));
786         QCOMPARE(envFile->environmentQuality(), ClangParsingEnvironment::Source);
787 
788         // if no environment is given, no update should be triggered
789         QVERIFY(!envFile->needsUpdate());
790 
791         // same env should also not trigger a reparse
792         ClangParsingEnvironment env = session.environment();
793         QCOMPARE(env.quality(), ClangParsingEnvironment::Source);
794         QVERIFY(!envFile->needsUpdate(&env));
795 
796         // but changing the environment should trigger an update
797         env.addIncludes(Path::List() << Path(QStringLiteral("/foo/bar/baz")));
798         QVERIFY(envFile->needsUpdate(&env));
799         envFile->setEnvironment(env);
800         QVERIFY(!envFile->needsUpdate(&env));
801 
802         // setting the environment quality higher should require an update
803         env.setQuality(ClangParsingEnvironment::BuildSystem);
804         QVERIFY(envFile->needsUpdate(&env));
805         envFile->setEnvironment(env);
806         QVERIFY(!envFile->needsUpdate(&env));
807 
808         // changing defines requires an update
809         env.addDefines(QHash<QString, QString>{ { "foo", "bar" } });
810         QVERIFY(envFile->needsUpdate(&env));
811 
812         // but only when changing the defines for the envFile's TU
813         const auto barTU = IndexedString("bar.cpp");
814         const auto oldTU = env.translationUnitUrl();
815         env.setTranslationUnitUrl(barTU);
816         QCOMPARE(env.translationUnitUrl(), barTU);
817         QVERIFY(!envFile->needsUpdate(&env));
818         env.setTranslationUnitUrl(oldTU);
819         QVERIFY(envFile->needsUpdate(&env));
820 
821         // update it again
822         envFile->setEnvironment(env);
823         QVERIFY(!envFile->needsUpdate(&env));
824         lastEnv = env;
825 
826         // now compare against a lower quality environment
827         // in such a case, we do not want to trigger an update
828         env.setQuality(ClangParsingEnvironment::Unknown);
829         env.setTranslationUnitUrl(barTU);
830         QVERIFY(!envFile->needsUpdate(&env));
831 
832         // even when the environment changes
833         env.addIncludes(Path::List() << Path(QStringLiteral("/lalalala")));
834         QVERIFY(!envFile->needsUpdate(&env));
835 
836         indexed = top->indexed();
837     }
838 
839     DUChain::self()->storeToDisk();
840 
841     {
842         DUChainWriteLocker lock;
843         QVERIFY(!DUChain::self()->isInMemory(indexed.index()));
844         QVERIFY(indexed.data());
845         QVERIFY(DUChain::self()->environmentFileForDocument(indexed));
846         auto envFile = QExplicitlySharedDataPointer<ClangParsingEnvironmentFile>(
847             dynamic_cast<ClangParsingEnvironmentFile*>(DUChain::self()->environmentFileForDocument(indexed).data()));
848         QVERIFY(envFile);
849 
850         QCOMPARE(envFile->features(), features);
851         QVERIFY(envFile->featuresSatisfied(features));
852         QVERIFY(!envFile->needsUpdate(&lastEnv));
853         DUChain::self()->removeDocumentChain(indexed.data());
854     }
855 }
856 
testActiveDocumentHasASTAttached()857 void TestDUChain::testActiveDocumentHasASTAttached()
858 {
859   const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses;
860 
861     IndexedTopDUContext indexed;
862     ClangParsingEnvironment lastEnv;
863     {
864         TestFile file(QStringLiteral("int main() {}\n"), QStringLiteral("cpp"));
865         const auto astFeatures = features | TopDUContext::AST;
866         file.parse(astFeatures);
867         file.setKeepDUChainData(true);
868         QVERIFY(file.waitForParsed());
869 
870         DUChainWriteLocker lock;
871         auto top = file.topContext();
872         QVERIFY(top);
873         auto sessionData = ParseSessionData::Ptr(dynamic_cast<ParseSessionData*>(top->ast().data()));
874         lock.unlock();
875         ParseSession session(sessionData);
876         lock.lock();
877         QVERIFY(session.data());
878         QVERIFY(top);
879         QVERIFY(top->ast());
880 
881         indexed = top->indexed();
882     }
883 
884     DUChain::self()->storeToDisk();
885 
886     {
887         DUChainWriteLocker lock;
888         QVERIFY(!DUChain::self()->isInMemory(indexed.index()));
889         QVERIFY(indexed.data());
890     }
891 
892     QUrl url;
893     {
894         DUChainReadLocker lock;
895         auto ctx = indexed.data();
896         QVERIFY(ctx);
897         QVERIFY(!ctx->ast());
898         url = ctx->url().toUrl();
899     }
900 
901     QVERIFY(!QFileInfo::exists(url.toLocalFile()));
902     QFile file(url.toLocalFile());
903     file.open(QIODevice::WriteOnly);
904     Q_ASSERT(file.isOpen());
905 
906     auto document = ICore::self()->documentController()->openDocument(url);
907     QVERIFY(document);
908     ICore::self()->documentController()->activateDocument(document);
909 
910     QApplication::processEvents();
911     ICore::self()->languageController()->backgroundParser()->parseDocuments();
912     QThread::sleep(1);
913 
914     document->close(KDevelop::IDocument::Discard);
915     {
916         DUChainReadLocker lock;
917         auto ctx = indexed.data();
918         QVERIFY(ctx);
919         QVERIFY(ctx->ast());
920     }
921 
922     DUChainWriteLocker lock;
923     DUChain::self()->removeDocumentChain(indexed.data());
924 }
925 
testActiveDocumentsGetBestPriority()926 void TestDUChain::testActiveDocumentsGetBestPriority()
927 {
928     // note: this test would make more sense in kdevplatform, but we don't have a language plugin available there
929     // (required for background parsing)
930     // TODO: Create a fake-language plugin in kdevplatform for testing purposes, use that.
931 
932     TestFile file1(QStringLiteral("int main() {}\n"), QStringLiteral("cpp"));
933     TestFile file2(QStringLiteral("int main() {}\n"), QStringLiteral("cpp"));
934     TestFile file3(QStringLiteral("int main() {}\n"), QStringLiteral("cpp"));
935 
936     DUChain::self()->storeToDisk();
937 
938     auto backgroundParser = ICore::self()->languageController()->backgroundParser();
939     QVERIFY(!backgroundParser->isQueued(file1.url()));
940 
941     auto documentController = ICore::self()->documentController();
942 
943     // open first document (no activation)
944     auto doc = documentController->openDocument(file1.url().toUrl(), KTextEditor::Range::invalid(), {IDocumentController::DoNotActivate});
945     QVERIFY(doc);
946     QVERIFY(backgroundParser->isQueued(file1.url()));
947     QCOMPARE(backgroundParser->priorityForDocument(file1.url()), (int)BackgroundParser::NormalPriority);
948 
949     // open second document, activate
950     doc = documentController->openDocument(file2.url().toUrl());
951     QVERIFY(doc);
952     QVERIFY(backgroundParser->isQueued(file2.url()));
953     QCOMPARE(backgroundParser->priorityForDocument(file2.url()), (int)BackgroundParser::BestPriority);
954 
955     // open third document, activate, too
956     doc = documentController->openDocument(file3.url().toUrl());
957     QVERIFY(doc);
958     QVERIFY(backgroundParser->isQueued(file3.url()));
959     QCOMPARE(backgroundParser->priorityForDocument(file3.url()), (int)BackgroundParser::BestPriority);
960 }
961 
testSystemIncludes()962 void TestDUChain::testSystemIncludes()
963 {
964     ClangParsingEnvironment env;
965 
966     Path::List projectIncludes = {
967         Path("/projects/1"),
968         Path("/projects/1/sub"),
969         Path("/projects/2"),
970         Path("/projects/2/sub")
971     };
972     env.addIncludes(projectIncludes);
973     auto includes = env.includes();
974     // no project paths set, so everything is considered a system include
975     QCOMPARE(includes.system, projectIncludes);
976     QVERIFY(includes.project.isEmpty());
977 
978     Path::List systemIncludes = {
979         Path("/sys"),
980         Path("/sys/sub")
981     };
982     env.addIncludes(systemIncludes);
983     includes = env.includes();
984     QCOMPARE(includes.system, projectIncludes + systemIncludes);
985     QVERIFY(includes.project.isEmpty());
986 
987     Path::List projects = {
988         Path("/projects/1"),
989         Path("/projects/2")
990     };
991     env.setProjectPaths(projects);
992     // now the list should be properly separated
993     QCOMPARE(env.projectPaths(), projects);
994     includes = env.includes();
995     QCOMPARE(includes.system, systemIncludes);
996     QCOMPARE(includes.project, projectIncludes);
997 }
998 
testReparseWithAllDeclarationsContextsAndUses()999 void TestDUChain::testReparseWithAllDeclarationsContextsAndUses()
1000 {
1001     TestFile file(QStringLiteral("int foo() { return 0; } int main() { return foo(); }"), QStringLiteral("cpp"));
1002     file.parse(TopDUContext::VisibleDeclarationsAndContexts);
1003 
1004     QVERIFY(file.waitForParsed(1000));
1005 
1006     {
1007         DUChainReadLocker lock;
1008         QVERIFY(file.topContext());
1009         QCOMPARE(file.topContext()->childContexts().size(), 2);
1010         QCOMPARE(file.topContext()->localDeclarations().size(), 2);
1011 
1012         auto dec = file.topContext()->localDeclarations().at(0);
1013         QEXPECT_FAIL("", "Skipping of function bodies is disabled for now", Continue);
1014         QVERIFY(dec->uses().isEmpty());
1015     }
1016 
1017     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1018 
1019     QVERIFY(file.waitForParsed(500));
1020 
1021     {
1022         DUChainReadLocker lock;
1023         QVERIFY(file.topContext());
1024         QCOMPARE(file.topContext()->childContexts().size(), 2);
1025         QCOMPARE(file.topContext()->localDeclarations().size(), 2);
1026 
1027         auto mainDecl = file.topContext()->localDeclarations()[1];
1028         QVERIFY(mainDecl->uses().isEmpty());
1029         auto foo = file.topContext()->localDeclarations().first();
1030         QCOMPARE(foo->uses().size(), 1);
1031     }
1032 }
1033 
testReparseOnDocumentActivated()1034 void TestDUChain::testReparseOnDocumentActivated()
1035 {
1036     TestFile file(QStringLiteral("int foo() { return 0; } int main() { return foo(); }"), QStringLiteral("cpp"));
1037     file.parse(TopDUContext::VisibleDeclarationsAndContexts);
1038 
1039     QVERIFY(file.waitForParsed(1000));
1040 
1041     {
1042         DUChainReadLocker lock;
1043         auto ctx = file.topContext();
1044         QVERIFY(ctx);
1045         QCOMPARE(ctx->childContexts().size(), 2);
1046         QCOMPARE(ctx->localDeclarations().size(), 2);
1047 
1048         auto dec = ctx->localDeclarations().at(0);
1049         QEXPECT_FAIL("", "Skipping of function bodies was disabled for now", Continue);
1050         QVERIFY(dec->uses().isEmpty());
1051 
1052         QVERIFY(!ctx->ast());
1053     }
1054 
1055     auto backgroundParser = ICore::self()->languageController()->backgroundParser();
1056     QVERIFY(!backgroundParser->isQueued(file.url()));
1057 
1058     auto doc = ICore::self()->documentController()->openDocument(file.url().toUrl());
1059     QVERIFY(doc);
1060     QVERIFY(backgroundParser->isQueued(file.url()));
1061 
1062     QSignalSpy spy(backgroundParser, &BackgroundParser::parseJobFinished);
1063     spy.wait();
1064 
1065     doc->close(KDevelop::IDocument::Discard);
1066 
1067     {
1068         DUChainReadLocker lock;
1069         auto ctx = file.topContext();
1070         QCOMPARE(ctx->features() & TopDUContext::AllDeclarationsContextsAndUses,
1071                  TopDUContext::AllDeclarationsContextsAndUses);
1072         QVERIFY(ctx->topContext()->ast());
1073     }
1074 }
1075 
testReparseInclude()1076 void TestDUChain::testReparseInclude()
1077 {
1078     TestFile header(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h"));
1079     TestFile impl("#include \"" + header.url().str() + "\"\n"
1080                   "int main() { return foo(); }", QStringLiteral("cpp"), &header);
1081 
1082     // Use TopDUContext::AST to imitate that document is opened in the editor, so that ClangParseJob can store translation unit, that'll be used for reparsing.
1083     impl.parse(TopDUContext::AllDeclarationsAndContexts | TopDUContext::AST);
1084     QVERIFY(impl.waitForParsed(5000));
1085     {
1086         DUChainReadLocker lock;
1087         auto implCtx = impl.topContext();
1088         QVERIFY(implCtx);
1089         QCOMPARE(implCtx->importedParentContexts().size(), 1);
1090     }
1091 
1092     impl.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST);
1093     QVERIFY(impl.waitForParsed(5000));
1094 
1095     DUChainReadLocker lock;
1096     auto implCtx = impl.topContext();
1097     QVERIFY(implCtx);
1098     QCOMPARE(implCtx->localDeclarations().size(), 1);
1099 
1100     QCOMPARE(implCtx->importedParentContexts().size(), 1);
1101 
1102     auto headerCtx = DUChain::self()->chainForDocument(header.url());
1103     QVERIFY(headerCtx);
1104     QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate());
1105     QCOMPARE(headerCtx->localDeclarations().size(), 1);
1106 
1107     QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10)));
1108 
1109     Declaration* foo = headerCtx->localDeclarations().first();
1110     QCOMPARE(foo->uses().size(), 1);
1111     QCOMPARE(foo->uses().begin().key(), impl.url());
1112     QCOMPARE(foo->uses().begin()->size(), 1);
1113     QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23));
1114 
1115     QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1);
1116     QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1);
1117     QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1);
1118     QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1);
1119 }
1120 
testReparseChangeEnvironment()1121 void TestDUChain::testReparseChangeEnvironment()
1122 {
1123     TestFile header(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h"));
1124     TestFile impl("#include \"" + header.url().str() + "\"\n"
1125                   "int main() { return foo(); }", QStringLiteral("cpp"), &header);
1126 
1127     uint hashes[3] = {0, 0, 0};
1128 
1129     for (int i = 0; i < 3; ++i) {
1130         impl.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST | TopDUContext::ForceUpdate);
1131         QVERIFY(impl.waitForParsed(5000));
1132 
1133         {
1134             DUChainReadLocker lock;
1135             QVERIFY(impl.topContext());
1136             auto env = dynamic_cast<ClangParsingEnvironmentFile*>(impl.topContext()->parsingEnvironmentFile().data());
1137             QVERIFY(env);
1138             QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source);
1139             hashes[i] = env->environmentHash();
1140             QVERIFY(hashes[i]);
1141 
1142             // we should never end up with multiple env files or chains in memory for these files
1143             QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1);
1144             QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1);
1145             QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1);
1146             QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1);
1147         }
1148 
1149         // in every run, we expect the environment to have changed
1150         for (int j = 0; j < i; ++j) {
1151             QVERIFY(hashes[i] != hashes[j]);
1152         }
1153 
1154         if (i == 0) {
1155             // 1) change defines
1156             m_provider->defines.insert(QStringLiteral("foooooooo"), QStringLiteral("baaar!"));
1157         } else if (i == 1) {
1158             // 2) change includes
1159             m_provider->includes.append(Path(QStringLiteral("/foo/bar/asdf/lalala")));
1160         } // 3) stop
1161     }
1162 }
1163 
testMacroDependentHeader()1164 void TestDUChain::testMacroDependentHeader()
1165 {
1166     TestFile header(QStringLiteral("struct MY_CLASS { struct Q{Q(); int m;}; int m; };\n"), QStringLiteral("h"));
1167     TestFile impl("#define MY_CLASS A\n"
1168                   "#include \"" + header.url().str() + "\"\n"
1169                   "#undef MY_CLASS\n"
1170                   "#define MY_CLASS B\n"
1171                   "#include \"" + header.url().str() + "\"\n"
1172                   "#undef MY_CLASS\n"
1173                   "A a;\n"
1174                   "const A::Q aq;\n"
1175                   "B b;\n"
1176                   "const B::Q bq;\n"
1177                   "int am = a.m;\n"
1178                   "int aqm = aq.m;\n"
1179                   "int bm = b.m;\n"
1180                   "int bqm = bq.m;\n"
1181                   , QStringLiteral("cpp"), &header);
1182 
1183     impl.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST | TopDUContext::ForceUpdate);
1184     QVERIFY(impl.waitForParsed(500000));
1185 
1186     DUChainReadLocker lock;
1187     TopDUContext* top = impl.topContext().data();
1188     QVERIFY(top);
1189     QCOMPARE(top->localDeclarations().size(), 10); // 2x macro, then a, aq, b, bq
1190     QCOMPARE(top->importedParentContexts().size(), 1);
1191     AbstractType::Ptr type = top->localDeclarations()[2]->abstractType();
1192     auto* sType = dynamic_cast<StructureType*>(type.data());
1193     QVERIFY(sType);
1194     QCOMPARE(sType->toString(), QString("A"));
1195     Declaration* decl = sType->declaration(top);
1196     QVERIFY(decl);
1197     AbstractType::Ptr type2 = top->localDeclarations()[4]->abstractType();
1198     auto* sType2 = dynamic_cast<StructureType*>(type2.data());
1199     QVERIFY(sType2);
1200     QCOMPARE(sType2->toString(), QString("B"));
1201     Declaration* decl2 = sType2->declaration(top);
1202     QVERIFY(decl2);
1203 
1204     TopDUContext* top2 = dynamic_cast<TopDUContext*>(top->importedParentContexts()[0].context(top));
1205     QVERIFY(top2);
1206     QCOMPARE(top2->localDeclarations().size(), 2);
1207     QCOMPARE(top2->localDeclarations()[0], decl);
1208     QCOMPARE(top2->localDeclarations()[1], decl2);
1209     qDebug() << "DECL RANGE:" << top2->localDeclarations()[0]->range().castToSimpleRange();
1210     qDebug() << "CTX RANGE:" << top2->localDeclarations()[0]->internalContext()->range().castToSimpleRange();
1211 
1212     // validate uses:
1213     QCOMPARE(top->usesCount(), 14);
1214     QCOMPARE(top->uses()[0].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A"));
1215     QCOMPARE(top->uses()[1].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A"));
1216     QCOMPARE(top->uses()[2].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q"));
1217     QCOMPARE(top->uses()[3].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B"));
1218     QCOMPARE(top->uses()[4].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B"));
1219     QCOMPARE(top->uses()[5].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q"));
1220     QCOMPARE(top->uses()[6].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a"));
1221     QCOMPARE(top->uses()[7].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::m"));
1222     QCOMPARE(top->uses()[8].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("aq"));
1223     QCOMPARE(top->uses()[9].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q::m"));
1224     QCOMPARE(top->uses()[10].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("b"));
1225     QCOMPARE(top->uses()[11].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::m"));
1226     QCOMPARE(top->uses()[12].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("bq"));
1227     QCOMPARE(top->uses()[13].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q::m"));
1228 }
1229 
testHeaderParsingOrder1()1230 void TestDUChain::testHeaderParsingOrder1()
1231 {
1232     TestFile header(QStringLiteral("typedef const A<int> B;\n"), QStringLiteral("h"));
1233     TestFile impl("template<class T> class A{};\n"
1234                   "#include \"" + header.url().str() + "\"\n"
1235                   "B c;", QStringLiteral("cpp"), &header);
1236 
1237     impl.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST | TopDUContext::ForceUpdate);
1238     QVERIFY(impl.waitForParsed(500000));
1239 
1240     DUChainReadLocker lock;
1241     TopDUContext* top = impl.topContext().data();
1242     QVERIFY(top);
1243     QCOMPARE(top->localDeclarations().size(), 2);
1244     QCOMPARE(top->importedParentContexts().size(), 1);
1245     AbstractType::Ptr type = top->localDeclarations()[1]->abstractType();
1246     auto* aType = dynamic_cast<TypeAliasType*>(type.data());
1247     QVERIFY(aType);
1248     AbstractType::Ptr targetType = aType->type();
1249     QVERIFY(targetType);
1250     auto *idType = dynamic_cast<IdentifiedType*>(targetType.data());
1251     QVERIFY(idType);
1252     // this declaration could be resolved, because it was created with an
1253     // indirect DeclarationId that is resolved from the perspective of 'top'
1254     Declaration* decl = idType->declaration(top);
1255     // NOTE: the decl. doesn't know (yet) about the template insantiation <int>
1256     QVERIFY(decl);
1257     QCOMPARE(decl, top->localDeclarations()[0]);
1258 
1259     // now ensure that a use was build for 'A' in header1
1260     TopDUContext* top2 = dynamic_cast<TopDUContext*>(top->importedParentContexts()[0].context(top));
1261     QVERIFY(top2);
1262     QEXPECT_FAIL("", "the use could not be created because the corresponding declaration didn't exist yet", Continue);
1263     QCOMPARE(top2->usesCount(), 1);
1264     // Declaration* decl2 = top2->uses()[0].usedDeclaration(top2);
1265     // QVERIFY(decl2);
1266     // QCOMPARE(decl, decl2);
1267 }
1268 
testHeaderParsingOrder2()1269 void TestDUChain::testHeaderParsingOrder2()
1270 {
1271     TestFile header(QStringLiteral("template<class T> class A{};\n"), QStringLiteral("h"));
1272     TestFile header2(QStringLiteral("typedef const A<int> B;\n"), QStringLiteral("h"));
1273     TestFile impl("#include \"" + header.url().str() + "\"\n"
1274                   "#include \"" + header2.url().str() + "\"\n"
1275                   "B c;", QStringLiteral("cpp"), &header);
1276 
1277     impl.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST | TopDUContext::ForceUpdate);
1278     QVERIFY(impl.waitForParsed(500000));
1279 
1280     DUChainReadLocker lock;
1281     TopDUContext* top = impl.topContext().data();
1282     QVERIFY(top);
1283     QCOMPARE(top->localDeclarations().size(), 1);
1284     QCOMPARE(top->importedParentContexts().size(), 2);
1285     AbstractType::Ptr type = top->localDeclarations()[0]->abstractType();
1286     auto* aType = dynamic_cast<TypeAliasType*>(type.data());
1287     QVERIFY(aType);
1288     AbstractType::Ptr targetType = aType->type();
1289     QVERIFY(targetType);
1290     auto *idType = dynamic_cast<IdentifiedType*>(targetType.data());
1291     QVERIFY(idType);
1292     Declaration* decl = idType->declaration(top);
1293     // NOTE: the decl. doesn't know (yet) about the template insantiation <int>
1294     QVERIFY(decl);
1295 
1296     // now ensure that a use was build for 'A' in header2
1297     TopDUContext* top2 = dynamic_cast<TopDUContext*>(top->importedParentContexts()[1].context(top));
1298     QVERIFY(top2);
1299     QCOMPARE(top2->usesCount(), 1);
1300     Declaration* decl2 = top2->uses()[0].usedDeclaration(top2);
1301     QCOMPARE(decl, decl2);
1302 }
1303 
testMacrosRanges()1304 void TestDUChain::testMacrosRanges()
1305 {
1306     TestFile file(QStringLiteral("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x);"), QStringLiteral("cpp"));
1307     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1308     QVERIFY(file.waitForParsed(5000));
1309 
1310     DUChainReadLocker lock;
1311     QVERIFY(file.topContext());
1312     QCOMPARE(file.topContext()->localDeclarations().size(), 2);
1313     auto macroDefinition = file.topContext()->localDeclarations()[0];
1314     QVERIFY(macroDefinition);
1315     QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19));
1316     auto structDeclaration = file.topContext()->localDeclarations()[1];
1317     QVERIFY(structDeclaration);
1318     QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0));
1319 
1320     QCOMPARE(macroDefinition->uses().size(), 1);
1321     QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11));
1322 }
1323 
testMacroUses()1324 void TestDUChain::testMacroUses()
1325 {
1326     TestFile file(QStringLiteral("#define USER(x) x\n#define USED\nUSER(USED)"), QStringLiteral("cpp"));
1327     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1328     QVERIFY(file.waitForParsed(5000));
1329 
1330     DUChainReadLocker lock;
1331     QVERIFY(file.topContext());
1332     QCOMPARE(file.topContext()->localDeclarations().size(), 2);
1333     auto macroDefinition1 = file.topContext()->localDeclarations()[0];
1334     auto macroDefinition2 = file.topContext()->localDeclarations()[1];
1335 
1336     QCOMPARE(macroDefinition1->uses().size(), 1);
1337     QCOMPARE(macroDefinition1->uses().begin()->first(), RangeInRevision(2,0,2,4));
1338 #if CINDEX_VERSION_MINOR < 32
1339     QEXPECT_FAIL("", "This appears to be a clang bug, the AST doesn't contain the macro use", Continue);
1340 #endif
1341     QCOMPARE(macroDefinition2->uses().size(), 1);
1342     if (macroDefinition2->uses().size())
1343     {
1344         QCOMPARE(macroDefinition2->uses().begin()->first(), RangeInRevision(2,5,2,9));
1345     }
1346 }
1347 
testMultiLineMacroRanges()1348 void TestDUChain::testMultiLineMacroRanges()
1349 {
1350     TestFile file(QStringLiteral("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x\n);"), QStringLiteral("cpp"));
1351     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1352     QVERIFY(file.waitForParsed(5000));
1353 
1354     DUChainReadLocker lock;
1355     QVERIFY(file.topContext());
1356     QCOMPARE(file.topContext()->localDeclarations().size(), 2);
1357     auto macroDefinition = file.topContext()->localDeclarations()[0];
1358     QVERIFY(macroDefinition);
1359     QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19));
1360     auto structDeclaration = file.topContext()->localDeclarations()[1];
1361     QVERIFY(structDeclaration);
1362     QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0));
1363 
1364     QCOMPARE(macroDefinition->uses().size(), 1);
1365     QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11));
1366 }
1367 
testNestedMacroRanges()1368 void TestDUChain::testNestedMacroRanges()
1369 {
1370     TestFile file(QStringLiteral("#define INNER int var; var = 0;\n#define MACRO() INNER\nint main(){MACRO(\n);}"), QStringLiteral("cpp"));
1371     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1372     QVERIFY(file.waitForParsed(5000));
1373 
1374     DUChainReadLocker lock;
1375     QVERIFY(file.topContext());
1376     QCOMPARE(file.topContext()->localDeclarations().size(), 3);
1377     auto main = file.topContext()->localDeclarations()[2];
1378     QVERIFY(main);
1379     auto mainCtx = main->internalContext()->childContexts().first();
1380     QVERIFY(mainCtx);
1381     QCOMPARE(mainCtx->localDeclarations().size(), 1);
1382     auto var = mainCtx->localDeclarations().first();
1383     QVERIFY(var);
1384     QCOMPARE(var->range(), RangeInRevision(2,11,2,11));
1385 
1386     QCOMPARE(var->uses().size(), 1);
1387     QCOMPARE(var->uses().begin()->first(), RangeInRevision(2,11,2,11));
1388 }
1389 
testNestedImports()1390 void TestDUChain::testNestedImports()
1391 {
1392     TestFile B(QStringLiteral("#pragma once\nint B();\n"), QStringLiteral("h"));
1393     TestFile C("#pragma once\n#include \"" + B.url().str() + "\"\nint C();\n", QStringLiteral("h"));
1394     TestFile A("#include \"" + B.url().str() + "\"\n" + "#include \"" + C.url().str() + "\"\nint A();\n", QStringLiteral("cpp"));
1395 
1396     A.parse();
1397     QVERIFY(A.waitForParsed(5000));
1398 
1399     DUChainReadLocker lock;
1400 
1401     auto BCtx = DUChain::self()->chainForDocument(B.url().toUrl());
1402     QVERIFY(BCtx);
1403     QVERIFY(BCtx->importedParentContexts().isEmpty());
1404 
1405     auto CCtx = DUChain::self()->chainForDocument(C.url().toUrl());
1406     QVERIFY(CCtx);
1407     QCOMPARE(CCtx->importedParentContexts().size(), 1);
1408     QVERIFY(CCtx->imports(BCtx, CursorInRevision(1, 10)));
1409 
1410     auto ACtx = A.topContext();
1411     QVERIFY(ACtx);
1412     QCOMPARE(ACtx->importedParentContexts().size(), 2);
1413     QVERIFY(ACtx->imports(BCtx, CursorInRevision(0, 10)));
1414     QVERIFY(ACtx->imports(CCtx, CursorInRevision(1, 10)));
1415 }
1416 
testEnvironmentWithDifferentOrderOfElements()1417 void TestDUChain::testEnvironmentWithDifferentOrderOfElements()
1418 {
1419     TestFile file(QStringLiteral("int main();\n"), QStringLiteral("cpp"));
1420 
1421     m_provider->includes.clear();
1422     m_provider->includes.append(Path(QStringLiteral("/path1")));
1423     m_provider->includes.append(Path(QStringLiteral("/path2")));
1424 
1425     m_provider->defines.clear();
1426     m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1"));
1427     m_provider->defines.insert(QStringLiteral("key2"), QStringLiteral("value2"));
1428     m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3"));
1429 
1430     uint previousHash = 0;
1431     for (int i: {0, 1, 2, 3}) {
1432         file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST | TopDUContext::ForceUpdate);
1433 
1434         QVERIFY(file.waitForParsed(5000));
1435 
1436         {
1437             DUChainReadLocker lock;
1438             QVERIFY(file.topContext());
1439             auto env = dynamic_cast<ClangParsingEnvironmentFile*>(file.topContext()->parsingEnvironmentFile().data());
1440             QVERIFY(env);
1441             QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source);
1442             if (previousHash) {
1443                 if (i == 3) {
1444                     QVERIFY(previousHash != env->environmentHash());
1445                 } else {
1446                     QCOMPARE(previousHash, env->environmentHash());
1447                 }
1448             }
1449             previousHash = env->environmentHash();
1450             QVERIFY(previousHash);
1451         }
1452 
1453         if (i == 0) {
1454             //Change order of defines. Hash of the environment should stay the same.
1455             m_provider->defines.clear();
1456             m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3"));
1457             m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1"));
1458             m_provider->defines.insert(QStringLiteral("key2"), QStringLiteral("value2"));
1459         } else if (i == 1) {
1460             //Add the same macros twice. Hash of the environment should stay the same.
1461             m_provider->defines.clear();
1462             m_provider->defines.insert(QStringLiteral("key2"), QStringLiteral("value2"));
1463             m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3"));
1464             m_provider->defines.insert(QStringLiteral("key3"), QStringLiteral("value3"));
1465             m_provider->defines.insert(QStringLiteral("key1"), QStringLiteral("value1"));
1466         } else if (i == 2) {
1467             //OTOH order of includes should change hash of the environment.
1468             m_provider->includes.clear();
1469             m_provider->includes.append(Path(QStringLiteral("/path2")));
1470             m_provider->includes.append(Path(QStringLiteral("/path1")));
1471         }
1472     }
1473 }
1474 
testReparseMacro()1475 void TestDUChain::testReparseMacro()
1476 {
1477     TestFile file(QStringLiteral("#define DECLARE(a) typedef struct a##_ {} *a;\nDECLARE(D);\nD d;"), QStringLiteral("cpp"));
1478     file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST);
1479     QVERIFY(file.waitForParsed(5000));
1480 
1481     {
1482         DUChainReadLocker lock;
1483         QVERIFY(file.topContext());
1484     }
1485 
1486     file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST | TopDUContext::ForceUpdate);
1487     QVERIFY(file.waitForParsed(5000));
1488 
1489     DUChainReadLocker lock;
1490     QVERIFY(file.topContext());
1491     QCOMPARE(file.topContext()->localDeclarations().size(), 5);
1492 
1493     auto macroDefinition = file.topContext()->localDeclarations()[0];
1494     QVERIFY(macroDefinition);
1495     QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,15));
1496     QCOMPARE(macroDefinition->uses().size(), 1);
1497     QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,7));
1498 
1499     auto structDeclaration = file.topContext()->localDeclarations()[1];
1500     QVERIFY(structDeclaration);
1501     QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0));
1502 
1503     auto structTypedef = file.topContext()->localDeclarations()[3];
1504     QVERIFY(structTypedef);
1505     QCOMPARE(structTypedef->range(), RangeInRevision(1,8,1,9));
1506     QCOMPARE(structTypedef->uses().size(), 1);
1507     QCOMPARE(structTypedef->uses().begin()->first(), RangeInRevision(2,0,2,1));
1508 }
1509 
testGotoStatement()1510 void TestDUChain::testGotoStatement()
1511 {
1512     TestFile file(QStringLiteral("int main() {\ngoto label;\ngoto label;\nlabel: return 0;}"), QStringLiteral("cpp"));
1513     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1514     QVERIFY(file.waitForParsed(5000));
1515 
1516     DUChainReadLocker lock;
1517     QVERIFY(file.topContext());
1518     QCOMPARE(file.topContext()->localDeclarations().size(), 1);
1519     auto main = file.topContext()->localDeclarations()[0];
1520     QVERIFY(main);
1521     auto mainCtx = main->internalContext()->childContexts().first();
1522     QVERIFY(mainCtx);
1523     QCOMPARE(mainCtx->localDeclarations().size(), 1);
1524     auto label = mainCtx->localDeclarations().first();
1525     QVERIFY(label);
1526     QCOMPARE(label->range(), RangeInRevision(3,0,3,5));
1527 
1528     QCOMPARE(label->uses().size(), 1);
1529     QCOMPARE(label->uses().begin()->first(), RangeInRevision(1,5,1,10));
1530     QCOMPARE(label->uses().begin()->last(), RangeInRevision(2,5,2,10));
1531 }
1532 
testRangesOfOperatorsInsideMacro()1533 void TestDUChain::testRangesOfOperatorsInsideMacro()
1534 {
1535     TestFile file(QStringLiteral("class Test{public: Test& operator++(int);};\n#define MACRO(var) var++;\nint main(){\nTest tst; MACRO(tst)}"), QStringLiteral("cpp"));
1536     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1537     QVERIFY(file.waitForParsed(5000));
1538 
1539     DUChainReadLocker lock;
1540     QVERIFY(file.topContext());
1541     QCOMPARE(file.topContext()->localDeclarations().size(), 3);
1542     auto testClass = file.topContext()->localDeclarations()[0];
1543     QVERIFY(testClass);
1544     auto operatorPlusPlus = testClass->internalContext()->localDeclarations().first();
1545     QVERIFY(operatorPlusPlus);
1546     QCOMPARE(operatorPlusPlus->uses().size(), 1);
1547     QCOMPARE(operatorPlusPlus->uses().begin()->first(), RangeInRevision(3,10,3,10));
1548 }
1549 
testUsesCreatedForDeclarations()1550 void TestDUChain::testUsesCreatedForDeclarations()
1551 {
1552     auto code = R"(template<typename T> void functionTemplate(T);
1553             template<typename U> void functionTemplate(U) {}
1554 
1555             namespace NS { class Class{}; }
1556             using NS::Class;
1557 
1558             Class function();
1559             NS::Class function() { return {}; }
1560 
1561             int main () {
1562                 functionTemplate(int());
1563                 function(); }
1564     )";
1565     TestFile file(code, QStringLiteral("cpp"));
1566     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1567     QVERIFY(file.waitForParsed());
1568 
1569     DUChainReadLocker lock;
1570     QVERIFY(file.topContext());
1571 
1572     auto functionTemplate = file.topContext()->findDeclarations(QualifiedIdentifier(QStringLiteral("functionTemplate")));
1573     QVERIFY(!functionTemplate.isEmpty());
1574     auto functionTemplateDeclaration = DUChainUtils::declarationForDefinition(functionTemplate.first());
1575     QVERIFY(!functionTemplateDeclaration->isDefinition());
1576 #if CINDEX_VERSION_MINOR < 29
1577     QEXPECT_FAIL("", "No API in LibClang to determine function template type", Continue);
1578 #endif
1579     QCOMPARE(functionTemplateDeclaration->uses().count(), 1);
1580 
1581     auto function = file.topContext()->findDeclarations(QualifiedIdentifier(QStringLiteral("function")));
1582     QVERIFY(!function.isEmpty());
1583     auto functionDeclaration = DUChainUtils::declarationForDefinition(function.first());
1584     QVERIFY(!functionDeclaration->isDefinition());
1585     QCOMPARE(functionDeclaration->uses().count(), 1);
1586 }
1587 
testReparseIncludeGuard()1588 void TestDUChain::testReparseIncludeGuard()
1589 {
1590     TestFile header(QStringLiteral("#ifndef GUARD\n#define GUARD\nint something;\n#endif\n"), QStringLiteral("h"));
1591     TestFile impl("#include \"" + header.url().str() + "\"\n", QStringLiteral("cpp"), &header);
1592 
1593     QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST));
1594     {
1595         DUChainReadLocker lock;
1596         QCOMPARE(static_cast<TopDUContext*>(impl.topContext()->
1597             importedParentContexts().first().context(impl.topContext()))->problems().size(), 0);
1598     }
1599     QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive));
1600     {
1601         DUChainReadLocker lock;
1602         QCOMPARE(static_cast<TopDUContext*>(impl.topContext()->
1603             importedParentContexts().first().context(impl.topContext()))->problems().size(), 0);
1604     }
1605 }
1606 
testIncludeGuardHeaderHeaderOnly()1607 void TestDUChain::testIncludeGuardHeaderHeaderOnly()
1608 {
1609     QFETCH(QString, code);
1610 
1611     // test that we do NOT get a warning for single standalone header file with include guards
1612     TestFile header(code, QStringLiteral("h"));
1613 
1614     QVERIFY(header.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST));
1615     {
1616         DUChainReadLocker lock;
1617         QCOMPARE(header.topContext()->problems().size(), 0);
1618     }
1619 }
1620 
testIncludeGuardHeaderHeaderOnly_data()1621 void TestDUChain::testIncludeGuardHeaderHeaderOnly_data()
1622 {
1623     QTest::addColumn<QString>("code");
1624 
1625     QTest::newRow("guard-ifdef") << QStringLiteral(
1626         "#ifndef GUARD\n"
1627         "#define GUARD\n"
1628         "int something;\n"
1629         "#endif\n"
1630     );
1631 
1632     QTest::newRow("guard-pragma") << QStringLiteral(
1633         "#pragma once\n"
1634         "int something;\n"
1635     );
1636 }
1637 
testIncludeGuardHeaderWarning()1638 void TestDUChain::testIncludeGuardHeaderWarning()
1639 {
1640     // test that we do get a warning for a header file without include guards
1641     TestFile header(QStringLiteral("int something;\n"), QStringLiteral("h"));
1642     TestFile impl("#include \"" + header.url().str() + "\"\n", QStringLiteral("cpp"));
1643 
1644     QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST | TopDUContext::ForceUpdate));
1645     {
1646         DUChainReadLocker lock;
1647         QVERIFY(impl.topContext());
1648 
1649         auto context = DUChain::self()->chainForDocument(impl.url());
1650         QVERIFY(context);
1651         QCOMPARE(context->problems().size(), 0);
1652 
1653         context = DUChain::self()->chainForDocument(header.url());
1654         QVERIFY(context);
1655         QCOMPARE(context->problems().size(), 1);
1656     }
1657 }
1658 
testExternC()1659 void TestDUChain::testExternC()
1660 {
1661     auto code = R"(extern "C" { void foo(); })";
1662     TestFile file(code, QStringLiteral("cpp"));
1663     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1664     QVERIFY(file.waitForParsed());
1665 
1666     DUChainReadLocker lock;
1667     auto top = file.topContext();
1668     QVERIFY(top);
1669     QVERIFY(!top->findDeclarations(QualifiedIdentifier("foo")).isEmpty());
1670 }
1671 
testIncludeExternC()1672 void TestDUChain::testIncludeExternC()
1673 {
1674     TestFile header(QStringLiteral("int foo() { return 42; }\n"), QStringLiteral("h"));
1675     // NOTE: header is _not_ explicitly being parsed, instead the impl job does that
1676 
1677     const auto code = R"(
1678         extern "C" {
1679             #include "%1"
1680         }
1681         int main() { return foo(); }
1682     )";
1683     TestFile impl(QString::fromUtf8(code).arg(header.url().str()), QStringLiteral("cpp"), &header);
1684     impl.parse(TopDUContext::AllDeclarationsContextsAndUses);
1685     QVERIFY(impl.waitForParsed());
1686 
1687     DUChainReadLocker lock;
1688 
1689     auto implCtx = impl.topContext();
1690     QVERIFY(implCtx);
1691     QCOMPARE(implCtx->localDeclarations().size(), 1);
1692 
1693     auto headerCtx = DUChain::self()->chainForDocument(header.url());
1694     QVERIFY(headerCtx);
1695     QCOMPARE(headerCtx->localDeclarations().size(), 1);
1696     Declaration* foo = headerCtx->localDeclarations().first();
1697     QCOMPARE(foo->uses().size(), 1);
1698     QCOMPARE(foo->uses().begin().key(), impl.url());
1699     QCOMPARE(foo->uses().begin()->size(), 1);
1700     QCOMPARE(foo->uses().begin()->first(), RangeInRevision(4, 28, 4, 31));
1701 }
1702 
testReparseUnchanged_data()1703 void TestDUChain::testReparseUnchanged_data()
1704 {
1705     QTest::addColumn<QString>("headerCode");
1706     QTest::addColumn<QString>("implCode");
1707 
1708     QTest::newRow("include-guards") << R"(
1709         #ifndef GUARD
1710         #define GUARD
1711         int something;
1712         #endif
1713     )" << R"(
1714         #include "%1"
1715     )";
1716 
1717     QTest::newRow("template-default-parameters") << R"(
1718         #ifndef TEST_H
1719         #define TEST_H
1720 
1721         template<unsigned T=123, unsigned... U>
1722         class dummy;
1723 
1724         template<unsigned T, unsigned... U>
1725         class dummy {
1726             int field[T];
1727         };
1728 
1729         #endif
1730     )" << R"(
1731         #include "%1"
1732 
1733         int main(int, char **) {
1734             dummy<> x;
1735             (void)x;
1736         }
1737     )";
1738 }
1739 
testReparseUnchanged()1740 void TestDUChain::testReparseUnchanged()
1741 {
1742     QFETCH(QString, headerCode);
1743     QFETCH(QString, implCode);
1744     TestFile header(headerCode, QStringLiteral("h"));
1745     TestFile impl(implCode.arg(header.url().str()), QStringLiteral("cpp"), &header);
1746 
1747     auto checkProblems = [&] (bool reparsed) {
1748         DUChainReadLocker lock;
1749         auto headerCtx = DUChain::self()->chainForDocument(header.url());
1750         QVERIFY(headerCtx);
1751         QVERIFY(headerCtx->problems().isEmpty());
1752         auto implCtx = DUChain::self()->chainForDocument(impl.url());
1753         QVERIFY(implCtx);
1754         if (reparsed && CINDEX_VERSION_MINOR > 29 && CINDEX_VERSION_MINOR < 33) {
1755             QEXPECT_FAIL("template-default-parameters", "the precompiled preamble messes the default template parameters up in clang 3.7", Continue);
1756         }
1757         QVERIFY(implCtx->problems().isEmpty());
1758     };
1759 
1760     impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST);
1761     checkProblems(false);
1762 
1763     impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive);
1764     checkProblems(true);
1765 }
1766 
testTypeAliasTemplate()1767 void TestDUChain::testTypeAliasTemplate()
1768 {
1769     TestFile file(QStringLiteral("template <typename T> using Alias = T; using Foo = Alias<int>;"), QStringLiteral("cpp"));
1770     QVERIFY(file.parseAndWait());
1771 
1772     DUChainReadLocker lock;
1773     QVERIFY(file.topContext());
1774 
1775     QCOMPARE(file.topContext()->localDeclarations().size(), 2);
1776     auto templateAlias = file.topContext()->localDeclarations().first();
1777     QVERIFY(templateAlias);
1778 #if CINDEX_VERSION_MINOR < 31
1779     QEXPECT_FAIL("", "TypeAliasTemplate is not exposed via LibClang", Abort);
1780 #endif
1781     QVERIFY(templateAlias->isTypeAlias());
1782     QVERIFY(templateAlias->abstractType());
1783     QCOMPARE(templateAlias->abstractType()->toString(), QStringLiteral("Alias"));
1784     QCOMPARE(templateAlias->uses().size(), 1);
1785     QCOMPARE(templateAlias->uses().first().size(), 1);
1786     QCOMPARE(templateAlias->uses().first().first(), RangeInRevision(0, 51, 0, 56));
1787 }
1788 
testDeclarationsInsideMacroExpansion()1789 void TestDUChain::testDeclarationsInsideMacroExpansion()
1790 {
1791     TestFile header(QStringLiteral("#define DECLARE(a) typedef struct a##__ {int var;} *a\nDECLARE(D);\n"), QStringLiteral("h"));
1792     TestFile file("#include \"" + header.url().str() + "\"\nint main(){\nD d; d->var;}\n", QStringLiteral("cpp"));
1793 
1794     file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST);
1795     QVERIFY(file.waitForParsed(5000));
1796 
1797     {
1798         DUChainReadLocker lock;
1799         QVERIFY(file.topContext());
1800     }
1801 
1802     file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST | TopDUContext::ForceUpdate);
1803     QVERIFY(file.waitForParsed(5000));
1804 
1805     DUChainReadLocker lock;
1806     QVERIFY(file.topContext());
1807     QCOMPARE(file.topContext()->localDeclarations().size(), 1);
1808 
1809     auto context = file.topContext()->childContexts().first()->childContexts().first();
1810     QVERIFY(context);
1811     QCOMPARE(context->localDeclarations().size(), 1);
1812     QCOMPARE(context->usesCount(), 3);
1813 
1814     QCOMPARE(context->uses()[0].m_range, RangeInRevision({2, 0}, {2, 1}));
1815     QCOMPARE(context->uses()[1].m_range, RangeInRevision({2, 5}, {2, 6}));
1816     QCOMPARE(context->uses()[2].m_range, RangeInRevision({2, 8}, {2, 11}));
1817 }
1818 
1819 // see also: https://bugs.kde.org/show_bug.cgi?id=368067
testForwardTemplateTypeParameterContext()1820 void TestDUChain::testForwardTemplateTypeParameterContext()
1821 {
1822     TestFile file(QStringLiteral(R"(
1823         template<typename MatchingName> class Foo;
1824 
1825         class MatchingName { void bar(); };
1826         void MatchingName::bar() {  }
1827     )"), QStringLiteral("cpp"));
1828 
1829     file.parse();
1830     QVERIFY(file.waitForParsed(500));
1831     DUChainReadLocker lock;
1832     const auto top = file.topContext();
1833     QVERIFY(top);
1834     DUChainDumper dumper(DUChainDumper::Features(DUChainDumper::DumpContext | DUChainDumper::DumpProblems));
1835     dumper.dump(top);
1836 
1837     auto declarations = top->localDeclarations();
1838     QCOMPARE(declarations.size(), 2);
1839 }
1840 
1841 // see also: https://bugs.kde.org/show_bug.cgi?id=368460
testTemplateFunctionParameterName()1842 void TestDUChain::testTemplateFunctionParameterName()
1843 {
1844     TestFile file(QStringLiteral(R"(
1845         template<class T>
1846         void foo(int name);
1847 
1848         void bar(int name);
1849     )"), QStringLiteral("cpp"));
1850 
1851     file.parse();
1852     QVERIFY(file.waitForParsed(500));
1853     DUChainReadLocker lock;
1854     const auto top = file.topContext();
1855     QVERIFY(top);
1856     DUChainDumper dumper(DUChainDumper::Features(DUChainDumper::DumpContext | DUChainDumper::DumpProblems));
1857     dumper.dump(top);
1858 
1859     const auto declarations = top->localDeclarations();
1860     QCOMPARE(declarations.size(), 2);
1861 
1862     for (auto decl : declarations) {
1863         auto ctx = DUChainUtils::argumentContext(decl);
1864         QVERIFY(ctx);
1865         auto args = ctx->localDeclarations();
1866         if (decl == declarations.first())
1867             QEXPECT_FAIL("", "We get two declarations, for both template and args :(", Continue);
1868         QCOMPARE(args.size(), 1);
1869         if (decl == declarations.first())
1870             QEXPECT_FAIL("", "see above, this then triggers T T here", Continue);
1871         QCOMPARE(args.first()->toString(), QStringLiteral("int name"));
1872     }
1873 }
1874 
containsErrors(const QList<Problem::Ptr> & problems)1875 static bool containsErrors(const QList<Problem::Ptr>& problems)
1876 {
1877     auto it = std::find_if(problems.begin(), problems.end(), [] (const Problem::Ptr& problem) {
1878         return problem->severity() == Problem::Error;
1879     });
1880     return it != problems.end();
1881 }
1882 
expectedXmmintrinErrors(const QList<Problem::Ptr> & problems)1883 static bool expectedXmmintrinErrors(const QList<Problem::Ptr>& problems)
1884 {
1885     for (const auto& problem : problems) {
1886         if (problem->severity() == Problem::Error && !problem->description().contains(QLatin1String("Cannot initialize a parameter of type"))) {
1887             return false;
1888         }
1889     }
1890     return true;
1891 }
1892 
verifyNoErrors(TopDUContext * top,QSet<TopDUContext * > & checked)1893 static void verifyNoErrors(TopDUContext* top, QSet<TopDUContext*>& checked)
1894 {
1895     const auto problems = top->problems();
1896     if (containsErrors(problems)) {
1897         qDebug() << top->url() << top->problems();
1898         if (top->url().str().endsWith(QLatin1String("xmmintrin.h")) && expectedXmmintrinErrors(problems)) {
1899             QEXPECT_FAIL("", "there are still some errors in xmmintrin.h b/c some clang provided intrinsincs are more strict than the GCC ones.", Continue);
1900             QVERIFY(false);
1901         } else {
1902             QFAIL("parse error detected");
1903         }
1904     }
1905     const auto imports = top->importedParentContexts();
1906     for (const auto& import : imports) {
1907         auto ctx = import.context(top);
1908         QVERIFY(ctx);
1909         auto importedTop = ctx->topContext();
1910         if (checked.contains(importedTop)) {
1911             continue;
1912         }
1913         checked.insert(importedTop);
1914         verifyNoErrors(importedTop, checked);
1915     }
1916 }
1917 
testFriendDeclaration()1918 void TestDUChain::testFriendDeclaration()
1919 {
1920     TestFile file(QStringLiteral(R"(
1921         struct FriendFoo
1922         {
1923             friend class FriendBar;
1924         };
1925 
1926         class FriendBar{};
1927 
1928         FriendBar friendBar;
1929     )"), QStringLiteral("cpp"));
1930     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1931 
1932     QVERIFY(file.waitForParsed(1000));
1933     {
1934         DUChainReadLocker lock;
1935         QVERIFY(file.topContext());
1936         QCOMPARE(file.topContext()->localDeclarations().size(), 3);
1937 
1938         auto friendBar = file.topContext()->localDeclarations()[1];
1939         if (CINDEX_VERSION_MINOR < 37) {
1940             QEXPECT_FAIL("", "Your clang version is too old", Abort);
1941         }
1942         QCOMPARE(friendBar->uses().size(), 1);
1943         QCOMPARE(friendBar->uses().begin()->first(), RangeInRevision(3,25,3,34));
1944         QCOMPARE(friendBar->uses().begin()->last(), RangeInRevision(8,8,8,17));
1945     }
1946 }
1947 
testVariadicTemplateArguments()1948 void TestDUChain::testVariadicTemplateArguments()
1949 {
1950     TestFile file(QStringLiteral(R"(
1951         template<typename T, typename... Targs>
1952         class VariadicTemplate {};
1953 
1954         VariadicTemplate<int, double, bool> variadic;
1955     )"), QStringLiteral("cpp"));
1956     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
1957 
1958     QVERIFY(file.waitForParsed(1000));
1959     {
1960         DUChainReadLocker lock;
1961         QVERIFY(file.topContext());
1962         QCOMPARE(file.topContext()->localDeclarations().size(), 2);
1963 
1964         auto decl = file.topContext()->localDeclarations()[1];
1965         QVERIFY(decl);
1966         if (CINDEX_VERSION_MINOR < 37) {
1967             QEXPECT_FAIL("", "Your clang version is too old", Abort);
1968         }
1969         QCOMPARE(decl->toString(), QStringLiteral("VariadicTemplate< int, double, bool > variadic"));
1970 
1971         QVERIFY(decl->abstractType());
1972         QCOMPARE(decl->abstractType()->toString(), QStringLiteral("VariadicTemplate< int, double, bool >"));
1973     }
1974 }
1975 
testProblemRequestedHere()1976 void TestDUChain::testProblemRequestedHere()
1977 {
1978     auto headerCode = QStringLiteral(R"(
1979 #pragma once
1980 
1981 template <typename T>
1982 T AddObjects(const T& a, const T& b)
1983 {
1984   return a + b;
1985 }
1986     )");
1987     TestFile header(headerCode, QStringLiteral("h"));
1988 
1989     QString sourceCode = QStringLiteral(R"(
1990 #include "%1"
1991 struct A {};
1992 
1993 int main()
1994 {
1995     A a, b;
1996     AddObjects(a, b);
1997     return 0;
1998 }
1999     )").arg(header.url().str());
2000     TestFile impl(sourceCode, QStringLiteral("cpp"), &header);
2001     QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses));
2002 
2003     DUChainReadLocker lock;
2004 
2005     auto top = impl.topContext();
2006     QVERIFY(top);
2007 
2008     auto* headerCtx = dynamic_cast<TopDUContext*>(top->importedParentContexts().first().context(top));
2009     QVERIFY(headerCtx);
2010     QCOMPARE(headerCtx->url(), header.url());
2011 
2012     QCOMPARE(headerCtx->problems().count(), 1);
2013     QCOMPARE(headerCtx->localDeclarations().count(), 1);
2014 
2015     // Verify that the problem is reported for the source file.
2016     QCOMPARE(top->problems().count(), 1);
2017     QCOMPARE(top->localDeclarations().count(), 2);
2018 }
2019 
testProblemRequestedHereSameFile()2020 void TestDUChain::testProblemRequestedHereSameFile()
2021 {
2022     auto sourceCode = QStringLiteral(R"(
2023 struct A {};
2024 
2025 template <typename T>
2026 T AddObjects(const T& a, const T& b)
2027 {
2028   return a + b;
2029 }
2030 
2031 int main()
2032 {
2033     A a, b;
2034     AddObjects(a, b);
2035     return 0;
2036 }
2037     )");
2038     TestFile impl(sourceCode, QStringLiteral("cpp"));
2039     QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses));
2040 
2041     DUChainReadLocker lock;
2042 
2043     auto top = impl.topContext();
2044     QVERIFY(top);
2045 
2046     QCOMPARE(top->problems().count(), 2);
2047 }
2048 
testProblemRequestedHereChain()2049 void TestDUChain::testProblemRequestedHereChain()
2050 {
2051     auto headerCode = QStringLiteral(R"(
2052 #pragma once
2053 
2054 template <typename T>
2055 T AddObjects(const T& a, const T& b)
2056 {
2057   return a + b;
2058 }
2059     )");
2060     TestFile header(headerCode, QStringLiteral("h"));
2061 
2062     QString sourceCode = QStringLiteral(R"(
2063 #include "%1"
2064 struct A {};
2065 
2066 template <typename T>
2067 T AddObjects2(const T& a, const T& b)
2068 {
2069   return AddObjects(a, b);
2070 }
2071 
2072 int main()
2073 {
2074     A a, b;
2075     AddObjects2(a, b);
2076     return 0;
2077 }
2078     )").arg(header.url().str());
2079     TestFile impl(sourceCode, QStringLiteral("cpp"), &header);
2080     QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses));
2081 
2082     DUChainReadLocker lock;
2083 
2084     auto top = impl.topContext();
2085     QVERIFY(top);
2086 
2087     auto* headerCtx = dynamic_cast<TopDUContext*>(top->importedParentContexts().first().context(top));
2088     QVERIFY(headerCtx);
2089     QCOMPARE(headerCtx->url(), header.url());
2090 
2091     QCOMPARE(headerCtx->problems().count(), 1);
2092     QCOMPARE(headerCtx->localDeclarations().count(), 1);
2093 
2094     // Verify that the problem is reported for the source file.
2095     QCOMPARE(top->problems().count(), 2);
2096     QCOMPARE(top->localDeclarations().count(), 3);
2097 }
2098 
testGccCompatibility()2099 void TestDUChain::testGccCompatibility()
2100 {
2101     // TODO: make it easier to change the compiler provider for testing purposes
2102     QTemporaryDir dir;
2103     auto project = new TestProject(Path(dir.path()), this);
2104     auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes");
2105     auto pathConfig = definesAndIncludesConfig.group("ProjectPath0");
2106     pathConfig.writeEntry("Path", ".");
2107     pathConfig.group("Compiler").writeEntry("Name", "GCC");
2108     m_projectController->addProject(project);
2109 
2110     {
2111         // TODO: Also test in C mode. Currently it doesn't work (some intrinsics missing?)
2112         TestFile file(QStringLiteral(R"(
2113             #include <x86intrin.h>
2114 
2115             int main() { return 0; }
2116         )"), QStringLiteral("cpp"), project, dir.path());
2117 
2118         file.parse();
2119         QVERIFY(file.waitForParsed(50000));
2120 
2121         DUChainReadLocker lock;
2122         QSet<TopDUContext*> checked;
2123         verifyNoErrors(file.topContext(), checked);
2124     }
2125 
2126     m_projectController->closeAllProjects();
2127 }
2128 
testLambda()2129 void TestDUChain::testLambda()
2130 {
2131     TestFile file(QStringLiteral("auto lambda = [](int p1, int p2, int p3) { int var1, var2; };"), QStringLiteral("cpp"));
2132     QVERIFY(file.parseAndWait());
2133     {
2134         DUChainReadLocker lock;
2135         QVERIFY(file.topContext());
2136         QCOMPARE(file.topContext()->localDeclarations().size(), 1);
2137         QCOMPARE(file.topContext()->childContexts().size(), 1);
2138 
2139         auto lambdaContext = file.topContext()->childContexts().first();
2140 
2141         QCOMPARE(lambdaContext->type(), DUContext::Function);
2142 
2143         QCOMPARE(lambdaContext->localDeclarations().size(), 3);
2144         QCOMPARE(lambdaContext->childContexts().size(), 1);
2145         QCOMPARE(lambdaContext->childContexts().first()->type(), DUContext::Other);
2146         QCOMPARE(lambdaContext->childContexts().first()->localDeclarations().size(), 2);
2147     }
2148 }
2149 
testQtIntegration()2150 void TestDUChain::testQtIntegration()
2151 {
2152     QTemporaryDir includeDir;
2153     {
2154         QDir dir(includeDir.path());
2155         dir.mkdir(QStringLiteral("QtCore"));
2156         // create the file but don't put anything in it
2157         QFile header(includeDir.path() + "/QtCore/qobjectdefs.h");
2158         QVERIFY(header.open(QIODevice::WriteOnly | QIODevice::Text));
2159     }
2160     QTemporaryDir dir;
2161     auto project = new TestProject(Path(dir.path()), this);
2162     m_provider->defines.clear();
2163     m_provider->includes = {Path(includeDir.path() + "/QtCore")};
2164 
2165     m_projectController->addProject(project);
2166 
2167     {
2168         TestFile file(QStringLiteral(R"(
2169             #define slots
2170             #define signals
2171             #define Q_SLOTS
2172             #define Q_SIGNALS
2173             #include <QtCore/qobjectdefs.h>
2174 
2175             struct MyObject {
2176             public:
2177               void other1();
2178             public slots:
2179               void slot1();
2180             signals:
2181               void signal1();
2182             private Q_SLOTS:
2183               void slot2();
2184             Q_SIGNALS:
2185               void signal2();
2186             public:
2187               void other2();
2188             };
2189         )"), QStringLiteral("cpp"), project, dir.path());
2190 
2191         file.parse();
2192         QVERIFY(file.waitForParsed(5000));
2193 
2194         DUChainReadLocker lock;
2195         auto top = file.topContext();
2196         QVERIFY(top);
2197         QVERIFY(top->problems().isEmpty());
2198         const auto methods = top->childContexts().last()->localDeclarations();
2199         QCOMPARE(methods.size(), 6);
2200         for (auto method : methods) {
2201             auto classFunction = dynamic_cast<ClassFunctionDeclaration*>(method);
2202             QVERIFY(classFunction);
2203             auto id = classFunction->identifier().toString();
2204             QCOMPARE(classFunction->isSignal(), id.startsWith(QLatin1String("signal")));
2205             QCOMPARE(classFunction->isSlot(), id.startsWith(QLatin1String("slot")));
2206         }
2207     }
2208 
2209     m_projectController->closeAllProjects();
2210 }
2211 
testHasInclude()2212 void TestDUChain::testHasInclude()
2213 {
2214     TestFile header(QStringLiteral(R"(
2215         #pragma once
2216         #if __has_include_next(<atomic>)
2217         // good
2218         #else
2219         #error broken c++11 setup (__has_include_next)
2220         #endif
2221     )"), QStringLiteral("h"));
2222     // NOTE: header is _not_ explicitly being parsed, instead the impl job does that
2223 
2224     TestFile file(QStringLiteral(R"(
2225         #if __has_include(<atomic>)
2226         // good
2227         #else
2228         #error broken c++11 setup (__has_include)
2229         #endif
2230         #include "%1"
2231     )").arg(header.url().str()), QStringLiteral("cpp"));
2232     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
2233 
2234     QVERIFY(file.waitForParsed(1000));
2235     {
2236         DUChainDumper dumper{DUChainDumper::DumpProblems};
2237 
2238         DUChainReadLocker lock;
2239 
2240         QVERIFY(file.topContext());
2241         dumper.dump(file.topContext());
2242         QVERIFY(file.topContext()->problems().isEmpty());
2243 
2244         auto headerCtx = DUChain::self()->chainForDocument(header.url());
2245         QVERIFY(headerCtx);
2246         dumper.dump(headerCtx);
2247         QVERIFY(headerCtx->problems().count() <= 1);
2248         const auto& headerProblems = headerCtx->problems();
2249         for (const auto& problem : headerProblems) {
2250             // ignore the following error:  "#include_next with absolute path [-Winclude-next-absolute-path]" "" [ (2, 12)  ->  (2, 30) ]
2251             QVERIFY(problem->description().contains(QLatin1String("-Winclude-next-absolute-path")));
2252         }
2253     }
2254 }
2255 
testSameFunctionDefinition()2256 void TestDUChain::testSameFunctionDefinition()
2257 {
2258     QString source(QStringLiteral(R"(
2259         #include <stdio.h>
2260         #include <stdlib.h>
2261 
2262         void configure()
2263         {
2264             printf("do stuff\n");
2265         }
2266     )"));
2267 
2268     QTemporaryDir dir;
2269     auto project = new TestProject(Path(dir.path()), this);
2270     m_projectController->addProject(project);
2271 
2272     TestFile file1(source, QStringLiteral("c"), project);
2273     TestFile file2(source, QStringLiteral("c"), project);
2274     TestFile file3(source, QStringLiteral("c"), project);
2275 
2276     file1.parse(TopDUContext::AllDeclarationsContextsAndUses);
2277     file2.parse(TopDUContext::AllDeclarationsContextsAndUses);
2278     file3.parse(TopDUContext::AllDeclarationsContextsAndUses);
2279 
2280     QVERIFY(file1.waitForParsed(1000));
2281     QVERIFY(file2.waitForParsed(1000));
2282     QVERIFY(file3.waitForParsed(1000));
2283 
2284     auto checkFunctionDefinition = [] (TestFile & file) {
2285         DUChainReadLocker lock;
2286         QCOMPARE(file.topContext()->localDeclarations().count(), 1);
2287         auto configureFunc = file.topContext()->localDeclarations().first();
2288         QCOMPARE(FunctionDefinition::definition(configureFunc), configureFunc);
2289     };
2290 
2291     checkFunctionDefinition(file1);
2292     checkFunctionDefinition(file2);
2293     checkFunctionDefinition(file3);
2294 
2295     m_projectController->closeAllProjects();
2296 }
2297 
testSizeAlignOf()2298 void TestDUChain::testSizeAlignOf()
2299 {
2300     TestFile file(QStringLiteral(R"(
2301         template<typename T>
2302         struct Foo { T arg; };
2303 
2304         using Bar = Foo<int>;
2305         Bar asdf;
2306         Foo<double> blub;
2307         template<typename F>
2308         using Asdf = Foo<F>;
2309         Asdf<char> c;
2310 
2311         struct MyClass {
2312             int i;
2313             int j;
2314         };
2315         void *ptr = nullptr;
2316 
2317         enum E { A, B };
2318         E e = A;
2319         enum class E2 : int { A, B };
2320         E2 e2 = E2::A;
2321         enum class E3 : long { A, B };
2322         E3 e3 = E3::A;
2323 
2324         int i = 0;
2325         int& ref = i;
2326         struct ref_struct { int& ref; };
2327 
2328         int fun();
2329 
2330         namespace myns { struct bar { int foo; }; }
2331         myns::bar myns_use;
2332 
2333         struct MyClass c_struct;
2334     )"),
2335                   QStringLiteral("cpp"));
2336     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
2337     QVERIFY(file.waitForParsed(1000));
2338 
2339     DUChainReadLocker lock;
2340     const auto top = file.topContext();
2341     QVERIFY(top);
2342     QVERIFY(top->problems().isEmpty());
2343 
2344     const auto decs = top->localDeclarations();
2345     QCOMPARE(decs.size(), 21);
2346 
2347     auto check = [&](int i, const QString& id, qint64 sizeOf, qint64 alignOf) {
2348         QCOMPARE(decs[i]->identifier().toString(), id);
2349         const auto type = TypeUtils::unAliasedType(decs[i]->abstractType());
2350         QVERIFY(type);
2351         QCOMPARE(type->sizeOf(), sizeOf);
2352         QCOMPARE(type->alignOf(), alignOf);
2353     };
2354     check(0, "Foo< T >", -1, -1);
2355     check(1, "Bar", 4, 4);
2356     check(2, "asdf", 4, 4);
2357     check(3, "blub", 8, 8);
2358     check(4, "Asdf", -1, -1);
2359     check(5, "c", 1, 1);
2360     check(6, "MyClass", 8, 4);
2361     check(7, "ptr", 8, 8);
2362     check(8, "E", 4, 4);
2363     check(9, "e", 4, 4);
2364     check(10, "E2", 4, 4);
2365     check(11, "e2", 4, 4);
2366     check(12, "E3", 8, 8);
2367     check(13, "e3", 8, 8);
2368     check(14, "i", 4, 4);
2369     // unexpected, but apparently correct
2370     // https://stackoverflow.com/questions/26631169/why-does-sizeof-a-reference-type-give-you-the-sizeof-the-type
2371     check(15, "ref", 4, 4);
2372     check(16, "ref_struct", 8, 8);
2373     check(17, "fun", -1, -1);
2374     check(19, "myns_use", 4, 4);
2375     check(20, "c_struct", 8, 4);
2376 }
2377 
testSizeAlignOfUpdate()2378 void TestDUChain::testSizeAlignOfUpdate()
2379 {
2380     TestFile file(QStringLiteral(R"(
2381         struct foo { int i; };
2382     )"),
2383                   QStringLiteral("cpp"));
2384     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
2385     QVERIFY(file.waitForParsed(1000));
2386 
2387     {
2388         DUChainReadLocker lock;
2389         const auto top = file.topContext();
2390         QVERIFY(top);
2391         QVERIFY(top->problems().isEmpty());
2392         QCOMPARE(top->localDeclarations().size(), 1);
2393         const auto type = top->localDeclarations().constFirst()->abstractType();
2394         QCOMPARE(type->sizeOf(), 4);
2395         QCOMPARE(type->alignOf(), 4);
2396     }
2397 
2398     file.setFileContents(QStringLiteral(R"(
2399         struct foo { char i[124]; };
2400     )"));
2401     file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate);
2402     QVERIFY(file.waitForParsed(1000));
2403 
2404     {
2405         DUChainReadLocker lock;
2406         const auto top = file.topContext();
2407         QVERIFY(top);
2408         QVERIFY(top->problems().isEmpty());
2409         QCOMPARE(top->localDeclarations().size(), 1);
2410         const auto type = top->localDeclarations().constFirst()->abstractType();
2411         QCOMPARE(type->sizeOf(), 124);
2412         QCOMPARE(type->alignOf(), 1);
2413     }
2414 }
2415 
testBitWidth()2416 void TestDUChain::testBitWidth()
2417 {
2418 #if CINDEX_VERSION_MINOR >= 16
2419     TestFile file(QStringLiteral(R"(
2420         struct foo {
2421             int a;
2422             unsigned int b:1;
2423             unsigned char c:7;
2424             int d:32;
2425             int e[2];
2426 
2427             // error case
2428             int f:0;
2429             int g:-1;
2430             int h:33;
2431         };)"), QStringLiteral("cpp"));
2432 
2433     QVERIFY(file.parseAndWait());
2434     {
2435         DUChainReadLocker lock;
2436         QVERIFY(file.topContext());
2437         QCOMPARE(file.topContext()->localDeclarations().size(), 1);
2438         QCOMPARE(file.topContext()->childContexts().size(), 1);
2439 
2440         auto fooContext = file.topContext()->childContexts().first();
2441         QVERIFY(fooContext);
2442         QCOMPARE(fooContext->type(), DUContext::Class);
2443         QCOMPARE(fooContext->localDeclarations().size(), 8);
2444         QCOMPARE(fooContext->childContexts().size(), 0);
2445 
2446         auto varA = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(0));
2447         QVERIFY(varA);
2448         QCOMPARE(varA->bitWidth(), -1);
2449         auto varB = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(1));
2450         QVERIFY(varB);
2451         QCOMPARE(varB->bitWidth(), 1);
2452         auto varC = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(2));
2453         QVERIFY(varC);
2454         QCOMPARE(varC->bitWidth(), 7);
2455         auto varD = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(3));
2456         QVERIFY(varD);
2457         QCOMPARE(varD->bitWidth(), 32);
2458         auto varE = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(4));
2459         QVERIFY(varE);
2460         QCOMPARE(varE->bitWidth(), -1);
2461 
2462         // error case
2463         auto top = file.topContext();
2464         QVERIFY(top);
2465         QCOMPARE(top->problems().count(), 3);
2466 
2467         // error: named bit-field 'f' has zero width
2468         auto varF = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(5));
2469         QVERIFY(varF);
2470         QCOMPARE(varF->bitWidth(), -1);
2471         // error: bit-field 'g' has negative width (-1)
2472         auto varG = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(6));
2473         QVERIFY(varG);
2474         QCOMPARE(varG->bitWidth(), -1);
2475         // warning: width of bit-field 'h' (33 bits) exceeds the width of its type; value will be truncated to 32 bits
2476         auto varH = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(7));
2477         QVERIFY(varH);
2478         QCOMPARE(varH->bitWidth(), 33);
2479     }
2480 #endif
2481 }
2482 
testBitWidthUpdate()2483 void TestDUChain::testBitWidthUpdate()
2484 {
2485 #if CINDEX_VERSION_MINOR >= 16
2486     TestFile file(QStringLiteral(R"(
2487         struct foo { int i:7; };
2488     )"),
2489                   QStringLiteral("cpp"));
2490     file.parse(TopDUContext::AllDeclarationsContextsAndUses);
2491     QVERIFY(file.waitForParsed(1000));
2492 
2493     {
2494         DUChainReadLocker lock;
2495         const auto top = file.topContext();
2496         QVERIFY(top);
2497         QVERIFY(top->problems().isEmpty());
2498         QCOMPARE(top->localDeclarations().size(), 1);
2499         QCOMPARE(top->childContexts().size(), 1);
2500 
2501         const auto fooContext = top->childContexts().first();
2502         QVERIFY(fooContext);
2503         QCOMPARE(fooContext->type(), DUContext::Class);
2504         QCOMPARE(fooContext->localDeclarations().size(), 1);
2505         QCOMPARE(fooContext->childContexts().size(), 0);
2506 
2507         const auto varI = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(0));
2508         QCOMPARE(varI->bitWidth(), 7);
2509     }
2510 
2511     file.setFileContents(QStringLiteral(R"(
2512         struct foo { int i; };
2513     )"));
2514     file.parse(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate);
2515     QVERIFY(file.waitForParsed(1000));
2516 
2517     {
2518         DUChainReadLocker lock;
2519         const auto top = file.topContext();
2520         QVERIFY(top);
2521         QVERIFY(top->problems().isEmpty());
2522         QCOMPARE(top->localDeclarations().size(), 1);
2523         QCOMPARE(top->childContexts().size(), 1);
2524 
2525         const auto fooContext = top->childContexts().first();
2526         QVERIFY(fooContext);
2527         QCOMPARE(fooContext->type(), DUContext::Class);
2528         QCOMPARE(fooContext->localDeclarations().size(), 1);
2529         QCOMPARE(fooContext->childContexts().size(), 0);
2530 
2531         const auto varI = dynamic_cast<ClassMemberDeclaration *>(fooContext->localDeclarations().at(0));
2532         QCOMPARE(varI->bitWidth(), -1);
2533     }
2534 #endif
2535 }
2536