From 9c46c96820dbc2e0707564929908e07b92860a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Mon, 3 Apr 2017 09:49:29 +0200 Subject: [PATCH] Fix and test include directive parsing --- external/external.cmake | 3 ++- include/cppast/libclang_parser.hpp | 10 ++++++--- src/CMakeLists.txt | 3 ++- src/libclang/libclang_parser.cpp | 26 ++++++++++++++++++++++- src/libclang/preprocessor.cpp | 34 ++++++++++++++++++++++++------ src/parser.cpp | 8 +++++-- test/cpp_preprocessor.cpp | 27 ++++++++++++++++++------ 7 files changed, 90 insertions(+), 21 deletions(-) diff --git a/external/external.cmake b/external/external.cmake index ce7453f..299c5c1 100644 --- a/external/external.cmake +++ b/external/external.cmake @@ -84,7 +84,7 @@ function(_cppast_find_llvm_config) endfunction() # find libclang using the config tool -# sets: LIBCLANG_INCLUDE_DIR, LIBCLANG_SYSTEM_INCLUDE_DIR, LIBCLANG_LIBRARY and CLANG_BINARY +# sets: LLVM_VERSION, LIBCLANG_INCLUDE_DIR, LIBCLANG_SYSTEM_INCLUDE_DIR, LIBCLANG_LIBRARY and CLANG_BINARY function(_cppast_find_libclang config_tool min_version force) # check version execute_process(COMMAND ${LLVM_CONFIG_BINARY} --version @@ -93,6 +93,7 @@ function(_cppast_find_libclang config_tool min_version force) message(FATAL_ERROR "Outdated LLVM version ${llvm_version}, minimal supported is ${min_version}") else() message(STATUS "Using LLVM version ${llvm_version}") + set(LLVM_VERSION ${llvm_version} CACHE INTERNAL "") endif() # get include directory diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index cc86455..441344e 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -19,6 +19,8 @@ namespace cppast { static const std::string& clang_binary(const libclang_compile_config& config); + static int clang_version(const libclang_compile_config& config); + static const std::vector& flags(const libclang_compile_config& config); }; } // namespace detail @@ -29,11 +31,12 @@ namespace cppast public: libclang_compile_config(); - /// \effects Sets the path to the location of the `clang++` binary. + /// \effects Sets the path to the location of the `clang++` binary and the version of that binary. /// \notes It will be used for preprocessing. - void set_clang_binary(std::string binary) + void set_clang_binary(std::string binary, int major, int minor, int patch) { - clang_binary_ = std::move(binary); + clang_binary_ = std::move(binary); + clang_version_ = major * 10000 + minor * 100 + patch; } private: @@ -51,6 +54,7 @@ namespace cppast } std::string clang_binary_; + int clang_version_; friend detail::libclang_compile_config_access; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cb3da32..f466c82 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,5 +96,6 @@ target_include_directories(cppast PUBLIC ../include) target_link_libraries(cppast PUBLIC type_safe _cppast_tiny_process _cppast_libclang) target_compile_definitions(cppast PUBLIC CPPAST_LIBCLANG_SYSTEM_INCLUDE_DIR="${LIBCLANG_SYSTEM_INCLUDE_DIR}" - CPPAST_CLANG_BINARY="${CLANG_BINARY}") + CPPAST_CLANG_BINARY="${CLANG_BINARY}" + CPPAST_CLANG_VERSION_STRING="${LLVM_VERSION}") set_target_properties(cppast PROPERTIES CXX_STANDARD 11) diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index fb74a76..200bbaa 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -22,15 +22,39 @@ const std::string& detail::libclang_compile_config_access::clang_binary( return config.clang_binary_; } +int detail::libclang_compile_config_access::clang_version(const libclang_compile_config& config) +{ + return config.clang_version_; +} + const std::vector& detail::libclang_compile_config_access::flags( const libclang_compile_config& config) { return config.get_flags(); } +namespace +{ + int parse_number(const char*& str) + { + auto result = 0; + for (; *str && *str != '.'; ++str) + { + result *= 10; + result += int(*str - '0'); + } + return result; + } +} + libclang_compile_config::libclang_compile_config() : compile_config({}) { - set_clang_binary(CPPAST_CLANG_BINARY); + auto ptr = CPPAST_CLANG_VERSION_STRING; + auto major = parse_number(ptr); + auto minor = parse_number(ptr); + auto patch = parse_number(ptr); + set_clang_binary(CPPAST_CLANG_BINARY, major, minor, patch); + add_include_dir(CPPAST_LIBCLANG_SYSTEM_INCLUDE_DIR); } diff --git a/src/libclang/preprocessor.cpp b/src/libclang/preprocessor.cpp index c1310f5..96f7f32 100644 --- a/src/libclang/preprocessor.cpp +++ b/src/libclang/preprocessor.cpp @@ -35,14 +35,19 @@ namespace // -E: print preprocessor output // -CC: keep comments, even in macro // -dD: print macro definitions as well - // -dI: print include directives as well + auto flags = std::string("-E -CC -dD"); + if (detail::libclang_compile_config_access::clang_version(c) >= 40000) + // -Xclang -dI: print include directives as well (clang >= 4.0.0) + flags += " -Xclang -dI"; // -fno-caret-diagnostics: don't show the source extract in diagnostics // -fno-show-column: don't show the column number - // -fdiagnostics-format=msvc: use easier to parse MSVC format + // -fdiagnostics-format msvc: use easier to parse MSVC format + flags += " -fno-caret-diagnostics -fno-show-column -fdiagnostics-format=msvc"; // -Wno-pragma-once-outside-header: hide wrong warning - std::string cmd(detail::libclang_compile_config_access::clang_binary(c) - + " -E -CC -dD -dI -fno-caret-diagnostics -fno-show-column " - "-fdiagnostics-format=msvc -Wno-pragma-once-outside-header "); + flags += " -Wno-pragma-once-outside-header"; + + std::string cmd(detail::libclang_compile_config_access::clang_binary(c) + " " + + std::move(flags) + " "); // add other flags for (auto& flag : detail::libclang_compile_config_access::flags(c)) @@ -61,10 +66,17 @@ namespace { // format: (): // or: : + auto fallback = ptr; std::string filename; while (*ptr && *ptr != ':' && *ptr != '(') filename.push_back(*ptr++); + if (filename == "error" || filename == "warning" || filename == "fatal error") + { + ptr = fallback; + return {}; + } + type_safe::optional line; if (*ptr == '(') { @@ -87,6 +99,7 @@ namespace severity parse_severity(const char*& ptr) { // format: : + auto fallback = ptr; std::string sev; while (*ptr && *ptr != ':') sev.push_back(*ptr++); @@ -99,7 +112,7 @@ namespace else if (sev == "fatal error") return severity::critical; else - DEBUG_UNREACHABLE(detail::assert_handler{}); + ptr = fallback; return severity::error; } @@ -249,6 +262,12 @@ namespace return std::strncmp(p.ptr(), str, std::strlen(str)) == 0; } + void skip(position& p, const char* str) + { + DEBUG_ASSERT(starts_with(p, str), detail::assert_handler{}); + p.skip(std::strlen(str)); + } + detail::pp_doc_comment parse_c_doc_comment(position& p) { detail::pp_doc_comment result; @@ -484,6 +503,8 @@ namespace if (!p.was_newl() || !starts_with(p, "#include")) return nullptr; p.skip(std::strlen("#include")); + if (starts_with(p, "_next")) + p.skip(std::strlen("_next")); skip_spaces(p); auto include_kind = cpp_include_kind::system; @@ -507,6 +528,7 @@ namespace filename += *p.ptr(); DEBUG_ASSERT(starts_with(p, end_str), detail::assert_handler{}, "bad termination"); p.skip(); + skip(p, " /* clang -E -dI */"); DEBUG_ASSERT(starts_with(p, "\n"), detail::assert_handler{}); // don't skip newline diff --git a/src/parser.cpp b/src/parser.cpp index a1ea5f6..9fe8bf0 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -20,7 +20,11 @@ bool diagnostic_logger::log(const char* source, const diagnostic& d) const bool stderr_diagnostic_logger::do_log(const char* source, const diagnostic& d) const { - std::fprintf(stderr, "[%s] [%s] %s %s\n", source, to_string(d.severity), - d.location.to_string().c_str(), d.message.c_str()); + auto loc = d.location.to_string(); + if (loc.empty()) + std::fprintf(stderr, "[%s] [%s] %s\n", source, to_string(d.severity), d.message.c_str()); + else + std::fprintf(stderr, "[%s] [%s] %s %s\n", source, to_string(d.severity), + d.location.to_string().c_str(), d.message.c_str()); return true; } diff --git a/test/cpp_preprocessor.cpp b/test/cpp_preprocessor.cpp index bcc30ec..d0ebde8 100644 --- a/test/cpp_preprocessor.cpp +++ b/test/cpp_preprocessor.cpp @@ -77,21 +77,22 @@ namespace ns2 } } -// requires clang 4.0, currently not available for testing -// TODO: -#if 0 -TEST_CASE("cpp_include_directive") +// requires clang 4.0 +TEST_CASE("cpp_include_directive", "[!hide][clang4]") { write_file("cpp_include_directive-header.hpp", R"( #define FOO )"); auto header_a = R"( +/// #include #include +/// #include "cpp_include_directive-header.hpp" #include "cpp_include_directive-header.hpp" )"; auto header_b = R"( +/// #include "header_a.hpp" #include "header_a.hpp" )"; @@ -105,20 +106,32 @@ TEST_CASE("cpp_include_directive") { REQUIRE(include.target().name() == include.name()); REQUIRE(include.include_kind() == cppast::cpp_include_kind::system); - REQUIRE(!include.target().get(idx)); + REQUIRE(include.target().get(idx).empty()); } else if (include.name() == "cpp_include_directive-header.hpp") { REQUIRE(include.target().name() == include.name()); REQUIRE(include.include_kind() == cppast::cpp_include_kind::local); - REQUIRE(!include.target().get(idx)); + REQUIRE(include.target().get(idx).empty()); } else REQUIRE(false); }); REQUIRE(count == 2u); + + count = test_visit(*file_b, [&](const cpp_include_directive& include) { + if (include.name() == "header_a.hpp") + { + REQUIRE(include.target().name() == include.name()); + REQUIRE(include.include_kind() == cppast::cpp_include_kind::local); + REQUIRE( + equal_ref(idx, include.target(), cpp_file_ref(cpp_entity_id(""), "header_a.hpp"))); + } + else + REQUIRE(false); + }); + REQUIRE(count == 1u); } -#endif TEST_CASE("comment matching") {