Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions src/xml_parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <string>
#include <tuple>
#include <typeindex>
#include <unordered_set>

#if defined(_MSVC_LANG) && !defined(__clang__)
#define __bt_cplusplus (_MSC_VER == 1900 ? 201103L : _MSVC_LANG)
Expand Down Expand Up @@ -196,7 +197,8 @@ struct XMLParser::PImpl
void recursivelyCreateSubtree(const std::string& tree_ID, const std::string& tree_path,
const std::string& prefix_path, Tree& output_tree,
Blackboard::Ptr blackboard,
const TreeNode::Ptr& root_node);
const TreeNode::Ptr& root_node,
std::unordered_set<std::string>& ancestors);

void getPortsRecursively(const XMLElement* element,
std::vector<std::string>& output_ports);
Expand Down Expand Up @@ -703,8 +705,9 @@ Tree XMLParser::instantiateTree(const Blackboard::Ptr& root_blackboard,
"root_blackboard");
}

std::unordered_set<std::string> ancestors;
_p->recursivelyCreateSubtree(main_tree_ID, {}, {}, output_tree, root_blackboard,
TreeNode::Ptr());
TreeNode::Ptr(), ancestors);
output_tree.initialize();
return output_tree;
}
Expand Down Expand Up @@ -1000,13 +1003,16 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
return new_node;
}

void BT::XMLParser::PImpl::recursivelyCreateSubtree(const std::string& tree_ID,
const std::string& tree_path,
const std::string& prefix_path,
Tree& output_tree,
Blackboard::Ptr blackboard,
const TreeNode::Ptr& root_node)
void BT::XMLParser::PImpl::recursivelyCreateSubtree(
const std::string& tree_ID, const std::string& tree_path,
const std::string& prefix_path, Tree& output_tree, Blackboard::Ptr blackboard,
const TreeNode::Ptr& root_node, std::unordered_set<std::string>& ancestors)
{
if(!ancestors.insert(tree_ID).second)
{
throw RuntimeError("Recursive behavior tree cycle detected: tree '", tree_ID,
"' references itself (directly or indirectly)");
}
std::function<void(const TreeNode::Ptr&, Tree::Subtree::Ptr, std::string,
const XMLElement*)>
recursiveStep;
Expand Down Expand Up @@ -1135,7 +1141,7 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree(const std::string& tree_ID,
recursivelyCreateSubtree(subtree_ID,
subtree_path, // name
subtree_path + "/", //prefix
output_tree, new_bb, node);
output_tree, new_bb, node, ancestors);
}
};

Expand All @@ -1157,6 +1163,7 @@ void BT::XMLParser::PImpl::recursivelyCreateSubtree(const std::string& tree_ID,
output_tree.subtrees.push_back(new_tree);

recursiveStep(root_node, new_tree, prefix_path, root_element);
ancestors.erase(tree_ID);
}

void XMLParser::PImpl::getPortsRecursively(const XMLElement* element,
Expand Down
80 changes: 80 additions & 0 deletions tests/gtest_subtree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,86 @@ TEST(SubTree, SubtreeNameNotRegistered)
ASSERT_ANY_THROW(factory.registerBehaviorTreeFromText(xml_text));
}

TEST(SubTree, RecursiveSubtree)
{
// clang-format off

static const char* xml_text = R"(
<root BTCPP_format="4" >
<BehaviorTree ID="MainTree">
<Sequence name="root">
<AlwaysSuccess/>
<SubTree ID="MainTree" />
</Sequence>
</BehaviorTree>
</root>
)";

// clang-format on
BehaviorTreeFactory factory;

ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_text));
}

TEST(SubTree, RecursiveCycle)
{
// clang-format off

static const char* xml_text = R"(
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<Sequence name="root">
<AlwaysSuccess/>
<SubTree ID="TreeA" />
</Sequence>
</BehaviorTree>
<BehaviorTree ID="TreeA">
<Sequence name="root">
<AlwaysSuccess/>
<SubTree ID="TreeB" />
</Sequence>
</BehaviorTree>
<BehaviorTree ID="TreeB">
<Sequence name="root">
<AlwaysSuccess/>
<SubTree ID="MainTree" />
</Sequence>
</BehaviorTree>
</root>
)";

// clang-format on
BehaviorTreeFactory factory;

ASSERT_ANY_THROW(auto tree = factory.createTreeFromText(xml_text));
}

TEST(SubTree, SubstringTreeIDsAreNotRecursive)
{
// Verify that tree IDs which are substrings of each other do NOT
// incorrectly trigger the recursive cycle detection.
// clang-format off

static const char* xml_text = R"(
<root BTCPP_format="4" main_tree_to_execute="Tree">
<BehaviorTree ID="Tree">
<SubTree ID="TreeABC" />
</BehaviorTree>
<BehaviorTree ID="TreeABC">
<AlwaysSuccess/>
</BehaviorTree>
</root>
)";

// clang-format on
BehaviorTreeFactory factory;

ASSERT_NO_THROW(auto tree = factory.createTreeFromText(xml_text));
}

// Test for Groot2 issue #56: duplicate _fullpath when multiple subtrees have the same name
// https://github.com/BehaviorTree/Groot2/issues/56
//
Expand Down
Loading