1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "clangcodeparser.h"
5
6#include "access.h"
7#include "classnode.h"
8#include "codechunk.h"
9#include "config.h"
10#include "enumnode.h"
11#include "functionnode.h"
12#include "namespacenode.h"
13#include "propertynode.h"
14#include "qdocdatabase.h"
15#include "typedefnode.h"
16#include "utilities.h"
17#include "variablenode.h"
18
19#include <QtCore/qdebug.h>
20#include <QtCore/qelapsedtimer.h>
21#include <QtCore/qfile.h>
22#include <QtCore/qscopedvaluerollback.h>
23#include <QtCore/qtemporarydir.h>
24#include <QtCore/qtextstream.h>
25#include <QtCore/qvarlengtharray.h>
26
27#include <clang-c/Index.h>
28
29#include <clang/AST/Decl.h>
30#include <clang/AST/DeclFriend.h>
31#include <clang/AST/DeclTemplate.h>
32#include <clang/AST/Expr.h>
33#include <clang/AST/Type.h>
34#include <clang/AST/TypeLoc.h>
35#include <clang/Basic/SourceLocation.h>
36#include <clang/Frontend/ASTUnit.h>
37#include <clang/Lex/Lexer.h>
38#include <llvm/Support/Casting.h>
39
40#include <cstdio>
41
42QT_BEGIN_NAMESPACE
43
44const QStringList ClangCodeParser::accepted_header_file_extensions{
45 "ch", "h", "h++", "hh", "hpp", "hxx"
46};
47
48// We're printing diagnostics in ClangCodeParser::printDiagnostics,
49// so avoid clang itself printing them.
50static const auto kClangDontDisplayDiagnostics = 0;
51
52static CXTranslationUnit_Flags flags_ = static_cast<CXTranslationUnit_Flags>(0);
53static CXIndex index_ = nullptr;
54
55QByteArray ClangCodeParser::s_fn;
56constexpr const char fnDummyFileName[] = "/fn_dummyfile.cpp";
57
58#ifndef QT_NO_DEBUG_STREAM
59template<class T>
60static QDebug operator<<(QDebug debug, const std::vector<T> &v)
61{
62 QDebugStateSaver saver(debug);
63 debug.noquote();
64 debug.nospace();
65 const size_t size = v.size();
66 debug << "std::vector<>[" << size << "](";
67 for (size_t i = 0; i < size; ++i) {
68 if (i)
69 debug << ", ";
70 debug << v[i];
71 }
72 debug << ')';
73 return debug;
74}
75#endif // !QT_NO_DEBUG_STREAM
76
77/*!
78 Call clang_visitChildren on the given cursor with the lambda as a callback
79 T can be any functor that is callable with a CXCursor parameter and returns a CXChildVisitResult
80 (in other word compatible with function<CXChildVisitResult(CXCursor)>
81 */
82template<typename T>
83bool visitChildrenLambda(CXCursor cursor, T &&lambda)
84{
85 CXCursorVisitor visitor = [](CXCursor c, CXCursor,
86 CXClientData client_data) -> CXChildVisitResult {
87 return (*static_cast<T *>(client_data))(c);
88 };
89 return clang_visitChildren(cursor, visitor, &lambda);
90}
91
92/*!
93 convert a CXString to a QString, and dispose the CXString
94 */
95static QString fromCXString(CXString &&string)
96{
97 QString ret = QString::fromUtf8(utf8: clang_getCString(string));
98 clang_disposeString(string);
99 return ret;
100}
101
102static QString templateDecl(CXCursor cursor);
103
104/*!
105 Returns a list of template parameters at \a cursor.
106*/
107static QStringList getTemplateParameters(CXCursor cursor)
108{
109 QStringList parameters;
110 visitChildrenLambda(cursor, lambda: [&parameters](CXCursor cur) {
111 QString name = fromCXString(string: clang_getCursorSpelling(cur));
112 QString type;
113
114 switch (clang_getCursorKind(cur)) {
115 case CXCursor_TemplateTypeParameter:
116 type = QStringLiteral("typename");
117 break;
118 case CXCursor_NonTypeTemplateParameter:
119 type = fromCXString(string: clang_getTypeSpelling(CT: clang_getCursorType(C: cur)));
120 // Hack: Omit QtPrivate template parameters from public documentation
121 if (type.startsWith(s: QLatin1String("QtPrivate")))
122 return CXChildVisit_Continue;
123 break;
124 case CXCursor_TemplateTemplateParameter:
125 type = templateDecl(cursor: cur) + QLatin1String(" class");
126 break;
127 default:
128 return CXChildVisit_Continue;
129 }
130
131 if (!name.isEmpty())
132 name.prepend(c: QLatin1Char(' '));
133
134 parameters << type + name;
135 return CXChildVisit_Continue;
136 });
137
138 return parameters;
139}
140
141/*!
142 Gets the template declaration at specified \a cursor.
143 */
144static QString templateDecl(CXCursor cursor)
145{
146 QStringList params = getTemplateParameters(cursor);
147 return QLatin1String("template <") + params.join(sep: QLatin1String(", ")) + QLatin1Char('>');
148}
149
150/*!
151 convert a CXSourceLocation to a qdoc Location
152 */
153static Location fromCXSourceLocation(CXSourceLocation location)
154{
155 unsigned int line, column;
156 CXString file;
157 clang_getPresumedLocation(location, filename: &file, line: &line, column: &column);
158 Location l(fromCXString(string: std::move(file)));
159 l.setColumnNo(column);
160 l.setLineNo(line);
161 return l;
162}
163
164/*!
165 convert a CX_CXXAccessSpecifier to Node::Access
166 */
167static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
168{
169 switch (spec) {
170 case CX_CXXPrivate:
171 return Access::Private;
172 case CX_CXXProtected:
173 return Access::Protected;
174 case CX_CXXPublic:
175 return Access::Public;
176 default:
177 return Access::Public;
178 }
179}
180
181/*!
182 Returns the spelling in the file for a source range
183 */
184
185struct FileCacheEntry
186{
187 QByteArray fileName;
188 QByteArray content;
189};
190
191static inline QString fromCache(const QByteArray &cache,
192 unsigned int offset1, unsigned int offset2)
193{
194 return QString::fromUtf8(ba: cache.mid(index: offset1, len: offset2 - offset1));
195}
196
197static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
198{
199 using FileCache = QList<FileCacheEntry>;
200 static FileCache cache;
201
202 CXString cxFileName = clang_getFileName(SFile: cxFile);
203 const QByteArray fileName = clang_getCString(string: cxFileName);
204 clang_disposeString(string: cxFileName);
205
206 for (const auto &entry : std::as_const(t&: cache)) {
207 if (fileName == entry.fileName)
208 return fromCache(cache: entry.content, offset1, offset2);
209 }
210
211 // "fn_dummyfile.cpp" comes with varying cxFile values
212 if (fileName == fnDummyFileName)
213 return fromCache(cache: ClangCodeParser::fn(), offset1, offset2);
214
215 QFile file(QString::fromUtf8(ba: fileName));
216 if (file.open(flags: QIODeviceBase::ReadOnly)) { // binary to match clang offsets
217 FileCacheEntry entry{.fileName: fileName, .content: file.readAll()};
218 cache.prepend(t: entry);
219 while (cache.size() > 5)
220 cache.removeLast();
221 return fromCache(cache: entry.content, offset1, offset2);
222 }
223 return {};
224}
225
226static QString getSpelling(CXSourceRange range)
227{
228 auto start = clang_getRangeStart(range);
229 auto end = clang_getRangeEnd(range);
230 CXFile file1, file2;
231 unsigned int offset1, offset2;
232 clang_getFileLocation(location: start, file: &file1, line: nullptr, column: nullptr, offset: &offset1);
233 clang_getFileLocation(location: end, file: &file2, line: nullptr, column: nullptr, offset: &offset2);
234
235 if (file1 != file2 || offset2 <= offset1)
236 return QString();
237
238 return readFile(cxFile: file1, offset1, offset2);
239}
240
241/*!
242 Returns the function name from a given cursor representing a
243 function declaration. This is usually clang_getCursorSpelling, but
244 not for the conversion function in which case it is a bit more complicated
245 */
246QString functionName(CXCursor cursor)
247{
248 if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) {
249 // For a CXCursor_ConversionFunction we don't want the spelling which would be something
250 // like "operator type-parameter-0-0" or "operator unsigned int". we want the actual name as
251 // spelled;
252 QString type = fromCXString(string: clang_getTypeSpelling(CT: clang_getCursorResultType(C: cursor)));
253 if (type.isEmpty())
254 return fromCXString(string: clang_getCursorSpelling(cursor));
255 return QLatin1String("operator ") + type;
256 }
257
258 QString name = fromCXString(string: clang_getCursorSpelling(cursor));
259
260 // Remove template stuff from constructor and destructor but not from operator<
261 auto ltLoc = name.indexOf(c: '<');
262 if (ltLoc > 0 && !name.startsWith(s: "operator<"))
263 name = name.left(n: ltLoc);
264 return name;
265}
266
267/*!
268 Reconstruct the qualified path name of a function that is
269 being overridden.
270 */
271static QString reconstructQualifiedPathForCursor(CXCursor cur)
272{
273 QString path;
274 auto kind = clang_getCursorKind(cur);
275 while (!clang_isInvalid(kind) && kind != CXCursor_TranslationUnit) {
276 switch (kind) {
277 case CXCursor_Namespace:
278 case CXCursor_StructDecl:
279 case CXCursor_ClassDecl:
280 case CXCursor_UnionDecl:
281 case CXCursor_ClassTemplate:
282 path.prepend(s: "::");
283 path.prepend(s: fromCXString(string: clang_getCursorSpelling(cur)));
284 break;
285 case CXCursor_FunctionDecl:
286 case CXCursor_FunctionTemplate:
287 case CXCursor_CXXMethod:
288 case CXCursor_Constructor:
289 case CXCursor_Destructor:
290 case CXCursor_ConversionFunction:
291 path = functionName(cursor: cur);
292 break;
293 default:
294 break;
295 }
296 cur = clang_getCursorSemanticParent(cursor: cur);
297 kind = clang_getCursorKind(cur);
298 }
299 return path;
300}
301
302/*!
303 Find the node from the QDocDatabase \a qdb that corrseponds to the declaration
304 represented by the cursor \a cur, if it exists.
305 */
306static Node *findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
307{
308 auto kind = clang_getCursorKind(cur);
309 if (clang_isInvalid(kind))
310 return nullptr;
311 if (kind == CXCursor_TranslationUnit)
312 return qdb->primaryTreeRoot();
313
314 Node *p = findNodeForCursor(qdb, cur: clang_getCursorSemanticParent(cursor: cur));
315 if (p == nullptr)
316 return nullptr;
317 if (!p->isAggregate())
318 return nullptr;
319 auto parent = static_cast<Aggregate *>(p);
320
321 QString name = fromCXString(string: clang_getCursorSpelling(cur));
322 switch (kind) {
323 case CXCursor_Namespace:
324 return parent->findNonfunctionChild(name, &Node::isNamespace);
325 case CXCursor_StructDecl:
326 case CXCursor_ClassDecl:
327 case CXCursor_UnionDecl:
328 case CXCursor_ClassTemplate:
329 return parent->findNonfunctionChild(name, &Node::isClassNode);
330 case CXCursor_FunctionDecl:
331 case CXCursor_FunctionTemplate:
332 case CXCursor_CXXMethod:
333 case CXCursor_Constructor:
334 case CXCursor_Destructor:
335 case CXCursor_ConversionFunction: {
336 NodeVector candidates;
337 parent->findChildren(name: functionName(cursor: cur), nodes&: candidates);
338 if (candidates.isEmpty())
339 return nullptr;
340 CXType funcType = clang_getCursorType(C: cur);
341 auto numArg = clang_getNumArgTypes(T: funcType);
342 bool isVariadic = clang_isFunctionTypeVariadic(T: funcType);
343 QVarLengthArray<QString, 20> args;
344 for (Node *candidate : std::as_const(t&: candidates)) {
345 if (!candidate->isFunction(g: Node::CPP))
346 continue;
347 auto fn = static_cast<FunctionNode *>(candidate);
348 const Parameters &parameters = fn->parameters();
349 if (parameters.count() != numArg + isVariadic)
350 continue;
351 if (fn->isConst() != bool(clang_CXXMethod_isConst(C: cur)))
352 continue;
353 if (isVariadic && parameters.last().type() != QLatin1String("..."))
354 continue;
355 if (fn->isRef() != (clang_Type_getCXXRefQualifier(T: funcType) == CXRefQualifier_LValue))
356 continue;
357 if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(T: funcType) == CXRefQualifier_RValue))
358 continue;
359
360 bool different = false;
361 for (int i = 0; i < numArg; ++i) {
362 CXType argType = clang_getArgType(T: funcType, i);
363 if (args.size() <= i)
364 args.append(t: fromCXString(string: clang_getTypeSpelling(CT: argType)));
365 QString recordedType = parameters.at(i).type();
366 QString typeSpelling = args.at(idx: i);
367 auto p = parent;
368 while (p && recordedType != typeSpelling) {
369 QString parentScope = p->name() + QLatin1String("::");
370 recordedType.remove(s: parentScope);
371 typeSpelling.remove(s: parentScope);
372 p = p->parent();
373 }
374 different = recordedType != typeSpelling;
375
376 // Retry with a canonical type spelling
377 if (different && (argType.kind == CXType_Typedef || argType.kind == CXType_Elaborated)) {
378 QStringView canonicalType = parameters.at(i).canonicalType();
379 if (!canonicalType.isEmpty()) {
380 different = canonicalType !=
381 fromCXString(string: clang_getTypeSpelling(CT: clang_getCanonicalType(T: argType)));
382 }
383 }
384 if (different) {
385 break;
386 }
387 }
388 if (!different)
389 return fn;
390 }
391 return nullptr;
392 }
393 case CXCursor_EnumDecl:
394 return parent->findNonfunctionChild(name, &Node::isEnumType);
395 case CXCursor_FieldDecl:
396 case CXCursor_VarDecl:
397 return parent->findNonfunctionChild(name, &Node::isVariable);
398 case CXCursor_TypedefDecl:
399 return parent->findNonfunctionChild(name, &Node::isTypedef);
400 default:
401 return nullptr;
402 }
403}
404
405static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
406{
407 CXCursor *overridden;
408 unsigned int numOverridden = 0;
409 clang_getOverriddenCursors(cursor, overridden: &overridden, num_overridden: &numOverridden);
410 for (uint i = 0; i < numOverridden; ++i) {
411 QString path = reconstructQualifiedPathForCursor(cur: overridden[i]);
412 if (!path.isEmpty()) {
413 fn->setOverride(true);
414 fn->setOverridesThis(path);
415 break;
416 }
417 }
418 clang_disposeOverriddenCursors(overridden);
419}
420
421class ClangVisitor
422{
423public:
424 ClangVisitor(QDocDatabase *qdb, const QMultiHash<QString, QString> &allHeaders)
425 : qdb_(qdb), parent_(qdb->primaryTreeRoot()), allHeaders_(allHeaders)
426 {
427 }
428
429 QDocDatabase *qdocDB() { return qdb_; }
430
431 CXChildVisitResult visitChildren(CXCursor cursor)
432 {
433 auto ret = visitChildrenLambda(cursor, lambda: [&](CXCursor cur) {
434 auto loc = clang_getCursorLocation(cur);
435 if (clang_Location_isFromMainFile(location: loc))
436 return visitSource(cursor: cur, loc);
437 CXFile file;
438 clang_getFileLocation(location: loc, file: &file, line: nullptr, column: nullptr, offset: nullptr);
439 bool isInteresting = false;
440 auto it = isInterestingCache_.find(key: file);
441 if (it != isInterestingCache_.end()) {
442 isInteresting = *it;
443 } else {
444 QFileInfo fi(fromCXString(string: clang_getFileName(SFile: file)));
445 // Match by file name in case of PCH/installed headers
446 isInteresting = allHeaders_.contains(key: fi.fileName());
447 isInterestingCache_[file] = isInteresting;
448 }
449 if (isInteresting) {
450 return visitHeader(cursor: cur, loc);
451 }
452
453 return CXChildVisit_Continue;
454 });
455 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
456 }
457
458 /*
459 Not sure about all the possibilities, when the cursor
460 location is not in the main file.
461 */
462 CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
463 {
464 auto ret = visitChildrenLambda(cursor, lambda: [&](CXCursor cur) {
465 auto loc = clang_getCursorLocation(cur);
466 if (clang_Location_isFromMainFile(location: loc))
467 return visitFnSignature(cursor: cur, loc, fnNode, ignoreSignature);
468 return CXChildVisit_Continue;
469 });
470 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
471 }
472
473 Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc);
474
475private:
476 /*!
477 SimpleLoc represents a simple location in the main source file,
478 which can be used as a key in a QMap.
479 */
480 struct SimpleLoc
481 {
482 unsigned int line {}, column {};
483 friend bool operator<(const SimpleLoc &a, const SimpleLoc &b)
484 {
485 return a.line != b.line ? a.line < b.line : a.column < b.column;
486 }
487 };
488 /*!
489 \variable ClangVisitor::declMap_
490 Map of all the declarations in the source file so we can match them
491 with a documentation comment.
492 */
493 QMap<SimpleLoc, CXCursor> declMap_;
494
495 QDocDatabase *qdb_;
496 Aggregate *parent_;
497 const QMultiHash<QString, QString> allHeaders_;
498 QHash<CXFile, bool> isInterestingCache_; // doing a canonicalFilePath is slow, so keep a cache.
499
500 /*!
501 Returns true if the symbol should be ignored for the documentation.
502 */
503 bool ignoredSymbol(const QString &symbolName)
504 {
505 if (symbolName == QLatin1String("QPrivateSignal"))
506 return true;
507 // Ignore functions generated by property macros
508 if (symbolName.startsWith(s: "_qt_property_"))
509 return true;
510 // Ignore template argument deduction guides
511 if (symbolName.startsWith(s: "<deduction guide"))
512 return true;
513 return false;
514 }
515
516 /*!
517 The type parameters do not need to be fully qualified
518 This function removes the ClassName:: if needed.
519
520 example: 'QLinkedList::iterator' -> 'iterator'
521 */
522 QString adjustTypeName(const QString &typeName)
523 {
524 auto parent = parent_->parent();
525 if (parent && parent->isClassNode()) {
526 QStringView typeNameConstRemoved(typeName);
527 if (typeNameConstRemoved.startsWith(s: QLatin1String("const ")))
528 typeNameConstRemoved = typeNameConstRemoved.mid(pos: 6);
529
530 auto parentName = parent->fullName();
531 if (typeNameConstRemoved.startsWith(s: parentName)
532 && typeNameConstRemoved.mid(pos: parentName.size(), n: 2) == QLatin1String("::")) {
533 QString result = typeName;
534 result.remove(i: typeName.indexOf(s: typeNameConstRemoved), len: parentName.size() + 2);
535 return result;
536 }
537 }
538 return typeName;
539 }
540
541 CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
542 CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
543 CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node **fnNode,
544 bool &ignoreSignature);
545 void processFunction(FunctionNode *fn, CXCursor cursor);
546 bool parseProperty(const QString &spelling, const Location &loc);
547 void readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor);
548 Aggregate *getSemanticParent(CXCursor cursor);
549};
550
551/*!
552 Visits a cursor in the .cpp file.
553 This fills the declMap_
554 */
555CXChildVisitResult ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
556{
557 auto kind = clang_getCursorKind(cursor);
558 if (clang_isDeclaration(kind)) {
559 SimpleLoc l;
560 clang_getPresumedLocation(location: loc, filename: nullptr, line: &l.line, column: &l.column);
561 declMap_.insert(key: l, value: cursor);
562 return CXChildVisit_Recurse;
563 }
564 return CXChildVisit_Continue;
565}
566
567/*!
568 If the semantic and lexical parent cursors of \a cursor are
569 not the same, find the Aggregate node for the semantic parent
570 cursor and return it. Otherwise return the current parent.
571 */
572Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor)
573{
574 CXCursor sp = clang_getCursorSemanticParent(cursor);
575 CXCursor lp = clang_getCursorLexicalParent(cursor);
576 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
577 Node *spn = findNodeForCursor(qdb: qdb_, cur: sp);
578 if (spn && spn->isAggregate()) {
579 return static_cast<Aggregate *>(spn);
580 }
581 }
582 return parent_;
583}
584
585CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation, Node **fnNode,
586 bool &ignoreSignature)
587{
588 switch (clang_getCursorKind(cursor)) {
589 case CXCursor_Namespace:
590 return CXChildVisit_Recurse;
591 case CXCursor_FunctionDecl:
592 case CXCursor_FunctionTemplate:
593 case CXCursor_CXXMethod:
594 case CXCursor_Constructor:
595 case CXCursor_Destructor:
596 case CXCursor_ConversionFunction: {
597 ignoreSignature = false;
598 if (ignoredSymbol(symbolName: functionName(cursor))) {
599 *fnNode = nullptr;
600 ignoreSignature = true;
601 } else {
602 *fnNode = findNodeForCursor(qdb: qdb_, cur: cursor);
603 if (*fnNode) {
604 if ((*fnNode)->isFunction(g: Node::CPP)) {
605 auto *fn = static_cast<FunctionNode *>(*fnNode);
606 readParameterNamesAndAttributes(fn, cursor);
607 }
608 } else { // Possibly an implicitly generated special member
609 QString name = functionName(cursor);
610 if (ignoredSymbol(symbolName: name))
611 return CXChildVisit_Continue;
612 Aggregate *semanticParent = getSemanticParent(cursor);
613 if (semanticParent && semanticParent->isClass()) {
614 auto *candidate = new FunctionNode(nullptr, name);
615 processFunction(fn: candidate, cursor);
616 if (!candidate->isSpecialMemberFunction()) {
617 delete candidate;
618 return CXChildVisit_Continue;
619 }
620 candidate->setDefault(true);
621 semanticParent->addChild(child: *fnNode = candidate);
622 }
623 }
624 }
625 break;
626 }
627 default:
628 break;
629 }
630 return CXChildVisit_Continue;
631}
632
633CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
634{
635 auto kind = clang_getCursorKind(cursor);
636 QString templateString;
637 switch (kind) {
638 case CXCursor_TypeAliasTemplateDecl:
639 case CXCursor_TypeAliasDecl: {
640 QString aliasDecl = getSpelling(range: clang_getCursorExtent(cursor)).simplified();
641 QStringList typeAlias = aliasDecl.split(sep: QLatin1Char('='));
642 if (typeAlias.size() == 2) {
643 typeAlias[0] = typeAlias[0].trimmed();
644 const QLatin1String usingString("using ");
645 qsizetype usingPos = typeAlias[0].indexOf(s: usingString);
646 if (usingPos != -1) {
647 if (kind == CXCursor_TypeAliasTemplateDecl)
648 templateString = typeAlias[0].left(n: usingPos).trimmed();
649 typeAlias[0].remove(i: 0, len: usingPos + usingString.size());
650 typeAlias[0] = typeAlias[0].split(sep: QLatin1Char(' ')).first();
651 typeAlias[1] = typeAlias[1].trimmed();
652 auto *ta = new TypeAliasNode(parent_, typeAlias[0], typeAlias[1]);
653 ta->setAccess(fromCX_CXXAccessSpecifier(spec: clang_getCXXAccessSpecifier(cursor)));
654 ta->setLocation(fromCXSourceLocation(location: clang_getCursorLocation(cursor)));
655 ta->setTemplateDecl(templateString);
656 }
657 }
658 return CXChildVisit_Continue;
659 }
660 case CXCursor_StructDecl:
661 case CXCursor_UnionDecl:
662 if (fromCXString(string: clang_getCursorSpelling(cursor)).isEmpty()) // anonymous struct or union
663 return CXChildVisit_Continue;
664 Q_FALLTHROUGH();
665 case CXCursor_ClassTemplate:
666 templateString = templateDecl(cursor);
667 Q_FALLTHROUGH();
668 case CXCursor_ClassDecl: {
669 if (!clang_isCursorDefinition(cursor))
670 return CXChildVisit_Continue;
671
672 if (findNodeForCursor(qdb: qdb_, cur: cursor)) // Was already parsed, probably in another TU
673 return CXChildVisit_Continue;
674
675 QString className = fromCXString(string: clang_getCursorSpelling(cursor));
676
677 Aggregate *semanticParent = getSemanticParent(cursor);
678 if (semanticParent && semanticParent->findNonfunctionChild(name: className, &Node::isClassNode)) {
679 return CXChildVisit_Continue;
680 }
681
682 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
683 clang_getTemplateCursorKind(C: cursor) : kind;
684
685 Node::NodeType type = Node::Class;
686 if (actualKind == CXCursor_StructDecl)
687 type = Node::Struct;
688 else if (actualKind == CXCursor_UnionDecl)
689 type = Node::Union;
690
691 auto *classe = new ClassNode(type, semanticParent, className);
692 classe->setAccess(fromCX_CXXAccessSpecifier(spec: clang_getCXXAccessSpecifier(cursor)));
693 classe->setLocation(fromCXSourceLocation(location: clang_getCursorLocation(cursor)));
694
695 if (kind == CXCursor_ClassTemplate)
696 classe->setTemplateDecl(templateString);
697
698 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
699 return visitChildren(cursor);
700 }
701 case CXCursor_CXXBaseSpecifier: {
702 if (!parent_->isClassNode())
703 return CXChildVisit_Continue;
704 auto access = fromCX_CXXAccessSpecifier(spec: clang_getCXXAccessSpecifier(cursor));
705 auto type = clang_getCursorType(C: cursor);
706 auto baseCursor = clang_getTypeDeclaration(T: type);
707 auto baseNode = findNodeForCursor(qdb: qdb_, cur: baseCursor);
708 auto classe = static_cast<ClassNode *>(parent_);
709 if (baseNode == nullptr || !baseNode->isClassNode()) {
710 QString bcName = reconstructQualifiedPathForCursor(cur: baseCursor);
711 classe->addUnresolvedBaseClass(access,
712 path: bcName.split(sep: QLatin1String("::"), behavior: Qt::SkipEmptyParts));
713 return CXChildVisit_Continue;
714 }
715 auto baseClasse = static_cast<ClassNode *>(baseNode);
716 classe->addResolvedBaseClass(access, node: baseClasse);
717 return CXChildVisit_Continue;
718 }
719 case CXCursor_Namespace: {
720 QString namespaceName = fromCXString(string: clang_getCursorDisplayName(cursor));
721 NamespaceNode *ns = nullptr;
722 if (parent_)
723 ns = static_cast<NamespaceNode *>(
724 parent_->findNonfunctionChild(name: namespaceName, &Node::isNamespace));
725 if (!ns) {
726 ns = new NamespaceNode(parent_, namespaceName);
727 ns->setAccess(Access::Public);
728 ns->setLocation(fromCXSourceLocation(location: clang_getCursorLocation(cursor)));
729 }
730 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
731 return visitChildren(cursor);
732 }
733 case CXCursor_FunctionTemplate:
734 templateString = templateDecl(cursor);
735 Q_FALLTHROUGH();
736 case CXCursor_FunctionDecl:
737 case CXCursor_CXXMethod:
738 case CXCursor_Constructor:
739 case CXCursor_Destructor:
740 case CXCursor_ConversionFunction: {
741 if (findNodeForCursor(qdb: qdb_, cur: cursor)) // Was already parsed, probably in another TU
742 return CXChildVisit_Continue;
743 QString name = functionName(cursor);
744 if (ignoredSymbol(symbolName: name))
745 return CXChildVisit_Continue;
746 // constexpr constructors generate also a global instance; ignore
747 if (kind == CXCursor_Constructor && parent_ == qdb_->primaryTreeRoot())
748 return CXChildVisit_Continue;
749
750 auto *fn = new FunctionNode(parent_, name);
751 CXSourceRange range = clang_Cursor_getCommentRange(C: cursor);
752 if (!clang_Range_isNull(range)) {
753 QString comment = getSpelling(range);
754 if (comment.startsWith(s: "//!")) {
755 qsizetype tag = comment.indexOf(c: QChar('['));
756 if (tag > 0) {
757 qsizetype end = comment.indexOf(c: QChar(']'), from: ++tag);
758 if (end > 0)
759 fn->setTag(comment.mid(position: tag, n: end - tag));
760 }
761 }
762 }
763 processFunction(fn, cursor);
764 fn->setTemplateDecl(templateString);
765 return CXChildVisit_Continue;
766 }
767#if CINDEX_VERSION >= 36
768 case CXCursor_FriendDecl: {
769 return visitChildren(cursor);
770 }
771#endif
772 case CXCursor_EnumDecl: {
773 auto *en = static_cast<EnumNode *>(findNodeForCursor(qdb: qdb_, cur: cursor));
774 if (en && en->items().size())
775 return CXChildVisit_Continue; // Was already parsed, probably in another TU
776 QString enumTypeName = fromCXString(string: clang_getCursorSpelling(cursor));
777 if (enumTypeName.isEmpty()) {
778 enumTypeName = "anonymous";
779 if (parent_ && (parent_->isClassNode() || parent_->isNamespace())) {
780 Node *n = parent_->findNonfunctionChild(name: enumTypeName, &Node::isEnumType);
781 if (n)
782 en = static_cast<EnumNode *>(n);
783 }
784 }
785 if (!en) {
786 en = new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(C: cursor));
787 en->setAccess(fromCX_CXXAccessSpecifier(spec: clang_getCXXAccessSpecifier(cursor)));
788 en->setLocation(fromCXSourceLocation(location: clang_getCursorLocation(cursor)));
789 }
790
791 // Enum values
792 visitChildrenLambda(cursor, lambda: [&](CXCursor cur) {
793 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
794 return CXChildVisit_Continue;
795
796 QString value;
797 visitChildrenLambda(cursor: cur, lambda: [&](CXCursor cur) {
798 if (clang_isExpression(clang_getCursorKind(cur))) {
799 value = getSpelling(range: clang_getCursorExtent(cur));
800 return CXChildVisit_Break;
801 }
802 return CXChildVisit_Continue;
803 });
804 if (value.isEmpty()) {
805 QLatin1String hex("0x");
806 if (!en->items().isEmpty() && en->items().last().value().startsWith(s: hex)) {
807 value = hex + QString::number(clang_getEnumConstantDeclValue(C: cur), base: 16);
808 } else {
809 value = QString::number(clang_getEnumConstantDeclValue(C: cur));
810 }
811 }
812
813 en->addItem(item: EnumItem(fromCXString(string: clang_getCursorSpelling(cur)), value));
814 return CXChildVisit_Continue;
815 });
816 return CXChildVisit_Continue;
817 }
818 case CXCursor_FieldDecl:
819 case CXCursor_VarDecl: {
820 if (findNodeForCursor(qdb: qdb_, cur: cursor)) // Was already parsed, probably in another TU
821 return CXChildVisit_Continue;
822
823 auto access = fromCX_CXXAccessSpecifier(spec: clang_getCXXAccessSpecifier(cursor));
824 auto var = new VariableNode(parent_, fromCXString(string: clang_getCursorSpelling(cursor)));
825 var->setAccess(access);
826 var->setLocation(fromCXSourceLocation(location: clang_getCursorLocation(cursor)));
827 var->setLeftType(fromCXString(string: clang_getTypeSpelling(CT: clang_getCursorType(C: cursor))));
828 var->setStatic(kind == CXCursor_VarDecl && parent_->isClassNode());
829 return CXChildVisit_Continue;
830 }
831 case CXCursor_TypedefDecl: {
832 if (findNodeForCursor(qdb: qdb_, cur: cursor)) // Was already parsed, probably in another TU
833 return CXChildVisit_Continue;
834 auto *td = new TypedefNode(parent_, fromCXString(string: clang_getCursorSpelling(cursor)));
835 td->setAccess(fromCX_CXXAccessSpecifier(spec: clang_getCXXAccessSpecifier(cursor)));
836 td->setLocation(fromCXSourceLocation(location: clang_getCursorLocation(cursor)));
837 // Search to see if this is a Q_DECLARE_FLAGS (if the type is QFlags<ENUM>)
838 visitChildrenLambda(cursor, lambda: [&](CXCursor cur) {
839 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
840 || fromCXString(string: clang_getCursorSpelling(cur)) != QLatin1String("QFlags"))
841 return CXChildVisit_Continue;
842 // Found QFlags<XXX>
843 visitChildrenLambda(cursor, lambda: [&](CXCursor cur) {
844 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
845 return CXChildVisit_Continue;
846 auto *en =
847 findNodeForCursor(qdb: qdb_, cur: clang_getTypeDeclaration(T: clang_getCursorType(C: cur)));
848 if (en && en->isEnumType())
849 static_cast<EnumNode *>(en)->setFlagsType(td);
850 return CXChildVisit_Break;
851 });
852 return CXChildVisit_Break;
853 });
854 return CXChildVisit_Continue;
855 }
856 default:
857 if (clang_isDeclaration(kind) && parent_->isClassNode()) {
858 // may be a property macro or a static_assert
859 // which is not exposed from the clang API
860 parseProperty(spelling: getSpelling(range: clang_getCursorExtent(cursor)),
861 loc: fromCXSourceLocation(location: loc));
862 }
863 return CXChildVisit_Continue;
864 }
865}
866
867void ClangVisitor::readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor)
868{
869 Parameters &parameters = fn->parameters();
870 // Visit the parameters and attributes
871 int i = 0;
872 visitChildrenLambda(cursor, lambda: [&](CXCursor cur) {
873 auto kind = clang_getCursorKind(cur);
874 if (kind == CXCursor_AnnotateAttr) {
875 QString annotation = fromCXString(string: clang_getCursorDisplayName(cur));
876 if (annotation == QLatin1String("qt_slot")) {
877 fn->setMetaness(FunctionNode::Slot);
878 } else if (annotation == QLatin1String("qt_signal")) {
879 fn->setMetaness(FunctionNode::Signal);
880 }
881 if (annotation == QLatin1String("qt_invokable"))
882 fn->setInvokable(true);
883 } else if (kind == CXCursor_CXXOverrideAttr) {
884 fn->setOverride(true);
885 } else if (kind == CXCursor_ParmDecl) {
886 if (i >= parameters.count())
887 return CXChildVisit_Break; // Attributes comes before parameters so we can break.
888 QString name = fromCXString(string: clang_getCursorSpelling(cur));
889 if (!name.isEmpty()) {
890 parameters[i].setName(name);
891 // Find the default value
892 visitChildrenLambda(cursor: cur, lambda: [&](CXCursor cur) {
893 if (clang_isExpression(clang_getCursorKind(cur))) {
894 QString defaultValue = getSpelling(range: clang_getCursorExtent(cur));
895 if (defaultValue.startsWith(c: '=')) // In some cases, the = is part of the range.
896 defaultValue = QStringView{defaultValue}.mid(pos: 1).trimmed().toString();
897 if (defaultValue.isEmpty())
898 defaultValue = QStringLiteral("...");
899 parameters[i].setDefaultValue(defaultValue);
900 return CXChildVisit_Break;
901 }
902 return CXChildVisit_Continue;
903 });
904 }
905 ++i;
906 }
907 return CXChildVisit_Continue;
908 });
909}
910
911/*!
912 * Returns the underlying Decl that \a cursor represents.
913 *
914 * This can be used to drop back down from a LibClang's CXCursor to
915 * the underlying C++ AST that Clang provides.
916 *
917 * It should be used when LibClang does not expose certain
918 * functionalities that are available in the C++ AST.
919 *
920 * The CXCursor should represent a declaration. Usages of this
921 * function on CXCursors that do not represent a declaration may
922 * produce undefined results.
923 */
924static const clang::Decl* get_cursor_declaration(CXCursor cursor) {
925 assert(clang_isDeclaration(clang_getCursorKind(cursor)));
926
927 return static_cast<const clang::Decl*>(cursor.data[0]);
928}
929
930void ClangVisitor::processFunction(FunctionNode *fn, CXCursor cursor)
931{
932 CXCursorKind kind = clang_getCursorKind(cursor);
933 CXType funcType = clang_getCursorType(C: cursor);
934 fn->setAccess(fromCX_CXXAccessSpecifier(spec: clang_getCXXAccessSpecifier(cursor)));
935 fn->setLocation(fromCXSourceLocation(location: clang_getCursorLocation(cursor)));
936 if (kind == CXCursor_Constructor
937 // a constructor template is classified as CXCursor_FunctionTemplate
938 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
939 fn->setMetaness(FunctionNode::Ctor);
940 else if (kind == CXCursor_Destructor)
941 fn->setMetaness(FunctionNode::Dtor);
942 else
943 fn->setReturnType(adjustTypeName(
944 typeName: fromCXString(string: clang_getTypeSpelling(CT: clang_getResultType(T: funcType)))));
945
946 fn->setStatic(clang_CXXMethod_isStatic(C: cursor));
947 fn->setConst(clang_CXXMethod_isConst(C: cursor));
948 fn->setVirtualness(!clang_CXXMethod_isVirtual(C: cursor)
949 ? FunctionNode::NonVirtual
950 : clang_CXXMethod_isPureVirtual(C: cursor)
951 ? FunctionNode::PureVirtual
952 : FunctionNode::NormalVirtual);
953
954 // REMARK: We assume that the following operations and casts are
955 // generally safe.
956 // Callers of those methods will generally check at the LibClang
957 // level the kind of cursor we are dealing with and will pass on
958 // only valid cursors that are of a function kind and that are at
959 // least a declaration.
960 //
961 // Failure to do so implies a bug in the call chain and should be
962 // dealt with as such.
963 const clang::Decl* declaration = get_cursor_declaration(cursor);
964 const clang::FunctionDecl* function_declaration{nullptr};
965 if (auto templated_decl = llvm::dyn_cast_or_null<const clang::FunctionTemplateDecl>(Val: declaration))
966 function_declaration = templated_decl->getTemplatedDecl();
967 else function_declaration = static_cast<const clang::FunctionDecl*>(declaration);
968
969 assert(function_declaration);
970
971 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<const clang::CXXConstructorDecl>(Val: function_declaration);
972
973 if (constructor_declaration && constructor_declaration->isCopyConstructor()) fn->setMetaness(FunctionNode::CCtor);
974 else if (constructor_declaration && constructor_declaration->isMoveConstructor()) fn->setMetaness(FunctionNode::MCtor);
975
976 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<const clang::CXXConversionDecl>(Val: function_declaration);
977
978 if (function_declaration->isConstexpr()) fn->markConstexpr();
979 if (
980 (constructor_declaration && constructor_declaration->isExplicit()) ||
981 (conversion_declaration && conversion_declaration->isExplicit())
982 ) fn->markExplicit();
983
984 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<const clang::CXXMethodDecl>(Val: function_declaration);
985
986 if (method_declaration && method_declaration->isCopyAssignmentOperator()) fn->setMetaness(FunctionNode::CAssign);
987 else if (method_declaration && method_declaration->isMoveAssignmentOperator()) fn->setMetaness(FunctionNode::MAssign);
988
989 const clang::FunctionType* function_type = function_declaration->getFunctionType();
990 const clang::FunctionProtoType* function_prototype = static_cast<const clang::FunctionProtoType*>(function_type);
991
992 if (function_prototype) {
993 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
994
995 if (exception_specification.Type != clang::ExceptionSpecificationType::EST_None) {
996 clang::SourceManager& source_manager = function_declaration->getASTContext().getSourceManager();
997 const clang::LangOptions& lang_options = function_declaration->getASTContext().getLangOpts();
998
999 fn->markNoexcept(
1000 expression: exception_specification.NoexceptExpr ?
1001 QString::fromStdString(s: clang::Lexer::getSourceText(Range: clang::CharSourceRange::getTokenRange(R: exception_specification.NoexceptExpr->getSourceRange()), SM: source_manager, LangOpts: lang_options).str()) :
1002 ""
1003 );
1004 }
1005 }
1006
1007 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(T: funcType);
1008 if (refQualKind == CXRefQualifier_LValue)
1009 fn->setRef(true);
1010 else if (refQualKind == CXRefQualifier_RValue)
1011 fn->setRefRef(true);
1012 // For virtual functions, determine what it overrides
1013 // (except for destructor for which we do not want to classify as overridden)
1014 if (!fn->isNonvirtual() && kind != CXCursor_Destructor)
1015 setOverridesForFunction(fn, cursor);
1016
1017 Parameters &parameters = fn->parameters();
1018 parameters.clear();
1019 parameters.reserve(count: function_declaration->getNumParams());
1020
1021 const clang::LangOptions& lang_options = function_declaration->getASTContext().getLangOpts();
1022 clang::PrintingPolicy p{lang_options};
1023
1024 for (clang::ParmVarDecl* const parameter_declaration : function_declaration->parameters()) {
1025 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1026
1027 parameters.append(type: adjustTypeName(typeName: QString::fromStdString(s: parameter_type.getAsString(Policy: p))));
1028
1029 if (!parameter_type.isCanonical())
1030 parameters.last().setCanonicalType(QString::fromStdString(s: parameter_type.getCanonicalType().getAsString(Policy: p)));
1031 }
1032
1033
1034 if (parameters.count() > 0) {
1035 if (parameters.last().type().endsWith(s: QLatin1String("QPrivateSignal"))) {
1036 parameters.pop_back(); // remove the QPrivateSignal argument
1037 parameters.setPrivateSignal();
1038 }
1039 }
1040 if (clang_isFunctionTypeVariadic(T: funcType))
1041 parameters.append(QStringLiteral("..."));
1042 readParameterNamesAndAttributes(fn, cursor);
1043
1044 if (declaration->getFriendObjectKind() != clang::Decl::FOK_None)
1045 fn->setRelatedNonmember(true);
1046}
1047
1048bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc)
1049{
1050 if (!spelling.startsWith(s: QLatin1String("Q_PROPERTY"))
1051 && !spelling.startsWith(s: QLatin1String("QDOC_PROPERTY"))
1052 && !spelling.startsWith(s: QLatin1String("Q_OVERRIDE")))
1053 return false;
1054
1055 qsizetype lpIdx = spelling.indexOf(c: QChar('('));
1056 qsizetype rpIdx = spelling.lastIndexOf(c: QChar(')'));
1057 if (lpIdx <= 0 || rpIdx <= lpIdx)
1058 return false;
1059
1060 QString signature = spelling.mid(position: lpIdx + 1, n: rpIdx - lpIdx - 1);
1061 signature = signature.simplified();
1062 QStringList parts = signature.split(sep: QChar(' '), behavior: Qt::SkipEmptyParts);
1063
1064 static const QStringList attrs =
1065 QStringList() << "READ" << "MEMBER" << "WRITE"
1066 << "NOTIFY" << "CONSTANT" << "FINAL"
1067 << "REQUIRED" << "BINDABLE" << "DESIGNABLE"
1068 << "RESET" << "REVISION" << "SCRIPTABLE"
1069 << "STORED" << "USER";
1070
1071 // Find the location of the first attribute. All preceding parts
1072 // represent the property type + name.
1073 auto it = std::find_if(first: parts.cbegin(), last: parts.cend(),
1074 pred: [](const QString &attr) -> bool {
1075 return attrs.contains(str: attr);
1076 });
1077
1078 if (it == parts.cend() || std::distance(first: parts.cbegin(), last: it) < 2)
1079 return false;
1080
1081 QStringList typeParts;
1082 std::copy(first: parts.cbegin(), last: it, result: std::back_inserter(x&: typeParts));
1083 parts.erase(abegin: parts.cbegin(), aend: it);
1084 QString name = typeParts.takeLast();
1085
1086 // Move the pointer operator(s) from name to type
1087 while (!name.isEmpty() && name.front() == QChar('*')) {
1088 typeParts.last().push_back(c: name.front());
1089 name.removeFirst();
1090 }
1091
1092 // Need at least READ or MEMBER + getter/member name
1093 if (parts.size() < 2 || name.isEmpty())
1094 return false;
1095
1096 auto *property = new PropertyNode(parent_, name);
1097 property->setAccess(Access::Public);
1098 property->setLocation(loc);
1099 property->setDataType(typeParts.join(sep: QChar(' ')));
1100
1101 int i = 0;
1102 while (i < parts.size()) {
1103 const QString &key = parts.at(i: i++);
1104 // Keywords with no associated values
1105 if (key == "CONSTANT") {
1106 property->setConstant();
1107 } else if (key == "REQUIRED") {
1108 property->setRequired();
1109 }
1110 if (i < parts.size()) {
1111 QString value = parts.at(i: i++);
1112 if (key == "READ") {
1113 qdb_->addPropertyFunction(property, funcName: value, funcRole: PropertyNode::FunctionRole::Getter);
1114 } else if (key == "WRITE") {
1115 qdb_->addPropertyFunction(property, funcName: value, funcRole: PropertyNode::FunctionRole::Setter);
1116 property->setWritable(true);
1117 } else if (key == "MEMBER") {
1118 property->setWritable(true);
1119 } else if (key == "STORED") {
1120 property->setStored(value.toLower() == "true");
1121 } else if (key == "BINDABLE") {
1122 property->setPropertyType(PropertyNode::PropertyType::BindableProperty);
1123 qdb_->addPropertyFunction(property, funcName: value, funcRole: PropertyNode::FunctionRole::Bindable);
1124 } else if (key == "RESET") {
1125 qdb_->addPropertyFunction(property, funcName: value, funcRole: PropertyNode::FunctionRole::Resetter);
1126 } else if (key == "NOTIFY") {
1127 qdb_->addPropertyFunction(property, funcName: value, funcRole: PropertyNode::FunctionRole::Notifier);
1128 }
1129 }
1130 }
1131 return true;
1132}
1133
1134/*!
1135 Given a comment at location \a loc, return a Node for this comment
1136 \a nextCommentLoc is the location of the next comment so the declaration
1137 must be inbetween.
1138 Returns nullptr if no suitable declaration was found between the two comments.
1139 */
1140Node *ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
1141{
1142 ClangVisitor::SimpleLoc docloc;
1143 clang_getPresumedLocation(location: loc, filename: nullptr, line: &docloc.line, column: &docloc.column);
1144 auto decl_it = declMap_.upperBound(key: docloc);
1145 if (decl_it == declMap_.end())
1146 return nullptr;
1147
1148 unsigned int declLine = decl_it.key().line;
1149 unsigned int nextCommentLine;
1150 clang_getPresumedLocation(location: nextCommentLoc, filename: nullptr, line: &nextCommentLine, column: nullptr);
1151 if (nextCommentLine < declLine)
1152 return nullptr; // there is another comment before the declaration, ignore it.
1153
1154 // make sure the previous decl was finished.
1155 if (decl_it != declMap_.begin()) {
1156 CXSourceLocation prevDeclEnd = clang_getRangeEnd(range: clang_getCursorExtent(*(std::prev(x: decl_it))));
1157 unsigned int prevDeclLine;
1158 clang_getPresumedLocation(location: prevDeclEnd, filename: nullptr, line: &prevDeclLine, column: nullptr);
1159 if (prevDeclLine >= docloc.line) {
1160 // The previous declaration was still going. This is only valid if the previous
1161 // declaration is a parent of the next declaration.
1162 auto parent = clang_getCursorLexicalParent(cursor: *decl_it);
1163 if (!clang_equalCursors(parent, *(std::prev(x: decl_it))))
1164 return nullptr;
1165 }
1166 }
1167 auto *node = findNodeForCursor(qdb: qdb_, cur: *decl_it);
1168 // borrow the parameter name from the definition
1169 if (node && node->isFunction(g: Node::CPP))
1170 readParameterNamesAndAttributes(fn: static_cast<FunctionNode *>(node), cursor: *decl_it);
1171 return node;
1172}
1173
1174/*!
1175 Get the include paths from the qdoc configuration database
1176 \a config. Call the initializeParser() in the base class.
1177 Get the defines list from the qdocconf database.
1178
1179 \note on \macos and Linux, we try to also query the system
1180 and framework (\macos) include paths from the compiler.
1181 */
1182void ClangCodeParser::initializeParser()
1183{
1184 Config &config = Config::instance();
1185 auto args = config.getCanonicalPathList(CONFIG_INCLUDEPATHS,
1186 flags: Config::IncludePaths);
1187#ifdef Q_OS_MACOS
1188 args.append(Utilities::getInternalIncludePaths(QStringLiteral("clang++")));
1189#elif defined(Q_OS_LINUX)
1190 args.append(other: Utilities::getInternalIncludePaths(QStringLiteral("g++")));
1191#endif
1192 m_includePaths.clear();
1193 for (const auto &path : std::as_const(t&: args)) {
1194 if (!path.isEmpty())
1195 m_includePaths.append(t: path.toUtf8());
1196 }
1197 m_includePaths.erase(abegin: std::unique(first: m_includePaths.begin(), last: m_includePaths.end()),
1198 aend: m_includePaths.end());
1199 m_pchFileDir.reset(other: nullptr);
1200 m_allHeaders.clear();
1201 m_pchName.clear();
1202 m_defines.clear();
1203 QSet<QString> accepted;
1204 {
1205 const QStringList tmpDefines{config.get(CONFIG_CLANGDEFINES).asStringList()};
1206 for (const QString &def : tmpDefines) {
1207 if (!accepted.contains(value: def)) {
1208 QByteArray tmp("-D");
1209 tmp.append(a: def.toUtf8());
1210 m_defines.append(t: tmp.constData());
1211 accepted.insert(value: def);
1212 }
1213 }
1214 }
1215 {
1216 const QStringList tmpDefines{config.get(CONFIG_DEFINES).asStringList()};
1217 for (const QString &def : tmpDefines) {
1218 if (!accepted.contains(value: def) && !def.contains(c: QChar('*'))) {
1219 QByteArray tmp("-D");
1220 tmp.append(a: def.toUtf8());
1221 m_defines.append(t: tmp.constData());
1222 accepted.insert(value: def);
1223 }
1224 }
1225 }
1226 qCDebug(lcQdoc).nospace() << __FUNCTION__ << " Clang v" << CINDEX_VERSION_MAJOR << '.'
1227 << CINDEX_VERSION_MINOR;
1228}
1229
1230/*!
1231 */
1232QString ClangCodeParser::language()
1233{
1234 return "Clang";
1235}
1236
1237/*!
1238 Returns a list of extensions for source files, i.e. not
1239 header files.
1240 */
1241QStringList ClangCodeParser::sourceFileNameFilter()
1242{
1243 return QStringList() << "*.c++"
1244 << "*.cc"
1245 << "*.cpp"
1246 << "*.cxx"
1247 << "*.mm";
1248}
1249
1250/*!
1251 Parse the C++ header file identified by \a filePath and add
1252 the parsed contents to the database. The \a location is used
1253 for reporting errors.
1254 */
1255void ClangCodeParser::parseHeaderFile(const Location & /*location*/, const QString &filePath)
1256{
1257 QFileInfo fi(filePath);
1258 const QString &fileName = fi.fileName();
1259 const QString &canonicalPath = fi.canonicalPath();
1260
1261 if (m_allHeaders.contains(key: fileName, value: canonicalPath))
1262 return;
1263
1264 m_allHeaders.insert(key: fileName, value: canonicalPath);
1265}
1266
1267static const char *defaultArgs_[] = {
1268/*
1269 https://bugreports.qt.io/browse/QTBUG-94365
1270 An unidentified bug in Clang 15.x causes parsing failures due to errors in
1271 the AST. This replicates only with C++20 support enabled - avoid the issue
1272 by using C++17 with Clang 15.
1273 */
1274#if LIBCLANG_VERSION_MAJOR == 15
1275 "-std=c++17",
1276#else
1277 "-std=c++20",
1278#endif
1279#ifndef Q_OS_WIN
1280 "-fPIC",
1281#else
1282 "-fms-compatibility-version=19",
1283#endif
1284 "-DQ_QDOC",
1285 "-DQ_CLANG_QDOC",
1286 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
1287 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
1288 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
1289 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
1290 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
1291 "-Wno-constant-logical-operand",
1292 "-Wno-macro-redefined",
1293 "-Wno-nullability-completeness",
1294 "-fvisibility=default",
1295 "-ferror-limit=0",
1296 ("-I" CLANG_RESOURCE_DIR)
1297};
1298
1299/*!
1300 Load the default arguments and the defines into \a args.
1301 Clear \a args first.
1302 */
1303void ClangCodeParser::getDefaultArgs()
1304{
1305 m_args.clear();
1306 m_args.insert(position: m_args.begin(), first: std::begin(arr&: defaultArgs_), last: std::end(arr&: defaultArgs_));
1307 // Add the defines from the qdocconf file.
1308 for (const auto &p : std::as_const(t&: m_defines))
1309 m_args.push_back(x: p.constData());
1310}
1311
1312static QList<QByteArray> includePathsFromHeaders(const QMultiHash<QString, QString> &allHeaders)
1313{
1314 QList<QByteArray> result;
1315 for (auto it = allHeaders.cbegin(); it != allHeaders.cend(); ++it) {
1316 const QByteArray path = "-I" + it.value().toLatin1();
1317 const QByteArray parent =
1318 "-I" + QDir::cleanPath(path: it.value() + QLatin1String("/../")).toLatin1();
1319 if (!result.contains(t: path))
1320 result.append(t: path);
1321 if (!result.contains(t: parent))
1322 result.append(t: parent);
1323 }
1324 return result;
1325}
1326
1327/*!
1328 Load the include paths into \a moreArgs. If no include paths
1329 were provided, try to guess reasonable include paths.
1330 */
1331void ClangCodeParser::getMoreArgs()
1332{
1333 if (m_includePaths.isEmpty()) {
1334 /*
1335 The include paths provided are inadequate. Make a list
1336 of reasonable places to look for include files and use
1337 that list instead.
1338 */
1339 qCWarning(lcQdoc) << "No include paths passed to qdoc; guessing reasonable include paths";
1340
1341 QString basicIncludeDir = QDir::cleanPath(path: QString(Config::installDir + "/../include"));
1342 m_moreArgs += "-I" + basicIncludeDir.toLatin1();
1343 m_moreArgs += includePathsFromHeaders(allHeaders: m_allHeaders);
1344 } else {
1345 m_moreArgs = m_includePaths;
1346 }
1347}
1348
1349/*!
1350 Building the PCH must be possible when there are no .cpp
1351 files, so it is moved here to its own member function, and
1352 it is called after the list of header files is complete.
1353 */
1354void ClangCodeParser::buildPCH(QString module_header)
1355{
1356 if (m_pchFileDir) return;
1357 if (module_header.isEmpty()) return;
1358
1359 m_pchFileDir.reset(other: new QTemporaryDir(QDir::tempPath() + QLatin1String("/qdoc_pch")));
1360 if (m_pchFileDir->isValid()) {
1361 const QByteArray module = module_header.toUtf8();
1362 QByteArray header;
1363
1364 qCDebug(lcQdoc) << "Build and visit PCH for" << module_header;
1365 // A predicate for std::find_if() to locate a path to the module's header
1366 // (e.g. QtGui/QtGui) to be used as pre-compiled header
1367 struct FindPredicate
1368 {
1369 enum SearchType { Any, Module };
1370 QByteArray &candidate_;
1371 const QByteArray &module_;
1372 SearchType type_;
1373 FindPredicate(QByteArray &candidate, const QByteArray &module,
1374 SearchType type = Any)
1375 : candidate_(candidate), module_(module), type_(type)
1376 {
1377 }
1378
1379 bool operator()(const QByteArray &p) const
1380 {
1381 if (type_ != Any && !p.endsWith(bv: module_))
1382 return false;
1383 candidate_ = p + "/";
1384 candidate_.append(a: module_);
1385 if (p.startsWith(bv: "-I"))
1386 candidate_ = candidate_.mid(index: 2);
1387 return QFile::exists(fileName: QString::fromUtf8(ba: candidate_));
1388 }
1389 };
1390
1391 // First, search for an include path that contains the module name, then any path
1392 QByteArray candidate;
1393 auto it = std::find_if(first: m_includePaths.begin(), last: m_includePaths.end(),
1394 pred: FindPredicate(candidate, module, FindPredicate::Module));
1395 if (it == m_includePaths.end())
1396 it = std::find_if(first: m_includePaths.begin(), last: m_includePaths.end(),
1397 pred: FindPredicate(candidate, module, FindPredicate::Any));
1398 if (it != m_includePaths.end())
1399 header = candidate;
1400
1401 if (header.isEmpty()) {
1402 qWarning() << "(qdoc) Could not find the module header in include paths for module"
1403 << module << " (include paths: " << m_includePaths << ")";
1404 qWarning() << " Artificial module header built from header dirs in qdocconf "
1405 "file";
1406 }
1407 m_args.push_back(x: "-xc++");
1408 CXTranslationUnit tu;
1409 QString tmpHeader = m_pchFileDir->path() + "/" + module;
1410 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(flags: QIODevice::Text | QIODevice::WriteOnly)) {
1411 QTextStream out(&tmpHeaderFile);
1412 if (header.isEmpty()) {
1413 for (auto it = m_allHeaders.constKeyValueBegin();
1414 it != m_allHeaders.constKeyValueEnd(); ++it) {
1415 if (!(*it).first.endsWith(s: QLatin1String("_p.h"))
1416 && !(*it).first.startsWith(s: QLatin1String("moc_"))) {
1417 QString line = QLatin1String("#include \"") + (*it).second
1418 + QLatin1String("/") + (*it).first + QLatin1String("\"");
1419 out << line << "\n";
1420 }
1421 }
1422 } else {
1423 QFileInfo headerFile(header);
1424 if (!headerFile.exists()) {
1425 qWarning() << "Could not find module header file" << header;
1426 return;
1427 }
1428 out << QLatin1String("#include \"") + header + QLatin1String("\"");
1429 }
1430 }
1431
1432 CXErrorCode err =
1433 clang_parseTranslationUnit2(CIdx: index_, source_filename: tmpHeader.toLatin1().data(), command_line_args: m_args.data(),
1434 num_command_line_args: static_cast<int>(m_args.size()), unsaved_files: nullptr, num_unsaved_files: 0,
1435 options: flags_ | CXTranslationUnit_ForSerialization, out_TU: &tu);
1436 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << tmpHeader << m_args
1437 << ") returns" << err;
1438
1439 printDiagnostics(translationUnit: tu);
1440
1441 if (!err && tu) {
1442 m_pchName = m_pchFileDir->path().toUtf8() + "/" + module + ".pch";
1443 auto error = clang_saveTranslationUnit(TU: tu, FileName: m_pchName.constData(),
1444 options: clang_defaultSaveOptions(TU: tu));
1445 if (error) {
1446 qCCritical(lcQdoc) << "Could not save PCH file for" << module_header;
1447 m_pchName.clear();
1448 } else {
1449 // Visit the header now, as token from pre-compiled header won't be visited
1450 // later
1451 CXCursor cur = clang_getTranslationUnitCursor(tu);
1452 ClangVisitor visitor(m_qdb, m_allHeaders);
1453 visitor.visitChildren(cursor: cur);
1454 qCDebug(lcQdoc) << "PCH built and visited for" << module_header;
1455 }
1456 } else {
1457 m_pchFileDir->remove();
1458 qCCritical(lcQdoc) << "Could not create PCH file for " << module_header;
1459 }
1460 clang_disposeTranslationUnit(tu);
1461 m_args.pop_back(); // remove the "-xc++";
1462 }
1463}
1464
1465/*!
1466 Precompile the header files for the current module.
1467 */
1468void ClangCodeParser::precompileHeaders(QString module_header)
1469{
1470 getDefaultArgs();
1471 getMoreArgs();
1472 for (const auto &p : std::as_const(t&: m_moreArgs))
1473 m_args.push_back(x: p.constData());
1474
1475 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
1476 | CXTranslationUnit_SkipFunctionBodies
1477 | CXTranslationUnit_KeepGoing);
1478
1479 index_ = clang_createIndex(excludeDeclarationsFromPCH: 1, displayDiagnostics: kClangDontDisplayDiagnostics);
1480
1481 buildPCH(module_header);
1482 clang_disposeIndex(index: index_);
1483}
1484
1485static float getUnpatchedVersion(QString t)
1486{
1487 if (t.count(c: QChar('.')) > 1)
1488 t.truncate(pos: t.lastIndexOf(c: QChar('.')));
1489 return t.toFloat();
1490}
1491
1492/*!
1493 Get ready to parse the C++ cpp file identified by \a filePath
1494 and add its parsed contents to the database. \a location is
1495 used for reporting errors.
1496
1497 Call matchDocsAndStuff() to do all the parsing and tree building.
1498 */
1499void ClangCodeParser::parseSourceFile(const Location & /*location*/, const QString &filePath, CppCodeParser& cpp_code_parser)
1500{
1501 /*
1502 The set of open namespaces is cleared before parsing
1503 each source file. The word "source" here means cpp file.
1504 */
1505 m_qdb->clearOpenNamespaces();
1506 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
1507 | CXTranslationUnit_SkipFunctionBodies
1508 | CXTranslationUnit_KeepGoing);
1509
1510 index_ = clang_createIndex(excludeDeclarationsFromPCH: 1, displayDiagnostics: kClangDontDisplayDiagnostics);
1511
1512 getDefaultArgs();
1513 if (!m_pchName.isEmpty() && !filePath.endsWith(s: ".mm")) {
1514 m_args.push_back(x: "-w");
1515 m_args.push_back(x: "-include-pch");
1516 m_args.push_back(x: m_pchName.constData());
1517 }
1518 getMoreArgs();
1519 for (const auto &p : std::as_const(t&: m_moreArgs))
1520 m_args.push_back(x: p.constData());
1521
1522 CXTranslationUnit tu;
1523 CXErrorCode err =
1524 clang_parseTranslationUnit2(CIdx: index_, source_filename: filePath.toLocal8Bit(), command_line_args: m_args.data(),
1525 num_command_line_args: static_cast<int>(m_args.size()), unsaved_files: nullptr, num_unsaved_files: 0, options: flags_, out_TU: &tu);
1526 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << filePath << m_args
1527 << ") returns" << err;
1528 printDiagnostics(translationUnit: tu);
1529
1530 if (err || !tu) {
1531 qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err;
1532 clang_disposeTranslationUnit(tu);
1533 clang_disposeIndex(index: index_);
1534 return;
1535 }
1536
1537 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
1538 ClangVisitor visitor(m_qdb, m_allHeaders);
1539 visitor.visitChildren(cursor: tuCur);
1540
1541 CXToken *tokens;
1542 unsigned int numTokens = 0;
1543 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
1544 clang_tokenize(TU: tu, Range: clang_getCursorExtent(tuCur), Tokens: &tokens, NumTokens: &numTokens);
1545
1546 for (unsigned int i = 0; i < numTokens; ++i) {
1547 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
1548 continue;
1549 QString comment = fromCXString(string: clang_getTokenSpelling(tu, tokens[i]));
1550 if (!comment.startsWith(s: "/*!"))
1551 continue;
1552
1553 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
1554 auto loc = fromCXSourceLocation(location: commentLoc);
1555 auto end_loc = fromCXSourceLocation(location: clang_getRangeEnd(range: clang_getTokenExtent(tu, tokens[i])));
1556 Doc::trimCStyleComment(location&: loc, str&: comment);
1557
1558 // Doc constructor parses the comment.
1559 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
1560 if (cpp_code_parser.hasTooManyTopics(doc))
1561 continue;
1562
1563 DocList docs;
1564 QString topic;
1565 NodeList nodes;
1566 const TopicList &topics = doc.topicsUsed();
1567 if (!topics.isEmpty())
1568 topic = topics[0].m_topic;
1569
1570 if (topic.isEmpty()) {
1571 Node *n = nullptr;
1572 if (i + 1 < numTokens) {
1573 // Try to find the next declaration.
1574 CXSourceLocation nextCommentLoc = commentLoc;
1575 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
1576 ++i; // already skip all the tokens that are not comments
1577 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
1578 n = visitor.nodeForCommentAtLocation(loc: commentLoc, nextCommentLoc);
1579 }
1580
1581 if (n) {
1582 nodes.append(t: n);
1583 docs.append(t: doc);
1584 } else if (CodeParser::isWorthWarningAbout(doc)) {
1585 bool future = false;
1586 if (doc.metaCommandsUsed().contains(COMMAND_SINCE)) {
1587 QString sinceVersion = doc.metaCommandArgs(COMMAND_SINCE).at(i: 0).first;
1588 if (getUnpatchedVersion(t: sinceVersion) >
1589 getUnpatchedVersion(t: Config::instance().get(CONFIG_VERSION).asString()))
1590 future = true;
1591 }
1592 if (!future) {
1593 doc.location().warning(
1594 QStringLiteral("Cannot tie this documentation to anything"),
1595 QStringLiteral("qdoc found a /*! ... */ comment, but there was no "
1596 "topic command (e.g., '\\%1', '\\%2') in the "
1597 "comment and no function definition following "
1598 "the comment.")
1599 .arg(COMMAND_FN, COMMAND_PAGE));
1600 }
1601 }
1602 } else {
1603 // Store the namespace scope from lexical parents of the comment
1604 m_namespaceScope.clear();
1605 CXCursor cur = clang_getCursor(tu, commentLoc);
1606 while (true) {
1607 CXCursorKind kind = clang_getCursorKind(cur);
1608 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
1609 break;
1610 if (kind == CXCursor_Namespace)
1611 m_namespaceScope << fromCXString(string: clang_getCursorSpelling(cur));
1612 cur = clang_getCursorLexicalParent(cursor: cur);
1613 }
1614 cpp_code_parser.processTopicArgs(doc, topic, nodes, docs);
1615 }
1616 cpp_code_parser.processMetaCommands(nodes, docs);
1617 }
1618
1619 clang_disposeTokens(TU: tu, Tokens: tokens, NumTokens: numTokens);
1620 clang_disposeTranslationUnit(tu);
1621 clang_disposeIndex(index: index_);
1622 m_namespaceScope.clear();
1623 s_fn.clear();
1624}
1625
1626/*!
1627 Use clang to parse the function signature from a function
1628 command. \a location is used for reporting errors. \a fnSignature
1629 is the string to parse. It is always a function decl.
1630 */
1631Node *ClangCodeParser::parseFnArg(const Location &location, const QString &fnSignature, const QString &idTag)
1632{
1633 Node *fnNode = nullptr;
1634 /*
1635 If the \fn command begins with a tag, then don't try to
1636 parse the \fn command with clang. Use the tag to search
1637 for the correct function node. It is an error if it can
1638 not be found. Return 0 in that case.
1639 */
1640 if (!idTag.isEmpty()) {
1641 fnNode = m_qdb->findFunctionNodeForTag(tag: idTag);
1642 if (!fnNode) {
1643 location.error(
1644 QStringLiteral("tag \\fn [%1] not used in any include file in current module").arg(a: idTag));
1645 } else {
1646 /*
1647 The function node was found. Use the formal
1648 parameter names from the \fn command, because
1649 they will be the names used in the documentation.
1650 */
1651 auto *fn = static_cast<FunctionNode *>(fnNode);
1652 QStringList leftParenSplit = fnSignature.mid(position: fnSignature.indexOf(s: fn->name())).split(sep: '(');
1653 if (leftParenSplit.size() > 1) {
1654 QStringList rightParenSplit = leftParenSplit[1].split(sep: ')');
1655 if (!rightParenSplit.empty()) {
1656 QString params = rightParenSplit[0];
1657 if (!params.isEmpty()) {
1658 QStringList commaSplit = params.split(sep: ',');
1659 Parameters &parameters = fn->parameters();
1660 if (parameters.count() == commaSplit.size()) {
1661 for (int i = 0; i < parameters.count(); ++i) {
1662 QStringList blankSplit = commaSplit[i].split(sep: ' ', behavior: Qt::SkipEmptyParts);
1663 if (blankSplit.size() > 1) {
1664 QString pName = blankSplit.last();
1665 // Remove any non-letters from the start of parameter name
1666 auto it = std::find_if(first: std::begin(cont&: pName), last: std::end(cont&: pName),
1667 pred: [](const QChar &c) { return c.isLetter(); });
1668 parameters[i].setName(
1669 pName.remove(i: 0, len: std::distance(first: std::begin(cont&: pName), last: it)));
1670 }
1671 }
1672 }
1673 }
1674 }
1675 }
1676 }
1677 return fnNode;
1678 }
1679 auto flags = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
1680 | CXTranslationUnit_SkipFunctionBodies
1681 | CXTranslationUnit_KeepGoing);
1682
1683 CXIndex index = clang_createIndex(excludeDeclarationsFromPCH: 1, displayDiagnostics: kClangDontDisplayDiagnostics);
1684
1685 std::vector<const char *> args(std::begin(arr&: defaultArgs_), std::end(arr&: defaultArgs_));
1686 // Add the defines from the qdocconf file.
1687 for (const auto &p : std::as_const(t&: m_defines))
1688 args.push_back(x: p.constData());
1689 if (!m_pchName.isEmpty()) {
1690 args.push_back(x: "-w");
1691 args.push_back(x: "-include-pch");
1692 args.push_back(x: m_pchName.constData());
1693 }
1694 CXTranslationUnit tu;
1695 s_fn.clear();
1696 for (const auto &ns : std::as_const(t&: m_namespaceScope))
1697 s_fn.prepend(a: "namespace " + ns.toUtf8() + " {");
1698 s_fn += fnSignature.toUtf8();
1699 if (!s_fn.endsWith(bv: ";"))
1700 s_fn += "{ }";
1701 s_fn.append(n: m_namespaceScope.size(), ch: '}');
1702
1703 const char *dummyFileName = fnDummyFileName;
1704 CXUnsavedFile unsavedFile { .Filename: dummyFileName, .Contents: s_fn.constData(),
1705 .Length: static_cast<unsigned long>(s_fn.size()) };
1706 CXErrorCode err = clang_parseTranslationUnit2(CIdx: index, source_filename: dummyFileName, command_line_args: args.data(),
1707 num_command_line_args: int(args.size()), unsaved_files: &unsavedFile, num_unsaved_files: 1, options: flags, out_TU: &tu);
1708 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << dummyFileName << args
1709 << ") returns" << err;
1710 printDiagnostics(translationUnit: tu);
1711 if (err || !tu) {
1712 location.error(QStringLiteral("clang could not parse \\fn %1").arg(a: fnSignature));
1713 clang_disposeTranslationUnit(tu);
1714 clang_disposeIndex(index);
1715 return fnNode;
1716 } else {
1717 /*
1718 Always visit the tu if one is constructed, because
1719 it might be possible to find the correct node, even
1720 if clang detected diagnostics. Only bother to report
1721 the diagnostics if they stop us finding the node.
1722 */
1723 CXCursor cur = clang_getTranslationUnitCursor(tu);
1724 ClangVisitor visitor(m_qdb, m_allHeaders);
1725 bool ignoreSignature = false;
1726 visitor.visitFnArg(cursor: cur, fnNode: &fnNode, ignoreSignature);
1727 /*
1728 If the visitor couldn't find a FunctionNode for the
1729 signature, then print the clang diagnostics if there
1730 were any.
1731 */
1732 if (fnNode == nullptr) {
1733 unsigned diagnosticCount = clang_getNumDiagnostics(Unit: tu);
1734 const auto &config = Config::instance();
1735 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
1736 bool report = true;
1737 QStringList signature = fnSignature.split(sep: QChar('('));
1738 if (signature.size() > 1) {
1739 QStringList qualifiedName = signature.at(i: 0).split(sep: QChar(' '));
1740 qualifiedName = qualifiedName.last().split(sep: QLatin1String("::"));
1741 if (qualifiedName.size() > 1) {
1742 QString qualifier = qualifiedName.at(i: 0);
1743 int i = 0;
1744 while (qualifier.size() > i && !qualifier.at(i).isLetter())
1745 qualifier[i++] = QChar(' ');
1746 if (i > 0)
1747 qualifier = qualifier.simplified();
1748 ClassNode *cn = m_qdb->findClassNode(path: QStringList(qualifier));
1749 if (cn && cn->isInternal())
1750 report = false;
1751 }
1752 }
1753 if (report) {
1754 location.warning(
1755 QStringLiteral("clang couldn't find function when parsing \\fn %1").arg(a: fnSignature));
1756 }
1757 }
1758 }
1759 }
1760 clang_disposeTranslationUnit(tu);
1761 clang_disposeIndex(index);
1762 return fnNode;
1763}
1764
1765void ClangCodeParser::printDiagnostics(const CXTranslationUnit &translationUnit) const
1766{
1767 if (!lcQdocClang().isDebugEnabled())
1768 return;
1769
1770 static const auto displayOptions = CXDiagnosticDisplayOptions::CXDiagnostic_DisplaySourceLocation
1771 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayColumn
1772 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayOption;
1773
1774 for (unsigned i = 0, numDiagnostics = clang_getNumDiagnostics(Unit: translationUnit); i < numDiagnostics; ++i) {
1775 auto diagnostic = clang_getDiagnostic(Unit: translationUnit, Index: i);
1776 auto formattedDiagnostic = clang_formatDiagnostic(Diagnostic: diagnostic, Options: displayOptions);
1777 qCDebug(lcQdocClang) << clang_getCString(string: formattedDiagnostic);
1778 clang_disposeString(string: formattedDiagnostic);
1779 clang_disposeDiagnostic(Diagnostic: diagnostic);
1780 }
1781}
1782
1783QT_END_NAMESPACE
1784

source code of qttools/src/qdoc/qdoc/clangcodeparser.cpp