0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
clang_utils.cc
Go to the documentation of this file.
1/**
2 * @file src/common/clang_utils.cc
3 *
4 * Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19#include "clang_utils.h"
20
21#include <clang/Lex/Preprocessor.h>
22
23namespace clanguml::common {
24
26 clang::AccessSpecifier access_specifier)
27{
28 auto access = model::access_t::kPublic;
29 switch (access_specifier) {
30 case clang::AccessSpecifier::AS_public:
32 break;
33 case clang::AccessSpecifier::AS_private:
35 break;
36 case clang::AccessSpecifier::AS_protected:
38 break;
39 default:
40 break;
41 }
42
43 return access;
44}
45
46model::namespace_ get_tag_namespace(const clang::TagDecl &declaration)
47{
49
50 const auto *parent{declaration.getParent()};
51
52 // First walk up to the nearest namespace, e.g. from nested class or enum
53 while ((parent != nullptr) && !parent->isNamespace()) {
54 parent = parent->getParent();
55 }
56
57 // Now build up the namespace
58 std::deque<std::string> namespace_tokens;
59 while ((parent != nullptr) && parent->isNamespace()) {
60 if (const auto *ns_decl = clang::dyn_cast<clang::NamespaceDecl>(parent);
61 ns_decl != nullptr) {
62 if (!ns_decl->isInline() && !ns_decl->isAnonymousNamespace())
63 namespace_tokens.push_front(ns_decl->getNameAsString());
64 }
65
66 parent = parent->getParent();
67 }
68
69 for (const auto &ns_token : namespace_tokens) {
70 ns |= ns_token;
71 }
72
73 return ns;
74}
75
76model::namespace_ get_template_namespace(const clang::TemplateDecl &declaration)
77{
78 model::namespace_ ns{declaration.getQualifiedNameAsString()};
79 ns.pop_back();
80
81 return ns;
82}
83
84std::string get_tag_name(const clang::TagDecl &declaration)
85{
86 auto base_name = declaration.getNameAsString();
87
88 if (base_name.empty()) {
89 base_name =
90 fmt::format("(anonymous_{})", std::to_string(declaration.getID()));
91 }
92
93 if ((declaration.getParent() != nullptr) &&
94 declaration.getParent()->isRecord()) {
95 // If the record is nested within another record (e.g. class or struct)
96 // we have to maintain a containment namespace in order to ensure
97 // unique names within the diagram
98 std::deque<std::string> record_parent_names;
99 record_parent_names.push_front(base_name);
100
101 const auto *cls_parent{declaration.getParent()};
102 while (cls_parent->isRecord()) {
103 if (const auto *record_decl =
104 clang::dyn_cast<clang::RecordDecl>(cls_parent);
105 record_decl != nullptr) {
106 record_parent_names.push_front(record_decl->getNameAsString());
107 }
108 cls_parent = cls_parent->getParent();
109 }
110 return fmt::format("{}", fmt::join(record_parent_names, "##"));
111 }
112
113 return base_name;
114}
115
116std::string to_string(const clang::ArrayType &array_type,
117 const clang::ASTContext &ctx, bool try_canonical,
118 std::vector<std::string> &dimensions)
119{
120 auto maybe_size = get_array_size(array_type);
121 std::string array_size =
122 maybe_size.has_value() ? std::to_string(maybe_size.value()) : "";
123 dimensions.emplace_back(std::move(array_size));
124
125 const auto underlying_type = array_type.getElementType();
126
127 if (underlying_type->isArrayType())
128 return to_string(*underlying_type->getAsArrayTypeUnsafe(), ctx,
129 try_canonical, dimensions);
130
131 std::string dimensions_str;
132 for (const auto &d : dimensions) {
133 dimensions_str += fmt::format("[{}]", d);
134 }
135 return fmt::format(
136 "{}{}", to_string(underlying_type, ctx, try_canonical), dimensions_str);
137}
138
139std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx,
140 bool try_canonical)
141{
142 if (type->isArrayType()) {
143 std::vector<std::string> dimensions;
144 return to_string(
145 *type->getAsArrayTypeUnsafe(), ctx, try_canonical, dimensions);
146 }
147
148 clang::PrintingPolicy print_policy(ctx.getLangOpts());
149 print_policy.SuppressScope = 0;
150 print_policy.PrintCanonicalTypes = 0;
151
152 std::string result;
153
154 result = type.getAsString(print_policy);
155
156 if (try_canonical && result.find('<') != std::string::npos) {
157 auto canonical_type_name =
158 type.getCanonicalType().getAsString(print_policy);
159
160 auto result_qualified_template_name =
161 result.substr(0, result.find('<'));
162 auto result_template_arguments = result.substr(result.find('<'));
163
164 auto canonical_qualified_template_name =
165 canonical_type_name.substr(0, canonical_type_name.find('<'));
166
167 // Choose the longer name (why do I have to do this?)
168 if (result_qualified_template_name.size() <
169 canonical_qualified_template_name.size()) {
170
171 result =
172 canonical_qualified_template_name + result_template_arguments;
173 }
174 }
175
176 // If for any reason clang reports the type as empty string, make sure
177 // it has some default name
178 if (result.empty())
179 result = "(anonymous)";
180 else if (util::contains(result, "unnamed struct") ||
181 util::contains(result, "unnamed union")) {
182 const auto *declarationTag = type->getAsTagDecl();
183 if (declarationTag == nullptr) {
184 result = "(unnamed undeclared)";
185 }
186 else {
187 result = common::get_tag_name(*declarationTag);
188 }
189 }
190 else if (util::contains(result, "anonymous struct") ||
191 util::contains(result, "anonymous union")) {
192 result = common::get_tag_name(*type->getAsTagDecl());
193 }
194
195 // Remove trailing spaces after commas in template arguments
196 clanguml::util::replace_all(result, ", ", ",");
197 clanguml::util::replace_all(result, "> >", ">>");
198
199 // Try to get rid of 'type-parameter-X-Y' ugliness
200 if (result.find("type-parameter-") != std::string::npos) {
202 common::dereference(type)->getAs<clang::TypedefType>(),
203 [&result, &type](auto *p) {
204 auto [unqualified_type, context] =
206 result = p->getDecl()->getNameAsString();
207 if (!context.empty()) {
208 std::vector<std::string> deduced_contexts;
209
210 for (const auto &c : context) {
211 deduced_contexts.push_back(c.to_string());
212 }
213
214 result = fmt::format(
215 "{} {}", result, fmt::join(deduced_contexts, " "));
216 }
217 });
218 }
219
220 return result;
221}
222
223std::string to_string(const clang::RecordType &type,
224 const clang::ASTContext &ctx, bool try_canonical)
225{
226 return to_string(type.desugar(), ctx, try_canonical);
227}
228
229std::string to_string(
230 const clang::TemplateArgument &arg, const clang::ASTContext *ctx)
231{
232 switch (arg.getKind()) {
233 case clang::TemplateArgument::Expression:
234 return to_string(arg.getAsExpr());
235 case clang::TemplateArgument::Type:
236 return to_string(arg.getAsType(), *ctx, false);
237 case clang::TemplateArgument::Null:
238 return "";
239 case clang::TemplateArgument::NullPtr:
240 return "nullptr";
241 case clang::TemplateArgument::Integral:
242 return std::to_string(arg.getAsIntegral().getExtValue());
243 case clang::TemplateArgument::Template:
244 return to_string(arg.getAsTemplate());
245 case clang::TemplateArgument::TemplateExpansion:
246 return to_string(arg.getAsTemplateOrTemplatePattern());
247 default:
248 return "";
249 }
250}
251
252std::string to_string(const clang::TemplateName &templ)
253{
254 if (templ.getAsTemplateDecl() != nullptr) {
255 return templ.getAsTemplateDecl()->getQualifiedNameAsString();
256 }
257
258 std::string result;
259 const clang::LangOptions lang_options;
260 llvm::raw_string_ostream ostream(result);
261 templ.print(ostream, clang::PrintingPolicy(lang_options));
262
263 return result;
264}
265
266std::string to_string(const clang::Expr *expr)
267{
268 const clang::LangOptions lang_options;
269 std::string result;
270 llvm::raw_string_ostream ostream(result);
271 expr->printPretty(ostream, nullptr, clang::PrintingPolicy(lang_options));
272
273 return result;
274}
275
276std::string to_string(const clang::ValueDecl *val)
277{
278 return val->getQualifiedNameAsString();
279}
280
281std::string to_string(const clang::Stmt *stmt)
282{
283 const clang::LangOptions lang_options;
284 std::string result;
285 llvm::raw_string_ostream ostream(result);
286 stmt->printPretty(ostream, nullptr, clang::PrintingPolicy(lang_options));
287
288 return result;
289}
290
291std::string to_string(const clang::FunctionTemplateDecl *decl)
292{
293 std::vector<std::string> template_parameters;
294 // Handle template function
295 for (const auto *parameter : *decl->getTemplateParameters()) {
296 if (clang::dyn_cast_or_null<clang::TemplateTypeParmDecl>(parameter) !=
297 nullptr) {
298 const auto *template_type_parameter =
299 clang::dyn_cast_or_null<clang::TemplateTypeParmDecl>(parameter);
300
301 std::string template_parameter{
302 template_type_parameter->getNameAsString()};
303
304 if (template_type_parameter->isParameterPack())
305 template_parameter += "...";
306
307 template_parameters.emplace_back(std::move(template_parameter));
308 }
309 else {
310 // TODO
311 }
312 }
313 return fmt::format("{}<{}>({})", decl->getQualifiedNameAsString(),
314 fmt::join(template_parameters, ","), "");
315}
316
317std::string to_string(const clang::TypeConstraint *tc)
318{
319 if (tc == nullptr)
320 return {};
321
322 const clang::PrintingPolicy print_policy(
323 tc->getNamedConcept()->getASTContext().getLangOpts());
324
325 std::string ostream_buf;
326 llvm::raw_string_ostream ostream{ostream_buf};
327 tc->print(ostream, print_policy);
328
329 return ostream.str();
330}
331
333 clang::SourceRange range, const clang::SourceManager &sm)
334{
335 return clang::Lexer::getSourceText(
336 clang::CharSourceRange::getCharRange(range), sm, clang::LangOptions())
337 .str();
338}
339
340std::string get_source_text(
341 clang::SourceRange range, const clang::SourceManager &sm)
342{
343 const clang::LangOptions lo;
344
345 auto start_loc = sm.getSpellingLoc(range.getBegin());
346 auto last_token_loc = sm.getSpellingLoc(range.getEnd());
347 auto end_loc = clang::Lexer::getLocForEndOfToken(last_token_loc, 0, sm, lo);
348 auto printable_range = clang::SourceRange{start_loc, end_loc};
349 return get_source_text_raw(printable_range, sm);
350}
351
352std::tuple<unsigned int, unsigned int, std::string>
353extract_template_parameter_index(const std::string &type_parameter)
354{
355 assert(type_parameter.find("type-parameter-") == 0);
356
357 auto type_parameter_and_suffix = util::split(type_parameter, " ");
358
359 auto toks = util::split(
360 type_parameter_and_suffix.front().substr(strlen("type-parameter-")),
361 "-");
362
363 std::string qualifier;
364
365 if (type_parameter_and_suffix.size() > 1) {
366 qualifier = type_parameter_and_suffix.at(1);
367 }
368
369 return {std::stoi(toks.at(0)), std::stoi(toks.at(1)), std::move(qualifier)};
370}
371
373 const config::diagram &config, std::string &parameter_type)
374{
375#ifdef _MSC_VER
376 auto root_name =
377 fmt::format("{}", std::filesystem::current_path().root_name().string());
378#else
379 auto root_name = std::string{"/"};
380#endif
381
382 std::string lambda_prefix{fmt::format("(lambda at {}", root_name)};
383
384 while (parameter_type.find(lambda_prefix) != std::string::npos) {
385 auto lambda_begin = parameter_type.find(lambda_prefix);
386 auto lambda_prefix_size = lambda_prefix.size();
387#ifdef _MSC_VER
388 // Skip the `\` or `/` after drive letter and semicolon
389 lambda_prefix_size++;
390#endif
391 auto absolute_lambda_path_end =
392 parameter_type.find(':', lambda_begin + lambda_prefix_size);
393 auto absolute_lambda_path = parameter_type.substr(
394 lambda_begin + lambda_prefix_size - 1,
395 absolute_lambda_path_end - (lambda_begin + lambda_prefix_size - 1));
396
397 auto relative_lambda_path = util::path_to_url(
398 config.make_path_relative(absolute_lambda_path).string());
399
400 parameter_type = fmt::format("{}(lambda at {}{}",
401 parameter_type.substr(0, lambda_begin), relative_lambda_path,
402 parameter_type.substr(absolute_lambda_path_end));
403 }
404}
405
406bool is_subexpr_of(const clang::Stmt *parent_stmt, const clang::Stmt *sub_stmt)
407{
408 if (parent_stmt == nullptr || sub_stmt == nullptr)
409 return false;
410
411 if (parent_stmt == sub_stmt)
412 return true;
413
414 return std::any_of(parent_stmt->child_begin(), parent_stmt->child_end(),
415 [sub_stmt](const auto *e) { return is_subexpr_of(e, sub_stmt); });
416}
417
418template <> eid_t to_id(const std::string &full_name)
419{
420 return static_cast<eid_t>(
421 static_cast<uint64_t>(std::hash<std::string>{}(full_name)));
422}
423
424eid_t to_id(const clang::QualType &type, const clang::ASTContext &ctx)
425{
426 return to_id(common::to_string(type, ctx));
427}
428
429template <> eid_t to_id(const clang::NamespaceDecl &declaration)
430{
431 return to_id(get_qualified_name(declaration));
432}
433
434template <> eid_t to_id(const clang::RecordDecl &declaration)
435{
436 return to_id(get_qualified_name(declaration));
437}
438
439template <> eid_t to_id(const clang::EnumDecl &declaration)
440{
441 return to_id(get_qualified_name(declaration));
442}
443
444template <> eid_t to_id(const clang::TagDecl &declaration)
445{
446 return to_id(get_qualified_name(declaration));
447}
448
449template <> eid_t to_id(const clang::CXXRecordDecl &declaration)
450{
451 return to_id(get_qualified_name(declaration));
452}
453
454template <> eid_t to_id(const clang::EnumType &t)
455{
456 return to_id(*t.getDecl());
457}
458
459template <> eid_t to_id(const std::filesystem::path &file)
460{
461 return to_id(file.lexically_normal().string());
462}
463
464template <> eid_t to_id(const clang::TemplateArgument &template_argument)
465{
466 if (template_argument.getKind() == clang::TemplateArgument::Type) {
467 if (const auto *enum_type =
468 template_argument.getAsType()->getAs<clang::EnumType>();
469 enum_type != nullptr)
470 return to_id(*enum_type->getAsTagDecl());
471
472 if (const auto *record_type =
473 template_argument.getAsType()->getAs<clang::RecordType>();
474 record_type != nullptr)
475 return to_id(*record_type->getAsRecordDecl());
476 }
477
478 throw std::runtime_error("Cannot generate id for template argument");
479}
480
481std::pair<common::model::namespace_, std::string> split_ns(
482 const std::string &full_name)
483{
484 assert(!full_name.empty());
485
486 auto name_before_template = ::clanguml::util::split(full_name, "<")[0];
488 ::clanguml::util::split(name_before_template, "::")};
489 auto name = ns.name();
490 ns.pop_back();
491 return {ns, name};
492}
493
494std::vector<common::model::template_parameter> parse_unexposed_template_params(
495 const std::string &params,
496 const std::function<std::string(const std::string &)> &ns_resolve,
497 int depth)
498{
500
501 std::vector<template_parameter> res;
502
503 auto it = params.begin();
504 while (std::isspace(*it) != 0)
505 ++it;
506
507 std::string type{};
508 std::vector<template_parameter> nested_params;
509 bool complete_class_template_argument{false};
510
511 while (it != params.end()) {
512 if (*it == '<') {
513 int nested_level{0};
514 auto bracket_match_begin = it + 1;
515 auto bracket_match_end = bracket_match_begin;
516 while (bracket_match_end != params.end()) {
517 if (*bracket_match_end == '<') {
518 nested_level++;
519 }
520 else if (*bracket_match_end == '>') {
521 if (nested_level > 0)
522 nested_level--;
523 else
524 break;
525 }
526 else {
527 }
528 bracket_match_end++;
529 }
530
531 std::string nested_params_str(
532 bracket_match_begin, bracket_match_end);
533
534 nested_params = parse_unexposed_template_params(
535 nested_params_str, ns_resolve, depth + 1);
536
537 if (nested_params.empty()) {
538 // We couldn't extract any nested template parameters from
539 // `nested_params_str` so just add it as type of template
540 // argument as is
541 nested_params.emplace_back(
542 template_parameter::make_unexposed_argument(
543 nested_params_str));
544 }
545
546 it = bracket_match_end - 1;
547 }
548 else if (*it == '>') {
549 complete_class_template_argument = true;
550 if (depth == 0) {
551 break;
552 }
553 }
554 else if (*it == ',') {
555 complete_class_template_argument = true;
556 }
557 else {
558 type += *it;
559 }
560 if (complete_class_template_argument) {
561 auto t = template_parameter::make_unexposed_argument(
562 ns_resolve(clanguml::util::trim_typename(type)));
563 type = "";
564 for (auto &&param : nested_params)
565 t.add_template_param(std::move(param));
566
567 res.emplace_back(std::move(t));
568 complete_class_template_argument = false;
569 }
570 it++;
571 }
572
573 if (!type.empty()) {
574 auto t = template_parameter::make_unexposed_argument(
575 ns_resolve(clanguml::util::trim_typename(type)));
576 type = "";
577 for (auto &&param : nested_params)
578 t.add_template_param(std::move(param));
579
580 res.emplace_back(std::move(t));
581 }
582
583 return res;
584}
585
586bool is_type_parameter(const std::string &t)
587{
588 return t.find("type-parameter-") == 0;
589}
590
591bool is_qualifier(const std::string &q)
592{
593 return q == "&" || q == "&&" || q == "const&";
594}
595
596bool is_bracket(const std::string &b)
597{
598 return b == "(" || b == ")" || b == "[" || b == "]";
599}
600
602{
603 return std::isalnum(c) != 0 || c == '_';
604}
605
606bool is_identifier(const std::string &t)
607{
608 return std::all_of(t.begin(), t.end(),
609 [](const char c) { return is_identifier_character(c); });
610}
611
612bool is_keyword(const std::string &t)
613{
614 static std::vector<std::string> keywords{"alignas", "alignof", "asm",
615 "auto", "bool", "break", "case", "catch", "char", "char16_t",
616 "char32_t", "class", "concept", "const", "constexpr", "const_cast",
617 "continue", "decltype", "default", "delete", "do", "double",
618 "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false",
619 "float", "for", "friend", "goto", "if", "inline", "int", "long",
620 "mutable", "namespace", "new", "noexcept", "nullptr", "operator",
621 "private", "protected", "public", "register", "reinterpret_cast",
622 "return", "requires", "short", "signed", "sizeof", "static",
623 "static_assert", "static_cast", "struct", "switch", "template", "this",
624 "thread_local", "throw", "true", "try", "typedef", "typeid", "typename",
625 "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t",
626 "while"};
627
628 return util::contains(keywords, t);
629}
630
631bool is_qualified_identifier(const std::string &t)
632{
633 return std::isalpha(t.at(0)) != 0 &&
634 std::all_of(t.begin(), t.end(), [](const char c) {
635 return is_identifier_character(c) || c == ':';
636 });
637}
638
639bool is_type_token(const std::string &t)
640{
641 return is_type_parameter(t) ||
642 (is_identifier(t) && !is_qualifier(t) && !is_bracket(t));
643}
644
645std::string format_condition_text(const std::string &condition_text)
646{
647 std::string result{condition_text};
648
649 if (result.size() < 2)
650 return {};
651
652 std::vector<std::string> text_lines = util::split(result, "\n", true);
653
654 // Trim each line
655 for (auto &line : text_lines) {
656 line = util::trim(line);
657 }
658
659 result = util::join(" ", text_lines);
660
661 if (result.at(0) == '(' && result.back() == ')')
662 return result.substr(1, result.size() - 2);
663
664 return result;
665}
666
667std::string get_condition_text(clang::SourceManager &sm, clang::IfStmt *stmt)
668{
669 auto condition_range =
670 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
671
672 return format_condition_text(get_source_text(condition_range, sm));
673}
674
675std::string get_condition_text(clang::SourceManager &sm, clang::WhileStmt *stmt)
676{
677 auto condition_range =
678 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
679
680 return format_condition_text(get_source_text(condition_range, sm));
681}
682
684 clang::SourceManager &sm, clang::CXXForRangeStmt *stmt)
685{
686 auto condition_range = stmt->getRangeStmt()->getSourceRange();
687
688 return format_condition_text(get_source_text(condition_range, sm));
689}
690
691std::string get_condition_text(clang::SourceManager &sm, clang::ForStmt *stmt)
692{
693 auto condition_range =
694 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
695
696 return format_condition_text(get_source_text(condition_range, sm));
697}
698
699std::string get_condition_text(clang::SourceManager &sm, clang::DoStmt *stmt)
700{
701 auto condition_range = stmt->getCond()->getSourceRange();
702
703 return format_condition_text(get_source_text(condition_range, sm));
704}
705
707 clang::SourceManager &sm, clang::ConditionalOperator *stmt)
708{
709 auto condition_range = stmt->getCond()->getSourceRange();
710
711 return format_condition_text(get_source_text(condition_range, sm));
712}
713
714clang::QualType dereference(clang::QualType type)
715{
716 auto res = type;
717
718 while (true) {
719 if (res->isReferenceType())
720 res = res.getNonReferenceType();
721 else if (res->isPointerType())
722 res = res->getPointeeType();
723 else
724 break;
725 }
726
727 return res;
728}
729
730std::pair<clang::QualType, std::deque<common::model::context>>
731consume_type_context(clang::QualType type)
732{
733 std::deque<common::model::context> res;
734
735 while (true) {
736 bool try_again{false};
738
739 if (type.isConstQualified()) {
740 ctx.is_const = true;
741 try_again = true;
742 }
743
744 if (type.isVolatileQualified()) {
745 ctx.is_volatile = true;
746 try_again = true;
747 }
748
749 if (type->isPointerType() || type->isReferenceType()) {
750 if (type.isConstQualified() || type.isVolatileQualified()) {
751 ctx.is_ref_const = type.isConstQualified();
752 ctx.is_ref_volatile = type.isVolatileQualified();
753
754 try_again = true;
755 }
756 }
757
758 if (type->isLValueReferenceType()) {
760 try_again = true;
761 }
762 else if (type->isRValueReferenceType()) {
764 try_again = true;
765 }
766 else if (type->isMemberFunctionPointerType() &&
767 type->getPointeeType()->getAs<clang::FunctionProtoType>() !=
768 nullptr) {
769 const auto ref_qualifier =
770 type->getPointeeType() // NOLINT
771 ->getAs<clang::FunctionProtoType>() // NOLINT
772 ->getRefQualifier();
773
774 if (ref_qualifier == clang::RefQualifierKind::RQ_RValue) {
776 try_again = true;
777 }
778 else if (ref_qualifier == clang::RefQualifierKind::RQ_LValue) {
780 try_again = true;
781 }
782 }
783 else if (type->isPointerType()) {
785 try_again = true;
786 }
787
788 if (try_again) {
789 if (type->isPointerType()) {
790 if (type->getPointeeType().isConstQualified())
791 ctx.is_const = true;
792 if (type->getPointeeType().isVolatileQualified())
793 ctx.is_volatile = true;
794
795 type = type->getPointeeType().getUnqualifiedType();
796 }
797 else if (type->isReferenceType()) {
798 if (type.getNonReferenceType().isConstQualified())
799 ctx.is_const = true;
800 if (type.getNonReferenceType().isVolatileQualified())
801 ctx.is_volatile = true;
802
803 type = type.getNonReferenceType().getUnqualifiedType();
804 }
805 else if (type.isConstQualified() || type.isVolatileQualified()) {
806 ctx.is_const = type.isConstQualified();
807 ctx.is_volatile = type.isVolatileQualified();
808 type = type.getUnqualifiedType();
809 }
810
811 res.push_front(ctx);
812
813 if (type->isMemberFunctionPointerType())
814 return std::make_pair(type, res);
815 }
816 else
817 return std::make_pair(type, res);
818 }
819}
820
822 const std::string &t)
823{
824 std::vector<std::string> result;
825
826 auto spaced_out = util::split(t, " ");
827
828 for (const auto &word : spaced_out) {
829 if (is_qualified_identifier(word)) {
830 if (word != "class" && word != "templated" && word != "struct")
831 result.emplace_back(word);
832 continue;
833 }
834
835 std::string tok;
836
837 for (const char c : word) {
838 if (c == '(' || c == ')' || c == '[' || c == ']' || c == '<' ||
839 c == '>') {
840 if (!tok.empty())
841 result.emplace_back(tok);
842 result.emplace_back(std::string{c});
843 tok.clear();
844 }
845 else if (c == ':') {
846 if (!tok.empty() && tok != ":") {
847 result.emplace_back(tok);
848 tok = ":";
849 }
850 else if (tok == ":") {
851 result.emplace_back("::");
852 tok = "";
853 }
854 else {
855 tok += ':';
856 }
857 }
858 else if (c == ',') {
859 if (!tok.empty()) {
860 result.emplace_back(tok);
861 }
862 result.emplace_back(",");
863 tok.clear();
864 }
865 else if (c == '*') {
866 if (!tok.empty()) {
867 result.emplace_back(tok);
868 }
869 result.emplace_back("*");
870 tok.clear();
871 }
872 else if (c == '.') {
873 // This can only be the case if we have a variadic template,
874 // right?
875 if (tok == "..") {
876 result.emplace_back("...");
877 tok.clear();
878 }
879 else if (tok == ".") {
880 tok = "..";
881 }
882 else if (!tok.empty()) {
883 result.emplace_back(tok);
884 tok = ".";
885 }
886 }
887 else {
888 tok += c;
889 }
890 }
891
892 tok = util::trim(tok);
893
894 if (!tok.empty()) {
895 if (tok != "class" && tok != "typename" && word != "struct")
896 result.emplace_back(tok);
897 tok.clear();
898 }
899 }
900
901 return result;
902}
903
904bool parse_source_location(const std::string &location_str, std::string &file,
905 unsigned &line, unsigned &column)
906{
907 auto tokens = util::split(location_str, ":");
908
909 if (tokens.size() < 3)
910 return false;
911
912 if (tokens.size() == 4) {
913 // Handle Windows paths
914 decltype(tokens) tmp_tokens{};
915 tmp_tokens.emplace_back(
916 fmt::format("{}:{}", tokens.at(0), tokens.at(1)));
917 tmp_tokens.emplace_back(tokens.at(2));
918 tmp_tokens.emplace_back(tokens.at(3));
919
920 tokens = std::move(tmp_tokens);
921 }
922
923 file = tokens.at(0);
924 try {
925 line = std::stoi(tokens.at(1));
926 }
927 catch (std::invalid_argument &e) {
928 return false;
929 }
930
931 try {
932 column = std::stoi(tokens.at(2));
933 }
934 catch (std::invalid_argument &e) {
935 column = 0;
936 }
937
938 return true;
939}
940
941clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm,
942 const clang::ASTContext &context, const clang::Stmt *stmt)
943{
944 return get_raw_comment(sm, context, stmt->getSourceRange());
945}
946
947clang::RawComment *get_declaration_raw_comment(const clang::SourceManager &sm,
948 const clang::ASTContext &context, const clang::Decl *decl)
949{
950 return get_raw_comment(sm, context, decl->getSourceRange());
951}
952
953clang::RawComment *get_raw_comment(const clang::SourceManager &sm,
954 const clang::ASTContext &context, const clang::SourceRange &source_range)
955{
956 auto expr_begin = source_range.getBegin();
957 const auto expr_begin_line = sm.getSpellingLineNumber(expr_begin);
958
959 std::string file_Path = sm.getFilename(expr_begin).str();
960
961 auto file_id = sm.getFileID(expr_begin);
962
963 if (!context.Comments.empty() &&
964 context.Comments.getCommentsInFile(file_id) != nullptr) {
965 for (const auto [offset, raw_comment] :
966 *context.Comments.getCommentsInFile(sm.getFileID(expr_begin))) {
967 const auto comment_end_line = sm.getSpellingLineNumber(
968 raw_comment->getSourceRange().getEnd());
969
970 if (expr_begin_line == comment_end_line ||
971 expr_begin_line == comment_end_line + 1)
972 return raw_comment;
973 }
974 }
975
976 return {};
977}
978
979bool is_coroutine(const clang::FunctionDecl &decl)
980{
981 const auto *body = decl.getBody();
982 return clang::isa_and_nonnull<clang::CoroutineBodyStmt>(body);
983}
984
985bool is_struct(const clang::NamedDecl *decl)
986{
987 if (decl == nullptr)
988 return false;
989
990 if (const auto *record = clang::dyn_cast<clang::CXXRecordDecl>(decl);
991 record) {
992 return record->isStruct();
993 }
994
995 if (const auto *tag = clang::dyn_cast<clang::TagDecl>(decl); tag) {
996 return tag->isStruct();
997 }
998
999 return false;
1000}
1001
1002bool has_attr(const clang::FunctionDecl *decl, clang::attr::Kind function_attr)
1003{
1004 return std::any_of(decl->attrs().begin(), decl->attrs().end(),
1005 [function_attr](
1006 auto &&attr) { return attr->getKind() == function_attr; });
1007}
1008
1009std::optional<size_t> get_array_size(const clang::ArrayType &type)
1010{
1011 if (const auto *constant_array =
1012 clang::dyn_cast<clang::ConstantArrayType>(&type);
1013 constant_array != nullptr) {
1014 return {constant_array->getSize().getZExtValue()};
1015 }
1016
1017 return {};
1018}
1019} // namespace clanguml::common