diff --git a/src/indexer.cc b/src/indexer.cc index 5cbde5e4..bd4f9a30 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -12,6 +12,7 @@ #include #include +#include // TODO: See if we can use clang_indexLoc_getFileLocation to get a type ref on // |Foobar| in DISALLOW_COPY(Foobar) @@ -20,6 +21,15 @@ namespace { const bool kIndexStdDeclarations = true; +std::vector BuildTypeDesc(clang::Cursor cursor) { + std::vector type_desc; + for (clang::Cursor arg : cursor.get_arguments()) { + if (arg.get_kind() == CXCursor_ParmDecl) + type_desc.push_back(arg.get_type_description()); + } + return type_desc; +} + void AddFuncRef(std::vector* result, IndexFuncRef ref) { if (!result->empty() && (*result)[result->size() - 1] == ref) return; @@ -89,6 +99,103 @@ struct NamespaceHelper { } }; +// Caches all instances of constructors, regardless if they are indexed or not. +// The constructor may have a make_unique call associated with it that we need +// to export. If we do not capture the parameter type description for the +// constructor we will not be able to attribute the constructor call correctly. +struct ConstructorCache { + using Usr = std::string; + struct Constructor { + std::vector param_type_desc; + Usr usr; + }; + std::unordered_map> constructors_; + + // This should be called whenever there is a constructor declaration. + void NotifyConstructor(clang::Cursor ctor_cursor) { + Constructor ctor; + ctor.usr = ctor_cursor.get_usr(); + ctor.param_type_desc = BuildTypeDesc(ctor_cursor); + + // Insert into |constructors_|. + std::string type_usr = ctor_cursor.get_semantic_parent().get_usr(); + auto existing_ctors = constructors_.find(type_usr); + if (existing_ctors != constructors_.end()) { + existing_ctors->second.push_back(ctor); + } else { + constructors_[type_usr] = {ctor}; + } + } + + // Tries to lookup a constructor in |type_usr| that takes arguments most + // closely aligned to |param_type_desc|. + optional TryFindConstructorUsr( + const std::string& type_usr, + const std::vector& param_type_desc) { + auto count_matching_prefix_length = [](const char* a, const char* b) { + int matched = 0; + while (*a && *b) { + if (*a != *b) + break; + ++a; + ++b; + ++matched; + } + // Additional score if the strings were the same length, which makes + // "a"/"a" match higher than "a"/"a&" + if (*a == *b) + matched += 1; + return matched; + }; + + // Try to find constructors for the type. If there are no constructors + // available, return an empty result. + auto ctors_it = constructors_.find(type_usr); + if (ctors_it == constructors_.end()) + return nullopt; + const std::vector& ctors = ctors_it->second; + if (ctors.empty()) + return nullopt; + + std::string best_usr; + int best_score = INT_MIN; + + // Scan constructors for the best possible match. + for (const Constructor& ctor : ctors) { + // If |param_type_desc| is empty and the constructor is as well, we don't + // need to bother searching, as this is the match. + if (param_type_desc.empty() && ctor.param_type_desc.empty()) { + best_usr = ctor.usr; + break; + } + + // Weight matching parameter length heavily, as it is more accurate than + // the fuzzy type matching approach. + int score = 0; + if (param_type_desc.size() == ctor.param_type_desc.size()) + score += param_type_desc.size() * 1000; + + // Do prefix-based match on parameter type description. This works well in + // practice because clang appends qualifiers to the end of the type, ie, + // |foo *&&| + for (int i = 0; + i < std::min(param_type_desc.size(), ctor.param_type_desc.size()); + ++i) { + score += count_matching_prefix_length(param_type_desc[i].c_str(), + ctor.param_type_desc[i].c_str()); + } + + if (score > best_score) { + best_usr = ctor.usr; + best_score = score; + } + } + + assert(!best_usr.empty()); + return best_usr; + } +}; + struct IndexParam { std::unordered_set seen_cx_files; std::vector seen_files; @@ -107,6 +214,7 @@ struct IndexParam { FileConsumer* file_consumer = nullptr; NamespaceHelper ns; + ConstructorCache ctors; IndexParam(clang::TranslationUnit* tu, FileConsumer* file_consumer) : tu(tu), file_consumer(file_consumer) {} @@ -875,12 +983,19 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { clang_indexLoc_getCXSourceLocation(decl->loc))) return; + IndexParam* param = static_cast(client_data); + + // Track all constructor declarations, as we may need to use it to manually + // associate std::make_unique and the like as constructor invocations. + if (decl->entityInfo->kind == CXIdxEntity_CXXConstructor) { + param->ctors.NotifyConstructor(decl->cursor); + } + assert(AreEqualLocations(decl->loc, decl->cursor)); CXFile file; clang_getSpellingLocation(clang_indexLoc_getCXSourceLocation(decl->loc), &file, nullptr, nullptr, nullptr); - IndexParam* param = static_cast(client_data); IndexFile* db = ConsumeFile(param, file); if (!db) return; @@ -1009,20 +1124,16 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { AddDeclTypeUsages(db, decl_cursor, decl->semanticContainer, decl->lexicalContainer); - func->is_constructor = decl->entityInfo->kind == CXIdxEntity_CXXConstructor; + func->is_constructor = + decl->entityInfo->kind == CXIdxEntity_CXXConstructor; // Add parameter list if we haven't seen this function before. // // note: If the function has no parameters, this block will be rerun // every time we see the function. Performance should hopefully be fine // but it may be a possible optimization. - if (func->parameter_type_descriptions.empty()) { - for (clang::Cursor arg : decl_cursor.get_arguments()) { - if (arg.get_kind() == CXCursor_ParmDecl) { - func->parameter_type_descriptions.push_back(arg.get_type_description()); - } - } - } + if (func->parameter_type_descriptions.empty()) + func->parameter_type_descriptions = BuildTypeDesc(decl_cursor); // Add definition or declaration. This is a bit tricky because we treat // template specializations as declarations, even though they are @@ -1378,6 +1489,50 @@ void indexEntityReference(CXClientData client_data, AddFuncRef(&called->callers, IndexFuncRef(loc_spelling, is_implicit)); } + // Checks if |str| starts with |start|. Ignores case. + auto str_begin = [](const char* start, const char* str) { + while (*start && *str) { + char a = tolower(*start); + char b = tolower(*str); + if (a != b) + return false; + ++start; + ++str; + } + return !*start; + }; + + bool is_template = ref->referencedEntity->templateKind != + CXIdxEntityCXXTemplateKind::CXIdxEntity_NonTemplate; + if (is_template && str_begin("make", ref->referencedEntity->name)) { + // Try to find the return type of called function. That type will have + // the constructor function we add a usage to. + optional opt_found_type = FindType(ref->cursor); + if (opt_found_type) { + std::string ctor_type_usr = + opt_found_type->get_referenced().get_usr(); + clang::Cursor call_cursor = ref->cursor; + + // Build a type description from the parameters of the call, so we + // can try to find a constructor with the same type description. + std::vector call_type_desc; + for (clang::Type type : call_cursor.get_type().get_arguments()) { + std::string type_desc = type.get_spelling(); + if (!type_desc.empty()) + call_type_desc.push_back(type_desc); + } + + // Try to find the constructor and add a reference. + optional ctor_usr = + param->ctors.TryFindConstructorUsr(ctor_type_usr, call_type_desc); + if (ctor_usr) { + IndexFunc* ctor = db->Resolve(db->ToFuncId(*ctor_usr)); + AddFuncRef(&ctor->callers, + IndexFuncRef(loc_spelling, true /*is_implicit*/)); + } + } + } + break; } diff --git a/src/libclangmm/Cursor.cc b/src/libclangmm/Cursor.cc index c7d51fd7..3b7e3848 100644 --- a/src/libclangmm/Cursor.cc +++ b/src/libclangmm/Cursor.cc @@ -181,7 +181,6 @@ Cursor Cursor::get_semantic_parent() const { std::vector Cursor::get_arguments() const { int size = clang_Cursor_getNumArguments(cx_cursor); - assert(size >= 0); if (size < 0) return std::vector(); diff --git a/src/test.cc b/src/test.cc index 40344380..084ed346 100644 --- a/src/test.cc +++ b/src/test.cc @@ -121,19 +121,7 @@ void RunTests() { float memory_after = -1.; { - // if (path != "tests/templates/specialized_func_definition.cc") continue; - // if (path != - // "tests/templates/namespace_template_class_template_func_usage_folded_into_one.cc") - // continue; if (path != "tests/multi_file/funky_enum.cc") continue; if - // (path != "tests/multi_file/simple_impl.cc") continue; if (path != - // "tests/inheritance/interface_pure_virtual.cc") continue; if (path != - // "tests/_empty_test.cc") continue; if (path != - // "tests/declaration_vs_definition/func_associated_function_params.cc") - // continue; - - // if (path != - // "tests/templates/template_class_type_usage_folded_into_one.cc") - // continue; path = "C:/Users/jacob/Desktop/superindex/indexer/" + path; + // if (path != "tests/constructors/make_functions.cc") continue; Config config; FileConsumer::SharedState file_consumer_shared; diff --git a/tests/constructors/make_functions.cc b/tests/constructors/make_functions.cc index 89ed1b6b..2c2cd446 100644 --- a/tests/constructors/make_functions.cc +++ b/tests/constructors/make_functions.cc @@ -1,3 +1,5 @@ +#include "make_functions.h" + template T* MakeUnique(Args&&... args) { return nullptr; @@ -8,14 +10,6 @@ T* maKE_NoRefs(Args... args) { return nullptr; } -struct Bar {}; -class Foobar { - public: - Foobar() {} - Foobar(int) {} - Foobar(int&&, Bar*, bool*) {} - Foobar(int, Bar*, bool*) {} -}; void caller22() { MakeUnique(); MakeUnique(1); @@ -26,42 +20,103 @@ void caller22() { // TODO: Eliminate the extra entries in the "types" array here. They come from // the template function definitions. +// Foobar is defined in a separate file to ensure that we can attribute +// MakeUnique calls across translation units. + /* -OUTPUT: +OUTPUT: make_functions.h { + "dependencies": ["C:/Users/jacob/Desktop/cquery/tests/constructors/make_functions.cc"], "types": [{ "id": 0, - "usr": "c:make_functions.cc@10", - "uses": ["2:1-2:2"] - }, { - "id": 1, - "usr": "c:make_functions.cc@22", - "uses": ["2:15-2:19"] - }, { - "id": 2, - "usr": "c:make_functions.cc@108", - "uses": ["7:1-7:2"] - }, { - "id": 3, - "usr": "c:make_functions.cc@120", - "uses": ["7:16-7:20"] - }, { - "id": 4, "usr": "c:@S@Bar", "short_name": "Bar", "detailed_name": "Bar", - "definition_spelling": "11:8-11:11", - "definition_extent": "11:1-11:14", - "uses": ["11:8-11:11", "16:17-16:20", "17:15-17:18", "22:29-22:32", "23:30-23:33"] + "definition_spelling": "1:8-1:11", + "definition_extent": "1:1-1:14", + "uses": ["1:8-1:11", "7:17-7:20", "8:15-8:18"] }, { - "id": 5, + "id": 1, "usr": "c:@S@Foobar", "short_name": "Foobar", "detailed_name": "Foobar", - "definition_spelling": "12:7-12:13", - "definition_extent": "12:1-18:2", - "funcs": [2, 3, 4, 5], - "uses": ["12:7-12:13", "14:3-14:9", "15:3-15:9", "16:3-16:9", "17:3-17:9", "20:14-20:20", "21:14-21:20", "22:14-22:20", "23:15-23:21"] + "definition_spelling": "3:7-3:13", + "definition_extent": "3:1-9:2", + "funcs": [0, 1, 2, 3], + "uses": ["3:7-3:13", "5:3-5:9", "6:3-6:9", "7:3-7:9", "8:3-8:9"] + }], + "funcs": [{ + "id": 0, + "usr": "c:@S@Foobar@F@Foobar#", + "short_name": "Foobar", + "detailed_name": "void Foobar::Foobar()", + "is_constructor": true, + "definition_spelling": "5:3-5:9", + "definition_extent": "5:3-5:14", + "declaring_type": 1 + }, { + "id": 1, + "usr": "c:@S@Foobar@F@Foobar#I#", + "short_name": "Foobar", + "detailed_name": "void Foobar::Foobar(int)", + "is_constructor": true, + "parameter_type_descriptions": ["int"], + "definition_spelling": "6:3-6:9", + "definition_extent": "6:3-6:17", + "declaring_type": 1 + }, { + "id": 2, + "usr": "c:@S@Foobar@F@Foobar#&&I#*$@S@Bar#*b#", + "short_name": "Foobar", + "detailed_name": "void Foobar::Foobar(int &&, Bar *, bool *)", + "is_constructor": true, + "parameter_type_descriptions": ["int &&", "Bar *", "bool *"], + "definition_spelling": "7:3-7:9", + "definition_extent": "7:3-7:32", + "declaring_type": 1 + }, { + "id": 3, + "usr": "c:@S@Foobar@F@Foobar#I#*$@S@Bar#*b#", + "short_name": "Foobar", + "detailed_name": "void Foobar::Foobar(int, Bar *, bool *)", + "is_constructor": true, + "parameter_type_descriptions": ["int", "Bar *", "bool *"], + "definition_spelling": "8:3-8:9", + "definition_extent": "8:3-8:30", + "declaring_type": 1 + }] +} +OUTPUT: make_functions.cc +{ + "includes": [{ + "line": 1, + "resolved_path": "C:/Users/jacob/Desktop/cquery/tests/constructors/make_functions.h" + }], + "dependencies": ["C:/Users/jacob/Desktop/cquery/tests/constructors/make_functions.h"], + "types": [{ + "id": 0, + "usr": "c:make_functions.cc@41", + "uses": ["4:1-4:2"] + }, { + "id": 1, + "usr": "c:make_functions.cc@53", + "uses": ["4:15-4:19"] + }, { + "id": 2, + "usr": "c:make_functions.cc@139", + "uses": ["9:1-9:2"] + }, { + "id": 3, + "usr": "c:make_functions.cc@151", + "uses": ["9:16-9:20"] + }, { + "id": 4, + "usr": "c:@S@Foobar", + "uses": ["14:14-14:20", "15:14-15:20", "16:14-16:20", "17:15-17:21"] + }, { + "id": 5, + "usr": "c:@S@Bar", + "uses": ["16:29-16:32", "17:30-17:33"] }], "funcs": [{ "id": 0, @@ -70,9 +125,9 @@ OUTPUT: "detailed_name": "T *MakeUnique(Args &&...)", "is_constructor": false, "parameter_type_descriptions": ["Args &&..."], - "definition_spelling": "2:4-2:14", - "definition_extent": "2:1-4:2", - "callers": ["6@20:3-20:13", "6@21:3-21:13", "6@22:3-22:13"] + "definition_spelling": "4:4-4:14", + "definition_extent": "4:1-6:2", + "callers": ["2@14:3-14:13", "2@15:3-15:13", "2@16:3-16:13"] }, { "id": 1, "usr": "c:@FT@>2#T#pTmaKE_NoRefs#Pt0.1#*t0.0#", @@ -80,78 +135,59 @@ OUTPUT: "detailed_name": "T *maKE_NoRefs(Args...)", "is_constructor": false, "parameter_type_descriptions": ["Args..."], - "definition_spelling": "7:4-7:15", - "definition_extent": "7:1-9:2", - "callers": ["6@23:3-23:14"] + "definition_spelling": "9:4-9:15", + "definition_extent": "9:1-11:2", + "callers": ["2@17:3-17:14"] }, { "id": 2, - "usr": "c:@S@Foobar@F@Foobar#", - "short_name": "Foobar", - "detailed_name": "void Foobar::Foobar()", - "is_constructor": true, - "definition_spelling": "14:3-14:9", - "definition_extent": "14:3-14:14", - "declaring_type": 5 - }, { - "id": 3, - "usr": "c:@S@Foobar@F@Foobar#I#", - "short_name": "Foobar", - "detailed_name": "void Foobar::Foobar(int)", - "is_constructor": true, - "parameter_type_descriptions": ["int"], - "definition_spelling": "15:3-15:9", - "definition_extent": "15:3-15:17", - "declaring_type": 5 - }, { - "id": 4, - "usr": "c:@S@Foobar@F@Foobar#&&I#*$@S@Bar#*b#", - "short_name": "Foobar", - "detailed_name": "void Foobar::Foobar(int &&, Bar *, bool *)", - "is_constructor": true, - "parameter_type_descriptions": ["int &&", "Bar *", "bool *"], - "definition_spelling": "16:3-16:9", - "definition_extent": "16:3-16:32", - "declaring_type": 5 - }, { - "id": 5, - "usr": "c:@S@Foobar@F@Foobar#I#*$@S@Bar#*b#", - "short_name": "Foobar", - "detailed_name": "void Foobar::Foobar(int, Bar *, bool *)", - "is_constructor": true, - "parameter_type_descriptions": ["int", "Bar *", "bool *"], - "definition_spelling": "17:3-17:9", - "definition_extent": "17:3-17:30", - "declaring_type": 5 - }, { - "id": 6, "usr": "c:@F@caller22#", "short_name": "caller22", "detailed_name": "void caller22()", "is_constructor": false, - "definition_spelling": "19:6-19:14", - "definition_extent": "19:1-24:2", - "callees": ["0@20:3-20:13", "0@21:3-21:13", "0@22:3-22:13", "1@23:3-23:14"] + "definition_spelling": "13:6-13:14", + "definition_extent": "13:1-18:2", + "callees": ["0@14:3-14:13", "0@15:3-15:13", "0@16:3-16:13", "1@17:3-17:14"] + }, { + "id": 3, + "usr": "c:@S@Foobar@F@Foobar#", + "is_constructor": false, + "callers": ["~-1@14:3-14:13"] + }, { + "id": 4, + "usr": "c:@S@Foobar@F@Foobar#I#", + "is_constructor": false, + "callers": ["~-1@15:3-15:13"] + }, { + "id": 5, + "usr": "c:@S@Foobar@F@Foobar#&&I#*$@S@Bar#*b#", + "is_constructor": false, + "callers": ["~-1@16:3-16:13"] + }, { + "id": 6, + "usr": "c:@S@Foobar@F@Foobar#I#*$@S@Bar#*b#", + "is_constructor": false, + "callers": ["~-1@17:3-17:14"] }], "vars": [{ "id": 0, - "usr": "c:make_functions.cc@55@FT@>2#T#pTMakeUnique#P&&t0.1#*t0.0#@args", + "usr": "c:make_functions.cc@86@FT@>2#T#pTMakeUnique#P&&t0.1#*t0.0#@args", "short_name": "args", "detailed_name": "Args &&... args", - "definition_spelling": "2:25-2:29", - "definition_extent": "2:15-2:29", + "definition_spelling": "4:25-4:29", + "definition_extent": "4:15-4:29", "is_local": true, "is_macro": false, - "uses": ["2:25-2:29"] + "uses": ["4:25-4:29"] }, { "id": 1, - "usr": "c:make_functions.cc@154@FT@>2#T#pTmaKE_NoRefs#Pt0.1#*t0.0#@args", + "usr": "c:make_functions.cc@185@FT@>2#T#pTmaKE_NoRefs#Pt0.1#*t0.0#@args", "short_name": "args", "detailed_name": "Args... args", - "definition_spelling": "7:24-7:28", - "definition_extent": "7:16-7:28", + "definition_spelling": "9:24-9:28", + "definition_extent": "9:16-9:28", "is_local": true, "is_macro": false, - "uses": ["7:24-7:28"] + "uses": ["9:24-9:28"] }] } */ diff --git a/tests/constructors/make_functions.h b/tests/constructors/make_functions.h new file mode 100644 index 00000000..f119cfb8 --- /dev/null +++ b/tests/constructors/make_functions.h @@ -0,0 +1,10 @@ +struct Bar {}; + +class Foobar { + public: + Foobar() {} + Foobar(int) {} + Foobar(int&&, Bar*, bool*) {} + Foobar(int, Bar*, bool*) {} +}; +