diff --git a/src/sequence_diagram/visitor/call_expression_context.cc b/src/sequence_diagram/visitor/call_expression_context.cc index ea619188..094ca5a0 100644 --- a/src/sequence_diagram/visitor/call_expression_context.cc +++ b/src/sequence_diagram/visitor/call_expression_context.cc @@ -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()) { diff --git a/src/sequence_diagram/visitor/call_expression_context.h b/src/sequence_diagram/visitor/call_expression_context.h index b65a23e2..05563ab6 100644 --- a/src/sequence_diagram/visitor/call_expression_context.h +++ b/src/sequence_diagram/visitor/call_expression_context.h @@ -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; + 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 current_lambda_caller_id_; - std::stack call_expr_stack_; + std::stack call_expr_stack_; std::stack if_stmt_stack_; std::stack elseif_stmt_stack_; diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 143a13bb..5f64fd53 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -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(expr)); + + context().leave_callexpr(); + + pop_message_to_diagram(expr); + + return true; +} + +bool translation_unit_visitor::TraverseCXXConstructExpr( + clang::CXXConstructExpr *expr) +{ + context().enter_callexpr(expr); + + RecursiveASTVisitor::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( + 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( + 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 active_participants_unique; diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 0e7ae533..beb52f65 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -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 call_expr_message_map_; + std::map + construct_expr_message_map_; std::map> diff --git a/tests/t20030/.clang-uml b/tests/t20030/.clang-uml new file mode 100644 index 00000000..a8e29397 --- /dev/null +++ b/tests/t20030/.clang-uml @@ -0,0 +1,15 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t20030_sequence: + type: sequence + glob: + - ../../tests/t20030/t20030.cc + include: + namespaces: + - clanguml::t20030 + using_namespace: + - clanguml::t20030 + start_from: + - function: "clanguml::t20030::tmain(int)" + - function: "clanguml::t20030::tmain(bool,int)" \ No newline at end of file diff --git a/tests/t20030/t20030.cc b/tests/t20030/t20030.cc new file mode 100644 index 00000000..ed103fd2 --- /dev/null +++ b/tests/t20030/t20030.cc @@ -0,0 +1,51 @@ +namespace clanguml { +namespace t20030 { + +int magic() { return 42; } + +class A { +public: + A() { create(); } + + A(int v) { a_ = v; } + + A &operator=(const A &a) + { + set(a.a_); + return *this; + } + + A &operator+=(int a) + { + add(a); + return *this; + } + + int value() const { return a_; } + +private: + void create() { a_ = 0; } + + void add(int a) { a_ += a; } + void set(int a) { a_ = a; } + + int a_; +}; + +void tmain(int a) +{ + A an_a{magic()}; + an_a += 1; +} + +int tmain(bool f, int a) +{ + auto an_a = A(); + auto an_b = A(); + an_a += 2; + an_b = an_a; + return an_b.value(); +}; + +} +} \ No newline at end of file diff --git a/tests/t20030/test_case.h b/tests/t20030/test_case.h new file mode 100644 index 00000000..6e7da9f1 --- /dev/null +++ b/tests/t20030/test_case.h @@ -0,0 +1,66 @@ +/** + * tests/t20030/test_case.h + * + * Copyright (c) 2021-2023 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("t20030", "[test-case][sequence]") +{ + auto [config, db] = load_config("t20030"); + + auto diagram = config.diagrams["t20030_sequence"]; + + REQUIRE(diagram->name == "t20030_sequence"); + + auto model = generate_sequence_diagram(*db, diagram); + + REQUIRE(model->name() == "t20030_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(int)"), _A("magic()"), "")); + + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "create()")); + REQUIRE_THAT( + puml, HasCall(_A("tmain(int)"), _A("A"), "operator+=(int)")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "add(int)")); + + REQUIRE_THAT(puml, HasCall(_A("tmain(bool,int)"), _A("A"), "A()")); + REQUIRE_THAT( + puml, HasCall(_A("tmain(bool,int)"), _A("A"), "operator+=(int)")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "add(int)")); + REQUIRE_THAT(puml, + HasCall(_A("tmain(bool,int)"), _A("A"), "operator=(const A &)")); + REQUIRE_THAT(puml, HasCall(_A("A"), _A("A"), "set(int)")); + REQUIRE_THAT(puml, HasCall(_A("tmain(bool,int)"), _A("A"), "value()")); + + save_puml( + config.output_directory() + "/" + diagram->name + ".puml", puml); + } + + { + auto j = generate_sequence_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); + } +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index cfc56a8c..3bdd3ab0 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -343,6 +343,7 @@ using namespace clanguml::test::matchers; #include "t20027/test_case.h" #include "t20028/test_case.h" #include "t20029/test_case.h" +#include "t20030/test_case.h" /// /// Package diagram tests diff --git a/tests/test_cases.h b/tests/test_cases.h index 1b825b1f..33e820c6 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -146,6 +146,7 @@ public: 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 diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 97c2b7ad..d244636f 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -286,6 +286,9 @@ test_cases: - name: t20029 title: Combined feature sequence diagram test case description: + - name: t20030 + title: Constructor and operator call test case + description: Package diagrams: - name: t30001 title: Basic package diagram test case