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, [&parameters](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 &parameters = 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 &parameters = 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 &parameters = 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 &parameters = 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 &parameters = 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