Skip to content
Open
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
11 changes: 9 additions & 2 deletions include/behaviortree_cpp/utils/convert_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,15 @@ inline void checkTruncation(const From& from)
// Handle floating point to integer
else if constexpr(std::is_floating_point_v<From> && std::is_integral_v<To>)
{
if(from > static_cast<From>(std::numeric_limits<To>::max()) ||
from < static_cast<From>(std::numeric_limits<To>::lowest()) ||
// static_cast<From>(To::max) rounds up to an out-of-range value when To has
// more significant bits than From's mantissa (e.g. double -> int64_t). In
// that case a plain `>` lets the rounded boundary through and the cast to To
// below overflows, so use a half-open upper bound to reject it first.
constexpr bool max_rounds_up =
std::numeric_limits<To>::digits > std::numeric_limits<From>::digits;
const From max_limit = static_cast<From>(std::numeric_limits<To>::max());
const bool over = max_rounds_up ? (from >= max_limit) : (from > max_limit);
if(over || from < static_cast<From>(std::numeric_limits<To>::lowest()) ||
from != std::nearbyint(from))
{
throw std::runtime_error("Invalid floating point to integer conversion");
Expand Down
12 changes: 10 additions & 2 deletions include/behaviortree_cpp/utils/safe_any.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,16 @@ inline bool ValidCast(const SRC& val)
// Handle conversion to integral
else if constexpr(std::is_integral_v<TO>)
{
if(val > static_cast<SRC>(std::numeric_limits<TO>::max()) ||
val < static_cast<SRC>(std::numeric_limits<TO>::lowest()))
// static_cast<SRC>(TO::max) rounds up to an out-of-range value when TO has
// more significant bits than a floating SRC's mantissa (e.g. double ->
// int64_t). A plain `>` would let that rounded boundary through and the
// cast to TO below overflows, so use a half-open upper bound in that case.
constexpr bool max_rounds_up =
std::is_floating_point_v<SRC> &&
std::numeric_limits<TO>::digits > std::numeric_limits<SRC>::digits;
const SRC max_limit = static_cast<SRC>(std::numeric_limits<TO>::max());
const bool over = max_rounds_up ? (val >= max_limit) : (val > max_limit);
if(over || val < static_cast<SRC>(std::numeric_limits<TO>::lowest()))
{
return false;
}
Expand Down
25 changes: 25 additions & 0 deletions tests/gtest_basic_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,31 @@ TEST(BasicTypes, ConvertFromString_Double)
ASSERT_THROW((void)convertFromString<double>("not_a_number"), RuntimeError);
}

TEST(BasicTypes, DoubleToIntBoundary)
{
// A double that lands exactly on 2^63 must be rejected before the cast, not
// static_cast to int64_t (which is out of range and undefined behavior).
// 2^63 = 9223372036854775808.0; INT64_MAX is 9223372036854775807.
const double two_pow_63 = 9223372036854775808.0;
EXPECT_ANY_THROW((void)BT::Any(two_pow_63).cast<int64_t>());

// 2^64 lands one past UINT64_MAX and must likewise be rejected.
const double two_pow_64 = 18446744073709551616.0;
EXPECT_ANY_THROW((void)BT::Any(two_pow_64).cast<uint64_t>());

// In-range integral doubles still convert cleanly.
const double in_range = 4611686018427387904.0; // 2^62
EXPECT_EQ(BT::Any(in_range).cast<int64_t>(), int64_t(4611686018427387904LL));
EXPECT_EQ(BT::Any(1000.0).cast<int64_t>(), int64_t(1000));

// Same boundary reached through Blackboard::set, which routes the numeric
// safe-cast check (isCastingSafe/ValidCast) instead of Any::convert.
auto bb = BT::Blackboard::create();
bb->set("k", int64_t(1)); // lock the entry to int64_t
EXPECT_ANY_THROW(bb->set("k", two_pow_63));
EXPECT_NO_THROW(bb->set("k", in_range));
}

TEST(BasicTypes, ConvertFromString_Bool)
{
ASSERT_TRUE(convertFromString<bool>("true"));
Expand Down