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