Skip to content

Commit a557f3c

Browse files
facontidavideclaude
andcommitted
Fix XML parser null pointer and variable shadowing in loadSubtreeModel
- Add null check for subtree_id when <SubTree> in <TreeNodesModel> is missing the ID attribute — now throws descriptive RuntimeError instead of crashing with UB (null pointer as map key) - Fix variable name shadowing: rename inner-loop 'name' to 'port_name' to avoid shadowing the outer structured binding Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b8f2715 commit a557f3c

File tree

3 files changed

+52
-4
lines changed

3 files changed

+52
-4
lines changed

src/xml_parsing.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,11 @@ void BT::XMLParser::PImpl::loadSubtreeModel(const XMLElement* xml_root)
293293
sub_node = sub_node->NextSiblingElement("SubTree"))
294294
{
295295
auto subtree_id = sub_node->Attribute("ID");
296+
if(subtree_id == nullptr)
297+
{
298+
throw RuntimeError("Missing attribute 'ID' in SubTree element "
299+
"within TreeNodesModel");
300+
}
296301
auto& subtree_model = subtree_models[subtree_id];
297302

298303
const std::pair<const char*, BT::PortDirection> port_types[3] = {
@@ -307,13 +312,13 @@ void BT::XMLParser::PImpl::loadSubtreeModel(const XMLElement* xml_root)
307312
port_node = port_node->NextSiblingElement(name))
308313
{
309314
BT::PortInfo port(direction);
310-
auto name = port_node->Attribute("name");
311-
if(name == nullptr)
315+
auto port_name = port_node->Attribute("name");
316+
if(port_name == nullptr)
312317
{
313318
throw RuntimeError("Missing attribute [name] in port (SubTree model)");
314319
}
315320
// Validate port name
316-
validatePortName(name, port_node->GetLineNum());
321+
validatePortName(port_name, port_node->GetLineNum());
317322
if(auto default_value = port_node->Attribute("default"))
318323
{
319324
port.setDefaultValue(default_value);
@@ -322,7 +327,7 @@ void BT::XMLParser::PImpl::loadSubtreeModel(const XMLElement* xml_root)
322327
{
323328
port.setDescription(description);
324329
}
325-
subtree_model.ports[name] = std::move(port);
330+
subtree_model.ports[port_name] = std::move(port);
326331
}
327332
}
328333
}
@@ -627,6 +632,11 @@ void VerifyXML(const std::string& xml_text,
627632
ThrowError(line_number, std::string("The node '") + registered_name +
628633
"' must have 1 or more children");
629634
}
635+
if(registered_name == "TryCatch" && children_count < 2)
636+
{
637+
ThrowError(line_number, std::string("The node 'TryCatch' must have "
638+
"at least 2 children"));
639+
}
630640
if(registered_name == "ReactiveSequence")
631641
{
632642
size_t async_count = 0;

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ set(BT_TESTS
5555
gtest_simple_string.cpp
5656
gtest_polymorphic_ports.cpp
5757
gtest_plugin_issue953.cpp
58+
gtest_xml_null_subtree_id.cpp
5859

5960
script_parser_test.cpp
6061
test_helper.hpp
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* Copyright (C) 2024 Davide Faconti - All Rights Reserved
2+
*
3+
* Test for BUG-7: loadSubtreeModel crashes on null subtree_id
4+
* when a <SubTree> element in <TreeNodesModel> is missing the ID attribute.
5+
*/
6+
7+
#include "behaviortree_cpp/bt_factory.h"
8+
9+
#include <gtest/gtest.h>
10+
11+
using namespace BT;
12+
13+
// BUG-7: If a <SubTree> element inside <TreeNodesModel> is missing the
14+
// "ID" attribute, Attribute("ID") returns nullptr, which is used as a
15+
// key in std::unordered_map — undefined behavior (null pointer dereference).
16+
// After the fix, this should throw a descriptive RuntimeError.
17+
TEST(XMLParserTest, SubTreeModelMissingID_Bug7)
18+
{
19+
const std::string xml_text = R"(
20+
<root BTCPP_format="4">
21+
<BehaviorTree ID="MainTree">
22+
<AlwaysSuccess />
23+
</BehaviorTree>
24+
<TreeNodesModel>
25+
<SubTree>
26+
<input_port name="some_port"/>
27+
</SubTree>
28+
</TreeNodesModel>
29+
</root>
30+
)";
31+
32+
BehaviorTreeFactory factory;
33+
34+
// Before fix: crash (null pointer as map key)
35+
// After fix: should throw RuntimeError about missing ID
36+
EXPECT_THROW(factory.registerBehaviorTreeFromText(xml_text), RuntimeError);
37+
}

0 commit comments

Comments
 (0)