0.6.0
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-2025 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 print_policy.PrintCanonicalTypes = 0;
193
194 std::string result;
195
196 result = type.getAsString(print_policy);
197
198 if (try_canonical && result.find('<') != std::string::npos) {
199 auto canonical_type_name =
200 type.getCanonicalType().getAsString(print_policy);
201
202 auto result_qualified_template_name =
203 result.substr(0, result.find('<'));
204 auto result_template_arguments = result.substr(result.find('<'));
205
206 auto canonical_qualified_template_name =
207 canonical_type_name.substr(0, canonical_type_name.find('<'));
208
209 // Choose the longer name (why do I have to do this?)
210 if (result_qualified_template_name.size() <
211 canonical_qualified_template_name.size()) {
212
213 result =
214 canonical_qualified_template_name + result_template_arguments;
215 }
216 }
217
218 // If for any reason clang reports the type as empty string, make sure
219 // it has some default name
220 if (result.empty())
221 result = "(anonymous)";
222 else if (util::contains(result, "unnamed struct") ||
223 util::contains(result, "unnamed union")) {
224 const auto *declarationTag = type->getAsTagDecl();
225 if (declarationTag == nullptr) {
226 result = "(unnamed undeclared)";
227 }
228 else {
229 result = common::get_tag_name(*declarationTag);
230 }
231 }
232 else if (util::contains(result, "anonymous struct") ||
233 util::contains(result, "anonymous union")) {
234 result = common::get_tag_name(*type->getAsTagDecl());
235 }
236
237 // Remove trailing spaces after commas in template arguments
238 clanguml::util::replace_all(result, ", ", ",");
239 clanguml::util::replace_all(result, "> >", ">>");
240
241 // Try to get rid of 'type-parameter-X-Y' ugliness
242 if (result.find("type-parameter-") != std::string::npos) {
244 common::dereference(type)->getAs<clang::TypedefType>(),
245 [&result, &type](auto *p) {
246 auto [unqualified_type, context] =
248 result = p->getDecl()->getNameAsString();
249 if (!context.empty()) {
250 std::vector<std::string> deduced_contexts;
251
252 for (const auto &c : context) {
253 deduced_contexts.push_back(c.to_string());
254 }
255
256 result = fmt::format(
257 "{} {}", result, fmt::join(deduced_contexts, " "));
258 }
259 });
260 }
261
262 return result;
263}
264
265std::string to_string(const clang::RecordType &type,
266 const clang::ASTContext &ctx, bool try_canonical)
267{
268 return to_string(type.desugar(), ctx, try_canonical);
269}
270
271std::string to_string(
272 const clang::TemplateArgument &arg, const clang::ASTContext *ctx)
273{
274 switch (arg.getKind()) {
275 case clang::TemplateArgument::Expression:
276 return to_string(arg.getAsExpr());
277 case clang::TemplateArgument::Type:
278 return to_string(arg.getAsType(), *ctx, false);
279 case clang::TemplateArgument::Null:
280 return "";
281 case clang::TemplateArgument::NullPtr:
282 return "nullptr";
283 case clang::TemplateArgument::Integral:
284 return std::to_string(arg.getAsIntegral().getExtValue());
285 case clang::TemplateArgument::Template:
286 return to_string(arg.getAsTemplate());
287 case clang::TemplateArgument::TemplateExpansion:
288 return to_string(arg.getAsTemplateOrTemplatePattern());
289 default:
290 return "";
291 }
292}
293
294std::string to_string(const clang::TemplateName &templ)
295{
296 if (templ.getAsTemplateDecl() != nullptr) {
297 return templ.getAsTemplateDecl()->getQualifiedNameAsString();
298 }
299
300 std::string result;
301 const clang::LangOptions lang_options;
302 llvm::raw_string_ostream ostream(result);
303 templ.print(ostream, clang::PrintingPolicy(lang_options));
304
305 return result;
306}
307
308std::string to_string(const clang::Expr *expr)
309{
310 const clang::LangOptions lang_options;
311 std::string result;
312 llvm::raw_string_ostream ostream(result);
313 expr->printPretty(ostream, nullptr, clang::PrintingPolicy(lang_options));
314
315 return result;
316}
317
318std::string to_string(const clang::ValueDecl *val)
319{
320 return val->getQualifiedNameAsString();
321}
322
323std::string to_string(const clang::Stmt *stmt)
324{
325 const clang::LangOptions lang_options;
326 std::string result;
327 llvm::raw_string_ostream ostream(result);
328 stmt->printPretty(ostream, nullptr, clang::PrintingPolicy(lang_options));
329
330 return result;
331}
332
333std::string to_string(const clang::FunctionTemplateDecl *decl)
334{
335 std::vector<std::string> template_parameters;
336 // Handle template function
337 for (const auto *parameter : *decl->getTemplateParameters()) {
338 if (clang::dyn_cast_or_null<clang::TemplateTypeParmDecl>(parameter) !=
339 nullptr) {
340 const auto *template_type_parameter =
341 clang::dyn_cast_or_null<clang::TemplateTypeParmDecl>(parameter);
342
343 std::string template_parameter{
344 template_type_parameter->getNameAsString()};
345
346 if (template_type_parameter->isParameterPack())
347 template_parameter += "...";
348
349 template_parameters.emplace_back(std::move(template_parameter));
350 }
351 else {
352 // TODO
353 }
354 }
355 return fmt::format("{}<{}>({})", decl->getQualifiedNameAsString(),
356 fmt::join(template_parameters, ","), "");
357}
358
359std::string to_string(const clang::TypeConstraint *tc)
360{
361 if (tc == nullptr)
362 return {};
363
364 const clang::PrintingPolicy print_policy(
365 tc->getNamedConcept()->getASTContext().getLangOpts());
366
367 std::string ostream_buf;
368 llvm::raw_string_ostream ostream{ostream_buf};
369 tc->print(ostream, print_policy);
370
371 return ostream.str();
372}
373
375 clang::SourceRange range, const clang::SourceManager &sm)
376{
377 return clang::Lexer::getSourceText(
378 clang::CharSourceRange::getCharRange(range), sm, clang::LangOptions())
379 .str();
380}
381
382std::string get_source_text(
383 clang::SourceRange range, const clang::SourceManager &sm)
384{
385 const clang::LangOptions lo;
386
387 auto start_loc = sm.getSpellingLoc(range.getBegin());
388 auto last_token_loc = sm.getSpellingLoc(range.getEnd());
389 auto end_loc = clang::Lexer::getLocForEndOfToken(last_token_loc, 0, sm, lo);
390 auto printable_range = clang::SourceRange{start_loc, end_loc};
391 return get_source_text_raw(printable_range, sm);
392}
393
394std::tuple<unsigned int, unsigned int, std::string>
395extract_template_parameter_index(const std::string &type_parameter)
396{
397 assert(type_parameter.find("type-parameter-") == 0);
398
399 auto type_parameter_and_suffix = util::split(type_parameter, " ");
400
401 auto toks = util::split(
402 type_parameter_and_suffix.front().substr(strlen("type-parameter-")),
403 "-");
404
405 std::string qualifier;
406
407 if (type_parameter_and_suffix.size() > 1) {
408 qualifier = type_parameter_and_suffix.at(1);
409 }
410
411 return {std::stoi(toks.at(0)), std::stoi(toks.at(1)), std::move(qualifier)};
412}
413
415 const config::diagram &config, std::string &parameter_type)
416{
417#ifdef _MSC_VER
418 auto root_name =
419 fmt::format("{}", std::filesystem::current_path().root_name().string());
420#else
421 auto root_name = std::string{"/"};
422#endif
423
424 std::string lambda_prefix{fmt::format("(lambda at {}", root_name)};
425
426 while (parameter_type.find(lambda_prefix) != std::string::npos) {
427 auto lambda_begin = parameter_type.find(lambda_prefix);
428 auto lambda_prefix_size = lambda_prefix.size();
429#ifdef _MSC_VER
430 // Skip the `\` or `/` after drive letter and semicolon
431 lambda_prefix_size++;
432#endif
433 auto absolute_lambda_path_end =
434 parameter_type.find(':', lambda_begin + lambda_prefix_size);
435 auto absolute_lambda_path = parameter_type.substr(
436 lambda_begin + lambda_prefix_size - 1,
437 absolute_lambda_path_end - (lambda_begin + lambda_prefix_size - 1));
438
439 auto relative_lambda_path = util::path_to_url(
440 config.make_path_relative(absolute_lambda_path).string());
441
442 parameter_type = fmt::format("{}(lambda at {}{}",
443 parameter_type.substr(0, lambda_begin), relative_lambda_path,
444 parameter_type.substr(absolute_lambda_path_end));
445 }
446}
447
448bool is_subexpr_of(const clang::Stmt *parent_stmt, const clang::Stmt *sub_stmt)
449{
450 if (parent_stmt == nullptr || sub_stmt == nullptr)
451 return false;
452
453 if (parent_stmt == sub_stmt)
454 return true;
455
456 return std::any_of(parent_stmt->child_begin(), parent_stmt->child_end(),
457 [sub_stmt](const auto *e) { return is_subexpr_of(e, sub_stmt); });
458}
459
460template <> eid_t to_id(const std::string &full_name)
461{
462 return static_cast<eid_t>(
463 static_cast<uint64_t>(std::hash<std::string>{}(full_name)));
464}
465
466eid_t to_id(const clang::QualType &type, const clang::ASTContext &ctx)
467{
468 return to_id(common::to_string(type, ctx));
469}
470
471template <> eid_t to_id(const clang::NamespaceDecl &declaration)
472{
473 return to_id(get_qualified_name(declaration));
474}
475
476template <> eid_t to_id(const clang::RecordDecl &declaration)
477{
478 return to_id(get_qualified_name(declaration));
479}
480
481template <> eid_t to_id(const clang::ObjCCategoryDecl &type)
482{
483 return to_id(fmt::format("__objc__category__{}", type.getNameAsString()));
484}
485
486template <> eid_t to_id(const clang::ObjCInterfaceDecl &type)
487{
488 return to_id(fmt::format("__objc__interface__{}", type.getNameAsString()));
489}
490
491template <> eid_t to_id(const clang::ObjCProtocolDecl &type)
492{
493 return to_id(fmt::format("__objc__protocol__{}", type.getNameAsString()));
494}
495
496template <> eid_t to_id(const clang::EnumDecl &declaration)
497{
498 return to_id(get_qualified_name(declaration));
499}
500
501template <> eid_t to_id(const clang::TagDecl &declaration)
502{
503 return to_id(get_qualified_name(declaration));
504}
505
506template <> eid_t to_id(const clang::CXXRecordDecl &declaration)
507{
508 return to_id(get_qualified_name(declaration));
509}
510
511template <> eid_t to_id(const clang::EnumType &t)
512{
513 return to_id(*t.getDecl());
514}
515
516template <> eid_t to_id(const std::filesystem::path &file)
517{
518 return to_id(file.lexically_normal().string());
519}
520
521template <> eid_t to_id(const clang::TemplateArgument &template_argument)
522{
523 if (template_argument.getKind() == clang::TemplateArgument::Type) {
524 if (const auto *enum_type =
525 template_argument.getAsType()->getAs<clang::EnumType>();
526 enum_type != nullptr)
527 return to_id(*enum_type->getAsTagDecl());
528
529 if (const auto *record_type =
530 template_argument.getAsType()->getAs<clang::RecordType>();
531 record_type != nullptr)
532 return to_id(*record_type->getAsRecordDecl());
533 }
534
535 throw std::runtime_error("Cannot generate id for template argument");
536}
537
538std::pair<common::model::namespace_, std::string> split_ns(
539 const std::string &full_name)
540{
541 assert(!full_name.empty());
542
543 auto name_before_template = ::clanguml::util::split(full_name, "<")[0];
545 ::clanguml::util::split(name_before_template, "::")};
546 auto name = ns.name();
547 ns.pop_back();
548 return {ns, name};
549}
550
551std::vector<common::model::template_parameter> parse_unexposed_template_params(
552 const std::string &params,
553 const std::function<std::string(const std::string &)> &ns_resolve,
554 int depth)
555{
557
558 std::vector<template_parameter> res;
559
560 auto it = params.begin();
561 while (std::isspace(*it) != 0)
562 ++it;
563
564 std::string type{};
565 std::vector<template_parameter> nested_params;
566 bool complete_class_template_argument{false};
567
568 while (it != params.end()) {
569 if (*it == '<') {
570 int nested_level{0};
571 auto bracket_match_begin = it + 1;
572 auto bracket_match_end = bracket_match_begin;
573 while (bracket_match_end != params.end()) {
574 if (*bracket_match_end == '<') {
575 nested_level++;
576 }
577 else if (*bracket_match_end == '>') {
578 if (nested_level > 0)
579 nested_level--;
580 else
581 break;
582 }
583 else {
584 }
585 bracket_match_end++;
586 }
587
588 std::string nested_params_str(
589 bracket_match_begin, bracket_match_end);
590
591 nested_params = parse_unexposed_template_params(
592 nested_params_str, ns_resolve, depth + 1);
593
594 if (nested_params.empty()) {
595 // We couldn't extract any nested template parameters from
596 // `nested_params_str` so just add it as type of template
597 // argument as is
598 nested_params.emplace_back(
599 template_parameter::make_unexposed_argument(
600 nested_params_str));
601 }
602
603 it = bracket_match_end - 1;
604 }
605 else if (*it == '>') {
606 complete_class_template_argument = true;
607 if (depth == 0) {
608 break;
609 }
610 }
611 else if (*it == ',') {
612 complete_class_template_argument = true;
613 }
614 else {
615 type += *it;
616 }
617 if (complete_class_template_argument) {
618 auto t = template_parameter::make_unexposed_argument(
619 ns_resolve(clanguml::util::trim_typename(type)));
620 type = "";
621 for (auto &&param : nested_params)
622 t.add_template_param(std::move(param));
623
624 res.emplace_back(std::move(t));
625 complete_class_template_argument = false;
626 }
627 it++;
628 }
629
630 if (!type.empty()) {
631 auto t = template_parameter::make_unexposed_argument(
632 ns_resolve(clanguml::util::trim_typename(type)));
633 type = "";
634 for (auto &&param : nested_params)
635 t.add_template_param(std::move(param));
636
637 res.emplace_back(std::move(t));
638 }
639
640 return res;
641}
642
643bool is_type_parameter(const std::string &t)
644{
645 return t.find("type-parameter-") == 0;
646}
647
648bool is_qualifier(const std::string &q)
649{
650 return q == "&" || q == "&&" || q == "const&";
651}
652
653bool is_bracket(const std::string &b)
654{
655 return b == "(" || b == ")" || b == "[" || b == "]";
656}
657
659{
660 return std::isalnum(c) != 0 || c == '_';
661}
662
663bool is_identifier(const std::string &t)
664{
665 return std::all_of(t.begin(), t.end(),
666 [](const char c) { return is_identifier_character(c); });
667}
668
669bool is_keyword(const std::string &t)
670{
671 static std::vector<std::string> keywords{"alignas", "alignof", "asm",
672 "auto", "bool", "break", "case", "catch", "char", "char16_t",
673 "char32_t", "class", "concept", "const", "constexpr", "const_cast",
674 "continue", "decltype", "default", "delete", "do", "double",
675 "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false",
676 "float", "for", "friend", "goto", "if", "inline", "int", "long",
677 "mutable", "namespace", "new", "noexcept", "nullptr", "operator",
678 "private", "protected", "public", "register", "reinterpret_cast",
679 "return", "requires", "short", "signed", "sizeof", "static",
680 "static_assert", "static_cast", "struct", "switch", "template", "this",
681 "thread_local", "throw", "true", "try", "typedef", "typeid", "typename",
682 "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t",
683 "while"};
684
685 return util::contains(keywords, t);
686}
687
688bool is_qualified_identifier(const std::string &t)
689{
690 return std::isalpha(t.at(0)) != 0 &&
691 std::all_of(t.begin(), t.end(), [](const char c) {
692 return is_identifier_character(c) || c == ':';
693 });
694}
695
696bool is_type_token(const std::string &t)
697{
698 return is_type_parameter(t) ||
699 (is_identifier(t) && !is_qualifier(t) && !is_bracket(t));
700}
701
702std::string format_condition_text(const std::string &condition_text)
703{
704 std::string result{condition_text};
705
706 if (result.size() < 2)
707 return {};
708
709 std::vector<std::string> text_lines = util::split(result, "\n", true);
710
711 // Trim each line
712 for (auto &line : text_lines) {
713 line = util::trim(line);
714 }
715
716 result = util::join(" ", text_lines);
717
718 if (result.at(0) == '(' && result.back() == ')')
719 return result.substr(1, result.size() - 2);
720
721 return result;
722}
723
724std::string get_condition_text(clang::SourceManager &sm, clang::IfStmt *stmt)
725{
726 auto condition_range =
727 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
728
729 return format_condition_text(get_source_text(condition_range, sm));
730}
731
732std::string get_condition_text(clang::SourceManager &sm, clang::WhileStmt *stmt)
733{
734 auto condition_range =
735 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
736
737 return format_condition_text(get_source_text(condition_range, sm));
738}
739
741 clang::SourceManager &sm, clang::CXXForRangeStmt *stmt)
742{
743 auto condition_range = stmt->getRangeStmt()->getSourceRange();
744
745 return format_condition_text(get_source_text(condition_range, sm));
746}
747
748std::string get_condition_text(clang::SourceManager &sm, clang::ForStmt *stmt)
749{
750 auto condition_range =
751 clang::SourceRange(stmt->getLParenLoc(), stmt->getRParenLoc());
752
753 return format_condition_text(get_source_text(condition_range, sm));
754}
755
756std::string get_condition_text(clang::SourceManager &sm, clang::DoStmt *stmt)
757{
758 auto condition_range = stmt->getCond()->getSourceRange();
759
760 return format_condition_text(get_source_text(condition_range, sm));
761}
762
764 clang::SourceManager &sm, clang::ConditionalOperator *stmt)
765{
766 auto condition_range = stmt->getCond()->getSourceRange();
767
768 return format_condition_text(get_source_text(condition_range, sm));
769}
770
771clang::QualType dereference(clang::QualType type)
772{
773 auto res = type;
774
775 while (true) {
776 if (res->isReferenceType())
777 res = res.getNonReferenceType();
778 else if (res->isPointerType())
779 res = res->getPointeeType();
780 else
781 break;
782 }
783
784 return res;
785}
786
787std::pair<clang::QualType, std::deque<common::model::context>>
788consume_type_context(clang::QualType type)
789{
790 std::deque<common::model::context> res;
791
792 while (true) {
793 bool try_again{false};
795
796 if (type.isConstQualified()) {
797 ctx.is_const = true;
798 try_again = true;
799 }
800
801 if (type.isVolatileQualified()) {
802 ctx.is_volatile = true;
803 try_again = true;
804 }
805
806 if (type->isPointerType() || type->isReferenceType()) {
807 if (type.isConstQualified() || type.isVolatileQualified()) {
808 ctx.is_ref_const = type.isConstQualified();
809 ctx.is_ref_volatile = type.isVolatileQualified();
810
811 try_again = true;
812 }
813 }
814
815 if (type->isLValueReferenceType()) {
817 try_again = true;
818 }
819 else if (type->isRValueReferenceType()) {
821 try_again = true;
822 }
823 else if (type->isMemberFunctionPointerType() &&
824 type->getPointeeType()->getAs<clang::FunctionProtoType>() !=
825 nullptr) {
826 const auto ref_qualifier =
827 type->getPointeeType() // NOLINT
828 ->getAs<clang::FunctionProtoType>() // NOLINT
829 ->getRefQualifier();
830
831 if (ref_qualifier == clang::RefQualifierKind::RQ_RValue) {
833 try_again = true;
834 }
835 else if (ref_qualifier == clang::RefQualifierKind::RQ_LValue) {
837 try_again = true;
838 }
839 }
840 else if (type->isPointerType()) {
842 try_again = true;
843 }
844
845 if (try_again) {
846 if (type->isPointerType()) {
847 if (type->getPointeeType().isConstQualified())
848 ctx.is_const = true;
849 if (type->getPointeeType().isVolatileQualified())
850 ctx.is_volatile = true;
851
852 type = type->getPointeeType().getUnqualifiedType();
853 }
854 else if (type->isReferenceType()) {
855 if (type.getNonReferenceType().isConstQualified())
856 ctx.is_const = true;
857 if (type.getNonReferenceType().isVolatileQualified())
858 ctx.is_volatile = true;
859
860 type = type.getNonReferenceType().getUnqualifiedType();
861 }
862 else if (type.isConstQualified() || type.isVolatileQualified()) {
863 ctx.is_const = type.isConstQualified();
864 ctx.is_volatile = type.isVolatileQualified();
865 type = type.getUnqualifiedType();
866 }
867
868 res.push_front(ctx);
869
870 if (type->isMemberFunctionPointerType())
871 return std::make_pair(type, res);
872 }
873 else
874 return std::make_pair(type, res);
875 }
876}
877
879 const std::string &t)
880{
881 std::vector<std::string> result;
882
883 auto spaced_out = util::split(t, " ");
884
885 for (const auto &word : spaced_out) {
886 if (is_qualified_identifier(word)) {
887 if (word != "class" && word != "templated" && word != "struct")
888 result.emplace_back(word);
889 continue;
890 }
891
892 std::string tok;
893
894 for (const char c : word) {
895 if (c == '(' || c == ')' || c == '[' || c == ']' || c == '<' ||
896 c == '>') {
897 if (!tok.empty())
898 result.emplace_back(tok);
899 result.emplace_back(std::string{c});
900 tok.clear();
901 }
902 else if (c == ':') {
903 if (!tok.empty() && tok != ":") {
904 result.emplace_back(tok);
905 tok = ":";
906 }
907 else if (tok == ":") {
908 result.emplace_back("::");
909 tok = "";
910 }
911 else {
912 tok += ':';
913 }
914 }
915 else if (c == ',') {
916 if (!tok.empty()) {
917 result.emplace_back(tok);
918 }
919 result.emplace_back(",");
920 tok.clear();
921 }
922 else if (c == '*') {
923 if (!tok.empty()) {
924 result.emplace_back(tok);
925 }
926 result.emplace_back("*");
927 tok.clear();
928 }
929 else if (c == '.') {
930 // This can only be the case if we have a variadic template,
931 // right?
932 if (tok == "..") {
933 result.emplace_back("...");
934 tok.clear();
935 }
936 else if (tok == ".") {
937 tok = "..";
938 }
939 else if (!tok.empty()) {
940 result.emplace_back(tok);
941 tok = ".";
942 }
943 }
944 else {
945 tok += c;
946 }
947 }
948
949 tok = util::trim(tok);
950
951 if (!tok.empty()) {
952 if (tok != "class" && tok != "typename" && word != "struct")
953 result.emplace_back(tok);
954 tok.clear();
955 }
956 }
957
958 return result;
959}
960
961bool parse_source_location(const std::string &location_str, std::string &file,
962 unsigned &line, unsigned &column)
963{
964 auto tokens = util::split(location_str, ":");
965
966 if (tokens.size() < 3)
967 return false;
968
969 if (tokens.size() == 4) {
970 // Handle Windows paths
971 decltype(tokens) tmp_tokens{};
972 tmp_tokens.emplace_back(
973 fmt::format("{}:{}", tokens.at(0), tokens.at(1)));
974 tmp_tokens.emplace_back(tokens.at(2));
975 tmp_tokens.emplace_back(tokens.at(3));
976
977 tokens = std::move(tmp_tokens);
978 }
979
980 file = tokens.at(0);
981 try {
982 line = std::stoi(tokens.at(1));
983 }
984 catch (std::invalid_argument &e) {
985 return false;
986 }
987
988 try {
989 column = std::stoi(tokens.at(2));
990 }
991 catch (std::invalid_argument &e) {
992 column = 0;
993 }
994
995 return true;
996}
997
998clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm,
999 const clang::ASTContext &context, const clang::Stmt *stmt)
1000{
1001 return get_raw_comment(sm, context, stmt->getSourceRange());
1002}
1003
1004clang::RawComment *get_declaration_raw_comment(const clang::SourceManager &sm,
1005 const clang::ASTContext &context, const clang::Decl *decl)
1006{
1007 return get_raw_comment(sm, context, decl->getSourceRange());
1008}
1009
1010clang::RawComment *get_raw_comment(const clang::SourceManager &sm,
1011 const clang::ASTContext &context, const clang::SourceRange &source_range)
1012{
1013 auto expr_begin = source_range.getBegin();
1014 const auto expr_begin_line = sm.getSpellingLineNumber(expr_begin);
1015
1016 std::string file_Path = sm.getFilename(expr_begin).str();
1017
1018 auto file_id = sm.getFileID(expr_begin);
1019
1020 if (!context.Comments.empty() &&
1021 context.Comments.getCommentsInFile(file_id) != nullptr) {
1022 for (const auto [offset, raw_comment] :
1023 *context.Comments.getCommentsInFile(sm.getFileID(expr_begin))) {
1024 const auto comment_end_line = sm.getSpellingLineNumber(
1025 raw_comment->getSourceRange().getEnd());
1026
1027 if (expr_begin_line == comment_end_line ||
1028 expr_begin_line == comment_end_line + 1)
1029 return raw_comment;
1030 }
1031 }
1032
1033 return {};
1034}
1035
1036bool is_coroutine(const clang::FunctionDecl &decl)
1037{
1038 const auto *body = decl.getBody();
1039 return clang::isa_and_nonnull<clang::CoroutineBodyStmt>(body);
1040}
1041
1042bool is_struct(const clang::NamedDecl *decl)
1043{
1044 if (decl == nullptr)
1045 return false;
1046
1047 if (const auto *record = clang::dyn_cast<clang::CXXRecordDecl>(decl);
1048 record) {
1049 return record->isStruct();
1050 }
1051
1052 if (const auto *tag = clang::dyn_cast<clang::TagDecl>(decl); tag) {
1053 return tag->isStruct();
1054 }
1055
1056 return false;
1057}
1058
1059bool has_attr(const clang::FunctionDecl *decl, clang::attr::Kind function_attr)
1060{
1061 return std::any_of(decl->attrs().begin(), decl->attrs().end(),
1062 [function_attr](
1063 auto &&attr) { return attr->getKind() == function_attr; });
1064}
1065
1066std::optional<size_t> get_array_size(const clang::ArrayType &type)
1067{
1068 if (const auto *constant_array =
1069 clang::dyn_cast<clang::ConstantArrayType>(&type);
1070 constant_array != nullptr) {
1071 return {constant_array->getSize().getZExtValue()};
1072 }
1073
1074 return {};
1075}
1076
1077void set_source_location(clang::SourceManager &source_manager,
1078 const clang::SourceLocation &location,
1080 std::filesystem::path tu_path, std::filesystem::path relative_to_path_)
1081{
1082 namespace fs = std::filesystem;
1083
1084 std::string file;
1085 unsigned line{};
1086 unsigned column{};
1087
1088 if (location.isValid()) {
1089 file =
1090 source_manager.getFilename(source_manager.getSpellingLoc(location))
1091 .str();
1092 line = source_manager.getSpellingLineNumber(location);
1093 column = source_manager.getSpellingColumnNumber(location);
1094
1095 if (file.empty()) {
1096 // Why do I have to do this?
1098 location.printToString(source_manager), file, line, column);
1099 }
1100 }
1101 else {
1102 auto success = parse_source_location(
1103 location.printToString(source_manager), file, line, column);
1104 if (!success) {
1105 LOG_DBG("Failed to extract source location for element from {}",
1106 location.printToString(source_manager));
1107 return;
1108 }
1109 }
1110
1111 // ensure the path is absolute
1112 fs::path file_path{file};
1113 if (!file_path.is_absolute()) {
1114 file_path = fs::absolute(file_path);
1115 }
1116
1117 file_path = file_path.lexically_normal();
1118
1119 file = file_path.string();
1120
1121 element.set_file(file);
1122
1123 if (util::is_relative_to(file_path, relative_to_path_)) {
1125 fs::path{element.file()}.lexically_relative(relative_to_path_)));
1126 }
1127 else {
1128 element.set_file_relative("");
1129 }
1130
1131 element.set_translation_unit(tu_path.string());
1132 element.set_line(line);
1133 element.set_column(column);
1134 element.set_location_id(location.getHashValue());
1135}
1136
1137const clang::Type *get_unqualified_type(const clang::TypedefDecl *decl)
1138{
1139 const auto *type_source_info = decl->getTypeSourceInfo();
1140
1141 if (type_source_info == nullptr)
1142 return nullptr;
1143
1144 return type_source_info->getType().split().Ty;
1145}
1146
1147const clang::EnumDecl *get_typedef_enum_decl(const clang::TypedefDecl *decl)
1148{
1149 if (decl == nullptr)
1150 return nullptr;
1151
1152 const clang::Type *unqualified_type = get_unqualified_type(decl);
1153
1154 if (unqualified_type->getTypeClass() == clang::Type::Elaborated) {
1155 const auto *tag_decl =
1156 clang::cast<clang::ElaboratedType>(unqualified_type)
1157 ->getNamedType()
1158 ->getAsTagDecl();
1159
1160 if (tag_decl == nullptr)
1161 return nullptr;
1162
1163 const auto *enum_decl = clang::dyn_cast<clang::EnumDecl>(tag_decl);
1164 if (enum_decl != nullptr && enum_decl->getIdentifier() == nullptr)
1165 return enum_decl;
1166 }
1167
1168 return nullptr;
1169}
1170} // namespace clanguml::common