Fix and test include directive parsing

This commit is contained in:
Jonathan Müller 2017-04-03 09:49:29 +02:00
commit 9c46c96820
7 changed files with 90 additions and 21 deletions

View file

@ -84,7 +84,7 @@ function(_cppast_find_llvm_config)
endfunction() endfunction()
# find libclang using the config tool # 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) function(_cppast_find_libclang config_tool min_version force)
# check version # check version
execute_process(COMMAND ${LLVM_CONFIG_BINARY} --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}") message(FATAL_ERROR "Outdated LLVM version ${llvm_version}, minimal supported is ${min_version}")
else() else()
message(STATUS "Using LLVM version ${llvm_version}") message(STATUS "Using LLVM version ${llvm_version}")
set(LLVM_VERSION ${llvm_version} CACHE INTERNAL "")
endif() endif()
# get include directory # get include directory

View file

@ -19,6 +19,8 @@ namespace cppast
{ {
static const std::string& clang_binary(const libclang_compile_config& config); static const std::string& clang_binary(const libclang_compile_config& config);
static int clang_version(const libclang_compile_config& config);
static const std::vector<std::string>& flags(const libclang_compile_config& config); static const std::vector<std::string>& flags(const libclang_compile_config& config);
}; };
} // namespace detail } // namespace detail
@ -29,11 +31,12 @@ namespace cppast
public: public:
libclang_compile_config(); 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. /// \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: private:
@ -51,6 +54,7 @@ namespace cppast
} }
std::string clang_binary_; std::string clang_binary_;
int clang_version_;
friend detail::libclang_compile_config_access; friend detail::libclang_compile_config_access;
}; };

View file

@ -96,5 +96,6 @@ target_include_directories(cppast PUBLIC ../include)
target_link_libraries(cppast PUBLIC type_safe _cppast_tiny_process _cppast_libclang) target_link_libraries(cppast PUBLIC type_safe _cppast_tiny_process _cppast_libclang)
target_compile_definitions(cppast PUBLIC target_compile_definitions(cppast PUBLIC
CPPAST_LIBCLANG_SYSTEM_INCLUDE_DIR="${LIBCLANG_SYSTEM_INCLUDE_DIR}" 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) set_target_properties(cppast PROPERTIES CXX_STANDARD 11)

View file

@ -22,15 +22,39 @@ const std::string& detail::libclang_compile_config_access::clang_binary(
return config.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<std::string>& detail::libclang_compile_config_access::flags( const std::vector<std::string>& detail::libclang_compile_config_access::flags(
const libclang_compile_config& config) const libclang_compile_config& config)
{ {
return config.get_flags(); 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({}) 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); add_include_dir(CPPAST_LIBCLANG_SYSTEM_INCLUDE_DIR);
} }

View file

@ -35,14 +35,19 @@ namespace
// -E: print preprocessor output // -E: print preprocessor output
// -CC: keep comments, even in macro // -CC: keep comments, even in macro
// -dD: print macro definitions as well // -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-caret-diagnostics: don't show the source extract in diagnostics
// -fno-show-column: don't show the column number // -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 // -Wno-pragma-once-outside-header: hide wrong warning
std::string cmd(detail::libclang_compile_config_access::clang_binary(c) flags += " -Wno-pragma-once-outside-header";
+ " -E -CC -dD -dI -fno-caret-diagnostics -fno-show-column "
"-fdiagnostics-format=msvc -Wno-pragma-once-outside-header "); std::string cmd(detail::libclang_compile_config_access::clang_binary(c) + " "
+ std::move(flags) + " ");
// add other flags // add other flags
for (auto& flag : detail::libclang_compile_config_access::flags(c)) for (auto& flag : detail::libclang_compile_config_access::flags(c))
@ -61,10 +66,17 @@ namespace
{ {
// format: <filename>(<line>): // format: <filename>(<line>):
// or: <filename>: // or: <filename>:
auto fallback = ptr;
std::string filename; std::string filename;
while (*ptr && *ptr != ':' && *ptr != '(') while (*ptr && *ptr != ':' && *ptr != '(')
filename.push_back(*ptr++); filename.push_back(*ptr++);
if (filename == "error" || filename == "warning" || filename == "fatal error")
{
ptr = fallback;
return {};
}
type_safe::optional<unsigned> line; type_safe::optional<unsigned> line;
if (*ptr == '(') if (*ptr == '(')
{ {
@ -87,6 +99,7 @@ namespace
severity parse_severity(const char*& ptr) severity parse_severity(const char*& ptr)
{ {
// format: <severity>: // format: <severity>:
auto fallback = ptr;
std::string sev; std::string sev;
while (*ptr && *ptr != ':') while (*ptr && *ptr != ':')
sev.push_back(*ptr++); sev.push_back(*ptr++);
@ -99,7 +112,7 @@ namespace
else if (sev == "fatal error") else if (sev == "fatal error")
return severity::critical; return severity::critical;
else else
DEBUG_UNREACHABLE(detail::assert_handler{}); ptr = fallback;
return severity::error; return severity::error;
} }
@ -249,6 +262,12 @@ namespace
return std::strncmp(p.ptr(), str, std::strlen(str)) == 0; 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 parse_c_doc_comment(position& p)
{ {
detail::pp_doc_comment result; detail::pp_doc_comment result;
@ -484,6 +503,8 @@ namespace
if (!p.was_newl() || !starts_with(p, "#include")) if (!p.was_newl() || !starts_with(p, "#include"))
return nullptr; return nullptr;
p.skip(std::strlen("#include")); p.skip(std::strlen("#include"));
if (starts_with(p, "_next"))
p.skip(std::strlen("_next"));
skip_spaces(p); skip_spaces(p);
auto include_kind = cpp_include_kind::system; auto include_kind = cpp_include_kind::system;
@ -507,6 +528,7 @@ namespace
filename += *p.ptr(); filename += *p.ptr();
DEBUG_ASSERT(starts_with(p, end_str), detail::assert_handler{}, "bad termination"); DEBUG_ASSERT(starts_with(p, end_str), detail::assert_handler{}, "bad termination");
p.skip(); p.skip();
skip(p, " /* clang -E -dI */");
DEBUG_ASSERT(starts_with(p, "\n"), detail::assert_handler{}); DEBUG_ASSERT(starts_with(p, "\n"), detail::assert_handler{});
// don't skip newline // don't skip newline

View file

@ -20,6 +20,10 @@ bool diagnostic_logger::log(const char* source, const diagnostic& d) const
bool stderr_diagnostic_logger::do_log(const char* source, const diagnostic& d) const bool stderr_diagnostic_logger::do_log(const char* source, const diagnostic& d) const
{ {
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), std::fprintf(stderr, "[%s] [%s] %s %s\n", source, to_string(d.severity),
d.location.to_string().c_str(), d.message.c_str()); d.location.to_string().c_str(), d.message.c_str());
return true; return true;

View file

@ -77,21 +77,22 @@ namespace ns2
} }
} }
// requires clang 4.0, currently not available for testing // requires clang 4.0
// TODO: TEST_CASE("cpp_include_directive", "[!hide][clang4]")
#if 0
TEST_CASE("cpp_include_directive")
{ {
write_file("cpp_include_directive-header.hpp", R"( write_file("cpp_include_directive-header.hpp", R"(
#define FOO #define FOO
)"); )");
auto header_a = R"( auto header_a = R"(
/// #include <iostream>
#include <iostream> #include <iostream>
/// #include "cpp_include_directive-header.hpp"
#include "cpp_include_directive-header.hpp" #include "cpp_include_directive-header.hpp"
)"; )";
auto header_b = R"( auto header_b = R"(
/// #include "header_a.hpp"
#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.target().name() == include.name());
REQUIRE(include.include_kind() == cppast::cpp_include_kind::system); 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") else if (include.name() == "cpp_include_directive-header.hpp")
{ {
REQUIRE(include.target().name() == include.name()); REQUIRE(include.target().name() == include.name());
REQUIRE(include.include_kind() == cppast::cpp_include_kind::local); REQUIRE(include.include_kind() == cppast::cpp_include_kind::local);
REQUIRE(!include.target().get(idx)); REQUIRE(include.target().get(idx).empty());
} }
else else
REQUIRE(false); REQUIRE(false);
}); });
REQUIRE(count == 2u); REQUIRE(count == 2u);
count = test_visit<cpp_include_directive>(*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") TEST_CASE("comment matching")
{ {