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