Fix external macro use when fast_preprocessing=false

Forget to add standard library includes...

Fixes #39.
This commit is contained in:
Jonathan Müller 2018-02-17 15:31:03 +01:00
commit cc5127979b
3 changed files with 226 additions and 191 deletions

View file

@ -185,6 +185,7 @@ namespace
} }
// get the command that preprocess a translation unit given the macros // get the command that preprocess a translation unit given the macros
// macro_file_path == nullptr <=> don't do fast preprocessing
std::string get_preprocess_command(const libclang_compile_config& c, const char* full_path, std::string get_preprocess_command(const libclang_compile_config& c, const char* full_path,
const char* macro_file_path) const char* macro_file_path)
{ {
@ -192,11 +193,16 @@ namespace
// -E: print preprocessor output // -E: print preprocessor output
// -CC: keep comments, even in macro // -CC: keep comments, even in macro
// -dD: keep macros // -dD: keep macros
// -no*: disable default include search paths auto flags = std::string("-x c++ -E -CC -dD");
auto flags = std::string("-x c++ -E -CC -dD -nostdinc -nostdinc++");
if (macro_file_path)
// -no*: disable default include search paths
flags += " -nostdinc -nostdinc++";
if (detail::libclang_compile_config_access::clang_version(c) >= 40000) if (detail::libclang_compile_config_access::clang_version(c) >= 40000)
// -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";
flags += diagnostics_flags(); flags += diagnostics_flags();
if (macro_file_path) if (macro_file_path)
@ -209,13 +215,14 @@ namespace
std::string cmd(detail::libclang_compile_config_access::clang_binary(c) + " " std::string cmd(detail::libclang_compile_config_access::clang_binary(c) + " "
+ std::move(flags) + " "); + std::move(flags) + " ");
// other flags, as long as they don't add include directories (if we're doing the single file optimization) // other flags
for (const auto& flag : detail::libclang_compile_config_access::flags(c)) for (const auto& flag : detail::libclang_compile_config_access::flags(c))
{ {
DEBUG_ASSERT(flag.size() > 2u && flag[0] == '-', detail::assert_handler{}, DEBUG_ASSERT(flag.size() > 2u && flag[0] == '-', detail::assert_handler{},
"that's an odd flag"); "that's an odd flag");
if (macro_file_path && (flag[0] != '-' || flag[1] != 'I')) if (!macro_file_path || flag[1] != 'I')
{ {
// only add this flag if it is not an include or we're not doing fast preprocessing
cmd += flag; cmd += flag;
cmd += ' '; cmd += ' ';
} }
@ -431,6 +438,10 @@ namespace
clang_preprocess_result clang_preprocess(const libclang_compile_config& c, clang_preprocess_result clang_preprocess(const libclang_compile_config& c,
const char* full_path, const diagnostic_logger& logger) const char* full_path, const diagnostic_logger& logger)
{ {
// if we're fast preprocessing we only preprocess the main file, not includes
// this is done by disabling all include search paths when doing the preprocessing
// to allow macros a separate preprocessing with the -dM flag is done that extracts all macros
// they are then manually defined before
auto fast_preprocessing = detail::libclang_compile_config_access::fast_preprocessing(c); auto fast_preprocessing = detail::libclang_compile_config_access::fast_preprocessing(c);
auto macro_file = fast_preprocessing ? write_macro_file(c, full_path, logger) : ""; auto macro_file = fast_preprocessing ? write_macro_file(c, full_path, logger) : "";
@ -706,7 +717,7 @@ namespace
if (starts_with(p, "*/")) if (starts_with(p, "*/"))
// empty comment // empty comment
p.skip(2u); p.skip(2u);
else if (starts_with(p, "*") || starts_with(p, "!")) else if (p.write_enabled() && (starts_with(p, "*") || starts_with(p, "!")))
{ {
// doc comment // doc comment
p.skip(); p.skip();
@ -771,14 +782,14 @@ namespace
return false; return false;
p.skip(2u); p.skip(2u);
if (starts_with(p, "/") || starts_with(p, "!")) if (p.write_enabled() && (starts_with(p, "/") || starts_with(p, "!")))
{ {
// C++ style doc comment // C++ style doc comment
p.skip(); p.skip();
auto comment = parse_cpp_doc_comment(p, false); auto comment = parse_cpp_doc_comment(p, false);
merge_or_add(output, std::move(comment)); merge_or_add(output, std::move(comment));
} }
else if (starts_with(p, "<")) else if (p.write_enabled() && starts_with(p, "<"))
{ {
// end of line doc comment // end of line doc comment
p.skip(); p.skip();
@ -903,6 +914,9 @@ namespace
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
if (!p.write_enabled())
return type_safe::nullopt;
if (filename.size() > 2u && filename[0] == '.' if (filename.size() > 2u && filename[0] == '.'
&& (filename[1] == '/' || filename[1] == '\\')) && (filename[1] == '/' || filename[1] == '\\'))
filename = filename.substr(2); filename = filename.substr(2);
@ -1083,7 +1097,10 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
else if (lm.value().flag == linemarker::enter_old) else if (lm.value().flag == linemarker::enter_old)
{ {
if (lm.value().file == path) if (lm.value().file == path)
{
p.enable_write(); p.enable_write();
p.set_line(lm.value().line);
}
} }
else if (lm.value().flag == linemarker::line_directive && p.write_enabled()) else if (lm.value().flag == linemarker::line_directive && p.write_enabled())
{ {

View file

@ -136,185 +136,3 @@ b
}); });
REQUIRE(count == 1u); REQUIRE(count == 1u);
} }
TEST_CASE("preprocessor line numbers")
{
auto code = R"(/// 1
#include <iostream>
/// 5
#include <string>
int var;
/// 11
#define foo \
\
\
int \
main()
/// 19
foo {}
/// 23
/* C comment
spanning
multiple
lines
*/
/**
*/
#include <vector>
/// 37
)";
auto file = parse({}, "preprocessor_line_numbers.cpp", code);
for (auto& comment : file->unmatched_comments())
{
if (comment.content[0] != '\n')
REQUIRE(comment.line == std::stoi(comment.content));
}
REQUIRE((file->unmatched_comments().size() == 6u + 1u));
}
TEST_CASE("comment content")
{
auto code = R"(
/// simple comment
///no space
/// multi
/// line
/// comment
/** C comment */
/**C comment no space*/
/** Multiline
C
comment
with indent */
/** Multiline
C
comment
with
indent */
/** Multiline
* C
* comment
* with
* indent
* star */
)";
auto file = parse({}, "comment-content.cpp", code);
auto comments = file->unmatched_comments();
REQUIRE((comments.size() == 8u));
REQUIRE(comments[0u].content == "simple comment");
REQUIRE(comments[1u].content == "no space");
REQUIRE(comments[2u].content == "multi\nline\ncomment");
REQUIRE(comments[3u].content == "C comment");
REQUIRE(comments[4u].content == "C comment no space");
REQUIRE(comments[5u].content == "Multiline\nC\n comment\nwith indent");
REQUIRE(comments[6u].content == "Multiline\nC\n comment\n with\n indent");
REQUIRE(comments[7u].content == "Multiline\nC\ncomment\nwith\nindent\nstar");
}
TEST_CASE("comment matching")
{
auto code = R"(
/// u
/// a
/// a
struct a {};
/// u
/** b
* b */
void b(int, float)
{
auto c = '#';
}
/** u */
//! c
/// c
enum class c
{
d, //< d
/// d
e, //< e
/// e
/** f
f **/
f,
};
/// g
/// g
#define g(name) \
class name \
{ \
};
/// h
/// h
g(h)
/*! i
i */
using i = int;
/// cstddef
/// cstddef
#include <cstddef>
/// j
/// j
template <typename T/**/>
void j();
)";
auto file = parse({}, "comment-matching.cpp", code);
visit(*file, [&](const cpp_entity& e, visitor_info) {
if (e.kind() == cpp_entity_kind::file_t)
return true;
else if (e.name().empty())
return true;
else if (is_templated(e))
return true;
INFO(e.name());
REQUIRE(e.comment());
REQUIRE(e.comment().value() == e.name() + "\n" + e.name());
return true;
});
auto add = 0u;
for (auto& comment : file->unmatched_comments())
{
if (comment.content == "cstddef\ncstddef")
// happens if include parsing is not supported
// error is still going to be detected because if it is supported, the entity will be matched above
add = 1u;
else
REQUIRE(comment.content == "u");
}
REQUIRE((file->unmatched_comments().size() == 3u + add));
}

View file

@ -4,9 +4,11 @@
#include "libclang/preprocessor.hpp" #include "libclang/preprocessor.hpp"
#include "test_parser.hpp" #include "test_parser.hpp"
#include <cppast/cpp_variable.hpp>
using namespace cppast; using namespace cppast;
TEST_CASE("preprocessor_parses_escaped_character", "[!hide][clang4]") TEST_CASE("preprocessor escaped character", "[!hide][clang4]")
{ {
write_file("ppec.hpp", R"( write_file("ppec.hpp", R"(
)"); )");
@ -33,9 +35,25 @@ TEST_CASE("preprocessor_parses_escaped_character", "[!hide][clang4]")
REQUIRE(preprocessed.includes[0].file_name == "ppec.hpp"); REQUIRE(preprocessed.includes[0].file_name == "ppec.hpp");
} }
TEST_CASE("preprocessing use external macro")
{
auto file = parse({}, "preprocessing_external_macro.cpp", R"(
#include <cmath>
#ifdef _GLIBCXX_RELEASE
// this requires libstdc++
/// auto result=(__builtin_nanf(""));
auto result = NAN;
#endif
)");
test_visit<cpp_variable>(*file, [&](const cpp_variable&) {});
}
TEST_CASE("fast_preprocessing include guard") TEST_CASE("fast_preprocessing include guard")
{ {
auto file_name = "fast_preprocessing_include_guard"; auto file_name = "fast_preprocessing_include_guard.hpp";
write_file(file_name, R"( write_file(file_name, R"(
// This is a C++ comment that should get skipped. // This is a C++ comment that should get skipped.
@ -63,3 +81,185 @@ struct foo {};
REQUIRE(result.macros.size() == 1u); REQUIRE(result.macros.size() == 1u);
REQUIRE(result.macros[0].macro->name() == "INCLUDE_GUARD"); REQUIRE(result.macros[0].macro->name() == "INCLUDE_GUARD");
} }
TEST_CASE("preprocessor line numbers")
{
auto code = R"(/// 1
#include <iostream>
/// 5
#include <string>
int var;
/// 11
#define foo \
\
\
int \
main()
/// 19
foo {}
/// 23
/* C comment
spanning
multiple
lines
*/
/**
*/
#include <vector>
/// 37
)";
auto file = parse({}, "preprocessor_line_numbers.cpp", code);
for (auto& comment : file->unmatched_comments())
{
if (comment.content[0] != '\n')
REQUIRE(comment.line == std::stoi(comment.content));
}
REQUIRE((file->unmatched_comments().size() == 6u + 1u));
}
TEST_CASE("comment content")
{
auto code = R"(
/// simple comment
///no space
/// multi
/// line
/// comment
/** C comment */
/**C comment no space*/
/** Multiline
C
comment
with indent */
/** Multiline
C
comment
with
indent */
/** Multiline
* C
* comment
* with
* indent
* star */
)";
auto file = parse({}, "comment-content.cpp", code);
auto comments = file->unmatched_comments();
REQUIRE((comments.size() == 8u));
REQUIRE(comments[0u].content == "simple comment");
REQUIRE(comments[1u].content == "no space");
REQUIRE(comments[2u].content == "multi\nline\ncomment");
REQUIRE(comments[3u].content == "C comment");
REQUIRE(comments[4u].content == "C comment no space");
REQUIRE(comments[5u].content == "Multiline\nC\n comment\nwith indent");
REQUIRE(comments[6u].content == "Multiline\nC\n comment\n with\n indent");
REQUIRE(comments[7u].content == "Multiline\nC\ncomment\nwith\nindent\nstar");
}
TEST_CASE("comment matching")
{
auto code = R"(
/// u
/// a
/// a
struct a {};
/// u
/** b
* b */
void b(int, float)
{
auto c = '#';
}
/** u */
//! c
/// c
enum class c
{
d, //< d
/// d
e, //< e
/// e
/** f
f **/
f,
};
/// g
/// g
#define g(name) \
class name \
{ \
};
/// h
/// h
g(h)
/*! i
i */
using i = int;
/// cstddef
/// cstddef
#include <cstddef>
/// j
/// j
template <typename T/**/>
void j();
)";
auto file = parse({}, "comment-matching.cpp", code);
visit(*file, [&](const cpp_entity& e, visitor_info) {
if (e.kind() == cpp_entity_kind::file_t)
return true;
else if (e.name().empty())
return true;
else if (is_templated(e))
return true;
INFO(e.name());
REQUIRE(e.comment());
REQUIRE(e.comment().value() == e.name() + "\n" + e.name());
return true;
});
auto add = 0u;
for (auto& comment : file->unmatched_comments())
{
if (comment.content == "cstddef\ncstddef")
// happens if include parsing is not supported
// error is still going to be detected because if it is supported, the entity will be matched above
add = 1u;
else
REQUIRE(comment.content == "u");
}
REQUIRE((file->unmatched_comments().size() == 3u + add));
}