Skip to content

Commit 88248a1

Browse files
committed
cleanup and fix validation
1 parent 468bb63 commit 88248a1

File tree

6 files changed

+61
-15
lines changed

6 files changed

+61
-15
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,3 @@ repos:
6161
- tomli
6262
args:
6363
[--toml=./pyproject.toml]
64-
65-
# Include What You Use (requires: apt install iwyu, and compile_commands.json)
66-
- repo: local
67-
hooks:
68-
- id: iwyu
69-
name: include-what-you-use
70-
entry: iwyu_tool -p build
71-
language: system
72-
files: \.(cpp|hpp|h)$
73-
exclude: ^3rdparty/|^include/behaviortree_cpp/contrib/
74-
pass_filenames: false
75-
stages: [manual]

include/behaviortree_cpp/action_node.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include <atomic>
2020
#include <future>
2121
#include <mutex>
22-
#include <thread>
2322

2423
namespace BT
2524
{

include/behaviortree_cpp/basic_types.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ struct Timestamp
348348

349349
[[nodiscard]] bool IsReservedAttribute(StringView str);
350350

351+
/// Returns the first forbidden character found in the name, or '\0' if valid.
352+
/// Forbidden characters include: space, tab, newline, CR, < > & " ' / \ : * ? | .
353+
/// and control characters (ASCII 0-31, 127). UTF-8 multibyte sequences are allowed.
354+
[[nodiscard]] char findForbiddenChar(StringView name);
355+
351356
class TypeInfo
352357
{
353358
public:

src/basic_types.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,10 +447,16 @@ bool IsAllowedPortName(StringView str)
447447
return false;
448448
}
449449
const char first_char = str.data()[0];
450+
// Port name cannot start with a digit
450451
if(std::isalpha(static_cast<unsigned char>(first_char)) == 0)
451452
{
452453
return false;
453454
}
455+
// Check for forbidden characters
456+
if(findForbiddenChar(str) != '\0')
457+
{
458+
return false;
459+
}
454460
return !IsReservedAttribute(str);
455461
}
456462

@@ -473,6 +479,36 @@ bool IsReservedAttribute(StringView str)
473479
return str == "name" || str == "ID" || str == "_autoremap";
474480
}
475481

482+
char findForbiddenChar(StringView name)
483+
{
484+
// Forbidden characters that break XML serialization or cause filesystem issues
485+
static constexpr std::array<char, 16> kForbiddenChars = {
486+
' ', '\t', '\n', '\r', '<', '>', '&', '"', '\'', '/', '\\', ':', '*', '?', '|', '.'
487+
};
488+
489+
for(const char c : name)
490+
{
491+
const auto uc = static_cast<unsigned char>(c);
492+
// Allow UTF-8 multibyte sequences (high bit set)
493+
if(uc >= 0x80)
494+
{
495+
continue;
496+
}
497+
// Block control characters (ASCII 0-31 and 127)
498+
if(uc < 32 || uc == 127)
499+
{
500+
return c;
501+
}
502+
// Check forbidden character list
503+
if(std::find(kForbiddenChars.begin(), kForbiddenChars.end(), c) !=
504+
kForbiddenChars.end())
505+
{
506+
return c;
507+
}
508+
}
509+
return '\0';
510+
}
511+
476512
Any convertFromJSON(StringView json_text, std::type_index type)
477513
{
478514
const nlohmann::json json = nlohmann::json::parse(json_text);

src/behavior_tree.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212

1313
#include "behaviortree_cpp/behavior_tree.h"
1414

15-
#include <cstring>
16-
1715
namespace BT
1816
{
1917
void applyRecursiveVisitor(const TreeNode* node,

src/xml_parsing.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ void BT::XMLParser::PImpl::loadSubtreeModel(const XMLElement* xml_root)
309309
{
310310
throw RuntimeError("Missing attribute [name] in port (SubTree model)");
311311
}
312+
// Validate port name
313+
validatePortName(name, port_node->GetLineNum());
312314
if(auto default_value = port_node->Attribute("default"))
313315
{
314316
port.setDefaultValue(default_value);
@@ -550,6 +552,11 @@ void VerifyXML(const std::string& xml_text,
550552
{
551553
ThrowError(line_number, "The tag <BehaviorTree> must have the attribute [ID]");
552554
}
555+
// Validate BehaviorTree ID as a model name
556+
if(!ID.empty())
557+
{
558+
validateModelName(ID, line_number);
559+
}
553560
if(registered_nodes.count(ID) != 0)
554561
{
555562
ThrowError(line_number, "The attribute [ID] of tag <BehaviorTree> must not use "
@@ -568,6 +575,8 @@ void VerifyXML(const std::string& xml_text,
568575
ThrowError(line_number,
569576
"<SubTree> with ID '" + ID + "' should not have any child");
570577
}
578+
// Validate SubTree ID as a model name
579+
validateModelName(ID, line_number);
571580
if(registered_nodes.count(ID) != 0)
572581
{
573582
ThrowError(line_number, "The attribute [ID] of tag <SubTree> must not use the "
@@ -578,6 +587,11 @@ void VerifyXML(const std::string& xml_text,
578587
{
579588
// use ID for builtin node types, otherwise use the element name
580589
const auto lookup_name = is_builtin ? ID : name;
590+
// Validate model name for custom node types (non-builtin element names)
591+
if(!is_builtin)
592+
{
593+
validateModelName(name, line_number);
594+
}
581595
const auto search = registered_nodes.find(lookup_name);
582596
const bool found = (search != registered_nodes.end());
583597
if(!found)
@@ -743,6 +757,12 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
743757
const char* attr_name = element->Attribute("name");
744758
const std::string instance_name = (attr_name != nullptr) ? attr_name : type_ID;
745759

760+
// Validate instance name if explicitly provided
761+
if(attr_name != nullptr)
762+
{
763+
validateInstanceName(instance_name, element->GetLineNum());
764+
}
765+
746766
const TreeNodeManifest* manifest = nullptr;
747767

748768
auto manifest_it = factory->manifests().find(type_ID);

0 commit comments

Comments
 (0)