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