handle super cycles while searching for a key or for a tag

A super cycle may be found during the validation, leading to an infinite loop. For example, calling find_key on an unknown key defined in the .cfg file may trigger this behavior if a cycle exists.

To handle this, a new version of these functions was introduced, keeping the original in the class' private scope. During the search there is a bookkeeping helper that will cause the routine to return nullptr if the search goes back to the initial tag context.
This commit is contained in:
Rafael Fillipe Silva 2023-12-20 00:08:49 -03:00 committed by Pentarctagon
parent f7511ea731
commit c64416bbb6
2 changed files with 42 additions and 7 deletions

View file

@ -108,10 +108,24 @@ void wml_tag::add_link(const std::string& link)
const wml_key* wml_tag::find_key(const std::string& name, const config& match, bool ignore_super) const
{
auto visited = std::vector<const wml_tag*>();
return find_key(name, match, ignore_super, visited);
}
const wml_key* wml_tag::find_key(const std::string& name, const config& match, bool ignore_super, std::vector<const wml_tag*>& visited) const
{
// Returns nullptr if a super cycle is detected.
if (std::find(visited.begin(), visited.end(), this) != visited.end()) {
return nullptr;
}
visited.push_back(this);
// Check the conditions first, so that conditional definitions
// override base definitions in the event of duplicates.
for(auto& cond : conditions_) {
if(cond.matches(match)) {
// Not considered for super cycle detection as super tags are ignored.
if(auto key = cond.find_key(name, match, true)) {
return key;
}
@ -137,13 +151,13 @@ const wml_key* wml_tag::find_key(const std::string& name, const config& match, b
for(auto& cond : conditions_) {
if(cond.matches(match)) {
// This results in a little redundancy (checking things twice) but at least it still works.
if(auto key = cond.find_key(name, match, false)) {
if(auto key = cond.find_key(name, match, false, visited)) {
return key;
}
}
}
for(auto& super_tag : super_refs_) {
if(const wml_key* found_key = super_tag->find_key(name, match)) {
if(const wml_key* found_key = super_tag->find_key(name, match, false, visited)) {
return found_key;
}
}
@ -164,6 +178,19 @@ const std::string* wml_tag::find_link(const std::string& name) const
const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& root, const config& match, bool ignore_super) const
{
auto visited = std::vector<const wml_tag*>();
return find_tag(fullpath, root, match, ignore_super, visited);
}
const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& root, const config& match, bool ignore_super, std::vector<const wml_tag*>& visited) const
{
// Returns nullptr if a super cycle is detected.
if (std::find(visited.begin(), visited.end(), this) != visited.end()) {
return nullptr;
}
visited.push_back(this);
if(fullpath.empty()) {
return nullptr;
}
@ -183,6 +210,7 @@ const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& roo
// override base definitions in the event of duplicates.
for(auto& cond : conditions_) {
if(cond.matches(match)) {
// Not considered for super cycle detection as super tags are ignored.
if(auto tag = cond.find_tag(fullpath, root, match, true)) {
return tag;
}
@ -194,13 +222,14 @@ const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& roo
if(next_path.empty()) {
return &(it_tags->second);
} else {
return it_tags->second.find_tag(next_path, root, match);
return it_tags->second.find_tag(next_path, root, match, false, visited);
}
}
const auto it_links = links_.find(name);
if(it_links != links_.end()) {
return root.find_tag(it_links->second + "/" + next_path, root, match);
// Reset cycle detection on links as we restart from the root.
return root.find_tag(it_links->second + "/" + next_path, root, match, false);
}
const auto it_fuzzy = std::find_if(tags_.begin(), tags_.end(), [&name](const tag_map::value_type& tag){
@ -213,7 +242,7 @@ const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& roo
if(next_path.empty()) {
return &(it_fuzzy->second);
} else {
return it_tags->second.find_tag(next_path, root, match);
return it_tags->second.find_tag(next_path, root, match, false, visited);
}
}
@ -221,13 +250,13 @@ const wml_tag* wml_tag::find_tag(const std::string& fullpath, const wml_tag& roo
for(auto& cond : conditions_) {
if(cond.matches(match)) {
// This results in a little redundancy (checking things twice) but at least it still works.
if(auto tag = cond.find_tag(fullpath, root, match, false)) {
if(auto tag = cond.find_tag(fullpath, root, match, false, visited)) {
return tag;
}
}
}
for(auto& super_tag : super_refs_) {
if(const wml_tag* found_tag = super_tag->find_tag(fullpath, root, match)) {
if(const wml_tag* found_tag = super_tag->find_tag(fullpath, root, match, false, visited)) {
return found_tag;
}
}

View file

@ -404,6 +404,12 @@ private:
/** Expands all "super", storing direct references for easier access. */
void expand(wml_tag& root);
/** Finds a key with super bookkeeping to handle super cycles. */
const wml_key* find_key(const std::string& name, const config& match, bool ignore_super, std::vector<const wml_tag*>& visited) const;
/** Finds a tag with super bookkeeping to handle super cycles. */
const wml_tag* find_tag(const std::string& fullpath, const wml_tag& root, const config& match, bool ignore_super, std::vector<const wml_tag*>& visited) const;
};
/**