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/package_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"
23
24#include "clang/Basic/Module.h"
25
26#include <spdlog/spdlog.h>
27
28#include <deque>
29
31
36
40 : visitor_specialization_t{sm, diagram, config}
41{
42}
43
45{
46 assert(ns != nullptr);
47
48 if (config().package_type() != config::package_type_t::kNamespace)
49 return true;
50
51 if (ns->isAnonymousNamespace() || ns->isInline())
52 return true;
53
54 auto qualified_name = common::get_qualified_name(*ns);
55
56 if (!diagram().should_include(namespace_{qualified_name}))
57 return true;
58
59 LOG_DBG("Visiting namespace declaration: {}", qualified_name);
60
61 auto package_path = namespace_{qualified_name};
62 auto package_parent = package_path;
63
64 std::string name;
65 if (!package_path.is_empty())
66 name = package_path.name();
67
68 if (!package_parent.is_empty())
69 package_parent.pop_back();
70
71 const auto usn = config().using_namespace();
72
73 auto p = std::make_unique<common::model::package>(usn);
74 package_path = package_path.relative_to(usn);
75
76 p->set_name(name);
77 p->set_namespace(package_parent);
78 p->set_id(common::to_id(*ns));
79 set_source_location(*ns, *p);
80
81 assert(p->id().value() > 0);
82
83 if (diagram().should_include(*p) && !diagram().get(p->id())) {
84 process_comment(*ns, *p);
85
86 p->set_style(p->style_spec());
87
88 for (const auto *attr : ns->attrs()) {
89 if (attr->getKind() == clang::attr::Kind::Deprecated) {
90 p->set_deprecated(true);
91 break;
92 }
93 }
94
95 if (!p->skip()) {
96 LOG_DBG("Adding package {}", p->full_name(false));
97
98 diagram().add(p->path(), std::move(p));
99 }
100 }
101
102 return true;
103}
104
106 clang::FunctionDecl *function_declaration)
107{
108 assert(function_declaration != nullptr);
109
110 // Skip system headers
111 if (source_manager().isInSystemHeader(
112 function_declaration->getSourceRange().getBegin()))
113 return true;
114
115 found_relationships_t relationships;
116
117 find_relationships(function_declaration->getReturnType(), relationships);
118
119 for (const auto *param : function_declaration->parameters()) {
120 if (param != nullptr)
121 find_relationships(param->getType(), relationships);
122 }
123
124 add_relationships(function_declaration, relationships);
125
126 return true;
127}
128
129bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls)
130{
131 assert(cls != nullptr);
132
133 // Skip system headers
134 if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin()))
135 return true;
136
137 // Templated records are handled by VisitClassTemplateDecl()
138 if (cls->isTemplated() || cls->isTemplateDecl() ||
139 (clang::dyn_cast_or_null<clang::ClassTemplateSpecializationDecl>(cls) !=
140 nullptr))
141 return true;
142
143 found_relationships_t relationships;
144
145 if (cls->isCompleteDefinition()) {
146 process_class_declaration(*cls, relationships);
147 add_relationships(cls, relationships);
148 }
149
150 return true;
151}
152
153bool translation_unit_visitor::VisitRecordDecl(clang::RecordDecl *decl)
154{
155 assert(decl != nullptr);
156
157 // Skip system headers
158 if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
159 return true;
160
161 found_relationships_t relationships;
162
163 if (decl->isCompleteDefinition()) {
164 process_record_children(*decl, relationships);
165 add_relationships(decl, relationships);
166 }
167
168 return true;
169}
170
172{
173 assert(decl != nullptr);
174
175 // Skip system headers
176 if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
177 return true;
178
179 found_relationships_t relationships;
180
181 if (decl->isCompleteDefinition()) {
182 add_relationships(decl, relationships);
183 }
184
185 return true;
186}
187
189 clang::ClassTemplateDecl *decl)
190{
191 assert(decl != nullptr);
192
193 // Skip system headers
194 if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
195 return true;
196
197 found_relationships_t relationships;
198
199 util::if_not_null(decl->getTemplatedDecl(),
200 [this, &relationships, decl](const auto *template_decl) {
201 if (template_decl->isCompleteDefinition()) {
202 process_class_declaration(*template_decl, relationships);
203 add_relationships(decl, relationships);
204 }
205 });
206
207 return true;
208}
209
210void translation_unit_visitor::add_relationships(
211 clang::Decl *cls, found_relationships_t &relationships)
212{
213 // If this diagram has directory packages, first make sure that the
214 // package for current directory is already in the model
215 if (config().package_type() == config::package_type_t::kDirectory) {
216 auto file = source_manager().getFilename(cls->getLocation()).str();
217
218 if (file.empty())
219 return;
220
221 auto relative_file = config().make_path_relative(file);
222 relative_file.make_preferred();
223
224 common::model::path parent_path{
225 relative_file.string(), common::model::path_type::kFilesystem};
226 parent_path.pop_back();
227 auto pkg_name = parent_path.name();
228 parent_path.pop_back();
229
230 auto pkg = std::make_unique<common::model::package>(
231 config().using_namespace());
232
233 pkg->set_name(pkg_name);
234 pkg->set_id(get_package_id(cls));
235 set_source_location(*cls, *pkg);
236
237 if (diagram().should_include(*pkg))
238 diagram().add(parent_path, std::move(pkg));
239 }
240 else if (config().package_type() == config::package_type_t::kModule) {
241 const auto *module = cls->getOwningModule();
242
243 if (module == nullptr) {
244 return;
245 }
246
247 std::string module_path_str = module->Name;
248#if LLVM_VERSION_MAJOR < 15
249 if (module->Kind == clang::Module::ModuleKind::PrivateModuleFragment) {
250#else
251 if (module->isPrivateModule()) {
252#endif
253 module_path_str = module->getTopLevelModule()->Name;
254 }
255
256 common::model::path module_path{
257 module_path_str, common::model::path_type::kModule};
258 module_path.pop_back();
259
260 auto relative_module =
261 config().make_module_relative(std::optional{module_path_str});
262
263 common::model::path parent_path{
264 relative_module, common::model::path_type::kModule};
265 auto pkg_name = parent_path.name();
266 parent_path.pop_back();
267
268 auto pkg = std::make_unique<common::model::package>(
269 config().using_module(), common::model::path_type::kModule);
270
271 pkg->set_name(pkg_name);
272 pkg->set_id(get_package_id(cls));
273 // This is for diagram filters
274 pkg->set_module(module_path.to_string());
275 // This is for rendering nested package structure
276 pkg->set_namespace(module_path);
277 set_source_location(*cls, *pkg);
278
279 if (diagram().should_include(*pkg))
280 diagram().add(parent_path, std::move(pkg));
281 }
282
283 auto current_package_id = get_package_id(cls);
284
285 if (current_package_id.value() == 0)
286 // These are relationships to a global namespace, and we don't care
287 // about those
288 return;
289
290 auto current_package = diagram().get(current_package_id);
291
292 if (current_package) {
293 std::vector<eid_t> parent_ids =
294 get_parent_package_ids(current_package_id);
295
296 for (const auto &dependency : relationships) {
297 const auto destination_id = std::get<0>(dependency);
298
299 // Skip dependency relationships to parent packages
300 if (util::contains(parent_ids, destination_id))
301 continue;
302
303 // Skip dependency relationship to child packages
304 if (util::contains(
305 get_parent_package_ids(destination_id), current_package_id))
306 continue;
307
308 relationship r{relationship_t::kDependency, destination_id,
310 if (destination_id != current_package_id)
311 current_package.value().add_relationship(std::move(r));
312 }
313 }
314}
315
316eid_t translation_unit_visitor::get_package_id(const clang::Decl *cls)
317{
318 if (config().package_type() == config::package_type_t::kNamespace) {
319 const auto *namespace_context =
320 cls->getDeclContext()->getEnclosingNamespaceContext();
321 if (namespace_context != nullptr && namespace_context->isNamespace()) {
322 return common::to_id(
323 *llvm::cast<clang::NamespaceDecl>(namespace_context));
324 }
325
326 return {};
327 }
328
329 if (config().package_type() == config::package_type_t::kModule) {
330 const auto *module = cls->getOwningModule();
331 if (module != nullptr) {
332 std::string module_path = module->Name;
333#if LLVM_VERSION_MAJOR < 15
334 if (module->Kind ==
335 clang::Module::ModuleKind::PrivateModuleFragment) {
336#else
337 if (module->isPrivateModule()) {
338#endif
339 module_path = module->getTopLevelModule()->Name;
340 }
341 return common::to_id(module_path);
342 }
343
344 return {};
345 }
346
347 auto file =
348 source_manager().getFilename(cls->getSourceRange().getBegin()).str();
349 auto relative_file = config().make_path_relative(file);
350 relative_file.make_preferred();
351 common::model::path parent_path{
352 relative_file.string(), common::model::path_type::kFilesystem};
353 parent_path.pop_back();
354
355 return common::to_id(parent_path.to_string());
356}
357
358void translation_unit_visitor::process_class_declaration(
359 const clang::CXXRecordDecl &cls, found_relationships_t &relationships)
360{
361 // Look for dependency relationships in class children (fields, methods)
362 process_class_children(cls, relationships);
363
364 // Look for dependency relationships in class bases
365 process_class_bases(cls, relationships);
366}
367
368void translation_unit_visitor::process_class_children(
369 const clang::CXXRecordDecl &cls, found_relationships_t &relationships)
370{
371 // Iterate over class methods (both regular and static)
372 for (const auto *method : cls.methods()) {
373 if (method != nullptr) {
374 process_method(*method, relationships);
375 }
376 }
377
378 if (const auto *decl_context =
379 clang::dyn_cast_or_null<clang::DeclContext>(&cls);
380 decl_context != nullptr) {
381 // Iterate over class template methods
382 for (auto const *decl_iterator : decl_context->decls()) {
383 auto const *method_template =
384 llvm::dyn_cast_or_null<clang::FunctionTemplateDecl>(
385 decl_iterator);
386 if (method_template == nullptr)
387 continue;
388
389 process_template_method(*method_template, relationships);
390 }
391 }
392
393 // Iterate over regular class fields
394 for (const auto *field : cls.fields()) {
395 if (field != nullptr)
396 process_field(*field, relationships);
397 }
398
399 // Static fields have to be processed by iterating over variable
400 // declarations
401 for (const auto *decl : cls.decls()) {
402 if (decl->getKind() == clang::Decl::Var) {
403 const clang::VarDecl *variable_declaration{
404 clang::dyn_cast_or_null<clang::VarDecl>(decl)};
405 if ((variable_declaration != nullptr) &&
406 variable_declaration->isStaticDataMember()) {
407 process_static_field(*variable_declaration, relationships);
408 }
409 }
410 }
411
412 if (cls.isCompleteDefinition())
413 for (const auto *friend_declaration : cls.friends()) {
414 if (friend_declaration != nullptr)
415 process_friend(*friend_declaration, relationships);
416 }
417}
418
419void translation_unit_visitor::process_class_bases(
420 const clang::CXXRecordDecl &cls, found_relationships_t &relationships)
421{
422 for (const auto &base : cls.bases()) {
423 find_relationships(base.getType(), relationships);
424 }
425}
426
427void translation_unit_visitor::process_method(
428 const clang::CXXMethodDecl &method, found_relationships_t &relationships)
429{
430 find_relationships(method.getReturnType(), relationships);
431
432 for (const auto *param : method.parameters()) {
433 if (param != nullptr)
434 find_relationships(param->getType(), relationships);
435 }
436}
437
438void translation_unit_visitor::process_record_children(
439 const clang::RecordDecl &cls, found_relationships_t &relationships)
440{
441 if (const auto *decl_context =
442 clang::dyn_cast_or_null<clang::DeclContext>(&cls);
443 decl_context != nullptr) {
444 // Iterate over class template methods
445 for (auto const *decl_iterator : decl_context->decls()) {
446 auto const *method_template =
447 llvm::dyn_cast_or_null<clang::FunctionTemplateDecl>(
448 decl_iterator);
449 if (method_template == nullptr)
450 continue;
451
452 process_template_method(*method_template, relationships);
453 }
454 }
455
456 // Iterate over regular class fields
457 for (const auto *field : cls.fields()) {
458 if (field != nullptr)
459 process_field(*field, relationships);
460 }
461
462 // Static fields have to be processed by iterating over variable
463 // declarations
464 for (const auto *decl : cls.decls()) {
465 if (decl->getKind() == clang::Decl::Var) {
466 const clang::VarDecl *variable_declaration{
467 clang::dyn_cast_or_null<clang::VarDecl>(decl)};
468 if ((variable_declaration != nullptr) &&
469 variable_declaration->isStaticDataMember()) {
470 process_static_field(*variable_declaration, relationships);
471 }
472 }
473 }
474}
475
476void translation_unit_visitor::process_template_method(
477 const clang::FunctionTemplateDecl &method,
478 found_relationships_t &relationships)
479{
480 // TODO: For now skip implicitly default methods
481 // in the future, add config option to choose
482 if (method.getTemplatedDecl()->isDefaulted() &&
483 !method.getTemplatedDecl()->isExplicitlyDefaulted())
484 return;
485
486 find_relationships(
487 method.getTemplatedDecl()->getReturnType(), relationships);
488
489 for (const auto *param : method.getTemplatedDecl()->parameters()) {
490 if (param != nullptr) {
491 find_relationships(param->getType(), relationships);
492 }
493 }
494}
495
496void translation_unit_visitor::process_field(
497 const clang::FieldDecl &field_declaration,
498 found_relationships_t &relationships)
499{
500 find_relationships(field_declaration.getType(), relationships,
501 relationship_t::kDependency);
502}
503
504void translation_unit_visitor::process_static_field(
505 const clang::VarDecl &field_declaration,
506 found_relationships_t &relationships)
507{
508 find_relationships(field_declaration.getType(), relationships,
509 relationship_t::kDependency);
510}
511
512void translation_unit_visitor::process_friend(
513 const clang::FriendDecl &friend_declaration,
514 found_relationships_t &relationships)
515{
516 if (const auto *friend_type_declaration =
517 friend_declaration.getFriendDecl()) {
518 if (friend_type_declaration->isTemplateDecl()) {
519 // TODO
520 }
521 }
522 else if (const auto *friend_type = friend_declaration.getFriendType()) {
523 find_relationships(friend_type->getType(), relationships);
524 }
525}
526
527bool translation_unit_visitor::find_relationships(const clang::QualType &type,
528 found_relationships_t &relationships, relationship_t relationship_hint)
529{
530 bool result{false};
531
532 if (type->isVoidType() || type->isVoidPointerType()) {
533 // pass
534 }
535 else if (type->isPointerType()) {
536 relationship_hint = relationship_t::kAssociation;
537 find_relationships(
538 type->getPointeeType(), relationships, relationship_hint);
539 }
540 else if (type->isRValueReferenceType()) {
541 relationship_hint = relationship_t::kAggregation;
542 find_relationships(
543 type.getNonReferenceType(), relationships, relationship_hint);
544 }
545 else if (type->isLValueReferenceType()) {
546 relationship_hint = relationship_t::kAssociation;
547 find_relationships(
548 type.getNonReferenceType(), relationships, relationship_hint);
549 }
550 else if (type->isArrayType()) {
551 find_relationships(type->getAsArrayTypeUnsafe()->getElementType(),
552 relationships, relationship_t::kAggregation);
553 }
554 else if (type->isEnumeralType()) {
555 if (const auto *enum_type = type->getAs<clang::EnumType>();
556 enum_type != nullptr) {
557 if (const auto *enum_decl = enum_type->getDecl();
558 enum_decl != nullptr)
559 relationships.emplace_back(
560 get_package_id(enum_decl), relationship_hint);
561 }
562 }
563 else if (const auto *template_specialization_type =
564 type->getAs<clang::TemplateSpecializationType>()) {
565 if (template_specialization_type != nullptr) {
566 // Add dependency to template declaration
567 relationships.emplace_back(
568 get_package_id(template_specialization_type->getTemplateName()
569 .getAsTemplateDecl()),
570 relationship_hint);
571
572 // Add dependencies to template arguments
573 for (const auto &template_argument :
574 template_specialization_type->template_arguments()) {
575 const auto template_argument_kind = template_argument.getKind();
576 if (template_argument_kind ==
577 clang::TemplateArgument::ArgKind::Integral) {
578 // pass
579 }
580 else if (template_argument_kind ==
581 clang::TemplateArgument::ArgKind::Null) {
582 // pass
583 }
584 else if (template_argument_kind ==
585 clang::TemplateArgument::ArgKind::Expression) {
586 // pass
587 }
588 else if (template_argument.getKind() ==
589 clang::TemplateArgument::ArgKind::NullPtr) {
590 // pass
591 }
592 else if (template_argument_kind ==
593 clang::TemplateArgument::ArgKind::Template) {
594 // pass
595 }
596 else if (template_argument_kind ==
597 clang::TemplateArgument::ArgKind::TemplateExpansion) {
598 // pass
599 }
600 else if (const auto *function_type =
601 template_argument.getAsType()
602 ->getAs<clang::FunctionProtoType>();
603 function_type != nullptr) {
604 for (const auto &param_type :
605 function_type->param_types()) {
606 result = find_relationships(param_type, relationships,
607 relationship_t::kDependency);
608 }
609 }
610 else if (template_argument_kind ==
611 clang::TemplateArgument::ArgKind::Type) {
612 result = find_relationships(template_argument.getAsType(),
613 relationships, relationship_hint);
614 }
615 }
616 }
617 }
618 else if (type->isRecordType()) {
619 if (const auto *cxxrecord_decl = type->getAsCXXRecordDecl();
620 cxxrecord_decl != nullptr) {
621 if (config().package_type() == config::package_type_t::kNamespace) {
622 const auto *namespace_context =
623 cxxrecord_decl->getEnclosingNamespaceContext();
624 if (namespace_context != nullptr &&
625 namespace_context->isNamespace()) {
626 const auto *namespace_declaration =
627 clang::cast<clang::NamespaceDecl>(namespace_context);
628
629 if (namespace_declaration != nullptr &&
630 diagram().should_include(
632 *namespace_declaration)})) {
633 const auto target_id = get_package_id(cxxrecord_decl);
634 relationships.emplace_back(
635 target_id, relationship_hint);
636 result = true;
637 }
638 }
639 }
640 else if (config().package_type() ==
642 const auto *module = cxxrecord_decl->getOwningModule();
643 if (module != nullptr) {
644 const auto target_id = get_package_id(cxxrecord_decl);
645 relationships.emplace_back(target_id, relationship_hint);
646 result = true;
647 }
648 }
649 else {
650 if (diagram().should_include(
652 *type->getAsCXXRecordDecl())})) {
653 const auto target_id =
654 get_package_id(type->getAsCXXRecordDecl());
655 relationships.emplace_back(target_id, relationship_hint);
656 result = true;
657 }
658 }
659 }
660 else if (const auto *record_decl = type->getAsRecordDecl();
661 record_decl != nullptr) {
662 // This is only possible for plain C translation unit, so we
663 // don't need to consider namespaces or modules here
664 if (config().package_type() == config::package_type_t::kDirectory) {
665 if (diagram().should_include(
666 namespace_{common::get_qualified_name(*record_decl)})) {
667 const auto target_id = get_package_id(record_decl);
668 relationships.emplace_back(target_id, relationship_hint);
669 result = true;
670 }
671 }
672 }
673 }
674
675 return result;
676}
677
678void translation_unit_visitor::finalize() { }
679
680std::vector<eid_t> translation_unit_visitor::get_parent_package_ids(eid_t id)
681{
682 std::vector<eid_t> parent_ids;
683 std::optional<eid_t> parent_id = id;
684
685 while (parent_id.has_value()) {
686 const auto pid = parent_id.value(); // NOLINT
687 parent_ids.push_back(pid);
688 auto parent = this->diagram().get(pid);
689 if (parent)
690 parent_id = parent.value().parent_element_id();
691 else
692 break;
693 }
694
695 return parent_ids;
696}
697
698} // namespace clanguml::package_diagram::visitor