Index std::make_unique and similar functions as constructor calls.

This commit is contained in:
Jacob Dufault 2017-11-08 19:55:13 -08:00
parent e7de24764e
commit 96894ae996
5 changed files with 300 additions and 112 deletions

View File

@ -12,6 +12,7 @@
#include <algorithm>
#include <chrono>
#include <climits>
// 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<std::string> BuildTypeDesc(clang::Cursor cursor) {
std::vector<std::string> 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<IndexFuncRef>* 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<std::string> param_type_desc;
Usr usr;
};
std::unordered_map<Usr, std::vector<Constructor>> 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<std::string> TryFindConstructorUsr(
const std::string& type_usr,
const std::vector<std::string>& 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<Constructor>& 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<CXFile> seen_cx_files;
std::vector<std::string> 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<IndexParam*>(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<IndexParam*>(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<clang::Cursor> 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<std::string> 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<std::string> 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;
}

View File

@ -181,7 +181,6 @@ Cursor Cursor::get_semantic_parent() const {
std::vector<Cursor> Cursor::get_arguments() const {
int size = clang_Cursor_getNumArguments(cx_cursor);
assert(size >= 0);
if (size < 0)
return std::vector<Cursor>();

View File

@ -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;

View File

@ -1,3 +1,5 @@
#include "make_functions.h"
template <typename T, typename... Args>
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<Foobar>();
MakeUnique<Foobar>(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"]
}]
}
*/

View File

@ -0,0 +1,10 @@
struct Bar {};
class Foobar {
public:
Foobar() {}
Foobar(int) {}
Foobar(int&&, Bar*, bool*) {}
Foobar(int, Bar*, bool*) {}
};