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