From 0d237fec0a18554a0ebc1ef0296d8443ce52fc4d Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sat, 21 Oct 2023 21:47:04 +0200 Subject: [PATCH] Added support for call comment directive to inject calls in comments (Fixes #196) --- docs/sequence_diagrams.md | 29 +++++++++++++ src/common/clang_utils.cc | 3 +- src/decorators/decorators.cc | 14 +++++++ src/decorators/decorators.h | 11 +++++ .../visitor/call_expression_context.cc | 4 ++ .../visitor/translation_unit_visitor.cc | 41 +++++++++++++------ tests/t20038/t20038.cc | 2 +- tests/t20038/test_case.h | 6 +-- 8 files changed, 92 insertions(+), 18 deletions(-) diff --git a/docs/sequence_diagrams.md b/docs/sequence_diagrams.md index 86905283..60b150c6 100644 --- a/docs/sequence_diagrams.md +++ b/docs/sequence_diagrams.md @@ -9,6 +9,7 @@ * [Customizing participants order](#customizing-participants-order) * [Generating return types](#generating-return-types) * [Generating condition statements](#generating-condition-statements) +* [Injecting call expressions manually through comments](#injecting-call-expressions-manually-through-comments) * [Including comments in sequence diagrams](#including-comments-in-sequence-diagrams) @@ -316,6 +317,34 @@ generate_condition_statements: true An example of a diagram with this feature enabled is presented below: ![extension](test_cases/t20033_sequence.svg) +## Injecting call expressions manually through comments +In some cases, `clang-uml` is not yet able to discover a call expression target +in some line of code. This can include passing function or method address to +some executor (e.g. thread), async calls etc. + +However, a call expression can be injected manually through a comment +directive `\uml{note CALLEE}`, when placed just before such line of code, for +example: + +```cpp + // \uml{call clanguml::t20038::B::bbb()} + auto bbb_future = std::async(std::launch::deferred, &B::bbb, b); +``` + +also see the [t20038](test_cases/t20038.md) test case. + +Please note that the callee must have fully qualified name including complete +namespace. + +In order to enable this, the `.clang-uml` must contain the following option: + +```yaml +add_compile_flags: + - -fparse-all-comments +``` + +otherwise Clang will skip these comments during AST traversal. + ## Including comments in sequence diagrams `clang-uml` can add code comments placed directly before are next to a call expression as notes in the diagram (see for instance diff --git a/src/common/clang_utils.cc b/src/common/clang_utils.cc index 7a9713ba..cbbaf991 100644 --- a/src/common/clang_utils.cc +++ b/src/common/clang_utils.cc @@ -856,7 +856,8 @@ clang::RawComment *get_expression_raw_comment(const clang::SourceManager &sm, auto expr_begin = stmt->getSourceRange().getBegin(); const auto expr_begin_line = sm.getSpellingLineNumber(expr_begin); - if (!context.Comments.empty()) + if (!context.Comments.empty() && + context.Comments.getCommentsInFile(sm.getFileID(expr_begin)) != nullptr) for (const auto [offset, raw_comment] : *context.Comments.getCommentsInFile(sm.getFileID(expr_begin))) { const auto comment_end_line = sm.getSpellingLineNumber( diff --git a/src/decorators/decorators.cc b/src/decorators/decorators.cc index c8ff0371..38c7a63f 100644 --- a/src/decorators/decorators.cc +++ b/src/decorators/decorators.cc @@ -47,6 +47,9 @@ std::shared_ptr decorator::from_string(std::string_view c) if (c.find(association::label) == 0) { return association::from_string(c); } + if (c.find(call::label) == 0) { + return call::from_string(c); + } return {}; } @@ -173,6 +176,17 @@ std::shared_ptr association::from_string(std::string_view c) return res; } +std::shared_ptr call::from_string(std::string_view c) +{ + auto res = std::make_shared(); + auto toks = res->tokenize(call::label, c); + + res->diagrams = toks.diagrams; + res->callee = util::trim(toks.text); + + return res; +} + std::vector> parse( std::string documentation_block, const std::string &clanguml_tag) { diff --git a/src/decorators/decorators.h b/src/decorators/decorators.h index 4a62525a..2425607d 100644 --- a/src/decorators/decorators.h +++ b/src/decorators/decorators.h @@ -140,6 +140,17 @@ struct association : public relationship { static std::shared_ptr from_string(std::string_view c); }; +/** + * @brief Represents a call message in sequence diagram + */ +struct call : public decorator { + static inline const std::string label{"call"}; + + std::string callee; + + static std::shared_ptr from_string(std::string_view c); +}; + /** * @brief Parse a documentation block and extract all clang-uml decorators * diff --git a/src/sequence_diagram/visitor/call_expression_context.cc b/src/sequence_diagram/visitor/call_expression_context.cc index e84064a3..862ca241 100644 --- a/src/sequence_diagram/visitor/call_expression_context.cc +++ b/src/sequence_diagram/visitor/call_expression_context.cc @@ -75,6 +75,10 @@ clang::ASTContext *call_expression_context::get_ast_context() const return ¤t_function_decl_->getASTContext(); } + if (current_method_decl_ != nullptr) { + return ¤t_function_decl_->getASTContext(); + } + return nullptr; } diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index 64673ab8..549fe504 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -947,13 +947,9 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) using clanguml::sequence_diagram::model::activity; using clanguml::sequence_diagram::model::message; - if (!should_include(expr)) + if (!context().valid() || context().get_ast_context() == nullptr) return true; - LOG_TRACE("Visiting call expression at {} [caller_id = {}]", - expr->getBeginLoc().printToString(source_manager()), - context().caller_id()); - message m{message_t::kCall, context().caller_id()}; m.in_static_declaration_context(within_static_variable_declaration_ > 0); @@ -967,6 +963,25 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) if (m.skip()) return true; + auto generated_message_from_comment{false}; + for (const auto &decorator : m.decorators()) { + auto call_decorator = + std::dynamic_pointer_cast(decorator); + if (call_decorator && + call_decorator->applies_to_diagram(config().name)) { + m.set_to(common::to_id(call_decorator->callee)); + generated_message_from_comment = true; + break; + } + } + + if (!generated_message_from_comment && !should_include(expr)) + return true; + + LOG_TRACE("Visiting call expression at {} [caller_id = {}]", + expr->getBeginLoc().printToString(source_manager()), + context().caller_id()); + // If we're currently inside a lambda expression, set it's id as // message source rather then enclosing context // Unless the lambda is declared in a function or method call @@ -978,17 +993,17 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) m.set_message_scope(common::model::message_scope_t::kCondition); } + if (generated_message_from_comment) { } // // Call to an overloaded operator // - if (const auto *operator_call_expr = - clang::dyn_cast_or_null(expr); - operator_call_expr != nullptr) { + else if (const auto *operator_call_expr = + clang::dyn_cast_or_null(expr); + operator_call_expr != nullptr) { if (!process_operator_call_expression(m, operator_call_expr)) return true; } - // // Call to a class method // @@ -1042,9 +1057,11 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr) } if (m.from() > 0 && m.to() > 0) { - auto expr_comment = get_expression_comment(source_manager(), - *context().get_ast_context(), context().caller_id(), expr); - m.set_comment(expr_comment); + if (!generated_message_from_comment) { + auto expr_comment = get_expression_comment(source_manager(), + *context().get_ast_context(), context().caller_id(), expr); + m.set_comment(expr_comment); + } if (diagram().sequences().find(m.from()) == diagram().sequences().end()) { diff --git a/tests/t20038/t20038.cc b/tests/t20038/t20038.cc index d07dc138..9f2b6a79 100644 --- a/tests/t20038/t20038.cc +++ b/tests/t20038/t20038.cc @@ -68,7 +68,7 @@ int tmain() // \uml{skip} b.bb(); - // TODO: \uml{call B::bbb()} + // \uml{call clanguml::t20038::B::bbb()} auto bbb_future = std::async(std::launch::deferred, &B::bbb, b); bbb_future.wait(); diff --git a/tests/t20038/test_case.h b/tests/t20038/test_case.h index 3d1a86da..9765010e 100644 --- a/tests/t20038/test_case.h +++ b/tests/t20038/test_case.h @@ -39,8 +39,7 @@ TEST_CASE("t20038", "[test-case][sequence]") REQUIRE_THAT(src, !HasCall(_A("tmain()"), _A("B"), "bb()")); - // TODO: REQUIRE_THAT( - // src, HasCall(_A("tmain()"), _A("B"), "bbb()")); + REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "bbb()")); REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "bbbb()")); @@ -90,8 +89,7 @@ TEST_CASE("t20038", "[test-case][sequence]") REQUIRE_THAT(src, !HasCall(_A("tmain()"), _A("B"), "bb()")); - // TODO: REQUIRE_THAT( - // src, HasCall(_A("tmain()"), _A("B"), "bbb()")); + REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "bbb()")); REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("B"), "bbbb()"));