From fd449dde4a7db53969e295e8c920cedb5c6ec06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Sun, 11 Jun 2017 22:16:56 +0200 Subject: [PATCH] Store full path of header as well --- include/cppast/cpp_preprocessor.hpp | 21 ++- src/libclang/libclang_parser.cpp | 49 +++++-- src/libclang/parse_functions.cpp | 5 + src/libclang/preprocessor.cpp | 206 +++++++++++++--------------- src/libclang/preprocessor.hpp | 16 ++- test/cpp_preprocessor.cpp | 3 + 6 files changed, 169 insertions(+), 131 deletions(-) diff --git a/include/cppast/cpp_preprocessor.hpp b/include/cppast/cpp_preprocessor.hpp index 53a9b77..bcdb62f 100644 --- a/include/cppast/cpp_preprocessor.hpp +++ b/include/cppast/cpp_preprocessor.hpp @@ -80,9 +80,11 @@ namespace cppast /// \notes It is not meant to be registered in the [cppast::cpp_entity_index](), /// as no other [cppast::cpp_entity]() can refer to it. static std::unique_ptr build(const cpp_file_ref& target, - cpp_include_kind kind) + cpp_include_kind kind, + std::string full_path) { - return std::unique_ptr(new cpp_include_directive(target, kind)); + return std::unique_ptr( + new cpp_include_directive(target, kind, std::move(full_path))); } /// \returns A reference to the [cppast::cpp_file]() it includes. @@ -97,17 +99,28 @@ namespace cppast return kind_; } + /// \returns The full path of the included file. + const std::string& full_path() const noexcept + { + return full_path_; + } + private: cpp_entity_kind do_get_entity_kind() const noexcept override; - cpp_include_directive(const cpp_file_ref& target, cpp_include_kind kind) - : cpp_entity(target.name()), target_(target.id()[0u]), kind_(kind) + cpp_include_directive(const cpp_file_ref& target, cpp_include_kind kind, + std::string full_path) + : cpp_entity(target.name()), + target_(target.id()[0u]), + kind_(kind), + full_path_(std::move(full_path)) { DEBUG_ASSERT(!target.is_overloaded(), detail::precondition_error_handler{}); } cpp_entity_id target_; cpp_include_kind kind_; + std::string full_path_; }; } // namespace cppast diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 1174c09..4718e97 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -205,14 +205,16 @@ namespace auto args = get_arguments(config); CXTranslationUnit tu; - auto error = + auto flags = CXTranslationUnit_Incomplete | CXTranslationUnit_KeepGoing; + if (detail::libclang_compile_config_access::clang_version(config) >= 40000) + flags |= CXTranslationUnit_DetailedPreprocessingRecord; + + auto error = clang_parseTranslationUnit2(idx.get(), path, // index and path args.data(), static_cast(args.size()), // arguments (ptr + size) &file, 1, // unsaved files (ptr + size) - CXTranslationUnit_Incomplete - | CXTranslationUnit_KeepGoing, // flags - &tu); + unsigned(flags), &tu); if (error != CXError_Success) { switch (error) @@ -259,25 +261,42 @@ std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, auto file = clang_getFile(tu.get(), path.c_str()); cpp_file::builder builder(path); - auto preprocessed_iter = preprocessed.entities.begin(); + auto macro_iter = preprocessed.macros.begin(); + auto include_iter = preprocessed.includes.begin(); // convert entity hierachies detail::parse_context context{tu.get(), file, type_safe::ref(logger()), type_safe::ref(idx), detail::comment_context(preprocessed.comments)}; detail::visit_tu(tu, path.c_str(), [&](const CXCursor& cur) { - // add macro if needed - for (auto line = get_line_no(cur); - preprocessed_iter != preprocessed.entities.end() && preprocessed_iter->line <= line; - ++preprocessed_iter) - builder.add_child(std::move(preprocessed_iter->entity)); + if (clang_getCursorKind(cur) == CXCursor_InclusionDirective) + { + DEBUG_ASSERT(include_iter != preprocessed.includes.end() + && get_line_no(cur) >= include_iter->line, + detail::assert_handler{}); - auto entity = detail::parse_entity(context, cur); - if (entity) - builder.add_child(std::move(entity)); + auto include = + cpp_include_directive::build(std::move(include_iter->file), include_iter->kind, + detail::get_cursor_name(cur).c_str()); + context.comments.match(*include, include_iter->line); + builder.add_child(std::move(include)); + + ++include_iter; + } + else if (clang_getCursorKind(cur) != CXCursor_MacroDefinition) + { + // add macro if needed + for (auto line = get_line_no(cur); + macro_iter != preprocessed.macros.end() && macro_iter->line <= line; ++macro_iter) + builder.add_child(std::move(macro_iter->macro)); + + auto entity = detail::parse_entity(context, cur); + if (entity) + builder.add_child(std::move(entity)); + } }); - for (; preprocessed_iter != preprocessed.entities.end(); ++preprocessed_iter) - builder.add_child(std::move(preprocessed_iter->entity)); + for (; macro_iter != preprocessed.macros.end(); ++macro_iter) + builder.add_child(std::move(macro_iter->macro)); for (auto& c : preprocessed.comments) { diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index 3e0af7e..6a5e6a4 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -104,6 +104,11 @@ std::unique_ptr detail::parse_entity(const detail::parse_context& co return entity; break; + case CXCursor_MacroDefinition: + case CXCursor_InclusionDirective: + DEBUG_UNREACHABLE(detail::assert_handler{}, "handle preprocessor in parser callback"); + break; + case CXCursor_Namespace: return parse_cpp_namespace(context, cur); case CXCursor_NamespaceAlias: diff --git a/src/libclang/preprocessor.cpp b/src/libclang/preprocessor.cpp index 1a83dd5..4559307 100644 --- a/src/libclang/preprocessor.cpp +++ b/src/libclang/preprocessor.cpp @@ -268,6 +268,89 @@ namespace p.skip(std::strlen(str)); } + void skip_spaces(position& p, bool bump = false) + { + while (starts_with(p, " ")) + if (bump) + p.bump(); + else + p.skip(); + } + + struct linemarker + { + std::string file; + unsigned line; + enum + { + line_directive, // no change in file + enter_new, // open a new file + enter_old, // return to an old file + } flag = line_directive; + bool is_system = false; + }; + + ts::optional parse_linemarker(position& p) + { + // format (at new line): # "" + // flag 1: enter_new + // flag 2: enter_old + // flag 3: system file + // flag 4: ignored + if (!p.was_newl() || !starts_with(p, "#")) + return ts::nullopt; + p.skip(); + DEBUG_ASSERT(!starts_with(p, "define") && !starts_with(p, "undef") + && !starts_with(p, "pragma"), + detail::assert_handler{}, "handle macros first"); + + linemarker result; + + std::string line; + for (skip_spaces(p); std::isdigit(*p.ptr()); p.skip()) + line += *p.ptr(); + result.line = unsigned(std::stoi(line)); + + skip_spaces(p); + DEBUG_ASSERT(*p.ptr() == '"', detail::assert_handler{}); + p.skip(); + + std::string file_name; + for (; !starts_with(p, "\""); p.skip()) + file_name += *p.ptr(); + p.skip(); + result.file = std::move(file_name); + + for (; !starts_with(p, "\n"); p.skip()) + { + skip_spaces(p); + + switch (*p.ptr()) + { + case '1': + DEBUG_ASSERT(result.flag == linemarker::line_directive, detail::assert_handler{}); + result.flag = linemarker::enter_new; + break; + case '2': + DEBUG_ASSERT(result.flag == linemarker::line_directive, detail::assert_handler{}); + result.flag = linemarker::enter_old; + break; + case '3': + result.is_system = true; + break; + case '4': + break; // ignored + + default: + DEBUG_UNREACHABLE(detail::assert_handler{}, "invalid line marker"); + break; + } + } + p.skip(); + + return result; + } + detail::pp_doc_comment parse_c_doc_comment(position& p) { detail::pp_doc_comment result; @@ -423,15 +506,6 @@ namespace return true; } - void skip_spaces(position& p, bool bump = false) - { - while (starts_with(p, " ")) - if (bump) - p.bump(); - else - p.skip(); - } - std::unique_ptr parse_macro(position& p, detail::preprocessor_output& output, bool in_main_file) @@ -504,15 +578,13 @@ namespace return result; } - std::unique_ptr parse_include(position& p, - detail::preprocessor_output& output, - bool in_main_file) + type_safe::optional parse_include(position& p, bool in_main_file) { // format (at new line, literal <>): #include // or: #include "filename" // note: don't write include back if (!p.was_newl() || !starts_with(p, "#include")) - return nullptr; + return type_safe::nullopt; p.skip(std::strlen("#include")); if (starts_with(p, "_next")) p.skip(std::strlen("_next")); @@ -544,16 +616,10 @@ namespace // don't skip newline if (!in_main_file) - return nullptr; + return type_safe::nullopt; - auto result = cpp_include_directive::build(cpp_file_ref(cpp_entity_id(filename), filename), - include_kind); - if (!output.comments.empty() && output.comments.back().matches(*result, p.cur_line())) - { - result->set_comment(std::move(output.comments.back().comment)); - output.comments.pop_back(); - } - return result; + return detail::pp_include{cpp_file_ref(cpp_entity_id(filename), filename), include_kind, + p.cur_line()}; } bool bump_pragma(position& p) @@ -568,80 +634,6 @@ namespace return true; } - - struct linemarker - { - std::string file; - unsigned line; - enum - { - line_directive, // no change in file - enter_new, // open a new file - enter_old, // return to an old file - } flag = line_directive; - bool is_system = false; - }; - - ts::optional parse_linemarker(position& p) - { - // format (at new line): # "" - // flag 1: enter_new - // flag 2: enter_old - // flag 3: system file - // flag 4: ignored - if (!p.was_newl() || !starts_with(p, "#")) - return ts::nullopt; - p.skip(); - DEBUG_ASSERT(!starts_with(p, "define") && !starts_with(p, "undef") - && !starts_with(p, "pragma"), - detail::assert_handler{}, "handle macros first"); - - linemarker result; - - std::string line; - for (skip_spaces(p); std::isdigit(*p.ptr()); p.skip()) - line += *p.ptr(); - result.line = unsigned(std::stoi(line)); - - skip_spaces(p); - DEBUG_ASSERT(*p.ptr() == '"', detail::assert_handler{}); - p.skip(); - - std::string file_name; - for (; !starts_with(p, "\""); p.skip()) - file_name += *p.ptr(); - p.skip(); - result.file = std::move(file_name); - - for (; !starts_with(p, "\n"); p.skip()) - { - skip_spaces(p); - - switch (*p.ptr()) - { - case '1': - DEBUG_ASSERT(result.flag == linemarker::line_directive, detail::assert_handler{}); - result.flag = linemarker::enter_new; - break; - case '2': - DEBUG_ASSERT(result.flag == linemarker::line_directive, detail::assert_handler{}); - result.flag = linemarker::enter_old; - break; - case '3': - result.is_system = true; - break; - case '4': - break; // ignored - - default: - DEBUG_UNREACHABLE(detail::assert_handler{}, "invalid line marker"); - break; - } - } - p.skip(); - - return result; - } } detail::preprocessor_output detail::preprocess(const libclang_compile_config& config, @@ -682,7 +674,7 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co severity::debug}); } - result.entities.push_back({std::move(macro), p.cur_line()}); + result.macros.push_back({std::move(macro), p.cur_line()}); } else if (auto undef = parse_undef(p)) { @@ -695,26 +687,24 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co diagnostic{std::move(message), source_location::make_file(path), severity::debug}); } - result.entities - .erase(std::remove_if(result.entities.begin(), result.entities.end(), - [&](const pp_entity& e) { - return e.entity->kind() - == cpp_entity_kind::macro_definition_t - && e.entity->name() == undef.value(); - }), - result.entities.end()); + result.macros.erase(std::remove_if(result.macros.begin(), result.macros.end(), + [&](const pp_macro& e) { + return e.macro->name() == undef.value(); + }), + result.macros.end()); } } - else if (auto include = parse_include(p, result, file_depth == 0u)) + else if (auto include = parse_include(p, file_depth == 0u)) { if (logger.is_verbose()) { - auto message = detail::format("parsing include '", include->name(), "'"); + auto message = + detail::format("parsing include '", include.value().file.name(), "'"); logger.log("preprocessor", diagnostic{std::move(message), source_location::make_file(path), severity::debug}); } - result.entities.push_back({std::move(include), p.cur_line()}); + result.includes.push_back(std::move(include.value())); } else if (bump_pragma(p)) continue; diff --git a/src/libclang/preprocessor.hpp b/src/libclang/preprocessor.hpp index 130ab54..6fa3640 100644 --- a/src/libclang/preprocessor.hpp +++ b/src/libclang/preprocessor.hpp @@ -12,10 +12,17 @@ namespace cppast { namespace detail { - struct pp_entity + struct pp_macro { - std::unique_ptr entity; - unsigned line; + std::unique_ptr macro; + unsigned line; + }; + + struct pp_include + { + cpp_file_ref file; + cpp_include_kind kind; + unsigned line; }; struct pp_doc_comment @@ -35,7 +42,8 @@ namespace cppast struct preprocessor_output { std::string source; - std::vector entities; + std::vector includes; + std::vector macros; std::vector comments; }; diff --git a/test/cpp_preprocessor.cpp b/test/cpp_preprocessor.cpp index 8c8945d..a96188f 100644 --- a/test/cpp_preprocessor.cpp +++ b/test/cpp_preprocessor.cpp @@ -107,12 +107,14 @@ TEST_CASE("cpp_include_directive", "[!hide][clang4]") REQUIRE(include.target().name() == include.name()); REQUIRE(include.include_kind() == cppast::cpp_include_kind::system); REQUIRE(include.target().get(idx).empty()); + REQUIRE_THAT(include.full_path(), Catch::EndsWith("iostream")); } 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).empty()); + REQUIRE(include.full_path() == "./cpp_include_directive-header.hpp"); } else REQUIRE(false); @@ -126,6 +128,7 @@ TEST_CASE("cpp_include_directive", "[!hide][clang4]") 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"); } else REQUIRE(false);