diff --git a/include/behaviortree_cpp/scripting/operators.hpp b/include/behaviortree_cpp/scripting/operators.hpp index 285b5fc3d..0f7d74c78 100644 --- a/include/behaviortree_cpp/scripting/operators.hpp +++ b/include/behaviortree_cpp/scripting/operators.hpp @@ -129,12 +129,20 @@ struct ExprUnaryArithmetic : ExprBase case negate: return Any(-rv); case complement: - if(rv > static_cast(std::numeric_limits::max()) || - rv < static_cast(std::numeric_limits::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(~rhs_v.cast())); + } + catch(const std::exception&) { throw RuntimeError("Number out of range for bitwise operation"); } - return Any(static_cast(~static_cast(rv))); case logical_not: return Any(static_cast(!static_cast(rv))); } diff --git a/tests/script_parser_test.cpp b/tests/script_parser_test.cpp index 31ee163f7..ac0822b9b 100644 --- a/tests/script_parser_test.cpp +++ b/tests/script_parser_test.cpp @@ -670,6 +670,26 @@ TEST(ParserTest, UnaryOperators) EXPECT_EQ(Parse("10 + -3").value().cast(), 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(0)); + EXPECT_EQ(Parse("~(2 * 3)").value().cast(), ~int64_t(6)); + EXPECT_EQ(Parse("~-9223372036854775808.0").value().cast(), + static_cast(~std::numeric_limits::min())); +} + TEST(ParserTest, TernaryExpressions) { BT::Ast::Environment env = { BT::Blackboard::create(), {} };