diff --git a/src/libclang/preprocessor.cpp b/src/libclang/preprocessor.cpp index 48bb2f9..797d0b7 100644 --- a/src/libclang/preprocessor.cpp +++ b/src/libclang/preprocessor.cpp @@ -185,6 +185,7 @@ namespace } // 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, const char* macro_file_path) { @@ -192,11 +193,16 @@ namespace // -E: print preprocessor output // -CC: keep comments, even in macro // -dD: keep macros - // -no*: disable default include search paths - auto flags = std::string("-x c++ -E -CC -dD -nostdinc -nostdinc++"); + auto flags = std::string("-x c++ -E -CC -dD"); + + if (macro_file_path) + // -no*: disable default include search paths + flags += " -nostdinc -nostdinc++"; + if (detail::libclang_compile_config_access::clang_version(c) >= 40000) // -Xclang -dI: print include directives as well (clang >= 4.0.0) flags += " -Xclang -dI"; + flags += diagnostics_flags(); if (macro_file_path) @@ -209,13 +215,14 @@ namespace std::string cmd(detail::libclang_compile_config_access::clang_binary(c) + " " + 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)) { DEBUG_ASSERT(flag.size() > 2u && flag[0] == '-', detail::assert_handler{}, "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 += ' '; } @@ -431,6 +438,10 @@ namespace clang_preprocess_result clang_preprocess(const libclang_compile_config& c, 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 macro_file = fast_preprocessing ? write_macro_file(c, full_path, logger) : ""; @@ -706,7 +717,7 @@ namespace if (starts_with(p, "*/")) // empty comment p.skip(2u); - else if (starts_with(p, "*") || starts_with(p, "!")) + else if (p.write_enabled() && (starts_with(p, "*") || starts_with(p, "!"))) { // doc comment p.skip(); @@ -771,14 +782,14 @@ namespace return false; p.skip(2u); - if (starts_with(p, "/") || starts_with(p, "!")) + if (p.write_enabled() && (starts_with(p, "/") || starts_with(p, "!"))) { // C++ style doc comment p.skip(); auto comment = parse_cpp_doc_comment(p, false); 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 p.skip(); @@ -903,6 +914,9 @@ namespace DEBUG_ASSERT(starts_with(p, "\n"), detail::assert_handler{}); // don't skip newline + if (!p.write_enabled()) + return type_safe::nullopt; + if (filename.size() > 2u && filename[0] == '.' && (filename[1] == '/' || filename[1] == '\\')) 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) { if (lm.value().file == path) + { p.enable_write(); + p.set_line(lm.value().line); + } } else if (lm.value().flag == linemarker::line_directive && p.write_enabled()) { diff --git a/test/cpp_preprocessor.cpp b/test/cpp_preprocessor.cpp index 9805099..0113ebb 100644 --- a/test/cpp_preprocessor.cpp +++ b/test/cpp_preprocessor.cpp @@ -136,185 +136,3 @@ b }); REQUIRE(count == 1u); } - -TEST_CASE("preprocessor line numbers") -{ - auto code = R"(/// 1 - -#include - -/// 5 - -#include - -int var; - -/// 11 - -#define foo \ - \ - \ - int \ - main() - -/// 19 - -foo {} - -/// 23 - -/* C comment -spanning -multiple -lines -*/ - -/** - -*/ - -#include - -/// 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 - -/// j -/// j -template -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)); -} diff --git a/test/preprocessor.cpp b/test/preprocessor.cpp index c7c5627..7886878 100644 --- a/test/preprocessor.cpp +++ b/test/preprocessor.cpp @@ -4,9 +4,11 @@ #include "libclang/preprocessor.hpp" #include "test_parser.hpp" +#include + using namespace cppast; -TEST_CASE("preprocessor_parses_escaped_character", "[!hide][clang4]") +TEST_CASE("preprocessor escaped character", "[!hide][clang4]") { 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"); } +TEST_CASE("preprocessing use external macro") +{ + auto file = parse({}, "preprocessing_external_macro.cpp", R"( +#include +#ifdef _GLIBCXX_RELEASE + +// this requires libstdc++ +/// auto result=(__builtin_nanf("")); +auto result = NAN; + +#endif +)"); + + test_visit(*file, [&](const cpp_variable&) {}); +} + 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"( // This is a C++ comment that should get skipped. @@ -63,3 +81,185 @@ struct foo {}; REQUIRE(result.macros.size() == 1u); REQUIRE(result.macros[0].macro->name() == "INCLUDE_GUARD"); } + +TEST_CASE("preprocessor line numbers") +{ + auto code = R"(/// 1 + +#include + +/// 5 + +#include + +int var; + +/// 11 + +#define foo \ + \ + \ + int \ + main() + +/// 19 + +foo {} + +/// 23 + +/* C comment +spanning +multiple +lines +*/ + +/** + +*/ + +#include + +/// 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 + +/// j +/// j +template +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)); +}