From 36e32ead8db34970cdba5e5f98762cf007fd17c7 Mon Sep 17 00:00:00 2001 From: Davide Faconti Date: Fri, 30 Jan 2026 19:06:32 +0100 Subject: [PATCH 01/11] Fix #1029: correct left-associativity for arithmetic operators in script parser The scripting engine evaluated "A - B + C" as "A - (B + C)" instead of "(A - B) + C". The root cause was that string_concat's operand chain referenced math_sum, causing it to appear twice in lexy's operation list with conflicting binding power levels. Changed string_concat::operand from math_sum to math_prefix to eliminate the duplicate entry. Co-Authored-By: Claude Opus 4.5 --- .../behaviortree_cpp/scripting/operators.hpp | 5 ++- tests/script_parser_test.cpp | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/include/behaviortree_cpp/scripting/operators.hpp b/include/behaviortree_cpp/scripting/operators.hpp index bd6bfc863..7df8af4d0 100644 --- a/include/behaviortree_cpp/scripting/operators.hpp +++ b/include/behaviortree_cpp/scripting/operators.hpp @@ -747,7 +747,10 @@ struct Expression : lexy::expression_production return dsl::op(LEXY_LIT("..")); }(); - using operand = math_sum; + // Use math_prefix (not math_sum) to avoid duplicating math_sum/math_product + // in the operation list, which would break left-associativity of +/-/*// + // due to conflicting binding power levels in lexy's _operation_list_of. + using operand = math_prefix; }; // ~x diff --git a/tests/script_parser_test.cpp b/tests/script_parser_test.cpp index bc9346dd4..a0823a981 100644 --- a/tests/script_parser_test.cpp +++ b/tests/script_parser_test.cpp @@ -424,6 +424,42 @@ TEST(ParserTest, Issue595) ASSERT_EQ(0, counters[0]); } +// https://github.com/BehaviorTree/BehaviorTree.CPP/issues/1029 +TEST(ParserTest, OperatorAssociativity_Issue1029) +{ + BT::Ast::Environment environment = { BT::Blackboard::create(), {} }; + + auto GetResult = [&environment](const char* text) -> BT::Any { + return GetScriptResult(environment, text); + }; + + // Addition and subtraction are left-associative: + // "5 - 2 + 1" should be (5-2)+1 = 4, NOT 5-(2+1) = 2 + EXPECT_EQ(GetResult("5 - 2 + 1").cast(), 4.0); + + // "10 - 3 - 2" should be (10-3)-2 = 5, NOT 10-(3-2) = 9 + EXPECT_EQ(GetResult("10 - 3 - 2").cast(), 5.0); + + // "2 + 3 - 1" should be (2+3)-1 = 4 + EXPECT_EQ(GetResult("2 + 3 - 1").cast(), 4.0); + + // Multiplication and division are also left-associative: + // "12 / 3 / 2" should be (12/3)/2 = 2, NOT 12/(3/2) = 8 + EXPECT_EQ(GetResult("12 / 3 / 2").cast(), 2.0); + + // "12 / 3 * 2" should be (12/3)*2 = 8, NOT 12/(3*2) = 2 + EXPECT_EQ(GetResult("12 / 3 * 2").cast(), 8.0); + + // Mixed precedence: "2 + 3 * 4 - 1" should be 2+(3*4)-1 = 13 + EXPECT_EQ(GetResult("2 + 3 * 4 - 1").cast(), 13.0); + + // Verify string concatenation operator (..) still works after operand fix + EXPECT_EQ(GetResult("A:='hello'; B:=' world'; A .. B").cast(), "hello " + "world"); + // Chained concatenation (left-associative) + EXPECT_EQ(GetResult("A .. ' ' .. B").cast(), "hello world"); +} + TEST(ParserTest, NewLine) { BT::BehaviorTreeFactory factory; From 498af67af8096bd75c55447af5656857c200d081 Mon Sep 17 00:00:00 2001 From: Davide Faconti Date: Fri, 30 Jan 2026 19:42:03 +0100 Subject: [PATCH 02/11] Add regression test for #923: ValidateScript OOB read with large invalid scripts The bug (fixed in cb6c751) was that ValidateScript used a fixed char[2048] buffer for error messages, causing out-of-bounds reads when error output exceeded the buffer. The current code uses std::string with std::back_inserter. This adds a regression test using the original reproducer to ensure the fix holds. Co-Authored-By: Claude Opus 4.5 --- tests/script_parser_test.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/script_parser_test.cpp b/tests/script_parser_test.cpp index a0823a981..fccb6b3ce 100644 --- a/tests/script_parser_test.cpp +++ b/tests/script_parser_test.cpp @@ -460,6 +460,30 @@ TEST(ParserTest, OperatorAssociativity_Issue1029) EXPECT_EQ(GetResult("A .. ' ' .. B").cast(), "hello world"); } +// https://github.com/BehaviorTree/BehaviorTree.CPP/issues/923 +// Regression test: ValidateScript must not crash on large invalid scripts +// that produce error messages exceeding any fixed-size buffer. +TEST(ParserTest, ValidateScriptLargeError_Issue923) +{ + // Build an invalid script large enough to overflow the old 2048-byte buffer + std::string script; + for(int i = 0; i < 10; i++) + { + script += "+6e66>6666.6+66\r6>6;6e62=6+6e66>66666'; en';o';o'; en'; "; + script += "\x7f" + "n" + "\x7f" + "r;6.6+66.H>6+6" + "\x80" + "6" + "\x1e" + ";@e66"; + } + // Must not crash (old code used a fixed char[2048] buffer causing OOB read) + auto result = BT::ValidateScript(script); + EXPECT_FALSE(result); // invalid script, but no crash +} + TEST(ParserTest, NewLine) { BT::BehaviorTreeFactory factory; From d0aeddab133b87e2045ba445cbe4a29f487b4d28 Mon Sep 17 00:00:00 2001 From: Davide Faconti Date: Fri, 30 Jan 2026 19:43:49 +0100 Subject: [PATCH 03/11] Add regression test for #832: script compare with negative number Comparing against negative literals (e.g., "A!=-1") used to throw "operator cannot be mixed with previous operators" because the unary minus prefix was in a conflicting operator group. This was resolved by the #1029 fix (string_concat operand change). This commit adds a regression test covering both direct script evaluation and XML precondition usage. Co-Authored-By: Claude Opus 4.5 --- tests/script_parser_test.cpp | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/script_parser_test.cpp b/tests/script_parser_test.cpp index fccb6b3ce..4b9dd5212 100644 --- a/tests/script_parser_test.cpp +++ b/tests/script_parser_test.cpp @@ -460,6 +460,44 @@ TEST(ParserTest, OperatorAssociativity_Issue1029) EXPECT_EQ(GetResult("A .. ' ' .. B").cast(), "hello world"); } +// https://github.com/BehaviorTree/BehaviorTree.CPP/issues/832 +TEST(ParserTest, CompareWithNegativeNumber_Issue832) +{ + BT::Ast::Environment environment = { BT::Blackboard::create(), {} }; + + auto GetResult = [&environment](const char* text) -> BT::Any { + return GetScriptResult(environment, text); + }; + + // "A != -1" should parse and evaluate correctly + EXPECT_EQ(GetResult("A:=0; A!=-1").cast(), 1); // 0 != -1 is true + EXPECT_EQ(GetResult("A:=-1; A!=-1").cast(), 0); // -1 != -1 is false + EXPECT_EQ(GetResult("A:=0; A==-1").cast(), 0); // 0 == -1 is false + EXPECT_EQ(GetResult("A:=0; A>-1").cast(), 1); // 0 > -1 is true + EXPECT_EQ(GetResult("A:=0; A<-1").cast(), 0); // 0 < -1 is false + + // Also test that ValidateScript accepts these expressions + EXPECT_TRUE(BT::ValidateScript("A:=0; A!=-1")); + EXPECT_TRUE(BT::ValidateScript("A:=0; A>-1")); + + // Reproducer from the issue: precondition with negative literal + BT::BehaviorTreeFactory factory; + const std::string xml_text = R"( + + + +