Added support for constructors and operators in sequence diagrams

This commit is contained in:
Bartek Kryza
2023-06-29 00:35:36 +02:00
parent 9e7b147244
commit 213483dd3b
10 changed files with 337 additions and 14 deletions

View File

@@ -218,10 +218,11 @@ void call_expression_context::leave_loopstmt()
return loop_stmt_stack_.pop();
}
clang::CallExpr *call_expression_context::current_callexpr() const
call_expression_context::callexpr_stack_t
call_expression_context::current_callexpr() const
{
if (call_expr_stack_.empty())
return nullptr;
return {};
return call_expr_stack_.top();
}
@@ -231,6 +232,11 @@ void call_expression_context::enter_callexpr(clang::CallExpr *expr)
call_expr_stack_.push(expr);
}
void call_expression_context::enter_callexpr(clang::CXXConstructExpr *expr)
{
call_expr_stack_.push(expr);
}
void call_expression_context::leave_callexpr()
{
if (!call_expr_stack_.empty()) {

View File

@@ -37,6 +37,15 @@ namespace clanguml::sequence_diagram::visitor {
* e.g. a class method or function.
*/
struct call_expression_context {
/**
* In Clang, call to a class constructor is represented by
* `clang::CXXConstructExpr`, which does inherit from `clang::CallExpr`.
* So to enable to track calls to constructors, we need to be able
* to add to the call stack either type.
*/
using callexpr_stack_t = std::variant<std::monostate, clang::CallExpr *,
clang::CXXConstructExpr *>;
call_expression_context();
/**
@@ -252,15 +261,22 @@ struct call_expression_context {
*
* @return Call expression
*/
clang::CallExpr *current_callexpr() const;
callexpr_stack_t current_callexpr() const;
/**
* @brief Enter a call expression
*
* @param stmt Call expression
* @param expr Call expression
*/
void enter_callexpr(clang::CallExpr *expr);
/**
* @brief Enter a constructor call expression
*
* @param expr Constructor call expression
*/
void enter_callexpr(clang::CXXConstructExpr *expr);
/**
* @brief Leave call expression
*/
@@ -302,7 +318,7 @@ private:
std::int64_t current_caller_id_{0};
std::stack<std::int64_t> current_lambda_caller_id_;
std::stack<clang::CallExpr *> call_expr_stack_;
std::stack<callexpr_stack_t> call_expr_stack_;
std::stack<clang::IfStmt *> if_stmt_stack_;
std::stack<clang::IfStmt *> elseif_stmt_stack_;

View File

@@ -488,6 +488,41 @@ bool translation_unit_visitor::TraverseCXXOperatorCallExpr(
return true;
}
bool translation_unit_visitor::TraverseCXXTemporaryObjectExpr(
clang::CXXTemporaryObjectExpr *expr)
{
context().enter_callexpr(expr);
RecursiveASTVisitor<
translation_unit_visitor>::TraverseCXXTemporaryObjectExpr(expr);
translation_unit_visitor::VisitCXXConstructExpr(
clang::dyn_cast<clang::CXXConstructExpr>(expr));
context().leave_callexpr();
pop_message_to_diagram(expr);
return true;
}
bool translation_unit_visitor::TraverseCXXConstructExpr(
clang::CXXConstructExpr *expr)
{
context().enter_callexpr(expr);
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXConstructExpr(
expr);
translation_unit_visitor::VisitCXXConstructExpr(expr);
context().leave_callexpr();
pop_message_to_diagram(expr);
return true;
}
bool translation_unit_visitor::TraverseCompoundStmt(clang::CompoundStmt *stmt)
{
using clanguml::common::model::message_t;
@@ -862,7 +897,8 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
// message source rather then enclosing context
// Unless the lambda is declared in a function or method call
if (context().lambda_caller_id() != 0) {
if (context().current_callexpr() == nullptr) {
if (!std::holds_alternative<clang::CallExpr *>(
context().current_callexpr())) {
m.set_from(context().lambda_caller_id());
}
else {
@@ -885,6 +921,7 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
if (!process_operator_call_expression(m, operator_call_expr))
return true;
}
//
// Call to a class method
//
@@ -963,17 +1000,69 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
return true;
}
bool translation_unit_visitor::VisitCXXConstructExpr(
clang::CXXConstructExpr *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;
using clanguml::sequence_diagram::model::message;
if (!should_include(expr->getConstructor()))
return true;
LOG_TRACE("Visiting cxx construct expression at {} [caller_id = {}]",
expr->getBeginLoc().printToString(source_manager()),
context().caller_id());
message m{message_t::kCall, context().caller_id()};
set_source_location(*expr, m);
if (context().lambda_caller_id() != 0) {
if (!std::holds_alternative<clang::CallExpr *>(
context().current_callexpr())) {
m.set_from(context().lambda_caller_id());
}
else {
LOG_DBG("Current lambda declaration is passed to a method or "
"function - keep the original caller id");
}
}
if (context().is_expr_in_current_control_statement_condition(expr)) {
m.set_message_scope(common::model::message_scope_t::kCondition);
}
if (!process_construct_expression(m, expr))
return true;
if (m.from() > 0 && m.to() > 0) {
if (diagram().sequences().find(m.from()) ==
diagram().sequences().end()) {
activity a{m.from()};
diagram().sequences().insert({m.from(), std::move(a)});
}
diagram().add_active_participant(m.from());
diagram().add_active_participant(m.to());
LOG_DBG("Found constructor call {} from {} [{}] to {} [{}] ",
m.message_name(), m.from(), m.from(), m.to(), m.to());
push_message(expr, std::move(m));
}
return true;
}
bool translation_unit_visitor::process_operator_call_expression(
model::message &m, const clang::CXXOperatorCallExpr *operator_call_expr)
{
if (operator_call_expr->getCalleeDecl() == nullptr)
return false;
// For now we only handle call overloaded operators
if (operator_call_expr->getOperator() !=
clang::OverloadedOperatorKind::OO_Call)
return false;
LOG_DBG("Operator '{}' call expression to {} at {}",
getOperatorSpelling(operator_call_expr->getOperator()),
operator_call_expr->getCalleeDecl()->getID(),
@@ -993,6 +1082,39 @@ bool translation_unit_visitor::process_operator_call_expression(
return true;
}
bool translation_unit_visitor::process_construct_expression(
model::message &m, const clang::CXXConstructExpr *construct_expr)
{
const auto *constructor = construct_expr->getConstructor();
if (constructor == nullptr)
return false;
const auto *constructor_parent = constructor->getParent();
if (constructor_parent == nullptr)
return false;
LOG_DBG("Constructor '{}' call expression to {} at {}",
construct_expr->getConstructor()->getNameAsString(),
constructor->getID(),
construct_expr->getBeginLoc().printToString(source_manager()));
auto maybe_id = get_unique_id(constructor->getID());
if (maybe_id.has_value()) {
m.set_to(maybe_id.value());
}
else {
m.set_to(constructor->getID());
}
m.set_message_name(
fmt::format("{}::{}", constructor_parent->getQualifiedNameAsString(),
constructor_parent->getNameAsString()));
diagram().add_active_participant(constructor->getID());
return true;
}
bool translation_unit_visitor::process_class_method_call_expression(
model::message &m, const clang::CXXMemberCallExpr *method_call_expr)
{
@@ -2102,6 +2224,12 @@ void translation_unit_visitor::push_message(
call_expr_message_map_.emplace(expr, std::move(m));
}
void translation_unit_visitor::push_message(
clang::CXXConstructExpr *expr, model::message &&m)
{
construct_expr_message_map_.emplace(expr, std::move(m));
}
void translation_unit_visitor::pop_message_to_diagram(clang::CallExpr *expr)
{
assert(expr != nullptr);
@@ -2119,6 +2247,25 @@ void translation_unit_visitor::pop_message_to_diagram(clang::CallExpr *expr)
call_expr_message_map_.erase(expr);
}
void translation_unit_visitor::pop_message_to_diagram(
clang::CXXConstructExpr *expr)
{
assert(expr != nullptr);
// Skip if no message was generated from this expr
if (construct_expr_message_map_.find(expr) ==
construct_expr_message_map_.end()) {
return;
}
auto msg = std::move(construct_expr_message_map_.at(expr));
auto caller_id = msg.from();
diagram().get_activity(caller_id).add_message(std::move(msg));
construct_expr_message_map_.erase(expr);
}
void translation_unit_visitor::finalize()
{
std::set<common::model::diagram_element::id_t> active_participants_unique;

View File

@@ -72,8 +72,11 @@ public:
bool TraverseCXXOperatorCallExpr(clang::CXXOperatorCallExpr *expr);
// TODO
// bool TraverseCXXConstructExpr(clang::CXXConstructExpr *expr);
bool VisitCXXConstructExpr(clang::CXXConstructExpr *expr);
bool TraverseCXXConstructExpr(clang::CXXConstructExpr *expr);
bool TraverseCXXTemporaryObjectExpr(clang::CXXTemporaryObjectExpr *expr);
bool VisitLambdaExpr(clang::LambdaExpr *expr);
@@ -422,7 +425,17 @@ private:
const clang::CXXDependentScopeMemberExpr *dependent_member_expr) const;
/**
* @brief Handle a operator call expresion
* @brief Handle CXX constructor call
*
* @param m Message model
* @param construct_expr CXX Construct expression
* @return True, if `m` contains a valid constructor call
*/
bool process_construct_expression(
model::message &m, const clang::CXXConstructExpr *construct_expr);
/**
* @brief Handle a operator call expression
*
* @param m Message model
* @param operator_call_expr Operator call expression
@@ -485,6 +498,7 @@ private:
* @param m Message model
*/
void push_message(clang::CallExpr *expr, model::message &&m);
void push_message(clang::CXXConstructExpr *expr, model::message &&m);
/**
* @brief Move a message model to diagram.
@@ -492,6 +506,7 @@ private:
* @param expr Call expression
*/
void pop_message_to_diagram(clang::CallExpr *expr);
void pop_message_to_diagram(clang::CXXConstructExpr *expr);
// Reference to the output diagram model
clanguml::sequence_diagram::model::diagram &diagram_;
@@ -507,6 +522,8 @@ private:
* sequence after the visitor leaves the call expression AST node
*/
std::map<clang::CallExpr *, model::message> call_expr_message_map_;
std::map<clang::CXXConstructExpr *, model::message>
construct_expr_message_map_;
std::map<common::model::diagram_element::id_t,
std::unique_ptr<clanguml::sequence_diagram::model::class_>>