1414
1515#include " behaviortree_cpp/scripting/operators.hpp"
1616
17+ #include < algorithm>
1718#include < charconv>
1819
1920namespace BT
@@ -55,6 +56,16 @@ class ScriptParser
5556 // parser already applies in VerifyXML and recursivelyCreateSubtree.
5657 static constexpr int kMaxNestingDepth = 256 ;
5758
59+ // Height of the AST returned by the last parsePrefix() / parseExpr() call.
60+ // A flat chain like "1+1+1+..." is parsed by the loop in parseExpr without
61+ // recursing, but it builds a left-deep AST that evaluate() and the node
62+ // destructors walk recursively, one frame per operator. Bounding the height
63+ // bounds that recursion. It uses the same limit as kMaxNestingDepth: each
64+ // level of evaluate() can take about 1 KB of stack in an unoptimized MSVC
65+ // build, so 1024 levels overflowed the 1 MB default stack on Windows.
66+ int height_ = 0 ;
67+ static constexpr int kMaxExpressionHeight = kMaxNestingDepth ;
68+
5869 // Binding power constants. Higher value = tighter binding.
5970 static constexpr int kAssignmentBP = 2 ;
6071 static constexpr int kTernaryBP = 4 ;
@@ -165,18 +176,36 @@ class ScriptParser
165176 type == TokenType::StarEqual || type == TokenType::SlashEqual;
166177 }
167178
179+ // Height of a node whose children are a subtree of height lhs_height and the
180+ // expression parsed last (height_). Throws if the AST gets too tall.
181+ int combinedHeight (int lhs_height) const
182+ {
183+ const int height = std::max (lhs_height, height_) + 1 ;
184+ if (height > kMaxExpressionHeight )
185+ {
186+ throw RuntimeError (StrCat (" Parse error at position " , std::to_string (peek ().pos ),
187+ " : expression is too long or too deeply nested (limit " ,
188+ std::to_string (kMaxExpressionHeight ), " levels)" ));
189+ }
190+ return height;
191+ }
192+
168193 // --- Parsing ---
169194
170195 // / Prefix: atoms and unary prefix operators
171196 Ast::expr_ptr parsePrefix ()
172197 {
173198 const auto & tok = peek ();
199+ // literals and identifiers are leaves; the cases below that parse a
200+ // subexpression overwrite it
201+ height_ = 1 ;
174202
175203 // Unary minus
176204 if (tok.type == TokenType::Minus)
177205 {
178206 advance ();
179207 auto operand = parseExpr (kPrefixBP );
208+ height_ = combinedHeight (0 );
180209 return std::make_shared<Ast::ExprUnaryArithmetic>(Ast::ExprUnaryArithmetic::negate,
181210 std::move (operand));
182211 }
@@ -185,6 +214,7 @@ class ScriptParser
185214 {
186215 advance ();
187216 auto operand = parseExpr (kPrefixBP );
217+ height_ = combinedHeight (0 );
188218 return std::make_shared<Ast::ExprUnaryArithmetic>(
189219 Ast::ExprUnaryArithmetic::complement, std::move (operand));
190220 }
@@ -193,6 +223,7 @@ class ScriptParser
193223 {
194224 advance ();
195225 auto operand = parseExpr (kPrefixBP );
226+ height_ = combinedHeight (0 );
196227 return std::make_shared<Ast::ExprUnaryArithmetic>(
197228 Ast::ExprUnaryArithmetic::logical_not, std::move (operand));
198229 }
@@ -280,6 +311,7 @@ class ScriptParser
280311 }
281312
282313 auto left = parsePrefix ();
314+ int left_height = height_;
283315
284316 while (true )
285317 {
@@ -294,20 +326,23 @@ class ScriptParser
294326 if (isAssignment (tokType))
295327 {
296328 left = parseAssignment (std::move (left));
329+ left_height = combinedHeight (left_height);
297330 break ;
298331 }
299332
300333 // Ternary (non-associative: parse once, then break)
301334 if (tokType == TokenType::Question)
302335 {
303336 left = parseTernary (std::move (left));
337+ left_height = combinedHeight (left_height);
304338 break ;
305339 }
306340
307341 // Chained comparison
308342 if (isComparison (tokType))
309343 {
310344 left = parseChainedComparison (std::move (left));
345+ left_height = combinedHeight (left_height);
311346 continue ;
312347 }
313348
@@ -316,9 +351,11 @@ class ScriptParser
316351 // Right BP = LBP + 1 for left-associativity
317352 auto right = parseExpr (lbp + 1 );
318353 left = makeBinary (std::move (left), opTok.type , std::move (right));
354+ left_height = combinedHeight (left_height);
319355 }
320356
321357 --depth_;
358+ height_ = left_height;
322359 return left;
323360 }
324361
@@ -358,8 +395,10 @@ class ScriptParser
358395 {
359396 advance (); // consume '?'
360397 auto thenExpr = parseExpr (0 ); // full expression inside
398+ const int then_height = height_;
361399 expect (TokenType::Colon, " expected ':' in ternary expression" );
362400 auto elseExpr = parseExpr (kTernaryBP );
401+ height_ = std::max (then_height, height_);
363402 return std::make_shared<Ast::ExprIf>(std::move (condition), std::move (thenExpr),
364403 std::move (elseExpr));
365404 }
@@ -369,13 +408,16 @@ class ScriptParser
369408 auto node = std::make_shared<Ast::ExprComparison>();
370409 node->operands .push_back (std::move (first));
371410
411+ int max_height = 0 ;
372412 while (isComparison (peek ().type ))
373413 {
374414 node->ops .push_back (mapComparisonOp (advance ().type ));
375415 // Parse the next operand above comparison level
376416 // so that arithmetic binds tighter
377417 node->operands .push_back (parseExpr (kComparisonBP + 1 ));
418+ max_height = std::max (max_height, height_);
378419 }
420+ height_ = max_height;
379421 return node;
380422 }
381423
0 commit comments