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