2319 lines
75 KiB
C++
2319 lines
75 KiB
C++
/**
|
|
* @file src/sequence_diagram/visitor/translation_unit_visitor.cc
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "translation_unit_visitor.h"
|
|
|
|
#include "common/clang_utils.h"
|
|
#include "common/model/namespace.h"
|
|
#include "sequence_diagram/model/participant.h"
|
|
|
|
namespace clanguml::sequence_diagram::visitor {
|
|
|
|
translation_unit_visitor::translation_unit_visitor(clang::SourceManager &sm,
|
|
clanguml::sequence_diagram::model::diagram &diagram,
|
|
const clanguml::config::sequence_diagram &config)
|
|
: visitor_specialization_t{sm, diagram, config}
|
|
, template_builder_{diagram, config, *this}
|
|
{
|
|
}
|
|
|
|
std::unique_ptr<sequence_diagram::model::class_>
|
|
translation_unit_visitor::create_element(
|
|
const clang::NamedDecl * /*decl*/) const
|
|
{
|
|
return std::make_unique<sequence_diagram::model::class_>(
|
|
config().using_namespace());
|
|
}
|
|
|
|
bool translation_unit_visitor::shouldVisitTemplateInstantiations()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
call_expression_context &translation_unit_visitor::context()
|
|
{
|
|
return call_expression_context_;
|
|
}
|
|
|
|
const call_expression_context &translation_unit_visitor::context() const
|
|
{
|
|
return call_expression_context_;
|
|
}
|
|
|
|
bool translation_unit_visitor::VisitCXXRecordDecl(
|
|
clang::CXXRecordDecl *declaration)
|
|
{
|
|
if (!should_include(declaration))
|
|
return true;
|
|
|
|
// Skip this class if it's parent template is already in the model
|
|
if (declaration->isTemplated() &&
|
|
declaration->getDescribedTemplate() != nullptr) {
|
|
if (get_unique_id(
|
|
common::id_t{declaration->getDescribedTemplate()->getID()}))
|
|
return true;
|
|
}
|
|
|
|
// TODO: Add support for classes defined in function/method bodies
|
|
if (declaration->isLocalClass() != nullptr)
|
|
return true;
|
|
|
|
LOG_TRACE("Visiting class declaration at {}",
|
|
declaration->getBeginLoc().printToString(source_manager()));
|
|
|
|
// Build the class declaration and store it in the diagram, even
|
|
// if we don't need it for any of the participants of this diagram
|
|
auto class_model_ptr = create_class_model(declaration);
|
|
|
|
if (!class_model_ptr)
|
|
return true;
|
|
|
|
context().reset();
|
|
|
|
const auto class_id = class_model_ptr->id();
|
|
|
|
set_unique_id(declaration->getID(), class_id);
|
|
|
|
auto &class_model =
|
|
diagram()
|
|
.get_participant<sequence_diagram::model::class_>(class_id)
|
|
.has_value()
|
|
? *diagram()
|
|
.get_participant<sequence_diagram::model::class_>(class_id)
|
|
.get()
|
|
: *class_model_ptr;
|
|
|
|
if (!declaration->isCompleteDefinition()) {
|
|
forward_declarations_.emplace(class_id, std::move(class_model_ptr));
|
|
return true;
|
|
}
|
|
|
|
forward_declarations_.erase(class_id);
|
|
|
|
if (diagram().should_include(class_model)) {
|
|
LOG_DBG("Adding class participant {} with id {}",
|
|
class_model.full_name(false), class_model.id());
|
|
|
|
assert(class_model.id() == class_id);
|
|
|
|
context().set_caller_id(class_id);
|
|
context().update(declaration);
|
|
|
|
diagram().add_participant(std::move(class_model_ptr));
|
|
}
|
|
else {
|
|
LOG_DBG(
|
|
"Skipping class {} with id {}", class_model.full_name(), class_id);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::VisitClassTemplateDecl(
|
|
clang::ClassTemplateDecl *declaration)
|
|
{
|
|
if (!should_include(declaration))
|
|
return true;
|
|
|
|
LOG_TRACE("Visiting class template declaration {} at {} [{}]",
|
|
declaration->getQualifiedNameAsString(),
|
|
declaration->getLocation().printToString(source_manager()),
|
|
(void *)declaration);
|
|
|
|
auto class_model_ptr = create_class_model(declaration->getTemplatedDecl());
|
|
|
|
if (!class_model_ptr)
|
|
return true;
|
|
|
|
tbuilder().build_from_template_declaration(*class_model_ptr, *declaration);
|
|
|
|
const auto class_full_name = class_model_ptr->full_name(false);
|
|
const auto id = common::to_id(class_full_name);
|
|
|
|
// Override the id with the template id, for now we don't care about the
|
|
// underlying templated class id
|
|
class_model_ptr->set_id(id);
|
|
|
|
set_unique_id(declaration->getID(), id);
|
|
|
|
if (!declaration->getTemplatedDecl()->isCompleteDefinition()) {
|
|
forward_declarations_.emplace(id, std::move(class_model_ptr));
|
|
return true;
|
|
}
|
|
forward_declarations_.erase(id);
|
|
|
|
if (diagram().should_include(*class_model_ptr)) {
|
|
LOG_DBG("Adding class template participant {} with id {}",
|
|
class_full_name, id);
|
|
|
|
context().set_caller_id(id);
|
|
context().update(declaration);
|
|
|
|
diagram().add_participant(std::move(class_model_ptr));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::VisitClassTemplateSpecializationDecl(
|
|
clang::ClassTemplateSpecializationDecl *declaration)
|
|
{
|
|
if (!should_include(declaration))
|
|
return true;
|
|
|
|
LOG_TRACE("Visiting template specialization declaration {} at {}",
|
|
declaration->getQualifiedNameAsString(),
|
|
declaration->getLocation().printToString(source_manager()));
|
|
|
|
// TODO: Add support for classes defined in function/method bodies
|
|
if (declaration->isLocalClass() != nullptr)
|
|
return true;
|
|
|
|
auto template_specialization_ptr =
|
|
process_class_template_specialization(declaration);
|
|
|
|
if (!template_specialization_ptr)
|
|
return true;
|
|
|
|
const auto class_full_name = template_specialization_ptr->full_name(false);
|
|
const auto id = common::to_id(class_full_name);
|
|
|
|
template_specialization_ptr->set_id(id);
|
|
|
|
set_unique_id(declaration->getID(), id);
|
|
|
|
if (!declaration->isCompleteDefinition()) {
|
|
forward_declarations_.emplace(
|
|
id, std::move(template_specialization_ptr));
|
|
return true;
|
|
}
|
|
forward_declarations_.erase(id);
|
|
|
|
if (diagram().should_include(*template_specialization_ptr)) {
|
|
LOG_DBG(
|
|
"Adding class template specialization participant {} with id {}",
|
|
class_full_name, id);
|
|
|
|
context().set_caller_id(id);
|
|
context().update(declaration);
|
|
|
|
diagram().add_participant(std::move(template_specialization_ptr));
|
|
}
|
|
|
|
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(
|
|
clang::CXXMethodDecl *declaration)
|
|
{
|
|
if (!should_include(declaration))
|
|
return true;
|
|
|
|
if (!declaration->isThisDeclarationADefinition()) {
|
|
if (auto *declaration_definition = declaration->getDefinition();
|
|
declaration_definition != nullptr) {
|
|
if (auto *method_definition = clang::dyn_cast<clang::CXXMethodDecl>(
|
|
declaration_definition);
|
|
method_definition != nullptr) {
|
|
LOG_DBG("Calling VisitCXXMethodDecl recursively for forward "
|
|
"declaration");
|
|
|
|
return VisitCXXMethodDecl(method_definition);
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG_TRACE("Visiting method {} in class {} [{}]",
|
|
declaration->getQualifiedNameAsString(),
|
|
declaration->getParent()->getQualifiedNameAsString(),
|
|
(void *)declaration->getParent());
|
|
|
|
context().update(declaration);
|
|
|
|
auto method_model_ptr = create_method_model(declaration);
|
|
|
|
if (!method_model_ptr)
|
|
return true;
|
|
|
|
process_comment(*declaration, *method_model_ptr);
|
|
|
|
set_source_location(*declaration, *method_model_ptr);
|
|
|
|
const auto method_full_name = method_model_ptr->full_name(false);
|
|
|
|
method_model_ptr->set_id(common::to_id(method_full_name));
|
|
|
|
// Callee methods in call expressions are referred to by first declaration
|
|
// id, so they should both be mapped to method_model
|
|
if (declaration->isThisDeclarationADefinition()) {
|
|
set_unique_id(
|
|
declaration->getFirstDecl()->getID(), method_model_ptr->id());
|
|
}
|
|
|
|
set_unique_id(declaration->getID(), method_model_ptr->id());
|
|
|
|
LOG_TRACE("Set id {} --> {} for method name {} [{}]", declaration->getID(),
|
|
method_model_ptr->id(), method_full_name,
|
|
declaration->isThisDeclarationADefinition());
|
|
|
|
context().update(declaration);
|
|
|
|
context().set_caller_id(method_model_ptr->id());
|
|
|
|
diagram().add_participant(std::move(method_model_ptr));
|
|
|
|
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(
|
|
clang::FunctionDecl *declaration)
|
|
{
|
|
if (declaration->isCXXClassMember())
|
|
return true;
|
|
|
|
if (!should_include(declaration))
|
|
return true;
|
|
|
|
if (!declaration->isThisDeclarationADefinition()) {
|
|
if (auto *declaration_definition = declaration->getDefinition();
|
|
declaration_definition != nullptr)
|
|
return VisitFunctionDecl(
|
|
static_cast<clang::FunctionDecl *>(declaration_definition));
|
|
}
|
|
|
|
LOG_TRACE("Visiting function declaration {} at {}",
|
|
declaration->getQualifiedNameAsString(),
|
|
declaration->getLocation().printToString(source_manager()));
|
|
|
|
if (declaration->isTemplated()) {
|
|
if (declaration->getDescribedTemplate() != nullptr) {
|
|
// If the described templated of this function is already in the
|
|
// model skip it:
|
|
if (get_unique_id(
|
|
common::id_t{declaration->getDescribedTemplate()->getID()}))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<model::function> function_model_ptr{};
|
|
|
|
if (declaration->isFunctionTemplateSpecialization()) {
|
|
function_model_ptr =
|
|
build_function_template_instantiation(*declaration);
|
|
}
|
|
else {
|
|
function_model_ptr = build_function_model(*declaration);
|
|
}
|
|
|
|
if (!function_model_ptr)
|
|
return true;
|
|
|
|
function_model_ptr->set_id(
|
|
common::to_id(function_model_ptr->full_name(false)));
|
|
|
|
function_model_ptr->is_void(declaration->getReturnType()->isVoidType());
|
|
|
|
function_model_ptr->is_operator(declaration->isOverloadedOperator());
|
|
|
|
function_model_ptr->is_cuda_kernel(
|
|
common::has_attr(declaration, clang::attr::CUDAGlobal));
|
|
|
|
function_model_ptr->is_cuda_device(
|
|
common::has_attr(declaration, clang::attr::CUDADevice));
|
|
|
|
context().update(declaration);
|
|
|
|
context().set_caller_id(function_model_ptr->id());
|
|
|
|
if (declaration->isThisDeclarationADefinition()) {
|
|
set_unique_id(
|
|
declaration->getFirstDecl()->getID(), function_model_ptr->id());
|
|
}
|
|
|
|
set_unique_id(declaration->getID(), function_model_ptr->id());
|
|
|
|
process_comment(*declaration, *function_model_ptr);
|
|
|
|
set_source_location(*declaration, *function_model_ptr);
|
|
|
|
diagram().add_participant(std::move(function_model_ptr));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::VisitFunctionTemplateDecl(
|
|
clang::FunctionTemplateDecl *declaration)
|
|
{
|
|
if (!should_include(declaration))
|
|
return true;
|
|
|
|
const auto function_name = declaration->getQualifiedNameAsString();
|
|
|
|
LOG_TRACE("Visiting function template declaration {} at {}", function_name,
|
|
declaration->getLocation().printToString(source_manager()));
|
|
|
|
auto function_template_model = build_function_template(*declaration);
|
|
|
|
process_comment(*declaration, *function_template_model);
|
|
|
|
set_source_location(*declaration, *function_template_model);
|
|
set_owning_module(*declaration, *function_template_model);
|
|
|
|
function_template_model->is_void(
|
|
declaration->getAsFunction()->getReturnType()->isVoidType());
|
|
|
|
function_template_model->set_id(
|
|
common::to_id(function_template_model->full_name(false)));
|
|
|
|
function_template_model->is_operator(
|
|
declaration->getAsFunction()->isOverloadedOperator());
|
|
|
|
context().update(declaration);
|
|
|
|
context().set_caller_id(function_template_model->id());
|
|
|
|
set_unique_id(declaration->getID(), function_template_model->id());
|
|
|
|
diagram().add_participant(std::move(function_template_model));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::VisitLambdaExpr(clang::LambdaExpr *expr)
|
|
{
|
|
if (!should_include(expr))
|
|
return true;
|
|
|
|
const auto lambda_full_name =
|
|
expr->getLambdaClass()->getCanonicalDecl()->getNameAsString();
|
|
|
|
LOG_TRACE("Visiting lambda expression {} at {} [caller_id = {}]",
|
|
lambda_full_name, expr->getBeginLoc().printToString(source_manager()),
|
|
context().caller_id());
|
|
|
|
LOG_TRACE("Lambda call operator ID {} - lambda class ID {}, class call "
|
|
"operator ID {}",
|
|
expr->getCallOperator()->getID(), expr->getLambdaClass()->getID(),
|
|
expr->getLambdaClass()->getLambdaCallOperator()->getID());
|
|
|
|
// Create lambda class participant
|
|
auto *cls = expr->getLambdaClass();
|
|
auto lambda_class_model_ptr = create_class_model(cls);
|
|
|
|
if (!lambda_class_model_ptr)
|
|
return true;
|
|
|
|
const auto cls_id = lambda_class_model_ptr->id();
|
|
|
|
set_unique_id(cls->getID(), cls_id);
|
|
|
|
auto lambda_method_model_ptr =
|
|
create_lambda_method_model(expr->getCallOperator());
|
|
|
|
lambda_method_model_ptr->set_class_id(cls_id);
|
|
|
|
// If this is a nested lambda, prepend the parent lambda name to this lambda
|
|
auto lambda_class_full_name = lambda_class_model_ptr->full_name(false);
|
|
lambda_method_model_ptr->set_class_full_name(lambda_class_full_name);
|
|
|
|
diagram().add_participant(std::move(lambda_class_model_ptr));
|
|
|
|
lambda_method_model_ptr->set_id(
|
|
common::to_id(get_participant(cls_id).value().full_name(false) +
|
|
"::" + lambda_method_model_ptr->full_name_no_ns()));
|
|
|
|
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()) &&
|
|
(!context().lambda_caller_id().has_value())) {
|
|
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());
|
|
|
|
set_unique_id(
|
|
expr->getCallOperator()->getID(), lambda_method_model_ptr->id());
|
|
|
|
diagram().add_participant(std::move(lambda_method_model_ptr));
|
|
|
|
[[maybe_unused]] const auto is_generic_lambda = expr->isGenericLambda();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseLambdaExpr(clang::LambdaExpr *expr)
|
|
{
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseLambdaExpr(expr);
|
|
|
|
// lambda context is entered inside the visitor
|
|
context().leave_lambda_expression();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseCallExpr(clang::CallExpr *expr)
|
|
{
|
|
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
|
|
return true;
|
|
|
|
LOG_TRACE("Entering call expression at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
context().enter_callexpr(expr);
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCallExpr(expr);
|
|
|
|
LOG_TRACE("Leaving call expression at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
context().leave_callexpr();
|
|
|
|
pop_message_to_diagram(expr);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseCUDAKernelCallExpr(
|
|
clang::CUDAKernelCallExpr *expr)
|
|
{
|
|
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
|
|
return true;
|
|
|
|
LOG_TRACE("Entering CUDA kernel call expression at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
context().enter_callexpr(expr);
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCallExpr(expr);
|
|
|
|
LOG_TRACE("Leaving CUDA kernel call expression at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
context().leave_callexpr();
|
|
|
|
pop_message_to_diagram(expr);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseCXXMemberCallExpr(
|
|
clang::CXXMemberCallExpr *expr)
|
|
{
|
|
if (source_manager().isInSystemHeader(expr->getSourceRange().getBegin()))
|
|
return true;
|
|
|
|
LOG_TRACE("Entering member call expression at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
context().enter_callexpr(expr);
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXMemberCallExpr(
|
|
expr);
|
|
|
|
LOG_TRACE("Leaving member call expression at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
context().leave_callexpr();
|
|
|
|
pop_message_to_diagram(expr);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseCXXOperatorCallExpr(
|
|
clang::CXXOperatorCallExpr *expr)
|
|
{
|
|
context().enter_callexpr(expr);
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXOperatorCallExpr(
|
|
expr);
|
|
|
|
context().leave_callexpr();
|
|
|
|
pop_message_to_diagram(expr);
|
|
|
|
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)
|
|
{
|
|
LOG_TRACE("Entering cxx construct call expression at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
context().enter_callexpr(expr);
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXConstructExpr(
|
|
expr);
|
|
|
|
translation_unit_visitor::VisitCXXConstructExpr(expr);
|
|
|
|
LOG_TRACE("Leaving cxx construct call expression at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
context().leave_callexpr();
|
|
|
|
pop_message_to_diagram(expr);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseCompoundStmt(clang::CompoundStmt *stmt)
|
|
{
|
|
using clanguml::common::model::message_t;
|
|
using clanguml::common::model::namespace_;
|
|
using clanguml::sequence_diagram::model::activity;
|
|
using clanguml::sequence_diagram::model::message;
|
|
|
|
if (stmt == nullptr)
|
|
return true;
|
|
|
|
const auto *current_ifstmt = context().current_ifstmt();
|
|
const auto *current_elseifstmt =
|
|
current_ifstmt != nullptr ? context().current_elseifstmt() : nullptr;
|
|
|
|
//
|
|
// Add final else block (not else if)
|
|
//
|
|
if (current_elseifstmt != nullptr) {
|
|
if (current_elseifstmt->getElse() == stmt) {
|
|
const auto current_caller_id = context().caller_id();
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
model::message m{message_t::kElse, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
diagram().add_message(std::move(m));
|
|
}
|
|
}
|
|
}
|
|
else if (current_ifstmt != nullptr) {
|
|
if (current_ifstmt->getElse() == stmt) {
|
|
const auto current_caller_id = context().caller_id();
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
model::message m{message_t::kElse, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
diagram().add_message(std::move(m));
|
|
}
|
|
}
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCompoundStmt(stmt);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseIfStmt(clang::IfStmt *stmt)
|
|
{
|
|
using clanguml::common::model::message_t;
|
|
using clanguml::common::model::namespace_;
|
|
using clanguml::sequence_diagram::model::activity;
|
|
using clanguml::sequence_diagram::model::message;
|
|
|
|
bool elseif_block{false};
|
|
|
|
const auto current_caller_id = context().caller_id();
|
|
const auto *current_ifstmt = context().current_ifstmt();
|
|
const auto *current_elseifstmt =
|
|
current_ifstmt != nullptr ? context().current_elseifstmt() : nullptr;
|
|
|
|
std::string condition_text;
|
|
if (config().generate_condition_statements())
|
|
condition_text = common::get_condition_text(source_manager(), stmt);
|
|
|
|
// Check if this is a beginning of a new if statement, or an
|
|
// else if condition of the current if statement
|
|
auto child_stmt_compare = [stmt](auto *child_stmt) {
|
|
return child_stmt == stmt;
|
|
};
|
|
|
|
if (current_ifstmt != nullptr)
|
|
elseif_block = elseif_block ||
|
|
std::any_of(current_ifstmt->children().begin(),
|
|
current_ifstmt->children().end(), child_stmt_compare);
|
|
if (current_elseifstmt != nullptr)
|
|
elseif_block = elseif_block ||
|
|
std::any_of(current_elseifstmt->children().begin(),
|
|
current_elseifstmt->children().end(), child_stmt_compare);
|
|
|
|
if ((current_caller_id.value() != 0) && !stmt->isConstexpr()) {
|
|
if (elseif_block) {
|
|
context().enter_elseifstmt(stmt);
|
|
|
|
message m{message_t::kElseIf, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.condition_text(condition_text);
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
else {
|
|
context().enter_ifstmt(stmt);
|
|
|
|
message m{message_t::kIf, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.condition_text(condition_text);
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseIfStmt(stmt);
|
|
|
|
if ((current_caller_id.value() != 0) && !stmt->isConstexpr()) {
|
|
if (!elseif_block) {
|
|
diagram().end_block_message(
|
|
{message_t::kIfEnd, current_caller_id}, message_t::kIf);
|
|
context().leave_ifstmt();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseWhileStmt(clang::WhileStmt *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();
|
|
|
|
std::string condition_text;
|
|
if (config().generate_condition_statements())
|
|
condition_text = common::get_condition_text(source_manager(), stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
context().enter_loopstmt(stmt);
|
|
message m{message_t::kWhile, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.condition_text(condition_text);
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseWhileStmt(stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
diagram().end_block_message(
|
|
{message_t::kWhileEnd, current_caller_id}, message_t::kWhile);
|
|
context().leave_loopstmt();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseDoStmt(clang::DoStmt *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();
|
|
|
|
std::string condition_text;
|
|
if (config().generate_condition_statements())
|
|
condition_text = common::get_condition_text(source_manager(), stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
context().enter_loopstmt(stmt);
|
|
message m{message_t::kDo, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.condition_text(condition_text);
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseDoStmt(stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
diagram().end_block_message(
|
|
{message_t::kDoEnd, current_caller_id}, message_t::kDo);
|
|
context().leave_loopstmt();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseForStmt(clang::ForStmt *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();
|
|
|
|
std::string condition_text;
|
|
if (config().generate_condition_statements())
|
|
condition_text = common::get_condition_text(source_manager(), stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
context().enter_loopstmt(stmt);
|
|
message m{message_t::kFor, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.condition_text(condition_text);
|
|
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseForStmt(stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
diagram().end_block_message(
|
|
{message_t::kForEnd, current_caller_id}, message_t::kFor);
|
|
context().leave_loopstmt();
|
|
}
|
|
|
|
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.value() != 0) {
|
|
context().enter_trystmt(stmt);
|
|
message m{message_t::kTry, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXTryStmt(stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
diagram().end_block_message(
|
|
{message_t::kTryEnd, current_caller_id}, message_t::kTry);
|
|
context().leave_trystmt();
|
|
}
|
|
|
|
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.value() != 0) &&
|
|
(context().current_trystmt() != nullptr)) {
|
|
std::string caught_type;
|
|
if (stmt->getCaughtType().isNull())
|
|
caught_type = "...";
|
|
else
|
|
caught_type = common::to_string(
|
|
stmt->getCaughtType(), *context().get_ast_context());
|
|
|
|
model::message m{message_t::kCatch, current_caller_id};
|
|
m.set_message_name(std::move(caught_type));
|
|
diagram().add_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXCatchStmt(stmt);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseCXXForRangeStmt(
|
|
clang::CXXForRangeStmt *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();
|
|
|
|
std::string condition_text;
|
|
if (config().generate_condition_statements())
|
|
condition_text = common::get_condition_text(source_manager(), stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
context().enter_loopstmt(stmt);
|
|
message m{message_t::kFor, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.condition_text(condition_text);
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCXXForRangeStmt(
|
|
stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
diagram().end_block_message(
|
|
{message_t::kForEnd, current_caller_id}, message_t::kFor);
|
|
context().leave_loopstmt();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseSwitchStmt(clang::SwitchStmt *stmt)
|
|
{
|
|
using clanguml::common::model::message_t;
|
|
|
|
const auto current_caller_id = context().caller_id();
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
context().enter_switchstmt(stmt);
|
|
model::message m{message_t::kSwitch, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseSwitchStmt(stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
context().leave_switchstmt();
|
|
diagram().end_block_message(
|
|
{message_t::kSwitchEnd, current_caller_id}, message_t::kSwitch);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseCaseStmt(clang::CaseStmt *stmt)
|
|
{
|
|
using clanguml::common::model::message_t;
|
|
|
|
const auto current_caller_id = context().caller_id();
|
|
|
|
if ((current_caller_id.value() != 0) &&
|
|
(context().current_switchstmt() != nullptr)) {
|
|
model::message m{message_t::kCase, current_caller_id};
|
|
m.set_message_name(common::to_string(stmt->getLHS()));
|
|
diagram().add_case_stmt_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseCaseStmt(stmt);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseDefaultStmt(clang::DefaultStmt *stmt)
|
|
{
|
|
using clanguml::common::model::message_t;
|
|
|
|
const auto current_caller_id = context().caller_id();
|
|
|
|
if ((current_caller_id.value() != 0) &&
|
|
(context().current_switchstmt() != nullptr)) {
|
|
model::message m{message_t::kCase, current_caller_id};
|
|
m.set_message_name("default");
|
|
diagram().add_case_stmt_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseDefaultStmt(stmt);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::TraverseConditionalOperator(
|
|
clang::ConditionalOperator *stmt)
|
|
{
|
|
using clanguml::common::model::message_t;
|
|
|
|
const auto current_caller_id = context().caller_id();
|
|
|
|
std::string condition_text;
|
|
if (config().generate_condition_statements())
|
|
condition_text = common::get_condition_text(source_manager(), stmt);
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
context().enter_conditionaloperator(stmt);
|
|
model::message m{message_t::kConditional, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
m.condition_text(condition_text);
|
|
m.set_comment(get_expression_comment(source_manager(),
|
|
*context().get_ast_context(), current_caller_id, stmt));
|
|
diagram().add_block_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseStmt(
|
|
stmt->getCond());
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseStmt(
|
|
stmt->getTrueExpr());
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
model::message m{message_t::kConditionalElse, current_caller_id};
|
|
set_source_location(*stmt, m);
|
|
diagram().add_message(std::move(m));
|
|
}
|
|
|
|
RecursiveASTVisitor<translation_unit_visitor>::TraverseStmt(
|
|
stmt->getFalseExpr());
|
|
|
|
if (current_caller_id.value() != 0) {
|
|
context().leave_conditionaloperator();
|
|
diagram().end_block_message(
|
|
{message_t::kConditionalEnd, current_caller_id},
|
|
message_t::kConditional);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::VisitCallExpr(clang::CallExpr *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 (!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);
|
|
|
|
set_source_location(*expr, m);
|
|
|
|
const auto *raw_expr_comment = clanguml::common::get_expression_raw_comment(
|
|
source_manager(), *context().get_ast_context(), expr);
|
|
const auto stripped_comment = process_comment(
|
|
raw_expr_comment, context().get_ast_context()->getDiagnostics(), m);
|
|
|
|
if (m.skip())
|
|
return true;
|
|
|
|
auto generated_message_from_comment = generate_message_from_comment(m);
|
|
|
|
if (!generated_message_from_comment && !should_include(expr)) {
|
|
processed_comments().erase(raw_expr_comment);
|
|
return true;
|
|
}
|
|
|
|
if (context().is_expr_in_current_control_statement_condition(expr)) {
|
|
m.set_message_scope(common::model::message_scope_t::kCondition);
|
|
}
|
|
|
|
if (generated_message_from_comment) {
|
|
LOG_DBG(
|
|
"Message for this call expression is taken from comment directive");
|
|
}
|
|
//
|
|
// Call to a CUDA kernel function
|
|
//
|
|
else if (const auto *cuda_call_expr =
|
|
clang::dyn_cast_or_null<clang::CUDAKernelCallExpr>(expr);
|
|
cuda_call_expr != nullptr) {
|
|
if (!process_cuda_kernel_call_expression(m, cuda_call_expr))
|
|
return true;
|
|
}
|
|
//
|
|
// Call to an overloaded operator
|
|
//
|
|
else if (const auto *operator_call_expr =
|
|
clang::dyn_cast_or_null<clang::CXXOperatorCallExpr>(expr);
|
|
operator_call_expr != nullptr) {
|
|
|
|
if (!process_operator_call_expression(m, operator_call_expr))
|
|
return true;
|
|
}
|
|
//
|
|
// Call to a class method
|
|
//
|
|
else if (const auto *method_call_expr =
|
|
clang::dyn_cast_or_null<clang::CXXMemberCallExpr>(expr);
|
|
method_call_expr != nullptr) {
|
|
|
|
if (!process_class_method_call_expression(m, method_call_expr))
|
|
return true;
|
|
}
|
|
//
|
|
// Call to function or template
|
|
//
|
|
else {
|
|
auto *callee_decl = expr->getCalleeDecl();
|
|
|
|
if (callee_decl == nullptr) {
|
|
LOG_DBG("Cannot get callee declaration - trying direct function "
|
|
"callee...");
|
|
|
|
callee_decl = expr->getDirectCallee();
|
|
|
|
if (callee_decl != nullptr)
|
|
LOG_DBG("Found function/method callee in: {}",
|
|
common::to_string(expr));
|
|
}
|
|
|
|
if (callee_decl == nullptr) {
|
|
//
|
|
// Call to a method of a class template
|
|
//
|
|
if (clang::dyn_cast_or_null<clang::CXXDependentScopeMemberExpr>(
|
|
expr->getCallee()) != nullptr) {
|
|
if (!process_class_template_method_call_expression(m, expr)) {
|
|
return true;
|
|
}
|
|
}
|
|
//
|
|
// Unresolved lookup expression are sometimes calls to template
|
|
// functions
|
|
//
|
|
else if (clang::dyn_cast_or_null<clang::UnresolvedLookupExpr>(
|
|
expr->getCallee()) != nullptr) {
|
|
if (!process_unresolved_lookup_call_expression(m, expr))
|
|
return true;
|
|
}
|
|
else if (clang::dyn_cast_or_null<clang::LambdaExpr>(
|
|
expr->getCallee()) != nullptr) {
|
|
LOG_DBG("Processing lambda expression callee");
|
|
if (!process_lambda_call_expression(m, expr))
|
|
return true;
|
|
}
|
|
else {
|
|
LOG_DBG("Found unsupported callee decl type for: {} at {}",
|
|
common::to_string(expr),
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
}
|
|
}
|
|
else {
|
|
auto success = process_function_call_expression(m, expr);
|
|
|
|
if (!success) {
|
|
LOG_DBG("Skipping call to call expression at: {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add message to diagram
|
|
if (m.from().value() > 0 && m.to().value() > 0) {
|
|
m.set_comment(stripped_comment);
|
|
|
|
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 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::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)
|
|
{
|
|
if (decl->isStaticLocal())
|
|
within_static_variable_declaration_++;
|
|
|
|
RecursiveASTVisitor::TraverseVarDecl(decl);
|
|
|
|
if (decl->isStaticLocal())
|
|
within_static_variable_declaration_--;
|
|
|
|
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 (expr == nullptr)
|
|
return true;
|
|
|
|
if (const auto *ctor = expr->getConstructor();
|
|
ctor != nullptr && !should_include(ctor))
|
|
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()};
|
|
|
|
m.in_static_declaration_context(within_static_variable_declaration_ > 0);
|
|
|
|
set_source_location(*expr, m);
|
|
|
|
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().value() > 0 && m.to().value() > 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_cuda_kernel_call_expression(
|
|
model::message &m, const clang::CUDAKernelCallExpr *expr)
|
|
{
|
|
const auto *callee_decl = expr->getCalleeDecl();
|
|
|
|
if (callee_decl == nullptr)
|
|
return false;
|
|
|
|
const auto *callee_function = callee_decl->getAsFunction();
|
|
|
|
if (callee_function == nullptr)
|
|
return false;
|
|
|
|
if (!should_include(callee_function))
|
|
return false;
|
|
|
|
// Skip free functions declared in files outside of included paths
|
|
if (config().combine_free_functions_into_file_participants() &&
|
|
!diagram().should_include(common::model::source_file{m.file()}))
|
|
return false;
|
|
|
|
auto callee_name = callee_function->getQualifiedNameAsString() + "()";
|
|
|
|
const auto maybe_id = get_unique_id(common::id_t{callee_function->getID()});
|
|
if (!maybe_id.has_value()) {
|
|
// This is hopefully not an interesting call...
|
|
m.set_to(common::id_t{callee_function->getID()});
|
|
}
|
|
else {
|
|
m.set_to(maybe_id.value());
|
|
}
|
|
|
|
m.set_message_name(callee_name.substr(0, callee_name.size() - 2));
|
|
|
|
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;
|
|
|
|
LOG_DBG("Operator '{}' call expression to {} at {}",
|
|
getOperatorSpelling(operator_call_expr->getOperator()),
|
|
operator_call_expr->getCalleeDecl()->getID(),
|
|
operator_call_expr->getBeginLoc().printToString(source_manager()));
|
|
|
|
// Handle the case if the callee is a lambda
|
|
if (const auto *lambda_method = clang::dyn_cast<clang::CXXMethodDecl>(
|
|
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(common::id_t{lambda_method->getParent()->getID()});
|
|
}
|
|
else {
|
|
auto maybe_id = get_unique_id(
|
|
common::id_t{operator_call_expr->getCalleeDecl()->getID()});
|
|
if (maybe_id.has_value()) {
|
|
m.set_to(maybe_id.value());
|
|
}
|
|
else {
|
|
m.set_to(
|
|
common::id_t{operator_call_expr->getCalleeDecl()->getID()});
|
|
}
|
|
}
|
|
|
|
m.set_message_name(fmt::format(
|
|
"operator{}", getOperatorSpelling(operator_call_expr->getOperator())));
|
|
|
|
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(common::id_t{constructor->getID()});
|
|
if (maybe_id.has_value()) {
|
|
m.set_to(maybe_id.value());
|
|
}
|
|
else {
|
|
m.set_to(common::id_t{constructor->getID()});
|
|
}
|
|
|
|
m.set_message_name(
|
|
fmt::format("{}::{}", constructor_parent->getQualifiedNameAsString(),
|
|
constructor_parent->getNameAsString()));
|
|
|
|
diagram().add_active_participant(common::id_t{constructor->getID()});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::process_class_method_call_expression(
|
|
model::message &m, const clang::CXXMemberCallExpr *method_call_expr)
|
|
{
|
|
// Get callee declaration as methods parent
|
|
const auto *method_decl = method_call_expr->getMethodDecl();
|
|
|
|
if (method_decl == nullptr)
|
|
return false;
|
|
|
|
std::string method_name = method_decl->getQualifiedNameAsString();
|
|
|
|
const auto *callee_decl =
|
|
method_decl != nullptr ? method_decl->getParent() : nullptr;
|
|
|
|
if (callee_decl == nullptr)
|
|
return false;
|
|
|
|
if (!should_include(callee_decl) || !should_include(method_decl))
|
|
return false;
|
|
|
|
m.set_to(common::id_t{method_decl->getID()});
|
|
m.set_message_name(method_decl->getNameAsString());
|
|
m.set_return_type(
|
|
method_call_expr->getCallReturnType(*context().get_ast_context())
|
|
.getAsString());
|
|
|
|
LOG_TRACE("Set callee method id {} for method name {}", m.to(),
|
|
method_decl->getQualifiedNameAsString());
|
|
|
|
diagram().add_active_participant(common::id_t{method_decl->getID()});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::process_class_template_method_call_expression(
|
|
model::message &m, const clang::CallExpr *expr)
|
|
{
|
|
const auto *dependent_member_callee =
|
|
clang::dyn_cast_or_null<clang::CXXDependentScopeMemberExpr>(
|
|
expr->getCallee());
|
|
|
|
if (dependent_member_callee == nullptr)
|
|
return false;
|
|
|
|
if (is_callee_valid_template_specialization(dependent_member_callee)) {
|
|
if (const auto *tst = dependent_member_callee->getBaseType()
|
|
->getAs<clang::TemplateSpecializationType>();
|
|
tst != nullptr) {
|
|
const auto *template_declaration =
|
|
tst->getTemplateName().getAsTemplateDecl();
|
|
|
|
std::string callee_method_full_name;
|
|
|
|
// First check if the primary template is already in the
|
|
// participants map
|
|
if (get_participant(template_declaration).has_value()) {
|
|
callee_method_full_name = get_participant(template_declaration)
|
|
.value()
|
|
.full_name(false) +
|
|
"::" + dependent_member_callee->getMember().getAsString();
|
|
|
|
for (const auto &[id, p] : diagram().participants()) {
|
|
const auto p_full_name = p->full_name(false);
|
|
|
|
if (p_full_name.find(callee_method_full_name + "(") == 0) {
|
|
// TODO: This selects the first matching template method
|
|
// without considering arguments!!!
|
|
m.set_to(id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Otherwise check if it is a smart pointer
|
|
else if (is_smart_pointer(template_declaration)) {
|
|
const auto *argument_template =
|
|
template_declaration->getTemplateParameters()
|
|
->asArray()
|
|
.front();
|
|
|
|
if (get_participant(argument_template).has_value()) {
|
|
callee_method_full_name = get_participant(argument_template)
|
|
.value()
|
|
.full_name(false) +
|
|
"::" +
|
|
dependent_member_callee->getMember().getAsString();
|
|
|
|
for (const auto &[id, p] : diagram().participants()) {
|
|
const auto p_full_name = p->full_name(false);
|
|
if (p_full_name.find(callee_method_full_name + "(") ==
|
|
0) {
|
|
// TODO: This selects the first matching template
|
|
// method
|
|
// without considering arguments!!!
|
|
m.set_to(id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
m.set_message_name(
|
|
dependent_member_callee->getMember().getAsString());
|
|
|
|
if (const auto maybe_id =
|
|
get_unique_id(common::id_t{template_declaration->getID()});
|
|
maybe_id.has_value())
|
|
diagram().add_active_participant(maybe_id.value());
|
|
}
|
|
}
|
|
else {
|
|
LOG_DBG("Skipping call due to unresolvable "
|
|
"CXXDependentScopeMemberExpr at {}",
|
|
expr->getBeginLoc().printToString(source_manager()));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::process_function_call_expression(
|
|
model::message &m, const clang::CallExpr *expr)
|
|
{
|
|
const auto *callee_decl = expr->getCalleeDecl();
|
|
|
|
if (callee_decl == nullptr)
|
|
return false;
|
|
|
|
const auto *callee_function = callee_decl->getAsFunction();
|
|
|
|
if (callee_function == nullptr)
|
|
return false;
|
|
|
|
if (!should_include(callee_function))
|
|
return false;
|
|
|
|
// Skip free functions declared in files outside of included paths
|
|
if (config().combine_free_functions_into_file_participants() &&
|
|
!diagram().should_include(common::model::source_file{m.file()}))
|
|
return false;
|
|
|
|
auto callee_name = callee_function->getQualifiedNameAsString() + "()";
|
|
|
|
const auto maybe_id = get_unique_id(common::id_t{callee_function->getID()});
|
|
if (!maybe_id.has_value()) {
|
|
// This is hopefully not an interesting call...
|
|
m.set_to(common::id_t{callee_function->getID()});
|
|
}
|
|
else {
|
|
m.set_to(maybe_id.value());
|
|
}
|
|
|
|
m.set_message_name(callee_name.substr(0, callee_name.size() - 2));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::process_lambda_call_expression(
|
|
model::message &m, const clang::CallExpr *expr) const
|
|
{
|
|
const auto *lambda_expr =
|
|
clang::dyn_cast_or_null<clang::LambdaExpr>(expr->getCallee());
|
|
|
|
if (lambda_expr == nullptr)
|
|
return true;
|
|
|
|
const auto lambda_class_id =
|
|
common::id_t{lambda_expr->getLambdaClass()->getID()};
|
|
const auto maybe_id = get_unique_id(lambda_class_id);
|
|
if (!maybe_id.has_value())
|
|
m.set_to(lambda_class_id);
|
|
else {
|
|
m.set_to(maybe_id.value());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::process_unresolved_lookup_call_expression(
|
|
model::message &m, const clang::CallExpr *expr) const
|
|
{
|
|
// This is probably a template
|
|
const auto *unresolved_expr =
|
|
clang::dyn_cast_or_null<clang::UnresolvedLookupExpr>(expr->getCallee());
|
|
|
|
if (unresolved_expr != nullptr) {
|
|
for (const auto *decl : unresolved_expr->decls()) {
|
|
if (clang::dyn_cast_or_null<clang::FunctionTemplateDecl>(decl) !=
|
|
nullptr) {
|
|
const auto *ftd =
|
|
clang::dyn_cast_or_null<clang::FunctionTemplateDecl>(decl);
|
|
|
|
const auto maybe_id = get_unique_id(common::id_t{ftd->getID()});
|
|
if (!maybe_id.has_value())
|
|
m.set_to(common::id_t{ftd->getID()});
|
|
else {
|
|
m.set_to(maybe_id.value());
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (clang::dyn_cast_or_null<clang::FunctionDecl>(decl) != nullptr) {
|
|
const auto *fd =
|
|
clang::dyn_cast_or_null<clang::FunctionDecl>(decl);
|
|
|
|
const auto maybe_id = get_unique_id(common::id_t{fd->getID()});
|
|
if (!maybe_id.has_value())
|
|
m.set_to(common::id_t{fd->getID()});
|
|
else {
|
|
m.set_to(maybe_id.value());
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
LOG_DBG("Unknown unresolved lookup expression");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::is_callee_valid_template_specialization(
|
|
const clang::CXXDependentScopeMemberExpr *dependent_member_expr) const
|
|
{
|
|
if (dependent_member_expr == nullptr)
|
|
return false;
|
|
|
|
if (dependent_member_expr->getBaseType().isNull())
|
|
return false;
|
|
|
|
const auto *tst = dependent_member_expr->getBaseType()
|
|
->getAs<clang::TemplateSpecializationType>();
|
|
|
|
if (tst == nullptr)
|
|
return false;
|
|
|
|
return !(tst->isPointerType());
|
|
}
|
|
|
|
bool translation_unit_visitor::is_smart_pointer(
|
|
const clang::TemplateDecl *primary_template) const
|
|
{
|
|
return primary_template->getQualifiedNameAsString().find(
|
|
"std::unique_ptr") == 0 ||
|
|
primary_template->getQualifiedNameAsString().find("std::shared_ptr") ==
|
|
0 ||
|
|
primary_template->getQualifiedNameAsString().find("std::weak_ptr") == 0;
|
|
}
|
|
|
|
std::unique_ptr<clanguml::sequence_diagram::model::class_>
|
|
translation_unit_visitor::create_class_model(clang::CXXRecordDecl *cls)
|
|
{
|
|
assert(cls != nullptr);
|
|
|
|
auto c_ptr{std::make_unique<clanguml::sequence_diagram::model::class_>(
|
|
config().using_namespace())};
|
|
auto &c = *c_ptr;
|
|
|
|
auto qualified_name = cls->getQualifiedNameAsString();
|
|
|
|
if (!cls->isLambda())
|
|
if (!should_include(cls))
|
|
return {};
|
|
|
|
auto ns = common::get_tag_namespace(*cls);
|
|
|
|
if (cls->isLambda() && !diagram().should_include(ns | "lambda"))
|
|
return {};
|
|
|
|
const auto *parent = cls->getParent();
|
|
|
|
if ((parent != nullptr) && parent->isRecord()) {
|
|
// Here we have 3 options, either:
|
|
// - the parent is a regular C++ class/struct
|
|
// - the parent is a class template declaration/specialization
|
|
// - the parent is a lambda (i.e. this is a nested lambda expression)
|
|
std::optional<common::id_t> id_opt;
|
|
const auto *parent_record_decl =
|
|
clang::dyn_cast<clang::RecordDecl>(parent);
|
|
|
|
assert(parent_record_decl != nullptr);
|
|
|
|
const common::id_t ast_id{parent_record_decl->getID()};
|
|
|
|
// First check if the parent has been added to the diagram as
|
|
// regular class
|
|
id_opt = get_unique_id(ast_id);
|
|
|
|
// If not, check if the parent template declaration is in the model
|
|
if (!id_opt &&
|
|
(parent_record_decl->getDescribedTemplate() != nullptr)) {
|
|
parent_record_decl->getDescribedTemplate()->getID();
|
|
if (parent_record_decl->getDescribedTemplate() != nullptr)
|
|
id_opt = get_unique_id(ast_id);
|
|
}
|
|
|
|
if (!id_opt)
|
|
return {};
|
|
|
|
auto parent_class =
|
|
diagram()
|
|
.get_participant<clanguml::sequence_diagram::model::class_>(
|
|
*id_opt);
|
|
|
|
assert(parent_class);
|
|
|
|
c.set_namespace(ns);
|
|
if (cls->getNameAsString().empty()) {
|
|
// Nested structs can be anonymous
|
|
if (anonymous_struct_relationships_.count(cls->getID()) > 0) {
|
|
const auto &[label, hint, access] =
|
|
anonymous_struct_relationships_[cls->getID()];
|
|
|
|
c.set_name(parent_class.value().name() + "##" +
|
|
fmt::format("({})", label));
|
|
|
|
parent_class.value().add_relationship(
|
|
{hint, common::to_id(c.full_name(false)), access, label});
|
|
}
|
|
else
|
|
c.set_name(parent_class.value().name() + "##" +
|
|
fmt::format(
|
|
"(anonymous_{})", std::to_string(cls->getID())));
|
|
}
|
|
else {
|
|
c.set_name(
|
|
parent_class.value().name() + "##" + cls->getNameAsString());
|
|
}
|
|
|
|
c.set_id(common::to_id(c.full_name(false)));
|
|
|
|
c.nested(true);
|
|
}
|
|
else if (cls->isLambda()) {
|
|
c.is_lambda(true);
|
|
if (cls->getParent() != nullptr) {
|
|
const auto type_name = make_lambda_name(cls);
|
|
|
|
c.set_name(type_name);
|
|
c.set_namespace(ns);
|
|
c.set_id(common::to_id(c.full_name(false)));
|
|
}
|
|
else {
|
|
LOG_WARN("Cannot find parent declaration for lambda {}",
|
|
cls->getQualifiedNameAsString());
|
|
return {};
|
|
}
|
|
}
|
|
else {
|
|
c.set_name(common::get_tag_name(*cls));
|
|
c.set_namespace(ns);
|
|
c.set_id(common::to_id(c.full_name(false)));
|
|
}
|
|
|
|
c.is_struct(cls->isStruct());
|
|
|
|
process_comment(*cls, c);
|
|
set_source_location(*cls, c);
|
|
|
|
if (c.skip())
|
|
return {};
|
|
|
|
c.set_style(c.style_spec());
|
|
|
|
return c_ptr;
|
|
}
|
|
|
|
void translation_unit_visitor::set_unique_id(
|
|
int64_t local_id, common::id_t global_id)
|
|
{
|
|
LOG_TRACE("Setting local element mapping {} --> {}", local_id, global_id);
|
|
|
|
local_ast_id_map_[local_id] = global_id;
|
|
}
|
|
|
|
std::optional<common::id_t> translation_unit_visitor::get_unique_id(
|
|
common::id_t local_id) const
|
|
{
|
|
if (local_ast_id_map_.find(local_id.ast_local_value()) ==
|
|
local_ast_id_map_.end())
|
|
return {};
|
|
|
|
return local_ast_id_map_.at(local_id.ast_local_value());
|
|
}
|
|
|
|
std::unique_ptr<model::function_template>
|
|
translation_unit_visitor::build_function_template(
|
|
const clang::FunctionTemplateDecl &declaration)
|
|
{
|
|
auto function_template_model_ptr =
|
|
std::make_unique<sequence_diagram::model::function_template>(
|
|
config().using_namespace());
|
|
|
|
set_qualified_name(declaration, *function_template_model_ptr);
|
|
|
|
tbuilder().build_from_template_declaration(
|
|
*function_template_model_ptr, declaration);
|
|
|
|
function_template_model_ptr->return_type(
|
|
common::to_string(declaration.getAsFunction()->getReturnType(),
|
|
declaration.getASTContext()));
|
|
|
|
for (const auto *param : declaration.getTemplatedDecl()->parameters()) {
|
|
function_template_model_ptr->add_parameter(
|
|
simplify_system_template(common::to_string(
|
|
param->getType(), declaration.getASTContext(), false)));
|
|
}
|
|
|
|
return function_template_model_ptr;
|
|
}
|
|
|
|
std::unique_ptr<model::function_template>
|
|
translation_unit_visitor::build_function_template_instantiation(
|
|
const clang::FunctionDecl &decl)
|
|
{
|
|
auto template_instantiation_ptr =
|
|
std::make_unique<model::function_template>(config().using_namespace());
|
|
auto &template_instantiation = *template_instantiation_ptr;
|
|
|
|
set_qualified_name(decl, template_instantiation);
|
|
|
|
tbuilder().build(template_instantiation, &decl, decl.getPrimaryTemplate(),
|
|
decl.getTemplateSpecializationArgs()->asArray(),
|
|
common::to_string(&decl));
|
|
|
|
// Handle function parameters
|
|
for (const auto *param : decl.parameters()) {
|
|
template_instantiation_ptr->add_parameter(
|
|
common::to_string(param->getType(), decl.getASTContext()));
|
|
}
|
|
|
|
return template_instantiation_ptr;
|
|
}
|
|
|
|
std::unique_ptr<model::function> translation_unit_visitor::build_function_model(
|
|
const clang::FunctionDecl &declaration)
|
|
{
|
|
auto function_model_ptr =
|
|
std::make_unique<sequence_diagram::model::function>(
|
|
config().using_namespace());
|
|
|
|
common::model::namespace_ ns{declaration.getQualifiedNameAsString()};
|
|
function_model_ptr->set_name(ns.name());
|
|
ns.pop_back();
|
|
function_model_ptr->set_namespace(ns);
|
|
|
|
function_model_ptr->return_type(common::to_string(
|
|
declaration.getReturnType(), declaration.getASTContext()));
|
|
|
|
for (const auto *param : declaration.parameters()) {
|
|
function_model_ptr->add_parameter(
|
|
simplify_system_template(common::to_string(
|
|
param->getType(), declaration.getASTContext(), false)));
|
|
}
|
|
|
|
return function_model_ptr;
|
|
}
|
|
|
|
std::unique_ptr<model::class_>
|
|
translation_unit_visitor::process_class_template_specialization(
|
|
clang::ClassTemplateSpecializationDecl *cls)
|
|
{
|
|
auto c_ptr{std::make_unique<model::class_>(config().using_namespace())};
|
|
|
|
tbuilder().build_from_class_template_specialization(*c_ptr, *cls);
|
|
|
|
auto &template_instantiation = *c_ptr;
|
|
|
|
// TODO: refactor to method get_qualified_name()
|
|
auto qualified_name = cls->getQualifiedNameAsString();
|
|
util::replace_all(qualified_name, "(anonymous namespace)", "");
|
|
util::replace_all(qualified_name, "::::", "::");
|
|
|
|
common::model::namespace_ ns{qualified_name};
|
|
ns.pop_back();
|
|
template_instantiation.set_name(cls->getNameAsString());
|
|
template_instantiation.set_namespace(ns);
|
|
|
|
template_instantiation.is_struct(cls->isStruct());
|
|
|
|
process_comment(*cls, template_instantiation);
|
|
set_source_location(*cls, template_instantiation);
|
|
set_owning_module(*cls, template_instantiation);
|
|
|
|
if (template_instantiation.skip())
|
|
return {};
|
|
|
|
template_instantiation.set_id(
|
|
common::to_id(template_instantiation.full_name(false)));
|
|
|
|
set_unique_id(cls->getID(), template_instantiation.id());
|
|
|
|
return c_ptr;
|
|
}
|
|
|
|
std::string translation_unit_visitor::simplify_system_template(
|
|
const std::string &full_name) const
|
|
{
|
|
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(
|
|
const clang::CXXRecordDecl *cls) const
|
|
{
|
|
std::string result;
|
|
const auto location = cls->getLocation();
|
|
const std::string source_location{lambda_source_location(location)};
|
|
|
|
if (context().lambda_caller_id().has_value()) {
|
|
// Parent is also a lambda (this id points to a lambda operator())
|
|
std::string parent_lambda_class_name{"()"};
|
|
if (diagram().get_participant<model::method>(
|
|
context().lambda_caller_id().value())) { // NOLINT
|
|
auto parent_lambda_class_id =
|
|
diagram()
|
|
.get_participant<model::method>(
|
|
context().lambda_caller_id().value()) // NOLINT
|
|
.value()
|
|
.class_id();
|
|
|
|
if (diagram().get_participant<model::class_>(
|
|
parent_lambda_class_id)) {
|
|
parent_lambda_class_name =
|
|
diagram()
|
|
.get_participant<model::class_>(parent_lambda_class_id)
|
|
.value()
|
|
.full_name(false);
|
|
}
|
|
}
|
|
|
|
result = fmt::format(
|
|
"{}##(lambda {})", parent_lambda_class_name, source_location);
|
|
}
|
|
else if (context().caller_id().value() != 0 &&
|
|
get_participant(context().caller_id()).has_value()) {
|
|
auto parent_full_name =
|
|
get_participant(context().caller_id()).value().full_name_no_ns();
|
|
|
|
result =
|
|
fmt::format("{}##(lambda {})", parent_full_name, source_location);
|
|
}
|
|
else {
|
|
result = fmt::format("(lambda {})", source_location);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void translation_unit_visitor::push_message(
|
|
clang::CallExpr *expr, model::message &&m)
|
|
{
|
|
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);
|
|
|
|
// Skip if no message was generated from this expr
|
|
if (call_expr_message_map_.find(expr) == call_expr_message_map_.end()) {
|
|
return;
|
|
}
|
|
|
|
auto msg = std::move(call_expr_message_map_.at(expr));
|
|
|
|
auto caller_id = msg.from();
|
|
|
|
if (caller_id == 0)
|
|
return;
|
|
|
|
if (diagram().has_activity(caller_id))
|
|
diagram().get_activity(caller_id).add_message(std::move(msg));
|
|
|
|
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()
|
|
{
|
|
resolve_ids_to_global();
|
|
|
|
// 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...
|
|
ensure_lambda_messages_have_operator_as_target();
|
|
|
|
if (config().inline_lambda_messages())
|
|
diagram().inline_lambda_operator_calls();
|
|
}
|
|
|
|
void translation_unit_visitor::ensure_lambda_messages_have_operator_as_target()
|
|
{
|
|
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().value() != 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()");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void translation_unit_visitor::resolve_ids_to_global()
|
|
{
|
|
std::set<common::id_t> active_participants_unique;
|
|
|
|
// Change all active participants AST local ids to diagram global ids
|
|
for (auto id : diagram().active_participants()) {
|
|
if (!id.is_global() &&
|
|
local_ast_id_map_.find(id.ast_local_value()) !=
|
|
local_ast_id_map_.end()) {
|
|
active_participants_unique.emplace(
|
|
local_ast_id_map_.at(id.ast_local_value()));
|
|
}
|
|
else if (id.is_global()) {
|
|
active_participants_unique.emplace(id);
|
|
}
|
|
}
|
|
|
|
diagram().active_participants() = std::move(active_participants_unique);
|
|
|
|
// Change all message callees AST local ids to diagram global ids
|
|
for (auto &[id, activity] : diagram().sequences()) {
|
|
for (auto &m : activity.messages()) {
|
|
if (!m.to().is_global() &&
|
|
local_ast_id_map_.find(m.to().ast_local_value()) !=
|
|
local_ast_id_map_.end()) {
|
|
m.set_to(local_ast_id_map_.at(m.to().ast_local_value()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<clanguml::sequence_diagram::model::method>
|
|
translation_unit_visitor::create_lambda_method_model(
|
|
clang::CXXMethodDecl *declaration)
|
|
{
|
|
auto method_model_ptr = std::make_unique<sequence_diagram::model::method>(
|
|
config().using_namespace());
|
|
|
|
common::model::namespace_ ns{declaration->getQualifiedNameAsString()};
|
|
auto method_name = ns.name();
|
|
method_model_ptr->set_method_name(method_name);
|
|
ns.pop_back();
|
|
method_model_ptr->set_name(ns.name());
|
|
ns.pop_back();
|
|
|
|
method_model_ptr->is_defaulted(declaration->isDefaulted());
|
|
method_model_ptr->is_assignment(declaration->isCopyAssignmentOperator() ||
|
|
declaration->isMoveAssignmentOperator());
|
|
method_model_ptr->is_const(declaration->isConst());
|
|
method_model_ptr->is_static(declaration->isStatic());
|
|
method_model_ptr->is_operator(declaration->isOverloadedOperator());
|
|
method_model_ptr->is_constructor(
|
|
clang::dyn_cast<clang::CXXConstructorDecl>(declaration) != nullptr);
|
|
|
|
method_model_ptr->is_void(declaration->getReturnType()->isVoidType());
|
|
|
|
method_model_ptr->return_type(common::to_string(
|
|
declaration->getReturnType(), declaration->getASTContext()));
|
|
|
|
for (const auto *param : declaration->parameters()) {
|
|
auto parameter_type =
|
|
common::to_string(param->getType(), param->getASTContext());
|
|
common::ensure_lambda_type_is_relative(config(), parameter_type);
|
|
parameter_type = simplify_system_template(parameter_type);
|
|
method_model_ptr->add_parameter(config().using_namespace().relative(
|
|
simplify_system_template(parameter_type)));
|
|
}
|
|
|
|
return method_model_ptr;
|
|
}
|
|
|
|
std::unique_ptr<clanguml::sequence_diagram::model::method>
|
|
translation_unit_visitor::create_method_model(clang::CXXMethodDecl *declaration)
|
|
{
|
|
auto method_model_ptr = std::make_unique<sequence_diagram::model::method>(
|
|
config().using_namespace());
|
|
|
|
common::model::namespace_ ns{declaration->getQualifiedNameAsString()};
|
|
auto method_name = ns.name();
|
|
method_model_ptr->set_method_name(method_name);
|
|
ns.pop_back();
|
|
method_model_ptr->set_name(ns.name());
|
|
ns.pop_back();
|
|
|
|
method_model_ptr->is_defaulted(declaration->isDefaulted());
|
|
method_model_ptr->is_assignment(declaration->isCopyAssignmentOperator() ||
|
|
declaration->isMoveAssignmentOperator());
|
|
method_model_ptr->is_const(declaration->isConst());
|
|
method_model_ptr->is_static(declaration->isStatic());
|
|
method_model_ptr->is_operator(declaration->isOverloadedOperator());
|
|
method_model_ptr->is_constructor(
|
|
clang::dyn_cast<clang::CXXConstructorDecl>(declaration) != nullptr);
|
|
|
|
clang::Decl *parent_decl = declaration->getParent();
|
|
|
|
if (context().current_class_template_decl_ != nullptr)
|
|
parent_decl = context().current_class_template_decl_;
|
|
|
|
LOG_DBG("Getting method's class with local id {}", parent_decl->getID());
|
|
|
|
const auto maybe_method_class = get_participant<model::class_>(parent_decl);
|
|
|
|
if (!maybe_method_class) {
|
|
LOG_DBG("Cannot find parent class_ for method {} in class {}",
|
|
declaration->getQualifiedNameAsString(),
|
|
declaration->getParent()->getQualifiedNameAsString());
|
|
return {};
|
|
}
|
|
|
|
const auto &method_class = maybe_method_class.value();
|
|
|
|
method_model_ptr->is_void(declaration->getReturnType()->isVoidType());
|
|
|
|
method_model_ptr->set_class_id(method_class.id());
|
|
method_model_ptr->set_class_full_name(method_class.full_name(false));
|
|
method_model_ptr->set_name(get_participant(method_model_ptr->class_id())
|
|
.value()
|
|
.full_name_no_ns() +
|
|
"::" + declaration->getNameAsString());
|
|
|
|
method_model_ptr->return_type(common::to_string(
|
|
declaration->getReturnType(), declaration->getASTContext()));
|
|
|
|
for (const auto *param : declaration->parameters()) {
|
|
auto parameter_type =
|
|
common::to_string(param->getType(), param->getASTContext());
|
|
common::ensure_lambda_type_is_relative(config(), parameter_type);
|
|
parameter_type = simplify_system_template(parameter_type);
|
|
method_model_ptr->add_parameter(config().using_namespace().relative(
|
|
simplify_system_template(parameter_type)));
|
|
}
|
|
|
|
return method_model_ptr;
|
|
}
|
|
|
|
bool translation_unit_visitor::should_include(const clang::TagDecl *decl) const
|
|
{
|
|
if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
|
|
return false;
|
|
|
|
const auto decl_file = decl->getLocation().printToString(source_manager());
|
|
|
|
return diagram().should_include(
|
|
namespace_{decl->getQualifiedNameAsString()}) &&
|
|
diagram().should_include(common::model::source_file{decl_file});
|
|
}
|
|
|
|
bool translation_unit_visitor::should_include(
|
|
const clang::LambdaExpr *expr) const
|
|
{
|
|
const auto expr_file = expr->getBeginLoc().printToString(source_manager());
|
|
return diagram().should_include(common::model::source_file{expr_file});
|
|
}
|
|
|
|
bool translation_unit_visitor::should_include(const clang::CallExpr *expr) const
|
|
{
|
|
if (context().caller_id() == 0)
|
|
return false;
|
|
|
|
// Skip casts, moves and such
|
|
if (expr->isCallToStdMove())
|
|
return false;
|
|
|
|
if (expr->isImplicitCXXThis())
|
|
return false;
|
|
|
|
if (clang::dyn_cast_or_null<clang::ImplicitCastExpr>(expr) != nullptr)
|
|
return false;
|
|
|
|
if (!context().valid())
|
|
return false;
|
|
|
|
const auto expr_file = expr->getBeginLoc().printToString(source_manager());
|
|
|
|
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(
|
|
const clang::CXXMethodDecl *decl) const
|
|
{
|
|
if (!should_include(decl->getParent()))
|
|
return false;
|
|
|
|
if (!diagram().should_include(
|
|
common::access_specifier_to_access_t(decl->getAccess())))
|
|
return false;
|
|
|
|
LOG_DBG("Including method {}", decl->getQualifiedNameAsString());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool translation_unit_visitor::should_include(
|
|
const clang::FunctionDecl *decl) const
|
|
{
|
|
const auto decl_file = decl->getLocation().printToString(source_manager());
|
|
|
|
return diagram().should_include(
|
|
namespace_{decl->getQualifiedNameAsString()}) &&
|
|
diagram().should_include(common::model::source_file{decl_file});
|
|
}
|
|
|
|
bool translation_unit_visitor::should_include(
|
|
const clang::FunctionTemplateDecl *decl) const
|
|
{
|
|
return should_include(decl->getAsFunction());
|
|
}
|
|
|
|
bool translation_unit_visitor::should_include(
|
|
const clang::ClassTemplateDecl *decl) const
|
|
{
|
|
if (source_manager().isInSystemHeader(decl->getSourceRange().getBegin()))
|
|
return false;
|
|
|
|
const auto decl_file = decl->getLocation().printToString(source_manager());
|
|
|
|
return diagram().should_include(
|
|
namespace_{decl->getQualifiedNameAsString()}) &&
|
|
diagram().should_include(common::model::source_file{decl_file});
|
|
}
|
|
|
|
std::optional<std::string> translation_unit_visitor::get_expression_comment(
|
|
const clang::SourceManager &sm, const clang::ASTContext &context,
|
|
const common::id_t caller_id, const clang::Stmt *stmt)
|
|
{
|
|
const auto *raw_comment =
|
|
clanguml::common::get_expression_raw_comment(sm, context, stmt);
|
|
|
|
if (raw_comment == nullptr)
|
|
return {};
|
|
|
|
if (!caller_id.is_global() &&
|
|
!processed_comments_by_caller_id_
|
|
.emplace(caller_id.ast_local_value(), raw_comment)
|
|
.second) {
|
|
return {};
|
|
}
|
|
|
|
const auto &[decorators, stripped_comment] = decorators::parse(
|
|
raw_comment->getFormattedText(sm, sm.getDiagnostics()));
|
|
|
|
if (stripped_comment.empty())
|
|
return {};
|
|
|
|
return stripped_comment;
|
|
}
|
|
} // namespace clanguml::sequence_diagram::visitor
|