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