interrogate_module: warn about circular dependencies between classes

This will help us catch cases wherein we inadvertently create a circular dependency between component libraries.  Right now it only checks inheritance and typedefs, but this can be expanded in the future.

Also, sorts the module initialization by dependency topology.  This isn't very important right now, but having this guarantee might help when refactoring module initialization in the future.
This commit is contained in:
rdb 2018-02-27 20:20:20 +01:00
parent bdb575e766
commit 8b6616814d

View File

@ -76,6 +76,79 @@ upcase_string(const string &str) {
}
*/
/**
* Finds a dependency cycle between the given dependency mapping, starting at
* the node that is already placed in the given cycle vector.
*/
static bool find_dependency_cycle(vector_string &cycle, map<string, set<string> > &dependencies) {
assert(!cycle.empty());
const set<string> &deps = dependencies[cycle.back()];
for (auto it = deps.begin(); it != deps.end(); ++it) {
auto it2 = std::find(cycle.begin(), cycle.end(), *it);
if (it2 != cycle.end()) {
// Chop off the part of the chain that is not relevant.
cycle.erase(cycle.begin(), it2);
cycle.push_back(*it);
return true;
}
// Recurse.
cycle.push_back(*it);
if (find_dependency_cycle(cycle, dependencies)) {
return true;
}
cycle.pop_back();
}
return false;
}
/**
* Given that a direct link has been established between the two libraries,
* finds the two types that make up this relationship and prints out the
* nature of their dependency.
*/
static bool print_dependent_types(const string &lib1, const string &lib2) {
for (int ti = 0; ti < interrogate_number_of_global_types(); ti++) {
TypeIndex thetype = interrogate_get_global_type(ti);
if (interrogate_type_has_module_name(thetype) &&
interrogate_type_has_library_name(thetype) &&
lib1 == interrogate_type_library_name(thetype) &&
module_name == interrogate_type_module_name(thetype)) {
// Get the dependencies for this library.
int num_derivations = interrogate_type_number_of_derivations(thetype);
for (int di = 0; di < num_derivations; ++di) {
TypeIndex basetype = interrogate_type_get_derivation(thetype, di);
if (interrogate_type_is_global(basetype) &&
interrogate_type_has_library_name(basetype) &&
interrogate_type_library_name(basetype) == lib2) {
cerr
<< " " << interrogate_type_scoped_name(thetype) << " ("
<< lib1 << ") inherits from "
<< interrogate_type_scoped_name(basetype) << " (" << lib2 << ")\n";
return true;
}
}
// It also counts if this is a typedef pointing to another type.
if (interrogate_type_is_typedef(thetype)) {
TypeIndex wrapped = interrogate_type_wrapped_type(thetype);
if (interrogate_type_is_global(wrapped) &&
interrogate_type_has_library_name(wrapped) &&
interrogate_type_library_name(wrapped) == lib2) {
cerr
<< " " << interrogate_type_scoped_name(thetype) << " ("
<< lib1 << ") is a typedef to "
<< interrogate_type_scoped_name(wrapped) << " (" << lib2 << ")\n";
}
}
}
}
return false;
}
int write_python_table_native(ostream &out) {
out << "\n#include \"dtoolbase.h\"\n"
<< "#include \"interrogate_request.h\"\n\n"
@ -83,7 +156,7 @@ int write_python_table_native(ostream &out) {
int count = 0;
vector_string libraries;
map<string, set<string> > dependencies;
// out << "extern \"C\" {\n";
@ -99,23 +172,112 @@ int write_python_table_native(ostream &out) {
// name add it to set of libraries
if (interrogate_function_has_library_name(function_index)) {
string library_name = interrogate_function_library_name(function_index);
if (std::find(libraries.begin(), libraries.end(), library_name) == libraries.end()) {
libraries.push_back(library_name);
}
dependencies[library_name];
}
// }
}
for (int ti = 0; ti < interrogate_number_of_types(); ti++) {
TypeIndex thetype = interrogate_get_type(ti);
for (int ti = 0; ti < interrogate_number_of_global_types(); ti++) {
TypeIndex thetype = interrogate_get_global_type(ti);
if (interrogate_type_has_module_name(thetype) && module_name == interrogate_type_module_name(thetype)) {
if (interrogate_type_has_library_name(thetype)) {
string library_name = interrogate_type_library_name(thetype);
set<string> &deps = dependencies[library_name];
// Get the dependencies for this library.
int num_derivations = interrogate_type_number_of_derivations(thetype);
for (int di = 0; di < num_derivations; ++di) {
TypeIndex basetype = interrogate_type_get_derivation(thetype, di);
if (interrogate_type_is_global(basetype) &&
interrogate_type_has_library_name(basetype)) {
string baselib = interrogate_type_library_name(basetype);
if (baselib != library_name) {
deps.insert(move(baselib));
}
}
}
if (interrogate_type_is_typedef(thetype)) {
TypeIndex wrapped = interrogate_type_wrapped_type(thetype);
if (interrogate_type_is_global(wrapped) &&
interrogate_type_has_library_name(wrapped)) {
string wrappedlib = interrogate_type_library_name(wrapped);
if (wrappedlib != library_name) {
deps.insert(move(wrappedlib));
}
}
}
}
}
}
// Now add the libraries in their proper ordering, based on dependencies.
vector_string libraries;
while (libraries.size() < dependencies.size()) {
// We have this check to make sure we don't enter an infinite loop.
bool added_any = false;
for (auto it = dependencies.begin(); it != dependencies.end(); ++it) {
const string &library_name = it->first;
set<string> &deps = dependencies[library_name];
// Remove the dependencies that have already been added from the deps.
if (!deps.empty()) {
for (auto li = libraries.begin(); li != libraries.end(); ++li) {
deps.erase(*li);
}
}
if (deps.empty()) {
// OK, no remaining dependencies, so we can add this.
if (std::find(libraries.begin(), libraries.end(), library_name) == libraries.end()) {
libraries.push_back(library_name);
added_any = true;
}
}
}
if (!added_any) {
// Oh dear, we must have hit a circular dependency. Go through the
// remaining libraries to figure it out and print it.
cerr << "Circular dependency between libraries detected:\n";
for (auto it = dependencies.begin(); it != dependencies.end(); ++it) {
const string &library_name = it->first;
set<string> &deps = dependencies[library_name];
if (deps.empty()) {
continue;
}
// But since it does indicate a potential architectural flaw, we do
// want to let the user know about this.
vector_string cycle;
cycle.push_back(library_name);
if (!find_dependency_cycle(cycle, dependencies)) {
continue;
}
assert(cycle.size() >= 2);
// Show the cycle of library dependencies.
auto ci = cycle.begin();
cerr << " " << *ci;
for (++ci; ci != cycle.end(); ++ci) {
cerr << " -> " << *ci;
}
cerr << "\n";
// Now print out the actual types that make up the cycle.
ci = cycle.begin();
string prev = *ci;
for (++ci; ci != cycle.end(); ++ci) {
print_dependent_types(prev, *ci);
prev = *ci;
}
// We have to arbitrarily break one of the dependencies in order to be
// able to proceed. Break the first dependency.
dependencies[cycle[0]].erase(cycle[1]);
}
}
}
vector_string::const_iterator ii;