diff --git a/include/behaviortree_cpp/basic_types.h b/include/behaviortree_cpp/basic_types.h index b61d49c16..4bebc687c 100644 --- a/include/behaviortree_cpp/basic_types.h +++ b/include/behaviortree_cpp/basic_types.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -447,6 +449,12 @@ template "and must start with an alphabetic character. " "Underscore is reserved."); } + if(std::any_of(sname.begin(), sname.end(), + [](unsigned char c) { return std::isspace(c); })) + { + throw RuntimeError( + StrCat("The name of a port must not contain whitespace: '", sname, "'")); + } std::pair out; diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index cf45c568b..bfa134f62 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -10,6 +10,8 @@ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include +#include #include #include #include @@ -17,7 +19,9 @@ #include #include #include +#include #include + #include "behaviortree_cpp/basic_types.h" #include "behaviortree_cpp/utils/strcat.hpp" @@ -214,6 +218,13 @@ void BT::XMLParser::PImpl::loadSubtreeModel(const XMLElement* xml_root) { throw RuntimeError("Missing attribute [name] in port (SubTree model)"); } + std::string_view sname(name); + if(std::any_of(sname.begin(), sname.end(), + [](unsigned char c) { return std::isspace(c); })) + { + throw RuntimeError( + StrCat("The name of a port must not contain whitespace: '", sname, "'")); + } 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..abb2809fc 100644 --- a/tests/gtest_ports.cpp +++ b/tests/gtest_ports.cpp @@ -861,3 +861,13 @@ TEST(PortTest, VectorAny) ASSERT_NO_THROW(status = tree.tickOnce()); ASSERT_EQ(status, NodeStatus::FAILURE); } + +TEST(PortTest, WhitespaceInPortName) +{ + ASSERT_ANY_THROW(BT::InputPort("port name")); + ASSERT_ANY_THROW(BT::InputPort("port\tname")); + ASSERT_ANY_THROW(BT::InputPort("port\nname")); + ASSERT_ANY_THROW(BT::InputPort(" leading")); + ASSERT_ANY_THROW(BT::InputPort("trailing ")); + ASSERT_NO_THROW(BT::InputPort("valid_port_name")); +} diff --git a/tests/gtest_subtree.cpp b/tests/gtest_subtree.cpp index c90a2597f..479b3ffc1 100644 --- a/tests/gtest_subtree.cpp +++ b/tests/gtest_subtree.cpp @@ -620,6 +620,48 @@ TEST(SubTree, SubtreeModels) tree.tickWhileRunning(); } +TEST(SubTree, WhitespaceInSubtreeModel) +{ + // clang-format off + + static const char* xml_text = R"( + + + + + + + + + + + + + + + + + + + + )"; + + // clang-format on + + BehaviorTreeFactory factory; + try + { + auto _ = factory.createTreeFromText(xml_text); + } + catch(RuntimeError e) + { + EXPECT_NE(std::string_view(e.what()).find("not contain whitespace"), + std::string_view::npos); + return; + } + FAIL() << "Exception was not thrown."; +} + class PrintToConsole : public BT::SyncActionNode { public: