#pragma once #include #include #include #include #include #include #include "libclangmm/clangmm.h" #include "libclangmm/Utility.h" #include "bitfield.h" #include "utils.h" #include "optional.h" #include #include #include #include struct IndexedTypeDef; struct IndexedFuncDef; struct IndexedVarDef; using namespace std::experimental; template struct Id { uint64_t id; Id() : id(0) {} // Needed for containers. Do not use directly. Id(uint64_t id) : id(id) {} bool operator==(const Id& other) const { return id == other.id; } bool operator<(const Id& other) const { return id < other.id; } }; namespace std { template struct hash> { size_t operator()(const Id& k) const { return hash()(k.id); } }; } template bool operator==(const Id& a, const Id& b) { assert(a.group == b.group && "Cannot compare Ids from different groups"); return a.id == b.id; } struct _FakeFileType {}; using FileId = Id<_FakeFileType>; using TypeId = Id; using FuncId = Id; using VarId = Id; struct Location { bool interesting; int raw_file_id; int line; int column; Location() { interesting = false; raw_file_id = -1; line = -1; column = -1; } Location(bool interesting, FileId file, uint32_t line, uint32_t column) { this->interesting = interesting; this->raw_file_id = file.id; this->line = line; this->column = column; } FileId file_id() const { return FileId(raw_file_id); } explicit Location(const char* encoded) : Location() { int len = strlen(encoded); assert(len >= 0); if (*encoded == '*') { interesting = true; ++encoded; } assert(encoded); raw_file_id = atoi(encoded); while (*encoded && *encoded != ':') ++encoded; if (*encoded == ':') ++encoded; assert(encoded); line = atoi(encoded); while (*encoded && *encoded != ':') ++encoded; if (*encoded == ':') ++encoded; assert(encoded); column = atoi(encoded); } std::string ToString() { // Output looks like this: // // *1:2:3 // // * => interesting // 1 => file id // 2 => line // 3 => column std::string result; if (interesting) result += '*'; result += std::to_string(raw_file_id); result += ':'; result += std::to_string(line); result += ':'; result += std::to_string(column); return result; } // Compare two Locations and check if they are equal. Ignores the value of // |interesting|. // operator== doesn't seem to work properly... bool IsEqualTo(const Location& o) const { // When comparing, ignore the value of |interesting|. return raw_file_id == o.raw_file_id && line == o.line && column == o.column; } bool operator==(const Location& o) const { return IsEqualTo(o); } bool operator<(const Location& o) const { return interesting < o.interesting && raw_file_id < o.raw_file_id && line < o.line && column < o.column; } Location WithInteresting(bool interesting) { Location result = *this; result.interesting = interesting; return result; } }; #if false // TODO: Move off of this weird wrapper, use struct with custom wrappers // directly. BEGIN_BITFIELD_TYPE(Location, uint64_t) ADD_BITFIELD_MEMBER(interesting, /*start:*/ 0, /*len:*/ 1); // 2 values ADD_BITFIELD_MEMBER(raw_file_group, /*start:*/ 1, /*len:*/ 4); // 16 values, ok if they wrap around. ADD_BITFIELD_MEMBER(raw_file_id, /*start:*/ 5, /*len:*/ 25); // 33,554,432 values ADD_BITFIELD_MEMBER(line, /*start:*/ 30, /*len:*/ 20); // 1,048,576 values ADD_BITFIELD_MEMBER(column, /*start:*/ 50, /*len:*/ 14); // 16,384 values Location(bool interesting, FileId file, uint32_t line, uint32_t column) { this->interesting = interesting; this->raw_file_group = file.group; this->raw_file_id = file.id; this->line = line; this->column = column; } FileId file_id() { return FileId(raw_file_id, raw_file_group); } std::string ToString() { // Output looks like this: // // *1:2:3 // // * => interesting // 1 => file id // 2 => line // 3 => column std::string result; if (interesting) result += '*'; result += std::to_string(raw_file_id); result += ':'; result += std::to_string(line); result += ':'; result += std::to_string(column); return result; } // Compare two Locations and check if they are equal. Ignores the value of // |interesting|. // operator== doesn't seem to work properly... bool IsEqualTo(const Location& o) { // When comparing, ignore the value of |interesting|. return (wrapper.value >> 1) == (o.wrapper.value >> 1); } Location WithInteresting(bool interesting) { Location result = *this; result.interesting = interesting; return result; } END_BITFIELD_TYPE() #endif template struct Ref { Id id; Location loc; Ref() {} // For serialization. Ref(Id id, Location loc) : id(id), loc(loc) {} bool operator==(const Ref& other) { return id == other.id && loc == other.loc; } bool operator!=(const Ref& other) { return !(*this == other); } bool operator<(const Ref& other) const { return id < other.id && loc < other.loc; } }; template bool operator==(const Ref& a, const Ref& b) { return a.id == b.id && a.loc == b.loc; } template bool operator!=(const Ref& a, const Ref& b) { return !(a == b); } using TypeRef = Ref; using FuncRef = Ref; using VarRef = Ref; // TODO: skip as much forward-processing as possible when |is_system_def| is // set to false. // TODO: Either eliminate the defs created as a by-product of cross-referencing, // or do not emit things we don't have definitions for. template struct TypeDefDefinitionData { // General metadata. std::string usr; std::string short_name; std::string qualified_name; // While a class/type can technically have a separate declaration/definition, // it doesn't really happen in practice. The declaration never contains // comments or insightful information. The user always wants to jump from // the declaration to the definition - never the other way around like in // functions and (less often) variables. // // It's also difficult to identify a `class Foo;` statement with the clang // indexer API (it's doable using cursor AST traversal), so we don't bother // supporting the feature. optional definition; // If set, then this is the same underlying type as the given value (ie, this // type comes from a using or typedef statement). optional alias_of; // Immediate parent types. std::vector parents; // Types, functions, and variables defined in this type. std::vector types; std::vector funcs; std::vector vars; TypeDefDefinitionData(const std::string& usr) : usr(usr) {} bool operator==(const TypeDefDefinitionData& other) const { return usr == other.usr && short_name == other.short_name && qualified_name == other.qualified_name && definition == other.definition && alias_of == other.alias_of && parents == other.parents && types == other.types && funcs == other.funcs && vars == other.vars; } bool operator!=(const TypeDefDefinitionData& other) const { return !(*this == other); } }; struct IndexedTypeDef { TypeDefDefinitionData<> def; TypeId id; // Immediate derived types. std::vector derived; // Every usage, useful for things like renames. // NOTE: Do not insert directly! Use AddUsage instead. std::vector uses; bool is_bad_def = true; IndexedTypeDef() : def("") {} // For serialization IndexedTypeDef(TypeId id, const std::string& usr); void AddUsage(Location loc, bool insert_if_not_present = true); bool operator<(const IndexedTypeDef& other) const { return def.usr < other.def.usr; } }; namespace std { template <> struct hash { size_t operator()(const IndexedTypeDef& k) const { return hash()(k.def.usr); } }; } template struct FuncDefDefinitionData { // General metadata. std::string usr; std::string short_name; std::string qualified_name; optional definition; // Type which declares this one (ie, it is a method) optional declaring_type; // Method this method overrides. optional base; // Local variables defined in this function. std::vector locals; // Functions that this function calls. std::vector callees; FuncDefDefinitionData(const std::string& usr) : usr(usr) { //assert(usr.size() > 0); } bool operator==(const FuncDefDefinitionData& other) const { return usr == other.usr && short_name == other.short_name && qualified_name == other.qualified_name && definition == other.definition && declaring_type == other.declaring_type && base == other.base && locals == other.locals && callees == other.callees; } bool operator!=(const FuncDefDefinitionData& other) const { return !(*this == other); } }; struct IndexedFuncDef { FuncDefDefinitionData<> def; FuncId id; // Places the function is forward-declared. std::vector declarations; // Methods which directly override this one. std::vector derived; // Functions which call this one. // TODO: Functions can get called outside of just functions - for example, // they can get called in static context (maybe redirect to main?) // or in class initializer list (redirect to class ctor?) // - Right now those usages will not get listed here (but they should be // inside of all_uses). std::vector callers; // All usages. For interesting usages, see callees. std::vector uses; bool is_bad_def = true; IndexedFuncDef() : def("") {} // For serialization IndexedFuncDef(FuncId id, const std::string& usr) : id(id), def(usr) { //assert(usr.size() > 0); } bool operator<(const IndexedFuncDef& other) const { return def.usr < other.def.usr; } }; namespace std { template <> struct hash { size_t operator()(const IndexedFuncDef& k) const { return hash()(k.def.usr); } }; } template struct VarDefDefinitionData { // General metadata. std::string usr; std::string short_name; std::string qualified_name; optional declaration; // TODO: definitions should be a list of locations, since there can be more // than one. optional definition; // Type of the variable. optional variable_type; // Type which declares this one (ie, it is a method) optional declaring_type; VarDefDefinitionData(const std::string& usr) : usr(usr) {} bool operator==(const VarDefDefinitionData& other) const { return usr == other.usr && short_name == other.short_name && qualified_name == other.qualified_name && declaration == other.declaration && definition == other.definition && variable_type == other.variable_type && declaring_type == other.declaring_type; } bool operator!=(const VarDefDefinitionData& other) const { return !(*this == other); } }; struct IndexedVarDef { VarDefDefinitionData<> def; VarId id; // Usages. std::vector uses; bool is_bad_def = true; IndexedVarDef() : def("") {} // For serialization IndexedVarDef(VarId id, const std::string& usr) : id(id), def(usr) { //assert(usr.size() > 0); } bool operator<(const IndexedVarDef& other) const { return def.usr < other.def.usr; } }; namespace std { template <> struct hash { size_t operator()(const IndexedVarDef& k) const { return hash()(k.def.usr); } }; } struct IdCache { std::unordered_map file_path_to_file_id; std::unordered_map usr_to_type_id; std::unordered_map usr_to_func_id; std::unordered_map usr_to_var_id; std::unordered_map file_id_to_file_path; std::unordered_map type_id_to_usr; std::unordered_map func_id_to_usr; std::unordered_map var_id_to_usr; IdCache(); Location Resolve(const CXSourceLocation& cx_loc, bool interesting); Location Resolve(const CXIdxLoc& cx_idx_loc, bool interesting); Location Resolve(const CXCursor& cx_cursor, bool interesting); Location Resolve(const clang::Cursor& cursor, bool interesting); }; struct IndexedFile { IdCache id_cache; std::string path; std::vector types; std::vector funcs; std::vector vars; IndexedFile(const std::string& path); TypeId ToTypeId(const std::string& usr); FuncId ToFuncId(const std::string& usr); VarId ToVarId(const std::string& usr); TypeId ToTypeId(const CXCursor& usr); FuncId ToFuncId(const CXCursor& usr); VarId ToVarId(const CXCursor& usr); IndexedTypeDef* Resolve(TypeId id); IndexedFuncDef* Resolve(FuncId id); IndexedVarDef* Resolve(VarId id); std::string ToString(); }; IndexedFile Parse(std::string filename, std::vector args, bool dump_ast = false);