// Copyright (C) 2017-2018 Jonathan Müller // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. #include "preprocessor.hpp" #include #include #include #include #include #include #include #include #include #include "parse_error.hpp" using namespace cppast; namespace tpl = TinyProcessLib; namespace ts = type_safe; bool detail::pp_doc_comment::matches(const cpp_entity&, unsigned e_line) { if (kind == detail::pp_doc_comment::end_of_line) return line == e_line; else return line + 1u == e_line; } namespace { //=== diagnostic parsing ===// source_location parse_source_location(const char*& ptr) { // format: (): // or: : auto fallback = ptr; std::string filename; while (*ptr && *ptr != ':' && *ptr != '(') filename.push_back(*ptr++); if (filename == "error" || filename == "warning" || filename == "fatal error") { ptr = fallback; return {}; } type_safe::optional line; if (*ptr == '(') { ++ptr; std::string str; while (*ptr != ')') str.push_back(*ptr++); ++ptr; line = unsigned(std::stoi(str)); } DEBUG_ASSERT(*ptr == ':', detail::assert_handler{}); ++ptr; return {type_safe::nullopt, std::move(filename), std::move(line), type_safe::nullopt}; } severity parse_severity(const char*& ptr) { // format: : auto fallback = ptr; std::string sev; while (*ptr && *ptr != ':') sev.push_back(*ptr++); ++ptr; if (sev == "warning") return severity::warning; else if (sev == "error") return severity::error; else if (sev == "fatal error") return severity::critical; else ptr = fallback; return severity::error; } // parse and log diagnostic void log_diagnostic(const diagnostic_logger& logger, const std::string& msg) { auto ptr = msg.c_str(); auto loc = parse_source_location(ptr); while (*ptr == ' ') ++ptr; auto sev = parse_severity(ptr); while (*ptr == ' ') ++ptr; std::string message; while (*ptr && *ptr != '\n') message.push_back(*ptr++); logger.log("preprocessor", diagnostic{std::move(message), std::move(loc), sev}); } // parses missing header file diagnostic and returns the file name, // if it is a missing header file diagnostic ts::optional parse_missing_file(const std::string& cur_file, const std::string& msg) { auto ptr = msg.c_str(); auto loc = parse_source_location(ptr); if (loc.file != cur_file) return type_safe::nullopt; while (*ptr == ' ') ++ptr; parse_severity(ptr); while (*ptr == ' ') ++ptr; // format 'file-name' file not found if (*ptr != '\'') return ts::nullopt; ++ptr; std::string filename; while (*ptr != '\'') filename += *ptr++; ++ptr; if (std::strcmp(ptr, " file not found") == 0) return std::move(filename); else throw libclang_error("preprocessor: unexpected diagnostic '" + msg + "'"); } //=== external preprocessor invocation ==// // quote a string std::string quote(std::string str) { return '"' + std::move(str) + '"'; } std::string diagnostics_flags() { std::string flags; // -fno-caret-diagnostics: don't show the source extract in diagnostics // -fno-show-column: don't show the column number // -fdiagnostics-format msvc: use easier to parse MSVC format flags += " -fno-caret-diagnostics -fno-show-column -fdiagnostics-format=msvc"; // -Wno-*: hide wrong warnings if header file is directly parsed/duplicate macro handling flags += " -Wno-macro-redefined -Wno-pragma-once-outside-header " "-Wno-pragma-system-header-outside-header " "-Wno-include-next-outside-header"; return flags; } // get the command that returns all macros defined in the TU std::string get_macro_command(const libclang_compile_config& c, const char* full_path) { // -x c++: force C++ as input language // -I.: add current working directory to include search path // -E: print preprocessor output // -dM: print macro definitions instead of preprocessed file auto flags = std::string("-x c++ -I. -E -dM"); flags += diagnostics_flags(); std::string cmd(detail::libclang_compile_config_access::clang_binary(c) + " " + std::move(flags) + " "); // other flags for (auto& flag : detail::libclang_compile_config_access::flags(c)) { cmd += flag; cmd += ' '; } return cmd + quote(full_path); } // 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) { // -x c++: force C++ as input language // -E: print preprocessor output // -dD: keep macros auto flags = std::string("-x c++ -E -dD"); // -CC: keep comments, even in macro // -C: keep comments, but not in macro if (!detail::libclang_compile_config_access::remove_comments_in_macro(c)) flags += " -CC"; else flags += " -C"; 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) { // include file that defines all macros flags += " -include "; flags += macro_file_path; } std::string cmd(detail::libclang_compile_config_access::clang_binary(c) + " " + std::move(flags) + " "); // other flags for (const auto& flag : detail::libclang_compile_config_access::flags(c)) { DEBUG_ASSERT(flag.size() >= 2u && flag[0] == '-', detail::assert_handler{}, ("\"" + flag + "\" that's an odd flag").c_str()); 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 += quote(flag); cmd += ' '; } } return cmd + quote(full_path); } std::string get_macro_file_name() { static std::atomic counter(0u); return "standardese-macro-file-" + std::to_string(++counter) + ".delete-me"; } template void bump_until(std::istreambuf_iterator& iter, const char (&str)[N]) { auto ptr = &str[0]; while (ptr != &str[N - 1]) { if (iter == std::istreambuf_iterator{}) // end of file break; else if (*iter != *ptr) { // try again ptr = &str[0]; if (*iter == *ptr) ++ptr; // it was the first character again } else // okay, move forward ++ptr; ++iter; } } template void skip_whitespace(Iter& begin, Iter end) { while (begin != end && (*begin == ' ' || *begin == '\t')) ++begin; } template std::string get_line(Iter& begin, Iter end) { std::string line; while (begin != end && *begin != '\n') line += *begin++; ++begin; // newline return line; } type_safe::optional get_include_guard_macro(const std::string& full_path) { std::ifstream file(full_path); auto iter = std::istreambuf_iterator(file); while (iter != std::istreambuf_iterator{}) { if (*iter == '/') { ++iter; if (*iter == '/') // C++ style comment, bump until \n bump_until(iter, "\n"); else if (*iter == '*') // C style comment bump_until(iter, "*/"); } else if (*iter == ' ' || *iter == '\t' || *iter == '\n') ++iter; // empty else if (*iter == '#') { // preprocessor line auto if_line = get_line(iter, {}); if (if_line.compare(0, 3, "#if") != 0) // not something starting with #if break; skip_whitespace(iter, {}); auto macro_line = get_line(iter, {}); if (macro_line.compare(0, 7, "#define") != 0) // not a corresponding define break; auto macro_name_begin = std::next(macro_line.begin(), 7); // skip whitespace after define skip_whitespace(macro_name_begin, macro_line.end()); auto macro_name_end = macro_name_begin; // skip over identifier while (macro_name_end != macro_line.end() && (*macro_name_end == '_' || std::isalnum(*macro_name_end))) ++macro_name_end; auto trailing_ws = macro_line.rbegin(); skip_whitespace(trailing_ws, macro_line.rend()); if (macro_name_end != trailing_ws.base()) // anything else after macro break; std::string macro_name(macro_name_begin, macro_name_end); if (if_line.find(macro_name) == std::string::npos) // macro name doesn't occur in if line break; else return macro_name; } else // line is neither empty, comment, nor preprocessor break; } // assume no include guard followed a bad line return type_safe::nullopt; } std::string write_macro_file(const libclang_compile_config& c, const std::string& full_path, const diagnostic_logger& logger) { std::string diagnostic; auto diagnostic_logger = [&](const char* str, std::size_t n) { diagnostic.reserve(diagnostic.size() + n); for (auto end = str + n; str != end; ++str) if (*str == '\r') continue; else if (*str == '\n') { // consume current diagnostic log_diagnostic(logger, diagnostic); diagnostic.clear(); } else diagnostic.push_back(*str); }; auto file = get_macro_file_name(); std::ofstream stream(file); auto cmd = get_macro_command(c, full_path.c_str()); tpl::Process process(cmd, "", [&](const char* str, std::size_t n) { stream.write(str, std::streamsize(n)); }, diagnostic_logger); if (auto include_guard = get_include_guard_macro(full_path)) // undefine include guard stream << "#undef " << include_guard.value(); auto exit_code = process.get_exit_status(); DEBUG_ASSERT(diagnostic.empty(), detail::assert_handler{}); if (exit_code != 0) throw libclang_error("preprocessor (macro): command '" + cmd + "' exited with non-zero exit code (" + std::to_string(exit_code) + ")"); return file; } struct clang_preprocess_result { std::string file; std::vector included_files; // needed for pre-clang 4.0.0 }; clang_preprocess_result clang_preprocess_impl(const libclang_compile_config& c, const diagnostic_logger& logger, const std::string& full_path, const char* macro_path) { clang_preprocess_result result; std::string diagnostic; auto expect_bad_exit_code = false; auto diagnostic_handler = [&](const char* str, std::size_t n) { diagnostic.reserve(diagnostic.size() + n); for (auto end = str + n; str != end; ++str) if (*str == '\r') continue; else if (*str == '\n') { // handle current diagnostic if (macro_path) { // hide diagnostics auto file = parse_missing_file(full_path, diagnostic); if (file) // save for clang without -dI flag result.included_files.push_back(file.value()); expect_bad_exit_code = true; } else log_diagnostic(logger, diagnostic); diagnostic.clear(); } else diagnostic.push_back(*str); }; auto cmd = get_preprocess_command(c, full_path.c_str(), macro_path); tpl::Process process(cmd, "", [&](const char* str, std::size_t n) { result.file.reserve(result.file.size() + n); for (auto ptr = str; ptr != str + n; ++ptr) if (*ptr == '\t') result.file += " "; // convert to two spaces else if (*ptr != '\r') result.file += *ptr; }, diagnostic_handler); // wait for process end auto exit_code = process.get_exit_status(); DEBUG_ASSERT(diagnostic.empty(), detail::assert_handler{}); if (exit_code != 0 && !expect_bad_exit_code) throw libclang_error("preprocessor: command '" + cmd + "' exited with non-zero exit code (" + std::to_string(exit_code) + ")"); return result; } clang_preprocess_result clang_preprocess(const libclang_compile_config& c, const char* full_path, const diagnostic_logger& logger) { if (!std::ifstream(full_path)) throw libclang_error("preprocessor: file '" + std::string(full_path) + "' doesn't exist"); // 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) : ""; clang_preprocess_result result; try { result = clang_preprocess_impl(c, logger, full_path, fast_preprocessing ? macro_file.c_str() : nullptr); } catch (...) { if (fast_preprocessing) { auto err = std::remove(macro_file.c_str()); DEBUG_ASSERT(err == 0, detail::assert_handler{}); } throw; } if (fast_preprocessing) { auto err = std::remove(macro_file.c_str()); DEBUG_ASSERT(err == 0, detail::assert_handler{}); } return result; } //==== parsing ===// class position { public: position(ts::object_ref result, const char* ptr) noexcept : result_(result), cur_line_(1u), cur_column_(0u), ptr_(ptr), write_(true) { } void set_line(unsigned line) { if (cur_line_ != line) { *result_ += "#line " + std::to_string(line) + "\n"; cur_line_ = line; cur_column_ = 0; } } void write_str(std::string str) { if (write_ == false) return; for (auto c : str) { *result_ += c; ++cur_column_; if (c == '\n') { ++cur_line_; cur_column_ = 0; } } } void bump() noexcept { if (write_ == true) { result_->push_back(*ptr_); ++cur_column_; if (*ptr_ == '\n') { ++cur_line_; cur_column_ = 0; } } ++ptr_; } void bump(std::size_t offset) noexcept { if (write_ == true) { for (std::size_t i = 0u; i != offset; ++i) bump(); } else skip(offset); } // no write, no newline detection void skip(std::size_t offset = 1u) noexcept { ptr_ += offset; } void skip_with_linecount() noexcept { if (write_ == true) { ++cur_column_; if (*ptr_ == '\n') { result_->push_back('\n'); ++cur_line_; cur_column_ = 0; } } ++ptr_; } void enable_write() noexcept { write_.set(); } void disable_write() noexcept { write_.try_reset(); } bool write_enabled() const noexcept { return write_ == true; } explicit operator bool() const noexcept { return *ptr_ != '\0'; } const char* ptr() const noexcept { return ptr_; } unsigned cur_line() const noexcept { return cur_line_; } unsigned cur_column() const noexcept { return cur_column_; } bool was_newl() const noexcept { return result_->empty() || result_->back() == '\n'; } private: ts::object_ref result_; unsigned cur_line_, cur_column_; const char* ptr_; ts::flag write_; }; bool starts_with(const position& p, const char* str, std::size_t len) { return std::strncmp(p.ptr(), str, len) == 0; } template bool starts_with(const position& p, const char (&str)[N]) { return std::strncmp(p.ptr(), str, N - 1) == 0; } void skip(position& p, const char* str) { DEBUG_ASSERT(starts_with(p, str, std::strlen(str)), detail::assert_handler{}); p.skip(std::strlen(str)); } void bump_spaces(position& p, bool bump = false) { while (starts_with(p, " ")) if (bump) p.bump(); else p.skip(); } detail::pp_doc_comment parse_c_doc_comment(position& p) { detail::pp_doc_comment result; result.kind = detail::pp_doc_comment::c; auto indent = p.cur_column() + 3; if (starts_with(p, " ")) { // skip one whitespace at most p.skip(); ++indent; } while (!starts_with(p, "*/")) { if (starts_with(p, "\n")) { // remove trailing spaces while (!result.comment.empty() && result.comment.back() == ' ') result.comment.pop_back(); // skip newline(s) while (starts_with(p, "\n")) { p.skip_with_linecount(); result.comment += '\n'; } // skip indentation auto actual_indent = 0u; for (auto i = 0u; i < indent && starts_with(p, " "); ++i) { ++actual_indent; p.skip(); } auto extra_indent = 0u; while (starts_with(p, " ")) { ++extra_indent; p.skip(); } // skip continuation star, if any if (starts_with(p, "*") && !starts_with(p, "*/")) { p.skip(); if (starts_with(p, " ")) // skip one whitespace at most p.skip(); } else { // insert extra indent again result.comment += std::string(extra_indent, ' '); // use minimum indent in the future indent = std::min(actual_indent, indent); } } else { result.comment += *p.ptr(); p.skip(); } } p.skip(2u); // remove trailing star if (!result.comment.empty() && result.comment.back() == '*') result.comment.pop_back(); // remove trailing spaces while (!result.comment.empty() && result.comment.back() == ' ') result.comment.pop_back(); result.line = p.cur_line(); return result; } bool skip_c_comment(position& p, detail::preprocessor_output& output) { if (!starts_with(p, "/*")) return false; p.skip(2u); if (starts_with(p, "*/")) // empty comment p.skip(2u); else if (p.write_enabled() && (starts_with(p, "*") || starts_with(p, "!"))) { // doc comment p.skip(); output.comments.push_back(parse_c_doc_comment(p)); } else { while (!starts_with(p, "*/")) p.skip_with_linecount(); p.skip(2u); } return true; } detail::pp_doc_comment parse_cpp_doc_comment(position& p, bool end_of_line) { detail::pp_doc_comment result; result.kind = end_of_line ? detail::pp_doc_comment::end_of_line : detail::pp_doc_comment::cpp; if (starts_with(p, " ")) // skip one whitespace at most p.skip(); while (!starts_with(p, "\n")) { result.comment += *p.ptr(); p.skip(); } // don't skip newline // remove trailing spaces while (!result.comment.empty() && result.comment.back() == ' ') result.comment.pop_back(); result.line = p.cur_line(); return result; } bool can_merge_comment(const detail::pp_doc_comment& comment, unsigned cur_line) { return comment.line + 1 == cur_line && (comment.kind == detail::pp_doc_comment::cpp || comment.kind == detail::pp_doc_comment::end_of_line); } void merge_or_add(detail::preprocessor_output& output, detail::pp_doc_comment comment) { if (output.comments.empty() || !can_merge_comment(output.comments.back(), comment.line)) output.comments.push_back(std::move(comment)); else { auto& result = output.comments.back(); result.comment += "\n" + std::move(comment.comment); if (result.kind != detail::pp_doc_comment::end_of_line) result.line = comment.line; } } bool skip_cpp_comment(position& p, detail::preprocessor_output& output) { if (!starts_with(p, "//")) return false; p.skip(2u); 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 (p.write_enabled() && starts_with(p, "<")) { // end of line doc comment p.skip(); auto comment = parse_cpp_doc_comment(p, true); output.comments.push_back(std::move(comment)); } else { auto newline = std::strchr(p.ptr(), '\n'); p.skip(std::size_t(newline - p.ptr())); // don't skip newline } return true; } std::unique_ptr parse_macro(position& p, detail::preprocessor_output& output) { // format (at new line): #define [replacement] // or: #define () [replacement] // note: keep macro definition in file if (!p.was_newl() || !starts_with(p, "#define")) return nullptr; // read line here for comment matching auto cur_line = p.cur_line(); p.bump(std::strlen("#define")); bump_spaces(p, true); std::string name; while (!starts_with(p, "(") && !starts_with(p, " ") && !starts_with(p, "\n")) { name += *p.ptr(); p.bump(); } ts::optional args; if (starts_with(p, "(")) { std::string str; for (p.bump(); !starts_with(p, ")"); p.bump()) str += *p.ptr(); p.bump(); args = std::move(str); } std::string rep; auto in_c_comment = false; for (bump_spaces(p, true); in_c_comment || !starts_with(p, "\n"); p.bump()) { if (starts_with(p, "/*")) in_c_comment = true; else if (in_c_comment && starts_with(p, "*/")) in_c_comment = false; rep += *p.ptr(); } // don't skip newline if (!p.write_enabled()) return nullptr; auto result = cpp_macro_definition::build(std::move(name), std::move(args), std::move(rep)); // match comment directly if (!output.comments.empty() && output.comments.back().matches(*result, cur_line)) { result->set_comment(std::move(output.comments.back().comment)); output.comments.pop_back(); } return result; } ts::optional parse_undef(position& p) { // format (at new line): #undef // due to a clang bug (http://bugs.llvm.org/show_bug.cgi?id=32631) I'll also an undef in the middle of the line if (/*!p.was_newl() ||*/ !starts_with(p, "#undef")) return ts::nullopt; p.bump(std::strlen("#undef")); std::string result; for (bump_spaces(p, true); !starts_with(p, "\n"); p.bump()) result += *p.ptr(); // don't skip newline return result; } type_safe::optional parse_include(position& p) { // format (at new line, literal <>): #include // or: #include "filename" // note: write include back if (!p.was_newl() || !starts_with(p, "#include")) return type_safe::nullopt; p.bump(std::strlen("#include")); if (starts_with(p, "_next")) p.bump(std::strlen("_next")); bump_spaces(p); auto include_kind = cpp_include_kind::system; auto end_str = ""; if (starts_with(p, "\"")) { include_kind = cpp_include_kind::local; end_str = "\""; } else if (starts_with(p, "<")) { include_kind = cpp_include_kind::system; end_str = ">"; } else DEBUG_UNREACHABLE(detail::assert_handler{}); p.bump(); std::string filename; for (; !starts_with(p, "\"") && !starts_with(p, ">"); p.bump()) filename += *p.ptr(); DEBUG_ASSERT(starts_with(p, end_str, std::strlen(end_str)), detail::assert_handler{}, "bad termination"); p.bump(); skip(p, " /* clang -E -dI */"); 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); return detail::pp_include{std::move(filename), "", include_kind, p.cur_line()}; } bool bump_pragma(position& p) { // format (at new line): #pragma \n if (!p.was_newl() || !starts_with(p, "#pragma")) return false; while (!starts_with(p, "\n")) p.bump(); // don't skip newline 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 (bump_spaces(p); std::isdigit(*p.ptr()); p.skip()) line += *p.ptr(); result.line = unsigned(std::stoi(line)); bump_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()) { bump_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; } } // namespace detail::preprocessor_output detail::preprocess(const libclang_compile_config& config, const char* path, const diagnostic_logger& logger) { detail::preprocessor_output result; std::unordered_map indirect_includes; auto preprocessed = clang_preprocess(config, path, logger); if (detail::libclang_compile_config_access::clang_version(config) < 40000) { // add headers from diagnostics w/o line information for (auto name : preprocessed.included_files) result.includes.push_back(pp_include{name, "", cpp_include_kind::local, 1u}); } position p(ts::ref(result.source), preprocessed.file.c_str()); ts::flag in_string(false), in_char(false), first_line(true); while (p) { auto next = std::strpbrk(p.ptr(), R"(\"'#/)"); // look for \, ", ', # or / if (next && next > p.ptr()) p.bump(std::size_t(next - p.ptr() - 1)); // subtract one to get before that character if (starts_with(p, R"(\\)")) // starts with two backslashes p.bump(2u); else if (starts_with(p, "\\\"")) // starts with \" p.bump(2u); else if (starts_with(p, R"(\")")) // starts with \" p.bump(2u); else if (starts_with(p, R"(\')")) // starts with \' p.bump(2u); else if (in_char == false && starts_with(p, R"(")")) // starts with " { p.bump(); in_string.toggle(); } else if (in_string == false && starts_with(p, "'")) { p.bump(); in_char.toggle(); } else if (in_string == true || in_char == true) p.bump(); else if (auto macro = parse_macro(p, result)) { if (logger.is_verbose()) logger.log("preprocessor", format_diagnostic(severity::debug, source_location::make_file(path, p.cur_line()), "parsing macro '", macro->name(), "'")); result.macros.push_back({std::move(macro), p.cur_line()}); } else if (auto undef = parse_undef(p)) { if (p.write_enabled()) { if (logger.is_verbose()) logger.log("preprocessor", format_diagnostic(severity::debug, source_location::make_file(path, p.cur_line()), "undefining macro '", undef.value(), "'")); 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)) { if (p.write_enabled()) { if (logger.is_verbose()) logger.log("preprocessor", format_diagnostic(severity::debug, source_location::make_file(path, p.cur_line()), "parsing include '", include.value().file_name, "'")); result.includes.push_back(std::move(include.value())); } } else if (bump_pragma(p)) continue; 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 // note: path can be empty if pre clang 4 and not fast preprocessing // in this case we can't get the full path at all if (!result.includes.empty()) { DEBUG_ASSERT(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) { p.enable_write(); p.set_line(lm.value().line); } } else if (lm.value().flag == linemarker::line_directive && p.write_enabled()) { if (first_line.try_reset() && lm.value().file == path && lm.value().line == 1u) { // this is the first line marker // just skip all builtin macro stuff until we reach the file again auto closing_line_marker = std::string("# 1 \"") + path + "\" 2\n"; auto ptr = std::strstr(p.ptr(), closing_line_marker.c_str()); DEBUG_ASSERT(ptr, detail::assert_handler{}); p.skip(std::size_t(ptr - p.ptr())); p.skip(closing_line_marker.size()); } else if (lm.value().line + 1 == p.cur_line()) { // this is a linemarker after a -dI injected include directive // it simply adds the newline we already did } else { DEBUG_ASSERT(lm.value().line >= p.cur_line(), detail::assert_handler{}); while (p.cur_line() < lm.value().line) p.write_str("\n"); } } } else if (skip_c_comment(p, result)) continue; else if (skip_cpp_comment(p, result)) continue; else p.bump(); } // 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; }