diff --git a/src/config/config.cc b/src/config/config.cc index 6fb1dfcc..f4d99ca1 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -151,21 +151,35 @@ template <> struct convert { } }; -template <> struct convert> { - static bool decode( - const Node &node, std::vector &rhs) +template <> struct convert> { + static bool decode(const Node &node, std::vector &rhs) { for (auto it = node.begin(); it != node.end(); ++it) { const YAML::Node &n = *it; if (n["usr"]) { - rhs.emplace_back(n["usr"].as()); + source_location loc; + loc.location_type = source_location::location_t::usr; + loc.location = n["usr"].as(); + rhs.emplace_back(std::move(loc)); } else if (n["marker"]) { - rhs.emplace_back(n["marker"].as()); + source_location loc; + loc.location_type = source_location::location_t::marker; + loc.location = n["marker"].as(); + rhs.emplace_back(std::move(loc)); } else if (n["file"] && n["line"]) { - rhs.emplace_back(std::make_pair( - n["file"].as(), n["line"].as())); + source_location loc; + loc.location_type = source_location::location_t::fileline; + loc.location = n["file"].as() + ":" + + n["line"].as(); + rhs.emplace_back(std::move(loc)); + } + else if (n["function"]) { + source_location loc; + loc.location_type = source_location::location_t::function; + loc.location = n["function"].as(); + rhs.emplace_back(std::move(loc)); } else { return false; @@ -261,7 +275,7 @@ template <> struct convert { return false; if (node["start_from"]) - rhs.start_from = node["start_from"].as(); + rhs.start_from = node["start_from"].as>(); return true; } diff --git a/src/config/config.h b/src/config/config.h index d085e40e..8516ce1a 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -81,13 +81,9 @@ struct diagram { }; struct source_location { - using usr = std::string; - using marker = std::string; - using file = std::pair; - // std::variant requires unique types, so we cannot add - // marker here, we need sth like boost::mp_unique - // type function - using variant = std::variant; + enum class location_t { usr, marker, fileline, function }; + location_t location_type; + std::string location; }; struct class_diagram : public diagram { @@ -102,7 +98,7 @@ struct class_diagram : public diagram { struct sequence_diagram : public diagram { virtual ~sequence_diagram() = default; - std::vector start_from; + std::vector start_from; }; struct config { diff --git a/src/main.cc b/src/main.cc index 1efbadbb..8b1d4f2c 100644 --- a/src/main.cc +++ b/src/main.cc @@ -77,10 +77,7 @@ int main(int argc, const char *argv[]) LOG_INFO("Loading compilation database from {} directory", config.compilation_database_dir); - auto db = - compilation_database::from_directory(config.compilation_database_dir); - - cppast::libclang_compilation_database db2(config.compilation_database_dir); + cppast::libclang_compilation_database db(config.compilation_database_dir); for (const auto &[name, diagram] : config.diagrams) { // If there are any specific diagram names provided on the command line, @@ -100,7 +97,7 @@ int main(int argc, const char *argv[]) if (std::dynamic_pointer_cast(diagram)) { auto model = clanguml::class_diagram::generators::plantuml::generate( - db2, name, dynamic_cast(*diagram)); + db, name, dynamic_cast(*diagram)); ofs << clanguml::class_diagram::generators::plantuml::generator( dynamic_cast(*diagram), diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index 2c142ef9..43d831d0 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -20,6 +20,9 @@ #include "sequence_diagram/visitor/translation_unit_context.h" +#include +#include + namespace clanguml::sequence_diagram::generators::plantuml { using diagram_model = clanguml::sequence_diagram::model::diagram; @@ -97,15 +100,20 @@ void generator::generate(std::ostream &ostr) const ostr << b << std::endl; for (const auto &sf : m_config.start_from) { - std::string start_from; - if (std::holds_alternative(sf)) { - start_from = std::get(sf); + if (sf.location_type == source_location::location_t::function) { + std::uint_least64_t start_from; + for (const auto &[k, v] : m_model.sequences) { + if (v.from == sf.location) { + start_from = k; + break; + } + } + generate_activity(m_model.sequences[start_from], ostr); } else { // TODO: Add support for other sequence start location types continue; } - generate_activity(m_model.sequences[start_from], ostr); } for (const auto &a : m_config.puml.after) ostr << a << std::endl; @@ -120,51 +128,33 @@ std::ostream &operator<<(std::ostream &os, const generator &g) } clanguml::sequence_diagram::model::diagram generate( - clanguml::cx::compilation_database &db, const std::string &name, + cppast::libclang_compilation_database &db, const std::string &name, clanguml::config::sequence_diagram &diagram) { spdlog::info("Generating diagram {}.puml", name); clanguml::sequence_diagram::model::diagram d; d.name = name; + cppast::cpp_entity_index idx; + cppast::simple_file_parser parser{ + type_safe::ref(idx)}; + + clanguml::sequence_diagram::visitor::translation_unit_visitor visitor( + idx, d, diagram); + // Get all translation units matching the glob from diagram // configuration - std::vector translation_units{}; + std::vector translation_units{}; for (const auto &g : diagram.glob) { spdlog::debug("Processing glob: {}", g); const auto matches = glob::rglob(g); std::copy(matches.begin(), matches.end(), std::back_inserter(translation_units)); } + cppast::parse_files(parser, translation_units, db); - // Process all matching translation units - for (const auto &tu_path : translation_units) { - spdlog::debug("Processing translation unit: {}", - std::filesystem::canonical(tu_path).c_str()); - - auto tu = db.parse_translation_unit(tu_path); - - auto cursor = clang_getTranslationUnitCursor(tu); - - if (clang_Cursor_isNull(cursor)) { - spdlog::debug("Cursor is NULL"); - } - - spdlog::debug("Cursor kind: {}", - clang_getCString(clang_getCursorKindSpelling(cursor.kind))); - spdlog::debug("Cursor name: {}", - clang_getCString(clang_getCursorDisplayName(cursor))); - - clanguml::sequence_diagram::visitor::translation_unit_context ctx( - d, diagram); - auto res = clang_visitChildren(cursor, - clanguml::sequence_diagram::visitor::translation_unit_visitor, - &ctx); - - spdlog::debug("Processing result: {}", res); - - clang_suspendTranslationUnit(tu); - } + for (auto &file : parser.files()) + visitor(file); return d; } diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h index 5b63cc08..e90557db 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.h @@ -24,6 +24,7 @@ #include "util/util.h" #include +#include #include #include @@ -62,7 +63,7 @@ private: }; clanguml::sequence_diagram::model::diagram generate( - clanguml::cx::compilation_database &db, const std::string &name, + cppast::libclang_compilation_database &db, const std::string &name, clanguml::config::sequence_diagram &diagram); } diff --git a/src/sequence_diagram/model/activity.h b/src/sequence_diagram/model/activity.h index 4ac2473e..cf054965 100644 --- a/src/sequence_diagram/model/activity.h +++ b/src/sequence_diagram/model/activity.h @@ -25,7 +25,7 @@ namespace clanguml::sequence_diagram::model { struct activity { - std::string usr; + std::uint_least64_t usr; std::string from; std::vector messages; }; diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index 44984631..6b23c644 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -28,7 +28,7 @@ struct diagram { bool started{false}; std::string name; - std::map sequences; + std::map sequences; }; } diff --git a/src/sequence_diagram/model/message.h b/src/sequence_diagram/model/message.h index 54a148eb..872d5411 100644 --- a/src/sequence_diagram/model/message.h +++ b/src/sequence_diagram/model/message.h @@ -27,9 +27,9 @@ namespace clanguml::sequence_diagram::model { struct message { message_t type; std::string from; - std::string from_usr; + std::uint_least64_t from_usr; std::string to; - std::string to_usr; + std::uint_least64_t to_usr; std::string message; std::string return_type; unsigned int line; diff --git a/src/sequence_diagram/visitor/translation_unit_context.cc b/src/sequence_diagram/visitor/translation_unit_context.cc index 547875b9..ffe490da 100644 --- a/src/sequence_diagram/visitor/translation_unit_context.cc +++ b/src/sequence_diagram/visitor/translation_unit_context.cc @@ -25,9 +25,11 @@ namespace clanguml::sequence_diagram::visitor { translation_unit_context::translation_unit_context( + cppast::cpp_entity_index &idx, clanguml::sequence_diagram::model::diagram &diagram, const clanguml::config::sequence_diagram &config) - : diagram_{diagram} + : entity_index_{idx} + , diagram_{diagram} , config_{config} { } @@ -55,6 +57,11 @@ clanguml::sequence_diagram::model::diagram &translation_unit_context::diagram() return diagram_; } +const cppast::cpp_entity_index &translation_unit_context::entity_index() const +{ + return entity_index_; +} + void translation_unit_context::set_current_method(cx::cursor method) { current_method_ = method; diff --git a/src/sequence_diagram/visitor/translation_unit_context.h b/src/sequence_diagram/visitor/translation_unit_context.h index f80da7e5..e4c48a4d 100644 --- a/src/sequence_diagram/visitor/translation_unit_context.h +++ b/src/sequence_diagram/visitor/translation_unit_context.h @@ -21,6 +21,8 @@ #include "cx/cursor.h" #include "sequence_diagram/model/diagram.h" +#include + #include #include #include @@ -29,7 +31,7 @@ namespace clanguml::sequence_diagram::visitor { class translation_unit_context { public: - translation_unit_context( + translation_unit_context(cppast::cpp_entity_index &idx, clanguml::sequence_diagram::model::diagram &diagram, const clanguml::config::sequence_diagram &config); @@ -50,10 +52,13 @@ public: cx::cursor ¤t_method(); private: - std::vector namespace_; - cx::cursor current_method_; + // Reference to the cppast entity index + cppast::cpp_entity_index &entity_index_; clanguml::sequence_diagram::model::diagram &diagram_; const clanguml::config::sequence_diagram &config_; + + std::vector namespace_; + cx::cursor current_method_; }; } diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.cc b/src/sequence_diagram/visitor/translation_unit_visitor.cc index e3e23c92..1409844f 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.cc +++ b/src/sequence_diagram/visitor/translation_unit_visitor.cc @@ -20,124 +20,125 @@ #include "translation_unit_context.h" +#include +#include +#include + namespace clanguml::sequence_diagram::visitor { -enum CXChildVisitResult translation_unit_visitor( - CXCursor cx_cursor, CXCursor cx_parent, CXClientData client_data) +translation_unit_visitor::translation_unit_visitor( + cppast::cpp_entity_index &idx, + clanguml::sequence_diagram::model::diagram &diagram, + const clanguml::config::sequence_diagram &config) + : ctx{idx, diagram, config} +{ +} + +void translation_unit_visitor::process_activities(const cppast::cpp_function &e) { using clanguml::sequence_diagram::model::activity; using clanguml::sequence_diagram::model::diagram; using clanguml::sequence_diagram::model::message; using clanguml::sequence_diagram::model::message_t; + using cppast::cpp_entity; + using cppast::cpp_entity_kind; + using cppast::cpp_function; + using cppast::cpp_member_function; + using cppast::cpp_member_function_call; + using cppast::visitor_info; - auto *ctx = (struct translation_unit_context *)client_data; + for (const auto &function_call_ptr : e.function_calls()) { + const auto &function_call = + static_cast(*function_call_ptr); - enum CXChildVisitResult ret = CXChildVisit_Break; + message m; + m.type = message_t::kCall; - cx::cursor cursor{std::move(cx_cursor)}; - cx::cursor parent{std::move(cx_parent)}; + if (!ctx.entity_index() + .lookup_definition(function_call.get_caller_id()) + .has_value()) + continue; - if (cursor.spelling().empty()) { - return CXChildVisit_Recurse; - } + if (!ctx.entity_index() + .lookup_definition(function_call.get_caller_method_id()) + .has_value()) + continue; - switch (cursor.kind()) { - case CXCursor_FunctionTemplate: - case CXCursor_CXXMethod: - case CXCursor_FunctionDecl: - ctx->set_current_method(cursor); - ret = CXChildVisit_Recurse; - break; - case CXCursor_CallExpr: { - auto referenced = cursor.referenced(); - auto referenced_type = referenced.type(); - auto referenced_cursor_name = referenced.display_name(); + if (!ctx.entity_index() + .lookup_definition(function_call.get_callee_id()) + .has_value()) + continue; - auto semantic_parent = referenced.semantic_parent(); - auto sp_name = semantic_parent.fully_qualified(); - auto lexical_parent = cursor.lexical_parent(); - auto lp_name = lexical_parent.spelling(); + if (!ctx.entity_index() + .lookup_definition(function_call.get_callee_method_id()) + .has_value()) + continue; - CXFile f; - unsigned int line{}; - unsigned int column{}; - unsigned int offset{}; - clang_getFileLocation(cursor.location(), &f, &line, &column, &offset); - std::string file{clang_getCString(clang_getFileName(f))}; + const auto &caller = + ctx.entity_index() + .lookup_definition(function_call.get_caller_id()) + .value(); + m.from = cx::util::ns(caller) + "::" + caller.name(); - auto &d = ctx->diagram(); - auto &config = ctx->config(); - if (referenced.kind() == CXCursor_CXXMethod) { - if (config.should_include(sp_name)) { - // Get calling object - std::string caller{}; - if (ctx->current_method() - .semantic_parent() - .is_translation_unit() || - ctx->current_method().semantic_parent().is_namespace()) { - caller = ctx->current_method() - .semantic_parent() - .fully_qualified() + - "::" + ctx->current_method().spelling() + "()"; - } - else { - caller = ctx->current_method() - .semantic_parent() - .fully_qualified(); - } + if (!ctx.config().should_include(m.from)) + continue; - auto caller_usr = ctx->current_method().usr(); - // Get called object - auto callee = referenced.semantic_parent().fully_qualified(); - auto callee_usr = referenced.semantic_parent().usr(); + if (caller.kind() == cpp_entity_kind::function_t) + m.from += "()"; - // Get called method - auto called_message = cursor.spelling(); - // Found method call: CXCursorKind () const - spdlog::debug("Adding method call at line {}:{} to diagram {}" - "\n\tCURRENT_METHOD: {}\n\tFROM: '{}'\n\tTO: " - "{}\n\tMESSAGE: {}\n\tFROM_USR: {}\n\tTO_USR: " - "{}\n\tRETURN_TYPE: {}", - file, line, d.name, ctx->current_method().spelling(), - caller, callee, called_message, caller_usr, callee_usr, - referenced.type().result_type().spelling()); + m.from_usr = type_safe::get(function_call.get_caller_method_id()); - message m; - m.type = message_t::kCall; - m.from = caller; - m.from_usr = caller_usr; - m.line = line; - m.to = callee; - m.to_usr = referenced.usr(); - m.message = called_message; - m.return_type = referenced.type().result_type().spelling(); + const auto &callee = + ctx.entity_index() + .lookup_definition(function_call.get_callee_id()) + .value(); + m.to = cx::util::ns(callee) + "::" + callee.name(); - if (d.sequences.find(caller_usr) == d.sequences.end()) { - activity a; - a.usr = caller_usr; - a.from = caller; - d.sequences.insert({caller_usr, std::move(a)}); - } + if (!ctx.config().should_include(m.to)) + continue; - d.sequences[caller_usr].messages.emplace_back(std::move(m)); - } - } - else if (referenced.kind() == CXCursor_FunctionDecl) { - // TODO + m.to_usr = type_safe::get(function_call.get_callee_method_id()); + + const auto &callee_method = + ctx.entity_index() + .lookup_definition(function_call.get_callee_method_id()) + .value(); + + m.message = callee_method.name(); + + if (ctx.diagram().sequences.find(m.from_usr) == + ctx.diagram().sequences.end()) { + activity a; + a.usr = m.from_usr; + a.from = m.from; + ctx.diagram().sequences.insert({m.from_usr, std::move(a)}); } - ret = CXChildVisit_Recurse; - break; - } - case CXCursor_Namespace: { - ret = CXChildVisit_Recurse; - break; - } - default: - ret = CXChildVisit_Recurse; - } + LOG_DBG("Adding sequence {} -{}()-> {}", m.from, m.message, m.to); - return ret; + ctx.diagram().sequences[m.from_usr].messages.emplace_back(std::move(m)); + } +} + +void translation_unit_visitor::operator()(const cppast::cpp_entity &file) +{ + using cppast::cpp_entity; + using cppast::cpp_entity_kind; + using cppast::cpp_function; + using cppast::cpp_member_function; + using cppast::cpp_member_function_call; + using cppast::visitor_info; + + cppast::visit(file, [&, this](const cpp_entity &e, visitor_info info) { + if (e.kind() == cpp_entity_kind::function_t) { + const auto &function = static_cast(e); + process_activities(function); + } + else if (e.kind() == cpp_entity_kind::member_function_t) { + const auto &member_function = static_cast(e); + process_activities(member_function); + } + }); } } diff --git a/src/sequence_diagram/visitor/translation_unit_visitor.h b/src/sequence_diagram/visitor/translation_unit_visitor.h index 752b2ab5..2ff32d63 100644 --- a/src/sequence_diagram/visitor/translation_unit_visitor.h +++ b/src/sequence_diagram/visitor/translation_unit_visitor.h @@ -20,10 +20,24 @@ #include "config/config.h" #include "cx/cursor.h" #include "sequence_diagram/model/diagram.h" +#include "sequence_diagram/visitor/translation_unit_context.h" + +#include namespace clanguml::sequence_diagram::visitor { -enum CXChildVisitResult translation_unit_visitor( - CXCursor cx_cursor, CXCursor cx_parent, CXClientData client_data); +class translation_unit_visitor { +public: + translation_unit_visitor(cppast::cpp_entity_index &idx, + clanguml::sequence_diagram::model::diagram &diagram, + const clanguml::config::sequence_diagram &config); + void operator()(const cppast::cpp_entity &file); + +private: + void process_activities(const cppast::cpp_function &e); + + // ctx allows to track current visitor context, e.g. current namespace + translation_unit_context ctx; +}; } diff --git a/tests/t20001/.clang-uml b/tests/t20001/.clang-uml index 2748c867..5a2b9c8c 100644 --- a/tests/t20001/.clang-uml +++ b/tests/t20001/.clang-uml @@ -14,7 +14,7 @@ diagrams: using_namespace: - clanguml::t20001 start_from: - - usr: "c:@N@clanguml@N@t20001@F@tmain#" + - function: "clanguml::t20001::tmain()" plantuml: before: - "' t20001 test sequence diagram" diff --git a/tests/t20001/test_case.h b/tests/t20001/test_case.h index 1034f4c1..36027af3 100644 --- a/tests/t20001/test_case.h +++ b/tests/t20001/test_case.h @@ -18,7 +18,7 @@ TEST_CASE("t20001", "[test-case][sequence]") { - auto [config, db] = load_config2("t20001"); + auto [config, db] = load_config("t20001"); auto diagram = config.diagrams["t20001_sequence"]; @@ -49,6 +49,7 @@ TEST_CASE("t20001", "[test-case][sequence]") REQUIRE_THAT(puml, HasCall("B", "A", "log_result")); REQUIRE_THAT(puml, HasCallWithResponse("B", "A", "add3")); REQUIRE_THAT(puml, HasCall("A", "add")); + REQUIRE_THAT(puml, !HasCall("A", "detail::C", "add")); save_puml( "./" + config.output_directory + "/" + diagram->name + ".puml", puml); diff --git a/tests/test_cases.cc b/tests/test_cases.cc index ab49b08b..999f6828 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -41,7 +41,7 @@ std::pair load_config2( } clanguml::sequence_diagram::model::diagram generate_sequence_diagram( - compilation_database &db, + cppast::libclang_compilation_database &db, std::shared_ptr diagram) { auto diagram_model =