Ensure matching line numbers in preprocessed file

This commit is contained in:
Jonathan Müller 2017-10-11 10:15:09 +02:00
commit 2777e396e6
7 changed files with 106 additions and 35 deletions

View file

@ -13,6 +13,18 @@
namespace cppast namespace cppast
{ {
/// An unmatched documentation comment.
struct cpp_doc_comment
{
std::string content;
unsigned line;
cpp_doc_comment(std::string content, unsigned line)
: content(std::move(content)), line(line)
{
}
};
/// A [cppast::cpp_entity]() modelling a file. /// A [cppast::cpp_entity]() modelling a file.
/// ///
/// This is the top-level entity of the AST. /// This is the top-level entity of the AST.
@ -26,9 +38,7 @@ namespace cppast
{ {
public: public:
/// \effects Sets the file name. /// \effects Sets the file name.
explicit builder(std::string name) : file_(new cpp_file(std::move(name))) explicit builder(std::string name) : file_(new cpp_file(std::move(name))) {}
{
}
/// \effects Adds an entity. /// \effects Adds an entity.
void add_child(std::unique_ptr<cpp_entity> child) noexcept void add_child(std::unique_ptr<cpp_entity> child) noexcept
@ -37,9 +47,9 @@ namespace cppast
} }
/// \effects Adds an unmatched documentation comment. /// \effects Adds an unmatched documentation comment.
void add_unmatched_comment(std::string str) void add_unmatched_comment(cpp_doc_comment comment)
{ {
file_->comments_.push_back(std::move(str)); file_->comments_.push_back(std::move(comment));
} }
/// \returns The not yet finished file. /// \returns The not yet finished file.
@ -62,20 +72,18 @@ namespace cppast
}; };
/// \returns The unmatched documentation comments. /// \returns The unmatched documentation comments.
type_safe::array_ref<const std::string> unmatched_comments() const noexcept type_safe::array_ref<const cpp_doc_comment> unmatched_comments() const noexcept
{ {
return type_safe::ref(comments_.data(), comments_.size()); return type_safe::ref(comments_.data(), comments_.size());
} }
private: private:
cpp_file(std::string name) : cpp_entity(std::move(name)) cpp_file(std::string name) : cpp_entity(std::move(name)) {}
{
}
/// \returns [cpp_entity_type::file_t](). /// \returns [cpp_entity_type::file_t]().
cpp_entity_kind do_get_entity_kind() const noexcept override; cpp_entity_kind do_get_entity_kind() const noexcept override;
std::vector<std::string> comments_; std::vector<cpp_doc_comment> comments_;
}; };
/// \exclude /// \exclude

View file

@ -403,9 +403,8 @@ namespace
auto args = get_arguments(config); auto args = get_arguments(config);
CXTranslationUnit tu; CXTranslationUnit tu;
auto flags = CXTranslationUnit_Incomplete | CXTranslationUnit_KeepGoing; auto flags = CXTranslationUnit_Incomplete | CXTranslationUnit_KeepGoing
if (detail::libclang_compile_config_access::clang_version(config) >= 40000) | CXTranslationUnit_DetailedPreprocessingRecord;
flags |= CXTranslationUnit_DetailedPreprocessingRecord;
auto error = auto error =
clang_parseTranslationUnit2(idx.get(), path, // index and path clang_parseTranslationUnit2(idx.get(), path, // index and path
@ -479,17 +478,20 @@ std::unique_ptr<cpp_file> libclang_parser::do_parse(const cpp_entity_index& idx,
detail::visit_tu(tu, path.c_str(), [&](const CXCursor& cur) { detail::visit_tu(tu, path.c_str(), [&](const CXCursor& cur) {
if (clang_getCursorKind(cur) == CXCursor_InclusionDirective) if (clang_getCursorKind(cur) == CXCursor_InclusionDirective)
{ {
DEBUG_ASSERT(include_iter != preprocessed.includes.end() if (!preprocessed.includes.empty())
&& get_line_no(cur) >= include_iter->line, {
detail::assert_handler{}); DEBUG_ASSERT(include_iter != preprocessed.includes.end()
&& get_line_no(cur) >= include_iter->line,
detail::assert_handler{});
auto include = auto include =
cpp_include_directive::build(std::move(include_iter->file), include_iter->kind, cpp_include_directive::build(std::move(include_iter->file), include_iter->kind,
detail::get_cursor_name(cur).c_str()); detail::get_cursor_name(cur).c_str());
context.comments.match(*include, include_iter->line); context.comments.match(*include, include_iter->line);
builder.add_child(std::move(include)); builder.add_child(std::move(include));
++include_iter; ++include_iter;
}
} }
else if (clang_getCursorKind(cur) != CXCursor_MacroDefinition) else if (clang_getCursorKind(cur) != CXCursor_MacroDefinition)
{ {
@ -510,7 +512,7 @@ std::unique_ptr<cpp_file> libclang_parser::do_parse(const cpp_entity_index& idx,
for (auto& c : preprocessed.comments) for (auto& c : preprocessed.comments)
{ {
if (!c.comment.empty()) if (!c.comment.empty())
builder.add_unmatched_comment(std::move(c.comment)); builder.add_unmatched_comment(cpp_doc_comment(std::move(c.comment), c.line));
} }
if (context.error) if (context.error)

View file

@ -19,7 +19,13 @@ namespace cppast
{ {
inline source_location make_location(const CXCursor& cur) inline source_location make_location(const CXCursor& cur)
{ {
return source_location::make_entity(get_display_name(cur).c_str()); auto loc = clang_getCursorLocation(cur);
CXString file;
unsigned line;
clang_getPresumedLocation(loc, &file, &line, nullptr);
return source_location::make_file(cxstring(file).c_str(), line);
} }
inline source_location make_location(const CXType& type) inline source_location make_location(const CXType& type)

View file

@ -80,7 +80,7 @@ void detail::comment_context::match(cpp_entity& e, const CXCursor& cur) const
{ {
auto pos = clang_getRangeStart(clang_getCursorExtent(cur)); auto pos = clang_getRangeStart(clang_getCursorExtent(cur));
unsigned line; unsigned line;
clang_getSpellingLocation(pos, nullptr, &line, nullptr, nullptr); clang_getPresumedLocation(pos, nullptr, &line, nullptr);
match(e, line); match(e, line);
} }

View file

@ -30,6 +30,11 @@ namespace
return '"' + std::move(str) + '"'; return '"' + std::move(str) + '"';
} }
bool support_include(const libclang_compile_config& c)
{
return detail::libclang_compile_config_access::clang_version(c) >= 40000;
}
// build the command that runs the preprocessor // build the command that runs the preprocessor
std::string get_command(const libclang_compile_config& c, const char* full_path) std::string get_command(const libclang_compile_config& c, const char* full_path)
{ {
@ -39,7 +44,7 @@ namespace
// -C: keep comments // -C: keep comments
// -dD: print macro definitions as well // -dD: print macro definitions as well
auto flags = std::string("-x c++ -I. -E -C -dD"); auto flags = std::string("-x c++ -I. -E -C -dD");
if (detail::libclang_compile_config_access::clang_version(c) >= 40000) if (support_include(c))
// -Xclang -dI: print include directives as well (clang >= 4.0.0) // -Xclang -dI: print include directives as well (clang >= 4.0.0)
flags += " -Xclang -dI"; 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
@ -191,6 +196,12 @@ namespace
{ {
} }
void set_line(unsigned line)
{
*result_ += "#line " + std::to_string(line) + "\n";
cur_line_ = line;
}
void write_str(std::string str) void write_str(std::string str)
{ {
if (write_ == false) if (write_ == false)
@ -363,7 +374,7 @@ namespace
break; break;
} }
} }
p.skip(); skip(p, "\n");
return result; return result;
} }
@ -691,7 +702,8 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
{ {
auto message = detail::format("parsing macro '", macro->name(), "'"); auto message = detail::format("parsing macro '", macro->name(), "'");
logger.log("preprocessor", logger.log("preprocessor",
diagnostic{std::move(message), source_location::make_file(path), diagnostic{std::move(message),
source_location::make_file(path, p.cur_line()),
severity::debug}); severity::debug});
} }
@ -705,7 +717,8 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
{ {
auto message = detail::format("undefining macro '", undef.value(), "'"); auto message = detail::format("undefining macro '", undef.value(), "'");
logger.log("preprocessor", logger.log("preprocessor",
diagnostic{std::move(message), source_location::make_file(path), diagnostic{std::move(message),
source_location::make_file(path, p.cur_line()),
severity::debug}); severity::debug});
} }
result.macros.erase(std::remove_if(result.macros.begin(), result.macros.end(), result.macros.erase(std::remove_if(result.macros.begin(), result.macros.end(),
@ -722,7 +735,8 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
auto message = auto message =
detail::format("parsing include '", include.value().file.name(), "'"); detail::format("parsing include '", include.value().file.name(), "'");
logger.log("preprocessor", logger.log("preprocessor",
diagnostic{std::move(message), source_location::make_file(path), diagnostic{std::move(message),
source_location::make_file(path, p.cur_line()),
severity::debug}); severity::debug});
} }
result.includes.push_back(std::move(include.value())); result.includes.push_back(std::move(include.value()));
@ -734,16 +748,19 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
switch (lm.value().flag) switch (lm.value().flag)
{ {
case linemarker::line_directive: case linemarker::line_directive:
break; // ignore if (file_depth == 0u)
// no need to handle it, preprocessed output doesn't need to match line numbers precisely p.set_line(lm.value().line);
break;
case linemarker::enter_new: case linemarker::enter_new:
if (file_depth == 0u && lm.value().file.front() != '<') if (file_depth == 0u && lm.value().file.front() != '<')
{ {
// this file is directly included by the given file // this file is directly included by the given file
// and it is not a fake file like builtin or command line
// write include with full path // write include with full path
// note: don't build include here, do it when an #include is encountered
p.write_str("#include \"" + lm.value().file + "\"\n"); p.write_str("#include \"" + lm.value().file + "\"\n");
// note: don't build include here, do it when an #include is encountered
} }
++file_depth; ++file_depth;
@ -755,6 +772,7 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
if (file_depth == 0u) if (file_depth == 0u)
{ {
DEBUG_ASSERT(lm.value().file == path, detail::assert_handler{}); DEBUG_ASSERT(lm.value().file == path, detail::assert_handler{});
p.set_line(lm.value().line);
p.enable_write(); p.enable_write();
} }
break; break;

View file

@ -553,7 +553,7 @@ namespace
{ {
auto msg = detail::format("unexpected type of kind '", auto msg = detail::format("unexpected type of kind '",
detail::get_type_kind_spelling(type).c_str(), "'"); detail::get_type_kind_spelling(type).c_str(), "'");
auto location = source_location::make_entity(get_type_spelling(type).c_str()); auto location = detail::make_location(type);
context.logger->log("libclang parser", diagnostic{msg, location, severity::warning}); context.logger->log("libclang parser", diagnostic{msg, location, severity::warning});
} }
// fallthrough // fallthrough

View file

@ -136,6 +136,43 @@ TEST_CASE("cpp_include_directive", "[!hide][clang4]")
REQUIRE(count == 1u); REQUIRE(count == 1u);
} }
TEST_CASE("preprocessor line numbers")
{
auto code = R"(/// 1
#include <iostream>
/// 5
#include <string>
int foo;
/// 11
#define foo \
\
\
int \
main()
/// 19
foo {}
/// 23
#include <vector>
/// 27
)";
auto file = parse({}, "preprocessor_line_numbers.cpp", code);
for (auto& comment : file->unmatched_comments())
REQUIRE(comment.line == std::stoi(comment.content));
REQUIRE((file->unmatched_comments().size() == 6u));
}
TEST_CASE("comment matching") TEST_CASE("comment matching")
{ {
auto code = R"( auto code = R"(
@ -205,6 +242,6 @@ void j();
}); });
for (auto& comment : file->unmatched_comments()) for (auto& comment : file->unmatched_comments())
REQUIRE(comment == "u"); REQUIRE(comment.content == "u");
REQUIRE((file->unmatched_comments().size() == 3u)); REQUIRE((file->unmatched_comments().size() == 3u));
} }