Fix a couple of bugs

This commit is contained in:
Jonathan Müller 2017-10-09 09:38:49 +02:00
commit 5c5f30aa7a
10 changed files with 189 additions and 66 deletions

View file

@ -49,7 +49,7 @@ namespace cppast
return semantic_parent_;
}
/// \returns The name of the semantic parent, if it has own,
/// \returns The name of the semantic parent, if it has one,
/// else the empty string.
/// \notes This may include template parameters.
std::string semantic_scope() const noexcept

View file

@ -23,6 +23,8 @@ namespace cppast
static int clang_version(const libclang_compile_config& config);
static const std::vector<std::string>& flags(const libclang_compile_config& config);
static bool write_preprocessed(const libclang_compile_config& config);
};
void for_each_file(const libclang_compilation_database& database, void* user_data,
@ -34,9 +36,7 @@ namespace cppast
{
public:
/// \effects Creates it with a message.
libclang_error(std::string msg) : std::runtime_error(std::move(msg))
{
}
libclang_error(std::string msg) : std::runtime_error(std::move(msg)) {}
};
/// A compilation database.
@ -121,6 +121,13 @@ namespace cppast
clang_version_ = major * 10000 + minor * 100 + patch;
}
/// \effects Sets whether or not the preprocessed file will be written out.
/// Default value is `false`.
void write_preprocessed(bool b) noexcept
{
write_preprocessed_ = b;
}
private:
void do_set_flags(cpp_standard standard, compile_flags flags) override;
@ -137,6 +144,7 @@ namespace cppast
std::string clang_binary_;
int clang_version_;
bool write_preprocessed_;
friend detail::libclang_compile_config_access;
};

View file

@ -100,6 +100,15 @@ namespace
detail::skip_brackets(stream);
}
std::vector<CXCursor> get_semantic_parents(CXCursor cur)
{
std::vector<CXCursor> result;
for (; !clang_isTranslationUnit(clang_getCursorKind(cur));
cur = clang_getCursorSemanticParent(cur))
result.push_back(cur);
return result;
}
bool is_class(const CXCursor& parent)
{
auto kind = clang_getCursorKind(parent);
@ -108,6 +117,9 @@ namespace
|| kind == CXCursor_ClassTemplatePartialSpecialization;
}
// returns the scope where the function is contained in
// for regular functions that is the lexcial parent
// for friend functions it is the enclosing scope of the class
CXCursor get_definition_scope(const CXCursor& cur, bool is_friend)
{
auto parent = clang_getCursorLexicalParent(cur);
@ -138,25 +150,63 @@ namespace
type_safe::optional<cpp_entity_ref> parse_scope(const CXCursor& cur, bool is_friend)
{
std::string scope;
// find the semantic parents we need until we're at the same level of the parent where the definition is
// the semantic parents are all the scopes that need to be appended
for (auto definition = get_definition_scope(cur, is_friend),
parent = clang_getCursorSemanticParent(cur);
!equivalent_cursor(definition, parent); parent = clang_getCursorSemanticParent(parent))
std::string scope_name;
auto friended = clang_getCursorReferenced(cur);
if (is_friend && !clang_Cursor_isNull(friended))
{
DEBUG_ASSERT(!clang_isTranslationUnit(clang_getCursorKind(parent)),
// it refers to another function
// find the common parent between the two cursors
// scope is the scope from the common parent down to the function
auto friended_parents = get_semantic_parents(friended);
auto cur_parents = get_semantic_parents(get_definition_scope(cur, true));
// remove common parents
while (!friended_parents.empty() && !cur_parents.empty()
&& equivalent_cursor(friended_parents.back(), cur_parents.back()))
{
friended_parents.pop_back();
cur_parents.pop_back();
}
DEBUG_ASSERT(!clang_isTranslationUnit(clang_getCursorKind(friended_parents.back()))
&& !friended_parents.empty(),
detail::parse_error_handler{}, cur,
"infinite loop while calculating scope");
auto parent_name = detail::cxstring(clang_getCursorDisplayName(parent));
scope = parent_name.std_str() + "::" + std::move(scope);
"invalid common parent of friend and friended");
// scope consists of all remaining parents of friended
// (last one is cursor itself)
for (auto iter = friended_parents.rbegin(); iter != std::prev(friended_parents.rend());
++iter)
{
auto parent_name = detail::cxstring(clang_getCursorDisplayName(*iter));
scope_name += parent_name.std_str() + "::";
}
}
else
{
// find the difference between the definition scope parent and semantic parent
// all semantic parents in between form the scope
// the definition scope is the lexical parent for regular functions,
// and the scope outside of the class for friend functions
for (auto definition = get_definition_scope(cur, is_friend),
parent = clang_getCursorSemanticParent(cur);
!equivalent_cursor(definition, parent);
parent = clang_getCursorSemanticParent(parent))
{
DEBUG_ASSERT(!clang_isTranslationUnit(clang_getCursorKind(parent)),
detail::parse_error_handler{}, cur,
"infinite loop while calculating scope");
auto parent_name = detail::cxstring(clang_getCursorDisplayName(parent));
scope_name = parent_name.std_str() + "::" + std::move(scope_name);
}
}
if (scope.empty())
if (scope_name.empty())
return type_safe::nullopt;
else
return cpp_entity_ref(detail::get_entity_id(clang_getCursorSemanticParent(cur)),
std::move(scope));
std::move(scope_name));
}
// just the tokens occurring in the prefix

View file

@ -5,6 +5,7 @@
#include <cppast/libclang_parser.hpp>
#include <cstring>
#include <fstream>
#include <vector>
#include <clang-c/CXCompilationDatabase.h>
@ -35,6 +36,12 @@ const std::vector<std::string>& detail::libclang_compile_config_access::flags(
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<database, CXCompilationDatabase>::value, "forgot to update type");
@ -74,7 +81,7 @@ namespace
}
}
libclang_compile_config::libclang_compile_config() : compile_config({})
libclang_compile_config::libclang_compile_config() : compile_config({}), write_preprocessed_(false)
{
// set given clang binary
auto ptr = CPPAST_CLANG_VERSION_STRING;
@ -328,9 +335,7 @@ libclang_parser::libclang_parser(type_safe::object_ref<const diagnostic_logger>
{
}
libclang_parser::~libclang_parser() noexcept
{
}
libclang_parser::~libclang_parser() noexcept {}
namespace
{
@ -373,10 +378,14 @@ namespace
auto sev = get_severity(diag);
if (sev)
{
auto loc = source_location::make_file(path); // line number won't help
auto text = detail::cxstring(clang_getDiagnosticSpelling(diag));
auto diag_loc = clang_getDiagnosticLocation(diag);
unsigned line;
clang_getPresumedLocation(diag_loc, nullptr, &line, nullptr);
logger.log("libclang", diagnostic{text.c_str(), loc, sev.value()});
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()});
}
}
}
@ -444,8 +453,15 @@ std::unique_ptr<cpp_file> libclang_parser::do_parse(const cpp_entity_index& idx,
"config has mismatched type");
auto& config = static_cast<const libclang_compile_config&>(c);
// preprocess + parse
// 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());

View file

@ -36,9 +36,9 @@ namespace
// -x c++: force C++ as input language
// -I.: add current working directory to include search path
// -E: print preprocessor output
// -CC: keep comments, even in macro
// -C: keep comments
// -dD: print macro definitions as well
auto flags = std::string("-x c++ -I. -E -CC -dD");
auto flags = std::string("-x c++ -I. -E -C -dD");
if (detail::libclang_compile_config_access::clang_version(c) >= 40000)
// -Xclang -dI: print include directives as well (clang >= 4.0.0)
flags += " -Xclang -dI";
@ -735,7 +735,7 @@ detail::preprocessor_output detail::preprocess(const libclang_compile_config& co
{
case linemarker::line_directive:
break; // ignore
// no need to handle it, preprocessed output doesn't need to match line numbers precisely
// no need to handle it, preprocessed output doesn't need to match line numbers precisely
case linemarker::enter_new:
if (file_depth == 0u && lm.value().file.front() != '<')

View file

@ -33,10 +33,23 @@ namespace
str.erase(0, 1);
}
bool remove_suffix(std::string& str, const char* suffix)
bool can_extend_suffix(const std::string& str, std::size_t suffix_length)
{
if (str.length() <= suffix_length + 1u)
{
auto c = str[str.length() - suffix_length - 1u];
return std::isalnum(c) || c == '_';
}
else
return false;
}
// if identifier only removes maximal suffix according to tokenization
bool remove_suffix(std::string& str, const char* suffix, bool is_identifier)
{
auto length = std::strlen(suffix);
if (str.length() >= length && str.compare(str.length() - length, length, suffix) == 0)
if (str.length() >= length && str.compare(str.length() - length, length, suffix) == 0
&& (!is_identifier || !can_extend_suffix(str, length)))
{
str.erase(str.length() - length);
remove_trailing_ws(str);
@ -46,10 +59,23 @@ namespace
return false;
}
bool remove_prefix(std::string& str, const char* prefix)
bool can_extend_prefix(const std::string& str, std::size_t prefix_length)
{
if (prefix_length < str.size())
{
auto c = str[prefix_length];
return std::isalnum(c) || c == '_';
}
else
return false;
}
// if identifier only removes maximal suffix according to tokenization
bool remove_prefix(std::string& str, const char* prefix, bool is_identifier)
{
auto length = std::strlen(prefix);
if (str.length() >= length && str.compare(0u, length, prefix) == 0)
if (str.length() >= length && str.compare(0u, length, prefix) == 0
&& (!is_identifier || !can_extend_prefix(str, length)))
{
str.erase(0u, length);
remove_leading_ws(str);
@ -69,16 +95,16 @@ namespace
cpp_cv suffix_cv(std::string& spelling)
{
auto cv = cpp_cv_none;
if (remove_suffix(spelling, "const"))
if (remove_suffix(spelling, "const", true))
{
if (remove_suffix(spelling, "volatile"))
if (remove_suffix(spelling, "volatile", true))
cv = cpp_cv_const_volatile;
else
cv = cpp_cv_const;
}
else if (remove_suffix(spelling, "volatile"))
else if (remove_suffix(spelling, "volatile", true))
{
if (remove_suffix(spelling, "const"))
if (remove_suffix(spelling, "const", true))
cv = cpp_cv_const_volatile;
else
cv = cpp_cv_volatile;
@ -88,20 +114,19 @@ namespace
}
// const/volatile at the beginning
// (weird that the better version is less performant, isn't it?)
cpp_cv prefix_cv(std::string& spelling)
{
auto cv = cpp_cv_none;
if (remove_prefix(spelling, "const"))
if (remove_prefix(spelling, "const", true))
{
if (remove_prefix(spelling, "volatile"))
if (remove_prefix(spelling, "volatile", true))
cv = cpp_cv_const_volatile;
else
cv = cpp_cv_const;
}
else if (remove_prefix(spelling, "volatile"))
else if (remove_prefix(spelling, "volatile", true))
{
if (remove_prefix(spelling, "const"))
if (remove_prefix(spelling, "const", true))
cv = cpp_cv_const_volatile;
else
cv = cpp_cv_volatile;
@ -172,9 +197,9 @@ namespace
// remove struct/class/union prefix on inline type definition
// i.e. C's typedef struct idiom
remove_prefix(spelling, "struct");
remove_prefix(spelling, "class");
remove_prefix(spelling, "union");
remove_prefix(spelling, "struct", true);
remove_prefix(spelling, "class", true);
remove_prefix(spelling, "union", true);
auto entity = b(std::move(spelling));
if (!entity)
@ -277,9 +302,9 @@ namespace
cpp_reference member_function_ref_qualifier(std::string& spelling)
{
if (remove_suffix(spelling, "&&"))
if (remove_suffix(spelling, "&&", false))
return cpp_ref_rvalue;
else if (remove_suffix(spelling, "&"))
else if (remove_suffix(spelling, "&", false))
return cpp_ref_lvalue;
return cpp_ref_none;
}
@ -453,9 +478,9 @@ namespace
return nullptr; // don't use decltype here
return make_leave_type(type, [&](std::string&& spelling) -> std::unique_ptr<cpp_type> {
if (!remove_prefix(spelling, "decltype("))
if (!remove_prefix(spelling, "decltype(", false))
return nullptr;
remove_suffix(spelling, "..."); // variadic decltype. fun
remove_suffix(spelling, "...", false); // variadic decltype. fun
DEBUG_ASSERT(!spelling.empty() && spelling.back() == ')', detail::parse_error_handler{},
type, "unexpected spelling");
spelling.pop_back();
@ -652,7 +677,7 @@ namespace
case CXType_Typedef:
return make_leave_type(type, [&](std::string&& spelling) {
auto decl = clang_getTypeDeclaration(type);
if (remove_prefix(spelling, "(anonymous"))
if (remove_prefix(spelling, "(anonymous", false))
spelling = ""; // anonymous type
return cpp_user_defined_type::build(
cpp_type_ref(detail::get_entity_id(decl), std::move(spelling)));

View file

@ -19,6 +19,10 @@ template<template<typename>class... T>
struct templated{
};
using struct_=int;
struct_ var2;
struct foo{
int a;
@ -56,6 +60,10 @@ struct templated
{
};
using struct_ = int;
struct_ var2;
struct foo
{
int a;

View file

@ -36,8 +36,21 @@ class g {};
namespace ns
{
namespace other_ns
{
namespace inner
{
/// h2
void h2() {}
}
}
/// h
class h {};
class h
{
/// friend void other_ns::inner::h2();
friend void other_ns::inner::h2();
};
}
/// b::f
@ -115,7 +128,7 @@ int d() {}
)";
cpp_entity_index idx;
auto check_definition = [&](cpp_entity_id id, const char* name) {
auto check_definition = [&](cpp_entity_id id, const char* name) {
auto def = idx.lookup_definition(id);
REQUIRE(def.has_value());
REQUIRE(def.value().comment());
@ -184,6 +197,13 @@ int d() {}
REQUIRE(func.definition());
check_definition(func.definition().value(), "b::f");
}
else if (func.name() == "h2")
{
REQUIRE(func.semantic_scope() == "other_ns::inner::");
REQUIRE(func.is_declaration());
REQUIRE(func.definition());
check_definition(func.definition().value(), "h2");
}
else if (func.name() == "operator int")
{
REQUIRE(func.semantic_scope() == "b::");
@ -269,5 +289,5 @@ int d() {}
else
REQUIRE(false);
});
REQUIRE(count == 17u);
REQUIRE(count == 18u);
}

View file

@ -173,23 +173,20 @@ f **/
#define g(name) \
class name \
{ \
/** i
i */ \
void i(); \
};
/// h
/// h
g(h)
/// j
/// j
using j = int;
/// i
/// i
using i = int;
/// k
/// k
/// j
/// j
template <typename T/**/>
void k();
void j();
)";
auto file = parse({}, "comment-matching.cpp", code);

View file

@ -87,12 +87,8 @@ void print_entity(std::ostream& out, const cppast::cpp_entity& e)
}
// no need to handle indentation, as only a single line is used
void do_indent() override
{
}
void do_unindent() override
{
}
void do_indent() override {}
void do_unindent() override {}
// called when a generic token sequence should be generated
// there are specialized callbacks for various token kinds,
@ -249,6 +245,9 @@ int main(int argc, char* argv[]) try
cppast::libclang_compile_config(database, options["file"].as<std::string>());
}
if (options.count("verbose"))
config.write_preprocessed(true);
if (options.count("include_directory"))
for (auto& include : options["include_directory"].as<std::vector<std::string>>())
config.add_include_dir(include);