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

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