Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions include/behaviortree_cpp/scripting/operators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,20 @@ struct ExprUnaryArithmetic : ExprBase
case negate:
return Any(-rv);
case complement:
if(rv > static_cast<double>(std::numeric_limits<int64_t>::max()) ||
rv < static_cast<double>(std::numeric_limits<int64_t>::min()))
// Use the checked conversion, like the binary bitwise operators do.
// A plain static_cast is undefined for NaN and for values at or
// beyond the int64_t range: the hand-written comparison used here
// before let NaN (every comparison is false) and 2^63 (not greater
// than double(INT64_MAX), which rounds up to 2^63) through, and
// silently truncated fractional values.
try
{
return Any(static_cast<double>(~rhs_v.cast<int64_t>()));
}
catch(const std::exception&)
{
throw RuntimeError("Number out of range for bitwise operation");
}
return Any(static_cast<double>(~static_cast<int64_t>(rv)));
case logical_not:
return Any(static_cast<double>(!static_cast<bool>(rv)));
}
Expand Down
20 changes: 20 additions & 0 deletions tests/script_parser_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,26 @@ TEST(ParserTest, UnaryOperators)
EXPECT_EQ(Parse("10 + -3").value().cast<double>(), 7.0);
}

TEST(ParserTest, BitwiseComplementRejectsNonIntegers)
{
BT::Ast::Environment env = { BT::Blackboard::create(), {} };
auto Parse = [&env](const char* str) { return BT::ParseScriptAndExecute(env, str); };

// The operand was cast to int64_t after a hand-written range check that
// accepted NaN and exactly 2^63 (undefined conversion, reported by UBSan)
// and silently truncated fractional values.
EXPECT_FALSE(Parse("~(0.0 / 0.0)").has_value());
EXPECT_FALSE(Parse("~9223372036854775808.0").has_value()); // 2^63
EXPECT_FALSE(Parse("~1.5").has_value());
EXPECT_FALSE(Parse("~(1.0 / 0.0)").has_value());

// Integral values, including the int64_t boundaries, still work.
EXPECT_EQ(Parse("~0").value().cast<int64_t>(), ~int64_t(0));
EXPECT_EQ(Parse("~(2 * 3)").value().cast<int64_t>(), ~int64_t(6));
EXPECT_EQ(Parse("~-9223372036854775808.0").value().cast<double>(),
static_cast<double>(~std::numeric_limits<int64_t>::min()));
}

TEST(ParserTest, TernaryExpressions)
{
BT::Ast::Environment env = { BT::Blackboard::create(), {} };
Expand Down
Loading