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