// Copyright (C) 2017 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 #include #include #include #include #include "libclang_visitor.hpp" #include "raii_wrapper.hpp" #include "parse_error.hpp" #include "parse_functions.hpp" #include "preprocessor.hpp" #include "tokenizer.hpp" using namespace cppast; const std::string& detail::libclang_compile_config_access::clang_binary( const libclang_compile_config& config) { return config.clang_binary_; } int detail::libclang_compile_config_access::clang_version(const libclang_compile_config& config) { return config.clang_version_; } const std::vector& detail::libclang_compile_config_access::flags( const libclang_compile_config& config) { return config.get_flags(); } bool detail::libclang_compile_config_access::write_preprocessed( const libclang_compile_config& config) { return config.write_preprocessed_; } libclang_compilation_database::libclang_compilation_database(const std::string& build_directory) { static_assert(std::is_same::value, "forgot to update type"); auto error = CXCompilationDatabase_NoError; database_ = clang_CompilationDatabase_fromDirectory(build_directory.c_str(), &error); if (error != CXCompilationDatabase_NoError) throw libclang_error("unable to load compilation database"); } libclang_compilation_database::~libclang_compilation_database() { if (database_) clang_CompilationDatabase_dispose(database_); } bool libclang_compilation_database::has_config(const char* file_name) const { auto cxcommands = clang_CompilationDatabase_getCompileCommands(database_, file_name); if (!cxcommands) return false; clang_CompileCommands_dispose(cxcommands); return true; } namespace { int parse_number(const char*& str) { auto result = 0; for (; *str && *str != '.'; ++str) { result *= 10; result += int(*str - '0'); } return result; } } libclang_compile_config::libclang_compile_config() : compile_config({}), write_preprocessed_(false) { // set given clang binary auto ptr = CPPAST_CLANG_VERSION_STRING; auto major = parse_number(ptr); auto minor = parse_number(ptr); auto patch = parse_number(ptr); set_clang_binary(CPPAST_CLANG_BINARY, major, minor, patch); // set system include dir add_include_dir(CPPAST_LIBCLANG_SYSTEM_INCLUDE_DIR); // set macros to detect cppast define_macro("__cppast__", "libclang"); define_macro("__cppast_version_major__", CPPAST_VERSION_MAJOR); define_macro("__cppast_version_minor__", CPPAST_VERSION_MINOR); } namespace { struct cxcompile_commands_deleter { void operator()(CXCompileCommands cmds) { clang_CompileCommands_dispose(cmds); } }; using cxcompile_commands = detail::raii_wrapper; bool has_drive_prefix(const std::string& file) { return file.size() > 2 && file[1] == ':'; } std::string get_full_path(const detail::cxstring& dir, const std::string& file) { if (has_drive_prefix(file) || file.front() == '/' || file.front() == '\\') // absolute file return file; else if (dir[dir.length() - 1] != '/' && dir[dir.length() - 1] != '\\') // relative needing separator return dir.std_str() + '/' + file; else // relative w/o separator return dir.std_str() + file; } } void detail::for_each_file(const libclang_compilation_database& database, void* user_data, void (*callback)(void*, std::string)) { cxcompile_commands commands( clang_CompilationDatabase_getAllCompileCommands(database.database_)); auto no = clang_CompileCommands_getSize(commands.get()); for (auto i = 0u; i != no; ++i) { auto cmd = clang_CompileCommands_getCommand(commands.get(), i); auto dir = cxstring(clang_CompileCommand_getDirectory(cmd)); callback(user_data, get_full_path(dir, cxstring(clang_CompileCommand_getFilename(cmd)).std_str())); } } namespace { bool is_flag(const detail::cxstring& str) { return str.length() > 1u && str[0] == '-'; } const char* find_flag_arg_sep(const std::string& last_flag) { if (last_flag[1] == 'D') // no separator, equal is part of the arg return nullptr; return std::strchr(last_flag.c_str(), '='); } template void parse_flags(CXCompileCommand cmd, Func callback) { auto no_args = clang_CompileCommand_getNumArgs(cmd); std::string last_flag; for (auto i = 1u /* 0 is compiler executable */; i != no_args; ++i) { detail::cxstring str(clang_CompileCommand_getArg(cmd, i)); if (is_flag(str)) { if (!last_flag.empty()) { // process last flag std::string args; if (auto ptr = find_flag_arg_sep(last_flag)) { auto pos = std::size_t(ptr - last_flag.c_str()); ++ptr; while (*ptr) args += *ptr++; last_flag.erase(pos); } else if (last_flag.size() > 2u) { // assume two character flag args = last_flag.substr(2u); last_flag.erase(2u); } callback(std::move(last_flag), std::move(args)); } last_flag = str.std_str(); } else if (!last_flag.empty()) { // we have flags + args callback(std::move(last_flag), str.std_str()); last_flag.clear(); } // else skip argument } } } libclang_compile_config::libclang_compile_config(const libclang_compilation_database& database, const std::string& file) : libclang_compile_config() { auto cxcommands = clang_CompilationDatabase_getCompileCommands(database.database_, file.c_str()); if (cxcommands == nullptr) throw libclang_error(detail::format("no compile commands specified for file '", file, "'")); cxcompile_commands commands(cxcommands); auto size = clang_CompileCommands_getSize(commands.get()); for (auto i = 0u; i != size; ++i) { auto cmd = clang_CompileCommands_getCommand(commands.get(), i); auto dir = detail::cxstring(clang_CompileCommand_getDirectory(cmd)); parse_flags(cmd, [&](std::string flag, std::string args) { if (flag == "-I") add_flag(std::move(flag) + get_full_path(dir, args)); else if (flag == "-D" || flag == "-U") // preprocessor options add_flag(std::move(flag) + std::move(args)); else if (flag == "-std") // standard add_flag(std::move(flag) + "=" + std::move(args)); else if (flag == "-f" && (args == "ms-compatibility" || args == "ms-extensions")) // other options add_flag(std::move(flag) + std::move(args)); }); } } void libclang_compile_config::do_set_flags(cpp_standard standard, compile_flags flags) { switch (standard) { case cpp_standard::cpp_98: if (flags & compile_flag::gnu_extensions) add_flag("-std=gnu++98"); else add_flag("-std=c++98"); break; case cpp_standard::cpp_03: if (flags & compile_flag::gnu_extensions) add_flag("-std=gnu++03"); else add_flag("-std=c++03"); break; case cpp_standard::cpp_11: if (flags & compile_flag::gnu_extensions) add_flag("-std=gnu++11"); else add_flag("-std=c++11"); break; case cpp_standard::cpp_14: if (flags & compile_flag::gnu_extensions) add_flag("-std=gnu++14"); else add_flag("-std=c++14"); break; case cpp_standard::cpp_1z: if (flags & compile_flag::gnu_extensions) add_flag("-std=gnu++1z"); else add_flag("-std=c++1z"); break; } if (flags & compile_flag::ms_compatibility) add_flag("-fms-compatibility"); if (flags & compile_flag::ms_extensions) add_flag("-fms-extensions"); } void libclang_compile_config::do_add_include_dir(std::string path) { add_flag("-I" + std::move(path)); } void libclang_compile_config::do_add_macro_definition(std::string name, std::string definition) { auto str = "-D" + std::move(name); if (!definition.empty()) str += "=" + std::move(definition); add_flag(std::move(str)); } void libclang_compile_config::do_remove_macro_definition(std::string name) { add_flag("-U" + std::move(name)); } type_safe::optional cppast::find_config_for( const libclang_compilation_database& database, std::string file_name) { if (database.has_config(file_name)) return libclang_compile_config(database, std::move(file_name)); auto dot = file_name.rfind('.'); if (dot != std::string::npos) file_name.erase(dot); if (database.has_config(file_name)) return libclang_compile_config(database, std::move(file_name)); static const char* extensions[] = {".h", ".hpp", ".cpp", ".h++", ".c++", ".hxx", ".cxx", ".hh", ".cc", ".H", ".C"}; for (auto ext : extensions) { auto name = file_name + ext; if (database.has_config(name)) return libclang_compile_config(database, std::move(name)); } return type_safe::nullopt; } struct libclang_parser::impl { detail::cxindex index; impl() : index(clang_createIndex(0, 0)) // no diagnostic, other one is irrelevant { } }; libclang_parser::libclang_parser(type_safe::object_ref logger) : parser(logger), pimpl_(new impl) { } libclang_parser::~libclang_parser() noexcept {} namespace { std::vector get_arguments(const libclang_compile_config& config) { std::vector args = {"-x", "c++", "-I."}; // force C++ and enable current directory for include search for (auto& flag : detail::libclang_compile_config_access::flags(config)) args.push_back(flag.c_str()); return args; } type_safe::optional get_severity(const CXDiagnostic& diag) { switch (clang_getDiagnosticSeverity(diag)) { case CXDiagnostic_Ignored: case CXDiagnostic_Note: case CXDiagnostic_Warning: // ignore those diagnostics return type_safe::nullopt; case CXDiagnostic_Error: return severity::error; case CXDiagnostic_Fatal: return severity::critical; } DEBUG_UNREACHABLE(detail::assert_handler{}); return type_safe::nullopt; } void print_diagnostics(const diagnostic_logger& logger, const char* path, const CXTranslationUnit& tu) { auto no = clang_getNumDiagnostics(tu); for (auto i = 0u; i != no; ++i) { auto diag = clang_getDiagnostic(tu, i); auto sev = get_severity(diag); if (sev) { auto diag_loc = clang_getDiagnosticLocation(diag); unsigned line; clang_getPresumedLocation(diag_loc, nullptr, &line, nullptr); auto loc = source_location::make_file(path, line); auto text = detail::cxstring(clang_getDiagnosticSpelling(diag)); if (text != "too many errors emitted, stopping now") logger.log("libclang", diagnostic{text.c_str(), loc, sev.value()}); } } } detail::cxtranslation_unit get_cxunit(const diagnostic_logger& logger, const detail::cxindex& idx, const libclang_compile_config& config, const char* path, const std::string& source) { CXUnsavedFile file; file.Filename = path; file.Contents = source.c_str(); file.Length = source.length(); auto args = get_arguments(config); CXTranslationUnit tu; auto flags = CXTranslationUnit_Incomplete | CXTranslationUnit_KeepGoing | 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) unsigned(flags), &tu); if (error != CXError_Success) { switch (error) { case CXError_Success: DEBUG_UNREACHABLE(detail::assert_handler{}); break; case CXError_Failure: throw libclang_error("clang_parseTranslationUnit: generic error"); case CXError_Crashed: throw libclang_error("clang_parseTranslationUnit: libclang crashed :("); case CXError_InvalidArguments: throw libclang_error("clang_parseTranslationUnit: you shouldn't see this message"); case CXError_ASTReadError: throw libclang_error("clang_parseTranslationUnit: AST deserialization error"); } } print_diagnostics(logger, path, tu); return detail::cxtranslation_unit(tu); } unsigned get_line_no(const CXCursor& cursor) { auto loc = clang_getCursorLocation(cursor); unsigned line; clang_getPresumedLocation(loc, nullptr, &line, nullptr); return line; } } std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, std::string path, const compile_config& c) const try { DEBUG_ASSERT(std::strcmp(c.name(), "libclang") == 0, detail::precondition_error_handler{}, "config has mismatched type"); auto& config = static_cast(c); // preprocess auto preprocessed = detail::preprocess(config, path.c_str(), logger()); if (detail::libclang_compile_config_access::write_preprocessed(config)) { std::ofstream file(path + ".pp"); file << preprocessed.source; } // parse auto tu = get_cxunit(logger(), pimpl_->index, config, path.c_str(), preprocessed.source); auto file = clang_getFile(tu.get(), path.c_str()); cpp_file::builder builder(path); auto macro_iter = preprocessed.macros.begin(); auto include_iter = preprocessed.includes.begin(); // convert entity hierarchies detail::parse_context context{tu.get(), file, type_safe::ref(logger()), type_safe::ref(idx), detail::comment_context(preprocessed.comments), false}; detail::visit_tu(tu, path.c_str(), [&](const CXCursor& cur) { if (clang_getCursorKind(cur) == CXCursor_InclusionDirective) { if (!preprocessed.includes.empty()) { DEBUG_ASSERT(include_iter != preprocessed.includes.end() && get_line_no(cur) >= include_iter->line, detail::assert_handler{}); 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 (; macro_iter != preprocessed.macros.end(); ++macro_iter) builder.add_child(std::move(macro_iter->macro)); for (auto& c : preprocessed.comments) { if (!c.comment.empty()) builder.add_unmatched_comment(cpp_doc_comment(std::move(c.comment), c.line)); } if (context.error) set_error(); return builder.finish(idx); } catch (detail::parse_error& ex) { logger().log("libclang parser", ex.get_diagnostic()); set_error(); return nullptr; }