diff --git a/include/behaviortree_cpp/basic_types.h b/include/behaviortree_cpp/basic_types.h index b61d49c16..99f62bf6b 100644 --- a/include/behaviortree_cpp/basic_types.h +++ b/include/behaviortree_cpp/basic_types.h @@ -443,8 +443,9 @@ template auto sname = static_cast(name); if(!IsAllowedPortName(sname)) { - throw RuntimeError("The name of a port must not be `name` or `ID` " - "and must start with an alphabetic character. " + throw RuntimeError("The name of a port must not be `name` or `ID`, " + "must start with an alphabetic character, " + "and must not contain whitespace. " "Underscore is reserved."); } diff --git a/src/basic_types.cpp b/src/basic_types.cpp index ab5e016bb..c6cfb58b4 100644 --- a/src/basic_types.cpp +++ b/src/basic_types.cpp @@ -2,6 +2,8 @@ #include "behaviortree_cpp/tree_node.h" #include "behaviortree_cpp/json_export.h" +#include +#include #include #include #include @@ -443,6 +445,10 @@ bool IsAllowedPortName(StringView str) { return false; } + if(std::any_of(str.begin(), str.end(), [](unsigned char c) { return std::isspace(c); })) + { + return false; + } return !IsReservedAttribute(str); } diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index cf45c568b..740120c0c 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -214,6 +214,14 @@ void BT::XMLParser::PImpl::loadSubtreeModel(const XMLElement* xml_root) { throw RuntimeError("Missing attribute [name] in port (SubTree model)"); } + if(!IsAllowedPortName(name)) + { + throw RuntimeError(StrCat("The port name [", name, "] in SubTree [", + subtree_id, + "] is not valid. Port names must start " + "with an alphabetic character and must " + "not contain whitespace.")); + } if(auto default_value = port_node->Attribute("default")) { port.setDefaultValue(default_value); diff --git a/tests/gtest_ports.cpp b/tests/gtest_ports.cpp index eaaf9733a..d4b21941a 100644 --- a/tests/gtest_ports.cpp +++ b/tests/gtest_ports.cpp @@ -861,3 +861,35 @@ TEST(PortTest, VectorAny) ASSERT_NO_THROW(status = tree.tickOnce()); ASSERT_EQ(status, NodeStatus::FAILURE); } + +TEST(PortTest, IsAllowedPortNameRejectsWhitespace) +{ + EXPECT_FALSE(IsAllowedPortName("my port")); + EXPECT_FALSE(IsAllowedPortName("port\tname")); + EXPECT_FALSE(IsAllowedPortName(" leading")); + EXPECT_FALSE(IsAllowedPortName("trailing ")); + EXPECT_FALSE(IsAllowedPortName("has\nnewline")); + + // Sanity check: valid names still work. + EXPECT_TRUE(IsAllowedPortName("valid_port")); + EXPECT_TRUE(IsAllowedPortName("anotherPort123")); +} + +TEST(PortTest, WhitespacePortNameInSubtreeModelThrows) +{ + BT::BehaviorTreeFactory factory; + + const std::string xml_txt = R"( + + + + + + + + + + )"; + + EXPECT_THROW(factory.createTreeFromText(xml_txt), BT::RuntimeError); +}