diff --git a/include/behaviortree_cpp/utils/convert_impl.hpp b/include/behaviortree_cpp/utils/convert_impl.hpp index e32eefbcf..f70861802 100644 --- a/include/behaviortree_cpp/utils/convert_impl.hpp +++ b/include/behaviortree_cpp/utils/convert_impl.hpp @@ -102,7 +102,11 @@ inline void checkTruncation(const From& from) } else { - doesnt_fit = std::abs(static_cast(from)) > static_cast(max_exact); + // Negate in unsigned arithmetic: std::abs(INT64_MIN) overflows. + const auto value = static_cast(from); + const uint64_t magnitude = + value < 0 ? 0 - static_cast(value) : static_cast(value); + doesnt_fit = magnitude > max_exact; } if(doesnt_fit) { diff --git a/include/behaviortree_cpp/utils/safe_any.hpp b/include/behaviortree_cpp/utils/safe_any.hpp index 7a9a9cb53..e403ff1f3 100644 --- a/include/behaviortree_cpp/utils/safe_any.hpp +++ b/include/behaviortree_cpp/utils/safe_any.hpp @@ -259,6 +259,12 @@ inline bool ValidCast(const SRC& val) { // For integral to float, check if we can represent the value exactly TO as_float = static_cast(val); + // as_float can round up to 2^digits (e.g. INT64_MAX -> 2^63), one past + // the max of SRC. Casting that back to SRC would overflow. + if(as_float >= std::ldexp(TO(1), std::numeric_limits::digits)) + { + return false; + } SRC back_conv = static_cast(as_float); return back_conv == val; } diff --git a/tests/gtest_basic_types.cpp b/tests/gtest_basic_types.cpp index 680c45ce3..270a1cb57 100644 --- a/tests/gtest_basic_types.cpp +++ b/tests/gtest_basic_types.cpp @@ -148,6 +148,25 @@ TEST(BasicTypes, DoubleToIntBoundary) EXPECT_NO_THROW(bb->set("k", in_range)); } +TEST(BasicTypes, IntToDoubleBoundary) +{ + // The precision check must not negate INT64_MIN (signed overflow, undefined + // behavior). Its magnitude is above 2^53, so it is rejected like INT64_MAX. + EXPECT_ANY_THROW((void)BT::Any(INT64_MIN).cast()); + EXPECT_ANY_THROW((void)BT::Any(INT64_MAX).cast()); + EXPECT_EQ(BT::Any(int64_t(-4503599627370496LL)).cast(), -4503599627370496.0); + + // Through Blackboard::set, INT64_MAX rounds up to 2^63 as a double, which must + // not be cast back to int64_t. All three extremes are beyond 2^53. + auto bb = BT::Blackboard::create(); + bb->set("d", 1.0); // lock the entry to double + EXPECT_ANY_THROW(bb->set("d", INT64_MAX)); + EXPECT_ANY_THROW(bb->set("d", UINT64_MAX)); + EXPECT_ANY_THROW(bb->set("d", INT64_MIN)); + EXPECT_NO_THROW(bb->set("d", int64_t(1000))); + EXPECT_EQ(bb->get("d"), 1000.0); +} + TEST(BasicTypes, ConvertFromString_Bool) { ASSERT_TRUE(convertFromString("true"));