From 1fe9918c1aa10505622272c9c64dac3c6febf2e3 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 23 Oct 2022 22:36:53 +0200 Subject: [PATCH] Initial refactoring of sequence diagram visitor to include participants --- src/common/types.h | 4 + .../plantuml/sequence_diagram_generator.cc | 29 +- src/sequence_diagram/model/diagram.cc | 14 + src/sequence_diagram/model/diagram.h | 35 +- src/sequence_diagram/model/message.h | 31 +- src/sequence_diagram/model/participant.cc | 131 +++++ src/sequence_diagram/model/participant.h | 177 +++++++ .../visitor/translation_unit_visitor.cc | 493 ++++++++++++++---- .../visitor/translation_unit_visitor.h | 211 +++++++- tests/t20004/.clang-uml | 14 + tests/t20004/t20004.cc | 18 + tests/t20004/test_case.h | 47 ++ tests/test_cases.cc | 1 + 13 files changed, 1089 insertions(+), 116 deletions(-) create mode 100644 src/sequence_diagram/model/participant.cc create mode 100644 src/sequence_diagram/model/participant.h create mode 100644 tests/t20004/.clang-uml create mode 100644 tests/t20004/t20004.cc create mode 100644 tests/t20004/test_case.h diff --git a/src/common/types.h b/src/common/types.h index 9a6b6ef0..0cb38440 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -36,6 +36,10 @@ public: { } + optional_ref(T *value) { value_ = value; } + + optional_ref(const T *value) { value_ = value; } + optional_ref(T &value) { value_ = &value; } optional_ref(const T &value) { value_ = &value; } diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index f04ed517..6a7912b7 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -38,15 +38,15 @@ generator::generator( void generator::generate_call(const message &m, std::ostream &ostr) const { - const auto from = m_config.using_namespace().relative(m.from); - const auto to = m_config.using_namespace().relative(m.to); + const auto from = m_config.using_namespace().relative(m.from_name); + const auto to = m_config.using_namespace().relative(m.to_name); if (from.empty() || to.empty()) { LOG_DBG("Skipping empty call from '{}' to '{}'", from, to); return; } - auto message = m.message; + auto message = m.message_name; if (!message.empty()) { message = m_config.using_namespace().relative(message); message += "()"; @@ -57,7 +57,7 @@ void generator::generate_call(const message &m, std::ostream &ostr) const << to << "\" : " << message << std::endl; LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from, - m.from_usr, to, m.to_usr); + m.from_name, to, m.to_name); } void generator::generate_return(const message &m, std::ostream &ostr) const @@ -65,8 +65,8 @@ void generator::generate_return(const message &m, std::ostream &ostr) const // Add return activity only for messages between different actors and // only if the return type is different than void if ((m.from != m.to) && (m.return_type != "void")) { - const auto from = m_config.using_namespace().relative(m.from); - const auto to = m_config.using_namespace().relative(m.to); + const auto from = m_config.using_namespace().relative(m.from_name); + const auto to = m_config.using_namespace().relative(m.to_name); ostr << '"' << to << "\" " << common::generators::plantuml::to_plantuml(message_t::kReturn) @@ -77,17 +77,25 @@ void generator::generate_return(const message &m, std::ostream &ostr) const void generator::generate_activity(const activity &a, std::ostream &ostr) const { for (const auto &m : a.messages) { - const auto to = m_config.using_namespace().relative(m.to); + const auto to = m_config.using_namespace().relative(m.to_name); if (to.empty()) continue; + LOG_DBG("Generating message {} --> {}", + m.from_name, m.to_name); generate_call(m, ostr); ostr << "activate " << '"' << to << '"' << std::endl; - if (m_model.sequences.find(m.to_usr) != m_model.sequences.end()) - generate_activity(m_model.sequences[m.to_usr], ostr); + if (m_model.sequences.find(m.to) != m_model.sequences.end()) { + LOG_DBG("Creating activity {} --> {} - missing sequence {}", + m.from_name, m.to_name, m.to); + generate_activity(m_model.sequences[m.to], ostr); + } + else + LOG_DBG("Skipping activity {} --> {} - missing sequence {}", + m.from_name, m.to_name, m.to); generate_return(m, ostr); @@ -97,6 +105,8 @@ void generator::generate_activity(const activity &a, std::ostream &ostr) const void generator::generate(std::ostream &ostr) const { + m_model.print(); + ostr << "@startuml" << std::endl; generate_plantuml_directives(ostr, m_config.puml().before); @@ -107,6 +117,7 @@ void generator::generate(std::ostream &ostr) const for (const auto &[k, v] : m_model.sequences) { std::string vfrom = v.from; if (vfrom == sf.location) { + LOG_DBG("Found sequence diagram start point: {}", k); start_from = k; break; } diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index 510e7d63..0d42987b 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -58,6 +58,20 @@ inja::json diagram::context() const return ctx; } +void diagram::print() const +{ + for (const auto &[from_id, act] : sequences) { + LOG_DBG("Sequence id={}:", from_id); + LOG_DBG(" Activity id={}, from={}:", act.usr, act.from); + for (const auto &message : act.messages) { + LOG_DBG( + " Message from={}, from_id={}, to={}, to_id={}, name={}", + message.from_name, message.from, message.to_name, message.to, + message.message_name); + } + } +} + } namespace clanguml::common::model { diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index 9190b12e..4579917a 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -20,6 +20,7 @@ #include "activity.h" #include "common/model/diagram.h" #include "common/types.h" +#include "participant.h" #include #include @@ -47,9 +48,41 @@ public: inja::json context() const override; + void print() const; + bool started{false}; - std::map sequences; + template + common::optional_ref get_participant( + common::model::diagram_element::id_t id) + { + if (participants.find(id) == participants.end()) { + return {}; + } + + return common::optional_ref( + static_cast(participants.at(id).get())); + } + + void add_participant(std::unique_ptr p) + { + LOG_DBG("Adding {} participant: {}, {} [{}]", p->type_name(), + p->full_name(false), p->id(), + p->type_name() == "method" + ? dynamic_cast(p.get())->method_name() + : ""); + + const auto pid = p->id(); + + if (participants.find(pid) == participants.end()) { + participants.emplace(pid, std::move(p)); + } + } + + std::map sequences; + + std::map> + participants; }; } diff --git a/src/sequence_diagram/model/message.h b/src/sequence_diagram/model/message.h index f092dc23..606fa95a 100644 --- a/src/sequence_diagram/model/message.h +++ b/src/sequence_diagram/model/message.h @@ -25,13 +25,34 @@ namespace clanguml::sequence_diagram::model { struct message { + message() + : from{} + , from_name{} + , to{} + , to_name{} + , message_name{} + , return_type{} + , line{} + { + } + common::model::message_t type; - std::string from; - std::uint_least64_t from_usr{}; - std::string to; - std::int64_t to_usr{}; - std::string message; + + /// Source participant id + std::uint_least64_t from; + std::string from_name; + // std::uint_least64_t from_usr{}; + + /// Target participant id + std::uint_least64_t to; + std::string to_name; + + // std::int64_t to_usr{}; + + std::string message_name; + std::string return_type; + unsigned int line; }; diff --git a/src/sequence_diagram/model/participant.cc b/src/sequence_diagram/model/participant.cc new file mode 100644 index 00000000..e4a28399 --- /dev/null +++ b/src/sequence_diagram/model/participant.cc @@ -0,0 +1,131 @@ +/** + * src/sequence_diagram/model/participant.cc + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "participant.h" + +namespace clanguml::sequence_diagram::model { +class_::class_(const common::model::namespace_ &using_namespace) + : participant{using_namespace} +{ +} + +bool class_::is_struct() const { return is_struct_; } + +void class_::is_struct(bool is_struct) { is_struct_ = is_struct; } + +bool class_::is_template() const { return is_template_; } + +void class_::is_template(bool is_template) { is_template_ = is_template; } + +bool class_::is_template_instantiation() const +{ + return is_template_instantiation_; +} + +void class_::is_template_instantiation(bool is_template_instantiation) +{ + is_template_instantiation_ = is_template_instantiation; +} + +void class_::add_template(class_diagram::model::template_parameter tmplt) +{ + templates_.emplace_back(std::move(tmplt)); +} + +const std::vector & +class_::templates() const +{ + return templates_; +} + +std::string class_::full_name_no_ns() const +{ + using namespace clanguml::util; + + std::ostringstream ostr; + + ostr << name(); + + render_template_params(ostr, false); + + return ostr.str(); +} + +std::string class_::full_name(bool relative) const +{ + using namespace clanguml::util; + using clanguml::common::model::namespace_; + + std::ostringstream ostr; + + ostr << name_and_ns(); + render_template_params(ostr, relative); + + std::string res; + + if (relative) + res = using_namespace().relative(ostr.str()); + else + res = ostr.str(); + + if (res.empty()) + return "<>"; + + return res; +} + +std::ostringstream &class_::render_template_params( + std::ostringstream &ostr, bool relative) const +{ + using clanguml::common::model::namespace_; + + if (!templates_.empty()) { + std::vector tnames; + std::vector tnames_simplified; + + std::transform(templates_.cbegin(), templates_.cend(), + std::back_inserter(tnames), + [ns = using_namespace(), relative]( + const auto &tmplt) { return tmplt.to_string(ns, relative); }); + + ostr << fmt::format("<{}>", fmt::join(tnames, ",")); + } + + return ostr; +} + +function::function(const common::model::namespace_ &using_namespace) + : participant{using_namespace} +{ +} + +std::string function::full_name(bool relative) const +{ + return element::full_name(relative) + "()"; +} + +std::string function::full_name_no_ns() const +{ + return element::full_name_no_ns() + "()"; +} + +method::method(const common::model::namespace_ &using_namespace) + : participant{using_namespace} +{ +} +} \ No newline at end of file diff --git a/src/sequence_diagram/model/participant.h b/src/sequence_diagram/model/participant.h new file mode 100644 index 00000000..31c3f525 --- /dev/null +++ b/src/sequence_diagram/model/participant.h @@ -0,0 +1,177 @@ +/** + * src/sequence_diagram/model/participant.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "class_diagram/model/template_parameter.h" +#include "class_diagram/model/type_alias.h" +#include "common/model/element.h" + +#include +#include + +namespace clanguml::sequence_diagram::model { + +struct participant : public common::model::element, + public common::model::stylable_element { + enum class stereotype_t { + participant = 0, + actor, + boundary, + control, + entity, + database, + collections, + queue + }; + + using common::model::element::element; + + participant(const participant &) = delete; + participant(participant &&) noexcept = delete; + participant &operator=(const participant &) = delete; + participant &operator=(participant &&) = delete; + + std::string type_name() const override { return "participant"; } + + stereotype_t stereotype_{stereotype_t::participant}; +}; +// +// struct template_trait { +// void set_base_template(const std::string &full_name) +// { +// base_template_full_name_ = full_name; +// } +// std::string base_template() const { return base_template_full_name_; } +// +// void add_template(class_diagram::model::template_parameter tmplt) +// { +// templates_.push_back(std::move(tmplt)); +// } +// +// const std::vector & +// templates() const +// { +// return templates_; +// } +// +// std::vector templates_; +// std::string base_template_full_name_; +//}; + +struct class_ : public participant { +public: + class_(const common::model::namespace_ &using_namespace); + + class_(const class_ &) = delete; + class_(class_ &&) noexcept = delete; + class_ &operator=(const class_ &) = delete; + class_ &operator=(class_ &&) = delete; + + std::string type_name() const override { return "class"; } + + bool is_struct() const; + void is_struct(bool is_struct); + + bool is_template() const; + void is_template(bool is_template); + + bool is_template_instantiation() const; + void is_template_instantiation(bool is_template_instantiation); + + void add_template(class_diagram::model::template_parameter tmplt); + + const std::vector & + templates() const; + + void set_base_template(const std::string &full_name); + std::string base_template() const; + + friend bool operator==(const class_ &l, const class_ &r); + + std::string full_name(bool relative = true) const override; + + std::string full_name_no_ns() const override; + + bool is_abstract() const; + + bool is_alias() const { return is_alias_; } + + void is_alias(bool alias) { is_alias_ = alias; } + + int calculate_template_specialization_match( + const class_ &other, const std::string &full_name) const; + +private: + std::ostringstream &render_template_params( + std::ostringstream &ostr, bool relative) const; + + bool is_struct_{false}; + bool is_template_{false}; + bool is_template_instantiation_{false}; + bool is_alias_{false}; + std::vector templates_; + std::string base_template_full_name_; + std::map + type_aliases_; + + std::string full_name_; +}; + +struct function : public participant { + function(const common::model::namespace_ &using_namespace); + + function(const function &) = delete; + function(function &&) noexcept = delete; + function &operator=(const function &) = delete; + function &operator=(function &&) = delete; + + std::string type_name() const override { return "function"; } + + std::string full_name(bool relative = true) const override; + + std::string full_name_no_ns() const override; +}; + +struct method : public participant { + method(const common::model::namespace_ &using_namespace); + + method(const function &) = delete; + method(method &&) noexcept = delete; + method &operator=(const method &) = delete; + method &operator=(method &&) = delete; + + std::string type_name() const override { return "method"; } + + const std::string method_name() const { + return method_name_; + } + + void set_method_name(const std::string& name) { method_name_ = name; } + + void set_class_id(diagram_element::id_t id) { class_id_ = id; } + + diagram_element::id_t class_id() const { return class_id_; } + +private: + diagram_element::id_t class_id_; + std::string method_name_; +}; + +struct function_template : public participant { }; + +} diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index fde34e68..e7082a8c 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -51,14 +51,10 @@ std::string to_string(const clang::FunctionTemplateDecl *decl) translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm, clanguml::sequence_diagram::model::diagram &diagram, const clanguml::config::sequence_diagram &config) - : source_manager_{sm} + : common::visitor::translation_unit_visitor{sm, config} , diagram_{diagram} , config_{config} - , current_class_decl_{nullptr} - , current_method_decl_{nullptr} - , current_function_decl_{nullptr} - , current_function_template_decl_{nullptr} - + , call_expression_context_{} { } @@ -75,49 +71,210 @@ translation_unit_visitor::config() const bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) { - current_class_decl_ = cls; + // Skip system headers + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) + return true; + + if (!diagram().should_include(cls->getQualifiedNameAsString())) + return true; + + call_expression_context_.reset(); + + if (!diagram().should_include(cls->getQualifiedNameAsString())) { + return true; + } + + if (cls->isTemplated() && cls->getDescribedTemplate()) { + // If the described templated of this class is already in the model + // skip it: + if (get_ast_local_id(cls->getDescribedTemplate()->getID())) + return true; + } + + // TODO: Add support for classes defined in function/method bodies + if (cls->isLocalClass()) + return true; + + // Build the class declaration and store it in the diagram, even + // if we don't need it for any of the participants of this diagram + auto c_ptr = create_class_declaration(cls); + + if (!c_ptr) + return true; + + const auto cls_id = c_ptr->id(); + + set_ast_local_id(cls->getID(), cls_id); + + auto &class_model = + diagram() + .get_participant(cls_id) + .has_value() + ? *diagram() + .get_participant(cls_id) + .get() + : *c_ptr; + + auto id = class_model.id(); + if (!cls->isCompleteDefinition()) { + forward_declarations_.emplace(id, std::move(c_ptr)); + return true; + } + else { + forward_declarations_.erase(id); + } + + if (diagram_.should_include(class_model)) { + LOG_DBG("Adding class {} with id {}", class_model.full_name(false), + class_model.id()); + + assert(class_model.id() == cls_id); + + call_expression_context_.set_caller_id(cls_id); + call_expression_context_.update(cls); + + diagram_.add_participant(std::move(c_ptr)); + } + else { + LOG_DBG("Skipping class {} with id {}", class_model.full_name(), + class_model.id()); + } + + // call_expression_context_.current_class_decl_ = cls; + // call_expression_context_.current_class_ = process_class(cls); return true; } -bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *method) +bool translation_unit_visitor::VisitClassTemplateDecl( + clang::ClassTemplateDecl *cls) { - current_method_decl_ = method; + if (source_manager().isInSystemHeader(cls->getSourceRange().getBegin())) + return true; + + if (!diagram().should_include(cls->getQualifiedNameAsString())) + return true; + + LOG_DBG("= Visiting class template declaration {} at {}", + cls->getQualifiedNameAsString(), + cls->getLocation().printToString(source_manager())); + + auto c_ptr = create_class_declaration(cls->getTemplatedDecl()); + + if (!c_ptr) + return true; + + // Override the id with the template id, for now we don't care about the + // underlying templated class id + + process_template_parameters(*cls, *c_ptr); + + const auto cls_full_name = c_ptr->full_name(false); + const auto id = common::to_id(cls_full_name); + + c_ptr->set_id(id); + + set_ast_local_id(cls->getID(), id); + + if (!cls->getTemplatedDecl()->isCompleteDefinition()) { + forward_declarations_.emplace(id, std::move(c_ptr)); + return true; + } + else { + forward_declarations_.erase(id); + } + + if (diagram_.should_include(*c_ptr)) { + const auto name = c_ptr->full_name(); + LOG_DBG("Adding class template {} with id {}", name, id); + + call_expression_context_.set_caller_id(id); + call_expression_context_.update(cls); + + diagram_.add_participant(std::move(c_ptr)); + } return true; } -bool translation_unit_visitor::VisitFunctionDecl( - clang::FunctionDecl *function_declaration) +bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m) { - if (!function_declaration->isCXXClassMember()) - current_class_decl_ = nullptr; + if (call_expression_context_.current_class_decl_ == nullptr || + call_expression_context_.current_class_template_decl_ == nullptr) + return true; - current_function_decl_ = function_declaration; + call_expression_context_.update(m); - // Check if this function is a part of template function declaration, - // If no - reset the current_function_template_decl_ - if (current_function_template_decl_ && - current_function_template_decl_->getQualifiedNameAsString() != - function_declaration->getQualifiedNameAsString()) - current_function_template_decl_ = nullptr; + auto m_ptr = std::make_unique( + config().using_namespace()); - LOG_DBG("Visiting function {}", - current_function_decl_->getQualifiedNameAsString()); + common::model::namespace_ ns{m->getQualifiedNameAsString()}; + m_ptr->set_method_name(ns.name()); + ns.pop_back(); + m_ptr->set_name(ns.name()); + ns.pop_back(); + m_ptr->set_namespace(ns); + m_ptr->set_id(common::to_id(m->getQualifiedNameAsString())); + + m_ptr->set_class_id( + get_ast_local_id(call_expression_context_.current_class_decl_->getID()) + .value()); + + call_expression_context_.update(m); + + call_expression_context_.set_caller_id(m_ptr->id()); + + set_ast_local_id(m->getID(), m_ptr->id()); + + diagram().add_participant(std::move(m_ptr)); + + return true; +} + +bool translation_unit_visitor::VisitFunctionDecl(clang::FunctionDecl *f) +{ + if (f->isCXXClassMember()) + return true; + + const auto function_name = f->getQualifiedNameAsString(); + + if (!diagram().should_include(function_name)) + return true; + + LOG_DBG("Visiting function declaration {}", function_name); + + auto f_ptr = std::make_unique( + config().using_namespace()); + + common::model::namespace_ ns{function_name}; + f_ptr->set_name(ns.name()); + ns.pop_back(); + f_ptr->set_namespace(ns); + f_ptr->set_id(common::to_id(function_name)); + + call_expression_context_.update(f); + + call_expression_context_.set_caller_id(f_ptr->id()); + + set_ast_local_id(f->getID(), f_ptr->id()); + + // TODO: Handle overloaded functions with different arguments + diagram().add_participant(std::move(f_ptr)); return true; } bool translation_unit_visitor::VisitFunctionTemplateDecl( - clang::FunctionTemplateDecl *function_declaration) + clang::FunctionTemplateDecl *function_template) { - if (!function_declaration->isCXXClassMember()) - current_class_decl_ = nullptr; + const auto function_name = function_template->getQualifiedNameAsString(); - current_function_template_decl_ = function_declaration; + if (!diagram().should_include(function_name)) + return true; - LOG_DBG("Visiting template function {}", - current_function_template_decl_->getQualifiedNameAsString()); + LOG_DBG("Visiting function template declaration {}", function_name); + + call_expression_context_.update(function_template); return true; } @@ -139,68 +296,33 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) if (clang::dyn_cast_or_null(expr)) return true; - // Skip if current class was excluded in the config - - if (current_class_decl_) { - std::string current_class_qualified_name = - current_class_decl_->getQualifiedNameAsString(); - if (!diagram().should_include(current_class_qualified_name)) - return true; - } - - // Skip if current function was excluded in the config - if (current_function_decl_) { - std::string current_function_qualified_name = - current_function_decl_->getQualifiedNameAsString(); - if (!diagram().should_include(current_function_qualified_name)) - return true; - } + if (!call_expression_context_.valid()) + return true; message m; m.type = message_t::kCall; + m.from = call_expression_context_.caller_id(); + m.from_name = diagram().participants.at(m.from)->full_name(false); - if (current_class_decl_ != nullptr) { - // Handle call expression within some class method - assert(current_method_decl_ != nullptr); - m.from = current_class_decl_->getQualifiedNameAsString(); - m.from_usr = current_method_decl_->getID(); - } - else if (current_function_template_decl_ != nullptr && - current_function_decl_ != nullptr) { - - m.from = to_string(current_function_template_decl_); - m.from_usr = current_function_template_decl_->getID(); - } - else if (current_function_decl_ != nullptr) { - // Handle call expression within free function - m.from = current_function_decl_->getQualifiedNameAsString() + "()"; - m.from_usr = current_function_decl_->getID(); - } - else { - m.from = current_function_template_decl_->getQualifiedNameAsString(); - std::vector params; - for (const auto &template_parameter : - *current_function_template_decl_->getTemplateParameters()) { - params.push_back(template_parameter->getNameAsString()); - } - m.from += fmt::format("<{}>", fmt::join(params, ",")); - } - - const auto ¤t_ast_context = current_class_decl_ - ? current_class_decl_->getASTContext() - : current_function_decl_->getASTContext(); + const auto ¤t_ast_context = + call_expression_context_.get_ast_context(); if (const auto *operator_call_expr = clang::dyn_cast_or_null(expr); operator_call_expr != nullptr) { // TODO: Handle C++ operator calls } + // + // class method + // else if (const auto *method_call_expr = clang::dyn_cast_or_null(expr); method_call_expr != nullptr) { // Get callee declaration as methods parent const auto *method_decl = method_call_expr->getMethodDecl(); + std::string method_name = method_decl->getQualifiedNameAsString(); + const auto *callee_decl = method_decl ? method_decl->getParent() : nullptr; @@ -209,9 +331,12 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) callee_decl->getQualifiedNameAsString()))) return true; - m.to = callee_decl->getQualifiedNameAsString(); - m.to_usr = method_decl->getID(); - m.message = method_decl->getNameAsString(); + // TODO: The method can be called before it's declaration has been + // encountered by the visitor - for now it's not a problem + // as overloaded methods are not supported + m.to = common::to_id(method_decl->getQualifiedNameAsString()); + m.to_name = callee_decl->getQualifiedNameAsString(); + m.message_name = method_decl->getNameAsString(); m.return_type = method_call_expr->getCallReturnType(current_ast_context) .getAsString(); } @@ -234,6 +359,7 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) function_call_expr->getCallee()); if (unresolved_expr) { + unresolved_expr->dump(); for (const auto *decl : unresolved_expr->decls()) { if (clang::dyn_cast_or_null< clang::FunctionTemplateDecl>(decl)) { @@ -241,9 +367,9 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) auto *ftd = clang::dyn_cast_or_null< clang::FunctionTemplateDecl>(decl); - m.to = to_string(ftd); - m.message = to_string(ftd); - m.to_usr = ftd->getID(); + m.to_name = to_string(ftd); + m.message_name = to_string(ftd); + m.to = get_ast_local_id(ftd->getID()).value(); break; } } @@ -262,9 +388,9 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) if (!callee_function) return true; - m.to = callee_function->getQualifiedNameAsString() + "()"; - m.message = callee_function->getNameAsString(); - m.to_usr = callee_function->getID(); + m.to_name = callee_function->getQualifiedNameAsString() + "()"; + m.message_name = callee_function->getNameAsString(); + m.to = get_ast_local_id(callee_function->getID()).value(); } m.return_type = function_call_expr->getCallReturnType(current_ast_context) @@ -274,20 +400,197 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) return true; } - if (diagram().sequences.find(m.from_usr) == diagram().sequences.end()) { - activity a; - a.usr = m.from_usr; - a.from = m.from; - diagram().sequences.insert({m.from_usr, std::move(a)}); + if(m.from > 0 && m.to > 0) { + if (diagram().sequences.find(m.from) == diagram().sequences.end()) { + activity a; + a.usr = m.from; + a.from = m.from_name; + diagram().sequences.insert({m.from, std::move(a)}); + } + + LOG_DBG("Found call {} from {} [{}] to {} [{}] ", m.message_name, + m.from_name, m.from, m.to_name, m.to); + + diagram().sequences[m.from].messages.emplace_back(std::move(m)); + + assert(!diagram().sequences.empty()); } - LOG_DBG("Found call {} from {} [{}] to {} [{}] ", m.message, m.from, - m.from_usr, m.to, m.to_usr); - - diagram().sequences[m.from_usr].messages.emplace_back(std::move(m)); - - assert(!diagram().sequences.empty()); - return true; } + +std::unique_ptr +translation_unit_visitor::create_class_declaration(clang::CXXRecordDecl *cls) +{ + assert(cls != nullptr); + + auto c_ptr{std::make_unique( + config_.using_namespace())}; + auto &c = *c_ptr; + + // TODO: refactor to method get_qualified_name() + auto qualified_name = + cls->getQualifiedNameAsString(); // common::get_qualified_name(*cls); + + if (!diagram().should_include(qualified_name)) + return {}; + + auto ns = common::get_tag_namespace(*cls); + + const auto *parent = cls->getParent(); + + if (parent && parent->isRecord()) { + // Here we have 2 options, either: + // - the parent is a regular C++ class/struct + // - the parent is a class template declaration/specialization + std::optional id_opt; + int64_t local_id = + static_cast(parent)->getID(); + + // First check if the parent has been added to the diagram as regular + // class + id_opt = get_ast_local_id(local_id); + + // If not, check if the parent template declaration is in the model + if (!id_opt) { + local_id = static_cast(parent) + ->getDescribedTemplate() + ->getID(); + if (static_cast(parent) + ->getDescribedTemplate()) + id_opt = get_ast_local_id(local_id); + } + + assert(id_opt); + + auto parent_class = + diagram_.get_participant( + *id_opt); + + assert(parent_class); + + c.set_namespace(ns); + if (cls->getNameAsString().empty()) { + // Nested structs can be anonymous + if (anonymous_struct_relationships_.count(cls->getID()) > 0) { + const auto &[label, hint, access] = + anonymous_struct_relationships_[cls->getID()]; + + c.set_name(parent_class.value().name() + "##" + + fmt::format("({})", label)); + + parent_class.value().add_relationship( + {hint, common::to_id(c.full_name(false)), access, label}); + } + else + c.set_name(parent_class.value().name() + "##" + + fmt::format( + "(anonymous_{})", std::to_string(cls->getID()))); + } + else { + c.set_name( + parent_class.value().name() + "##" + cls->getNameAsString()); + } + + c.set_id(common::to_id(c.full_name(false))); + + c.nested(true); + } + else { + c.set_name(common::get_tag_name(*cls)); + c.set_namespace(ns); + c.set_id(common::to_id(c.full_name(false))); + } + + c.is_struct(cls->isStruct()); + + process_comment(*cls, c); + set_source_location(*cls, c); + + if (c.skip()) + return {}; + + c.set_style(c.style_spec()); + + return c_ptr; +} + +bool translation_unit_visitor::process_template_parameters( + const clang::ClassTemplateDecl &template_declaration, + sequence_diagram::model::class_ &c) +{ + using class_diagram::model::template_parameter; + + LOG_DBG("Processing class {} template parameters...", + common::get_qualified_name(template_declaration)); + + if (template_declaration.getTemplateParameters() == nullptr) + return false; + + for (const auto *parameter : + *template_declaration.getTemplateParameters()) { + if (clang::dyn_cast_or_null(parameter)) { + const auto *template_type_parameter = + clang::dyn_cast_or_null(parameter); + template_parameter ct; + ct.set_type(""); + ct.is_template_parameter(true); + ct.set_name(template_type_parameter->getNameAsString()); + ct.set_default_value(""); + ct.is_variadic(template_type_parameter->isParameterPack()); + + c.add_template(std::move(ct)); + } + else if (clang::dyn_cast_or_null( + parameter)) { + const auto *template_nontype_parameter = + clang::dyn_cast_or_null( + parameter); + template_parameter ct; + ct.set_type(template_nontype_parameter->getType().getAsString()); + ct.set_name(template_nontype_parameter->getNameAsString()); + ct.is_template_parameter(false); + ct.set_default_value(""); + ct.is_variadic(template_nontype_parameter->isParameterPack()); + + c.add_template(std::move(ct)); + } + else if (clang::dyn_cast_or_null( + parameter)) { + const auto *template_template_parameter = + clang::dyn_cast_or_null( + parameter); + template_parameter ct; + ct.set_type(""); + ct.set_name(template_template_parameter->getNameAsString() + "<>"); + ct.is_template_parameter(true); + ct.set_default_value(""); + ct.is_variadic(template_template_parameter->isParameterPack()); + + c.add_template(std::move(ct)); + } + else { + // pass + } + } + + return false; +} + +void translation_unit_visitor::set_ast_local_id( + int64_t local_id, common::model::diagram_element::id_t global_id) +{ + LOG_DBG("== Setting local element mapping {} --> {}", local_id, global_id); + + local_ast_id_map_[local_id] = global_id; +} + +std::optional +translation_unit_visitor::get_ast_local_id(int64_t local_id) const +{ + if (local_ast_id_map_.find(local_id) == local_ast_id_map_.end()) + return {}; + + return local_ast_id_map_.at(local_id); +} } diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 811868ed..bc4dd548 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -17,6 +17,7 @@ */ #pragma once +#include "common/visitor/translation_unit_visitor.h" #include "config/config.h" #include "sequence_diagram/model/diagram.h" @@ -26,8 +27,182 @@ namespace clanguml::sequence_diagram::visitor { +std::string to_string(const clang::FunctionTemplateDecl *decl); + +struct call_expression_context { + call_expression_context() + : current_class_decl_{nullptr} + , current_method_decl_{nullptr} + , current_function_decl_{nullptr} + , current_function_template_decl_{nullptr} + { + } + + void reset() + { + current_class_decl_ = nullptr; + current_method_decl_ = nullptr; + current_function_decl_ = nullptr; + current_function_template_decl_ = nullptr; + } + + bool valid() const + { + return (current_class_decl_ != nullptr) || + (current_method_decl_ != nullptr) || + (current_function_decl_ != nullptr) || + (current_function_template_decl_ != nullptr); + } + + void update(clang::CXXRecordDecl *cls) { current_class_decl_ = cls; } + + void update(clang::ClassTemplateDecl *clst) + { + current_class_template_decl_ = clst; + } + + auto &get_ast_context() + { + return current_class_decl_ ? current_class_decl_->getASTContext() + : current_function_decl_->getASTContext(); + } + + void update(clang::CXXMethodDecl *method) { current_method_decl_ = method; } + + void update(clang::FunctionDecl *function) + { + if (!function->isCXXClassMember()) + reset(); + + current_function_decl_ = function; + + // Check if this function is a part of template function declaration, + // If no - reset the current_function_template_decl_ + if (current_function_template_decl_ && + current_function_template_decl_->getQualifiedNameAsString() != + function->getQualifiedNameAsString()) { + current_function_template_decl_ = nullptr; + } + // else { + // call_expression_context_.current_class_method_ = + // process_class_method(method); + // } + } + + void update(clang::FunctionTemplateDecl *function_template) + { + current_function_template_decl_ = function_template; + + if (!function_template->isCXXClassMember()) + current_class_decl_ = nullptr; + + current_function_template_decl_ = function_template; + } + + bool in_class_method() const { return current_class_decl_ != nullptr; } + + bool in_function() const + { + return current_class_decl_ == nullptr && + current_function_decl_ != nullptr; + } + + bool in_function_template() const + { + return current_function_decl_ != nullptr && + current_function_template_decl_ != nullptr; + } + + // std::string caller_name() const + // { + // if (in_class_method()) + // return current_class_decl_->getQualifiedNameAsString(); + // else if (in_function_template()) { + // return to_string(current_function_template_decl_); + // } + // else if (in_function()) { + // const auto function_name = + // current_function_decl_->getQualifiedNameAsString(); + // LOG_DBG("Processing function {}", function_name); + // // Handle call expression within free function + // if + // (current_function_decl_->isFunctionTemplateSpecialization()) { + // /* + // /// This template specialization was formed from a + // template-id but + // /// has not yet been declared, defined, or + // instantiated. TSK_Undeclared = 0, + // /// This template specialization was implicitly + // instantiated + // from a + // /// template. (C++ [temp.inst]). + // TSK_ImplicitInstantiation, + // /// This template specialization was declared or + // defined by + // an + // /// explicit specialization (C++ [temp.expl.spec]) or + // partial + // /// specialization (C++ [temp.class.spec]). + // TSK_ExplicitSpecialization, + // /// This template specialization was instantiated from + // a + // template + // /// due to an explicit instantiation declaration + // request + // /// (C++11 [temp.explicit]). + // TSK_ExplicitInstantiationDeclaration, + // /// This template specialization was instantiated from + // a + // template + // /// due to an explicit instantiation definition + // request + // /// (C++ [temp.explicit]). + // TSK_ExplicitInstantiationDefinition + // */ + // [[maybe_unused]] const auto specialization_kind = + // current_function_decl_->getTemplateSpecializationKind(); + // [[maybe_unused]] const auto *primary_template = + // current_function_decl_->getPrimaryTemplate(); + // + // for (const auto &arg : + // current_function_decl_->getTemplateSpecializationArgs() + // ->asArray()) { + // LOG_DBG("TEMPLATE SPECIALIZATION ARG:"); + // arg.dump(); + // } + // + // LOG_DBG("--------------"); + // } + // + // return function_name + "()"; + // } + // else + // return ""; + // } + + std::int64_t caller_id() const + { + return current_caller_id_; + } + + void set_caller_id(std::int64_t id) { + LOG_DBG("Setting current caller id to {}", id); + current_caller_id_ = id; + } + + clang::CXXRecordDecl *current_class_decl_; + clang::ClassTemplateDecl *current_class_template_decl_; + clang::CXXMethodDecl *current_method_decl_; + clang::FunctionDecl *current_function_decl_; + clang::FunctionTemplateDecl *current_function_template_decl_; + +private: + std::int64_t current_caller_id_; +}; + class translation_unit_visitor - : public clang::RecursiveASTVisitor { + : public clang::RecursiveASTVisitor, + public common::visitor::translation_unit_visitor { public: translation_unit_visitor(clang::SourceManager &sm, clanguml::sequence_diagram::model::diagram &diagram, @@ -39,6 +214,8 @@ public: virtual bool VisitCXXRecordDecl(clang::CXXRecordDecl *cls); + bool VisitClassTemplateDecl(clang::ClassTemplateDecl *cls); + virtual bool VisitFunctionDecl(clang::FunctionDecl *function_declaration); bool VisitFunctionTemplateDecl( @@ -50,8 +227,22 @@ public: void finalize() { } + /// Store the mapping from local clang entity id (obtained using + /// getID()) method to clang-uml global id + void set_ast_local_id( + int64_t local_id, common::model::diagram_element::id_t global_id); + + /// Retrieve the global clang-uml entity id based on the clang local id + std::optional get_ast_local_id( + int64_t local_id) const; + private: - clang::SourceManager &source_manager_; + std::unique_ptr + create_class_declaration(clang::CXXRecordDecl *cls); + + bool process_template_parameters( + const clang::ClassTemplateDecl &template_declaration, + sequence_diagram::model::class_ &c); // Reference to the output diagram model clanguml::sequence_diagram::model::diagram &diagram_; @@ -59,10 +250,18 @@ private: // Reference to class diagram config const clanguml::config::sequence_diagram &config_; - clang::CXXRecordDecl *current_class_decl_; - clang::CXXMethodDecl *current_method_decl_; - clang::FunctionDecl *current_function_decl_; - clang::FunctionTemplateDecl *current_function_template_decl_; + call_expression_context call_expression_context_; + + std::map> + forward_declarations_; + + std::map local_ast_id_map_; + + std::map> + anonymous_struct_relationships_; }; } diff --git a/tests/t20004/.clang-uml b/tests/t20004/.clang-uml new file mode 100644 index 00000000..de348abf --- /dev/null +++ b/tests/t20004/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20004_sequence: + type: sequence + glob: + - ../../tests/t20004/t20004.cc + include: + namespaces: + - clanguml::t20004 + using_namespace: + - clanguml::t20004 + start_from: + - function: "clanguml::t20004::main()" \ No newline at end of file diff --git a/tests/t20004/t20004.cc b/tests/t20004/t20004.cc new file mode 100644 index 00000000..2942e101 --- /dev/null +++ b/tests/t20004/t20004.cc @@ -0,0 +1,18 @@ +namespace clanguml { +namespace t20004 { + +template T m4(T p); + +template T m3(T p) { return m4(p); } + +template T m2(T p) { return m3(p); } + +template T m1(T p) { return m2(p); } + +template<> [[maybe_unused]] int m4(int p) { return p+2; } + +int main() { + return m1(0); +} +} +} diff --git a/tests/t20004/test_case.h b/tests/t20004/test_case.h new file mode 100644 index 00000000..b6b1d9c4 --- /dev/null +++ b/tests/t20004/test_case.h @@ -0,0 +1,47 @@ +/** + * tests/t20004/test_case.h + * + * Copyright (c) 2021-2022 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t20004", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20004"); + + auto diagram = config.diagrams["t20004_sequence"]; + + REQUIRE(diagram->name == "t20004_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20004_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, HasFunctionCall("main()", "m1()")); + REQUIRE_THAT(puml, HasFunctionCall("m2", "m3")); + REQUIRE_THAT(puml, HasFunctionCall("m3", "m4")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall("A", "log_result")); + //REQUIRE_THAT(puml, HasCallWithResponse("B", "A", "add3")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index a95557f9..84ec433d 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -250,6 +250,7 @@ using namespace clanguml::test::matchers; #include "t20001/test_case.h" #include "t20002/test_case.h" #include "t20003/test_case.h" +#include "t20004/test_case.h" /// /// Package diagram tests