From c2b86d1cade57ec1f41c8caaef4a6530d45051e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Wed, 21 Feb 2018 16:03:25 +0100 Subject: [PATCH] Fix full path of includes --- include/cppast/libclang_parser.hpp | 2 + src/libclang/libclang_parser.cpp | 7 ++-- src/libclang/preprocessor.cpp | 60 ++++++++++++++++++++++++++++-- src/libclang/preprocessor.hpp | 2 +- test/cpp_preprocessor.cpp | 4 +- test/preprocessor.cpp | 45 ++++++++++++++++++++-- test/test_parser.hpp | 9 +++-- 7 files changed, 113 insertions(+), 16 deletions(-) diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index 4e4df82..6efcee7 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -137,6 +137,8 @@ namespace cppast /// Later stages will use the includes again. /// This hack breaks if you define the same macro multiple times in the file being parsed (headers don't matter) /// or you rely on the order of macro directives. + /// \notes If this option is `true`, the full file name of include directives is not available, + /// just the name as written in the source code. void fast_preprocessing(bool b) noexcept { fast_preprocessing_ = b; diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 5c2c78c..10730cf 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -516,8 +516,9 @@ std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, && get_line_no(cur) >= include_iter->line, detail::assert_handler{}); - // create an include directive - auto full_path = detail::get_cursor_name(cur); + auto full_path = include_iter->full_path.empty() ? include_iter->file_name : + include_iter->full_path; + // if we got an absolute file path for the current file, // also use an absolute file path for the id // otherwise just use the file name as written in the source file @@ -531,7 +532,7 @@ std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, auto include = cpp_include_directive::build(cpp_file_ref(id, std::move(include_iter->file_name)), - include_iter->kind, full_path.std_str()); + include_iter->kind, std::move(full_path)); context.comments.match(*include, include_iter->line, false); // must not skip comments, // includes are not reported in order diff --git a/src/libclang/preprocessor.cpp b/src/libclang/preprocessor.cpp index 9effa6f..6b3ecb6 100644 --- a/src/libclang/preprocessor.cpp +++ b/src/libclang/preprocessor.cpp @@ -9,8 +9,10 @@ #include #include #include -#include #include +#include + +#include #include @@ -941,7 +943,7 @@ namespace && (filename[1] == '/' || filename[1] == '\\')) filename = filename.substr(2); - return detail::pp_include{std::move(filename), include_kind, p.cur_line()}; + return detail::pp_include{std::move(filename), "", include_kind, p.cur_line()}; } bool bump_pragma(position& p) @@ -1035,7 +1037,8 @@ namespace detail::preprocessor_output detail::preprocess(const libclang_compile_config& config, const char* path, const diagnostic_logger& logger) { - detail::preprocessor_output result; + detail::preprocessor_output result; + std::unordered_map indirect_includes; auto preprocessed = clang_preprocess(config, path, logger); @@ -1113,7 +1116,31 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co else if (auto lm = parse_linemarker(p)) { if (lm.value().flag == linemarker::enter_new) + { + if (p.write_enabled()) + { + // this is a direct include, update the full path of the last include + DEBUG_ASSERT(!result.includes.empty() + && result.includes.back().full_path.empty() + && lm.value().file.find(result.includes.back().file_name) + != std::string::npos, + detail::assert_handler{}); + result.includes.back().full_path = lm.value().file; + } + else + { + // this is an indirect include, remember it to get full path for indirect includes + auto& full_path = lm.value().file; + + auto last_dir = full_path.find_last_of("/\\"); + auto file_name = + last_dir == std::string::npos ? full_path : full_path.substr(last_dir + 1u); + + indirect_includes.emplace(std::move(file_name), full_path); + } + p.disable_write(); + } else if (lm.value().flag == linemarker::enter_old) { if (lm.value().file == path) @@ -1159,8 +1186,33 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co if (result.includes.empty()) { // add headers from diagnostics w/o line information + // only needed for older clangs for (auto name : preprocessed.included_files) - result.includes.push_back(pp_include{name, cpp_include_kind::local, 1u}); + result.includes.push_back(pp_include{name, "", cpp_include_kind::local, 1u}); + } + + // get full path for indirect includes + // doesn't work if fast preprocessing + if (!detail::libclang_compile_config_access::fast_preprocessing(config)) + { + for (auto& include : result.includes) + if (include.full_path.empty()) + { + auto last_sep = include.file_name.find_last_of("/\\"); + + auto iter = indirect_includes.find(last_sep == std::string::npos ? + include.file_name : + include.file_name.substr(last_sep + 1u)); + if (iter != indirect_includes.end()) + include.full_path = iter->second; + else + logger.log("preprocessor", + format_diagnostic(severity::warning, + source_location::make_file(path, include.line), + "unable to retrieve full path for include '", + include.file_name, + "' (please file a bug report)")); + } } return result; diff --git a/src/libclang/preprocessor.hpp b/src/libclang/preprocessor.hpp index e09015b..5b7969f 100644 --- a/src/libclang/preprocessor.hpp +++ b/src/libclang/preprocessor.hpp @@ -20,7 +20,7 @@ namespace cppast struct pp_include { - std::string file_name; + std::string file_name, full_path; cpp_include_kind kind; unsigned line; }; diff --git a/test/cpp_preprocessor.cpp b/test/cpp_preprocessor.cpp index 0113ebb..f41015d 100644 --- a/test/cpp_preprocessor.cpp +++ b/test/cpp_preprocessor.cpp @@ -115,7 +115,7 @@ b REQUIRE(include.target().name() == include.name()); REQUIRE(include.include_kind() == cppast::cpp_include_kind::local); REQUIRE(include.target().get(idx).empty()); - REQUIRE(include.full_path() == "cpp_include_directive-header.hpp"); + REQUIRE(include.full_path() == "./cpp_include_directive-header.hpp"); } else REQUIRE(false); @@ -129,7 +129,7 @@ b REQUIRE(include.include_kind() == cppast::cpp_include_kind::local); REQUIRE( equal_ref(idx, include.target(), cpp_file_ref(cpp_entity_id(""), "header_a.hpp"))); - REQUIRE(include.full_path() == "header_a.hpp"); + REQUIRE(include.full_path() == "./header_a.hpp"); } else REQUIRE(false); diff --git a/test/preprocessor.cpp b/test/preprocessor.cpp index 5860644..1605443 100644 --- a/test/preprocessor.cpp +++ b/test/preprocessor.cpp @@ -30,6 +30,15 @@ TEST_CASE("preprocessor escaped character", "[!hide][clang4]") libclang_compile_config config; config.set_flags(cpp_standard::cpp_latest); + SECTION("fast") + { + config.fast_preprocessing(true); + } + SECTION("normal") + { + config.fast_preprocessing(false); + } + auto preprocessed = detail::preprocess(config, "ppec.cpp", default_logger().get()); REQUIRE(preprocessed.includes.size() == 1); REQUIRE(preprocessed.includes[0].file_name == "ppec.hpp"); @@ -37,6 +46,16 @@ TEST_CASE("preprocessor escaped character", "[!hide][clang4]") TEST_CASE("preprocessing use external macro") { + bool fast_preprocessing = false; + SECTION("fast_preprocessing") + { + fast_preprocessing = true; + } + SECTION("normal") + { + fast_preprocessing = false; + } + auto file = parse({}, "preprocessing_external_macro.cpp", R"( #include #ifdef _GLIBCXX_RELEASE @@ -46,7 +65,7 @@ TEST_CASE("preprocessing use external macro") auto result = NAN; #endif -)"); +)", fast_preprocessing); test_visit(*file, [&](const cpp_variable&) {}); } @@ -91,6 +110,16 @@ struct foo {}; TEST_CASE("preprocessor line numbers") { + bool fast_preprocessing = false; + SECTION("fast_preprocessing") + { + fast_preprocessing = true; + } + SECTION("normal") + { + fast_preprocessing = false; + } + auto code = R"(/// 1 #include @@ -130,7 +159,7 @@ lines /// 37 )"; - auto file = parse({}, "preprocessor_line_numbers.cpp", code); + auto file = parse({}, "preprocessor_line_numbers.cpp", code, fast_preprocessing); for (auto& comment : file->unmatched_comments()) { if (comment.content[0] != '\n') @@ -188,6 +217,16 @@ with indent */ TEST_CASE("comment matching") { + bool fast_preprocessing = false; + SECTION("fast_preprocessing") + { + fast_preprocessing = true; + } + SECTION("normal") + { + fast_preprocessing = false; + } + auto code = R"( /// u @@ -243,7 +282,7 @@ template void j(); )"; - auto file = parse({}, "comment-matching.cpp", code); + auto file = parse({}, "comment-matching.cpp", code, fast_preprocessing); visit(*file, [&](const cpp_entity& e, visitor_info) { if (e.kind() == cpp_entity_kind::file_t) return true; diff --git a/test/test_parser.hpp b/test/test_parser.hpp index 62591f1..fe6871c 100644 --- a/test/test_parser.hpp +++ b/test/test_parser.hpp @@ -24,12 +24,14 @@ inline void write_file(const char* name, const char* code) } inline std::unique_ptr parse_file(const cppast::cpp_entity_index& idx, - const char* name) + const char* name, + bool fast_preprocessing = false) { using namespace cppast; libclang_compile_config config; config.set_flags(cpp_standard::cpp_latest); + config.fast_preprocessing(fast_preprocessing); libclang_parser p(default_logger()); @@ -40,10 +42,11 @@ inline std::unique_ptr parse_file(const cppast::cpp_entity_ind } inline std::unique_ptr parse(const cppast::cpp_entity_index& idx, - const char* name, const char* code) + const char* name, const char* code, + bool fast_preprocessing = false) { write_file(name, code); - return parse_file(idx, name); + return parse_file(idx, name, fast_preprocessing); } class test_generator : public cppast::code_generator