Added support for call expressions tracking through lambdas in function arguments (#168)
This commit is contained in:
@@ -112,11 +112,15 @@ void generator::generate_call(const message &m, std::ostream &ostr) const
|
|||||||
|
|
||||||
void generator::generate_return(const message &m, std::ostream &ostr) const
|
void generator::generate_return(const message &m, std::ostream &ostr) const
|
||||||
{
|
{
|
||||||
|
|
||||||
// Add return activity only for messages between different actors and
|
// Add return activity only for messages between different actors and
|
||||||
// only if the return type is different than void
|
// only if the return type is different than void
|
||||||
|
if (m.from() == m.to())
|
||||||
|
return;
|
||||||
|
|
||||||
const auto &from = model().get_participant<model::participant>(m.from());
|
const auto &from = model().get_participant<model::participant>(m.from());
|
||||||
const auto &to = model().get_participant<model::function>(m.to());
|
const auto &to = model().get_participant<model::function>(m.to());
|
||||||
if ((m.from() != m.to()) && !to.value().is_void()) {
|
if (to.has_value() && !to.value().is_void()) {
|
||||||
const std::string from_alias = generate_alias(from.value());
|
const std::string from_alias = generate_alias(from.value());
|
||||||
|
|
||||||
const std::string to_alias = generate_alias(to.value());
|
const std::string to_alias = generate_alias(to.value());
|
||||||
|
|||||||
@@ -190,12 +190,17 @@ public:
|
|||||||
*/
|
*/
|
||||||
void is_lambda(bool is_lambda);
|
void is_lambda(bool is_lambda);
|
||||||
|
|
||||||
|
void set_lambda_operator_id(common::id_t id) { lambda_operator_id_ = id; }
|
||||||
|
|
||||||
|
common::id_t lambda_operator_id() const { return lambda_operator_id_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool is_struct_{false};
|
bool is_struct_{false};
|
||||||
bool is_template_{false};
|
bool is_template_{false};
|
||||||
bool is_template_instantiation_{false};
|
bool is_template_instantiation_{false};
|
||||||
bool is_alias_{false};
|
bool is_alias_{false};
|
||||||
bool is_lambda_{false};
|
bool is_lambda_{false};
|
||||||
|
common::id_t lambda_operator_id_{0};
|
||||||
|
|
||||||
std::string full_name_;
|
std::string full_name_;
|
||||||
};
|
};
|
||||||
@@ -406,7 +411,7 @@ struct method : public function {
|
|||||||
*
|
*
|
||||||
* @return Fully qualified elements name.
|
* @return Fully qualified elements name.
|
||||||
*/
|
*/
|
||||||
std::string full_name(bool /*relative*/) const override;
|
std::string full_name(bool relative) const override;
|
||||||
|
|
||||||
std::string message_name(message_render_mode mode) const override;
|
std::string message_name(message_render_mode mode) const override;
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,22 @@ bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool translation_unit_visitor::TraverseCXXMethodDecl(
|
||||||
|
clang::CXXMethodDecl *declaration)
|
||||||
|
{
|
||||||
|
// We need to backup the context, since other methods or functions can
|
||||||
|
// be traversed during this traversal (e.g. template function/method
|
||||||
|
// specializations)
|
||||||
|
auto context_backup = context();
|
||||||
|
|
||||||
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXMethodDecl(
|
||||||
|
declaration);
|
||||||
|
|
||||||
|
call_expression_context_ = context_backup;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool translation_unit_visitor::VisitCXXMethodDecl(
|
bool translation_unit_visitor::VisitCXXMethodDecl(
|
||||||
clang::CXXMethodDecl *declaration)
|
clang::CXXMethodDecl *declaration)
|
||||||
{
|
{
|
||||||
@@ -229,6 +245,9 @@ bool translation_unit_visitor::VisitCXXMethodDecl(
|
|||||||
if (auto *method_definition = clang::dyn_cast<clang::CXXMethodDecl>(
|
if (auto *method_definition = clang::dyn_cast<clang::CXXMethodDecl>(
|
||||||
declaration_definition);
|
declaration_definition);
|
||||||
method_definition != nullptr) {
|
method_definition != nullptr) {
|
||||||
|
LOG_DBG("Calling VisitCXXMethodDecl recursively for forward "
|
||||||
|
"declaration");
|
||||||
|
|
||||||
return VisitCXXMethodDecl(method_definition);
|
return VisitCXXMethodDecl(method_definition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +274,7 @@ bool translation_unit_visitor::VisitCXXMethodDecl(
|
|||||||
method_model_ptr->set_id(common::to_id(method_full_name));
|
method_model_ptr->set_id(common::to_id(method_full_name));
|
||||||
|
|
||||||
// Callee methods in call expressions are referred to by first declaration
|
// Callee methods in call expressions are referred to by first declaration
|
||||||
// id
|
// id, so they should both be mapped to method_model
|
||||||
if (declaration->isThisDeclarationADefinition()) {
|
if (declaration->isThisDeclarationADefinition()) {
|
||||||
set_unique_id(
|
set_unique_id(
|
||||||
declaration->getFirstDecl()->getID(), method_model_ptr->id());
|
declaration->getFirstDecl()->getID(), method_model_ptr->id());
|
||||||
@@ -276,6 +295,22 @@ bool translation_unit_visitor::VisitCXXMethodDecl(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool translation_unit_visitor::TraverseFunctionDecl(
|
||||||
|
clang::FunctionDecl *declaration)
|
||||||
|
{
|
||||||
|
// We need to backup the context, since other methods or functions can
|
||||||
|
// be traversed during this traversal (e.g. template function/method
|
||||||
|
// specializations)
|
||||||
|
auto context_backup = context();
|
||||||
|
|
||||||
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseFunctionDecl(
|
||||||
|
declaration);
|
||||||
|
|
||||||
|
call_expression_context_ = context_backup;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool translation_unit_visitor::VisitFunctionDecl(
|
bool translation_unit_visitor::VisitFunctionDecl(
|
||||||
clang::FunctionDecl *declaration)
|
clang::FunctionDecl *declaration)
|
||||||
{
|
{
|
||||||
@@ -391,8 +426,9 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr)
|
|||||||
const auto lambda_full_name =
|
const auto lambda_full_name =
|
||||||
expr->getLambdaClass()->getCanonicalDecl()->getNameAsString();
|
expr->getLambdaClass()->getCanonicalDecl()->getNameAsString();
|
||||||
|
|
||||||
LOG_TRACE("Visiting lambda expression {} at {}", lambda_full_name,
|
LOG_TRACE("Visiting lambda expression {} at {} [caller_id = {}]",
|
||||||
expr->getBeginLoc().printToString(source_manager()));
|
lambda_full_name, expr->getBeginLoc().printToString(source_manager()),
|
||||||
|
context().caller_id());
|
||||||
|
|
||||||
LOG_TRACE("Lambda call operator ID {} - lambda class ID {}, class call "
|
LOG_TRACE("Lambda call operator ID {} - lambda class ID {}, class call "
|
||||||
"operator ID {}",
|
"operator ID {}",
|
||||||
@@ -427,6 +463,33 @@ bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr)
|
|||||||
lambda_method_model_ptr->set_id(common::to_id(
|
lambda_method_model_ptr->set_id(common::to_id(
|
||||||
get_participant(cls_id).value().full_name(false) + "::" + method_name));
|
get_participant(cls_id).value().full_name(false) + "::" + method_name));
|
||||||
|
|
||||||
|
get_participant<model::class_>(cls_id).value().set_lambda_operator_id(
|
||||||
|
lambda_method_model_ptr->id());
|
||||||
|
|
||||||
|
// If lambda expression is in an argument to a method/function, and that
|
||||||
|
// method function would be excluded by filters
|
||||||
|
if (std::holds_alternative<clang::CallExpr *>(
|
||||||
|
context().current_callexpr())/* &&
|
||||||
|
!should_include(
|
||||||
|
std::get<clang::CallExpr *>(context().current_callexpr()))*/) {
|
||||||
|
using clanguml::common::model::message_t;
|
||||||
|
using clanguml::sequence_diagram::model::message;
|
||||||
|
|
||||||
|
message m{message_t::kCall, context().caller_id()};
|
||||||
|
set_source_location(*expr, m);
|
||||||
|
m.set_from(context().caller_id());
|
||||||
|
m.set_to(lambda_method_model_ptr->id());
|
||||||
|
|
||||||
|
diagram().add_active_participant(m.from());
|
||||||
|
diagram().add_active_participant(m.to());
|
||||||
|
|
||||||
|
LOG_DBG("Found call {} from {} [{}] to {} [{}]", m.message_name(),
|
||||||
|
m.from(), m.from(), m.to(), m.to());
|
||||||
|
|
||||||
|
push_message(std::get<clang::CallExpr *>(context().current_callexpr()),
|
||||||
|
std::move(m));
|
||||||
|
}
|
||||||
|
|
||||||
context().enter_lambda_expression(lambda_method_model_ptr->id());
|
context().enter_lambda_expression(lambda_method_model_ptr->id());
|
||||||
|
|
||||||
set_unique_id(
|
set_unique_id(
|
||||||
@@ -454,10 +517,19 @@ bool translation_unit_visitor::TraverseLambdaExpr(clang::LambdaExpr *expr)
|
|||||||
|
|
||||||
bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr)
|
bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr)
|
||||||
{
|
{
|
||||||
|
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
LOG_DBG("Entering call expression at {}",
|
||||||
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
|
||||||
context().enter_callexpr(expr);
|
context().enter_callexpr(expr);
|
||||||
|
|
||||||
RecursiveASTVisitor<translation_unit_visitor>::TraverseCallExpr(expr);
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCallExpr(expr);
|
||||||
|
|
||||||
|
LOG_DBG("Leaving call expression at {}",
|
||||||
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
|
||||||
context().leave_callexpr();
|
context().leave_callexpr();
|
||||||
|
|
||||||
pop_message_to_diagram(expr);
|
pop_message_to_diagram(expr);
|
||||||
@@ -468,9 +540,24 @@ bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr)
|
|||||||
bool translation_unit_visitor::TraverseCXXMemberCallExpr(
|
bool translation_unit_visitor::TraverseCXXMemberCallExpr(
|
||||||
clang::CXXMemberCallExpr *expr)
|
clang::CXXMemberCallExpr *expr)
|
||||||
{
|
{
|
||||||
|
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
LOG_DBG("Entering member call expression at {} to {}::{}",
|
||||||
|
expr->getBeginLoc().printToString(source_manager()),
|
||||||
|
common::to_string(expr->getObjectType(), context().get_ast_context()),
|
||||||
|
common::to_string(expr->getMethodDecl()));
|
||||||
|
|
||||||
|
context().enter_callexpr(expr);
|
||||||
|
|
||||||
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXMemberCallExpr(
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXMemberCallExpr(
|
||||||
expr);
|
expr);
|
||||||
|
|
||||||
|
LOG_DBG("Leaving member call expression at {}",
|
||||||
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
|
||||||
|
context().leave_callexpr();
|
||||||
|
|
||||||
pop_message_to_diagram(expr);
|
pop_message_to_diagram(expr);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -479,9 +566,13 @@ bool translation_unit_visitor::TraverseCXXMemberCallExpr(
|
|||||||
bool translation_unit_visitor::TraverseCXXOperatorCallExpr(
|
bool translation_unit_visitor::TraverseCXXOperatorCallExpr(
|
||||||
clang::CXXOperatorCallExpr *expr)
|
clang::CXXOperatorCallExpr *expr)
|
||||||
{
|
{
|
||||||
|
context().enter_callexpr(expr);
|
||||||
|
|
||||||
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXOperatorCallExpr(
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXOperatorCallExpr(
|
||||||
expr);
|
expr);
|
||||||
|
|
||||||
|
context().leave_callexpr();
|
||||||
|
|
||||||
pop_message_to_diagram(expr);
|
pop_message_to_diagram(expr);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -944,6 +1035,10 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
if (!context().valid() || context().get_ast_context() == nullptr)
|
if (!context().valid() || context().get_ast_context() == nullptr)
|
||||||
return true;
|
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()};
|
message m{message_t::kCall, context().caller_id()};
|
||||||
|
|
||||||
m.in_static_declaration_context(within_static_variable_declaration_ > 0);
|
m.in_static_declaration_context(within_static_variable_declaration_ > 0);
|
||||||
@@ -957,25 +1052,11 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
if (m.skip())
|
if (m.skip())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
auto generated_message_from_comment{false};
|
auto generated_message_from_comment = generate_message_from_comment(m);
|
||||||
for (const auto &decorator : m.decorators()) {
|
|
||||||
auto call_decorator =
|
|
||||||
std::dynamic_pointer_cast<decorators::call>(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))
|
if (!generated_message_from_comment && !should_include(expr))
|
||||||
return true;
|
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
|
// If we're currently inside a lambda expression, set it's id as
|
||||||
// message source rather then enclosing context
|
// message source rather then enclosing context
|
||||||
// Unless the lambda is declared in a function or method call
|
// Unless the lambda is declared in a function or method call
|
||||||
@@ -987,7 +1068,9 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
m.set_message_scope(common::model::message_scope_t::kCondition);
|
m.set_message_scope(common::model::message_scope_t::kCondition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (generated_message_from_comment) { }
|
if (generated_message_from_comment) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
//
|
//
|
||||||
// Call to an overloaded operator
|
// Call to an overloaded operator
|
||||||
//
|
//
|
||||||
@@ -1015,8 +1098,11 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
auto *callee_decl = expr->getCalleeDecl();
|
auto *callee_decl = expr->getCalleeDecl();
|
||||||
|
|
||||||
if (callee_decl == nullptr) {
|
if (callee_decl == nullptr) {
|
||||||
LOG_DBG("Cannot get callee declaration - trying direct callee...");
|
LOG_DBG("Cannot get callee declaration - trying direct function "
|
||||||
|
"callee...");
|
||||||
callee_decl = expr->getDirectCallee();
|
callee_decl = expr->getDirectCallee();
|
||||||
|
LOG_DBG(
|
||||||
|
"Found function/method callee in: {}", common::to_string(expr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callee_decl == nullptr) {
|
if (callee_decl == nullptr) {
|
||||||
@@ -1040,9 +1126,10 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!process_function_call_expression(m, expr)) {
|
auto success = process_function_call_expression(m, expr);
|
||||||
LOG_DBG("Skipping call to unsupported type of call expression "
|
|
||||||
"at: {}",
|
if (!success) {
|
||||||
|
LOG_DBG("Skipping call to call expression at: {}",
|
||||||
expr->getBeginLoc().printToString(source_manager()));
|
expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1050,6 +1137,7 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add message to diagram
|
||||||
if (m.from() > 0 && m.to() > 0) {
|
if (m.from() > 0 && m.to() > 0) {
|
||||||
if (!generated_message_from_comment) {
|
if (!generated_message_from_comment) {
|
||||||
auto expr_comment = get_expression_comment(source_manager(),
|
auto expr_comment = get_expression_comment(source_manager(),
|
||||||
@@ -1075,6 +1163,23 @@ bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *expr)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool translation_unit_visitor::generate_message_from_comment(
|
||||||
|
model::message &m) const
|
||||||
|
{
|
||||||
|
auto generated_message_from_comment{false};
|
||||||
|
for (const auto &decorator : m.decorators()) {
|
||||||
|
auto call_decorator =
|
||||||
|
std::dynamic_pointer_cast<decorators::call>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return generated_message_from_comment;
|
||||||
|
}
|
||||||
|
|
||||||
bool translation_unit_visitor::TraverseVarDecl(clang::VarDecl *decl)
|
bool translation_unit_visitor::TraverseVarDecl(clang::VarDecl *decl)
|
||||||
{
|
{
|
||||||
if (decl->isStaticLocal())
|
if (decl->isStaticLocal())
|
||||||
@@ -1155,12 +1260,30 @@ bool translation_unit_visitor::process_operator_call_expression(
|
|||||||
operator_call_expr->getCalleeDecl()->getID(),
|
operator_call_expr->getCalleeDecl()->getID(),
|
||||||
operator_call_expr->getBeginLoc().printToString(source_manager()));
|
operator_call_expr->getBeginLoc().printToString(source_manager()));
|
||||||
|
|
||||||
auto maybe_id = get_unique_id(operator_call_expr->getCalleeDecl()->getID());
|
// Handle the case if the callee is a lambda
|
||||||
if (maybe_id.has_value()) {
|
if (const auto *lambda_method = clang::dyn_cast<clang::CXXMethodDecl>(
|
||||||
m.set_to(maybe_id.value());
|
operator_call_expr->getCalleeDecl());
|
||||||
|
lambda_method != nullptr && lambda_method->getParent()->isLambda()) {
|
||||||
|
|
||||||
|
LOG_DBG("Operator callee is a lambda: {}",
|
||||||
|
common::to_string(lambda_method));
|
||||||
|
|
||||||
|
const auto source_location{
|
||||||
|
lambda_source_location(lambda_method->getParent()->getLocation())};
|
||||||
|
|
||||||
|
auto lambda_name = make_lambda_name(lambda_method->getParent());
|
||||||
|
|
||||||
|
m.set_to(lambda_method->getParent()->getID());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m.set_to(operator_call_expr->getCalleeDecl()->getID());
|
auto maybe_id =
|
||||||
|
get_unique_id(operator_call_expr->getCalleeDecl()->getID());
|
||||||
|
if (maybe_id.has_value()) {
|
||||||
|
m.set_to(maybe_id.value());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m.set_to(operator_call_expr->getCalleeDecl()->getID());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m.set_message_name(fmt::format(
|
m.set_message_name(fmt::format(
|
||||||
@@ -1513,9 +1636,6 @@ translation_unit_visitor::create_class_model(clang::CXXRecordDecl *cls)
|
|||||||
c.set_name(type_name);
|
c.set_name(type_name);
|
||||||
c.set_namespace(ns);
|
c.set_namespace(ns);
|
||||||
c.set_id(common::to_id(c.full_name(false)));
|
c.set_id(common::to_id(c.full_name(false)));
|
||||||
|
|
||||||
// TODO: Check if lambda is declared as an argument passed to a
|
|
||||||
// function/method call
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LOG_WARN("Cannot find parent declaration for lambda {}",
|
LOG_WARN("Cannot find parent declaration for lambda {}",
|
||||||
@@ -1675,29 +1795,38 @@ std::string translation_unit_visitor::simplify_system_template(
|
|||||||
return config().simplify_template_type(full_name);
|
return config().simplify_template_type(full_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string translation_unit_visitor::lambda_source_location(
|
||||||
|
const clang::SourceLocation &source_location) const
|
||||||
|
{
|
||||||
|
const auto file_line =
|
||||||
|
source_manager().getSpellingLineNumber(source_location);
|
||||||
|
const auto file_column =
|
||||||
|
source_manager().getSpellingColumnNumber(source_location);
|
||||||
|
const std::string file_name =
|
||||||
|
config()
|
||||||
|
.make_path_relative(
|
||||||
|
source_manager().getFilename(source_location).str())
|
||||||
|
.string();
|
||||||
|
return fmt::format("{}:{}:{}", file_name, file_line, file_column);
|
||||||
|
}
|
||||||
|
|
||||||
std::string translation_unit_visitor::make_lambda_name(
|
std::string translation_unit_visitor::make_lambda_name(
|
||||||
const clang::CXXRecordDecl *cls) const
|
const clang::CXXRecordDecl *cls) const
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
const auto location = cls->getLocation();
|
const auto location = cls->getLocation();
|
||||||
const auto file_line = source_manager().getSpellingLineNumber(location);
|
const std::string source_location{lambda_source_location(location)};
|
||||||
const auto file_column = source_manager().getSpellingColumnNumber(location);
|
|
||||||
const std::string file_name =
|
|
||||||
config()
|
|
||||||
.make_path_relative(source_manager().getFilename(location).str())
|
|
||||||
.string();
|
|
||||||
|
|
||||||
if (context().caller_id() != 0 &&
|
if (context().caller_id() != 0 &&
|
||||||
get_participant(context().caller_id()).has_value()) {
|
get_participant(context().caller_id()).has_value()) {
|
||||||
auto parent_full_name =
|
auto parent_full_name =
|
||||||
get_participant(context().caller_id()).value().full_name_no_ns();
|
get_participant(context().caller_id()).value().full_name_no_ns();
|
||||||
|
|
||||||
result = fmt::format("{}##(lambda {}:{}:{})", parent_full_name,
|
result =
|
||||||
file_name, file_line, file_column);
|
fmt::format("{}##(lambda {})", parent_full_name, source_location);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
result =
|
result = fmt::format("(lambda {})", source_location);
|
||||||
fmt::format("(lambda {}:{}:{})", file_name, file_line, file_column);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -1775,6 +1904,25 @@ void translation_unit_visitor::finalize()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change all messages with target set to an id of a lambda expression to
|
||||||
|
// to the ID of their operator() - this is necessary, as some calls to
|
||||||
|
// lambda expressions are visited before the actual lambda expressions
|
||||||
|
// are visited...
|
||||||
|
for (auto &[id, activity] : diagram().sequences()) {
|
||||||
|
for (auto &m : activity.messages()) {
|
||||||
|
auto participant = diagram().get_participant<model::class_>(m.to());
|
||||||
|
|
||||||
|
if (participant && participant.value().is_lambda() &&
|
||||||
|
participant.value().lambda_operator_id() != 0) {
|
||||||
|
LOG_DBG("Changing lambda expression target id from {} to {}",
|
||||||
|
m.to(), participant.value().lambda_operator_id());
|
||||||
|
|
||||||
|
m.set_to(participant.value().lambda_operator_id());
|
||||||
|
m.set_message_name("operator()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<clanguml::sequence_diagram::model::method>
|
std::unique_ptr<clanguml::sequence_diagram::model::method>
|
||||||
@@ -1882,7 +2030,21 @@ bool translation_unit_visitor::should_include(const clang::CallExpr *expr) const
|
|||||||
|
|
||||||
const auto expr_file = expr->getBeginLoc().printToString(source_manager());
|
const auto expr_file = expr->getBeginLoc().printToString(source_manager());
|
||||||
|
|
||||||
return diagram().should_include(common::model::source_file{expr_file});
|
if (!diagram().should_include(common::model::source_file{expr_file}))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto *callee_decl = expr->getCalleeDecl();
|
||||||
|
|
||||||
|
if (callee_decl != nullptr) {
|
||||||
|
const auto *callee_function = callee_decl->getAsFunction();
|
||||||
|
|
||||||
|
if ((callee_function == nullptr) || !should_include(callee_function))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return should_include(callee_function);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool translation_unit_visitor::should_include(
|
bool translation_unit_visitor::should_include(
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ public:
|
|||||||
|
|
||||||
bool TraverseLambdaExpr(clang::LambdaExpr *expr);
|
bool TraverseLambdaExpr(clang::LambdaExpr *expr);
|
||||||
|
|
||||||
|
bool TraverseCXXMethodDecl(clang::CXXMethodDecl *declaration);
|
||||||
|
|
||||||
bool VisitCXXMethodDecl(clang::CXXMethodDecl *declaration);
|
bool VisitCXXMethodDecl(clang::CXXMethodDecl *declaration);
|
||||||
|
|
||||||
bool VisitCXXRecordDecl(clang::CXXRecordDecl *declaration);
|
bool VisitCXXRecordDecl(clang::CXXRecordDecl *declaration);
|
||||||
@@ -104,6 +106,8 @@ public:
|
|||||||
bool VisitClassTemplateSpecializationDecl(
|
bool VisitClassTemplateSpecializationDecl(
|
||||||
clang::ClassTemplateSpecializationDecl *declaration);
|
clang::ClassTemplateSpecializationDecl *declaration);
|
||||||
|
|
||||||
|
bool TraverseFunctionDecl(clang::FunctionDecl *declaration);
|
||||||
|
|
||||||
bool VisitFunctionDecl(clang::FunctionDecl *declaration);
|
bool VisitFunctionDecl(clang::FunctionDecl *declaration);
|
||||||
|
|
||||||
bool VisitFunctionTemplateDecl(
|
bool VisitFunctionTemplateDecl(
|
||||||
@@ -336,6 +340,21 @@ private:
|
|||||||
*/
|
*/
|
||||||
std::string make_lambda_name(const clang::CXXRecordDecl *cls) const;
|
std::string make_lambda_name(const clang::CXXRecordDecl *cls) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render lambda source location to string
|
||||||
|
*
|
||||||
|
* Returns exact source code location of the lambda expression in the form
|
||||||
|
* <filepath>:<line>:<column>.
|
||||||
|
*
|
||||||
|
* The filepath is relative to the `relative_to` config option.
|
||||||
|
*
|
||||||
|
* @param source_location Clang SourceLocation instance associated with
|
||||||
|
* lambda expression
|
||||||
|
* @return String representation of the location
|
||||||
|
*/
|
||||||
|
std::string lambda_source_location(
|
||||||
|
const clang::SourceLocation &source_location) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if template is a smart pointer
|
* @brief Check if template is a smart pointer
|
||||||
*
|
*
|
||||||
@@ -441,6 +460,15 @@ private:
|
|||||||
const clang::SourceManager &sm, const clang::ASTContext &context,
|
const clang::SourceManager &sm, const clang::ASTContext &context,
|
||||||
int64_t caller_id, const clang::Stmt *stmt);
|
int64_t caller_id, const clang::Stmt *stmt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes model message from comment call directive
|
||||||
|
*
|
||||||
|
* @param m Message instance
|
||||||
|
* @return True, if the comment associated with the call expression
|
||||||
|
* contained a call directive and it was parsed correctly.
|
||||||
|
*/
|
||||||
|
bool generate_message_from_comment(model::message &m) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get template builder reference
|
* @brief Get template builder reference
|
||||||
*
|
*
|
||||||
|
|||||||
17
tests/t20044/.clang-uml
Normal file
17
tests/t20044/.clang-uml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
add_compile_flags:
|
||||||
|
- -fparse-all-comments
|
||||||
|
diagrams:
|
||||||
|
t20044_sequence:
|
||||||
|
type: sequence
|
||||||
|
glob:
|
||||||
|
- t20044.cc
|
||||||
|
generate_message_comments: true
|
||||||
|
include:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20044
|
||||||
|
exclude:
|
||||||
|
namespaces:
|
||||||
|
- clanguml::t20044::detail2
|
||||||
|
using_namespace: clanguml::t20044
|
||||||
|
from:
|
||||||
|
- function: "clanguml::t20044::tmain()"
|
||||||
100
tests/t20044/t20044.cc
Normal file
100
tests/t20044/t20044.cc
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// #include "include/expected.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace clanguml {
|
||||||
|
namespace t20044 {
|
||||||
|
|
||||||
|
enum class error { OK, FAIL };
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
// Trivial std::expected mock-up just for testing calls through lambda
|
||||||
|
// expressions passed as arguments to methods
|
||||||
|
template <typename V, typename E> class expected {
|
||||||
|
private:
|
||||||
|
std::optional<V> value_;
|
||||||
|
std::optional<E> error_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit expected(V v)
|
||||||
|
: value_{std::move(v)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
explicit expected(E e)
|
||||||
|
: error_{std::move(e)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &value() const { return *value_; }
|
||||||
|
|
||||||
|
const auto &error() const { return *error_; }
|
||||||
|
|
||||||
|
template <class F> auto and_then(F &&f) &&
|
||||||
|
{
|
||||||
|
if (value_)
|
||||||
|
return f(*value_);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
namespace detail2 {
|
||||||
|
template <typename F> void run(F &&f) { f(); }
|
||||||
|
} // namespace detail2
|
||||||
|
|
||||||
|
using result_t = detail::expected<int, error>;
|
||||||
|
|
||||||
|
struct A {
|
||||||
|
auto a() const { }
|
||||||
|
|
||||||
|
auto a1() const { return result_t{10}; }
|
||||||
|
|
||||||
|
auto a2(int arg) const { return result_t{arg + 1}; }
|
||||||
|
|
||||||
|
auto a4(int arg) const { return result_t{arg + 1}; }
|
||||||
|
|
||||||
|
void a5() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
auto a3(int arg) { return result_t{arg + 1}; }
|
||||||
|
|
||||||
|
struct R {
|
||||||
|
template <typename F> R(F &&f) { f(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
int tmain()
|
||||||
|
{
|
||||||
|
A a;
|
||||||
|
|
||||||
|
// Call to template constructor with callable parameter and lambda
|
||||||
|
// expression as argument
|
||||||
|
R r([&a]() { a.a(); });
|
||||||
|
|
||||||
|
std::function<result_t(const A &, int)> a4_wrapper = &A::a4;
|
||||||
|
|
||||||
|
std::function<result_t(int)> a4_wrapper_to_a =
|
||||||
|
std::bind(a4_wrapper, a, std::placeholders::_1);
|
||||||
|
|
||||||
|
// The message to detail2::run() is skipped due to exclude filter, however
|
||||||
|
// the call to lambda and A::a5() is rendered
|
||||||
|
// TODO: Add some marker to highlight that this is not a direct call
|
||||||
|
detail2::run([&]() { a.a5(); });
|
||||||
|
|
||||||
|
return a
|
||||||
|
.a1()
|
||||||
|
// Call to a template method accepting a callable with lambda expression
|
||||||
|
// as argument, fully tracked showing method's activity and
|
||||||
|
.and_then([&](auto &&arg) { return a.a2(arg); })
|
||||||
|
// TODO: Call to a method accepting a callable with function pointer
|
||||||
|
// as argument
|
||||||
|
.and_then(a3)
|
||||||
|
// TODO: Call to a method accepting a callable with std::function as
|
||||||
|
// argument
|
||||||
|
.and_then(a4_wrapper_to_a)
|
||||||
|
.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
91
tests/t20044/test_case.h
Normal file
91
tests/t20044/test_case.h
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* tests/t20044/test_case.h
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-2024 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("t20044", "[test-case][sequence]")
|
||||||
|
{
|
||||||
|
auto [config, db] = load_config("t20044");
|
||||||
|
|
||||||
|
auto diagram = config.diagrams["t20044_sequence"];
|
||||||
|
|
||||||
|
REQUIRE(diagram->name == "t20044_sequence");
|
||||||
|
|
||||||
|
auto model = generate_sequence_diagram(*db, diagram);
|
||||||
|
|
||||||
|
REQUIRE(model->name() == "t20044_sequence");
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_puml(diagram, *model);
|
||||||
|
AliasMatcher _A(src);
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, StartsWith("@startuml"));
|
||||||
|
REQUIRE_THAT(src, EndsWith("@enduml\n"));
|
||||||
|
|
||||||
|
// Check if all calls exist
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(
|
||||||
|
_A("tmain()"), _A("R"), "R((lambda at t20044.cc:74:9) &&)"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("R"), _A("tmain()::(lambda t20044.cc:74:9)"),
|
||||||
|
"operator()()"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()::(lambda t20044.cc:74:9)"), _A("A"), "a()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"), _A("tmain()::(lambda t20044.cc:84:18)"),
|
||||||
|
"operator()()"));
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()::(lambda t20044.cc:84:18)"), _A("A"), "a5()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src, HasCall(_A("tmain()"), _A("A"), "a1()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("tmain()"), _A("detail::expected<int,error>"),
|
||||||
|
"and_then((lambda at t20044.cc:90:19) &&)"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(_A("detail::expected<int,error>"),
|
||||||
|
_A("tmain()::(lambda t20044.cc:90:19)"), "operator()()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(
|
||||||
|
_A("tmain()::(lambda t20044.cc:90:19)"), _A("A"), "a2(int)"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(src,
|
||||||
|
HasCall(
|
||||||
|
_A("A"), _A("detail::expected<int,error>"), "expected(int)"));
|
||||||
|
|
||||||
|
save_puml(config.output_directory(), diagram->name + ".puml", src);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto j = generate_sequence_json(diagram, *model);
|
||||||
|
|
||||||
|
using namespace json;
|
||||||
|
|
||||||
|
save_json(config.output_directory(), diagram->name + ".json", j);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto src = generate_sequence_mermaid(diagram, *model);
|
||||||
|
|
||||||
|
mermaid::AliasMatcher _A(src);
|
||||||
|
using mermaid::IsClass;
|
||||||
|
|
||||||
|
save_mermaid(config.output_directory(), diagram->name + ".mmd", src);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -470,6 +470,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t20041/test_case.h"
|
#include "t20041/test_case.h"
|
||||||
#include "t20042/test_case.h"
|
#include "t20042/test_case.h"
|
||||||
#include "t20043/test_case.h"
|
#include "t20043/test_case.h"
|
||||||
|
#include "t20044/test_case.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Package diagram tests
|
/// Package diagram tests
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ type: class
|
|||||||
title: Class diagram TU visitor
|
title: Class diagram TU visitor
|
||||||
include_relations_also_as_members: false
|
include_relations_also_as_members: false
|
||||||
generate_method_arguments: none
|
generate_method_arguments: none
|
||||||
generate_packages: false
|
generate_packages: true
|
||||||
glob:
|
glob:
|
||||||
- src/common/visitor/*.cc
|
- src/common/visitor/*.cc
|
||||||
- src/class_diagram/visitor/*.cc
|
- src/class_diagram/visitor/*.cc
|
||||||
|
|||||||
Reference in New Issue
Block a user