implement cycle detection for type aliases (links)

Since the detection happens after loading the config tree, it is necessary to expose the links and subtypes of the types alias and composite, respectively.

This implementation builds a graph of all links to detect cycles, ignoring all other types.

To handle links in elements of lists, all subtypes of composite types are visited, keeping the type name of the parent composite type.

Signed-off-by: Rafael Fillipe Silva <rfsc.mori@gmail.com>
This commit is contained in:
Rafael Fillipe Silva 2024-02-25 02:38:43 +00:00 committed by Pentarctagon
parent c925737f54
commit e24c13a1b7
3 changed files with 94 additions and 0 deletions

View file

@ -69,6 +69,7 @@ class wml_type_alias : public wml_type {
public:
wml_type_alias(const std::string& name, const std::string& link) : wml_type(name), link_(link) {}
bool matches(const config_attribute_value& value, const map& type_map) const override;
const std::string& link() const {return link_;}
};
/**
@ -84,6 +85,7 @@ public:
{
subtypes_.push_back(type);
}
const std::vector<std::shared_ptr<wml_type>>& subtypes() const {return subtypes_;}
};
/**

View file

@ -18,9 +18,11 @@
#include "filesystem.hpp"
#include "log.hpp"
#include "serialization/preprocessor.hpp"
#include "serialization/schema/type.hpp"
#include "serialization/string_utils.hpp"
#include "utils/back_edge_detector.hpp"
#include "wml_exception.hpp"
#include <boost/graph/adjacency_list.hpp>
#include <tuple>
namespace schema_validation
@ -154,6 +156,19 @@ static std::string inheritance_cycle_error(const std::string& file,
return ss.str();
}
static std::string link_cycle_error(const std::string& file,
int line,
const std::string& tag,
const std::string& value,
bool flag_exception)
{
std::ostringstream ss;
ss << "Link cycle from " << tag << " to " << value << " found\n"
<< at(file, line);
print_output(ss.str(), flag_exception);
return ss.str();
}
static std::string missing_super_error(const std::string& file,
int line,
const std::string& tag,
@ -285,10 +300,75 @@ bool schema_validator::read_config_file(const std::string& filename)
}
}
detect_link_cycles(filename);
config_read_ = true;
return true;
}
void schema_validator::detect_link_cycles(const std::string& filename) {
link_graph_t link_graph;
link_graph_map_t link_map;
for (auto [type_name, type] : types_) {
collect_link_source(link_graph, link_map, type_name, type.get());
}
boost::depth_first_search(link_graph,
boost::visitor(utils::back_edge_detector([&](const link_graph_t::edge_descriptor edge) {
const auto source = std::find_if(link_map.begin(), link_map.end(),
[&](const auto& link) { return link.second == boost::source(edge, link_graph); });
assert(source != link_map.end());
const auto target = std::find_if(link_map.begin(), link_map.end(),
[&](const auto& link) { return link.second == boost::target(edge, link_graph); });
assert(target != link_map.end());
const auto& alias_type = link_graph[source->second];
const auto& link_type = link_graph[target->second];
throw abstract_validator::error(link_cycle_error(filename, 0, alias_type, link_type, false));
})));
}
void schema_validator::collect_link_source(link_graph_t& link_graph, link_graph_map_t& link_map, const std::string& type_name, const wml_type* type) {
if (auto alias = dynamic_cast<const wml_type_alias*>(type)) {
auto it = types_.find(alias->link());
if (it != types_.end()) {
collect_link_target(link_graph, link_map, alias->link(), it->second.get(), alias);
}
} else if (auto composite = dynamic_cast<const wml_type_composite*>(type)) {
for(auto elem : composite->subtypes()) {
collect_link_source(link_graph, link_map, type_name, elem.get());
}
}
}
void schema_validator::collect_link_target(link_graph_t& link_graph, link_graph_map_t& link_map, const std::string& type_name, const wml_type* type, const wml_type_alias* alias) {
if (auto link = dynamic_cast<const wml_type_alias*>(type)) {
if (link_map.find(alias) == link_map.end()) {
link_map.emplace(
alias,
boost::add_vertex(type_name, link_graph));
}
if (link_map.find(link) == link_map.end()) {
link_map.emplace(
link,
boost::add_vertex(alias->link(), link_graph));
}
boost::add_edge(link_map[alias], link_map[link], link_graph);
} else if (auto composite = dynamic_cast<const wml_type_composite*>(type)) {
for(auto elem : composite->subtypes()) {
collect_link_target(link_graph, link_map, type_name, elem.get(), alias);
}
}
}
/*
* Please, @Note that there is some magic in pushing and poping to/from stacks.
* assume they all are on their place due to parser algorithm

View file

@ -199,6 +199,18 @@ private:
int start_line,
const std::string& file,
std::vector<const wml_tag*>& visited);
using link_graph_t = boost::adjacency_list<boost::vecS,
boost::vecS,
boost::directedS,
std::string>;
using link_graph_map_t = std::map<const wml_type_alias*,
link_graph_t::vertex_descriptor>;
void detect_link_cycles(const std::string& filename);
void collect_link_source(link_graph_t& link_graph, link_graph_map_t& link_map, const std::string& type_name, const wml_type* type);
void collect_link_target(link_graph_t& link_graph, link_graph_map_t& link_map, const std::string& type_name, const wml_type* type, const wml_type_alias* alias);
};
// A validator specifically designed for validating a schema