From 3020ffd69fcb05f15ecd95ec7a873d79f459467e Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Tue, 13 Dec 2022 00:29:52 +0100 Subject: [PATCH] Added support for try/catch statements in sequence diagrams --- src/common/model/enums.cc | 6 +++ src/common/model/enums.h | 3 ++ .../plantuml/sequence_diagram_generator.cc | 9 ++++ src/sequence_diagram/model/diagram.cc | 54 +++++++++++++++++-- src/sequence_diagram/model/diagram.h | 7 +++ .../visitor/call_expression_context.cc | 27 ++++++++-- .../visitor/call_expression_context.h | 8 ++- .../visitor/translation_unit_visitor.cc | 47 ++++++++++++++++ .../visitor/translation_unit_visitor.h | 4 ++ tests/t20023/.clang-uml | 14 +++++ tests/t20023/t20023.cc | 40 ++++++++++++++ tests/t20023/test_case.h | 46 ++++++++++++++++ tests/test_cases.cc | 1 + tests/test_cases.yaml | 3 ++ 14 files changed, 260 insertions(+), 9 deletions(-) create mode 100644 tests/t20023/.clang-uml create mode 100644 tests/t20023/t20023.cc create mode 100644 tests/t20023/test_case.h diff --git a/src/common/model/enums.cc b/src/common/model/enums.cc index ea40af08..9aaf3b33 100644 --- a/src/common/model/enums.cc +++ b/src/common/model/enums.cc @@ -94,6 +94,12 @@ std::string to_string(message_t r) return "for"; case message_t::kForEnd: return "end for"; + case message_t::kTry: + return "try"; + case message_t::kCatch: + return "catch"; + case message_t::kTryEnd: + return "end try"; default: assert(false); return ""; diff --git a/src/common/model/enums.h b/src/common/model/enums.h index 6ef22218..66c93249 100644 --- a/src/common/model/enums.h +++ b/src/common/model/enums.h @@ -53,6 +53,9 @@ enum class message_t { kDoEnd, kFor, kForEnd, + kTry, + kCatch, + kTryEnd, kNone }; diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index c2ebe161..c72d99ae 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -190,6 +190,15 @@ void generator::generate_activity(const activity &a, std::ostream &ostr, else if (m.type() == message_t::kDoEnd) { ostr << "end\n"; } + else if (m.type() == message_t::kTry) { + ostr << "group try\n"; + } + else if (m.type() == message_t::kCatch) { + ostr << "else " << m.message_name() << '\n'; + } + else if (m.type() == message_t::kTryEnd) { + ostr << "end\n"; + } } } diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index 99091e9d..e9a4bdf1 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -224,6 +224,55 @@ void diagram::end_if_stmt( } } +void diagram::add_try_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + using clanguml::common::model::message_t; + + if (sequences_.find(current_caller_id) == sequences_.end()) { + activity a{current_caller_id}; + sequences_.insert({current_caller_id, std::move(a)}); + } + + get_activity(current_caller_id) + .add_message({message_t::kTry, current_caller_id}); +} + +void diagram::end_try_stmt( + const common::model::diagram_element::id_t current_caller_id) +{ + using clanguml::common::model::message_t; + + message m{message_t::kTryEnd, current_caller_id}; + + if (sequences_.find(current_caller_id) != sequences_.end()) { + auto ¤t_messages = get_activity(current_caller_id).messages(); + + if (current_messages.back().type() == message_t::kTry) { + current_messages.pop_back(); + } + else { + current_messages.emplace_back(std::move(m)); + } + } +} + +void diagram::add_catch_stmt( + const int64_t current_caller_id, std::string caught_type) +{ + using clanguml::common::model::message_t; + + if (sequences_.find(current_caller_id) == sequences_.end()) { + activity a{current_caller_id}; + sequences_.insert({current_caller_id, std::move(a)}); + } + + message m{message_t::kCatch, current_caller_id}; + m.set_message_name(std::move(caught_type)); + + get_activity(current_caller_id).add_message(std::move(m)); +} + bool diagram::started() const { return started_; } void diagram::started(bool s) { started_ = s; } @@ -255,13 +304,13 @@ diagram::participants() const std::set &diagram::active_participants() { return active_participants_; -}; +} const std::set & diagram::active_participants() const { return active_participants_; -}; +} void diagram::print() const { @@ -305,7 +354,6 @@ void diagram::print() const } } } - } namespace clanguml::common::model { diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index 2b530f49..b51b1128 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -85,6 +85,9 @@ public: void end_if_stmt(common::model::diagram_element::id_t current_caller_id, common::model::message_t type); + void add_try_stmt(common::model::diagram_element::id_t current_caller_id); + void end_try_stmt(common::model::diagram_element::id_t current_caller_id); + void add_loop_stmt(common::model::diagram_element::id_t current_caller_id, common::model::message_t type); void end_loop_stmt(common::model::diagram_element::id_t current_caller_id, @@ -119,6 +122,10 @@ public: const std::set & active_participants() const; + void add_catch_stmt( + const common::model::diagram_element::id_t current_caller_id, + std::string caught_type); + private: bool started_{false}; diff --git a/src/sequence_diagram/visitor/call_expression_context.cc b/src/sequence_diagram/visitor/call_expression_context.cc index 14699a1f..97611c38 100644 --- a/src/sequence_diagram/visitor/call_expression_context.cc +++ b/src/sequence_diagram/visitor/call_expression_context.cc @@ -196,7 +196,7 @@ clang::IfStmt *call_expression_context::current_ifstmt() const void call_expression_context::enter_ifstmt(clang::IfStmt *stmt) { - return if_stmt_stack_.push(stmt); + if_stmt_stack_.push(stmt); } void call_expression_context::leave_ifstmt() @@ -209,13 +209,13 @@ void call_expression_context::leave_ifstmt() void call_expression_context::enter_elseifstmt(clang::IfStmt *stmt) { - return elseif_stmt_stack_.push(stmt); + elseif_stmt_stack_.push(stmt); } void call_expression_context::leave_elseifstmt() { if (elseif_stmt_stack_.empty()) - return elseif_stmt_stack_.pop(); + elseif_stmt_stack_.pop(); } clang::IfStmt *call_expression_context::current_elseifstmt() const @@ -236,7 +236,7 @@ clang::Stmt *call_expression_context::current_loopstmt() const void call_expression_context::enter_loopstmt(clang::Stmt *stmt) { - return loop_stmt_stack_.push(stmt); + loop_stmt_stack_.push(stmt); } void call_expression_context::leave_loopstmt() @@ -245,6 +245,25 @@ void call_expression_context::leave_loopstmt() return loop_stmt_stack_.pop(); } +clang::Stmt *call_expression_context::current_trystmt() const +{ + if (try_stmt_stack_.empty()) + return nullptr; + + return try_stmt_stack_.top(); +} + +void call_expression_context::enter_trystmt(clang::Stmt *stmt) +{ + try_stmt_stack_.push(stmt); +} + +void call_expression_context::leave_trystmt() +{ + if (try_stmt_stack_.empty()) + try_stmt_stack_.pop(); +} + bool call_expression_context::is_expr_in_current_control_statement_condition( const clang::Stmt *stmt) const { diff --git a/src/sequence_diagram/visitor/call_expression_context.h b/src/sequence_diagram/visitor/call_expression_context.h index 7e4e54b9..891bcc7d 100644 --- a/src/sequence_diagram/visitor/call_expression_context.h +++ b/src/sequence_diagram/visitor/call_expression_context.h @@ -74,13 +74,16 @@ struct call_expression_context { void enter_elseifstmt(clang::IfStmt *stmt); void leave_elseifstmt(); - clang::IfStmt *current_elseifstmt() const; - clang::Stmt *current_loopstmt() const; + clang::Stmt *current_loopstmt() const; void enter_loopstmt(clang::Stmt *stmt); void leave_loopstmt(); + clang::Stmt *current_trystmt() const; + void enter_trystmt(clang::Stmt *stmt); + void leave_trystmt(); + bool is_expr_in_current_control_statement_condition( const clang::Stmt *stmt) const; @@ -101,6 +104,7 @@ private: std::stack elseif_stmt_stack_; std::stack loop_stmt_stack_; + std::stack try_stmt_stack_; }; } \ No newline at end of file diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 061046cb..31fab119 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -705,6 +705,53 @@ bool translation_unit_visitor::TraverseForStmt(clang::ForStmt *stmt) return true; } +bool translation_unit_visitor::TraverseCXXTryStmt(clang::CXXTryStmt *stmt) +{ + using clanguml::common::model::message_t; + using clanguml::sequence_diagram::model::activity; + using clanguml::sequence_diagram::model::message; + + const auto current_caller_id = context().caller_id(); + + if (current_caller_id) { + context().enter_trystmt(stmt); + diagram().add_try_stmt(current_caller_id); + } + + RecursiveASTVisitor::TraverseCXXTryStmt(stmt); + + if (current_caller_id) { + context().leave_trystmt(); + diagram().end_try_stmt(current_caller_id); + } + + return true; +} + +bool translation_unit_visitor::TraverseCXXCatchStmt(clang::CXXCatchStmt *stmt) +{ + using clanguml::common::model::message_t; + using clanguml::sequence_diagram::model::activity; + using clanguml::sequence_diagram::model::message; + + const auto current_caller_id = context().caller_id(); + + if (current_caller_id && context().current_trystmt()) { + std::string caught_type; + if (stmt->getCaughtType().isNull()) + caught_type = "..."; + else + caught_type = common::to_string( + stmt->getCaughtType(), *context().get_ast_context()); + + diagram().add_catch_stmt(current_caller_id, std::move(caught_type)); + } + + RecursiveASTVisitor::TraverseCXXCatchStmt(stmt); + + return true; +} + bool translation_unit_visitor::TraverseCXXForRangeStmt( clang::CXXForRangeStmt *stmt) { diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 83eb4dbc..8dcbbfa2 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -76,6 +76,10 @@ public: bool TraverseCXXForRangeStmt(clang::CXXForRangeStmt *stmt); + bool TraverseCXXTryStmt(clang::CXXTryStmt *stmt); + + bool TraverseCXXCatchStmt(clang::CXXCatchStmt *stmt); + clanguml::sequence_diagram::model::diagram &diagram(); const clanguml::sequence_diagram::model::diagram &diagram() const; diff --git a/tests/t20023/.clang-uml b/tests/t20023/.clang-uml new file mode 100644 index 00000000..086afe9d --- /dev/null +++ b/tests/t20023/.clang-uml @@ -0,0 +1,14 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20023_sequence: + type: sequence + glob: + - ../../tests/t20023/t20023.cc + include: + namespaces: + - clanguml::t20023 + using_namespace: + - clanguml::t20023 + start_from: + - function: "clanguml::t20023::tmain()" \ No newline at end of file diff --git a/tests/t20023/t20023.cc b/tests/t20023/t20023.cc new file mode 100644 index 00000000..ff0f8f73 --- /dev/null +++ b/tests/t20023/t20023.cc @@ -0,0 +1,40 @@ +#include + +namespace clanguml { +namespace t20023 { + +struct A { + int a1() { return 1; } + int a2() { return 2; } + int a3() { return 3; } + int a4() { return 3; } + + int a() + { + try { + return a1(); + } + catch (std::runtime_error &) { + return a2(); + } + catch (std::logic_error &) { + return a3(); + } + catch (...) { + return a4(); + } + } +}; + +int tmain() +{ + A a; + + int result{}; + + result = a.a(); + + return result; +} +} +} \ No newline at end of file diff --git a/tests/t20023/test_case.h b/tests/t20023/test_case.h new file mode 100644 index 00000000..b381887b --- /dev/null +++ b/tests/t20023/test_case.h @@ -0,0 +1,46 @@ +/** + * tests/t20023/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("t20023", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20023"); + + auto diagram = config.diagrams["t20023_sequence"]; + + REQUIRE(diagram->name == "t20023_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20023_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("tmain()"), _A("A"), "a()")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a1()")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a2()")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a3()")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "a4()")); + + 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 3a9b7b6a..20a8fa44 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -269,6 +269,7 @@ using namespace clanguml::test::matchers; #include "t20020/test_case.h" #include "t20021/test_case.h" #include "t20022/test_case.h" +#include "t20023/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 37fd33cb..1c670f81 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -214,6 +214,9 @@ test_cases: - name: t20022 title: Forward class declaration sequence diagram test case description: + - name: t20023 + title: Try/catch statement sequence diagram test case + description: Package diagrams: - name: t30001 title: Basic package diagram test case