diff --git a/.circleci/config.yml b/.circleci/config.yml index 69a0807..589245a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ workflows: name: test-gcc requires: - build-gcc - exclude_regex: ".*inheritance_polymorphic.*|.*arrays_member_int0" + exclude_regex: ".*inheritance_polymorphic.*|.*arrays_member_int0|ClangTypeParserTest.*" - coverage: name: coverage requires: @@ -104,6 +104,7 @@ jobs: paths: - build/* - extern/* + - include/* - types/* test: diff --git a/oi/type_graph/ClangTypeParserTest.cpp b/oi/type_graph/ClangTypeParserTest.cpp new file mode 100644 index 0000000..4671773 --- /dev/null +++ b/oi/type_graph/ClangTypeParserTest.cpp @@ -0,0 +1,194 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "oi/type_graph/ClangTypeParser.h" +#include "oi/type_graph/Printer.h" + +using namespace oi::detail; +using namespace oi::detail::type_graph; + +class ClangTypeParserTest : public ::testing::Test { + public: + std::string run(std::string_view function, ClangTypeParserOptions opt); + void test(std::string_view function, + std::string_view expected, + ClangTypeParserOptions opts = { + .chaseRawPointers = true, + .readEnumValues = true, + }); +}; + +// This stuff is a total mess. Set up factories as ClangTooling expects them. +class ConsumerContext; +class CreateTypeGraphConsumer; + +class CreateTypeGraphAction : public clang::ASTFrontendAction { + public: + CreateTypeGraphAction(ConsumerContext& ctx_) : ctx{ctx_} { + } + + void ExecuteAction() override; + std::unique_ptr CreateASTConsumer( + clang::CompilerInstance& CI, clang::StringRef file) override; + + private: + ConsumerContext& ctx; +}; + +class CreateTypeGraphActionFactory + : public clang::tooling::FrontendActionFactory { + public: + CreateTypeGraphActionFactory(ConsumerContext& ctx_) : ctx{ctx_} { + } + + std::unique_ptr create() override { + return std::make_unique(ctx); + } + + private: + ConsumerContext& ctx; +}; + +class ConsumerContext { + public: + ConsumerContext( + const std::vector>& containerInfos_) + : containerInfos{containerInfos_} { + } + std::string_view fullyQualifiedName; + ClangTypeParserOptions opts; + const std::vector>& containerInfos; + TypeGraph typeGraph; + Type* result = nullptr; + + private: + clang::Sema* sema = nullptr; + friend CreateTypeGraphConsumer; + friend CreateTypeGraphAction; +}; + +class CreateTypeGraphConsumer : public clang::ASTConsumer { + private: + ConsumerContext& ctx; + + public: + CreateTypeGraphConsumer(ConsumerContext& ctx_) : ctx{ctx_} { + } + + void HandleTranslationUnit(clang::ASTContext& Context) override { + const clang::Type* type = nullptr; + for (const clang::Type* ty : Context.getTypes()) { + std::string fqnWithoutTemplateParams; + switch (ty->getTypeClass()) { + case clang::Type::Record: + fqnWithoutTemplateParams = llvm::cast(ty) + ->getDecl() + ->getQualifiedNameAsString(); + break; + default: + continue; + } + if (fqnWithoutTemplateParams != ctx.fullyQualifiedName) + continue; + + type = ty; + break; + } + EXPECT_NE(type, nullptr); + + ClangTypeParser parser{ctx.typeGraph, ctx.containerInfos, ctx.opts}; + ctx.result = &parser.parse(Context, *ctx.sema, *type); + } +}; + +void CreateTypeGraphAction::ExecuteAction() { + clang::CompilerInstance& CI = getCompilerInstance(); + + if (!CI.hasSema()) + CI.createSema(clang::TU_Complete, nullptr); + ctx.sema = &CI.getSema(); + + clang::ASTFrontendAction::ExecuteAction(); +} + +std::unique_ptr CreateTypeGraphAction::CreateASTConsumer( + [[maybe_unused]] clang::CompilerInstance& CI, + [[maybe_unused]] clang::StringRef file) { + return std::make_unique(ctx); +} + +std::string ClangTypeParserTest::run(std::string_view type, + ClangTypeParserOptions opts) { + std::string err{"failed to load compilation database"}; + auto db = + clang::tooling::CompilationDatabase::loadFromDirectory(BUILD_DIR, err); + if (!db) { + throw std::runtime_error("failed to load compilation database"); + } + + std::vector> cis; + ConsumerContext ctx{cis}; + ctx.fullyQualifiedName = type; + ctx.opts = std::move(opts); + CreateTypeGraphActionFactory factory{ctx}; + + std::vector sourcePaths{TARGET_CPP_PATH}; + clang::tooling::ClangTool tool{*db, sourcePaths}; + if (auto retCode = tool.run(&factory); retCode != 0) { + throw std::runtime_error("clang type parsing failed"); + } + + std::stringstream out; + NodeTracker tracker; + Printer printer{out, tracker, ctx.typeGraph.size()}; + printer.print(*ctx.result); + + return std::move(out).str(); +} + +void ClangTypeParserTest::test(std::string_view type, + std::string_view expected, + ClangTypeParserOptions opts) { + std::string actual = run(type, std::move(opts)); + expected.remove_prefix(1); // Remove initial '\n' + EXPECT_EQ(expected, actual); +} + +TEST_F(ClangTypeParserTest, SimpleStruct) { + test("ns_simple::SimpleStruct", R"( +[0] Struct: SimpleStruct [ns_simple::SimpleStruct] (size: 16, align: 8) + Member: a (offset: 0) + Primitive: int32_t + Member: b (offset: 4) + Primitive: int8_t + Member: c (offset: 8) + Primitive: int64_t +)"); +} + +TEST_F(ClangTypeParserTest, MemberAlignment) { + test("ns_alignment::MemberAlignment", R"( +[0] Struct: MemberAlignment [ns_alignment::MemberAlignment] (size: 64, align: 32) + Member: c (offset: 0) + Primitive: int8_t + Member: c32 (offset: 32, align: 32) + Primitive: int8_t +)"); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 287ebcd..ccdb606 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -69,6 +69,31 @@ target_link_libraries(test_type_graph include(GoogleTest) gtest_discover_tests(test_type_graph) +add_executable(test_clang_type_parser + main.cpp + ../oi/type_graph/ClangTypeParserTest.cpp +) +add_dependencies(test_clang_type_parser integration_test_target) +target_compile_definitions(test_clang_type_parser PRIVATE + TARGET_CPP_PATH="${CMAKE_CURRENT_BINARY_DIR}/integration/integration_test_target.cpp" + BUILD_DIR="${CMAKE_BINARY_DIR}" +) +target_link_libraries(test_clang_type_parser + codegen + container_info + type_graph + + range-v3 + + GTest::gmock_main +) +if (FORCE_LLVM_STATIC) + target_link_libraries(test_clang_type_parser clangTooling ${llvm_libs}) +else() + target_link_libraries(test_clang_type_parser clang-cpp LLVM) +endif() +gtest_discover_tests(test_clang_type_parser) + cpp_unittest( NAME test_parser SRCS test_parser.cpp