ASDenysPetrov created this revision. ASDenysPetrov added reviewers: vsavchenko, NoQ, steakhal, xazax.hun. ASDenysPetrov added a project: clang. Herald added subscribers: manas, martong, dkrupp, donat.nagy, Szelethus, mikhail.ramalho, a.sidorin, rnkovacs, szepet, baloghadamsoftware. ASDenysPetrov requested review of this revision. Herald added a subscriber: cfe-commits.
Handle casts for ranges working similarly to APSIntType::apply function but for the whole range set. Support promotions, truncations and conversions. Example: Promotion: `char [0, 42] -> short [0, 42] -> int [0, 42] -> llong [0, 42]` Truncation: `llong [4295033088, 4295033130] -> int [65792, 65834] -> short [256, 298] -> char [0, 42]` Conversion: `char [-42, 42] -> uint [0, 42]U[4294967254, 4294967295] -> short[-42, 42]` Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D103094 Files: clang/include/clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp clang/unittests/StaticAnalyzer/RangeSetTest.cpp
Index: clang/unittests/StaticAnalyzer/RangeSetTest.cpp =================================================================== --- clang/unittests/StaticAnalyzer/RangeSetTest.cpp +++ clang/unittests/StaticAnalyzer/RangeSetTest.cpp @@ -35,12 +35,18 @@ const RangeSet &Set) { return OS << toString(Set); } +LLVM_ATTRIBUTE_UNUSED static std::ostream &operator<<(std::ostream &OS, + APSIntType Ty) { + return OS << (Ty.isUnsigned() ? "u" : "s") << Ty.getBitWidth(); +} } // namespace ento } // namespace clang namespace { +template <class T> constexpr bool is_signed_v = std::is_signed<T>::value; + template <typename T> struct TestValues { static constexpr T MIN = std::numeric_limits<T>::min(); static constexpr T MAX = std::numeric_limits<T>::max(); @@ -48,7 +54,7 @@ // which unary minus does not affect on, // e.g. int8/int32(0), uint8(128), uint32(2147483648). static constexpr T MID = - std::is_signed<T>::value ? 0 : ~(static_cast<T>(-1) / static_cast<T>(2)); + is_signed_v<T> ? 0 : ~(static_cast<T>(-1) / static_cast<T>(2)); static constexpr T A = MID - (MAX - MID) / 3 * 2; static constexpr T B = MID - (MAX - MID) / 3; static constexpr T C = -B; @@ -56,6 +62,34 @@ static_assert(MIN < A && A < B && B < MID && MID < C && C < D && D < MAX, "Values shall be in an ascending order"); + // Clear bits in low bytes by the given amount. + template <T Value, size_t Bytes> + static const T ClearLowBytes = static_cast<T>(static_cast<uint64_t>(Value) + << ((Bytes >= 8) ? 0 : Bytes) * + 8); + + template <T Value, typename Base> + static constexpr T TruncZeroOf = ClearLowBytes<Value + 1, sizeof(Base)>; + + // Random number with active bits in every byte. 0xAAAA'AAAA + static constexpr T XAAA = static_cast<T>( + 0b10101010'10101010'10101010'10101010'10101010'10101010'10101010'10101010); + template <typename Base> + static constexpr T XAAATruncZeroOf = TruncZeroOf<XAAA, Base>; // 0xAAAA'AB00 + + // Random number with active bits in every byte. 0x5555'5555 + static constexpr T X555 = static_cast<T>( + 0b01010101'01010101'01010101'01010101'01010101'01010101'01010101'01010101); + template <typename Base> + static constexpr T X555TruncZeroOf = TruncZeroOf<X555, Base>; // 0x5555'5600 + + // Numbers for ranges with the same bits in the lowest byte. + // 0xAAAA'AA2A + static constexpr T FromA = ClearLowBytes<XAAA, sizeof(T) - 1> + 42; + static constexpr T ToA = FromA + 2; // 0xAAAA'AA2C + // 0x5555'552A + static constexpr T FromB = ClearLowBytes<X555, sizeof(T) - 1> + 42; + static constexpr T ToB = FromB + 2; // 0x5555'552C }; template <typename BaseType> class RangeSetTest : public testing::Test { @@ -69,8 +103,11 @@ // End init block using Self = RangeSetTest<BaseType>; - using RawRange = std::pair<BaseType, BaseType>; - using RawRangeSet = std::initializer_list<RawRange>; + template <typename T> using RawRangeT = std::pair<T, T>; + template <typename T> + using RawRangeSetT = std::initializer_list<RawRangeT<T>>; + using RawRange = RawRangeT<BaseType>; + using RawRangeSet = RawRangeSetT<BaseType>; const llvm::APSInt &from(BaseType X) { static llvm::APSInt Base{sizeof(BaseType) * 8, @@ -84,9 +121,21 @@ } RangeSet from(const RawRangeSet &Init) { + static APSIntType Ty{sizeof(BaseType) * 8, + std::is_unsigned<BaseType>::value}; + return from<BaseType>(Ty, Init); + } + + template <typename T> RangeSet from(APSIntType Ty, RawRangeSetT<T> Init) { + llvm::APSInt First, Second; + Ty.apply(First); + Ty.apply(Second); RangeSet RangeSet = F.getEmptySet(); for (const auto &Raw : Init) { - RangeSet = F.add(RangeSet, from(Raw)); + First = Raw.first; + Second = Raw.second; + RangeSet = + F.add(RangeSet, Range(BVF.getValue(First), BVF.getValue(Second))); } return RangeSet; } @@ -206,9 +255,27 @@ RawRangeSet RawExpected) { wrap(&Self::checkDeleteImpl, Point, RawFrom, RawExpected); } -}; -} // namespace + void checkCastToImpl(RangeSet What, APSIntType Ty, RangeSet Expected) { + RangeSet Result = F.castTo(What, Ty); + EXPECT_EQ(Result, Expected) + << "while casting " << toString(What) << " to " << Ty; + } + + template <typename From, typename To> + void checkCastTo(RawRangeSetT<From> What, RawRangeSetT<To> Expected) { + static APSIntType FromTy(sizeof(From) * 8, !is_signed_v<From>); + static APSIntType ToTy(sizeof(To) * 8, !is_signed_v<To>); + this->checkCastToImpl(from(FromTy, What), ToTy, from(ToTy, Expected)); + } + + void checkCastTo_NOOP(); + template <typename From> void checkCastTo_Promotion(); + template <typename From> void checkCastTo_Truncation(); + template <typename From> void checkCastTo_Conversion(); + template <typename From> void checkCastTo_PromotionConversion(); + template <typename From> void checkCastTo_TruncationConversion(); +}; using IntTypes = ::testing::Types<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t>; @@ -574,3 +641,423 @@ {{MIN, MIN}, {B, MID}, {MID + 1, C}, {C + 4, D - 1}}, {{MIN, MIN}, {A, C}, {C + 2, D}, {MAX - 1, MAX}}); } + +TYPED_TEST(RangeSetTest, RangeSetCastToTest) { + // TODO: Simplify calls below using list of types (int8_t, int16_t, + // int32_t, etc.). + + // NOOP + this->checkCastTo_NOOP(); + + // Promotion + this->template checkCastTo_Promotion<int8_t>(); + this->template checkCastTo_Promotion<int16_t>(); + this->template checkCastTo_Promotion<int32_t>(); + this->template checkCastTo_Promotion<uint8_t>(); + this->template checkCastTo_Promotion<uint16_t>(); + this->template checkCastTo_Promotion<uint32_t>(); + + // Truncation + this->template checkCastTo_Truncation<int16_t>(); + this->template checkCastTo_Truncation<int32_t>(); + this->template checkCastTo_Truncation<int64_t>(); + this->template checkCastTo_Truncation<uint16_t>(); + this->template checkCastTo_Truncation<uint32_t>(); + this->template checkCastTo_Truncation<uint64_t>(); + + // Conversion + this->template checkCastTo_Conversion<int8_t>(); + this->template checkCastTo_Conversion<int16_t>(); + this->template checkCastTo_Conversion<int32_t>(); + this->template checkCastTo_Conversion<int64_t>(); + this->template checkCastTo_Conversion<uint8_t>(); + this->template checkCastTo_Conversion<uint16_t>(); + this->template checkCastTo_Conversion<uint32_t>(); + this->template checkCastTo_Conversion<uint64_t>(); + + // Promotion + Conversion + this->template checkCastTo_PromotionConversion<int8_t>(); + this->template checkCastTo_PromotionConversion<int16_t>(); + this->template checkCastTo_PromotionConversion<int32_t>(); + this->template checkCastTo_PromotionConversion<uint8_t>(); + this->template checkCastTo_PromotionConversion<uint16_t>(); + this->template checkCastTo_PromotionConversion<uint32_t>(); + + // Truncation + Conversion + this->template checkCastTo_TruncationConversion<int16_t>(); + this->template checkCastTo_TruncationConversion<int32_t>(); + this->template checkCastTo_TruncationConversion<int64_t>(); + this->template checkCastTo_TruncationConversion<uint16_t>(); + this->template checkCastTo_TruncationConversion<uint32_t>(); + this->template checkCastTo_TruncationConversion<uint64_t>(); +} + +template <typename BaseType> void RangeSetTest<BaseType>::checkCastTo_NOOP() { + // Just to reduce the verbosity. + using F = BaseType; // From + using T = BaseType; // To + + using TV = TestValues<T>; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + constexpr auto B = TV::B; + constexpr auto C = TV::C; + + // One point + this->checkCastTo<F, T>({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo<F, T>({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}}, {{MID, MID}}); + this->checkCastTo<F, T>({{B, B}}, {{B, B}}); + this->checkCastTo<F, T>({{C, C}}, {{C, C}}); + // Two points + this->checkCastTo<F, T>({{MIN, MIN}, {MAX, MAX}}, {{MIN, MIN}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo<F, T>({{MID, MID}, {MAX, MAX}}, {{MID, MID}, {MAX, MAX}}); + this->checkCastTo<F, T>({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo<F, T>({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo<F, T>({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + this->checkCastTo<F, T>({{MIN, MAX}}, {{MIN, MAX}}); + this->checkCastTo<F, T>({{MIN, MID}}, {{MIN, MID}}); + this->checkCastTo<F, T>({{MID, MAX}}, {{MID, MAX}}); + this->checkCastTo<F, T>({{B, MAX}}, {{B, MAX}}); + this->checkCastTo<F, T>({{C, MAX}}, {{C, MAX}}); + this->checkCastTo<F, T>({{MIN, C}}, {{MIN, C}}); + this->checkCastTo<F, T>({{MIN, B}}, {{MIN, B}}); + this->checkCastTo<F, T>({{B, C}}, {{B, C}}); + // Two ranges + this->checkCastTo<F, T>({{MIN, B}, {C, MAX}}, {{MIN, B}, {C, MAX}}); + this->checkCastTo<F, T>({{B, MID}, {C, MAX}}, {{B, MID}, {C, MAX}}); + this->checkCastTo<F, T>({{MIN, B}, {MID, C}}, {{MIN, B}, {MID, C}}); +} + +template <typename BaseType> +template <typename From> +void RangeSetTest<BaseType>::checkCastTo_Promotion() { + // Just to reduce the verbosity. + using F = From; // From + using T = BaseType; // To + + constexpr bool CanPromote = + (sizeof(F) < sizeof(T) && is_signed_v<F> == is_signed_v<T>); + + // Use `if constexpr` here. + if (CanPromote) { + using TV = TestValues<F>; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + constexpr auto B = TV::B; + constexpr auto C = TV::C; + // One point + this->checkCastTo<F, T>({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo<F, T>({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}}, {{MID, MID}}); + this->checkCastTo<F, T>({{B, B}}, {{B, B}}); + this->checkCastTo<F, T>({{C, C}}, {{C, C}}); + // Two points + this->checkCastTo<F, T>({{MIN, MIN}, {MAX, MAX}}, {{MIN, MIN}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo<F, T>({{MID, MID}, {MAX, MAX}}, {{MID, MID}, {MAX, MAX}}); + this->checkCastTo<F, T>({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo<F, T>({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo<F, T>({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + this->checkCastTo<F, T>({{MIN, MAX}}, {{MIN, MAX}}); + this->checkCastTo<F, T>({{MIN, MID}}, {{MIN, MID}}); + this->checkCastTo<F, T>({{MID, MAX}}, {{MID, MAX}}); + this->checkCastTo<F, T>({{B, MAX}}, {{B, MAX}}); + this->checkCastTo<F, T>({{C, MAX}}, {{C, MAX}}); + this->checkCastTo<F, T>({{MIN, C}}, {{MIN, C}}); + this->checkCastTo<F, T>({{MIN, B}}, {{MIN, B}}); + this->checkCastTo<F, T>({{B, C}}, {{B, C}}); + // Two ranges + this->checkCastTo<F, T>({{MIN, B}, {C, MAX}}, {{MIN, B}, {C, MAX}}); + this->checkCastTo<F, T>({{B, MID}, {C, MAX}}, {{B, MID}, {C, MAX}}); + this->checkCastTo<F, T>({{MIN, B}, {MID, C}}, {{MIN, B}, {MID, C}}); + } +} + +template <typename BaseType> +template <typename From> +void RangeSetTest<BaseType>::checkCastTo_Truncation() { + // Just to reduce the verbosity. + using F = From; // From + using T = BaseType; // To + using TV = TestValues<F>; + + constexpr bool CanTruncate = + (sizeof(F) > sizeof(T) && is_signed_v<F> == is_signed_v<T>); + + // Use `if constexpr` here. + if (CanTruncate) { + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + constexpr auto B = TV::B; + constexpr auto C = TV::C; + // One point + this->checkCastTo<F, T>({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo<F, T>({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}}, {{MID, MID}}); + this->checkCastTo<F, T>({{B, B}}, {{B, B}}); + this->checkCastTo<F, T>({{C, C}}, {{C, C}}); + // Two points + // Use `if constexpr` here. + if (is_signed_v<F>) { + this->checkCastTo<F, T>({{MIN, MIN}, {MAX, MAX}}, {{MAX, MIN}}); + this->checkCastTo<F, T>({{MID, MID}, {MAX, MAX}}, {{MAX, MID}}); + } else { + this->checkCastTo<F, T>({{MIN, MIN}, {MAX, MAX}}, + {{MIN, MIN}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}, {MAX, MAX}}, + {{MID, MID}, {MAX, MAX}}); + } + this->checkCastTo<F, T>({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo<F, T>({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo<F, T>({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo<F, T>({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + constexpr auto ToMIN = TestValues<T>::MIN; + constexpr auto ToMAX = TestValues<T>::MAX; + this->checkCastTo<F, T>({{MIN, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, MID}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MID, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{B, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, C}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, B}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{B, C}}, {{ToMIN, ToMAX}}); + // Two ranges + this->checkCastTo<F, T>({{MIN, B}, {C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{B, MID}, {C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, B}, {MID, C}}, {{ToMIN, ToMAX}}); + constexpr auto XAAA = TV::XAAA; + constexpr auto X555 = TV::X555; + constexpr auto ZA = TV::template XAAATruncZeroOf<T>; + constexpr auto Z5 = TV::template X555TruncZeroOf<T>; + this->checkCastTo<F, T>({{XAAA, ZA}, {X555, Z5}}, + {{ToMIN, 0}, {X555, ToMAX}}); + // Use `if constexpr` here. + if (is_signed_v<F>) { + // One range + this->checkCastTo<F, T>({{XAAA, ZA}}, {{XAAA, 0}}); + // Two ranges + this->checkCastTo<F, T>({{XAAA, ZA}, {1, 42}}, {{XAAA, 42}}); + } else { + // One range + this->checkCastTo<F, T>({{XAAA, ZA}}, {{0, 0}, {XAAA, ToMAX}}); + // Two ranges + this->checkCastTo<F, T>({{1, 42}, {XAAA, ZA}}, {{0, 42}, {XAAA, ToMAX}}); + } + constexpr auto FromA = TV::FromA; + constexpr auto ToA = TV::ToA; + constexpr auto FromB = TV::FromB; + constexpr auto ToB = TV::ToB; + this->checkCastTo<F, T>({{FromA, ToA}, {FromB, ToB}}, {{FromA, ToA}}); + } +} + +template <typename BaseType> +template <typename From> +void RangeSetTest<BaseType>::checkCastTo_Conversion() { + // Just to reduce the verbosity. + using F = From; // From + using T = BaseType; // To + + constexpr bool CanConvert = + (sizeof(F) == sizeof(T) && is_signed_v<F> != is_signed_v<T>); + + // Use `if constexpr` here. + if (CanConvert) { + using TV = TestValues<F>; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + constexpr auto B = TV::B; + constexpr auto C = TV::C; + // One point + this->checkCastTo<F, T>({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo<F, T>({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}}, {{MID, MID}}); + this->checkCastTo<F, T>({{B, B}}, {{B, B}}); + this->checkCastTo<F, T>({{C, C}}, {{C, C}}); + // Two points + this->checkCastTo<F, T>({{MIN, MIN}, {MAX, MAX}}, {{MAX, MIN}}); + this->checkCastTo<F, T>({{MID, MID}, {MAX, MAX}}, {{MID, MID}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo<F, T>({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo<F, T>({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo<F, T>({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + constexpr auto ToMIN = TestValues<T>::MIN; + constexpr auto ToMAX = TestValues<T>::MAX; + this->checkCastTo<F, T>({{MIN, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, MID}}, {{ToMIN, ToMIN}, {MIN, ToMAX}}); + this->checkCastTo<F, T>({{MID, MAX}}, {{MID, MAX}}); + this->checkCastTo<F, T>({{B, MAX}}, {{ToMIN, MAX}, {B, ToMAX}}); + this->checkCastTo<F, T>({{C, MAX}}, {{C, MAX}}); + this->checkCastTo<F, T>({{MIN, C}}, {{ToMIN, C}, {MIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, B}}, {{MIN, B}}); + this->checkCastTo<F, T>({{B, C}}, {{ToMIN, C}, {B, ToMAX}}); + // Two ranges + this->checkCastTo<F, T>({{MIN, B}, {C, MAX}}, {{C, B}}); + this->checkCastTo<F, T>({{B, MID}, {C, MAX}}, + {{MID, MID}, {C, MAX}, {B, ToMAX}}); + this->checkCastTo<F, T>({{MIN, B}, {MID, C}}, {{MID, C}, {MIN, B}}); + } +} + +template <typename BaseType> +template <typename From> +void RangeSetTest<BaseType>::checkCastTo_PromotionConversion() { + // Just to reduce the verbosity. + using F = From; // From + using T = BaseType; // To + + constexpr bool CanPromoteAndConvert = + (sizeof(F) < sizeof(T) && is_signed_v<F> != is_signed_v<T>); + + // Use `if constexpr` here. + if (CanPromoteAndConvert) { + using TV = TestValues<F>; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + constexpr auto B = TV::B; + constexpr auto C = TV::C; + // One point + this->checkCastTo<F, T>({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo<F, T>({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}}, {{MID, MID}}); + this->checkCastTo<F, T>({{B, B}}, {{B, B}}); + this->checkCastTo<F, T>({{C, C}}, {{C, C}}); + // Two points + this->checkCastTo<F, T>({{MIN, MIN}, {MAX, MAX}}, {{MAX, MAX}, {MIN, MIN}}); + this->checkCastTo<F, T>({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo<F, T>({{MID, MID}, {MAX, MAX}}, {{MID, MID}, {MAX, MAX}}); + this->checkCastTo<F, T>({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo<F, T>({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo<F, T>({{B, B}, {C, C}}, {{B, B}, {C, C}}); + + // Use `if constexpr` here. + if (is_signed_v<F>) { + // One range + this->checkCastTo<F, T>({{MIN, MAX}}, {{0, MAX}, {MIN, -1}}); + this->checkCastTo<F, T>({{MIN, MID}}, {{0, 0}, {MIN, -1}}); + this->checkCastTo<F, T>({{MID, MAX}}, {{0, MAX}}); + this->checkCastTo<F, T>({{B, MAX}}, {{0, MAX}, {B, -1}}); + this->checkCastTo<F, T>({{C, MAX}}, {{C, MAX}}); + this->checkCastTo<F, T>({{MIN, C}}, {{0, C}, {MIN, -1}}); + this->checkCastTo<F, T>({{MIN, B}}, {{MIN, B}}); + this->checkCastTo<F, T>({{B, C}}, {{0, C}, {B, -1}}); + // Two ranges + this->checkCastTo<F, T>({{MIN, B}, {C, MAX}}, {{C, MAX}, {MIN, B}}); + this->checkCastTo<F, T>({{B, MID}, {C, MAX}}, + {{0, 0}, {C, MAX}, {B, -1}}); + this->checkCastTo<F, T>({{MIN, B}, {MID, C}}, {{0, C}, {MIN, B}}); + } else { + // One range + this->checkCastTo<F, T>({{MIN, MAX}}, {{MIN, MAX}}); + this->checkCastTo<F, T>({{MIN, MID}}, {{MIN, MID}}); + this->checkCastTo<F, T>({{MID, MAX}}, {{MID, MAX}}); + this->checkCastTo<F, T>({{B, MAX}}, {{B, MAX}}); + this->checkCastTo<F, T>({{C, MAX}}, {{C, MAX}}); + this->checkCastTo<F, T>({{MIN, C}}, {{MIN, C}}); + this->checkCastTo<F, T>({{MIN, B}}, {{MIN, B}}); + this->checkCastTo<F, T>({{B, C}}, {{B, C}}); + // Two ranges + this->checkCastTo<F, T>({{MIN, B}, {C, MAX}}, {{MIN, B}, {C, MAX}}); + this->checkCastTo<F, T>({{B, MID}, {C, MAX}}, {{B, MID}, {C, MAX}}); + this->checkCastTo<F, T>({{MIN, B}, {MID, C}}, {{MIN, B}, {MID, C}}); + } + } +} + +template <typename BaseType> +template <typename From> +void RangeSetTest<BaseType>::checkCastTo_TruncationConversion() { + // Just to reduce the verbosity. + using F = From; // From + using T = BaseType; // To + + constexpr bool CanTruncateAndConvert = + (sizeof(F) > sizeof(T) && is_signed_v<F> != is_signed_v<T>); + + if (CanTruncateAndConvert) { + using TV = TestValues<F>; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + constexpr auto B = TV::B; + constexpr auto C = TV::C; + // One point + this->checkCastTo<F, T>({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo<F, T>({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}}, {{MID, MID}}); + this->checkCastTo<F, T>({{B, B}}, {{B, B}}); + this->checkCastTo<F, T>({{C, C}}, {{C, C}}); + // Two points + // Use `if constexpr` here. + if (is_signed_v<F>) { + this->checkCastTo<F, T>({{MIN, MIN}, {MAX, MAX}}, + {{MIN, MIN}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}, {MAX, MAX}}, + {{MID, MID}, {MAX, MAX}}); + } else { + this->checkCastTo<F, T>({{MIN, MIN}, {MAX, MAX}}, {{MAX, MIN}}); + this->checkCastTo<F, T>({{MID, MID}, {MAX, MAX}}, {{MAX, MIN}}); + } + this->checkCastTo<F, T>({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo<F, T>({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo<F, T>({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo<F, T>({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo<F, T>({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + constexpr auto ToMIN = TestValues<T>::MIN; + constexpr auto ToMAX = TestValues<T>::MAX; + this->checkCastTo<F, T>({{MIN, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, MID}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MID, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{B, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, C}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, B}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{B, C}}, {{ToMIN, ToMAX}}); + // Two ranges + this->checkCastTo<F, T>({{MIN, B}, {C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{B, MID}, {C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo<F, T>({{MIN, B}, {MID, C}}, {{ToMIN, ToMAX}}); + constexpr auto XAAA = TV::XAAA; + constexpr auto X555 = TV::X555; + constexpr auto ZA = TV::template XAAATruncZeroOf<T>; + constexpr auto Z5 = TV::template X555TruncZeroOf<T>; + this->checkCastTo<F, T>({{XAAA, ZA}, {X555, Z5}}, + {{ToMIN, 0}, {X555, ToMAX}}); + // Use `if constexpr` here. + if (is_signed_v<F>) { + // One range + this->checkCastTo<F, T>({{XAAA, ZA}}, {{0, 0}, {XAAA, ToMAX}}); + // Two ranges + this->checkCastTo<F, T>({{XAAA, ZA}, {1, 42}}, {{0, 42}, {XAAA, ToMAX}}); + } else { + // One range + this->checkCastTo<F, T>({{XAAA, ZA}}, {{XAAA, 0}}); + // Two ranges + this->checkCastTo<F, T>({{1, 42}, {XAAA, ZA}}, {{XAAA, 42}}); + } + constexpr auto FromA = TV::FromA; + constexpr auto ToA = TV::ToA; + constexpr auto FromB = TV::FromB; + constexpr auto ToB = TV::ToB; + this->checkCastTo<F, T>({{FromA, ToA}, {FromB, ToB}}, {{FromA, ToA}}); + } +} + +} // namespace Index: clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -680,6 +680,63 @@ return makePersistent(std::move(Result)); } +// Convert range set to the given integral type using truncation and promotion. +// This works similar to APSIntType::apply function but for the range set. +RangeSet clang::ento::RangeSet::Factory::castTo(RangeSet What, APSIntType Ty) { + if (What.isEmpty()) + return What; + + using llvm::APInt; + using llvm::APSInt; + ContainerType Result; + ContainerType RHS; + + // CastRangeSize is a number of all possible values of cast type. + // Example: char has 256 values; short has 65535 values + // But in fact we use `number of values` - 1, because + // we can't hold `UINT64 number of values` inside uint64_t. + // And it's OK, it's enough to do correct calculations. + uint64_t CastRangeSize = APInt::getMaxValue(Ty.getBitWidth()).getZExtValue(); + + // Convert each range from the original range set. + for (const Range &R : What) { + // Get bounds of the next range. + APSInt FromInt = R.From(); + APSInt ToInt = R.To(); + RHS.clear(); + // CurrentRangeSize is a number of all possible values of the current range + // minus one. + uint64_t CurrentRangeSize = (ToInt - FromInt).getZExtValue(); + if (CurrentRangeSize < CastRangeSize) { + // Truncate or promote bounds of the range. + Ty.apply(FromInt); + Ty.apply(ToInt); + if (FromInt > ToInt) { + RHS.emplace_back(ValueFactory.getMinValue(Ty), + ValueFactory.getValue(ToInt)); + RHS.emplace_back(ValueFactory.getValue(FromInt), + ValueFactory.getMaxValue(Ty)); + } else + RHS.emplace_back(ValueFactory.getValue(FromInt), + ValueFactory.getValue(ToInt)); + Result = unite(Result, RHS); + } else { + // We are enough to cover the whole range, then exit. + RHS.emplace_back(ValueFactory.getMinValue(Ty), + ValueFactory.getMaxValue(Ty)); + Result = std::move(RHS); + break; + } + } + + return makePersistent(std::move(Result)); +} + +RangeSet clang::ento::RangeSet::Factory::castTo(RangeSet What, QualType T) { + assert(T->isIntegralOrEnumerationType() && "T shall be an integral type."); + return castTo(What, ValueFactory.getAPSIntType(T)); +} + RangeSet RangeSet::Factory::deletePoint(RangeSet From, const llvm::APSInt &Point) { if (!From.contains(Point)) Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h @@ -236,6 +236,16 @@ /// Complexity: O(N) /// where N = size(What) RangeSet negate(RangeSet What); + /// Performs promotions, truncations and conversions of the given set. + /// + /// NOTE: This function is NOT self-inverse: + /// - castTo(castTo(OrigRangeOfChar, int), char) != OrigRangeOfChar. + /// - castTo(castTo(OrigRangeOfChar, int), char) == AnotherRangeOfChar. + /// + /// Complexity: O(N^2) + /// where N = size(What) + RangeSet castTo(RangeSet What, APSIntType Ty); + RangeSet castTo(RangeSet What, QualType T); private: /// Return a persistent version of the given container.
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits