From 8c8d180e0f834bd59fbf7689a76059e9b4668bc4 Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 14 Feb 2021 23:50:35 +0100 Subject: [PATCH] Added initial seqence diagram support --- .clanguml-sequence.example | 18 ++ include/CMakeLists.txt | 16 -- include/source_file.hpp | 7 - src/config/config.h | 54 +++++- src/main.cc | 66 ++----- src/uml/class_diagram_visitor.h | 10 +- src/uml/cx.h | 265 +++++++++++++++++++++++++++++ src/uml/sequence_diagram_model.h | 44 +++++ src/uml/sequence_diagram_visitor.h | 126 ++++++++++++++ tests/test_example.cpp | 10 +- 10 files changed, 524 insertions(+), 92 deletions(-) create mode 100644 .clanguml-sequence.example delete mode 100644 include/CMakeLists.txt delete mode 100644 include/source_file.hpp create mode 100644 src/uml/cx.h create mode 100644 src/uml/sequence_diagram_model.h create mode 100644 src/uml/sequence_diagram_visitor.h diff --git a/.clanguml-sequence.example b/.clanguml-sequence.example new file mode 100644 index 00000000..c4dd2b89 --- /dev/null +++ b/.clanguml-sequence.example @@ -0,0 +1,18 @@ +compilation_database_dir: build +output_directory: puml +diagrams: + main_sequence_diagram: + type: sequence + glob: + - src/main.cc + using_namespace: clanguml + start_from: + file: src/puml/class_diagram_generator.h + line: 108 + classes: + - config + - diagram + - class_diagram + puml: + - 'note top of diagram: Aggregate template' + diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt deleted file mode 100644 index ac951ebd..00000000 --- a/include/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ - -# Make an explicit list of all source files in CLANGUML_INC. This is important -# because CMake is not a build system: it is a build system generator. Suppose -# you add a file foo.cpp to src/ after running cmake .. . If you set -# CLANGUML_INC with `file(GLOB ... )`, this is not passed to the makefile; it -# doesn't know that foo.cpp exists and will not re-run cmake. Your -# collaborator's builds will fail and it will be unclear why. Whether you use -# file(GLOB ...) or not, you will need to re-run cmake, but with an explicit -# file list, you know beforehand why your code isn't compiling. -set(CLANGUML_INC source_file.hpp) - -# Form the full path to the source files... -PREPEND(CLANGUML_INC) - -# ... and pass the variable to the parent scope. -set(CLANGUML_INC ${CLANGUML_INC} PARENT_SCOPE) diff --git a/include/source_file.hpp b/include/source_file.hpp deleted file mode 100644 index 2acce8b7..00000000 --- a/include/source_file.hpp +++ /dev/null @@ -1,7 +0,0 @@ - -#include - -template -T add(T a, T b){ - return a + b; -} diff --git a/src/config/config.h b/src/config/config.h index 2576103b..57a8bcbd 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace clanguml { namespace config { @@ -45,12 +46,23 @@ struct class_diagram : public diagram { } }; +struct source_location { + std::string file; + unsigned int line; +}; + +struct sequence_diagram : public diagram { + virtual ~sequence_diagram() = default; + + std::optional start_from; +}; + struct config { // the glob list is additive and relative to the current // directory std::vector glob; std::string compilation_database_dir{"."}; - std::map> diagrams; + std::map> diagrams; }; config load(const std::string &config_file); @@ -60,6 +72,9 @@ config load(const std::string &config_file); namespace YAML { using clanguml::config::class_diagram; using clanguml::config::config; +using clanguml::config::sequence_diagram; +using clanguml::config::source_location; + // // class_diagram Yaml decoder // @@ -74,13 +89,41 @@ template <> struct convert { } }; +template <> struct convert { + static bool decode(const Node &node, source_location &rhs) + { + rhs.file = node["file"].as(); + rhs.line = node["line"].as(); + return true; + } +}; + + +// +// sequence_diagram Yaml decoder +// +template <> struct convert { + static bool decode(const Node &node, sequence_diagram &rhs) + { + rhs.using_namespace = node["using_namespace"].as(); + rhs.glob = node["glob"].as>(); + rhs.puml = node["puml"].as>(); + + if(node["start_from"]) + rhs.start_from = node["start_from"].as(); + return true; + } +}; + // // config Yaml decoder // template <> struct convert { static bool decode(const Node &node, config &rhs) { - rhs.glob = node["glob"].as>(); + if (node["glob"]) + rhs.glob = node["glob"].as>(); + if (node["compilation_database_dir"]) rhs.compilation_database_dir = node["compilation_database_dir"].as(); @@ -93,9 +136,14 @@ template <> struct convert { const auto diagram_type = d.second["type"].as(); if (diagram_type == "class") { rhs.diagrams[d.first.as()] = - std::make_unique( + std::make_shared( d.second.as()); } + if (diagram_type == "sequence") { + rhs.diagrams[d.first.as()] = + std::make_shared( + d.second.as()); + } else { spdlog::warn( "Diagrams of type {} are not supported at the moment... ", diff --git a/src/main.cc b/src/main.cc index 6b6fa90a..03612568 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,8 +1,10 @@ #include "config/config.h" #include "puml/class_diagram_generator.h" +#include "puml/sequence_diagram_generator.h" #include "uml/class_diagram_model.h" #include "uml/class_diagram_visitor.h" #include "uml/compilation_database.h" +#include "uml/sequence_diagram_visitor.h" #include #include @@ -18,8 +20,9 @@ #include #include -using clanguml::config::config; -using clanguml::cx::compilation_database; +using namespace clanguml; +using config::config; +using cx::compilation_database; int main(int argc, const char *argv[]) { @@ -44,7 +47,7 @@ int main(int argc, const char *argv[]) spdlog::info("Loading clang-uml config from {}", config_path); - auto config = clanguml::config::load(config_path); + auto config = config::load(config_path); spdlog::info("Loading compilation database from {} directory", config.compilation_database_dir); @@ -53,58 +56,17 @@ int main(int argc, const char *argv[]) compilation_database::from_directory(config.compilation_database_dir); for (const auto &[name, diagram] : config.diagrams) { - spdlog::info("Generating diagram {}.puml", name); - clanguml::model::class_diagram::diagram d; - d.name = name; + using config::class_diagram; + using config::sequence_diagram; - // Get all translation units matching the glob from diagram - // configuration - std::vector translation_units{}; - for (const auto &g : diagram->glob) { - spdlog::debug("Processing glob: {}", g); - const auto matches = glob::glob(g); - std::copy(matches.begin(), matches.end(), - std::back_inserter(translation_units)); + if (std::dynamic_pointer_cast(diagram)) { + generators::class_diagram::generate( + db, name, dynamic_cast(*diagram)); } - - // 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::visitor::class_diagram::tu_context ctx(d); - auto res = clang_visitChildren(cursor, - clanguml::visitor::class_diagram::translation_unit_visitor, - &ctx); - - spdlog::debug("Processing result: {}", res); - - clang_suspendTranslationUnit(tu); + else if (std::dynamic_pointer_cast(diagram)) { + generators::sequence_diagram::generate( + db, name, dynamic_cast(*diagram)); } - - std::filesystem::path path{"puml/" + d.name + ".puml"}; - std::ofstream ofs; - ofs.open(path, std::ofstream::out | std::ofstream::trunc); - - auto generator = clanguml::generators::class_diagram::puml::generator( - dynamic_cast(*diagram), d); - - ofs << generator; - - ofs.close(); } return 0; } diff --git a/src/uml/class_diagram_visitor.h b/src/uml/class_diagram_visitor.h index 873972ce..80909c38 100644 --- a/src/uml/class_diagram_visitor.h +++ b/src/uml/class_diagram_visitor.h @@ -44,13 +44,13 @@ struct class_visitor_context : element_visitor_context { }; struct tu_context { - tu_context(diagram &d) - : diagram(d) + tu_context(diagram &d_) + : d{d_} { } std::vector namespace_; - diagram &diagram; + class_diagram::diagram &d; }; enum CXChildVisitResult visit_if_cursor_valid( @@ -310,7 +310,7 @@ static enum CXChildVisitResult translation_unit_visitor( clang_visitChildren(cursor, class_visitor, &c); - ctx->diagram.classes.emplace_back(std::move(c)); + ctx->d.classes.emplace_back(std::move(c)); }); ret = CXChildVisit_Continue; @@ -327,7 +327,7 @@ static enum CXChildVisitResult translation_unit_visitor( clang_visitChildren(cursor, enum_visitor, &e); - ctx->diagram.enums.emplace_back(std::move(e)); + ctx->d.enums.emplace_back(std::move(e)); }); ret = CXChildVisit_Continue; diff --git a/src/uml/cx.h b/src/uml/cx.h new file mode 100644 index 00000000..e8fb33e7 --- /dev/null +++ b/src/uml/cx.h @@ -0,0 +1,265 @@ +#pragma once + +#include +#include +#include + +namespace clanguml { +namespace cx { + +namespace detail { +std::string to_string(CXString &&cxs) +{ + std::string r{clang_getCString(cxs)}; + clang_disposeString(cxs); + return r; +} +} + +using detail::to_string; + +class type { +public: + type(CXType &&t) + : m_type{std::move(t)} + { + } + ~type() = default; + + std::string spelling() const + { + return to_string(clang_getTypeSpelling(m_type)); + } + + bool operator==(const type &b) const + { + return clang_equalTypes(m_type, b.get()); + } + + type canonical() const { return {clang_getCanonicalType(m_type)}; } + + bool is_const_qualified() const + { + return clang_isConstQualifiedType(m_type); + } + + bool is_volatile_qualified() const + { + return clang_isVolatileQualifiedType(m_type); + } + + bool is_restricted_qualified() const + { + return clang_isRestrictQualifiedType(m_type); + } + + std::string typedef_name() const + { + return to_string(clang_getTypedefName(m_type)); + } + + type pointee_type() const { return {clang_getPointeeType(m_type)}; } + + /* + *cursor type_declaration() const + *{ + * return {clang_getTypeDeclaration(m_type)}; + *} + */ + + /* + *std::string type_kind_spelling() const + *{ + * return clang_getTypeKindSpelling(kind()); + *} + */ + + CXCallingConv calling_convention() const + { + return clang_getFunctionTypeCallingConv(m_type); + } + + type result_type() const { return clang_getResultType(m_type); } + + int exception_specification_type() const + { + return clang_getExceptionSpecificationType(m_type); + } + + int argument_type_count() const { return clang_getNumArgTypes(m_type); } + + type argument_type(int i) const { return {clang_getArgType(m_type, i)}; } + + bool is_function_variadic() const + { + return clang_isFunctionTypeVariadic(m_type); + } + + bool is_pod() const { return clang_isPODType(m_type); } + + type element_type() const { return clang_getElementType(m_type); } + + long long element_count() const { return clang_getNumElements(m_type); } + + type array_element_type() const + { + return clang_getArrayElementType(m_type); + } + + type named_type() const { return clang_Type_getNamedType(m_type); } + + CXTypeNullabilityKind nullability() const + { + return clang_Type_getNullability(m_type); + } + + type class_type() const { return clang_Type_getClassType(m_type); } + + long long size_of() const { return clang_Type_getSizeOf(m_type); } + + type modified_type() const { return clang_Type_getModifiedType(m_type); } + + type value_type() const { return clang_Type_getValueType(m_type); } + + int template_arguments_count() const + { + return clang_Type_getNumTemplateArguments(m_type); + } + + type template_argument_type(int i) const + { + return clang_Type_getTemplateArgumentAsType(m_type, i); + } + + const CXType &get() const { return m_type; } + + CXRefQualifierKind cxxref_qualifier() const + { + return clang_Type_getCXXRefQualifier(m_type); + } + +private: + CXType m_type; +}; + +class cursor { +public: + cursor() + : m_cursor{clang_getNullCursor()} + { + } + + cursor(CXCursor &&c) + : m_cursor{std::move(c)} + { + } + + cursor(const CXCursor &c) + : m_cursor{c} + { + } + + cursor(const cursor &c) + : m_cursor{c.get()} + { + } + + ~cursor() = default; + + bool operator==(const cursor &b) const + { + return clang_equalCursors(m_cursor, b.get()); + } + + cx::type type() const { return {clang_getCursorType(m_cursor)}; } + + std::string display_name() const + { + return to_string(clang_getCursorDisplayName(m_cursor)); + } + + std::string spelling() const + { + return to_string(clang_getCursorSpelling(m_cursor)); + } + + std::string fully_qualified() const + { + std::list res; + cursor iterator{m_cursor}; + while (iterator.kind() != CXCursor_TranslationUnit) { + auto name = iterator.spelling(); + if (!name.empty()) + res.push_front(iterator.spelling()); + iterator = iterator.semantic_parent(); + } + + return fmt::format("{}", fmt::join(res, "::")); + } + + cursor referenced() const + { + return cx::cursor{clang_getCursorReferenced(m_cursor)}; + } + + cursor semantic_parent() const + { + return {clang_getCursorSemanticParent(m_cursor)}; + } + + cursor lexical_parent() const + { + return {clang_getCursorLexicalParent(m_cursor)}; + } + + CXCursorKind kind() const { return m_cursor.kind; } + + bool is_definition() const { return clang_isCursorDefinition(m_cursor); } + + bool is_declaration() const { return clang_isDeclaration(kind()); } + + bool is_invalid_declaration() const + { + return clang_isInvalidDeclaration(m_cursor); + } + + CXSourceLocation location() const + { + return clang_getCursorLocation(m_cursor); + } + + bool is_reference() const { return clang_isReference(kind()); } + + bool is_expression() const { return clang_isExpression(kind()); } + + bool is_statement() const { return clang_isStatement(kind()); } + + bool is_attribute() const { return clang_isAttribute(kind()); } + + bool has_attrs() const { return clang_Cursor_hasAttrs(m_cursor); } + + bool is_invalid() const { return clang_isInvalid(kind()); } + + bool is_translation_unit() const { return clang_isTranslationUnit(kind()); } + + bool is_preprocessing() const { return clang_isPreprocessing(kind()); } + + CXVisibilityKind visibitity() const + { + return clang_getCursorVisibility(m_cursor); + } + + CXAvailabilityKind availability() const + { + return clang_getCursorAvailability(m_cursor); + } + + std::string usr() const { return to_string(clang_getCursorUSR(m_cursor)); } + + const CXCursor &get() const { return m_cursor; } + +private: + CXCursor m_cursor; +}; +} +} diff --git a/src/uml/sequence_diagram_model.h b/src/uml/sequence_diagram_model.h new file mode 100644 index 00000000..fb30038b --- /dev/null +++ b/src/uml/sequence_diagram_model.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace clanguml { +namespace model { +namespace sequence_diagram { + +enum class message_t { kCall, kReturn }; + +struct message { + message_t type; + std::string from; + std::string from_usr; + std::string to; + std::string message; + unsigned int line; +}; + +struct diagram { + std::string name; + std::vector sequence; + + void sort() + { + std::sort(sequence.begin(), sequence.end(), + [](const auto &a, const auto &b) -> bool { + if (a.from_usr == b.from_usr) + return a.line > b.line; + + return a.from_usr > b.from_usr; + }); + } +}; +} +} +} diff --git a/src/uml/sequence_diagram_visitor.h b/src/uml/sequence_diagram_visitor.h new file mode 100644 index 00000000..2ea1f1ec --- /dev/null +++ b/src/uml/sequence_diagram_visitor.h @@ -0,0 +1,126 @@ +#pragma once + +#include "cx.h" +#include "sequence_diagram_model.h" + +#include +#include +#include + +#include +#include +#include + +namespace clanguml { +namespace visitor { +namespace sequence_diagram { + +using clanguml::model::sequence_diagram::diagram; +using clanguml::model::sequence_diagram::message; +using clanguml::model::sequence_diagram::message_t; + +struct tu_context { + tu_context(diagram &d_) + : d{d_} + { + } + + std::vector namespace_; + cx::cursor current_method; + clanguml::model::sequence_diagram::diagram &d; +}; + +static enum CXChildVisitResult translation_unit_visitor( + CXCursor cx_cursor, CXCursor cx_parent, CXClientData client_data) +{ + struct tu_context *ctx = (struct tu_context *)client_data; + + enum CXChildVisitResult ret = CXChildVisit_Break; + + cx::cursor cursor{std::move(cx_cursor)}; + cx::cursor parent{std::move(cx_parent)}; + + if (cursor.spelling().empty()) { + return CXChildVisit_Recurse; + } + + switch (cursor.kind()) { + case CXCursor_FunctionTemplate: + case CXCursor_CXXMethod: + case CXCursor_FunctionDecl: + ctx->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(); + + 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(); + + CXFile f; + unsigned int line{}; + unsigned int column{}; + unsigned int offset{}; + clang_getFileLocation( + cursor.location(), &f, &line, &column, &offset); + + if (referenced.kind() == CXCursor_CXXMethod) { + if (sp_name.find("clanguml::") == 0) { + // Get calling object + std::string caller = + ctx->current_method.semantic_parent().fully_qualified(); + if (caller.empty() && + clang_Location_isFromMainFile(cursor.location()) == 0) + caller = "
"; + + std::string caller_usr = ctx->current_method.usr(); + // Get called object + std::string callee = + cursor.referenced().semantic_parent().fully_qualified(); + + // Get called method + std::string called_message = cursor.spelling(); + + // Found method call: CXCursorKind () const + spdlog::debug("Found method call at line {}:{} " + "\n\tCURRENT_METHOD: {}\n\tFROM: {}\n\tTO: " + "{}\n\tMESSAGE: {}", + clang_getCString(clang_getFileName(f)), line, + ctx->current_method.spelling(), caller, callee, + called_message); + + message m; + m.type = message_t::kCall; + m.from = caller; + m.from_usr = caller_usr; + m.line = line; + m.to = callee; + m.message = called_message; + + ctx->d.sequence.emplace_back(std::move(m)); + } + } + else if (referenced.kind() == CXCursor_FunctionDecl) { + // TODO + } + + ret = CXChildVisit_Continue; + break; + } + case CXCursor_Namespace: { + ret = CXChildVisit_Recurse; + break; + } + default: + ret = CXChildVisit_Recurse; + } + + return ret; +} +} +} +} diff --git a/tests/test_example.cpp b/tests/test_example.cpp index 88a571c7..fe693ce6 100644 --- a/tests/test_example.cpp +++ b/tests/test_example.cpp @@ -1,19 +1,11 @@ #define CATCH_CONFIG_MAIN #include "catch.hpp" -#include #include #include TEST_CASE("Test add", "[unit-test]"){ // not very good tests, but oh well... - REQUIRE(add(2, 3) == 5); - REQUIRE(add(2., 3.) == 5.); - REQUIRE(add(0, 0) == 0); + REQUIRE(2+5 == 5); std::cout << "RUNNING TEST" << std::endl; - std::complex a(2., 3.); - std::complex b(-1., 20.); - std::complex c(1., 23.); - REQUIRE(add(a,b) == c); - }