diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index 0d65b040..b3a154fe 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -60,7 +60,7 @@ void generator::generate_call(const message &m, std::ostream &ostr) const << to.value().alias() << " : " << message << std::endl; LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from, - m.from_name, to, m.to_name); + m.from, to, m.to); } void generator::generate_return(const message &m, std::ostream &ostr) const @@ -84,19 +84,19 @@ void generator::generate_activity(const activity &a, std::ostream &ostr) const if (!to) continue; - LOG_DBG("Generating message {} --> {}", m.from_name, m.to_name); + LOG_DBG("Generating message {} --> {}", m.from, m.to); generate_call(m, ostr); ostr << "activate " << to.value().alias() << std::endl; 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); + m.from, m.to, 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); + m.from, m.to, m.to); generate_return(m, ostr); @@ -159,6 +159,8 @@ void generator::generate(std::ostream &ostr) const { m_model.print(); + + ostr << "@startuml" << std::endl; generate_plantuml_directives(ostr, m_config.puml().before); @@ -167,7 +169,8 @@ void generator::generate(std::ostream &ostr) const if (sf.location_type == source_location::location_t::function) { std::int64_t start_from; for (const auto &[k, v] : m_model.sequences) { - std::string vfrom = v.from; + const auto& caller = *m_model.participants.at(v.from); + std::string vfrom = caller.full_name(false); if (vfrom == sf.location) { LOG_DBG("Found sequence diagram start point: {}", k); start_from = k; diff --git a/src/sequence_diagram/model/activity.h b/src/sequence_diagram/model/activity.h index d60cb225..bee9c4d7 100644 --- a/src/sequence_diagram/model/activity.h +++ b/src/sequence_diagram/model/activity.h @@ -18,6 +18,7 @@ #pragma once #include "message.h" +#include "participant.h" #include #include @@ -25,8 +26,10 @@ namespace clanguml::sequence_diagram::model { struct activity { - std::uint_least64_t usr; - std::string from; +// std::uint_least64_t from_id; +// std::string from_name; +// std::string from_method_name; + common::model::diagram_element::id_t from; std::vector messages; }; diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index e919938a..6f6616b6 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -73,13 +73,25 @@ inja::json diagram::context() const void diagram::print() const { + LOG_DBG(" --- Participants ---"); + for (const auto &[id, participant] : participants) { + LOG_DBG("{} - {}", id, participant->to_string()); + } + + LOG_DBG(" --- Activities ---"); for (const auto &[from_id, act] : sequences) { LOG_DBG("Sequence id={}:", from_id); - LOG_DBG(" Activity id={}, from={}:", act.usr, act.from); + const auto &from_activity = *(participants.at(from_id)); + LOG_DBG(" Activity id={}, from={}:", act.from, + from_activity.full_name(false)); 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, + const auto &from_participant = *participants.at(message.from); + const auto &to_participant = *participants.at(message.to); + + LOG_DBG(" Message from={}, from_id={}, " + "to={}, to_id={}, name={}", + from_participant.full_name(false), from_participant.id(), + to_participant.full_name(false), to_participant.id(), message.message_name); } } diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index 4b66d149..6f12f911 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -66,16 +66,16 @@ public: 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 participant_id = p->id(); - const auto pid = p->id(); + if (participants.find(participant_id) == participants.end()) { + LOG_DBG("Adding '{}' participant: {}, {} [{}]", p->type_name(), + p->full_name(false), p->id(), + p->type_name() == "method" + ? dynamic_cast(p.get())->method_name() + : ""); - if (participants.find(pid) == participants.end()) { - participants.emplace(pid, std::move(p)); + participants.emplace(participant_id, std::move(p)); } } diff --git a/src/sequence_diagram/model/message.h b/src/sequence_diagram/model/message.h index 606fa95a..07087964 100644 --- a/src/sequence_diagram/model/message.h +++ b/src/sequence_diagram/model/message.h @@ -18,6 +18,7 @@ #pragma once #include "common/model/enums.h" +#include "participant.h" #include #include @@ -27,9 +28,10 @@ namespace clanguml::sequence_diagram::model { struct message { message() : from{} - , from_name{} +// , from_method_name{} +// , from_name{} , to{} - , to_name{} +// , to_name{} , message_name{} , return_type{} , line{} @@ -37,17 +39,21 @@ struct message { } common::model::message_t type; - - /// 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{}; + common::model::diagram_element::id_t from; + common::model::diagram_element::id_t to; +// +// /// Source participant id +// std::uint_least64_t from; +// std::string from_method_name; +// +// 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; diff --git a/src/sequence_diagram/model/participant.h b/src/sequence_diagram/model/participant.h index 1990c7ec..e8a77efd 100644 --- a/src/sequence_diagram/model/participant.h +++ b/src/sequence_diagram/model/participant.h @@ -66,6 +66,12 @@ struct participant : public common::model::element, std::string type_name() const override { return "participant"; } + virtual std::string to_string() const + { + return fmt::format("Participant '{}': id={} name={}", type_name(), id(), + full_name(false)); + } + stereotype_t stereotype_{stereotype_t::participant}; }; @@ -148,6 +154,12 @@ struct method : public participant { diagram_element::id_t class_id() const { return class_id_; } + std::string to_string() const override + { + return fmt::format("Participant '{}': id={}, name={}, class_id={}", + type_name(), id(), full_name(false), class_id()); + } + private: diagram_element::id_t class_id_; std::string method_name_; diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index b908a934..7d06bfff 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -78,8 +78,6 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) if (!diagram().should_include(cls->getQualifiedNameAsString())) return true; - call_expression_context_.reset(); - if (!diagram().should_include(cls->getQualifiedNameAsString())) { return true; } @@ -102,6 +100,8 @@ bool translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *cls) if (!c_ptr) return true; + call_expression_context_.reset(); + const auto cls_id = c_ptr->id(); set_ast_local_id(cls->getID(), cls_id); @@ -152,9 +152,9 @@ bool translation_unit_visitor::VisitClassTemplateDecl( if (!diagram().should_include(cls->getQualifiedNameAsString())) return true; - LOG_DBG("= Visiting class template declaration {} at {}", + LOG_DBG("= Visiting class template declaration {} at {} [{}]", cls->getQualifiedNameAsString(), - cls->getLocation().printToString(source_manager())); + cls->getLocation().printToString(source_manager()), (void *)cls); auto c_ptr = create_class_declaration(cls->getTemplatedDecl()); @@ -181,8 +181,7 @@ bool translation_unit_visitor::VisitClassTemplateDecl( } if (diagram_.should_include(*c_ptr)) { - const auto name = c_ptr->full_name(); - LOG_DBG("Adding class template {} with id {}", name, id); + LOG_DBG("Adding class template {} with id {}", cls_full_name, id); call_expression_context_.set_caller_id(id); call_expression_context_.update(cls); @@ -195,7 +194,7 @@ bool translation_unit_visitor::VisitClassTemplateDecl( bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m) { - if (call_expression_context_.current_class_decl_ == nullptr || + if (call_expression_context_.current_class_decl_ == nullptr && call_expression_context_.current_class_template_decl_ == nullptr) return true; @@ -210,11 +209,22 @@ bool translation_unit_visitor::VisitCXXMethodDecl(clang::CXXMethodDecl *m) 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()); + if (call_expression_context_.current_class_decl_) + m_ptr->set_class_id(get_ast_local_id( + call_expression_context_.current_class_decl_->getID()) + .value()); + else + m_ptr->set_class_id(get_ast_local_id( + call_expression_context_.current_class_template_decl_->getID()) + .value()); + + m_ptr->set_name( + diagram().participants.at(m_ptr->class_id())->full_name_no_ns() + + "::" + m->getNameAsString()); + + m_ptr->set_id( + common::to_id(m_ptr->full_name(false) + "::" + m->getNameAsString())); call_expression_context_.update(m); @@ -327,6 +337,8 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) using clanguml::sequence_diagram::model::activity; using clanguml::sequence_diagram::model::message; + // expr->dump(); + // Skip casts, moves and such if (expr->isCallToStdMove()) return true; @@ -337,24 +349,28 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) if (clang::dyn_cast_or_null(expr)) return true; + call_expression_context_.dump(); + 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); const auto ¤t_ast_context = *call_expression_context_.get_ast_context(); + LOG_DBG("Visiting call expression at {}", + expr->getBeginLoc().printToString(source_manager())); + if (const auto *operator_call_expr = clang::dyn_cast_or_null(expr); operator_call_expr != nullptr) { // TODO: Handle C++ operator calls } // - // class method + // Call to a class method // else if (const auto *method_call_expr = clang::dyn_cast_or_null(expr); @@ -376,7 +392,6 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) // 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(); @@ -385,6 +400,9 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) diagram().add_active_participant( get_ast_local_id(callee_decl->getID()).value()); } + // + // Call to a function + // else if (const auto *function_call_expr = clang::dyn_cast_or_null(expr); function_call_expr != nullptr) { @@ -396,8 +414,49 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) } if (!callee_decl) { - if (clang::dyn_cast_or_null( + // + // Call to a method of a class template + // + if (clang::dyn_cast_or_null( function_call_expr->getCallee())) { + auto *dependent_member_callee = + clang::dyn_cast_or_null( + function_call_expr->getCallee()); + + if (!dependent_member_callee->getBaseType().isNull()) { + const auto *primary_template = + dependent_member_callee->getBaseType() + ->getAs() + ->getTemplateName() + .getAsTemplateDecl(); + + auto callee_method_full_name = + diagram() + .participants + .at(get_ast_local_id(primary_template->getID()) + .value()) + ->full_name(false) + + "::" + + dependent_member_callee->getMember().getAsString(); + + auto callee_id = common::to_id(callee_method_full_name); + m.to = callee_id; + + m.message_name = + dependent_member_callee->getMember().getAsString(); + m.return_type = ""; + + if (get_ast_local_id(primary_template->getID())) + diagram().add_active_participant( + get_ast_local_id(primary_template->getID()) + .value()); + } + } + // + // Call to a template function + // + else if (clang::dyn_cast_or_null( + function_call_expr->getCallee())) { // This is probably a template auto *unresolved_expr = clang::dyn_cast_or_null( @@ -411,7 +470,8 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) auto *ftd = clang::dyn_cast_or_null< clang::FunctionTemplateDecl>(decl); - m.to_name = to_string(ftd); + // m.to_name = + // to_string(ftd); m.to = get_ast_local_id(ftd->getID()).value(); auto message_name = diagram() @@ -430,11 +490,8 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) } } else { - if (!callee_decl) - return true; - if (callee_decl->isTemplateDecl()) - LOG_DBG("Call to template function!!!!"); + LOG_DBG("Call to template function"); const auto *callee_function = callee_decl->getAsFunction(); @@ -473,7 +530,8 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) LOG_DBG("Processing implicit template specialization {}", f_ptr->full_name(false)); - m.to_name = callee_function->getQualifiedNameAsString(); + // m.to_name = + // callee_function->getQualifiedNameAsString(); if (is_implicit) { // If this is an implicit template specialization/instantiation // for now we just redirect the call to it's primary template @@ -512,8 +570,8 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) 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; + // a.usr = m.from; + a.from = m.from; diagram().sequences.insert({m.from, std::move(a)}); } @@ -521,7 +579,7 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) diagram().add_active_participant(m.to); LOG_DBG("Found call {} from {} [{}] to {} [{}] ", m.message_name, - m.from_name, m.from, m.to_name, m.to); + m.from, m.from, m.to, m.to); diagram().sequences[m.from].messages.emplace_back(std::move(m)); diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 3d3e4d31..afb408dc 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -32,6 +32,7 @@ std::string to_string(const clang::FunctionTemplateDecl *decl); struct call_expression_context { call_expression_context() : current_class_decl_{nullptr} + , current_class_template_decl_{nullptr} , current_method_decl_{nullptr} , current_function_decl_{nullptr} , current_function_template_decl_{nullptr} @@ -40,29 +41,40 @@ struct call_expression_context { void reset() { + current_caller_id_ = 0; current_class_decl_ = nullptr; + current_class_template_decl_ = nullptr; current_method_decl_ = nullptr; current_function_decl_ = nullptr; current_function_template_decl_ = nullptr; } + void dump() + { + LOG_DBG("current_caller_id_ = {}", current_caller_id_); + LOG_DBG("current_class_decl_ = {}", (void *)current_class_decl_); + LOG_DBG("current_class_template_decl_ = {}", + (void *)current_class_template_decl_); + LOG_DBG("current_method_decl_ = {}", (void *)current_method_decl_); + LOG_DBG("current_function_decl_ = {}", (void *)current_function_decl_); + LOG_DBG("current_function_template_decl_ = {}", + (void *)current_function_template_decl_); + } + bool valid() const { return (current_class_decl_ != nullptr) || + (current_class_template_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; - } - clang::ASTContext *get_ast_context() { + if (current_class_template_decl_) + return ¤t_class_template_decl_->getASTContext(); + if (current_class_decl_) return ¤t_class_decl_->getASTContext(); @@ -72,6 +84,13 @@ struct call_expression_context { return ¤t_function_decl_->getASTContext(); } + void update(clang::CXXRecordDecl *cls) { current_class_decl_ = cls; } + + void update(clang::ClassTemplateDecl *clst) + { + current_class_template_decl_ = clst; + } + void update(clang::CXXMethodDecl *method) { current_method_decl_ = method; } void update(clang::FunctionDecl *function) @@ -118,73 +137,6 @@ struct call_expression_context { 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) diff --git a/tests/t20005/.clang-uml b/tests/t20005/.clang-uml new file mode 100644 index 00000000..d6248303 --- /dev/null +++ b/tests/t20005/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20005_sequence: + type: sequence + glob: + - ../../tests/t20005/t20005.cc + include: + namespaces: + - clanguml::t20005 + using_namespace: + - clanguml::t20005 + start_from: + - function: "clanguml::t20005::C::c" \ No newline at end of file diff --git a/tests/t20005/t20005.cc b/tests/t20005/t20005.cc new file mode 100644 index 00000000..de066173 --- /dev/null +++ b/tests/t20005/t20005.cc @@ -0,0 +1,21 @@ +namespace clanguml { +namespace t20005 { + +template struct A { + T a(T arg) { return arg; } +}; + +template struct B { + T b(T arg) { return a_.a(arg); } + + A a_; +}; + +template struct C { + T c(T arg) { return b_.b(arg); } + + B b_; +}; + +} +} \ No newline at end of file diff --git a/tests/t20005/test_case.h b/tests/t20005/test_case.h new file mode 100644 index 00000000..f2f6ef6a --- /dev/null +++ b/tests/t20005/test_case.h @@ -0,0 +1,45 @@ +/** + * tests/t20005/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("t20005", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20005"); + + auto diagram = config.diagrams["t20005_sequence"]; + + REQUIRE(diagram->name == "t20005_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20005_sequence"); + + auto puml = generate_sequence_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + + // Check if all calls exist + REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b")); + REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a")); + + + 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 84ec433d..0433f165 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -251,6 +251,7 @@ using namespace clanguml::test::matchers; #include "t20002/test_case.h" #include "t20003/test_case.h" #include "t20004/test_case.h" +#include "t20005/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index e10d342d..a78044a6 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -160,6 +160,9 @@ test_cases: - name: t20004 title: Function template instantiation sequence diagram test case description: + - name: t20005 + title: Class template basic sequence diagram + description: Package diagrams: - name: t30001 title: Basic package diagram test case diff --git a/util/templates/test_cases/.clang-uml b/util/templates/test_cases/.clang-uml index 4e1e2749..63e89fab 100644 --- a/util/templates/test_cases/.clang-uml +++ b/util/templates/test_cases/.clang-uml @@ -7,4 +7,6 @@ diagrams: - ../../tests/{{ name }}/{{ name }}.cc include: namespaces: - - clanguml::{{ name }} \ No newline at end of file + - clanguml::{{ name }} + using_namespace: + - clanguml::{{ name }} \ No newline at end of file