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/sequence_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
21#include "common/clang_utils.h"
24
26
30 : visitor_specialization_t{sm, diagram, config}
31 , template_builder_{diagram, config, *this}
32{
33}
34
35std::unique_ptr<sequence_diagram::model::class_>
37 const clang::NamedDecl * /*decl*/) const
38{
39 return std::make_unique<sequence_diagram::model::class_>(
40 config().using_namespace());
41}
42
44{
45 return true;
46}
47
49{
51}
52
54{
56}
57
59 clang::CXXRecordDecl *declaration)
60{
61 if (!should_include(declaration))
62 return true;
63
64 // Skip this class if it's parent template is already in the model
65 if (declaration->isTemplated() &&
66 declaration->getDescribedTemplate() != nullptr) {
67 if (get_unique_id(eid_t{declaration->getDescribedTemplate()->getID()}))
68 return true;
69 }
70
71 // TODO: Add support for classes defined in function/method bodies
72 if (declaration->isLocalClass() != nullptr)
73 return true;
74
75 LOG_TRACE("Visiting class declaration at {}",
76 declaration->getBeginLoc().printToString(source_manager()));
77
78 // Build the class declaration and store it in the diagram, even
79 // if we don't need it for any of the participants of this diagram
80 auto class_model_ptr = create_class_model(declaration);
81
82 if (!class_model_ptr)
83 return true;
84
85 context().reset();
86
87 const auto class_id = class_model_ptr->id();
88
89 set_unique_id(declaration->getID(), class_id);
90
91 auto &class_model =
92 diagram()
93 .get_participant<sequence_diagram::model::class_>(class_id)
94 .has_value()
95 ? *diagram()
96 .get_participant<sequence_diagram::model::class_>(class_id)
97 .get()
98 : *class_model_ptr;
99
100 if (!declaration->isCompleteDefinition()) {
101 forward_declarations_.emplace(class_id, std::move(class_model_ptr));
102 return true;
103 }
104
105 forward_declarations_.erase(class_id);
106
107 if (diagram().should_include(class_model)) {
108 LOG_DBG("Adding class participant {} with id {}",
109 class_model.full_name(false), class_model.id());
110
111 assert(class_model.id() == class_id);
112
113 context().set_caller_id(class_id);
114 context().update(declaration);
115
116 diagram().add_participant(std::move(class_model_ptr));
117 }
118 else {
119 LOG_DBG(
120 "Skipping class {} with id {}", class_model.full_name(), class_id);
121 }
122
123 return true;
124}
125
127 clang::ClassTemplateDecl *declaration)
128{
129 if (!should_include(declaration))
130 return true;
131
132 LOG_TRACE("Visiting class template declaration {} at {} [{}]",
133 declaration->getQualifiedNameAsString(),
134 declaration->getLocation().printToString(source_manager()),
135 (void *)declaration);
136
137 auto class_model_ptr = create_class_model(declaration->getTemplatedDecl());
138
139 if (!class_model_ptr)
140 return true;
141
142 tbuilder().build_from_template_declaration(*class_model_ptr, *declaration);
143
144 const auto class_full_name = class_model_ptr->full_name(false);
145 const auto id = common::to_id(class_full_name);
146
147 // Override the id with the template id, for now we don't care about the
148 // underlying templated class id
149 class_model_ptr->set_id(id);
150
151 set_unique_id(declaration->getID(), id);
152
153 if (!declaration->getTemplatedDecl()->isCompleteDefinition()) {
154 forward_declarations_.emplace(id, std::move(class_model_ptr));
155 return true;
156 }
157 forward_declarations_.erase(id);
158
159 if (diagram().should_include(*class_model_ptr)) {
160 LOG_DBG("Adding class template participant {} with id {}",
161 class_full_name, id);
162
164 context().update(declaration);
165
166 diagram().add_participant(std::move(class_model_ptr));
167 }
168
169 return true;
170}
171
173 clang::ClassTemplateSpecializationDecl *declaration)
174{
175 if (!should_include(declaration))
176 return true;
177
178 LOG_TRACE("Visiting template specialization declaration {} at {}",
179 declaration->getQualifiedNameAsString(),
180 declaration->getLocation().printToString(source_manager()));
181
182 // TODO: Add support for classes defined in function/method bodies
183 if (declaration->isLocalClass() != nullptr)
184 return true;
185
186 auto template_specialization_ptr =
188
189 if (!template_specialization_ptr)
190 return true;
191
192 const auto class_full_name = template_specialization_ptr->full_name(false);
193 const auto id = common::to_id(class_full_name);
194
195 template_specialization_ptr->set_id(id);
196
197 set_unique_id(declaration->getID(), id);
198
199 if (!declaration->isCompleteDefinition()) {
200 forward_declarations_.emplace(
201 id, std::move(template_specialization_ptr));
202 return true;
203 }
204 forward_declarations_.erase(id);
205
206 if (diagram().should_include(*template_specialization_ptr)) {
207 LOG_DBG(
208 "Adding class template specialization participant {} with id {}",
209 class_full_name, id);
210
212 context().update(declaration);
213
214 diagram().add_participant(std::move(template_specialization_ptr));
215 }
216
217 return true;
218}
219
221 clang::CXXMethodDecl *declaration)
222{
223 // We need to backup the context, since other methods or functions can
224 // be traversed during this traversal (e.g. template function/method
225 // specializations)
226 auto context_backup = context();
227
228 RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXMethodDecl(
229 declaration);
230
231 call_expression_context_ = context_backup;
232
233 return true;
234}
235
237 clang::CXXMethodDecl *declaration)
238{
239 if (!should_include(declaration))
240 return true;
241
242 if (!declaration->isThisDeclarationADefinition()) {
243 if (auto *declaration_definition = declaration->getDefinition();
244 declaration_definition != nullptr) {
245 if (auto *method_definition = clang::dyn_cast<clang::CXXMethodDecl>(
246 declaration_definition);
247 method_definition != nullptr) {
248 LOG_DBG("Calling VisitCXXMethodDecl recursively for forward "
249 "declaration");
250
251 return VisitCXXMethodDecl(method_definition);
252 }
253 }
254 }
255
256 LOG_TRACE("Visiting method {} in class {} [{}]",
257 declaration->getQualifiedNameAsString(),
258 declaration->getParent()->getQualifiedNameAsString(),
259 (void *)declaration->getParent());
260
261 context().update(declaration);
262
263 auto method_model_ptr = create_method_model(declaration);
264
265 if (!method_model_ptr)
266 return true;
267
268 process_comment(*declaration, *method_model_ptr);
269
270 set_source_location(*declaration, *method_model_ptr);
271
272 const auto method_full_name = method_model_ptr->full_name(false);
273
274 method_model_ptr->set_id(common::to_id(method_full_name));
275
276 // Callee methods in call expressions are referred to by first declaration
277 // id, so they should both be mapped to method_model
278 if (declaration->isThisDeclarationADefinition()) {
280 declaration->getFirstDecl()->getID(), method_model_ptr->id());
281 }
282
283 set_unique_id(declaration->getID(), method_model_ptr->id());
284
285 LOG_TRACE("Set id {} --> {} for method name {} [{}]", declaration->getID(),
286 method_model_ptr->id(), method_full_name,
287 declaration->isThisDeclarationADefinition());
288
289 context().update(declaration);
290
291 context().set_caller_id(method_model_ptr->id());
292
293 diagram().add_participant(std::move(method_model_ptr));
294
295 return true;
296}
297
299 clang::FunctionDecl *declaration)
300{
301 // We need to backup the context, since other methods or functions can
302 // be traversed during this traversal (e.g. template function/method
303 // specializations)
304 auto context_backup = context();
305
306 RecursiveASTVisitor<translation_unit_visitor>::TraverseFunctionDecl(
307 declaration);
308
309 call_expression_context_ = context_backup;
310
311 return true;
312}
313
315 clang::FunctionDecl *declaration)
316{
317 if (declaration->isCXXClassMember())
318 return true;
319
320 if (!should_include(declaration))
321 return true;
322
323 if (!declaration->isThisDeclarationADefinition()) {
324 if (auto *declaration_definition = declaration->getDefinition();
325 declaration_definition != nullptr)
326 return VisitFunctionDecl(
327 static_cast<clang::FunctionDecl *>(declaration_definition));
328 }
329
330 LOG_TRACE("Visiting function declaration {} at {}",
331 declaration->getQualifiedNameAsString(),
332 declaration->getLocation().printToString(source_manager()));
333
334 if (declaration->isTemplated()) {
335 if (declaration->getDescribedTemplate() != nullptr) {
336 // If the described templated of this function is already in the
337 // model skip it:
338 if (get_unique_id(
339 eid_t{declaration->getDescribedTemplate()->getID()}))
340 return true;
341 }
342 }
343
344 std::unique_ptr<model::function> function_model_ptr{};
345
346 if (declaration->isFunctionTemplateSpecialization()) {
347 function_model_ptr =
349 }
350 else {
351 function_model_ptr = build_function_model(*declaration);
352 }
353
354 if (!function_model_ptr)
355 return true;
356
357 function_model_ptr->set_id(
358 common::to_id(function_model_ptr->full_name(false)));
359
360 function_model_ptr->is_void(declaration->getReturnType()->isVoidType());
361
362 function_model_ptr->is_operator(declaration->isOverloadedOperator());
363
364 function_model_ptr->is_cuda_kernel(
365 common::has_attr(declaration, clang::attr::CUDAGlobal));
366
367 function_model_ptr->is_cuda_device(
368 common::has_attr(declaration, clang::attr::CUDADevice));
369
370 context().update(declaration);
371
372 context().set_caller_id(function_model_ptr->id());
373
374 if (declaration->isThisDeclarationADefinition()) {
376 declaration->getFirstDecl()->getID(), function_model_ptr->id());
377 }
378
379 set_unique_id(declaration->getID(), function_model_ptr->id());
380
381 process_comment(*declaration, *function_model_ptr);
382
383 set_source_location(*declaration, *function_model_ptr);
384
385 diagram().add_participant(std::move(function_model_ptr));
386
387 return true;
388}
389
391 clang::FunctionTemplateDecl *declaration)
392{
393 if (!should_include(declaration))
394 return true;
395
396 const auto function_name = declaration->getQualifiedNameAsString();
397
398 LOG_TRACE("Visiting function template declaration {} at {}", function_name,
399 declaration->getLocation().printToString(source_manager()));
400
401 auto function_template_model = build_function_template(*declaration);
402
403 process_comment(*declaration, *function_template_model);
404
405 set_source_location(*declaration, *function_template_model);
406 set_owning_module(*declaration, *function_template_model);
407
408 function_template_model->is_void(
409 declaration->getAsFunction()->getReturnType()->isVoidType());
410
411 function_template_model->set_id(
412 common::to_id(function_template_model->full_name(false)));
413
414 function_template_model->is_operator(
415 declaration->getAsFunction()->isOverloadedOperator());
416
417 context().update(declaration);
418
419 context().set_caller_id(function_template_model->id());
420
421 set_unique_id(declaration->getID(), function_template_model->id());
422
423 diagram().add_participant(std::move(function_template_model));
424
425 return true;
426}
427
428bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr)
429{
430 if (!should_include(expr))
431 return true;
432
433 const auto lambda_full_name =
434 expr->getLambdaClass()->getCanonicalDecl()->getNameAsString();
435
436 LOG_TRACE("Visiting lambda expression {} at {} [caller_id = {}]",
437 lambda_full_name, expr->getBeginLoc().printToString(source_manager()),
438 context().caller_id());
439
440 LOG_TRACE("Lambda call operator ID {} - lambda class ID {}, class call "
441 "operator ID {}",
442 expr->getCallOperator()->getID(), expr->getLambdaClass()->getID(),
443 expr->getLambdaClass()->getLambdaCallOperator()->getID());
444
445 // Create lambda class participant
446 auto *cls = expr->getLambdaClass();
447 auto lambda_class_model_ptr = create_class_model(cls);
448
449 if (!lambda_class_model_ptr)
450 return true;
451
452 const auto cls_id = lambda_class_model_ptr->id();
453
454 set_unique_id(cls->getID(), cls_id);
455
456 auto lambda_method_model_ptr =
457 create_lambda_method_model(expr->getCallOperator());
458
459 lambda_method_model_ptr->set_class_id(cls_id);
460
461 // If this is a nested lambda, prepend the parent lambda name to this lambda
462 auto lambda_class_full_name = lambda_class_model_ptr->full_name(false);
463 lambda_method_model_ptr->set_class_full_name(lambda_class_full_name);
464
465 diagram().add_participant(std::move(lambda_class_model_ptr));
466
467 lambda_method_model_ptr->set_id(
468 common::to_id(get_participant(cls_id).value().full_name(false) +
469 "::" + lambda_method_model_ptr->full_name_no_ns()));
470
471 get_participant<model::class_>(cls_id).value().set_lambda_operator_id(
472 lambda_method_model_ptr->id());
473
474 // If lambda expression is in an argument to a method/function, and that
475 // method function would be excluded by filters
476 if (std::holds_alternative<clang::CallExpr *>(
477 context().current_callexpr()) &&
478 (!context().lambda_caller_id().has_value())) {
481
482 message m{message_t::kCall, context().caller_id()};
483 set_source_location(*expr, m);
484 m.set_from(context().caller_id());
485 m.set_to(lambda_method_model_ptr->id());
486
487 diagram().add_active_participant(m.from());
488 diagram().add_active_participant(m.to());
489
490 LOG_DBG("Found call {} from {} [{}] to {} [{}]", m.message_name(),
491 m.from(), m.from(), m.to(), m.to());
492
493 push_message(std::get<clang::CallExpr *>(context().current_callexpr()),
494 std::move(m));
495 }
496
497 context().enter_lambda_expression(lambda_method_model_ptr->id());
498
500 expr->getCallOperator()->getID(), lambda_method_model_ptr->id());
501
502 diagram().add_participant(std::move(lambda_method_model_ptr));
503
504 [[maybe_unused]] const auto is_generic_lambda = expr->isGenericLambda();
505
506 return true;
507}
508
510{
511 RecursiveASTVisitor<translation_unit_visitor>::TraverseLambdaExpr(expr);
512
513 // lambda context is entered inside the visitor
515
516 return true;
517}
518
520{
521 if (!config().include_system_headers() &&
522 source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
523 return true;
524
525 LOG_TRACE("Entering call expression at {}",
526 expr->getBeginLoc().printToString(source_manager()));
527
528 context().enter_callexpr(expr);
529
530 RecursiveASTVisitor<translation_unit_visitor>::TraverseCallExpr(expr);
531
532 LOG_TRACE("Leaving call expression at {}",
533 expr->getBeginLoc().printToString(source_manager()));
534
536
538
539 return true;
540}
541
543 clang::CUDAKernelCallExpr *expr)
544{
545 if (!config().include_system_headers() &&
546 source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
547 return true;
548
549 LOG_TRACE("Entering CUDA kernel call expression at {}",
550 expr->getBeginLoc().printToString(source_manager()));
551
552 context().enter_callexpr(expr);
553
554 RecursiveASTVisitor<translation_unit_visitor>::TraverseCallExpr(expr);
555
556 LOG_TRACE("Leaving CUDA kernel call expression at {}",
557 expr->getBeginLoc().printToString(source_manager()));
558
560
562
563 return true;
564}
565
567 clang::CXXMemberCallExpr *expr)
568{
569 if (!config().include_system_headers() &&
570 source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
571 return true;
572
573 LOG_TRACE("Entering member call expression at {}",
574 expr->getBeginLoc().printToString(source_manager()));
575
576 context().enter_callexpr(expr);
577
578 RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXMemberCallExpr(
579 expr);
580
581 LOG_TRACE("Leaving member call expression at {}",
582 expr->getBeginLoc().printToString(source_manager()));
583
585
587
588 return true;
589}
590
592 clang::CXXOperatorCallExpr *expr)
593{
594 context().enter_callexpr(expr);
595
596 RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXOperatorCallExpr(
597 expr);
598
600
602
603 return true;
604}
605
607 clang::CXXTemporaryObjectExpr *expr)
608{
609 context().enter_callexpr(expr);
610
611 RecursiveASTVisitor<
613
615 clang::dyn_cast<clang::CXXConstructExpr>(expr));
616
618
620
621 return true;
622}
623
625 clang::CXXConstructExpr *expr)
626{
627 LOG_TRACE("Entering cxx construct call expression at {}",
628 expr->getBeginLoc().printToString(source_manager()));
629
630 context().enter_callexpr(expr);
631
632 RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXConstructExpr(
633 expr);
634
636
637 LOG_TRACE("Leaving cxx construct call expression at {}",
638 expr->getBeginLoc().printToString(source_manager()));
639
641
643
644 return true;
645}
646
648{
653
654 if (stmt == nullptr)
655 return true;
656
657 const auto *current_ifstmt = context().current_ifstmt();
658 const auto *current_elseifstmt =
659 current_ifstmt != nullptr ? context().current_elseifstmt() : nullptr;
660
661 //
662 // Add final else block (not else if)
663 //
664 if (current_elseifstmt != nullptr) {
665 if (current_elseifstmt->getElse() == stmt) {
666 const auto current_caller_id = context().caller_id();
667
668 if (current_caller_id.value() != 0) {
669 model::message m{message_t::kElse, current_caller_id};
670 set_source_location(*stmt, m);
671 diagram().add_message(std::move(m));
672 }
673 }
674 }
675 else if (current_ifstmt != nullptr) {
676 if (current_ifstmt->getElse() == stmt) {
677 const auto current_caller_id = context().caller_id();
678
679 if (current_caller_id.value() != 0) {
680 model::message m{message_t::kElse, current_caller_id};
681 set_source_location(*stmt, m);
682 diagram().add_message(std::move(m));
683 }
684 }
685 }
686
687 RecursiveASTVisitor<translation_unit_visitor>::TraverseCompoundStmt(stmt);
688
689 return true;
690}
691
693{
698
699 bool elseif_block{false};
700
701 const auto current_caller_id = context().caller_id();
702 const auto *current_ifstmt = context().current_ifstmt();
703 const auto *current_elseifstmt =
704 current_ifstmt != nullptr ? context().current_elseifstmt() : nullptr;
705
706 std::string condition_text;
707 if (config().generate_condition_statements())
708 condition_text = common::get_condition_text(source_manager(), stmt);
709
710 // Check if this is a beginning of a new if statement, or an
711 // else if condition of the current if statement
712 auto child_stmt_compare = [stmt](auto *child_stmt) {
713 return child_stmt == stmt;
714 };
715
716 if (current_ifstmt != nullptr)
717 elseif_block = elseif_block ||
718 std::any_of(current_ifstmt->children().begin(),
719 current_ifstmt->children().end(), child_stmt_compare);
720 if (current_elseifstmt != nullptr)
721 elseif_block = elseif_block ||
722 std::any_of(current_elseifstmt->children().begin(),
723 current_elseifstmt->children().end(), child_stmt_compare);
724
725 if ((current_caller_id.value() != 0) && !stmt->isConstexpr()) {
726 if (elseif_block) {
728
729 message m{message_t::kElseIf, current_caller_id};
730 set_source_location(*stmt, m);
731 m.condition_text(condition_text);
733 *context().get_ast_context(), current_caller_id, stmt));
734 diagram().add_block_message(std::move(m));
735 }
736 else {
737 context().enter_ifstmt(stmt);
738
739 message m{message_t::kIf, current_caller_id};
740 set_source_location(*stmt, m);
741 m.condition_text(condition_text);
743 *context().get_ast_context(), current_caller_id, stmt));
744 diagram().add_block_message(std::move(m));
745 }
746 }
747
748 RecursiveASTVisitor<translation_unit_visitor>::TraverseIfStmt(stmt);
749
750 if ((current_caller_id.value() != 0) && !stmt->isConstexpr()) {
751 if (!elseif_block) {
752 diagram().end_block_message(
753 {message_t::kIfEnd, current_caller_id}, message_t::kIf);
755 }
756 }
757
758 return true;
759}
760
762{
766
767 const auto current_caller_id = context().caller_id();
768
769 std::string condition_text;
770 if (config().generate_condition_statements())
771 condition_text = common::get_condition_text(source_manager(), stmt);
772
773 if (current_caller_id.value() != 0) {
774 LOG_TRACE("Entering while statement at {}",
775 stmt->getBeginLoc().printToString(source_manager()));
776
777 context().enter_loopstmt(stmt);
778 message m{message_t::kWhile, current_caller_id};
779 set_source_location(*stmt, m);
780 m.condition_text(condition_text);
782 *context().get_ast_context(), current_caller_id, stmt));
783 diagram().add_block_message(std::move(m));
784 }
785
786 RecursiveASTVisitor<translation_unit_visitor>::TraverseWhileStmt(stmt);
787
788 if (current_caller_id.value() != 0) {
789 diagram().end_block_message(
790 {message_t::kWhileEnd, current_caller_id}, message_t::kWhile);
792 }
793
794 return true;
795}
796
798{
802
803 const auto current_caller_id = context().caller_id();
804
805 std::string condition_text;
806 if (config().generate_condition_statements())
807 condition_text = common::get_condition_text(source_manager(), stmt);
808
809 if (current_caller_id.value() != 0) {
810 context().enter_loopstmt(stmt);
811 message m{message_t::kDo, current_caller_id};
812 set_source_location(*stmt, m);
813 m.condition_text(condition_text);
815 *context().get_ast_context(), current_caller_id, stmt));
816 diagram().add_block_message(std::move(m));
817 }
818
819 RecursiveASTVisitor<translation_unit_visitor>::TraverseDoStmt(stmt);
820
821 if (current_caller_id.value() != 0) {
822 diagram().end_block_message(
823 {message_t::kDoEnd, current_caller_id}, message_t::kDo);
825 }
826
827 return true;
828}
829
831{
835
836 const auto current_caller_id = context().caller_id();
837
838 std::string condition_text;
839 if (config().generate_condition_statements())
840 condition_text = common::get_condition_text(source_manager(), stmt);
841
842 if (current_caller_id.value() != 0) {
843 context().enter_loopstmt(stmt);
844 message m{message_t::kFor, current_caller_id};
845 set_source_location(*stmt, m);
846 m.condition_text(condition_text);
847
849 *context().get_ast_context(), current_caller_id, stmt));
850
851 diagram().add_block_message(std::move(m));
852 }
853
854 RecursiveASTVisitor<translation_unit_visitor>::TraverseForStmt(stmt);
855
856 if (current_caller_id.value() != 0) {
857 diagram().end_block_message(
858 {message_t::kForEnd, current_caller_id}, message_t::kFor);
860 }
861
862 return true;
863}
864
866{
870
871 const auto current_caller_id = context().caller_id();
872
873 if (current_caller_id.value() != 0) {
874 context().enter_trystmt(stmt);
875 message m{message_t::kTry, current_caller_id};
876 set_source_location(*stmt, m);
878 *context().get_ast_context(), current_caller_id, stmt));
879 diagram().add_block_message(std::move(m));
880 }
881
882 RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXTryStmt(stmt);
883
884 if (current_caller_id.value() != 0) {
885 diagram().end_block_message(
886 {message_t::kTryEnd, current_caller_id}, message_t::kTry);
888 }
889
890 return true;
891}
892
894{
898
899 const auto current_caller_id = context().caller_id();
900
901 if ((current_caller_id.value() != 0) &&
902 (context().current_trystmt() != nullptr)) {
903 std::string caught_type;
904 if (stmt->getCaughtType().isNull())
905 caught_type = "...";
906 else
907 caught_type = common::to_string(
908 stmt->getCaughtType(), *context().get_ast_context());
909
910 model::message m{message_t::kCatch, current_caller_id};
911 m.set_message_name(std::move(caught_type));
912 diagram().add_message(std::move(m));
913 }
914
915 RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXCatchStmt(stmt);
916
917 return true;
918}
919
921 clang::CXXForRangeStmt *stmt)
922{
926
927 const auto current_caller_id = context().caller_id();
928
929 std::string condition_text;
930 if (config().generate_condition_statements())
931 condition_text = common::get_condition_text(source_manager(), stmt);
932
933 if (current_caller_id.value() != 0) {
934 context().enter_loopstmt(stmt);
935 message m{message_t::kFor, current_caller_id};
936 set_source_location(*stmt, m);
937 m.condition_text(condition_text);
939 *context().get_ast_context(), current_caller_id, stmt));
940 diagram().add_block_message(std::move(m));
941 }
942
943 RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXForRangeStmt(
944 stmt);
945
946 if (current_caller_id.value() != 0) {
947 diagram().end_block_message(
948 {message_t::kForEnd, current_caller_id}, message_t::kFor);
950 }
951
952 return true;
953}
954
956{
958
959 const auto current_caller_id = context().caller_id();
960
961 if (current_caller_id.value() != 0) {
963 model::message m{message_t::kSwitch, current_caller_id};
964 set_source_location(*stmt, m);
966 *context().get_ast_context(), current_caller_id, stmt));
967 diagram().add_block_message(std::move(m));
968 }
969
970 RecursiveASTVisitor<translation_unit_visitor>::TraverseSwitchStmt(stmt);
971
972 if (current_caller_id.value() != 0) {
974 diagram().end_block_message(
975 {message_t::kSwitchEnd, current_caller_id}, message_t::kSwitch);
976 }
977
978 return true;
979}
980
982{
984
985 const auto current_caller_id = context().caller_id();
986
987 if ((current_caller_id.value() != 0) &&
988 (context().current_switchstmt() != nullptr)) {
989 model::message m{message_t::kCase, current_caller_id};
990 m.set_message_name(common::to_string(stmt->getLHS()));
991 diagram().add_case_stmt_message(std::move(m));
992 }
993
994 RecursiveASTVisitor<translation_unit_visitor>::TraverseCaseStmt(stmt);
995
996 return true;
997}
998
1000{
1002
1003 const auto current_caller_id = context().caller_id();
1004
1005 if ((current_caller_id.value() != 0) &&
1006 (context().current_switchstmt() != nullptr)) {
1007 model::message m{message_t::kCase, current_caller_id};
1008 m.set_message_name("default");
1009 diagram().add_case_stmt_message(std::move(m));
1010 }
1011
1012 RecursiveASTVisitor<translation_unit_visitor>::TraverseDefaultStmt(stmt);
1013
1014 return true;
1015}
1016
1018 clang::ConditionalOperator *stmt)
1019{
1021
1022 const auto current_caller_id = context().caller_id();
1023
1024 std::string condition_text;
1025 if (config().generate_condition_statements())
1026 condition_text = common::get_condition_text(source_manager(), stmt);
1027
1028 if (current_caller_id.value() != 0) {
1030 model::message m{message_t::kConditional, current_caller_id};
1031 set_source_location(*stmt, m);
1032 m.condition_text(condition_text);
1033 m.set_comment(get_expression_comment(source_manager(),
1034 *context().get_ast_context(), current_caller_id, stmt));
1035 diagram().add_block_message(std::move(m));
1036 }
1037
1038 RecursiveASTVisitor<translation_unit_visitor>::TraverseStmt(
1039 stmt->getCond());
1040
1041 RecursiveASTVisitor<translation_unit_visitor>::TraverseStmt(
1042 stmt->getTrueExpr());
1043
1044 if (current_caller_id.value() != 0) {
1045 model::message m{message_t::kConditionalElse, current_caller_id};
1046 set_source_location(*stmt, m);
1047 diagram().add_message(std::move(m));
1048 }
1049
1050 RecursiveASTVisitor<translation_unit_visitor>::TraverseStmt(
1051 stmt->getFalseExpr());
1052
1053 if (current_caller_id.value() != 0) {
1055 diagram().end_block_message(
1056 {message_t::kConditionalEnd, current_caller_id},
1057 message_t::kConditional);
1058 }
1059
1060 return true;
1061}
1062
1064{
1070
1071 if (!context().valid() || context().get_ast_context() == nullptr)
1072 return true;
1073
1074 LOG_TRACE("Visiting call expression at {} [caller_id = {}]",
1075 expr->getBeginLoc().printToString(source_manager()),
1076 context().caller_id());
1077
1078 message m{message_t::kCall, context().caller_id()};
1079
1080 m.in_static_declaration_context(within_static_variable_declaration_ > 0);
1081
1082 set_source_location(*expr, m);
1083
1084 const auto *raw_expr_comment = clanguml::common::get_expression_raw_comment(
1085 source_manager(), *context().get_ast_context(), expr);
1086 const auto stripped_comment = process_comment(
1087 raw_expr_comment, context().get_ast_context()->getDiagnostics(), m);
1088
1089 if (m.skip())
1090 return true;
1091
1092 auto generated_message_from_comment = generate_message_from_comment(m);
1093
1094 if (!generated_message_from_comment && !should_include(expr)) {
1095 LOG_DBG("Skipping call expression due to filter at: {}",
1096 expr->getBeginLoc().printToString(source_manager()));
1097
1098 processed_comments().erase(raw_expr_comment);
1099 return true;
1100 }
1101
1102 if (context().is_expr_in_current_control_statement_condition(expr)) {
1103 m.set_message_scope(common::model::message_scope_t::kCondition);
1104 }
1105
1106 if (generated_message_from_comment) {
1107 LOG_DBG(
1108 "Message for this call expression is taken from comment directive");
1109 }
1110 //
1111 // Call to a CUDA kernel function
1112 //
1113 else if (const auto *cuda_call_expr =
1114 clang::dyn_cast_or_null<clang::CUDAKernelCallExpr>(expr);
1115 cuda_call_expr != nullptr) {
1116 if (!process_cuda_kernel_call_expression(m, cuda_call_expr))
1117 return true;
1118 }
1119 //
1120 // Call to an overloaded operator
1121 //
1122 else if (const auto *operator_call_expr =
1123 clang::dyn_cast_or_null<clang::CXXOperatorCallExpr>(expr);
1124 operator_call_expr != nullptr) {
1125
1126 if (!process_operator_call_expression(m, operator_call_expr))
1127 return true;
1128 }
1129 //
1130 // Call to a class method
1131 //
1132 else if (const auto *method_call_expr =
1133 clang::dyn_cast_or_null<clang::CXXMemberCallExpr>(expr);
1134 method_call_expr != nullptr) {
1135
1136 if (!process_class_method_call_expression(m, method_call_expr))
1137 return true;
1138 }
1139 //
1140 // Call to function or template
1141 //
1142 else {
1143 auto *callee_decl = expr->getCalleeDecl();
1144
1145 if (callee_decl == nullptr) {
1146 LOG_DBG("Cannot get callee declaration - trying direct function "
1147 "callee...");
1148
1149 callee_decl = expr->getDirectCallee();
1150
1151 if (callee_decl != nullptr)
1152 LOG_DBG("Found function/method callee in: {}",
1153 common::to_string(expr));
1154 }
1155
1156 if (callee_decl == nullptr) {
1157 //
1158 // Call to a method of a class template
1159 //
1160 if (clang::dyn_cast_or_null<clang::CXXDependentScopeMemberExpr>(
1161 expr->getCallee()) != nullptr) {
1163 return true;
1164 }
1165 }
1166 //
1167 // Unresolved lookup expression are sometimes calls to template
1168 // functions
1169 //
1170 else if (clang::dyn_cast_or_null<clang::UnresolvedLookupExpr>(
1171 expr->getCallee()) != nullptr) {
1173 return true;
1174 }
1175 else if (clang::dyn_cast_or_null<clang::LambdaExpr>(
1176 expr->getCallee()) != nullptr) {
1177 LOG_DBG("Processing lambda expression callee");
1178 if (!process_lambda_call_expression(m, expr))
1179 return true;
1180 }
1181 else {
1182 LOG_DBG("Found unsupported callee decl type for: {} at {}",
1183 common::to_string(expr),
1184 expr->getBeginLoc().printToString(source_manager()));
1185 }
1186 }
1187 else {
1188 auto success = process_function_call_expression(m, expr);
1189
1190 if (!success) {
1191 LOG_DBG("Skipping call expression at: {}",
1192 expr->getBeginLoc().printToString(source_manager()));
1193
1194 return true;
1195 }
1196 }
1197 }
1198
1199 // Add message to diagram
1200 if (m.from().value() > 0 && m.to().value() > 0) {
1201 if (raw_expr_comment != nullptr)
1202 m.set_comment(raw_expr_comment->getBeginLoc().getHashValue(),
1203 stripped_comment);
1204
1205 if (diagram().sequences().find(m.from()) ==
1206 diagram().sequences().end()) {
1207 activity a{m.from()};
1208 diagram().sequences().insert({m.from(), std::move(a)});
1209 }
1210
1211 diagram().add_active_participant(m.from());
1212 diagram().add_active_participant(m.to());
1213
1214 LOG_DBG("Found call {} from {} [{}] to {} [{}] ", m.message_name(),
1215 m.from(), m.from(), m.to(), m.to());
1216
1217 push_message(expr, std::move(m));
1218 }
1219
1220 return true;
1221}
1222
1224 model::message &m) const
1225{
1226 auto generated_message_from_comment{false};
1227 for (const auto &decorator : m.decorators()) {
1228 auto call_decorator =
1229 std::dynamic_pointer_cast<decorators::call>(decorator);
1230 if (call_decorator &&
1231 call_decorator->applies_to_diagram(config().name)) {
1232 m.set_to(common::to_id(call_decorator->callee));
1233 generated_message_from_comment = true;
1234 break;
1235 }
1236 }
1237 return generated_message_from_comment;
1238}
1239
1241{
1242 if (decl->isStaticLocal())
1244
1245 RecursiveASTVisitor::TraverseVarDecl(decl);
1246
1247 if (decl->isStaticLocal())
1249
1250 return true;
1251}
1252
1254 clang::CXXConstructExpr *expr)
1255{
1261
1262 if (expr == nullptr)
1263 return true;
1264
1265 if (const auto *ctor = expr->getConstructor();
1266 ctor != nullptr && !should_include(ctor))
1267 return true;
1268
1269 LOG_TRACE("Visiting cxx construct expression at {} [caller_id = {}]",
1270 expr->getBeginLoc().printToString(source_manager()),
1271 context().caller_id());
1272
1273 message m{message_t::kCall, context().caller_id()};
1274
1275 m.in_static_declaration_context(within_static_variable_declaration_ > 0);
1276
1277 set_source_location(*expr, m);
1278
1279 if (context().is_expr_in_current_control_statement_condition(expr)) {
1280 m.set_message_scope(common::model::message_scope_t::kCondition);
1281 }
1282
1283 if (!process_construct_expression(m, expr))
1284 return true;
1285
1286 if (m.from().value() > 0 && m.to().value() > 0) {
1287 if (diagram().sequences().find(m.from()) ==
1288 diagram().sequences().end()) {
1289 activity a{m.from()};
1290 diagram().sequences().insert({m.from(), std::move(a)});
1291 }
1292
1293 diagram().add_active_participant(m.from());
1294 diagram().add_active_participant(m.to());
1295
1296 LOG_DBG("Found constructor call {} from {} [{}] to {} [{}] ",
1297 m.message_name(), m.from(), m.from(), m.to(), m.to());
1298
1299 push_message(expr, std::move(m));
1300 }
1301
1302 return true;
1303}
1304
1306 model::message &m, const clang::CUDAKernelCallExpr *expr)
1307{
1308 const auto *callee_decl = expr->getCalleeDecl();
1309
1310 if (callee_decl == nullptr)
1311 return false;
1312
1313 const auto *callee_function = callee_decl->getAsFunction();
1314
1315 if (callee_function == nullptr)
1316 return false;
1317
1318 if (!should_include(callee_function))
1319 return false;
1320
1321 // Skip free functions declared in files outside of included paths
1322 if (config().combine_free_functions_into_file_participants() &&
1324 return false;
1325
1326 auto callee_name = callee_function->getQualifiedNameAsString() + "()";
1327
1328 m.set_to(id_mapper().resolve_or(eid_t{callee_function->getID()}));
1329 m.set_message_name(callee_name.substr(0, callee_name.size() - 2));
1330
1331 return true;
1332}
1333
1335 model::message &m, const clang::CXXOperatorCallExpr *operator_call_expr)
1336{
1337 if (operator_call_expr->getCalleeDecl() == nullptr)
1338 return false;
1339
1340 LOG_DBG("Operator '{}' call expression to {} at {}",
1341 getOperatorSpelling(operator_call_expr->getOperator()),
1342 operator_call_expr->getCalleeDecl()->getID(),
1343 operator_call_expr->getBeginLoc().printToString(source_manager()));
1344
1345 // Handle the case if the callee is a lambda
1346 if (const auto *lambda_method = clang::dyn_cast<clang::CXXMethodDecl>(
1347 operator_call_expr->getCalleeDecl());
1348 lambda_method != nullptr && lambda_method->getParent()->isLambda()) {
1349
1350 LOG_DBG("Operator callee is a lambda: {}",
1351 common::to_string(lambda_method));
1352
1353 const auto source_location{
1354 lambda_source_location(lambda_method->getParent()->getLocation())};
1355
1356 auto lambda_name = make_lambda_name(lambda_method->getParent());
1357
1358 m.set_to(eid_t{lambda_method->getParent()->getID()});
1359 }
1360 else {
1361 const auto operator_ast_id =
1362 operator_call_expr->getCalleeDecl()->getID();
1363 m.set_to(id_mapper().resolve_or(eid_t{operator_ast_id}));
1364 }
1365
1366 m.set_message_name(fmt::format(
1367 "operator{}", getOperatorSpelling(operator_call_expr->getOperator())));
1368
1369 return true;
1370}
1371
1373 model::message &m, const clang::CXXConstructExpr *construct_expr)
1374{
1375 const auto *constructor = construct_expr->getConstructor();
1376 if (constructor == nullptr)
1377 return false;
1378
1379 const auto *constructor_parent = constructor->getParent();
1380 if (constructor_parent == nullptr)
1381 return false;
1382
1383 LOG_DBG("Constructor '{}' call expression to {} at {}",
1384 construct_expr->getConstructor()->getNameAsString(),
1385 constructor->getID(),
1386 construct_expr->getBeginLoc().printToString(source_manager()));
1387
1388 m.set_to(id_mapper().resolve_or(eid_t{constructor->getID()}));
1390 fmt::format("{}::{}", constructor_parent->getQualifiedNameAsString(),
1391 constructor_parent->getNameAsString()));
1392
1393 diagram().add_active_participant(eid_t{constructor->getID()});
1394
1395 return true;
1396}
1397
1399 model::message &m, const clang::CXXMemberCallExpr *method_call_expr)
1400{
1401 // Get callee declaration as methods parent
1402 const auto *method_decl = method_call_expr->getMethodDecl();
1403
1404 if (method_decl == nullptr)
1405 return false;
1406
1407 std::string method_name = method_decl->getQualifiedNameAsString();
1408
1409 const auto *callee_decl =
1410 method_decl != nullptr ? method_decl->getParent() : nullptr;
1411
1412 if (callee_decl == nullptr)
1413 return false;
1414
1415 if (!should_include(callee_decl) || !should_include(method_decl))
1416 return false;
1417
1418 m.set_to(eid_t{method_decl->getID()});
1419 m.set_message_name(method_decl->getNameAsString());
1421 method_call_expr->getCallReturnType(*context().get_ast_context())
1422 .getAsString());
1423
1424 LOG_TRACE("Set callee method id {} for method name {}", m.to(),
1425 method_decl->getQualifiedNameAsString());
1426
1427 diagram().add_active_participant(eid_t{method_decl->getID()});
1428
1429 return true;
1430}
1431
1433 model::message &m, const clang::CallExpr *expr)
1434{
1435 const auto *dependent_member_callee =
1436 clang::dyn_cast_or_null<clang::CXXDependentScopeMemberExpr>(
1437 expr->getCallee());
1438
1439 if (dependent_member_callee == nullptr)
1440 return false;
1441
1442 if (is_callee_valid_template_specialization(dependent_member_callee)) {
1443 if (const auto *tst = dependent_member_callee->getBaseType()
1444 ->getAs<clang::TemplateSpecializationType>();
1445 tst != nullptr) {
1446 const auto *template_declaration =
1447 tst->getTemplateName().getAsTemplateDecl();
1448
1449 std::string callee_method_full_name;
1450
1451 // First check if the primary template is already in the
1452 // participants map
1453 if (get_participant(template_declaration).has_value()) {
1454 callee_method_full_name = get_participant(template_declaration)
1455 .value()
1456 .full_name(false) +
1457 "::" + dependent_member_callee->getMember().getAsString();
1458
1459 for (const auto &[id, p] : diagram().participants()) {
1460 const auto p_full_name = p->full_name(false);
1461
1462 if (p_full_name.find(callee_method_full_name + "(") == 0) {
1463 // TODO: This selects the first matching template method
1464 // without considering arguments!!!
1465 m.set_to(id);
1466 break;
1467 }
1468 }
1469 }
1470 // Otherwise check if it is a smart pointer
1471 else if (is_smart_pointer(template_declaration)) {
1472 const auto *argument_template =
1473 template_declaration->getTemplateParameters()
1474 ->asArray()
1475 .front();
1476
1477 if (get_participant(argument_template).has_value()) {
1478 callee_method_full_name = get_participant(argument_template)
1479 .value()
1480 .full_name(false) +
1481 "::" +
1482 dependent_member_callee->getMember().getAsString();
1483
1484 for (const auto &[id, p] : diagram().participants()) {
1485 const auto p_full_name = p->full_name(false);
1486 if (p_full_name.find(callee_method_full_name + "(") ==
1487 0) {
1488 // TODO: This selects the first matching template
1489 // method without considering arguments!!!
1490 m.set_to(id);
1491 break;
1492 }
1493 }
1494 }
1495 else
1496 return false;
1497 }
1498
1500 dependent_member_callee->getMember().getAsString());
1501
1502 if (const auto maybe_id =
1503 get_unique_id(eid_t{template_declaration->getID()});
1504 maybe_id.has_value())
1505 diagram().add_active_participant(maybe_id.value());
1506 }
1507 }
1508 else {
1509 LOG_DBG("Skipping call due to unresolvable "
1510 "CXXDependentScopeMemberExpr at {}",
1511 expr->getBeginLoc().printToString(source_manager()));
1512 }
1513
1514 return true;
1515}
1516
1518 model::message &m, const clang::CallExpr *expr)
1519{
1520 const auto *callee_decl = expr->getCalleeDecl();
1521
1522 if (callee_decl == nullptr)
1523 return false;
1524
1525 const auto *callee_function = callee_decl->getAsFunction();
1526
1527 if (callee_function == nullptr)
1528 return false;
1529
1530 if (!should_include(callee_function))
1531 return false;
1532
1533 // Skip free functions declared in files outside of included paths
1534 if (config().combine_free_functions_into_file_participants() &&
1536 return false;
1537
1538 auto callee_name = callee_function->getQualifiedNameAsString() + "()";
1539
1540 m.set_to(id_mapper().resolve_or(eid_t{callee_function->getID()}));
1541 m.set_message_name(callee_name.substr(0, callee_name.size() - 2));
1542
1543 return true;
1544}
1545
1547 model::message &m, const clang::CallExpr *expr) const
1548{
1549 const auto *lambda_expr =
1550 clang::dyn_cast_or_null<clang::LambdaExpr>(expr->getCallee());
1551
1552 if (lambda_expr == nullptr)
1553 return true;
1554
1555 const auto lambda_class_id = eid_t{lambda_expr->getLambdaClass()->getID()};
1556 m.set_to(id_mapper().resolve_or(eid_t{lambda_class_id}));
1557
1558 return true;
1559}
1560
1562 model::message &m, const clang::CallExpr *expr) const
1563{
1564 // This is probably a template
1565 const auto *unresolved_expr =
1566 clang::dyn_cast_or_null<clang::UnresolvedLookupExpr>(expr->getCallee());
1567
1568 if (unresolved_expr != nullptr) {
1569 for (const auto *decl : unresolved_expr->decls()) {
1570 if (clang::dyn_cast_or_null<clang::FunctionTemplateDecl>(decl) !=
1571 nullptr) {
1572 const auto *ftd =
1573 clang::dyn_cast_or_null<clang::FunctionTemplateDecl>(decl);
1574 m.set_to(id_mapper().resolve_or(eid_t{ftd->getID()}));
1575 break;
1576 }
1577
1578 if (clang::dyn_cast_or_null<clang::FunctionDecl>(decl) != nullptr) {
1579 const auto *fd =
1580 clang::dyn_cast_or_null<clang::FunctionDecl>(decl);
1581 m.set_to(id_mapper().resolve_or(eid_t{fd->getID()}));
1582 break;
1583 }
1584
1585 LOG_DBG("Unknown unresolved lookup expression");
1586 }
1587 }
1588
1589 return true;
1590}
1591
1593 const clang::CXXDependentScopeMemberExpr *dependent_member_expr) const
1594{
1595 if (dependent_member_expr == nullptr)
1596 return false;
1597
1598 if (dependent_member_expr->getBaseType().isNull())
1599 return false;
1600
1601 const auto *tst = dependent_member_expr->getBaseType()
1602 ->getAs<clang::TemplateSpecializationType>();
1603
1604 if (tst == nullptr)
1605 return false;
1606
1607 return !(tst->isPointerType());
1608}
1609
1611 const clang::TemplateDecl *primary_template) const
1612{
1613 return primary_template->getQualifiedNameAsString().find(
1614 "std::unique_ptr") == 0 ||
1615 primary_template->getQualifiedNameAsString().find("std::shared_ptr") ==
1616 0 ||
1617 primary_template->getQualifiedNameAsString().find("std::weak_ptr") == 0;
1618}
1619
1620std::unique_ptr<clanguml::sequence_diagram::model::class_>
1622{
1623 assert(cls != nullptr);
1624
1625 auto c_ptr{std::make_unique<clanguml::sequence_diagram::model::class_>(
1626 config().using_namespace())};
1627 auto &c = *c_ptr;
1628
1629 auto qualified_name = cls->getQualifiedNameAsString();
1630
1631 if (!cls->isLambda())
1632 if (!should_include(cls))
1633 return {};
1634
1635 auto ns = common::get_tag_namespace(*cls);
1636
1637 if (cls->isLambda() && !diagram().should_include(ns | "lambda"))
1638 return {};
1639
1640 const auto *parent = cls->getParent();
1641
1642 if ((parent != nullptr) && parent->isRecord()) {
1643 // Here we have 3 options, either:
1644 // - the parent is a regular C++ class/struct
1645 // - the parent is a class template declaration/specialization
1646 // - the parent is a lambda (i.e. this is a nested lambda expression)
1647 std::optional<eid_t> id_opt;
1648 const auto *parent_record_decl =
1649 clang::dyn_cast<clang::RecordDecl>(parent);
1650
1651 assert(parent_record_decl != nullptr);
1652
1653 const eid_t ast_id{parent_record_decl->getID()};
1654
1655 // First check if the parent has been added to the diagram as
1656 // regular class
1657 id_opt = get_unique_id(ast_id);
1658
1659 // If not, check if the parent template declaration is in the model
1660 if (!id_opt &&
1661 (parent_record_decl->getDescribedTemplate() != nullptr)) {
1662 parent_record_decl->getDescribedTemplate()->getID();
1663 if (parent_record_decl->getDescribedTemplate() != nullptr)
1664 id_opt = get_unique_id(ast_id);
1665 }
1666
1667 if (!id_opt)
1668 return {};
1669
1670 auto parent_class =
1671 diagram()
1673 *id_opt);
1674
1675 if (!parent_class) {
1676 return {};
1677 }
1678
1679 c.set_namespace(ns);
1680 if (cls->getNameAsString().empty()) {
1681 // Nested structs can be anonymous
1682 if (anonymous_struct_relationships_.count(cls->getID()) > 0) {
1683 const auto &[label, hint, access] =
1684 anonymous_struct_relationships_[cls->getID()];
1685
1686 c.set_name(parent_class.value().name() +
1687 "::" + fmt::format("({})", label));
1688
1689 parent_class.value().add_relationship(
1690 {hint, common::to_id(c.full_name(false)), access, label});
1691 }
1692 else
1693 c.set_name(parent_class.value().name() + "::" +
1694 fmt::format(
1695 "(anonymous_{})", std::to_string(cls->getID())));
1696 }
1697 else {
1698 c.set_name(
1699 parent_class.value().name() + "::" + cls->getNameAsString());
1700 }
1701
1702 c.set_id(common::to_id(c.full_name(false)));
1703
1704 c.nested(true);
1705 }
1706 else if (cls->isLambda()) {
1707 c.is_lambda(true);
1708 if (cls->getParent() != nullptr) {
1709 const auto type_name = make_lambda_name(cls);
1710
1711 c.set_name(type_name);
1712 c.set_namespace(ns);
1713 c.set_id(common::to_id(c.full_name(false)));
1714 }
1715 else {
1716 LOG_WARN("Cannot find parent declaration for lambda {}",
1717 cls->getQualifiedNameAsString());
1718 return {};
1719 }
1720 }
1721 else {
1722 c.set_name(common::get_tag_name(*cls));
1723 c.set_namespace(ns);
1724 c.set_id(common::to_id(c.full_name(false)));
1725 }
1726
1727 c.is_struct(cls->isStruct());
1728
1729 process_comment(*cls, c);
1730 set_source_location(*cls, c);
1731
1732 if (c.skip())
1733 return {};
1734
1735 c.set_style(c.style_spec());
1736
1737 return c_ptr;
1738}
1739
1740void translation_unit_visitor::set_unique_id(int64_t local_id, eid_t global_id)
1741{
1742 LOG_TRACE("Setting local element mapping {} --> {}", local_id, global_id);
1743
1744 assert(global_id.is_global());
1745
1746 id_mapper().add(local_id, global_id);
1747}
1748
1750 eid_t local_id) const
1751{
1752 return id_mapper().get_global_id(local_id);
1753}
1754
1755std::unique_ptr<model::function_template>
1757 const clang::FunctionTemplateDecl &declaration)
1758{
1759 auto function_template_model_ptr =
1760 std::make_unique<sequence_diagram::model::function_template>(
1761 config().using_namespace());
1762
1763 set_qualified_name(declaration, *function_template_model_ptr);
1764
1766 *function_template_model_ptr, declaration);
1767
1768 function_template_model_ptr->return_type(
1769 common::to_string(declaration.getAsFunction()->getReturnType(),
1770 declaration.getASTContext()));
1771
1772 for (const auto *param : declaration.getTemplatedDecl()->parameters()) {
1773 function_template_model_ptr->add_parameter(
1775 param->getType(), declaration.getASTContext(), false)));
1776 }
1777
1778 return function_template_model_ptr;
1779}
1780
1781std::unique_ptr<model::function_template>
1783 const clang::FunctionDecl &decl)
1784{
1785 auto template_instantiation_ptr =
1786 std::make_unique<model::function_template>(config().using_namespace());
1787 auto &template_instantiation = *template_instantiation_ptr;
1788
1789 set_qualified_name(decl, template_instantiation);
1790
1791 tbuilder().build(template_instantiation, &decl, decl.getPrimaryTemplate(),
1792 decl.getTemplateSpecializationArgs()->asArray(),
1793 common::to_string(&decl));
1794
1795 // Handle function parameters
1796 for (const auto *param : decl.parameters()) {
1797 template_instantiation_ptr->add_parameter(
1798 common::to_string(param->getType(), decl.getASTContext()));
1799 }
1800
1801 return template_instantiation_ptr;
1802}
1803
1804std::unique_ptr<model::function> translation_unit_visitor::build_function_model(
1805 const clang::FunctionDecl &declaration)
1806{
1807 auto function_model_ptr =
1808 std::make_unique<sequence_diagram::model::function>(
1809 config().using_namespace());
1810
1811 common::model::namespace_ ns{declaration.getQualifiedNameAsString()};
1812 function_model_ptr->set_name(ns.name());
1813 ns.pop_back();
1814 function_model_ptr->set_namespace(ns);
1815
1816 function_model_ptr->return_type(common::to_string(
1817 declaration.getReturnType(), declaration.getASTContext()));
1818
1819 for (const auto *param : declaration.parameters()) {
1820 function_model_ptr->add_parameter(
1822 param->getType(), declaration.getASTContext(), false)));
1823 }
1824
1825 return function_model_ptr;
1826}
1827
1828std::unique_ptr<model::class_>
1830 clang::ClassTemplateSpecializationDecl *cls)
1831{
1832 auto c_ptr{std::make_unique<model::class_>(config().using_namespace())};
1833
1835
1836 auto &template_instantiation = *c_ptr;
1837
1838 // TODO: refactor to method get_qualified_name()
1839 auto qualified_name = cls->getQualifiedNameAsString();
1840 util::replace_all(qualified_name, "(anonymous namespace)", "");
1841 util::replace_all(qualified_name, "::::", "::");
1842
1843 common::model::namespace_ ns{qualified_name};
1844 ns.pop_back();
1845 template_instantiation.set_name(cls->getNameAsString());
1846 template_instantiation.set_namespace(ns);
1847
1848 template_instantiation.is_struct(cls->isStruct());
1849
1850 process_comment(*cls, template_instantiation);
1851 set_source_location(*cls, template_instantiation);
1852 set_owning_module(*cls, template_instantiation);
1853
1854 if (template_instantiation.skip())
1855 return {};
1856
1857 template_instantiation.set_id(
1858 common::to_id(template_instantiation.full_name(false)));
1859
1860 set_unique_id(cls->getID(), template_instantiation.id());
1861
1862 return c_ptr;
1863}
1864
1866 const std::string &full_name) const
1867{
1868 return config().simplify_template_type(full_name);
1869}
1870
1872 const clang::SourceLocation &source_location) const
1873{
1874 const auto file_line =
1875 source_manager().getSpellingLineNumber(source_location);
1876 const auto file_column =
1877 source_manager().getSpellingColumnNumber(source_location);
1878 const std::string file_name =
1879 config()
1880 .make_path_relative(
1881 source_manager().getFilename(source_location).str())
1882 .string();
1883 return fmt::format("{}:{}:{}", file_name, file_line, file_column);
1884}
1885
1887 const clang::CXXRecordDecl *cls) const
1888{
1889 std::string result;
1890 const auto location = cls->getLocation();
1891 const std::string source_location{lambda_source_location(location)};
1892
1893 const auto maybe_lambda_caller_id = context().lambda_caller_id();
1894 if (maybe_lambda_caller_id.has_value()) {
1895 // Parent is also a lambda (this id points to a lambda operator())
1896 std::string parent_lambda_class_name{"()"};
1897 if (diagram().get_participant<model::method>(
1898 maybe_lambda_caller_id.value())) {
1899 auto parent_lambda_class_id =
1900 diagram()
1901 .get_participant<model::method>(
1902 maybe_lambda_caller_id.value())
1903 .value()
1904 .class_id();
1905
1906 if (diagram().get_participant<model::class_>(
1907 parent_lambda_class_id)) {
1908 parent_lambda_class_name =
1909 diagram()
1910 .get_participant<model::class_>(parent_lambda_class_id)
1911 .value()
1912 .full_name(false);
1913 }
1914 }
1915
1916 result = fmt::format(
1917 "{}##(lambda {})", parent_lambda_class_name, source_location);
1918 }
1919 else if (context().caller_id().value() != 0 &&
1920 get_participant(context().caller_id()).has_value()) {
1921 auto parent_full_name =
1922 get_participant(context().caller_id()).value().full_name_no_ns();
1923
1924 result =
1925 fmt::format("{}##(lambda {})", parent_full_name, source_location);
1926 }
1927 else {
1928 result = fmt::format("(lambda {})", source_location);
1929 }
1930
1931 return result;
1932}
1933
1935 clang::CallExpr *expr, model::message &&m)
1936{
1937 call_expr_message_map_.emplace(expr, std::move(m));
1938}
1939
1941 clang::CXXConstructExpr *expr, model::message &&m)
1942{
1943 construct_expr_message_map_.emplace(expr, std::move(m));
1944}
1945
1947{
1948 assert(expr != nullptr);
1949
1950 // Skip if no message was generated from this expr
1951 if (call_expr_message_map_.find(expr) == call_expr_message_map_.end()) {
1952 return;
1953 }
1954
1955 auto msg = std::move(call_expr_message_map_.at(expr));
1956
1957 auto caller_id = msg.from();
1958
1959 if (caller_id == 0)
1960 return;
1961
1962 if (diagram().has_activity(caller_id))
1963 diagram().get_activity(caller_id).add_message(std::move(msg));
1964
1965 call_expr_message_map_.erase(expr);
1966}
1967
1969 clang::CXXConstructExpr *expr)
1970{
1971 assert(expr != nullptr);
1972
1973 // Skip if no message was generated from this expr
1974 if (construct_expr_message_map_.find(expr) ==
1976 return;
1977 }
1978
1979 auto msg = std::move(construct_expr_message_map_.at(expr));
1980
1981 auto caller_id = msg.from();
1982 diagram().get_activity(caller_id).add_message(std::move(msg));
1983
1984 construct_expr_message_map_.erase(expr);
1985}
1986
1988{
1990
1991 // Change all messages with target set to an id of a lambda expression to
1992 // to the ID of their operator() - this is necessary, as some calls to
1993 // lambda expressions are visited before the actual lambda expressions
1994 // are visited...
1996
1997 if (config().inline_lambda_messages())
1998 diagram().inline_lambda_operator_calls();
1999}
2000
2002{
2003 for (auto &[id, activity] : diagram().sequences()) {
2004 for (auto &m : activity.messages()) {
2005 auto participant = diagram().get_participant<model::class_>(m.to());
2006
2007 if (participant && participant.value().is_lambda() &&
2008 participant.value().lambda_operator_id().value() != 0) {
2009 LOG_DBG("Changing lambda expression target id from {} to {}",
2010 m.to(), participant.value().lambda_operator_id());
2011
2012 m.set_to(participant.value().lambda_operator_id());
2013 m.set_message_name("operator()");
2014 }
2015 }
2016 }
2017}
2018
2020{
2021 std::set<eid_t> active_participants_unique;
2022
2023 // Change all active participants AST local ids to diagram global ids
2024 for (auto id : diagram().active_participants()) {
2025 if (const auto unique_id = get_unique_id(id);
2026 !id.is_global() && unique_id.has_value()) {
2027 active_participants_unique.emplace(unique_id.value());
2028 }
2029 else if (id.is_global()) {
2030 active_participants_unique.emplace(id);
2031 }
2032 }
2033
2034 diagram().active_participants() = std::move(active_participants_unique);
2035
2036 // Change all message callees AST local ids to diagram global ids
2037 for (auto &[id, activity] : diagram().sequences()) {
2038 for (auto &m : activity.messages()) {
2039 if (const auto unique_id = get_unique_id(m.to());
2040 !m.to().is_global() && unique_id.has_value()) {
2041 m.set_to(unique_id.value());
2042 assert(m.to().is_global());
2043 }
2044 }
2045 }
2046}
2047
2048std::unique_ptr<clanguml::sequence_diagram::model::method>
2050 clang::CXXMethodDecl *declaration)
2051{
2052 auto method_model_ptr = std::make_unique<sequence_diagram::model::method>(
2053 config().using_namespace());
2054
2055 common::model::namespace_ ns{declaration->getQualifiedNameAsString()};
2056 auto method_name = ns.name();
2057 method_model_ptr->set_method_name(method_name);
2058 ns.pop_back();
2059 method_model_ptr->set_name(ns.name());
2060 ns.pop_back();
2061 method_model_ptr->set_namespace(ns);
2062
2063 method_model_ptr->is_defaulted(declaration->isDefaulted());
2064 method_model_ptr->is_assignment(declaration->isCopyAssignmentOperator() ||
2065 declaration->isMoveAssignmentOperator());
2066 method_model_ptr->is_const(declaration->isConst());
2067 method_model_ptr->is_static(declaration->isStatic());
2068 method_model_ptr->is_operator(declaration->isOverloadedOperator());
2069 method_model_ptr->is_constructor(
2070 clang::dyn_cast<clang::CXXConstructorDecl>(declaration) != nullptr);
2071
2072 method_model_ptr->is_void(declaration->getReturnType()->isVoidType());
2073
2074 method_model_ptr->return_type(common::to_string(
2075 declaration->getReturnType(), declaration->getASTContext()));
2076
2077 for (const auto *param : declaration->parameters()) {
2078 auto parameter_type =
2079 common::to_string(param->getType(), param->getASTContext());
2081 parameter_type = simplify_system_template(parameter_type);
2082 method_model_ptr->add_parameter(config().using_namespace().relative(
2083 simplify_system_template(parameter_type)));
2084 }
2085
2086 return method_model_ptr;
2087}
2088
2089std::unique_ptr<clanguml::sequence_diagram::model::method>
2090translation_unit_visitor::create_method_model(clang::CXXMethodDecl *declaration)
2091{
2092 auto method_model_ptr = std::make_unique<sequence_diagram::model::method>(
2093 config().using_namespace());
2094
2095 common::model::namespace_ ns{declaration->getQualifiedNameAsString()};
2096 auto method_name = ns.name();
2097 method_model_ptr->set_method_name(method_name);
2098 ns.pop_back();
2099 method_model_ptr->set_name(ns.name());
2100 ns.pop_back();
2101 method_model_ptr->set_namespace(ns);
2102
2103 method_model_ptr->is_defaulted(declaration->isDefaulted());
2104 method_model_ptr->is_assignment(declaration->isCopyAssignmentOperator() ||
2105 declaration->isMoveAssignmentOperator());
2106 method_model_ptr->is_const(declaration->isConst());
2107 method_model_ptr->is_static(declaration->isStatic());
2108 method_model_ptr->is_operator(declaration->isOverloadedOperator());
2109 method_model_ptr->is_constructor(
2110 clang::dyn_cast<clang::CXXConstructorDecl>(declaration) != nullptr);
2111
2112 clang::Decl *parent_decl = declaration->getParent();
2113
2114 if (context().current_class_template_decl_ != nullptr)
2115 parent_decl = context().current_class_template_decl_;
2116
2117 LOG_DBG("Getting method's class with local id {}", parent_decl->getID());
2118
2119 const auto maybe_method_class = get_participant<model::class_>(parent_decl);
2120
2121 if (!maybe_method_class) {
2122 LOG_DBG("Cannot find parent class_ for method {} in class {}",
2123 declaration->getQualifiedNameAsString(),
2124 declaration->getParent()->getQualifiedNameAsString());
2125 return {};
2126 }
2127
2128 const auto &method_class = maybe_method_class.value();
2129
2130 method_model_ptr->is_void(declaration->getReturnType()->isVoidType());
2131
2132 method_model_ptr->set_class_id(method_class.id());
2133 method_model_ptr->set_class_full_name(method_class.full_name(false));
2134 method_model_ptr->set_name(get_participant(method_model_ptr->class_id())
2135 .value()
2136 .full_name_no_ns() +
2137 "::" + declaration->getNameAsString());
2138
2139 method_model_ptr->return_type(common::to_string(
2140 declaration->getReturnType(), declaration->getASTContext()));
2141
2142 for (const auto *param : declaration->parameters()) {
2143 auto parameter_type =
2144 common::to_string(param->getType(), param->getASTContext());
2146 parameter_type = simplify_system_template(parameter_type);
2147 method_model_ptr->add_parameter(config().using_namespace().relative(
2148 simplify_system_template(parameter_type)));
2149 }
2150
2151 return method_model_ptr;
2152}
2153
2154bool translation_unit_visitor::should_include(const clang::TagDecl *decl) const
2155{
2157 dynamic_cast<const clang::NamedDecl *>(decl));
2158}
2159
2161 const clang::LambdaExpr *expr) const
2162{
2163 const auto expr_file = expr->getBeginLoc().printToString(source_manager());
2164 return diagram().should_include(common::model::source_file{expr_file});
2165}
2166
2167bool translation_unit_visitor::should_include(const clang::CallExpr *expr) const
2168{
2169 if (context().caller_id() == 0)
2170 return false;
2171
2172 // Skip casts, moves and such
2173 if (expr->isCallToStdMove())
2174 return false;
2175
2176 if (expr->isImplicitCXXThis())
2177 return false;
2178
2179 if (clang::dyn_cast_or_null<clang::ImplicitCastExpr>(expr) != nullptr)
2180 return false;
2181
2182 if (!context().valid())
2183 return false;
2184
2185 const auto expr_file = expr->getBeginLoc().printToString(source_manager());
2186
2188 return false;
2189
2190 const auto *callee_decl = expr->getCalleeDecl();
2191
2192 if (callee_decl != nullptr) {
2193 const auto *callee_function = callee_decl->getAsFunction();
2194
2195 if ((callee_function == nullptr) || !should_include(callee_function)) {
2196 LOG_DBG("Skipping call expression at {}",
2197 expr->getBeginLoc().printToString(source_manager()));
2198 return false;
2199 }
2200
2201 return should_include(callee_function);
2202 }
2203
2204 return true;
2205}
2206
2208 const clang::CXXMethodDecl *decl) const
2209{
2210 if (!should_include(decl->getParent()))
2211 return false;
2212
2213 if (!diagram().should_include(
2214 common::access_specifier_to_access_t(decl->getAccess())))
2215 return false;
2216
2217 LOG_DBG("Including method {}", decl->getQualifiedNameAsString());
2218
2219 return true;
2220}
2221
2223 const clang::FunctionDecl *decl) const
2224{
2226}
2227
2229 const clang::FunctionTemplateDecl *decl) const
2230{
2231 return visitor_specialization_t::should_include(decl->getAsFunction());
2232}
2233
2235 const clang::ClassTemplateDecl *decl) const
2236{
2238}
2239
2240std::optional<std::pair<unsigned int, std::string>>
2242 const clang::ASTContext &context, const eid_t caller_id,
2243 const clang::Stmt *stmt)
2244{
2245 const auto *raw_comment =
2247
2248 if (raw_comment == nullptr)
2249 return {};
2250
2251 if (!caller_id.is_global() &&
2253 .emplace(caller_id.ast_local_value(), raw_comment)
2254 .second) {
2255 return {};
2256 }
2257
2258 const auto &[decorators, stripped_comment] = decorators::parse(
2259 raw_comment->getFormattedText(sm, sm.getDiagnostics()));
2260
2261 if (stripped_comment.empty())
2262 return {};
2263
2264 return {{raw_comment->getBeginLoc().getHashValue(), stripped_comment}};
2265}
2266} // namespace clanguml::sequence_diagram::visitor