0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
translation_unit_visitor.cc
Go to the documentation of this file.
1/**
2 * @file src/class_diagram/visitor/translation_unit_visitor.cc
3 *
4 * Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
20#include "common/clang_utils.h"
21
22#include <clang/AST/ExprConcepts.h>
23#include <clang/Basic/FileManager.h>
24#include <clang/Lex/Preprocessor.h>
25#include <spdlog/spdlog.h>
26
28
33 , template_builder_{diagram, config, *this}
34{
35}
36
38 const clang::NamedDecl *decl) const
39{
40 auto cls = std::make_unique<class_>(config().using_namespace());
41 cls->is_struct(common::is_struct(decl));
42 return cls;
43}
44
46{
47 assert(ns != nullptr);
48
49 if (config().package_type() == config::package_type_t::kDirectory)
50 return true;
51
52 if (ns->isAnonymousNamespace() || ns->isInline())
53 return true;
54
55 LOG_DBG("= Visiting namespace declaration {} at {}",
56 ns->getQualifiedNameAsString(),
57 ns->getLocation().printToString(source_manager()));
58
59 auto package_path = namespace_{common::get_qualified_name(*ns)};
60 auto package_parent = package_path;
61
62 std::string name;
63 if (!package_path.is_empty())
64 name = package_path.name();
65
66 if (!package_parent.is_empty())
67 package_parent.pop_back();
68
69 const auto usn = config().using_namespace();
70
71 auto p = std::make_unique<common::model::package>(usn);
72 package_path = package_path.relative_to(usn);
73
74 p->set_name(name);
75 p->set_namespace(package_parent);
76 p->set_id(common::to_id(*ns));
77 id_mapper().add(ns->getID(), p->id());
78
79 if (config().filter_mode() == config::filter_mode_t::advanced ||
80 (diagram().should_include(*p) && !diagram().get(p->id()))) {
81 process_comment(*ns, *p);
82 set_source_location(*ns, *p);
83
84 p->set_style(p->style_spec());
85
86 for (const auto *attr : ns->attrs()) {
87 if (attr->getKind() == clang::attr::Kind::Deprecated) {
88 p->set_deprecated(true);
89 break;
90 }
91 }
92
93 if (!p->skip()) {
94 diagram().add(package_path, std::move(p));
95 }
96 }
97
98 return true;
99}
100
102{
103 assert(enm != nullptr);
104
105 // Anonymous enum values should be rendered as class fields
106 // with type enum
107 if (enm->getNameAsString().empty())
108 return true;
109
110 if (!should_include(enm))
111 return true;
112
113 LOG_DBG("= Visiting enum declaration {} at {}",
114 enm->getQualifiedNameAsString(),
115 enm->getLocation().printToString(source_manager()));
116
117 auto e_ptr = std::make_unique<enum_>(config().using_namespace());
118 auto &e = *e_ptr;
119
120 std::string qualified_name = common::get_qualified_name(*enm);
121
122 auto ns{common::get_tag_namespace(*enm)};
123
124 const auto *parent = enm->getParent();
125
126 // Id of parent class or struct in which this enum is potentially nested
127 std::optional<eid_t> parent_id_opt;
128
129 if (parent != nullptr) {
130 const auto *parent_record_decl =
131 clang::dyn_cast<clang::RecordDecl>(parent);
132
133 if (parent_record_decl != nullptr) {
134 eid_t local_id{parent_record_decl->getID()};
135
136 // First check if the parent has been added to the diagram as
137 // regular class
138 parent_id_opt = id_mapper().get_global_id(local_id);
139
140 // If not, check if the parent template declaration is in the model
141 if (!parent_id_opt) {
142 if (parent_record_decl->getDescribedTemplate() != nullptr) {
143 local_id =
144 parent_record_decl->getDescribedTemplate()->getID();
145 parent_id_opt = id_mapper().get_global_id(local_id);
146 }
147 }
148 }
149 }
150
151 if (parent_id_opt && diagram().find<class_>(*parent_id_opt)) {
152 auto parent_class = diagram().find<class_>(*parent_id_opt);
153
154 e.set_namespace(ns);
155 e.set_name(parent_class.value().name() + "##" + enm->getNameAsString());
156 e.set_id(common::to_id(e.full_name(false)));
157 e.add_relationship({relationship_t::kContainment, *parent_id_opt});
158 e.nested(true);
159 }
160 else {
161 e.set_name(common::get_tag_name(*enm));
162 e.set_namespace(ns);
163 e.set_id(common::to_id(e.full_name(false)));
164 }
165
166 id_mapper().add(enm->getID(), e.id());
167
168 process_comment(*enm, e);
169 set_source_location(*enm, e);
170 set_owning_module(*enm, e);
171
172 if (e.skip())
173 return true;
174
175 e.set_style(e.style_spec());
176
177 for (const auto &ev : enm->enumerators()) {
178 e.constants().push_back(ev->getNameAsString());
179 }
180
181 add_enum(std::move(e_ptr));
182
183 return true;
184}
185
187 clang::ClassTemplateSpecializationDecl *cls)
188{
189 if (!should_include(cls))
190 return true;
191
192 LOG_DBG("= Visiting template specialization declaration {} at {} "
193 "(described class id {})",
194 cls->getQualifiedNameAsString(),
195 cls->getLocation().printToString(source_manager()),
196 cls->getSpecializedTemplate()
197 ? cls->getSpecializedTemplate()->getTemplatedDecl()->getID()
198 : 0);
199
200 // TODO: Add support for classes defined in function/method bodies
201 if (cls->isLocalClass() != nullptr)
202 return true;
203
204 auto template_specialization_ptr = process_template_specialization(cls);
205
206 if (!template_specialization_ptr)
207 return true;
208
209 auto &template_specialization = *template_specialization_ptr;
210
211 if (cls->hasDefinition()) {
212 // Process template specialization bases
213 process_class_bases(cls, template_specialization);
214
215 // Process class child entities
216 process_class_children(cls, template_specialization);
217 }
218
219 if (!template_specialization.template_specialization_found()) {
220 // Only do this if we haven't found a better specialization during
221 // construction of the template specialization
222 const eid_t ast_id{cls->getSpecializedTemplate()->getID()};
223 const auto maybe_id = id_mapper().get_global_id(ast_id);
224 if (maybe_id.has_value())
225 template_specialization.add_relationship(
226 {relationship_t::kInstantiation, maybe_id.value()});
227 }
228
229 if (diagram().should_include(template_specialization)) {
230 const auto full_name = template_specialization.full_name(false);
231 const auto id = template_specialization.id();
232
233 LOG_DBG("Adding class template specialization {} with id {}", full_name,
234 id);
235
236 add_class(std::move(template_specialization_ptr));
237 }
238
239 return true;
240}
241
243 clang::TypeAliasTemplateDecl *cls)
244{
245 if (!should_include(cls))
246 return true;
247
248 LOG_DBG("= Visiting template type alias declaration {} at {}",
249 cls->getQualifiedNameAsString(),
250 cls->getLocation().printToString(source_manager()));
251
252 const auto *template_type_specialization_ptr =
253 cls->getTemplatedDecl()
254 ->getUnderlyingType()
255 ->getAs<clang::TemplateSpecializationType>();
256
257 if (template_type_specialization_ptr == nullptr)
258 return true;
259
260 auto template_specialization_ptr =
261 std::make_unique<class_>(config().using_namespace());
263 *template_specialization_ptr, cls, *template_type_specialization_ptr);
264
265 if (diagram().should_include(*template_specialization_ptr)) {
266 const auto name = template_specialization_ptr->full_name();
267 const auto id = template_specialization_ptr->id();
268
269 LOG_DBG("Adding class {} with id {}", name, id);
270
271 set_source_location(*cls, *template_specialization_ptr);
272 set_owning_module(*cls, *template_specialization_ptr);
273
274 add_class(std::move(template_specialization_ptr));
275 }
276
277 return true;
278}
279
281 clang::ClassTemplateDecl *cls)
282{
283 if (!should_include(cls))
284 return true;
285
286 LOG_DBG("= Visiting class template declaration {} at {}",
287 cls->getQualifiedNameAsString(),
288 cls->getLocation().printToString(source_manager()));
289
290 auto c_ptr = create_class_declaration(cls->getTemplatedDecl());
291
292 if (!c_ptr)
293 return true;
294
295 add_processed_template_class(cls->getQualifiedNameAsString());
296
297 tbuilder().build_from_template_declaration(*c_ptr, *cls, *c_ptr);
298
299 // Override the id with the template id, for now we don't care about the
300 // underlying templated class id
301 const auto cls_full_name = c_ptr->full_name(false);
302 const auto id = common::to_id(cls_full_name);
303
304 c_ptr->set_id(id);
305 c_ptr->is_template(true);
306
307 id_mapper().add(cls->getID(), id);
308
309 constexpr auto kMaxConstraintCount = 24U;
310 llvm::SmallVector<const clang::Expr *, kMaxConstraintCount> constraints{};
311 if (cls->hasAssociatedConstraints()) {
312 cls->getAssociatedConstraints(constraints);
313 }
314
315 for (const auto *expr : constraints) {
317 }
318
319 if (!cls->getTemplatedDecl()->isCompleteDefinition()) {
320 forward_declarations_.emplace(id, std::move(c_ptr));
321 return true;
322 }
323 process_class_declaration(*cls->getTemplatedDecl(), *c_ptr);
324 forward_declarations_.erase(id);
325
326 if (diagram().should_include(*c_ptr)) {
327 const auto name = c_ptr->full_name();
328 LOG_DBG("Adding class template {} with id {}", name, id);
329
330 add_class(std::move(c_ptr));
331 }
332
333 return true;
334}
335
337{
338 if (clang::dyn_cast_or_null<clang::CXXRecordDecl>(rec) != nullptr)
339 // This is handled by VisitCXXRecordDecl()
340 return true;
341
342 // It seems we are in a C (not C++) translation unit
343 if (!should_include(rec))
344 return true;
345
346 LOG_DBG("= Visiting record declaration {} at {}",
347 rec->getQualifiedNameAsString(),
348 rec->getLocation().printToString(source_manager()));
349
350 auto record_ptr = create_record_declaration(rec);
351
352 if (!record_ptr)
353 return true;
354
355 const auto rec_id = record_ptr->id();
356
357 id_mapper().add(rec->getID(), rec_id);
358
359 auto &record_model = diagram().find<class_>(rec_id).has_value()
360 ? *diagram().find<class_>(rec_id).get()
361 : *record_ptr;
362
363 if (rec->isCompleteDefinition() && !record_model.complete()) {
364 process_record_members(rec, record_model);
365 record_model.complete(true);
366 }
367
368 auto id = record_model.id();
369 if (!rec->isCompleteDefinition()) {
370 forward_declarations_.emplace(id, std::move(record_ptr));
371 return true;
372 }
373 forward_declarations_.erase(id);
374
375 if (diagram().should_include(record_model)) {
376 LOG_DBG("Adding struct/union {} with id {}",
377 record_model.full_name(false), record_model.id());
378
379 add_class(std::move(record_ptr));
380 }
381 else {
382 LOG_DBG("Skipping struct/union {} with id {}", record_model.full_name(),
383 record_model.id());
384 }
385
386 return true;
387}
388
390{
391 if (!should_include(cpt))
392 return true;
393
394 LOG_DBG("= Visiting concept (isType: {}) declaration {} at {}",
395 cpt->isTypeConcept(), cpt->getQualifiedNameAsString(),
396 cpt->getLocation().printToString(source_manager()));
397
398 auto concept_model = create_concept_declaration(cpt);
399
400 if (!concept_model)
401 return true;
402
403 const auto concept_id = concept_model->id();
404
405 id_mapper().add(cpt->getID(), concept_id);
406
407 tbuilder().build_from_template_declaration(*concept_model, *cpt);
408
409 constexpr auto kMaxConstraintCount = 24U;
410 llvm::SmallVector<const clang::Expr *, kMaxConstraintCount> constraints{};
411 if (cpt->hasAssociatedConstraints()) {
412 cpt->getAssociatedConstraints(constraints);
413 }
414
415 for (const auto *expr : constraints) {
417 }
418
419 if (cpt->getConstraintExpr() != nullptr) {
421 cpt, cpt->getConstraintExpr(), *concept_model);
422
424 *concept_model, cpt->getConstraintExpr());
425 }
426
427 if (diagram().should_include(*concept_model)) {
428 LOG_DBG("Adding concept {} with id {}", concept_model->full_name(false),
429 concept_model->id());
430
431 add_concept(std::move(concept_model));
432 }
433 else {
434 LOG_DBG("Skipping concept {} with id {}", concept_model->full_name(),
435 concept_model->id());
436 }
437
438 return true;
439}
440
442 const clang::ConceptDecl *cpt, const clang::Expr *expr,
443 model::concept_ &concept_model) const
444{
445 if (const auto *constraint = llvm::dyn_cast<clang::RequiresExpr>(expr);
446 constraint) {
447
448 auto constraint_source = common::to_string(constraint);
449
450 LOG_DBG("== Processing constraint: '{}'", constraint_source);
451
452 for ([[maybe_unused]] const auto *requirement :
453 constraint->getRequirements()) {
454 // TODO
455 }
456
457 // process 'requires (...)' declaration
458 for (const auto *decl : constraint->getBody()->decls()) {
459 if (const auto *parm_var_decl =
460 llvm::dyn_cast<clang::ParmVarDecl>(decl);
461 parm_var_decl) {
462 parm_var_decl->getQualifiedNameAsString();
463
464 auto param_name = parm_var_decl->getNameAsString();
465 auto param_type = common::to_string(
466 parm_var_decl->getType(), cpt->getASTContext());
467
468 LOG_DBG("=== Processing parameter variable declaration: {}, {}",
469 param_type, param_name);
470
471 concept_model.add_parameter(
472 {std::move(param_type), std::move(param_name)});
473 }
474 else {
475 LOG_DBG("=== Processing some other concept declaration: {}",
476 decl->getID());
477 }
478 }
479
480 // process concept body requirements '{ }' if any
481 for (const auto *req : constraint->getRequirements()) {
482 if (req->getKind() == clang::concepts::Requirement::RK_Simple) {
483 const auto *simple_req =
484 llvm::dyn_cast<clang::concepts::ExprRequirement>(req);
485
486 if (simple_req != nullptr) {
488 simple_req->getExpr(), [&concept_model](const auto *e) {
489 auto simple_expr = common::to_string(e);
490
491 LOG_DBG("=== Processing expression requirement: {}",
492 simple_expr);
493
494 concept_model.add_statement(std::move(simple_expr));
495 });
496 }
497 }
498 else if (req->getKind() == clang::concepts::Requirement::RK_Type) {
500 llvm::dyn_cast<clang::concepts::TypeRequirement>(req),
501 [&concept_model, cpt](const auto *t) {
502 auto type_name = common::to_string(
503 t->getType()->getType(), cpt->getASTContext());
504
505 LOG_DBG(
506 "=== Processing type requirement: {}", type_name);
507
508 concept_model.add_statement(std::move(type_name));
509 });
510 }
511 else if (req->getKind() ==
512 clang::concepts::Requirement::RK_Nested) {
513 const auto *nested_req =
514 llvm::dyn_cast<clang::concepts::NestedRequirement>(req);
515
516 if (nested_req != nullptr) {
518 nested_req->getConstraintExpr(), [](const auto *e) {
519 LOG_DBG("=== Processing nested requirement: {}",
520 common::to_string(e));
521 });
522 }
523 }
524 else if (req->getKind() ==
525 clang::concepts::Requirement::RK_Compound) {
526 const auto *compound_req =
527 llvm::dyn_cast<clang::concepts::ExprRequirement>(req);
528
529 if (compound_req != nullptr) {
530 const auto *compound_expr_ptr = compound_req->getExpr();
531
532 if (compound_expr_ptr != nullptr) {
533 auto compound_expr =
534 common::to_string(compound_expr_ptr);
535
536 auto req_return_type =
537 compound_req->getReturnTypeRequirement();
538
539 if (!req_return_type.isEmpty()) {
540 compound_expr =
541 fmt::format("{{{}}} -> {}", compound_expr,
543 req_return_type.getTypeConstraint()));
544 }
545 else if (compound_req->hasNoexceptRequirement()) {
546 compound_expr =
547 fmt::format("{{{}}} noexcept", compound_expr);
548 }
549
550 LOG_DBG("=== Processing compound requirement: {}",
551 compound_expr);
552
553 concept_model.add_statement(std::move(compound_expr));
554 }
555 }
556 }
557 }
558 }
559 else if (const auto *binop = llvm::dyn_cast<clang::BinaryOperator>(expr);
560 binop) {
561 process_constraint_requirements(cpt, binop->getLHS(), concept_model);
562 process_constraint_requirements(cpt, binop->getRHS(), concept_model);
563 }
564 else if (const auto *unop = llvm::dyn_cast<clang::UnaryOperator>(expr);
565 unop) {
566 process_constraint_requirements(cpt, unop->getSubExpr(), concept_model);
567 }
568}
569
571 clanguml::common::model::element &c, const clang::Expr *expr)
572{
573 if (expr == nullptr)
574 return;
575 found_relationships_t relationships;
576
577 common::if_dyn_cast<clang::UnresolvedLookupExpr>(expr, [&](const auto *ul) {
578 for (const auto ta : ul->template_arguments()) {
579 find_relationships(ta.getArgument().getAsType(), relationships,
580 relationship_t::kConstraint);
581 }
582 });
583
584 common::if_dyn_cast<clang::ConceptSpecializationExpr>(
585 expr, [&](const auto *cs) {
587 });
588
589 common::if_dyn_cast<clang::RequiresExpr>(expr, [&](const auto *re) {
590 // TODO
591 });
592
593 common::if_dyn_cast<clang::BinaryOperator>(expr, [&](const auto *op) {
596 });
597
598 common::if_dyn_cast<clang::UnaryOperator>(expr, [&](const auto *op) {
600 });
601
602 for (const auto &[type_element_id, relationship_type] : relationships) {
603 if (type_element_id != c.id() &&
604 (relationship_type != relationship_t::kNone)) {
605
606 relationship r{relationship_type, type_element_id};
607
608 c.add_relationship(std::move(r));
609 }
610 }
611}
612
615 const clang::ConceptSpecializationExpr *concept_specialization)
616{
617
618 if (const auto *cpt = concept_specialization->getNamedConcept();
619 should_include(cpt)) {
620
621 const auto cpt_name = cpt->getNameAsString();
622 const eid_t ast_id{cpt->getID()};
623 const auto maybe_id = id_mapper().get_global_id(ast_id);
624 if (!maybe_id)
625 return;
626
627 const auto target_id = maybe_id.value();
628
629 std::vector<std::string> constrained_template_params;
630
631 size_t argument_index{};
632
633 for (const auto ta : concept_specialization->getTemplateArguments()) {
634 if (ta.getKind() == clang::TemplateArgument::Type) {
635 auto type_name =
636 common::to_string(ta.getAsType(), cpt->getASTContext());
637 extract_constrained_template_param_name(concept_specialization,
638 cpt, constrained_template_params, argument_index,
639 type_name);
640 }
641 else if (ta.getKind() == clang::TemplateArgument::Pack) {
642 if (!ta.getPackAsArray().empty() &&
643 ta.getPackAsArray().front().isPackExpansion()) {
644 const auto &pack_head =
645 ta.getPackAsArray().front().getAsType();
646 auto type_name =
647 common::to_string(pack_head, cpt->getASTContext());
649 concept_specialization, cpt,
650 constrained_template_params, argument_index, type_name);
651 }
652 }
653 else {
654 auto type_name =
655 common::to_string(ta.getAsType(), cpt->getASTContext());
656 LOG_DBG(
657 "=== Unsupported concept type parameter: {}", type_name);
658 }
659 argument_index++;
660 }
661
662 if (!constrained_template_params.empty())
664 {relationship_t::kConstraint, target_id, access_t::kNone,
665 fmt::format(
666 "{}", fmt::join(constrained_template_params, ","))});
667 }
668}
669
670bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
671{
672 if (!should_include(cls))
673 return true;
674
675 LOG_DBG("= Visiting class declaration {} at {}",
676 cls->getQualifiedNameAsString(),
677 cls->getLocation().printToString(source_manager()));
678
679 LOG_DBG(
680 "== getQualifiedNameAsString() = {}", cls->getQualifiedNameAsString());
681 if (cls->getOwningModule() != nullptr)
682 LOG_DBG(
683 "== getOwningModule()->Name = {}", cls->getOwningModule()->Name);
684 LOG_DBG("== getID() = {}", cls->getID());
685 LOG_DBG("== isTemplateDecl() = {}", cls->isTemplateDecl());
686 LOG_DBG("== isTemplated() = {}", cls->isTemplated());
687 LOG_DBG("== getParent()->isRecord()() = {}", cls->getParent()->isRecord());
688
689 if (const auto *parent_record =
690 clang::dyn_cast<clang::RecordDecl>(cls->getParent());
691 parent_record != nullptr) {
692 LOG_DBG("== getParent()->getQualifiedNameAsString() = {}",
693 parent_record->getQualifiedNameAsString());
694 }
695
696 if (has_processed_template_class(cls->getQualifiedNameAsString()))
697 // If we have already processed the template of this class
698 // skip it
699 return true;
700
701 if (cls->isTemplated() && (cls->getDescribedTemplate() != nullptr)) {
702 // If the described templated of this class is already in the model
703 // skip it:
704 const eid_t ast_id{cls->getDescribedTemplate()->getID()};
705 if (id_mapper().get_global_id(ast_id))
706 return true;
707 }
708
709 // TODO: Add support for classes defined in function/method bodies
710 if (cls->isLocalClass() != nullptr)
711 return true;
712
713 auto c_ptr = create_class_declaration(cls);
714
715 if (!c_ptr)
716 return true;
717
718 const auto cls_id = c_ptr->id();
719
720 id_mapper().add(cls->getID(), cls_id);
721
722 auto &class_model = diagram().find<class_>(cls_id).has_value()
723 ? *diagram().find<class_>(cls_id).get()
724 : *c_ptr;
725
726 if (cls->isCompleteDefinition() && !class_model.complete())
727 process_class_declaration(*cls, class_model);
728
729 auto id = class_model.id();
730 if (!cls->isCompleteDefinition()) {
731 forward_declarations_.emplace(id, std::move(c_ptr));
732 return true;
733 }
734 forward_declarations_.erase(id);
735
736 if (diagram().should_include(class_model)) {
737 LOG_DBG("Adding class {} with id {}", class_model.full_name(false),
738 class_model.id());
739
740 add_class(std::move(c_ptr));
741 }
742 else {
743 LOG_DBG("Skipping class {} with id {}", class_model.full_name(),
744 class_model.id());
745 }
746
747 return true;
748}
749
750std::unique_ptr<clanguml::class_diagram::model::concept_>
752{
753 assert(cpt != nullptr);
754
755 if (!should_include(cpt))
756 return {};
757
758 auto concept_ptr{
759 std::make_unique<model::concept_>(config().using_namespace())};
760 auto &concept_model = *concept_ptr;
761
762 auto ns = common::get_template_namespace(*cpt);
763
764 concept_model.set_name(cpt->getNameAsString());
765 concept_model.set_namespace(ns);
766 concept_model.set_id(common::to_id(concept_model.full_name(false)));
767
768 process_comment(*cpt, concept_model);
769 set_source_location(*cpt, concept_model);
770 set_owning_module(*cpt, concept_model);
771
772 if (concept_model.skip())
773 return {};
774
775 concept_model.set_style(concept_model.style_spec());
776
777 return concept_ptr;
778}
779
781 clang::RecordDecl *rec)
782{
783 assert(rec != nullptr);
784
785 if (!should_include(rec))
786 return {};
787
788 auto record_ptr{std::make_unique<class_>(config().using_namespace())};
789 auto &record = *record_ptr;
790
791 process_record_parent(rec, record, namespace_{});
792
793 if (!record.is_nested()) {
794 auto record_name = rec->getQualifiedNameAsString();
795
796#if LLVM_VERSION_MAJOR < 16
797 if (record_name == "(anonymous)") {
798 util::if_not_null(rec->getTypedefNameForAnonDecl(),
799 [&record_name](const clang::TypedefNameDecl *name) {
800 record_name = name->getNameAsString();
801 });
802 }
803#endif
804
805 record.set_name(record_name);
806 record.set_id(common::to_id(record.full_name(false)));
807 }
808
809 process_comment(*rec, record);
810 set_source_location(*rec, record);
811 set_owning_module(*rec, record);
812
813 const auto record_full_name = record_ptr->full_name(false);
814
815 record.is_struct(rec->isStruct());
816 record.is_union(rec->isUnion());
817
818 if (record.skip())
819 return {};
820
821 record.set_style(record.style_spec());
822
823 return record_ptr;
824}
825
827 clang::CXXRecordDecl *cls)
828{
829 assert(cls != nullptr);
830
831 if (!should_include(cls))
832 return {};
833
834 auto c_ptr{std::make_unique<class_>(config().using_namespace())};
835 auto &c = *c_ptr;
836
837 auto ns{common::get_tag_namespace(*cls)};
838
839 process_record_parent(cls, c, ns);
840
841 if (!c.is_nested()) {
842 c.set_name(common::get_tag_name(*cls));
843 c.set_namespace(ns);
844 c.set_id(common::to_id(c.full_name(false)));
845 }
846
847 c.is_struct(cls->isStruct());
848
849 process_comment(*cls, c);
850 set_source_location(*cls, c);
851 set_owning_module(*cls, c);
852
853 if (c.skip())
854 return {};
855
856 c.set_style(c.style_spec());
857
858 return c_ptr;
859}
860
862 clang::RecordDecl *cls, class_ &c, const namespace_ &ns)
863{
864 const auto *parent = cls->getParent();
865
866 std::optional<eid_t> id_opt;
867
868 auto parent_ns = ns;
869 if (parent != nullptr) {
870 const auto *parent_record_decl =
871 clang::dyn_cast<clang::RecordDecl>(parent);
872
873 if (parent_record_decl != nullptr) {
874 parent_ns = common::get_tag_namespace(*parent_record_decl);
875
876 eid_t ast_id{parent_record_decl->getID()};
877
878 // First check if the parent has been added to the diagram as
879 // regular class
880 id_opt = id_mapper().get_global_id(ast_id);
881
882 // If not, check if the parent template declaration is in the
883 // model
884 if (!id_opt) {
885 if (parent_record_decl->getDescribedTemplate() != nullptr) {
886 ast_id =
887 parent_record_decl->getDescribedTemplate()->getID();
888 id_opt = id_mapper().get_global_id(ast_id);
889 }
890 }
891 }
892 }
893
894 if (id_opt && diagram().find<class_>(*id_opt)) {
895 // Here we have 2 options, either:
896 // - the parent is a regular C++ class/struct
897 // - the parent is a class template declaration/specialization
898 auto parent_class = diagram().find<class_>(*id_opt);
899
900 c.set_namespace(parent_ns);
901 const auto cls_name = cls->getNameAsString();
902 if (cls_name.empty()) {
903 // Nested structs can be anonymous
904 if (anonymous_struct_relationships_.count(cls->getID()) > 0) {
905 const auto &[label, hint, access, destination_multiplicity] =
907
908 c.set_name(parent_class.value().name() + "##" +
909 fmt::format("({})", label));
910
911 std::string destination_multiplicity_str{};
912 if (destination_multiplicity.has_value()) {
913 destination_multiplicity_str =
914 std::to_string(*destination_multiplicity);
915 }
916
917 parent_class.value().add_relationship(
918 {hint, common::to_id(c.full_name(false)), access, label, "",
919 destination_multiplicity_str});
920 }
921 else
922 c.set_name(parent_class.value().name() + "##" +
923 fmt::format(
924 "(anonymous_{})", std::to_string(cls->getID())));
925 }
926 else {
927 c.set_name(
928 parent_class.value().name() + "##" + cls->getNameAsString());
929 }
930
931 c.set_id(common::to_id(c.full_name(false)));
932
933 if (!cls->getNameAsString().empty()) {
934 // Don't add anonymous structs as contained in the class
935 // as they are already added as aggregations
936 c.add_relationship({relationship_t::kContainment, *id_opt});
937 }
938
939 c.nested(true);
940 }
941}
942
944 const clang::CXXRecordDecl &cls, class_ &c)
945{
946 // Process class child entities
947 process_class_children(&cls, c);
948
949 // Process class bases
950 process_class_bases(&cls, c);
951
952 c.complete(true);
953}
954
956 const clang::CXXRecordDecl *cls, class_ &c)
957{
958 for (const auto &base : cls->bases()) {
959 class_parent cp;
960 auto name_and_ns = common::model::namespace_{
961 common::to_string(base.getType(), cls->getASTContext())};
962
963 cp.set_name(name_and_ns.to_string());
964
965 if (const auto *tsp =
966 base.getType()->getAs<clang::TemplateSpecializationType>();
967 tsp != nullptr) {
968 auto template_specialization_ptr =
969 std::make_unique<class_>(config().using_namespace());
971 *template_specialization_ptr, cls, *tsp, {});
972
973 cp.set_id(template_specialization_ptr->id());
974 cp.set_name(template_specialization_ptr->full_name(false));
975
976 if (diagram().should_include(*template_specialization_ptr)) {
977 add_class(std::move(template_specialization_ptr));
978 }
979 }
980 else if (const auto *record_type =
981 base.getType()->getAs<clang::RecordType>();
982 record_type != nullptr) {
983 cp.set_name(record_type->getDecl()->getQualifiedNameAsString());
984 cp.set_id(common::to_id(*record_type->getDecl()));
985 }
986 else
987 // This could be a template parameter - we don't want it here
988 continue;
989
990 cp.is_virtual(base.isVirtual());
991
992 cp.set_access(
993 common::access_specifier_to_access_t(base.getAccessSpecifier()));
994
995 LOG_DBG("Found base class {} [{}] for class {}", cp.name(), cp.id(),
996 c.name());
997
998 c.add_parent(std::move(cp));
999 }
1000}
1001
1003 const clang::RecordDecl *cls, class_ &c)
1004{
1005 // Iterate over regular class fields
1006 for (const auto *field : cls->fields()) {
1007 if (field != nullptr)
1008 process_field(*field, c);
1009 }
1010}
1011
1013 const clang::CXXRecordDecl *cls, class_ &c)
1014{
1015 assert(cls != nullptr);
1016
1017 // Iterate over class methods (both regular and static)
1018 for (const auto *method : cls->methods()) {
1019 if (method != nullptr) {
1020 process_method(*method, c);
1021 }
1022 }
1023
1024 // Iterate over class template methods
1025 if (const auto *cls_decl_context =
1026 clang::dyn_cast_or_null<clang::DeclContext>(cls);
1027 cls_decl_context != nullptr) {
1028 for (auto const *decl_iterator : cls_decl_context->decls()) {
1029 auto const *method_template =
1030 llvm::dyn_cast_or_null<clang::FunctionTemplateDecl>(
1031 decl_iterator);
1032 if (method_template == nullptr)
1033 continue;
1034
1035 process_template_method(*method_template, c);
1036 }
1037 }
1038
1039 // Iterate over regular class fields
1040 for (const auto *field : cls->fields()) {
1041 if (field != nullptr)
1042 process_field(*field, c);
1043 }
1044
1045 // Static fields have to be processed by iterating over variable
1046 // declarations
1047 for (const auto *decl : cls->decls()) {
1048 if (decl->getKind() == clang::Decl::Var) {
1049 const clang::VarDecl *variable_declaration{
1050 clang::dyn_cast_or_null<clang::VarDecl>(decl)};
1051 if ((variable_declaration != nullptr) &&
1052 variable_declaration->isStaticDataMember()) {
1053 process_static_field(*variable_declaration, c);
1054 }
1055 }
1056 else if (decl->getKind() == clang::Decl::Enum) {
1057 const auto *enum_decl =
1058 clang::dyn_cast_or_null<clang::EnumDecl>(decl);
1059 if (enum_decl == nullptr)
1060 continue;
1061
1062 if (enum_decl->getNameAsString().empty()) {
1063 for (const auto *enum_const : enum_decl->enumerators()) {
1065 enum_decl->getAccess()),
1066 enum_const->getNameAsString(), "enum"};
1067 c.add_member(std::move(m));
1068 }
1069 }
1070 }
1071 }
1072
1073 if (cls->isCompleteDefinition())
1074 for (const auto *friend_declaration : cls->friends()) {
1075 if (friend_declaration != nullptr)
1076 process_friend(*friend_declaration, c);
1077 }
1078}
1079
1081 const clang::FriendDecl &f, class_ &c)
1082{
1083 if (const auto *friend_type_info = f.getFriendType()) {
1084 const auto friend_type = friend_type_info->getType();
1085 if (friend_type->getAs<clang::TemplateSpecializationType>() !=
1086 nullptr) {
1087 // TODO: handle template friend
1088 }
1089 else if (friend_type->getAs<clang::RecordType>() != nullptr) {
1090 if (should_include(friend_type->getAsRecordDecl())) {
1091 relationship r{relationship_t::kFriendship,
1092 common::to_id(*friend_type->getAsRecordDecl()),
1094 "<<friend>>"};
1095
1096 c.add_relationship(std::move(r));
1097 }
1098 }
1099 }
1100}
1101
1103 const clang::CXXMethodDecl &mf, class_ &c)
1104{
1105 // TODO: For now skip implicitly default methods
1106 // in the future, add config option to choose
1107 if (mf.isDefaulted() && !mf.isExplicitlyDefaulted())
1108 return;
1109
1110 auto method_return_type =
1111 common::to_string(mf.getReturnType(), mf.getASTContext());
1112
1113 common::ensure_lambda_type_is_relative(config(), method_return_type);
1114
1115 auto method_name = mf.getNameAsString();
1116 if (mf.isTemplated()) {
1117 // Sometimes in template specializations method names contain the
1118 // template parameters for some reason - drop them
1119 // Is there a better way to do this?
1120 method_name = method_name.substr(0, method_name.find('<'));
1121 }
1122
1124 util::trim(method_name),
1125 config().simplify_template_type(method_return_type)};
1126
1127 process_method_properties(mf, c, method_name, method);
1128
1129 process_comment(mf, method);
1130
1131 // Register the source location of the field declaration
1132 set_source_location(mf, method);
1133
1134 if (method.skip())
1135 return;
1136
1137 for (const auto *param : mf.parameters()) {
1138 if (param != nullptr)
1139 process_function_parameter(*param, method, c);
1140 }
1141
1142 // find relationship for return type
1143 found_relationships_t relationships;
1144
1145 // Move dereferencing to build() method of template_builder
1146 if (const auto *templ = mf.getReturnType()
1147 .getNonReferenceType()
1148 .getUnqualifiedType()
1149 ->getAs<clang::TemplateSpecializationType>();
1150 templ != nullptr) {
1151 const auto *unaliased_type = templ;
1152 if (unaliased_type->isTypeAlias())
1153 unaliased_type = unaliased_type->getAliasedType()
1154 ->getAs<clang::TemplateSpecializationType>();
1155
1156 if (unaliased_type != nullptr) {
1157 auto template_specialization_ptr =
1158 std::make_unique<class_>(config().using_namespace());
1160 *template_specialization_ptr,
1161 unaliased_type->getTemplateName().getAsTemplateDecl(),
1162 *unaliased_type, &c);
1163
1164 if (diagram().should_include(*template_specialization_ptr)) {
1165 relationships.emplace_back(template_specialization_ptr->id(),
1166 relationship_t::kDependency);
1167
1168 add_class(std::move(template_specialization_ptr));
1169 }
1170 }
1171 }
1172
1174 mf.getReturnType(), relationships, relationship_t::kDependency);
1175
1176 for (const auto &[type_element_id, relationship_type] : relationships) {
1177 if (type_element_id != c.id() &&
1178 (relationship_type != relationship_t::kNone)) {
1179 relationship r{relationship_t::kDependency, type_element_id};
1180
1181 LOG_DBG("Adding method return type relationship from {}::{} to "
1182 "{}: {}",
1183 c.full_name(), mf.getNameAsString(),
1184 clanguml::common::model::to_string(r.type()), r.label());
1185
1186 c.add_relationship(std::move(r));
1187 }
1188 }
1189
1190 // Also consider the container itself if it is a template
1191 // instantiation it's arguments could count as reference to relevant
1192 // types
1193 auto underlying_type = mf.getReturnType();
1194 if (underlying_type->isReferenceType())
1195 underlying_type = underlying_type.getNonReferenceType();
1196 if (underlying_type->isPointerType())
1197 underlying_type = underlying_type->getPointeeType();
1198
1199 if (const auto *atsp = underlying_type->getAs<clang::AutoType>();
1200 atsp != nullptr) {
1202 }
1203
1204 method.update(config().using_namespace());
1205
1206 if (diagram().should_include(method)) {
1207 LOG_DBG("Adding method: {}", method.name());
1208
1209 c.add_method(std::move(method));
1210 }
1211}
1212
1214 const clang::CXXMethodDecl &mf, const class_ &c,
1215 const std::string &method_name, class_method &method) const
1216{
1217 const bool is_constructor = c.name() == method_name;
1218 const bool is_destructor = fmt::format("~{}", c.name()) == method_name;
1219
1220#if LLVM_VERSION_MAJOR > 17
1221 method.is_pure_virtual(mf.isPureVirtual());
1222#else
1223 method.is_pure_virtual(mf.isPure());
1224#endif
1225 method.is_virtual(mf.isVirtual());
1226 method.is_const(mf.isConst());
1227 method.is_defaulted(mf.isDefaulted());
1228 method.is_deleted(mf.isDeleted());
1229 method.is_static(mf.isStatic());
1230 method.is_operator(mf.isOverloadedOperator());
1231 method.is_constexpr(mf.isConstexprSpecified() && !is_constructor);
1232 method.is_consteval(mf.isConsteval());
1233 method.is_constructor(is_constructor);
1234 method.is_destructor(is_destructor);
1235 method.is_move_assignment(mf.isMoveAssignmentOperator());
1236 method.is_copy_assignment(mf.isCopyAssignmentOperator());
1237 method.is_noexcept(isNoexceptExceptionSpec(mf.getExceptionSpecType()));
1238 method.is_coroutine(common::is_coroutine(mf));
1239}
1240
1243 class_ &c, const clang::AutoType *atsp)
1244{
1245 auto desugared_atsp = atsp->getDeducedType();
1246
1247 if (atsp->isSugared()) {
1248 const auto *deduced_type =
1249 atsp->desugar()->getAs<clang::DeducedTemplateSpecializationType>();
1250
1251 if (deduced_type != nullptr)
1252 desugared_atsp = deduced_type->getDeducedType();
1253 }
1254
1255 if (desugared_atsp.isNull())
1256 return;
1257
1258 const auto *deduced_record_type = desugared_atsp->isRecordType()
1259 ? desugared_atsp->getAs<clang::RecordType>()
1260 : nullptr;
1261
1262 if (deduced_record_type != nullptr) {
1263 if (auto *deduced_auto_decl =
1264 llvm::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(
1265 deduced_record_type->getDecl());
1266 deduced_auto_decl != nullptr) {
1267
1268 const auto diagram_class_count_before_visit =
1269 diagram().classes().size();
1270
1271 VisitClassTemplateSpecializationDecl(deduced_auto_decl);
1272
1273 const bool visitor_added_new_template_specialization =
1274 (diagram().classes().size() -
1275 diagram_class_count_before_visit) > 0;
1276
1277 if (visitor_added_new_template_specialization) {
1278 const auto &template_specialization_model =
1279 diagram().classes().back();
1280
1281 if (should_include(deduced_auto_decl)) {
1282 relationship r{relationship_t::kDependency,
1283 template_specialization_model.get().id()};
1284
1285 c.add_relationship(std::move(r));
1286 }
1287 }
1288 }
1289 }
1290}
1291
1293 const clang::FunctionTemplateDecl &mf, class_ &c)
1294{
1295 // TODO: For now skip implicitly default methods
1296 // in the future, add config option to choose
1297 if (mf.getTemplatedDecl()->isDefaulted() &&
1298 !mf.getTemplatedDecl()->isExplicitlyDefaulted())
1299 return;
1300
1302 util::trim(mf.getNameAsString()),
1303 mf.getTemplatedDecl()->getReturnType().getAsString()};
1304
1305 auto method_name = mf.getNameAsString();
1306 if (mf.isTemplated()) {
1307 // Sometimes in template specializations method names contain the
1308 // template parameters for some reason - drop them
1309 // Is there a better way to do this?
1310 method_name = method_name.substr(0, method_name.find('<'));
1311 }
1313 clang::dyn_cast<clang::CXXMethodDecl>(mf.getTemplatedDecl()),
1314 [&](const auto *decl) {
1315 process_method_properties(*decl, c, method_name, method);
1316 });
1317
1319
1320 process_comment(mf, method);
1321
1322 if (method.skip())
1323 return;
1324
1325 for (const auto *param : mf.getTemplatedDecl()->parameters()) {
1326 if (param != nullptr)
1327 process_function_parameter(*param, method, c);
1328 }
1329
1330 method.update(config().using_namespace());
1331
1332 if (diagram().should_include(method)) {
1333 LOG_DBG("Adding method: {}", method.name());
1334
1335 c.add_method(std::move(method));
1336 }
1337}
1338
1339bool translation_unit_visitor::find_relationships(const clang::QualType &type,
1340 found_relationships_t &relationships,
1342{
1343 bool result{false};
1344
1345 if (type->isPointerType()) {
1346 relationship_hint = relationship_t::kAssociation;
1348 type->getPointeeType(), relationships, relationship_hint);
1349 }
1350 else if (type->isRValueReferenceType()) {
1351 relationship_hint = relationship_t::kAggregation;
1353 type.getNonReferenceType(), relationships, relationship_hint);
1354 }
1355 else if (type->isLValueReferenceType()) {
1356 relationship_hint = relationship_t::kAssociation;
1358 type.getNonReferenceType(), relationships, relationship_hint);
1359 }
1360 else if (type->isArrayType()) {
1361 find_relationships(type->getAsArrayTypeUnsafe()->getElementType(),
1362 relationships, relationship_t::kAggregation);
1363 }
1364 else if (type->isEnumeralType()) {
1365 if (const auto *enum_type = type->getAs<clang::EnumType>();
1366 enum_type != nullptr) {
1367 // Use AST's local ID here for relationship target, as we can't
1368 // calculate here properly the ID for nested enums. It will be
1369 // resolved properly in finalize().
1370 relationships.emplace_back(
1371 enum_type->getDecl()->getID(), relationship_hint);
1372 }
1373 }
1374 else if (type->isRecordType()) {
1375 const auto *type_instantiation_decl =
1376 type->getAs<clang::TemplateSpecializationType>();
1377
1378 if (type_instantiation_decl != nullptr) {
1379 // If this template should be included in the diagram
1380 // add it - and then process recursively its arguments
1381 if (should_include(type_instantiation_decl->getTemplateName()
1382 .getAsTemplateDecl())) {
1383 relationships.emplace_back(
1384 type_instantiation_decl->getTemplateName()
1385 .getAsTemplateDecl()
1386 ->getID(),
1387 relationship_hint);
1388 }
1389 for (const auto &template_argument :
1390 type_instantiation_decl->template_arguments()) {
1391 const auto template_argument_kind = template_argument.getKind();
1392 if (template_argument_kind ==
1393 clang::TemplateArgument::ArgKind::Integral) {
1394 // pass
1395 }
1396 else if (template_argument_kind ==
1397 clang::TemplateArgument::ArgKind::Null) {
1398 // pass
1399 }
1400 else if (template_argument_kind ==
1401 clang::TemplateArgument::ArgKind::Expression) {
1402 // pass
1403 }
1404 else if (template_argument.getKind() ==
1405 clang::TemplateArgument::ArgKind::NullPtr) {
1406 // pass
1407 }
1408 else if (template_argument_kind ==
1409 clang::TemplateArgument::ArgKind::Template) {
1410 // pass
1411 }
1412 else if (template_argument_kind ==
1413 clang::TemplateArgument::ArgKind::TemplateExpansion) {
1414 // pass
1415 }
1416 else if (const auto *function_type =
1417 template_argument.getAsType()
1418 ->getAs<clang::FunctionProtoType>();
1419 function_type != nullptr) {
1420 for (const auto &param_type :
1421 function_type->param_types()) {
1422 result = find_relationships(param_type, relationships,
1423 relationship_t::kDependency);
1424 }
1425 }
1426 else if (template_argument_kind ==
1427 clang::TemplateArgument::ArgKind::Type) {
1428 result = find_relationships(template_argument.getAsType(),
1429 relationships, relationship_hint);
1430 }
1431 }
1432 }
1433 else if (type->getAsCXXRecordDecl() != nullptr) {
1434 relationships.emplace_back(
1435 type->getAsCXXRecordDecl()->getID(), relationship_hint);
1436 result = true;
1437 }
1438 else {
1439 relationships.emplace_back(
1440 type->getAsRecordDecl()->getID(), relationship_hint);
1441 result = true;
1442 }
1443 }
1444 else if (const auto *template_specialization_type =
1445 type->getAs<clang::TemplateSpecializationType>();
1446 template_specialization_type != nullptr) {
1447 if (should_include(template_specialization_type->getTemplateName()
1448 .getAsTemplateDecl())) {
1449 relationships.emplace_back(
1450 template_specialization_type->getTemplateName()
1451 .getAsTemplateDecl()
1452 ->getID(),
1453 relationship_hint);
1454 }
1455 for (const auto &template_argument :
1456 template_specialization_type->template_arguments()) {
1457 const auto template_argument_kind = template_argument.getKind();
1458 if (template_argument_kind ==
1459 clang::TemplateArgument::ArgKind::Integral) {
1460 // pass
1461 }
1462 else if (template_argument_kind ==
1463 clang::TemplateArgument::ArgKind::Null) {
1464 // pass
1465 }
1466 else if (template_argument_kind ==
1467 clang::TemplateArgument::ArgKind::Expression) {
1468 // pass
1469 }
1470 else if (template_argument.getKind() ==
1471 clang::TemplateArgument::ArgKind::NullPtr) {
1472 // pass
1473 }
1474 else if (template_argument_kind ==
1475 clang::TemplateArgument::ArgKind::Template) {
1476 // pass
1477 }
1478 else if (template_argument_kind ==
1479 clang::TemplateArgument::ArgKind::TemplateExpansion) {
1480 // pass
1481 }
1482 else if (const auto *function_type =
1483 template_argument.getAsType()
1484 ->getAs<clang::FunctionProtoType>();
1485 function_type != nullptr) {
1486 for (const auto &param_type : function_type->param_types()) {
1487 result = find_relationships(
1488 param_type, relationships, relationship_t::kDependency);
1489 }
1490 }
1491 else if (template_argument_kind ==
1492 clang::TemplateArgument::ArgKind::Type) {
1493 result = find_relationships(template_argument.getAsType(),
1494 relationships, relationship_hint);
1495 }
1496 }
1497 }
1498
1499 return result;
1500}
1501
1503 const clang::ParmVarDecl &p, class_method &method, class_ &c,
1504 const std::set<std::string> & /*template_parameter_names*/)
1505{
1506 method_parameter parameter;
1507 parameter.set_name(p.getNameAsString());
1508
1509 process_comment(p, parameter);
1510
1511 if (parameter.skip())
1512 return;
1513
1514 auto parameter_type = common::to_string(p.getType(), p.getASTContext());
1515
1516 // Is there no better way to determine that 'type' is a lambda?
1518
1519 parameter.set_type(parameter_type);
1520
1521 if (p.hasDefaultArg()) {
1522 const auto *default_arg = p.getDefaultArg();
1523 if (default_arg != nullptr) {
1524 auto default_arg_str = common::get_source_text(
1525 default_arg->getSourceRange(), source_manager());
1526 parameter.set_default_value(default_arg_str);
1527 }
1528 }
1529
1530 if (!parameter.skip_relationship()) {
1531 // find relationship for the type
1532 found_relationships_t relationships;
1533
1534 LOG_DBG("Looking for relationships in type: {}",
1535 common::to_string(p.getType(), p.getASTContext()));
1536
1537 if (const auto *templ =
1538 p.getType()
1539 .getNonReferenceType()
1540 .getUnqualifiedType()
1541 ->getAs<clang::TemplateSpecializationType>();
1542 templ != nullptr) {
1543 auto template_specialization_ptr =
1544 std::make_unique<class_>(config().using_namespace());
1546 *template_specialization_ptr,
1547 templ->getTemplateName().getAsTemplateDecl(), *templ, &c);
1548
1549 if (diagram().should_include(*template_specialization_ptr)) {
1550 relationships.emplace_back(template_specialization_ptr->id(),
1551 relationship_t::kDependency);
1552
1553 add_class(std::move(template_specialization_ptr));
1554 }
1555 }
1556
1558 p.getType(), relationships, relationship_t::kDependency);
1559
1560 for (const auto &[type_element_id, relationship_type] : relationships) {
1561 if (type_element_id != c.id() &&
1562 (relationship_type != relationship_t::kNone)) {
1563 relationship r{relationship_t::kDependency, type_element_id};
1564
1565 LOG_DBG("Adding function parameter relationship from {} to "
1566 "{}: {}",
1568 r.label());
1569
1570 c.add_relationship(std::move(r));
1571 }
1572 }
1573 }
1574
1575 method.add_parameter(std::move(parameter));
1576}
1577
1579 const class_member &field, const found_relationships_t &relationships,
1580 bool break_on_first_aggregation)
1581{
1582 auto [decorator_rtype, decorator_rmult] = field.get_relationship();
1583
1584 for (const auto &[target, relationship_type] : relationships) {
1585 if (relationship_type != relationship_t::kNone) {
1586 relationship r{relationship_type, target};
1587 r.set_label(field.name());
1588 r.set_access(field.access());
1589 bool mulitplicity_provided_in_comment{false};
1590 if (decorator_rtype != relationship_t::kNone) {
1591 r.set_type(decorator_rtype);
1592 auto mult = util::split(decorator_rmult, ":", false);
1593 if (mult.size() == 2) {
1594 mulitplicity_provided_in_comment = true;
1595 r.set_multiplicity_source(mult[0]);
1596 r.set_multiplicity_destination(mult[1]);
1597 }
1598 }
1599 if (!mulitplicity_provided_in_comment &&
1600 field.destination_multiplicity().has_value()) {
1601 r.set_multiplicity_destination(
1602 std::to_string(*field.destination_multiplicity()));
1603 }
1604
1605 r.set_style(field.style_spec());
1606
1607 LOG_DBG("Adding relationship from {} to {} with label {}",
1608 c.full_name(false), r.destination(),
1609 clanguml::common::model::to_string(r.type()), r.label());
1610
1611 c.add_relationship(std::move(r));
1612
1613 if (break_on_first_aggregation &&
1614 relationship_type == relationship_t::kAggregation)
1615 break;
1616 }
1617 }
1618}
1619
1621 const clang::VarDecl &field_declaration, class_ &c)
1622{
1623 const auto field_type = field_declaration.getType();
1624 auto type_name =
1625 common::to_string(field_type, field_declaration.getASTContext());
1626 if (type_name.empty())
1627 type_name = "<<anonymous>>";
1628
1629 class_member field{
1630 common::access_specifier_to_access_t(field_declaration.getAccess()),
1631 field_declaration.getNameAsString(),
1632 config().simplify_template_type(type_name)};
1633
1634 field.is_static(true);
1635
1636 process_comment(field_declaration, field);
1637 set_source_location(field_declaration, field);
1638
1639 if (field.skip())
1640 return;
1641
1642 if (!field.skip_relationship()) {
1643 found_relationships_t relationships;
1644
1645 // find relationship for the type
1646 find_relationships(field_declaration.getType(), relationships,
1647 relationship_t::kAssociation);
1648
1649 add_relationships(c, field, relationships);
1650 }
1651
1652 c.add_member(std::move(field));
1653}
1654
1655std::unique_ptr<class_>
1657 clang::ClassTemplateSpecializationDecl *cls)
1658{
1659 auto c_ptr = std::make_unique<class_>(config().using_namespace());
1661
1662 auto &template_instantiation = *c_ptr;
1663 template_instantiation.is_template(true);
1664
1665 // TODO: refactor to method get_qualified_name()
1666 auto qualified_name = cls->getQualifiedNameAsString();
1667 util::replace_all(qualified_name, "(anonymous namespace)", "");
1668 util::replace_all(qualified_name, "::::", "::");
1669
1670 namespace_ ns{qualified_name};
1671 ns.pop_back();
1672 template_instantiation.set_name(cls->getNameAsString());
1673 template_instantiation.set_namespace(ns);
1674
1675 template_instantiation.is_struct(cls->isStruct());
1676
1677 process_record_parent(cls, template_instantiation, ns);
1678
1679 if (!template_instantiation.is_nested()) {
1680 template_instantiation.set_name(common::get_tag_name(*cls));
1681 template_instantiation.set_id(
1682 common::to_id(template_instantiation.full_name(false)));
1683 }
1684
1685 process_comment(*cls, template_instantiation);
1686 set_source_location(*cls, template_instantiation);
1687 set_owning_module(*cls, template_instantiation);
1688
1689 if (template_instantiation.skip())
1690 return {};
1691
1692 id_mapper().add(cls->getID(), template_instantiation.id());
1693
1694 return c_ptr;
1695}
1696
1698 const clang::FieldDecl &field_declaration, class_ &c)
1699{
1700 LOG_DBG(
1701 "== Visiting record member {}", field_declaration.getNameAsString());
1702
1703 // Default hint for relationship is aggregation
1704 auto relationship_hint = relationship_t::kAggregation;
1705 // If the first type of the template instantiation of this field type
1706 // has been added as aggregation relationship with class 'c', don't
1707 // add it's nested template types as aggregation
1708 [[maybe_unused]] bool template_instantiation_added_as_aggregation{false};
1709 // The actual field type
1710 auto field_type = field_declaration.getType();
1711 // String representation of the field type
1712 auto type_name =
1713 common::to_string(field_type, field_declaration.getASTContext());
1714 // The field name
1715 const auto field_name = field_declaration.getNameAsString();
1716
1717 auto field_type_str =
1718 common::to_string(field_type, field_declaration.getASTContext(), false);
1719
1721
1722 class_member field{
1723 common::access_specifier_to_access_t(field_declaration.getAccess()),
1724 field_name, config().simplify_template_type(field_type_str)};
1725
1726 // Parse the field comment
1727 process_comment(field_declaration, field);
1728 // Register the source location of the field declaration
1729 set_source_location(field_declaration, field);
1730
1731 // If the comment contains a skip directive, just return
1732 if (field.skip())
1733 return;
1734
1735 if (field_type->isPointerType()) {
1736 relationship_hint = relationship_t::kAssociation;
1737 field_type = field_type->getPointeeType();
1738 }
1739 else if (field_type->isLValueReferenceType()) {
1740 relationship_hint = relationship_t::kAssociation;
1741 field_type = field_type.getNonReferenceType();
1742 }
1743 else if (field_type->isArrayType()) {
1744 relationship_hint = relationship_t::kAggregation;
1745 while (field_type->isArrayType()) {
1746 auto current_multiplicity = field.destination_multiplicity();
1747 if (!current_multiplicity)
1748 field.set_destination_multiplicity(common::get_array_size(
1749 *field_type->getAsArrayTypeUnsafe()));
1750 else {
1751 auto maybe_array_size =
1752 common::get_array_size(*field_type->getAsArrayTypeUnsafe());
1753 if (maybe_array_size.has_value()) {
1754 field.set_destination_multiplicity(
1755 current_multiplicity.value() *
1756 maybe_array_size.value());
1757 }
1758 }
1759
1760 field_type = field_type->getAsArrayTypeUnsafe()->getElementType();
1761 }
1762 }
1763 else if (field_type->isRValueReferenceType()) {
1764 field_type = field_type.getNonReferenceType();
1765 }
1766
1767 if (type_name.find("std::shared_ptr") == 0)
1768 relationship_hint = relationship_t::kAssociation;
1769 if (type_name.find("std::weak_ptr") == 0)
1770 relationship_hint = relationship_t::kAssociation;
1771
1772 found_relationships_t relationships;
1773
1774 const auto *template_field_type =
1775 field_type->getAs<clang::TemplateSpecializationType>();
1776 // TODO: Refactor to an unalias_type() method
1777 if (template_field_type != nullptr)
1778 if (template_field_type->isTypeAlias())
1779 template_field_type =
1780 template_field_type->getAliasedType()
1781 ->getAs<clang::TemplateSpecializationType>();
1782
1783 bool field_type_is_template_template_parameter{false};
1784 if (template_field_type != nullptr) {
1785 // Skip types which are template template parameters of the parent
1786 // template
1787 for (const auto &class_template_param : c.template_params()) {
1788 if (class_template_param.name() ==
1789 template_field_type->getTemplateName()
1790 .getAsTemplateDecl()
1791 ->getNameAsString() +
1792 "<>") {
1793 field_type_is_template_template_parameter = true;
1794 }
1795 }
1796 }
1797
1798 // Process the type which is template instantiation of some sort
1799 if (template_field_type != nullptr &&
1800 !field_type_is_template_template_parameter) {
1801 // Build the template instantiation for the field type
1802 auto template_specialization_ptr =
1803 std::make_unique<class_>(config().using_namespace());
1805 *template_specialization_ptr,
1806 field_type->getAs<clang::TemplateSpecializationType>()
1807 ->getTemplateName()
1808 .getAsTemplateDecl(),
1809 *template_field_type, {&c});
1810
1811 if (!field.skip_relationship() && template_specialization_ptr) {
1812 const auto &template_specialization = *template_specialization_ptr;
1813
1814 // Check if this template instantiation should be added to the
1815 // current diagram. Even if the top level template type for
1816 // this instantiation should not be part of the diagram, e.g.
1817 // it's a std::vector<>, it's nested types might be added
1818 bool add_template_instantiation_to_diagram{false};
1819 if (diagram().should_include(
1820 template_specialization.get_namespace())) {
1821
1822 found_relationships_t::value_type r{
1823 template_specialization.id(), relationship_hint};
1824
1825 add_template_instantiation_to_diagram = true;
1826
1827 // If the template instantiation for the build type has been
1828 // added as aggregation, skip its nested templates
1829 template_instantiation_added_as_aggregation =
1830 relationship_hint == relationship_t::kAggregation;
1831 relationships.emplace_back(std::move(r));
1832 }
1833
1834 // Try to find relationships to types nested in the template
1835 // instantiation
1836 found_relationships_t nested_relationships;
1837 if (!template_instantiation_added_as_aggregation) {
1838 for (const auto &template_argument :
1839 template_specialization.template_params()) {
1840
1841 LOG_DBG("Looking for nested relationships from {}::{} in "
1842 "template argument {}",
1843 c.full_name(false), field_name,
1844 template_argument.to_string(
1845 config().using_namespace(), false));
1846
1847 template_instantiation_added_as_aggregation =
1848 template_argument.find_nested_relationships(
1849 nested_relationships, relationship_hint,
1850 [&d = diagram()](const std::string &full_name) {
1851 if (full_name.empty())
1852 return false;
1853 auto [ns, name] = common::split_ns(full_name);
1854 return d.should_include(ns, name);
1855 });
1856 }
1857
1858 // Add any relationships to the class 'c' to the diagram,
1859 // unless the top level type has been added as aggregation
1860 add_relationships(c, field, nested_relationships,
1861 /* break on first aggregation */ false);
1862 }
1863
1864 // Add the template instantiation object to the diagram if it
1865 // matches the include pattern
1866 if (add_template_instantiation_to_diagram)
1867 add_class(std::move(template_specialization_ptr));
1868 }
1869 }
1870
1871 if (!field.skip_relationship()) {
1872 // Find relationship for the type if the type has not been added
1873 // as aggregation
1874 if (!template_instantiation_added_as_aggregation) {
1875 if ((field_type->getAsRecordDecl() != nullptr) &&
1876 field_type->getAsRecordDecl()->getNameAsString().empty()) {
1877 // Relationships to fields whose type is an anonymous nested
1878 // struct have to be handled separately here
1879 anonymous_struct_relationships_[field_type->getAsRecordDecl()
1880 ->getID()] =
1881 std::make_tuple(field.name(), relationship_hint,
1882 field.access(), field.destination_multiplicity());
1883 }
1884 else
1886 field_type, relationships, relationship_hint);
1887 }
1888
1889 add_relationships(c, field, relationships);
1890 }
1891
1892 // If this is an anonymous struct - replace the anonymous_XYZ part with
1893 // field name
1894 if ((field_type->getAsRecordDecl() != nullptr) &&
1895 field_type->getAsRecordDecl()->getNameAsString().empty()) {
1896 if (util::contains(field.type(), "(anonymous_")) {
1897 std::regex anonymous_re("anonymous_(\\d*)");
1898 field.set_type(
1899 std::regex_replace(field.type(), anonymous_re, field_name));
1900 }
1901 }
1902
1903 c.add_member(std::move(field));
1904}
1905
1907{
1908 for (auto &[id, c] : forward_declarations_) {
1909 if (diagram().should_include(c->get_namespace())) {
1910 add_class(std::move(c));
1911 }
1912 }
1913 forward_declarations_.clear();
1914}
1915
1917{
1918 // TODO: Refactor to a map with relationships attached to references
1919 // to elements
1920 for (const auto &cls : diagram().classes()) {
1921 for (auto &rel : cls.get().relationships()) {
1922 if (!rel.destination().is_global()) {
1923 const auto maybe_id =
1924 id_mapper().get_global_id(rel.destination());
1925 if (maybe_id) {
1926 LOG_DBG("= Resolved instantiation destination from local "
1927 "id {} to global id {}",
1928 rel.destination(), *maybe_id);
1929 rel.set_destination(*maybe_id);
1930 }
1931 }
1932 }
1933 }
1934 for (const auto &cpt : diagram().concepts()) {
1935 for (auto &rel : cpt.get().relationships()) {
1936 if (!rel.destination().is_global()) {
1937 const auto maybe_id =
1938 id_mapper().get_global_id(rel.destination());
1939 if (maybe_id) {
1940 LOG_DBG("= Resolved instantiation destination from local "
1941 "id {} to global id {}",
1942 rel.destination(), *maybe_id);
1943 rel.set_destination(*maybe_id);
1944 }
1945 }
1946 }
1947 }
1948 for (const auto &enm : diagram().enums()) {
1949 for (auto &rel : enm.get().relationships()) {
1950 if (!rel.destination().is_global()) {
1951 const auto maybe_id =
1952 id_mapper().get_global_id(rel.destination());
1953 if (maybe_id) {
1954 LOG_DBG("= Resolved instantiation destination from local "
1955 "id {} to global id {}",
1956 rel.destination(), *maybe_id);
1957 rel.set_destination(*maybe_id);
1958 }
1959 }
1960 }
1961 }
1962}
1963
1965{
1968 if (config().skip_redundant_dependencies()) {
1969 diagram().remove_redundant_dependencies();
1970 }
1971}
1972
1974 const clang::ConceptSpecializationExpr *concept_specialization,
1975 const clang::ConceptDecl *cpt,
1976 std::vector<std::string> &constrained_template_params,
1977 size_t argument_index, std::string &type_name) const
1978{
1979 const auto full_declaration_text = common::get_source_text_raw(
1980 concept_specialization->getSourceRange(), source_manager());
1981
1982 if (!full_declaration_text.empty()) {
1983 // Handle typename constraint in requires clause
1984 if (type_name.find("type-parameter-") == 0) {
1985 const auto concept_declaration_text = full_declaration_text.substr(
1986 full_declaration_text.find(cpt->getNameAsString()) +
1987 cpt->getNameAsString().size() + 1);
1988
1989 auto template_params = common::parse_unexposed_template_params(
1990 concept_declaration_text, [](const auto &t) { return t; });
1991
1992 if (template_params.size() > argument_index)
1993 type_name = template_params[argument_index].to_string(
1994 config().using_namespace(), false);
1995 }
1996 constrained_template_params.push_back(type_name);
1997 }
1998}
1999
2001 std::string qualified_name)
2002{
2003 processed_template_qualified_names_.emplace(std::move(qualified_name));
2004}
2005
2007 const std::string &qualified_name) const
2008{
2010}
2011
2013 std::unique_ptr<common::model::template_element> element)
2014{
2015 add_class(util::unique_pointer_cast<class_>(std::move(element)));
2016}
2017
2018void translation_unit_visitor::add_class(std::unique_ptr<class_> &&c)
2019{
2020 if ((config().generate_packages() &&
2021 config().package_type() == config::package_type_t::kDirectory)) {
2022 assert(!c->file().empty());
2023
2024 const auto file = config().make_path_relative(c->file());
2025
2028 p.pop_back();
2029
2030 diagram().add(p, std::move(c));
2031 }
2032 else if ((config().generate_packages() &&
2033 config().package_type() == config::package_type_t::kModule)) {
2034
2035 const auto module_path = config().make_module_relative(c->module());
2036
2038
2039 diagram().add(p, std::move(c));
2040 }
2041 else {
2042 diagram().add(c->path(), std::move(c));
2043 }
2044}
2045
2046void translation_unit_visitor::add_enum(std::unique_ptr<enum_> &&e)
2047{
2048 if ((config().generate_packages() &&
2049 config().package_type() == config::package_type_t::kDirectory)) {
2050 assert(!e->file().empty());
2051
2052 const auto file = config().make_path_relative(e->file());
2053
2056 p.pop_back();
2057
2058 diagram().add(p, std::move(e));
2059 }
2060 else if ((config().generate_packages() &&
2061 config().package_type() == config::package_type_t::kModule)) {
2062
2063 const auto module_path = config().make_module_relative(e->module());
2064
2066
2067 diagram().add(p, std::move(e));
2068 }
2069 else {
2070 diagram().add(e->path(), std::move(e));
2071 }
2072}
2073
2074void translation_unit_visitor::add_concept(std::unique_ptr<concept_> &&c)
2075{
2076 if ((config().generate_packages() &&
2077 config().package_type() == config::package_type_t::kDirectory)) {
2078 assert(!c->file().empty());
2079
2080 const auto file = config().make_path_relative(c->file());
2081
2084 p.pop_back();
2085
2086 diagram().add(p, std::move(c));
2087 }
2088 else if ((config().generate_packages() &&
2089 config().package_type() == config::package_type_t::kModule)) {
2090
2091 const auto module_path = config().make_module_relative(c->module());
2092
2094
2095 diagram().add(p, std::move(c));
2096 }
2097 else {
2098 diagram().add(c->path(), std::move(c));
2099 }
2100}
2101
2103 common::model::template_element &template_instantiation_base,
2104 const std::string &full_name, eid_t templated_decl_id)
2105{
2106 auto &template_instantiation = dynamic_cast<class_diagram::model::class_ &>(
2107 template_instantiation_base);
2108
2109 // First try to find the best match for this template in partially
2110 // specialized templates
2111 std::string destination{};
2112 std::string best_match_full_name{};
2113 auto full_template_name = template_instantiation.full_name(false);
2114 int best_match{};
2115 eid_t best_match_id{};
2116
2117 for (const auto templ : diagram().classes()) {
2118 if (templ.get() == template_instantiation)
2119 continue;
2120
2121 auto c_full_name = templ.get().full_name(false);
2122 auto match =
2123 template_instantiation.calculate_template_specialization_match(
2124 templ.get());
2125
2126 if (match > best_match) {
2127 best_match = match;
2128 best_match_full_name = c_full_name;
2129 best_match_id = templ.get().id();
2130 }
2131 }
2132
2133 auto templated_decl_global_id =
2134 id_mapper().get_global_id(templated_decl_id).value_or(eid_t{});
2135
2136 if (best_match_id.value() > 0) {
2137 destination = best_match_full_name;
2138 template_instantiation.add_relationship(
2140 template_instantiation.template_specialization_found(true);
2141 }
2142 // If we can't find optimal match for parent template specialization,
2143 // just use whatever clang suggests
2144 else if (diagram().has_element(templated_decl_global_id)) {
2145 template_instantiation.add_relationship(
2147 templated_decl_global_id});
2148 template_instantiation.template_specialization_found(true);
2149 }
2150 else if (diagram().should_include(common::model::namespace_{full_name})) {
2151 LOG_DBG("Skipping instantiation relationship from {} to {}",
2152 template_instantiation.full_name(false), templated_decl_global_id);
2153 }
2154 else {
2155 LOG_DBG("== Cannot determine global id for specialization template {} "
2156 "- delaying until the translation unit is complete ",
2157 templated_decl_global_id);
2158
2159 template_instantiation.add_relationship(
2161 }
2162}
2163
2164} // namespace clanguml::class_diagram::visitor