0.6.3
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-2026 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
47 clang::ObjCIvarDecl::AccessControl access_specifier)
48{
49 auto access = model::access_t::kPublic;
50 switch (access_specifier) {
51 case clang::ObjCIvarDecl::AccessControl::Public:
53 break;
54 case clang::ObjCIvarDecl::AccessControl::Private:
56 break;
57 case clang::ObjCIvarDecl::AccessControl::Protected:
59 break;
60 default:
61 break;
62 }
63
64 return access;
65}
66
67model::namespace_ get_tag_namespace(const clang::TagDecl &declaration)
68{
70
71 const auto *parent{declaration.getParent()};
72
73 // First walk up to the nearest namespace, e.g. from nested class or enum
74 while ((parent != nullptr) && !parent->isNamespace()) {
75 parent = parent->getParent();
76 }
77
78 // Now build up the namespace
79 std::deque<std::string> namespace_tokens;
80 while ((parent != nullptr) && parent->isNamespace()) {
81 if (const auto *ns_decl = clang::dyn_cast<clang::NamespaceDecl>(parent);
82 ns_decl != nullptr) {
83 if (!ns_decl->isInline() && !ns_decl->isAnonymousNamespace())
84 namespace_tokens.push_front(ns_decl->getNameAsString());
85 }
86
87 parent = parent->getParent();
88 }
89
90 for (const auto &ns_token : namespace_tokens) {
91 ns |= ns_token;
92 }
93
94 return ns;
95}
96
97model::namespace_ get_template_namespace(const clang::TemplateDecl &declaration)
98{
99 model::namespace_ ns{declaration.getQualifiedNameAsString()};
100 ns.pop_back();
101
102 return ns;
103}
104
105std::string get_tag_name(const clang::TagDecl &declaration)
106{
107 auto base_name = declaration.getNameAsString();
108
109 if (base_name.empty()) {
110 base_name =
111 fmt::format("(anonymous_{})", std::to_string(declaration.getID()));
112 }
113
114 if ((declaration.getParent() != nullptr) &&
115 declaration.getParent()->isRecord()) {
116 // If the record is nested within another record (e.g. class or struct)
117 // we have to maintain a containment namespace in order to ensure
118 // unique names within the diagram
119 std::deque<std::string> record_parent_names;
120 record_parent_names.push_front(base_name);
121
122 const auto *cls_parent{declaration.getParent()};
123 while (cls_parent->isRecord()) {
124 if (const auto *record_decl =
125 clang::dyn_cast<clang::RecordDecl>(cls_parent);
126 record_decl != nullptr) {
127 record_parent_names.push_front(record_decl->getNameAsString());
128 }
129 cls_parent = cls_parent->getParent();
130 }
131 return fmt::format("{}", fmt::join(record_parent_names, "##"));
132 }
133
134 return base_name;
135}
136
137std::string to_string(const clang::ArrayType &array_type,
138 const clang::ASTContext &ctx, bool try_canonical,
139 std::vector<std::string> &dimensions)
140{
141 auto maybe_size = get_array_size(array_type);
142 std::string array_size =
143 maybe_size.has_value() ? std::to_string(maybe_size.value()) : "";
144 dimensions.emplace_back(std::move(array_size));
145
146 const auto underlying_type = array_type.getElementType();
147
148 if (underlying_type->isArrayType())
149 return to_string(*underlying_type->getAsArrayTypeUnsafe(), ctx,
150 try_canonical, dimensions);
151
152 std::string dimensions_str;
153 for (const auto &d : dimensions) {
154 dimensions_str += fmt::format("[{}]", d);
155 }
156 return fmt::format(
157 "{}{}", to_string(underlying_type, ctx, try_canonical), dimensions_str);
158}
159
160std::string to_string(
161 const clang::TemplateArgumentLoc &argLoc, const clang::ASTContext &context)
162{
163 std::string result;
164 llvm::raw_string_ostream stream(result);
165
166 clang::PrintingPolicy policy(context.getLangOpts());
167
168 const clang::TemplateArgument &arg = argLoc.getArgument();
169
170#if LLVM_VERSION_MAJOR > 18
171 arg.print(policy, stream, false);
172#else
173 arg.dump(stream);
174#endif
175
176 stream.flush();
177
178 return result;
179}
180
181std::string to_string(const clang::QualType &type, const clang::ASTContext &ctx,
182 bool try_canonical)
183{
184 if (type->isArrayType()) {
185 std::vector<std::string> dimensions;
186 return to_string(
187 *type->getAsArrayTypeUnsafe(), ctx, try_canonical, dimensions);
188 }
189
190 clang::PrintingPolicy print_policy(ctx.getLangOpts());
191 print_policy.SuppressScope = 0;
192#if LLVM_VERSION_MAJOR < 21
193 print_policy.PrintCanonicalTypes = 0;
194#else
195 print_policy.PrintAsCanonical = 0;
196#endif
197
198 std::string result;
199
200 result = type.getAsString(print_policy);
201
202 if (try_canonical && result.find('<') != std::string::npos) {
203 auto canonical_type_name =
204 type.getCanonicalType().getAsString(print_policy);
205
206 auto result_qualified_template_name =
207 result.substr(0, result.find('<'));
208 auto result_template_arguments = result.substr(result.find('<'));
209
210 auto canonical_qualified_template_name =
211 canonical_type_name.substr(0, canonical_type_name.find('<'));
212
213 // Choose the longer name (why do I have to do this?)
214 if (result_qualified_template_name.size() <
215 canonical_qualified_template_name.size()) {
216
217 result =
218 canonical_qualified_template_name + result_template_arguments;
219 }
220 }
221
222 // If for any reason clang reports the type as empty string, make sure
223 // it has some default name
224 if (result.empty())
225 result = "(anonymous)";
226 else if (util::contains(result, "unnamed struct") ||
227 util::contains(result, "unnamed union") ||
228 util::contains(result, "struct (unnamed") ||
229 util::contains(result, "union (unnamed")) {
230 const auto *declarationTag = type->getAsTagDecl();
231 if (declarationTag == nullptr) {
232 result = "(unnamed undeclared)";
233 }
234 else {
235 result = common::get_tag_name(*declarationTag);
236 }
237 }
238 else if (util::contains(result, "anonymous struct") ||
239 util::contains(result, "anonymous union")) {
240 result = common::get_tag_name(*type->getAsTagDecl());
241 }
242
243 // Remove trailing spaces after commas in template arguments
244 clanguml::util::replace_all(result, ", ", ",");
245 clanguml::util::replace_all(result, "> >", ">>");
246
247 // Try to get rid of 'type-parameter-X-Y' ugliness
248 if (result.find("type-parameter-") != std::string::npos) {
250 common::dereference(type)->getAs<clang::TypedefType>(),
251 [&result, &type](auto *p) {
252 auto [unqualified_type, context] =
254 result = p->getDecl()->getNameAsString();
255 if (!context.empty()) {
256 std::vector<std::string> deduced_contexts;
257
258 for (const auto &c : context) {
259 deduced_contexts.push_back(c.to_string());
260 }
261
262 result = fmt::format(
263 "{} {}", result, fmt::join(deduced_contexts, " "));
264 }
265 });
266 }
267
268 return result;
269}
270
271std::string to_string(const clang::RecordType &type,
272 const clang::ASTContext &ctx, bool try_canonical)
273{
274 return to_string(type.desugar(), ctx, try_canonical);
275}
276
277std::string to_string(
278 const clang::TemplateArgument &arg, const clang::ASTContext *ctx)
279{
280 switch (arg.getKind()) {
281 case clang::TemplateArgument::Expression:
282 return to_string(arg.getAsExpr());
283 case clang::TemplateArgument::Type:
284 return to_string(arg.getAsType(), *ctx, false);
285 case clang::TemplateArgument::Null:
286 return "";
287 case clang::TemplateArgument::NullPtr:
288 return "nullptr";
289 case clang::TemplateArgument::Integral:
290 return std::to_string(arg.getAsIntegral().getExtValue());
291 case clang::TemplateArgument::Template:
292 return to_string(arg.getAsTemplate());
293 case clang::TemplateArgument::TemplateExpansion:
294 return to_string(arg.getAsTemplateOrTemplatePattern());
295 default:
296 return "";
297 }
298}
299
300std::string to_string(const clang::TemplateName &templ)
301{
302 if (templ.getAsTemplateDecl() != nullptr) {
303 return templ.getAsTemplateDecl()->getQualifiedNameAsString();
304 }
305
306 std::string result;
307 const clang::LangOptions lang_options;
308 llvm::raw_string_ostream ostream(result);
309 templ.print(ostream, clang::PrintingPolicy(lang_options));
310
311 return result;
312}
313
314std::string to_string(const clang::Expr *expr)
315{
316 const clang::LangOptions lang_options;
317 std::string result;
318 llvm::raw_string_ostream ostream(result);
319 expr->printPretty(ostream, nullptr, clang::PrintingPolicy(lang_options));
320
321 return result;
322}
323
324std::string to_string(const clang::ValueDecl *val)
325{
326 return val->getQualifiedNameAsString();
327}
328
329std::string to_string(const clang::Stmt *stmt)
330{
331 const clang::LangOptions lang_options;
332 std::string result;
333 llvm::raw_string_ostream ostream(result);
334 stmt->printPretty(ostream, nullptr, clang::PrintingPolicy(lang_options));
335
336 return result;
337}
338
339std::string to_string(const clang::FunctionTemplateDecl *decl)
340{
341 std::vector<std::string> template_parameters;
342 // Handle template function
343 for (const auto *parameter : *decl->getTemplateParameters()) {
344 if (clang::dyn_cast_or_null<clang::TemplateTypeParmDecl>(parameter) !=
345 nullptr) {
346 const auto *template_type_parameter =
347 clang::dyn_cast_or_null<clang::TemplateTypeParmDecl>(parameter);
348
349 std::string template_parameter{
350 template_type_parameter->getNameAsString()};
351
352 if (template_type_parameter->isParameterPack())
353 template_parameter += "...";
354
355 template_parameters.emplace_back(std::move(template_parameter));
356 }
357 else {
358 // TODO
359 }
360 }
361 return fmt::format("{}<{}>({})", decl->getQualifiedNameAsString(),
362 fmt::join(template_parameters, ","), "");
363}
364
365std::string to_string(const clang::TypeConstraint *tc)
366{
367 if (tc == nullptr)
368 return {};
369
370 const clang::PrintingPolicy print_policy(
371 tc->getNamedConcept()->getASTContext().getLangOpts());
372
373 std::string ostream_buf;
374 llvm::raw_string_ostream ostream{ostream_buf};
375 tc->print(ostream, print_policy);
376
377 return ostream.str();
378}
379
381 clang::SourceRange range, const clang::SourceManager &sm)
382{
383 return clang::Lexer::getSourceText(
384 clang::CharSourceRange::getCharRange(range), sm, clang::LangOptions())
385 .str();
386}
387
388std::string get_source_text(
389 clang::SourceRange range, const clang::SourceManager &sm)
390{
391 const clang::LangOptions lo;
392
393 auto start_loc = sm.getSpellingLoc(range.getBegin());
394 auto last_token_loc = sm.getSpellingLoc(range.getEnd());
395 auto end_loc = clang::Lexer::getLocForEndOfToken(last_token_loc, 0, sm, lo);
396 auto printable_range = clang::SourceRange{start_loc, end_loc};
397 return get_source_text_raw(printable_range, sm);
398}
399
400std::tuple<unsigned int, unsigned int, std::string>
401extract_template_parameter_index(const std::string &type_parameter)
402{
403 assert(type_parameter.find("type-parameter-") == 0);
404
405 auto type_parameter_and_suffix = util::split(type_parameter, " ");
406
407 auto toks = util::split(
408 type_parameter_and_suffix.front().substr(strlen("type-parameter-")),
409 "-");
410
411 std::string qualifier;
412
413 if (type_parameter_and_suffix.size() > 1) {
414 qualifier = type_parameter_and_suffix.at(1);
415 }
416
417 return {std::stoi(toks.at(0)), std::stoi(toks.at(1)), std::move(qualifier)};
418}
419
421 const config::diagram &config, std::string &parameter_type)
422{
423#ifdef _MSC_VER
424 auto root_name =
425 fmt::format("{}", std::filesystem::current_path().root_name().string());
426#else
427 auto root_name = std::string{"/"};
428#endif
429
430 std::string lambda_prefix{fmt::format("(lambda at {}", root_name)};
431
432 while (parameter_type.find(lambda_prefix) != std::string::npos) {
433 auto lambda_begin = parameter_type.find(lambda_prefix);
434 auto lambda_prefix_size = lambda_prefix.size();
435#ifdef _MSC_VER
436 // Skip the `\` or `/` after drive letter and semicolon
437 lambda_prefix_size++;
438#endif
439 auto absolute_lambda_path_end =
440 parameter_type.find(':', lambda_begin + lambda_prefix_size);
441 auto absolute_lambda_path = parameter_type.substr(
442 lambda_begin + lambda_prefix_size - 1,
443 absolute_lambda_path_end - (lambda_begin + lambda_prefix_size - 1));
444
445 auto relative_lambda_path = util::path_to_url(
446 config.make_path_relative(absolute_lambda_path).string());
447
448 parameter_type = fmt::format("{}(lambda at {}{}",
449 parameter_type.substr(0, lambda_begin), relative_lambda_path,
450 parameter_type.substr(absolute_lambda_path_end));
451 }
452}
453
454bool is_subexpr_of(const clang::Stmt *parent_stmt, const clang::Stmt *sub_stmt)
455{
456 if (parent_stmt == nullptr || sub_stmt == nullptr)
457 return false;
458
459 if (parent_stmt == sub_stmt)
460 return true;
461
462 return std::any_of(parent_stmt->child_begin(), parent_stmt->child_end(),
463 [sub_stmt](const auto *e) { return is_subexpr_of(e, sub_stmt); });
464}
465
466template <> eid_t to_id(const std::string &full_name)
467{
468 return static_cast<eid_t>(
469 static_cast<uint64_t>(std::hash<std::string>{}(full_name)));
470}
471
472eid_t to_id(const clang::QualType &type, const clang::ASTContext &ctx)
473{
474 return to_id(common::to_string(type, ctx));
475}
476
477template <> eid_t to_id(const clang::NamespaceDecl &declaration)
478{
479 return to_id(get_qualified_name(declaration));
480}
481
482template <> eid_t to_id(const clang::RecordDecl &declaration)
483{
484 return to_id(get_qualified_name(declaration));
485}
486
487template <> eid_t to_id(const clang::ObjCCategoryDecl &type)
488{
489 return to_id(fmt::format("__objc__category__{}", type.getNameAsString()));
490}
491
492template <> eid_t to_id(const clang::ObjCInterfaceDecl &type)
493{
494 return to_id(fmt::format("__objc__interface__{}", type.getNameAsString()));
495}
496
497template <> eid_t to_id(const clang::ObjCProtocolDecl &type)
498{
499 return to_id(fmt::format("__objc__protocol__{}", type.getNameAsString()));
500}
501
502template <> eid_t to_id(const clang::EnumDecl &declaration)
503{
504 return to_id(get_qualified_name(declaration));
505}
506
507template <> eid_t to_id(const clang::TagDecl &declaration)
508{
509 return to_id(get_qualified_name(declaration));
510}
511
512template <> eid_t to_id(const clang::CXXRecordDecl &declaration)
513{
514 return to_id(get_qualified_name(declaration));
515}
516
517template <> eid_t to_id(const clang::EnumType &t)
518{
519 return to_id(*t.getDecl());
520}
521
522template <> eid_t to_id(const std::filesystem::path &file)
523{
524 return to_id(file.lexically_normal().string());
525}
526
527template <> eid_t to_id(const clang::TemplateArgument &template_argument)
528{
529 if (template_argument.getKind() == clang::TemplateArgument::Type) {
530 if (const auto *enum_type =
531 template_argument.getAsType()->getAs<clang::EnumType>();
532 enum_type != nullptr)
533 return to_id(*enum_type->getAsTagDecl());
534
535 if (const auto *record_type =
536 template_argument.getAsType()->getAs<clang::RecordType>();
537 record_type != nullptr)
538 return to_id(*record_type->getAsRecordDecl());
539 }
540
541 throw std::runtime_error("Cannot generate id for template argument");
542}
543
544std::pair<common::model::namespace_, std::string> split_ns(
545 const std::string &full_name)
546{
547 assert(!full_name.empty());
548
549 auto name_before_template = ::clanguml::util::split(full_name, "<")[0];
551 ::clanguml::util::split(name_before_template, "::")};
552 auto name = ns.name();
553 ns.pop_back();
554 return {ns, name};
555}
556
557std::vector<common::model::template_parameter> parse_unexposed_template_params(
558 const std::string &params,
559 const std::function<std::string(const std::string &)> &ns_resolve,
560 int depth)
561{
563
564 std::vector<template_parameter> res;
565
566 auto it = params.begin();
567 while (std::isspace(*it) != 0)
568 ++it;
569
570 std::string type{};
571 std::vector<template_parameter> nested_params;
572 bool complete_class_template_argument{false};
573
574 while (it != params.end()) {
575 if (*it == '<') {
576 int nested_level{0};
577 auto bracket_match_begin = it + 1;
578 auto bracket_match_end = bracket_match_begin;
579 while (bracket_match_end != params.end()) {
580 if (*bracket_match_end == '<') {
581 nested_level++;
582 }
583 else if (*bracket_match_end == '>') {
584 if (nested_level > 0)
585 nested_level--;
586 else
587 break;
588 }
589 else {
590 }
591 bracket_match_end++;
592 }
593
594 std::string nested_params_str(
595 bracket_match_begin, bracket_match_end);
596
597 nested_params = parse_unexposed_template_params(
598 nested_params_str, ns_resolve, depth + 1);
599
600 if (nested_params.empty()) {
601 // We couldn't extract any nested template parameters from
602 // `nested_params_str` so just add it as type of template
603 // argument as is
604 nested_params.emplace_back(
605 template_parameter::make_unexposed_argument(
606 nested_params_str));
607 }
608
609 it = bracket_match_end - 1;
610 }
611 else if (*it == '>') {
612 complete_class_template_argument = true;
613 if (depth == 0) {
614 break;
615 }
616 }
617 else if (*it == ',') {
618 complete_class_template_argument = true;
619 }
620 else {
621 type += *it;
622 }
623 if (complete_class_template_argument) {
624 auto t = template_parameter::make_unexposed_argument(
625 ns_resolve(clanguml::util::trim_typename(type)));
626 type = "";
627 for (auto &&param : nested_params)
628 t.add_template_param(std::move(param));
629
630 res.emplace_back(std::move(t));
631 complete_class_template_argument = false;
632 }
633 it++;
634 }
635
636 if (!type.empty()) {
637 auto t = template_parameter::make_unexposed_argument(
638 ns_resolve(clanguml::util::trim_typename(type)));
639 type = "";
640 for (auto &&param : nested_params)
641 t.add_template_param(std::move(param));
642
643 res.emplace_back(std::move(t));
644 }
645
646 return res;
647}
648
649bool is_type_parameter(const std::string &t)
650{
651 return t.find("type-parameter-") == 0;
652}
653
654bool is_qualifier(const std::string &q)
655{
656 return q == "&" || q == "&&" || q == "const&";
657}
658
659bool is_bracket(const std::string &b)
660{
661 return b == "(" || b == ")" || b == "[" || b == "]";
662}
663
665{
666 return std::isalnum(c) != 0 || c == '_';
667}
668
669bool is_identifier(const std::string &t)
670{
671 return std::all_of(t.begin(), t.end(),
672 [](const char c) { return is_identifier_character(c); });
673}
674
675bool is_keyword(const std::string &t)
676{
677 static std::vector<std::string> keywords{"alignas", "alignof", "asm",
678 "auto", "bool", "break", "case", "catch", "char", "char16_t",
679 "char32_t", "class", "concept", "const", "constexpr", "const_cast",
680 "continue", "decltype", "default", "delete", "do", "double",
681 "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false",
682 "float", "for", "friend", "goto", "if", "inline", "int", "long",
683 "mutable", "namespace", "new", "noexcept", "nullptr", "operator",
684 "private", "protected", "public", "register", "reinterpret_cast",
685 "return", "requires", "short", "signed", "sizeof", "static",
686 "static_assert", "static_cast", "struct", "switch", "template", "this",
687 "thread_local", "throw", "true", "try", "typedef", "typeid", "typename",
688 "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t",
689 "while"};
690
691 return util::contains(keywords, t);
692}
693
694bool is_qualified_identifier(const std::string &t)
695{
696 return std::isalpha(t.at(0)) != 0 &&
697 std::all_of(t.begin(), t.end(), [](const char c) {
698 return is_identifier_character(c) || c == ':';
699 });
700}
701
702bool is_type_token(const std::string &t)
703{
704 return is_type_parameter(t) ||
705 (is_identifier(t) && !is_qualifier(t) && !is_bracket(t));
706}
707
708std::string format_condition_text(const std::string &condition_text)
709{
710 std::string result{condition_text};
711
712 if (result.size() < 2)
713 return {};
714
715 std::vector<std::string> text_lines = util::split(result, "\n", true);
716
717 // Trim each line
718 for (auto &line : text_lines) {
719 line = util::trim(line);
720 }
721
722 result = util::join(" ", text_lines);
723
724 if (result.at(0) == '(' && result.back() == ')')
725 return result.substr(1, result.size() - 2);
726
727 return result;
728}
729
730std::string get_condition_text(clang::SourceManager &sm, clang::IfStmt *stmt)
731{
732 auto condition_range =
733 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
734
735 return format_condition_text(get_source_text(condition_range, sm));
736}
737
738std::string get_condition_text(clang::SourceManager &sm, clang::WhileStmt *stmt)
739{
740 auto condition_range =
741 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
742
743 return format_condition_text(get_source_text(condition_range, sm));
744}
745
747 clang::SourceManager &sm, clang::CXXForRangeStmt *stmt)
748{
749 auto condition_range = stmt->getRangeStmt()->getSourceRange();
750
751 return format_condition_text(get_source_text(condition_range, sm));
752}
753
754std::string get_condition_text(clang::SourceManager &sm, clang::ForStmt *stmt)
755{
756 auto condition_range =
757 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
758
759 return format_condition_text(get_source_text(condition_range, sm));
760}
761
762std::string get_condition_text(clang::SourceManager &sm, clang::DoStmt *stmt)
763{
764 auto condition_range = stmt->getCond()->getSourceRange();
765
766 return format_condition_text(get_source_text(condition_range, sm));
767}
768
770 clang::SourceManager &sm, clang::ConditionalOperator *stmt)
771{
772 auto condition_range = stmt->getCond()->getSourceRange();
773
774 return format_condition_text(get_source_text(condition_range, sm));
775}
776
777clang::QualType dereference(clang::QualType type)
778{
779 auto res = type;
780
781 while (true) {
782 if (res->isReferenceType())
783 res = res.getNonReferenceType();
784 else if (res->isPointerType())
785 res = res->getPointeeType();
786 else
787 break;
788 }
789
790 return res;
791}
792
793std::pair<clang::QualType, std::deque<common::model::context>>
794consume_type_context(clang::QualType type)
795{
796 std::deque<common::model::context> res;
797
798 while (true) {
799 bool try_again{false};
801
802 if (type.isConstQualified()) {
803 ctx.is_const = true;
804 try_again = true;
805 }
806
807 if (type.isVolatileQualified()) {
808 ctx.is_volatile = true;
809 try_again = true;
810 }
811
812 if (type->isPointerType() || type->isReferenceType()) {
813 if (type.isConstQualified() || type.isVolatileQualified()) {
814 ctx.is_ref_const = type.isConstQualified();
815 ctx.is_ref_volatile = type.isVolatileQualified();
816
817 try_again = true;
818 }
819 }
820
821 if (type->isLValueReferenceType()) {
823 try_again = true;
824 }
825 else if (type->isRValueReferenceType()) {
827 try_again = true;
828 }
829 else if (type->isMemberFunctionPointerType() &&
830 type->getPointeeType()->getAs<clang::FunctionProtoType>() !=
831 nullptr) {
832 const auto ref_qualifier =
833 type->getPointeeType() // NOLINT
834 ->getAs<clang::FunctionProtoType>() // NOLINT
835 ->getRefQualifier();
836
837 if (ref_qualifier == clang::RefQualifierKind::RQ_RValue) {
839 try_again = true;
840 }
841 else if (ref_qualifier == clang::RefQualifierKind::RQ_LValue) {
843 try_again = true;
844 }
845 }
846 else if (type->isPointerType()) {
848 try_again = true;
849 }
850
851 if (try_again) {
852 if (type->isPointerType()) {
853 if (type->getPointeeType().isConstQualified())
854 ctx.is_const = true;
855 if (type->getPointeeType().isVolatileQualified())
856 ctx.is_volatile = true;
857
858 type = type->getPointeeType().getUnqualifiedType();
859 }
860 else if (type->isReferenceType()) {
861 if (type.getNonReferenceType().isConstQualified())
862 ctx.is_const = true;
863 if (type.getNonReferenceType().isVolatileQualified())
864 ctx.is_volatile = true;
865
866 type = type.getNonReferenceType().getUnqualifiedType();
867 }
868 else if (type.isConstQualified() || type.isVolatileQualified()) {
869 ctx.is_const = type.isConstQualified();
870 ctx.is_volatile = type.isVolatileQualified();
871 type = type.getUnqualifiedType();
872 }
873
874 res.push_front(ctx);
875
876 if (type->isMemberFunctionPointerType())
877 return std::make_pair(type, res);
878 }
879 else
880 return std::make_pair(type, res);
881 }
882}
883
885 const std::string &t)
886{
887 std::vector<std::string> result;
888
889 auto spaced_out = util::split(t, " ");
890
891 for (const auto &word : spaced_out) {
892 if (is_qualified_identifier(word)) {
893 if (word != "class" && word != "templated" && word != "struct")
894 result.emplace_back(word);
895 continue;
896 }
897
898 std::string tok;
899
900 for (const char c : word) {
901 if (c == '(' || c == ')' || c == '[' || c == ']' || c == '<' ||
902 c == '>') {
903 if (!tok.empty())
904 result.emplace_back(tok);
905 result.emplace_back(std::string{c});
906 tok.clear();
907 }
908 else if (c == ':') {
909 if (!tok.empty() && tok != ":") {
910 result.emplace_back(tok);
911 tok = ":";
912 }
913 else if (tok == ":") {
914 result.emplace_back("::");
915 tok = "";
916 }
917 else {
918 tok += ':';
919 }
920 }
921 else if (c == ',') {
922 if (!tok.empty()) {
923 result.emplace_back(tok);
924 }
925 result.emplace_back(",");
926 tok.clear();
927 }
928 else if (c == '*') {
929 if (!tok.empty()) {
930 result.emplace_back(tok);
931 }
932 result.emplace_back("*");
933 tok.clear();
934 }
935 else if (c == '.') {
936 // This can only be the case if we have a variadic template,
937 // right?
938 if (tok == "..") {
939 result.emplace_back("...");
940 tok.clear();
941 }
942 else if (tok == ".") {
943 tok = "..";
944 }
945 else if (!tok.empty()) {
946 result.emplace_back(tok);
947 tok = ".";
948 }
949 }
950 else {
951 tok += c;
952 }
953 }
954
955 tok = util::trim(tok);
956
957 if (!tok.empty()) {
958 if (tok != "class" && tok != "typename" && word != "struct")
959 result.emplace_back(tok);
960 tok.clear();
961 }
962 }
963
964 return result;
965}
966
967bool parse_source_location(const std::string &location_str, std::string &file,
968 unsigned &line, unsigned &column)
969{
970 auto tokens = util::split(location_str, ":");
971
972 if (tokens.size() < 3)
973 return false;
974
975 if (tokens.size() == 4) {
976 // Handle Windows paths
977 decltype(tokens) tmp_tokens{};
978 tmp_tokens.emplace_back(
979 fmt::format("{}:{}", tokens.at(0), tokens.at(1)));
980 tmp_tokens.emplace_back(tokens.at(2));
981 tmp_tokens.emplace_back(tokens.at(3));
982
983 tokens = std::move(tmp_tokens);
984 }
985
986 file = tokens.at(0);
987 try {
988 line = std::stoi(tokens.at(1));
989 }
990 catch (std::invalid_argument &e) {
991 return false;
992 }
993
994 try {
995 column = std::stoi(tokens.at(2));
996 }
997 catch (std::invalid_argument &e) {
998 column = 0;
999 }
1000
1001 return true;
1002}
1003
1004clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm,
1005 const clang::ASTContext &context, const clang::Stmt *stmt)
1006{
1007 return get_raw_comment(sm, context, stmt->getSourceRange());
1008}
1009
1010clang::RawComment *get_declaration_raw_comment(const clang::SourceManager &sm,
1011 const clang::ASTContext &context, const clang::Decl *decl)
1012{
1013 return get_raw_comment(sm, context, decl->getSourceRange());
1014}
1015
1016clang::RawComment *get_raw_comment(const clang::SourceManager &sm,
1017 const clang::ASTContext &context, const clang::SourceRange &source_range)
1018{
1019 auto expr_begin = sm.getExpansionLoc(source_range.getBegin());
1020 const auto expr_begin_line = sm.getExpansionLineNumber(expr_begin);
1021
1022 auto file_id = sm.getFileID(expr_begin);
1023
1024 const auto has_comments = !context.Comments.empty();
1025 const auto *comments = context.Comments.getCommentsInFile(file_id);
1026
1027 if (has_comments && comments != nullptr) {
1028 for (const auto [offset, raw_comment] :
1029 *context.Comments.getCommentsInFile(sm.getFileID(expr_begin))) {
1030 const auto comment_end_line = sm.getExpansionLineNumber(
1031 raw_comment->getSourceRange().getEnd());
1032
1033 if (expr_begin_line == comment_end_line ||
1034 expr_begin_line == comment_end_line + 1)
1035 return raw_comment;
1036 }
1037 }
1038
1039 return {};
1040}
1041
1042bool is_coroutine(const clang::FunctionDecl &decl)
1043{
1044 const auto *body = decl.getBody();
1045 return clang::isa_and_nonnull<clang::CoroutineBodyStmt>(body);
1046}
1047
1048bool is_struct(const clang::NamedDecl *decl)
1049{
1050 if (decl == nullptr)
1051 return false;
1052
1053 if (const auto *record = clang::dyn_cast<clang::CXXRecordDecl>(decl);
1054 record) {
1055 return record->isStruct();
1056 }
1057
1058 if (const auto *tag = clang::dyn_cast<clang::TagDecl>(decl); tag) {
1059 return tag->isStruct();
1060 }
1061
1062 return false;
1063}
1064
1065bool has_attr(const clang::FunctionDecl *decl, clang::attr::Kind function_attr)
1066{
1067 return std::any_of(decl->attrs().begin(), decl->attrs().end(),
1068 [function_attr](
1069 auto &&attr) { return attr->getKind() == function_attr; });
1070}
1071
1072std::optional<size_t> get_array_size(const clang::ArrayType &type)
1073{
1074 if (const auto *constant_array =
1075 clang::dyn_cast<clang::ConstantArrayType>(&type);
1076 constant_array != nullptr) {
1077 return {constant_array->getSize().getZExtValue()};
1078 }
1079
1080 return {};
1081}
1082
1083void set_source_location(clang::SourceManager &source_manager,
1084 const clang::SourceLocation &location,
1086 std::filesystem::path tu_path, std::filesystem::path relative_to_path_)
1087{
1088 namespace fs = std::filesystem;
1089
1090 std::string file;
1091 unsigned line{};
1092 unsigned column{};
1093
1094 if (location.isValid()) {
1095 file =
1096 source_manager.getFilename(source_manager.getSpellingLoc(location))
1097 .str();
1098 line = source_manager.getSpellingLineNumber(location);
1099 column = source_manager.getSpellingColumnNumber(location);
1100
1101 if (file.empty()) {
1102 // Why do I have to do this?
1104 location.printToString(source_manager), file, line, column);
1105 }
1106 }
1107 else {
1108 auto success = parse_source_location(
1109 location.printToString(source_manager), file, line, column);
1110 if (!success) {
1111 LOG_DBG("Failed to extract source location for element from {}",
1112 location.printToString(source_manager));
1113 return;
1114 }
1115 }
1116
1117 // ensure the path is absolute
1118 fs::path file_path{file};
1119 if (!file_path.is_absolute()) {
1120 file_path = fs::absolute(file_path);
1121 }
1122
1123 file_path = weakly_canonical(file_path);
1124
1125 file = file_path.string();
1126
1127 element.set_file(file);
1128
1129 if (util::is_relative_to(file_path, relative_to_path_)) {
1131 fs::path{element.file()}.lexically_relative(relative_to_path_)));
1132 }
1133 else {
1134 element.set_file_relative("");
1135 }
1136
1137 element.set_translation_unit(tu_path.string());
1138 element.set_line(line);
1139 element.set_column(column);
1140 element.set_location_id(location.getHashValue());
1141}
1142
1143const clang::Type *get_unqualified_type(const clang::TypedefDecl *decl)
1144{
1145 const auto *type_source_info = decl->getTypeSourceInfo();
1146
1147 if (type_source_info == nullptr)
1148 return nullptr;
1149
1150 return type_source_info->getType().split().Ty;
1151}
1152
1153const clang::EnumDecl *get_typedef_enum_decl(const clang::TypedefDecl *decl)
1154{
1155 if (decl == nullptr)
1156 return nullptr;
1157
1158 const clang::Type *unqualified_type = get_unqualified_type(decl);
1159
1160#if LLVM_VERSION_MAJOR < 22
1161 if (unqualified_type->getTypeClass() == clang::Type::Elaborated) {
1162 const auto *tag_decl =
1163 clang::cast<clang::ElaboratedType>(unqualified_type)
1164 ->getNamedType()
1165 ->getAsTagDecl();
1166
1167 if (tag_decl == nullptr)
1168 return nullptr;
1169
1170 const auto *enum_decl = clang::dyn_cast<clang::EnumDecl>(tag_decl);
1171 if (enum_decl != nullptr && enum_decl->getIdentifier() == nullptr)
1172 return enum_decl;
1173 }
1174#else
1175 {
1176 const auto *tag_decl = unqualified_type->getAsTagDecl();
1177
1178 if (tag_decl == nullptr)
1179 return nullptr;
1180
1181 const auto *enum_decl = clang::dyn_cast<clang::EnumDecl>(tag_decl);
1182 if (enum_decl != nullptr && enum_decl->getIdentifier() == nullptr)
1183 return enum_decl;
1184 }
1185#endif
1186
1187 return nullptr;
1188}
1189
1191 const clang::TemplateTypeParmType *type_parameter,
1192 const clang::TemplateDecl *template_decl)
1193{
1194 clang::TemplateTypeParmDecl *template_type_parameter{nullptr};
1195
1196#if LLVM_VERSION_MAJOR >= 22
1197 // In LLVM 22+, getDecl() on a TemplateTypeParmType used in a base class
1198 // may return the outer template's parameter (carrying an inherited concept
1199 // constraint) instead of the base template's own parameter. Prefer the
1200 // template_decl's own parameter at the matching index to avoid spuriously
1201 // inheriting constraints (e.g. HList<T...> becoming HList<IsArithmetic
1202 // T...> when used as base of Arithmetic<IsArithmetic T...>).
1203 if (template_decl->getTemplateParameters()->size() >
1204 type_parameter->getIndex()) {
1205 template_type_parameter = clang::dyn_cast<clang::TemplateTypeParmDecl>(
1206 template_decl->getTemplateParameters()->getParam(
1207 type_parameter->getIndex()));
1208 }
1209 if (template_type_parameter == nullptr &&
1210 type_parameter->getDecl() != nullptr) {
1211 template_type_parameter = type_parameter->getDecl();
1212 }
1213#else
1214 if (type_parameter->getDecl() != nullptr) {
1215 template_type_parameter = type_parameter->getDecl();
1216 }
1217 else if (template_decl->getTemplateParameters()->size() >
1218 type_parameter->getIndex()) {
1219 template_type_parameter = clang::dyn_cast<clang::TemplateTypeParmDecl>(
1220 template_decl->getTemplateParameters()->getParam(
1221 type_parameter->getIndex()));
1222 }
1223#endif
1224
1225 if (template_type_parameter == nullptr ||
1226 template_type_parameter->getTypeConstraint() == nullptr)
1227 return nullptr;
1228
1229#if LLVM_VERSION_MAJOR < 22
1230 return template_type_parameter->getTypeConstraint()->getNamedConcept();
1231#else
1232 return clang::cast<clang::ConceptDecl>(
1233 template_type_parameter->getTypeConstraint()->getNamedConcept());
1234#endif
1235}
1236
1237bool is_lambda_call(const clang::Expr *expr)
1238{
1239 const auto *call_expr = clang::dyn_cast<clang::CallExpr>(expr);
1240
1241 if (call_expr == nullptr)
1242 return false;
1243
1244 if (clang::dyn_cast_or_null<clang::LambdaExpr>(call_expr->getCallee()) !=
1245 nullptr) {
1246 return true;
1247 }
1248
1249 const auto *function_callee = call_expr->getDirectCallee();
1250 if (function_callee != nullptr) {
1251
1252 const auto *method_decl =
1253 clang::dyn_cast<clang::CXXMethodDecl>(function_callee);
1254 if (method_decl == nullptr)
1255 return false;
1256
1257 if (method_decl->getParent()->isLambda() &&
1258 method_decl->getOverloadedOperator() == clang::OO_Call) {
1259 return true;
1260 }
1261 }
1262
1263 return false;
1264}
1265
1266bool is_lambda_method(const clang::FunctionDecl *decl)
1267{
1268 if (decl == nullptr)
1269 return false;
1270
1271 if (const auto *method = clang::dyn_cast<clang::CXXMethodDecl>(decl);
1272 method != nullptr) {
1273 if (method->getParent() != nullptr && method->getParent()->isLambda())
1274 return true;
1275 }
1276
1277 return false;
1278}
1279} // namespace clanguml::common