Added support for try/catch statements in sequence diagrams

This commit is contained in:
Bartek Kryza
2022-12-13 00:29:52 +01:00
parent 91a9aa861d
commit 3020ffd69f
14 changed files with 260 additions and 9 deletions

View File

@@ -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 "";

View File

@@ -53,6 +53,9 @@ enum class message_t {
kDoEnd,
kFor,
kForEnd,
kTry,
kCatch,
kTryEnd,
kNone
};

View File

@@ -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";
}
}
}

View File

@@ -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 &current_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<common::model::diagram_element::id_t> &diagram::active_participants()
{
return active_participants_;
};
}
const std::set<common::model::diagram_element::id_t> &
diagram::active_participants() const
{
return active_participants_;
};
}
void diagram::print() const
{
@@ -305,7 +354,6 @@ void diagram::print() const
}
}
}
}
namespace clanguml::common::model {

View File

@@ -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<common::model::diagram_element::id_t> &
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};

View File

@@ -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
{

View File

@@ -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<clang::IfStmt *> elseif_stmt_stack_;
std::stack<clang::Stmt *> loop_stmt_stack_;
std::stack<clang::Stmt *> try_stmt_stack_;
};
}

View File

@@ -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<translation_unit_visitor>::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<translation_unit_visitor>::TraverseCXXCatchStmt(stmt);
return true;
}
bool translation_unit_visitor::TraverseCXXForRangeStmt(
clang::CXXForRangeStmt *stmt)
{

View File

@@ -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;

14
tests/t20023/.clang-uml Normal file
View File

@@ -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()"

40
tests/t20023/t20023.cc Normal file
View File

@@ -0,0 +1,40 @@
#include <stdexcept>
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;
}
}
}

46
tests/t20023/test_case.h Normal file
View File

@@ -0,0 +1,46 @@
/**
* tests/t20023/test_case.h
*
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
*
* 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);
}

View File

@@ -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

View File

@@ -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