From b0a09ad9bbdffc8d68bbe0fc5445741105379dbb Mon Sep 17 00:00:00 2001 From: Davide Faconti Date: Sun, 1 Feb 2026 20:16:49 +0100 Subject: [PATCH 1/2] Fix #934: prevent segfault when substituting a SubTree node When a substitution rule replaced a SubTree with a TestNode, dynamic_cast returned nullptr but setSubtreeID was called unconditionally, causing a null pointer dereference. Add a null-check so setSubtreeID is only called on actual SubTree nodes. Co-Authored-By: Claude Opus 4.5 --- src/xml_parsing.cpp | 7 ++++++- tests/gtest_substitution.cpp | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index a11383041..d7f23b7e1 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -867,8 +867,13 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element, config.input_ports = port_remap; new_node = factory->instantiateTreeNode(instance_name, toStr(NodeType::SUBTREE), config); + // If a substitution rule replaced the SubTree with a different node + // (e.g. a TestNode), the dynamic_cast will return nullptr. auto subtree_node = dynamic_cast(new_node.get()); - subtree_node->setSubtreeID(type_ID); + if(subtree_node) + { + subtree_node->setSubtreeID(type_ID); + } } else { diff --git a/tests/gtest_substitution.cpp b/tests/gtest_substitution.cpp index 44f6f37c3..cb97570bc 100644 --- a/tests/gtest_substitution.cpp +++ b/tests/gtest_substitution.cpp @@ -50,3 +50,39 @@ TEST(Substitution, Parser) ASSERT_EQ(*std::get_if(&rules.at("actionC")), "NotAConfig"); } + +// Regression test for issue #934: segfault when substituting a SubTree node +TEST(Substitution, SubTreeNodeSubstitution) +{ + static const char* parent_xml = R"( + + + + + + )"; + + static const char* child_xml = R"( + + + + + + )"; + + BehaviorTreeFactory factory; + factory.registerBehaviorTreeFromText(parent_xml); + factory.registerBehaviorTreeFromText(child_xml); + + BT::TestNodeConfig config; + config.return_status = BT::NodeStatus::SUCCESS; + factory.addSubstitutionRule("child", config); + + // This should not crash (was a segfault before the fix) + Tree tree; + ASSERT_NO_THROW(tree = factory.createTree("Parent")); + + // The substituted tree should tick successfully + auto status = tree.tickWhileRunning(); + ASSERT_EQ(status, BT::NodeStatus::SUCCESS); +} From 10b9ae53ef891e138ac7df33b86102ab43cddd5d Mon Sep 17 00:00:00 2001 From: Davide Faconti Date: Sun, 1 Feb 2026 20:29:17 +0100 Subject: [PATCH 2/2] Fix clang-tidy readability-implicit-bool-conversion warnings Use explicit != nullptr comparisons for pointer-to-bool conversions flagged by clang-tidy. Co-Authored-By: Claude Opus 4.5 --- src/tree_node.cpp | 2 +- src/xml_parsing.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tree_node.cpp b/src/tree_node.cpp index d464d6af9..16171a135 100644 --- a/src/tree_node.cpp +++ b/src/tree_node.cpp @@ -481,7 +481,7 @@ AnyPtrLocked BT::TreeNode::getLockedPortContent(const std::string& key) { const auto bb_key = std::string(*remapped_key); auto result = _p->config.blackboard->getAnyLocked(bb_key); - if(!result && _p->config.manifest) + if(!result && _p->config.manifest != nullptr) { // Entry doesn't exist yet. Create it using the port's type info // from the manifest so that getLockedPortContent works even when diff --git a/src/xml_parsing.cpp b/src/xml_parsing.cpp index d7f23b7e1..7c2e6004c 100644 --- a/src/xml_parsing.cpp +++ b/src/xml_parsing.cpp @@ -870,7 +870,7 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element, // If a substitution rule replaced the SubTree with a different node // (e.g. a TestNode), the dynamic_cast will return nullptr. auto subtree_node = dynamic_cast(new_node.get()); - if(subtree_node) + if(subtree_node != nullptr) { subtree_node->setSubtreeID(type_ID); }