Commit 94dfdc55 authored by Pedro Eugenio Rocha Pedreira's avatar Pedro Eugenio Rocha Pedreira Committed by Facebook Github Bot

Fix signed to unsigned conversion

Summary:
`folly::to<uint32_t>()` and `folly::to<uint64_t>()` were silently failing
and truncating the input when negative values were passed:

```
int8_t x = -1;
folly::to<uint8_t>(x); // THROWS

int16_t x = -1;
folly::to<uint16_t>(x); // THROWS
```

all throw folly::ConversionError, but

```
int32_t x = -1;
folly::to<uint32_t>(x); // DOES NOT THROW

int64_t x = -1;
folly::to<uint64_t>(x); // DOES NOT THROW
```

The actual bug is in `less_than<>()`, from Traits.h, which in these cases relies that `(uint32)0 <= std::numeric_limits<int32_t>::min()`, which doesn't hold according to the standard.

The fixes in this diff are:

- Fix `less_than<>()` to properly support signed to unsigned conversions
- Fix `less_than<>()` to properly support unsigned to signed conversions
- Fix `greater_than<>()` to properly support signed to unsigned conversions
- Fix `greater_than<>()` to properly support unsigned to signed conversions
- Add unit tests to cover all these cases.

Reviewed By: yfeldblum

Differential Revision: D19511557

fbshipit-source-id: 48649dfcdadeaa652c8f57f7b11481de44a88927
parent 9b21248f
main v2022.02.14.00 v2022.02.07.00 v2022.01.31.00 v2022.01.24.00 v2022.01.17.00 v2022.01.10.00 v2022.01.03.00 v2021.12.27.00 v2021.12.20.00 v2021.12.13.00 v2021.12.06.00 v2021.11.29.00 v2021.11.15.00 v2021.11.08.00 v2021.11.01.00 v2021.10.25.00 v2021.10.18.00 v2021.10.11.00 v2021.10.04.00 v2021.09.27.00 v2021.09.20.00 v2021.09.13.00 v2021.09.06.00 v2021.08.30.00 v2021.08.23.00 v2021.08.02.00 v2021.07.22.00 v2021.07.20.01 v2021.07.20.00 v2021.06.28.00 v2021.06.14.00 v2021.06.07.00 v2021.05.31.00 v2021.05.24.00 v2021.05.17.00 v2021.05.10.00 v2021.05.03.00 v2021.04.26.00 v2021.04.19.00 v2021.04.12.00 v2021.04.05.00 v2021.03.29.00 v2021.03.22.00 v2021.03.15.00 v2021.03.08.00 v2021.03.01.00 v2021.02.22.00 v2021.02.15.00 v2021.02.08.00 v2021.02.01.00 v2021.01.25.00 v2021.01.18.01 v2021.01.18.00 v2021.01.11.00 v2021.01.04.00 v2020.12.28.00 v2020.12.21.00 v2020.12.14.00 v2020.12.07.00 v2020.11.30.00 v2020.11.23.00 v2020.11.16.00 v2020.11.09.00 v2020.11.02.00 v2020.10.26.00 v2020.10.19.00 v2020.10.12.00 v2020.10.05.00 v2020.09.28.00 v2020.09.21.00 v2020.09.14.00 v2020.09.07.00 v2020.08.31.00 v2020.08.24.00 v2020.08.17.00 v2020.08.10.00 v2020.08.03.00 v2020.07.27.00 v2020.07.20.00 v2020.07.13.00 v2020.07.06.00 v2020.06.29.00 v2020.06.15.00 v2020.06.08.00 v2020.06.01.00 v2020.05.25.00 v2020.05.18.00 v2020.05.11.00 v2020.05.04.00 v2020.04.27.00 v2020.04.20.00 v2020.04.13.00 v2020.04.06.00 v2020.03.30.00 v2020.03.23.00 v2020.03.16.00 v2020.03.09.00 v2020.03.02.00 v2020.02.24.00 v2020.02.17.00 v2020.02.10.00 v2020.02.03.00
No related merge requests found
...@@ -688,6 +688,30 @@ using IsOneOf = StrictDisjunction<std::is_same<T, Ts>...>; ...@@ -688,6 +688,30 @@ using IsOneOf = StrictDisjunction<std::is_same<T, Ts>...>;
* @author: Marcelo Juchem <marcelo@fb.com> * @author: Marcelo Juchem <marcelo@fb.com>
*/ */
// same as `x < 0`
template <typename T>
constexpr bool is_negative(T x) {
return std::is_signed<T>::value && x < T(0);
}
// same as `x <= 0`
template <typename T>
constexpr bool is_non_positive(T x) {
return !x || folly::is_negative(x);
}
// same as `x > 0`
template <typename T>
constexpr bool is_positive(T x) {
return !is_non_positive(x);
}
// same as `x >= 0`
template <typename T>
constexpr bool is_non_negative(T x) {
return !x || is_positive(x);
}
namespace detail { namespace detail {
// folly::to integral specializations can end up generating code // folly::to integral specializations can end up generating code
...@@ -705,6 +729,9 @@ template <typename RHS, RHS rhs, typename LHS> ...@@ -705,6 +729,9 @@ template <typename RHS, RHS rhs, typename LHS>
bool less_than_impl(LHS const lhs) { bool less_than_impl(LHS const lhs) {
// clang-format off // clang-format off
return return
// Ensure signed and unsigned values won't be compared directly.
(!std::is_signed<RHS>::value && is_negative(lhs)) ? true :
(!std::is_signed<LHS>::value && is_negative(rhs)) ? false :
rhs > std::numeric_limits<LHS>::max() ? true : rhs > std::numeric_limits<LHS>::max() ? true :
rhs <= std::numeric_limits<LHS>::min() ? false : rhs <= std::numeric_limits<LHS>::min() ? false :
lhs < rhs; lhs < rhs;
...@@ -715,6 +742,9 @@ template <typename RHS, RHS rhs, typename LHS> ...@@ -715,6 +742,9 @@ template <typename RHS, RHS rhs, typename LHS>
bool greater_than_impl(LHS const lhs) { bool greater_than_impl(LHS const lhs) {
// clang-format off // clang-format off
return return
// Ensure signed and unsigned values won't be compared directly.
(!std::is_signed<RHS>::value && is_negative(lhs)) ? false :
(!std::is_signed<LHS>::value && is_negative(rhs)) ? true :
rhs > std::numeric_limits<LHS>::max() ? false : rhs > std::numeric_limits<LHS>::max() ? false :
rhs < std::numeric_limits<LHS>::min() ? true : rhs < std::numeric_limits<LHS>::min() ? true :
lhs > rhs; lhs > rhs;
...@@ -725,30 +755,6 @@ FOLLY_POP_WARNING ...@@ -725,30 +755,6 @@ FOLLY_POP_WARNING
} // namespace detail } // namespace detail
// same as `x < 0`
template <typename T>
constexpr bool is_negative(T x) {
return std::is_signed<T>::value && x < T(0);
}
// same as `x <= 0`
template <typename T>
constexpr bool is_non_positive(T x) {
return !x || folly::is_negative(x);
}
// same as `x > 0`
template <typename T>
constexpr bool is_positive(T x) {
return !is_non_positive(x);
}
// same as `x >= 0`
template <typename T>
constexpr bool is_non_negative(T x) {
return !x || is_positive(x);
}
template <typename RHS, RHS rhs, typename LHS> template <typename RHS, RHS rhs, typename LHS>
bool less_than(LHS const lhs) { bool less_than(LHS const lhs) {
return detail:: return detail::
......
...@@ -1041,9 +1041,59 @@ std::string prefixWithType(V value) { ...@@ -1041,9 +1041,59 @@ std::string prefixWithType(V value) {
EXPECT_CONV_ERROR_QUOTE( \ EXPECT_CONV_ERROR_QUOTE( \
to<type>(val), code, prefixWithType<type>(val).c_str(), false) to<type>(val), code, prefixWithType<type>(val).c_str(), false)
template <typename TUnsigned>
void unsignedUnderflow() {
EXPECT_CONV_ERROR_ARITH(
TUnsigned, std::numeric_limits<int8_t>::min(), ARITH_NEGATIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
TUnsigned, std::numeric_limits<int16_t>::min(), ARITH_NEGATIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
TUnsigned, std::numeric_limits<int32_t>::min(), ARITH_NEGATIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
TUnsigned, std::numeric_limits<int64_t>::min(), ARITH_NEGATIVE_OVERFLOW);
}
TEST(Conv, ConversionErrorIntToInt) { TEST(Conv, ConversionErrorIntToInt) {
EXPECT_CONV_ERROR_ARITH(signed char, 128, ARITH_POSITIVE_OVERFLOW); // Test overflow upper bound. First unsigned to signed.
EXPECT_CONV_ERROR_ARITH(unsigned char, -1, ARITH_NEGATIVE_OVERFLOW); EXPECT_CONV_ERROR_ARITH(
int8_t, std::numeric_limits<uint8_t>::max(), ARITH_POSITIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
int16_t, std::numeric_limits<uint16_t>::max(), ARITH_POSITIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
int32_t, std::numeric_limits<uint32_t>::max(), ARITH_POSITIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
int64_t, std::numeric_limits<uint64_t>::max(), ARITH_POSITIVE_OVERFLOW);
// Signed to signed.
EXPECT_CONV_ERROR_ARITH(
int8_t, std::numeric_limits<int16_t>::max(), ARITH_POSITIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
int16_t, std::numeric_limits<int32_t>::max(), ARITH_POSITIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
int32_t, std::numeric_limits<int64_t>::max(), ARITH_POSITIVE_OVERFLOW);
// Unsigned to unsigned.
EXPECT_CONV_ERROR_ARITH(
uint8_t, std::numeric_limits<uint16_t>::max(), ARITH_POSITIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
uint16_t, std::numeric_limits<uint32_t>::max(), ARITH_POSITIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
uint32_t, std::numeric_limits<uint64_t>::max(), ARITH_POSITIVE_OVERFLOW);
// Test underflows from signed to unsigned data types. Make sure we test all
// combinations.
unsignedUnderflow<uint8_t>();
unsignedUnderflow<uint16_t>();
unsignedUnderflow<uint32_t>();
unsignedUnderflow<uint64_t>();
// Signed to signed.
EXPECT_CONV_ERROR_ARITH(
int8_t, std::numeric_limits<int16_t>::min(), ARITH_NEGATIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
int16_t, std::numeric_limits<int32_t>::min(), ARITH_NEGATIVE_OVERFLOW);
EXPECT_CONV_ERROR_ARITH(
int32_t, std::numeric_limits<int64_t>::min(), ARITH_NEGATIVE_OVERFLOW);
} }
TEST(Conv, ConversionErrorFloatToFloat) { TEST(Conv, ConversionErrorFloatToFloat) {
......
...@@ -173,10 +173,31 @@ TEST(Traits, relational) { ...@@ -173,10 +173,31 @@ TEST(Traits, relational) {
EXPECT_FALSE((folly::less_than<uint8_t, 255u, uint8_t>(255u))); EXPECT_FALSE((folly::less_than<uint8_t, 255u, uint8_t>(255u)));
EXPECT_TRUE((folly::less_than<uint8_t, 255u, uint8_t>(254u))); EXPECT_TRUE((folly::less_than<uint8_t, 255u, uint8_t>(254u)));
// Making sure signed to unsigned comparisons are not truncated.
EXPECT_TRUE((folly::less_than<uint8_t, 0, int8_t>(-1)));
EXPECT_TRUE((folly::less_than<uint16_t, 0, int16_t>(-1)));
EXPECT_TRUE((folly::less_than<uint32_t, 0, int32_t>(-1)));
EXPECT_TRUE((folly::less_than<uint64_t, 0, int64_t>(-1)));
EXPECT_FALSE((folly::less_than<int8_t, -1, uint8_t>(0)));
EXPECT_FALSE((folly::less_than<int16_t, -1, uint16_t>(0)));
EXPECT_FALSE((folly::less_than<int32_t, -1, uint32_t>(0)));
EXPECT_FALSE((folly::less_than<int64_t, -1, uint64_t>(0)));
EXPECT_FALSE((folly::greater_than<uint8_t, 0u, uint8_t>(0u))); EXPECT_FALSE((folly::greater_than<uint8_t, 0u, uint8_t>(0u)));
EXPECT_TRUE((folly::greater_than<uint8_t, 0u, uint8_t>(254u))); EXPECT_TRUE((folly::greater_than<uint8_t, 0u, uint8_t>(254u)));
EXPECT_FALSE((folly::greater_than<uint8_t, 255u, uint8_t>(255u))); EXPECT_FALSE((folly::greater_than<uint8_t, 255u, uint8_t>(255u)));
EXPECT_FALSE((folly::greater_than<uint8_t, 255u, uint8_t>(254u))); EXPECT_FALSE((folly::greater_than<uint8_t, 255u, uint8_t>(254u)));
EXPECT_FALSE((folly::greater_than<uint8_t, 0, int8_t>(-1)));
EXPECT_FALSE((folly::greater_than<uint16_t, 0, int16_t>(-1)));
EXPECT_FALSE((folly::greater_than<uint32_t, 0, int32_t>(-1)));
EXPECT_FALSE((folly::greater_than<uint64_t, 0, int64_t>(-1)));
EXPECT_TRUE((folly::greater_than<int8_t, -1, uint8_t>(0)));
EXPECT_TRUE((folly::greater_than<int16_t, -1, uint16_t>(0)));
EXPECT_TRUE((folly::greater_than<int32_t, -1, uint32_t>(0)));
EXPECT_TRUE((folly::greater_than<int64_t, -1, uint64_t>(0)));
} }
#if FOLLY_HAVE_INT128_T #if FOLLY_HAVE_INT128_T
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment