Skip to content
Open
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
23 changes: 23 additions & 0 deletions src/xml_parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,29 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
}
else if(!IsReservedAttribute(port_name))
{
// The name may still match a declared port. validatePortName only rejects
// a leading digit, so a SubTree model can declare
// <input_port name="_myPort"/>, a name IsAllowedPortName refuses and no
// C++ InputPort() can create. Falling through to other_attributes would
// drop the remapping without a word and leave the node on its declared
// default, so report it instead.
bool is_declared_port = manifest != nullptr && manifest->ports.count(port_name) > 0;
if(!is_declared_port && node_type == NodeType::SUBTREE)
{
auto model_it = subtree_models.find(type_ID);
is_declared_port = model_it != subtree_models.end() &&
model_it->second.ports.count(port_name) > 0;
}
if(is_declared_port)
{
const std::string reason = " and is declared, but the name cannot be used"
" as a port. A port name must begin with an"
" alphabetic character. Rename the port, for"
" example [_my_port] to [my_port].";
throw RuntimeError(StrCat("A port with name [", port_name,
"] is found in the XML (", type_ID, ", line ",
std::to_string(element->GetLineNum()), ")", reason));
}
other_attributes[port_name] = port_value;
}
}
Expand Down
76 changes: 76 additions & 0 deletions tests/gtest_subtree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1090,3 +1090,79 @@ TEST(SubTree, LiteralNonBooleanPortsRejectLogicalNot)
ASSERT_THROW((void)tree.tickWhileRunning(), RuntimeError);
ASSERT_EQ(tree.subtrees[1]->blackboard->get<std::string>("value"), "1not_bool");
}

// A SubTree model can declare a port whose name no node can bind.
// validatePortName only rejects a leading digit, while IsAllowedPortName, which
// classifies instance attributes and which InputPort()/OutputPort() enforce,
// requires an alphabetic first character. Before the fix the remapping below was
// diverted into other_attributes and the SubTree read its declared default, with
// no exception and no log.
TEST(SubTree, DeclaredPortNameThatCannotBeBound)
{
static const char* xml_text = R"(
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<Sequence>
<Script code="outer:='from_parent'"/>
<SubTree ID="Sub" _myPort="{outer}"/>
</Sequence>
</BehaviorTree>
<BehaviorTree ID="Sub">
<AlwaysSuccess/>
</BehaviorTree>
<TreeNodesModel>
<SubTree ID="Sub">
<input_port name="_myPort" default="never_wired"/>
</SubTree>
</TreeNodesModel>
</root>)";

BehaviorTreeFactory factory;
EXPECT_THROW(factory.createTreeFromText(xml_text), RuntimeError);
}

// The same underscore attribute on a SubTree that does not declare it is not a
// port. It must keep landing in other_attributes and must not throw.
TEST(SubTree, UndeclaredUnderscoreAttributeIsNotAPort)
{
static const char* xml_text = R"(
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<SubTree ID="Sub" _my_editor_state="true"/>
</BehaviorTree>
<BehaviorTree ID="Sub">
<AlwaysSuccess/>
</BehaviorTree>
<TreeNodesModel>
<SubTree ID="Sub">
<input_port name="goal" default="g"/>
</SubTree>
</TreeNodesModel>
</root>)";

BehaviorTreeFactory factory;
EXPECT_NO_THROW(factory.createTreeFromText(xml_text));
}

// A declared port nobody remaps stays inert and keeps loading, so existing trees
// carrying such a declaration are unaffected.
TEST(SubTree, DeclaredUnbindablePortLoadsWhenNotRemapped)
{
static const char* xml_text = R"(
<root BTCPP_format="4" main_tree_to_execute="MainTree">
<BehaviorTree ID="MainTree">
<SubTree ID="Sub"/>
</BehaviorTree>
<BehaviorTree ID="Sub">
<AlwaysSuccess/>
</BehaviorTree>
<TreeNodesModel>
<SubTree ID="Sub">
<input_port name="_myPort" default="inert"/>
</SubTree>
</TreeNodesModel>
</root>)";

BehaviorTreeFactory factory;
EXPECT_NO_THROW(factory.createTreeFromText(xml_text));
}
Loading