diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index c5fec542..34c511f7 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -164,6 +164,22 @@ std::string get_source_text( return get_source_text_raw(printable_range, sm); } +bool is_subexpr_of(const clang::Stmt *parent_stmt, const clang::Stmt *sub_stmt) +{ + if (parent_stmt == nullptr || sub_stmt == nullptr) + return false; + + if (parent_stmt == sub_stmt) + return true; + + for (const auto *e : parent_stmt->children()) { + if (is_subexpr_of(e, sub_stmt)) + return true; + } + + return false; +} + template <> id_t to_id(const std::string &full_name) { return std::hash{}(full_name) >> 3; diff --git a/src/common/clang_utils.h b/src/common/clang_utils.h index b3860a59..c332dd10 100644 --- a/src/common/clang_utils.h +++ b/src/common/clang_utils.h @@ -68,6 +68,8 @@ std::string get_source_text_raw( std::string get_source_text( clang::SourceRange range, const clang::SourceManager &sm); +bool is_subexpr_of(const clang::Stmt *parent_stmt, const clang::Stmt *sub_stmt); + template id_t to_id(const T &declaration); template <> id_t to_id(const std::string &full_name); diff --git a/src/common/model/enums.h b/src/common/model/enums.h index fbec4a55..6ef22218 100644 --- a/src/common/model/enums.h +++ b/src/common/model/enums.h @@ -39,6 +39,7 @@ enum class relationship_t { kDependency }; +/// Types of sequence diagram activity elements enum class message_t { kCall, kReturn, @@ -55,6 +56,13 @@ enum class message_t { kNone }; +/// The scope of the call expression represented in the sequence diagram +enum class message_scope_t { + kNormal, // This is a regular call expression + kCondition // This is a call expression from within a control condition + // e.g if(a->is_valid()) { ... } +}; + std::string to_string(relationship_t r); std::string to_string(access_t r); diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index 2c6668ef..c2ebe161 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -79,14 +79,25 @@ void generator::generate_call(const message &m, std::ostream &ostr) const const std::string to_alias = generate_alias(to.value()); ostr << from_alias << " " - << common::generators::plantuml::to_plantuml(message_t::kCall) << " " - << to_alias; + << common::generators::plantuml::to_plantuml(message_t::kCall) << " "; + + ostr << to_alias; if (m_config.generate_links) { common_generator::generate_link(ostr, m); } - ostr << " : " << message << std::endl; + ostr << " : "; + + if (m.message_scope() == common::model::message_scope_t::kCondition) + ostr << "**[**"; + + ostr << message; + + if (m.message_scope() == common::model::message_scope_t::kCondition) + ostr << "**]**"; + + ostr << '\n'; LOG_DBG("Generated call '{}' from {} [{}] to {} [{}]", message, from, m.from(), to, m.to()); diff --git a/src/sequence_diagram/model/message.cc b/src/sequence_diagram/model/message.cc index 93f23937..19a7fb21 100644 --- a/src/sequence_diagram/model/message.cc +++ b/src/sequence_diagram/model/message.cc @@ -50,4 +50,11 @@ void message::set_return_type(std::string t) { return_type_ = std::move(t); } const std::string &message::return_type() const { return return_type_; } +void message::set_message_scope(common::model::message_scope_t scope) +{ + scope_ = scope; +} + +common::model::message_scope_t message::message_scope() const { return scope_; } + } diff --git a/src/sequence_diagram/model/message.h b/src/sequence_diagram/model/message.h index 48c6ca0a..c25c0f2d 100644 --- a/src/sequence_diagram/model/message.h +++ b/src/sequence_diagram/model/message.h @@ -47,6 +47,9 @@ public: void set_return_type(std::string t); const std::string &return_type() const; + void set_message_scope(common::model::message_scope_t scope); + common::model::message_scope_t message_scope() const; + private: common::model::message_t type_{common::model::message_t::kNone}; @@ -54,6 +57,9 @@ private: common::model::diagram_element::id_t to_{}; + common::model::message_scope_t scope_{ + common::model::message_scope_t::kNormal}; + // This is only for better verbose messages, we cannot rely on this // always std::string message_name_{}; diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 79f7eaf0..68f92860 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -732,6 +732,7 @@ bool translation_unit_visitor::TraverseCXXForRangeStmt( bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) { + using clanguml::common::model::message_scope_t; using clanguml::common::model::message_t; using clanguml::common::model::namespace_; using clanguml::sequence_diagram::model::activity; @@ -774,6 +775,20 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) } } + if (context().current_ifstmt()) { + if (common::is_subexpr_of( + context().current_ifstmt()->getCond(), expr)) { + m.set_message_scope(common::model::message_scope_t::kCondition); + } + } + + if (context().current_elseifstmt()) { + if (common::is_subexpr_of( + context().current_elseifstmt()->getCond(), expr)) { + m.set_message_scope(common::model::message_scope_t::kCondition); + } + } + if (const auto *operator_call_expr = clang::dyn_cast_or_null(expr); operator_call_expr != nullptr) { diff --git a/tests/t20020/t20020.cc b/tests/t20020/t20020.cc index 498ded67..7ed55e85 100644 --- a/tests/t20020/t20020.cc +++ b/tests/t20020/t20020.cc @@ -7,6 +7,7 @@ struct A { int a1() { return 0; } int a2() { return 1; } int a3() { return 2; } + int a4() { return 3; } }; struct B { @@ -26,6 +27,8 @@ struct C { } bool c2() const { return true; } + + int c3(int x) { return x * 2; } }; template struct D { @@ -46,13 +49,15 @@ int tmain() result = a.a1(); } else if (reinterpret_cast(&a) % 64 == 0ULL) { - if (a.a2() > 2) + if (c.c3(a.a2()) > 2) result = b.b1(); - else + else if (a.a3() % 2) result = b.b2(); + else + result = 0; } else { - result = a.a3(); + result = a.a4(); } b.log(); diff --git a/tests/t20020/test_case.h b/tests/t20020/test_case.h index 8bcb57d4..3fd1aaec 100644 --- a/tests/t20020/test_case.h +++ b/tests/t20020/test_case.h @@ -36,14 +36,19 @@ TEST_CASE("t20020", "[test-case][sequence]") // Check if all calls exist REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a1()")); - REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a2()")); - REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("A"), "a3()")); + REQUIRE_THAT( + puml, HasCallInControlCondition(_A("tmain()"), _A("A"), "a2()")); + REQUIRE_THAT( + puml, HasCallInControlCondition(_A("tmain()"), _A("A"), "a3()")); REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b1()")); REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "b2()")); REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("B"), "log()")); REQUIRE_THAT(puml, HasCall(_A("tmain()"), _A("C"), "c1()")); + REQUIRE_THAT(puml, HasCallInControlCondition(_A("C"), _A("C"), "c2()")); + REQUIRE_THAT( + puml, HasCallInControlCondition(_A("tmain()"), _A("C"), "c3(int)")); save_puml( "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); diff --git a/tests/test_cases.h b/tests/test_cases.h index 1bfe9dd8..5930191c 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -132,8 +132,10 @@ public: , m_message{message} { util::replace_all(m_message, "(", "\\("); - util::replace_all(m_message, "*", "\\*"); util::replace_all(m_message, ")", "\\)"); + util::replace_all(m_message, "*", "\\*"); + util::replace_all(m_message, "[", "\\["); + util::replace_all(m_message, "]", "\\]"); } bool match(T const &in) const override @@ -169,6 +171,13 @@ auto HasCall(std::string const &from, std::string const &to, return HasCallMatcher(from, to, message); } +auto HasCallInControlCondition(std::string const &from, std::string const &to, + std::string const &message, + CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) +{ + return HasCallMatcher(from, to, fmt::format("**[**{}**]**", message)); +} + auto HasCall(std::string const &from, std::string const &message, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes) {