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