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. 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/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 constexpr bool is_signed_v = std::is_signed::value; + template struct TestValues { static constexpr T MIN = std::numeric_limits::min(); static constexpr T MAX = std::numeric_limits::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::value ? 0 : ~(static_cast(-1) / static_cast(2)); + is_signed_v ? 0 : ~(static_cast(-1) / static_cast(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 + static const T ClearLowBytes = static_cast(static_cast(Value) + << ((Bytes >= 8) ? 0 : Bytes) * + 8); + + template + static constexpr T TruncZeroOf = ClearLowBytes; + + // Random number with active bits in every byte. 0xAAAA'AAAA + static constexpr T XAAA = static_cast( + 0b10101010'10101010'10101010'10101010'10101010'10101010'10101010'10101010); + template + static constexpr T XAAATruncZeroOf = TruncZeroOf; // 0xAAAA'AB00 + + // Random number with active bits in every byte. 0x5555'5555 + static constexpr T X555 = static_cast( + 0b01010101'01010101'01010101'01010101'01010101'01010101'01010101'01010101); + template + static constexpr T X555TruncZeroOf = TruncZeroOf; // 0x5555'5600 + + // Numbers for ranges with the same bits in the lowest byte. + // 0xAAAA'AA2A + static constexpr T FromA = ClearLowBytes + 42; + static constexpr T ToA = FromA + 2; // 0xAAAA'AA2C + // 0x5555'552A + static constexpr T FromB = ClearLowBytes + 42; + static constexpr T ToB = FromB + 2; // 0x5555'552C }; template class RangeSetTest : public testing::Test { @@ -69,8 +103,11 @@ // End init block using Self = RangeSetTest; - using RawRange = std::pair; - using RawRangeSet = std::initializer_list; + template using RawRangeT = std::pair; + template + using RawRangeSetT = std::initializer_list>; + using RawRange = RawRangeT; + using RawRangeSet = RawRangeSetT; 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::value}; + return from(Ty, Init); + } + + template RangeSet from(APSIntType Ty, RawRangeSetT 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 + void checkCastTo(RawRangeSetT What, RawRangeSetT Expected) { + static APSIntType FromTy(sizeof(From) * 8, !is_signed_v); + static APSIntType ToTy(sizeof(To) * 8, !is_signed_v); + this->checkCastToImpl(from(FromTy, What), ToTy, from(ToTy, Expected)); + } + + void checkCastTo_NOOP(); + template void checkCastTo_Promotion(); + template void checkCastTo_Truncation(); + template void checkCastTo_Conversion(); + template void checkCastTo_PromotionConversion(); + template void checkCastTo_TruncationConversion(); +}; using IntTypes = ::testing::Types; @@ -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(); + this->template checkCastTo_Promotion(); + this->template checkCastTo_Promotion(); + this->template checkCastTo_Promotion(); + this->template checkCastTo_Promotion(); + this->template checkCastTo_Promotion(); + + // Truncation + this->template checkCastTo_Truncation(); + this->template checkCastTo_Truncation(); + this->template checkCastTo_Truncation(); + this->template checkCastTo_Truncation(); + this->template checkCastTo_Truncation(); + this->template checkCastTo_Truncation(); + + // Conversion + this->template checkCastTo_Conversion(); + this->template checkCastTo_Conversion(); + this->template checkCastTo_Conversion(); + this->template checkCastTo_Conversion(); + this->template checkCastTo_Conversion(); + this->template checkCastTo_Conversion(); + this->template checkCastTo_Conversion(); + this->template checkCastTo_Conversion(); + + // Promotion + Conversion + this->template checkCastTo_PromotionConversion(); + this->template checkCastTo_PromotionConversion(); + this->template checkCastTo_PromotionConversion(); + this->template checkCastTo_PromotionConversion(); + this->template checkCastTo_PromotionConversion(); + this->template checkCastTo_PromotionConversion(); + + // Truncation + Conversion + this->template checkCastTo_TruncationConversion(); + this->template checkCastTo_TruncationConversion(); + this->template checkCastTo_TruncationConversion(); + this->template checkCastTo_TruncationConversion(); + this->template checkCastTo_TruncationConversion(); + this->template checkCastTo_TruncationConversion(); +} + +template void RangeSetTest::checkCastTo_NOOP() { + // Just to reduce the verbosity. + using F = BaseType; // From + using T = BaseType; // To + + using TV = TestValues; + 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({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo({{MID, MID}}, {{MID, MID}}); + this->checkCastTo({{B, B}}, {{B, B}}); + this->checkCastTo({{C, C}}, {{C, C}}); + // Two points + this->checkCastTo({{MIN, MIN}, {MAX, MAX}}, {{MIN, MIN}, {MAX, MAX}}); + this->checkCastTo({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo({{MID, MID}, {MAX, MAX}}, {{MID, MID}, {MAX, MAX}}); + this->checkCastTo({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + this->checkCastTo({{MIN, MAX}}, {{MIN, MAX}}); + this->checkCastTo({{MIN, MID}}, {{MIN, MID}}); + this->checkCastTo({{MID, MAX}}, {{MID, MAX}}); + this->checkCastTo({{B, MAX}}, {{B, MAX}}); + this->checkCastTo({{C, MAX}}, {{C, MAX}}); + this->checkCastTo({{MIN, C}}, {{MIN, C}}); + this->checkCastTo({{MIN, B}}, {{MIN, B}}); + this->checkCastTo({{B, C}}, {{B, C}}); + // Two ranges + this->checkCastTo({{MIN, B}, {C, MAX}}, {{MIN, B}, {C, MAX}}); + this->checkCastTo({{B, MID}, {C, MAX}}, {{B, MID}, {C, MAX}}); + this->checkCastTo({{MIN, B}, {MID, C}}, {{MIN, B}, {MID, C}}); +} + +template +template +void RangeSetTest::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 == is_signed_v); + + // Use `if constexpr` here. + if (CanPromote) { + using TV = TestValues; + 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({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo({{MID, MID}}, {{MID, MID}}); + this->checkCastTo({{B, B}}, {{B, B}}); + this->checkCastTo({{C, C}}, {{C, C}}); + // Two points + this->checkCastTo({{MIN, MIN}, {MAX, MAX}}, {{MIN, MIN}, {MAX, MAX}}); + this->checkCastTo({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo({{MID, MID}, {MAX, MAX}}, {{MID, MID}, {MAX, MAX}}); + this->checkCastTo({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + this->checkCastTo({{MIN, MAX}}, {{MIN, MAX}}); + this->checkCastTo({{MIN, MID}}, {{MIN, MID}}); + this->checkCastTo({{MID, MAX}}, {{MID, MAX}}); + this->checkCastTo({{B, MAX}}, {{B, MAX}}); + this->checkCastTo({{C, MAX}}, {{C, MAX}}); + this->checkCastTo({{MIN, C}}, {{MIN, C}}); + this->checkCastTo({{MIN, B}}, {{MIN, B}}); + this->checkCastTo({{B, C}}, {{B, C}}); + // Two ranges + this->checkCastTo({{MIN, B}, {C, MAX}}, {{MIN, B}, {C, MAX}}); + this->checkCastTo({{B, MID}, {C, MAX}}, {{B, MID}, {C, MAX}}); + this->checkCastTo({{MIN, B}, {MID, C}}, {{MIN, B}, {MID, C}}); + } +} + +template +template +void RangeSetTest::checkCastTo_Truncation() { + // Just to reduce the verbosity. + using F = From; // From + using T = BaseType; // To + using TV = TestValues; + + constexpr bool CanTruncate = + (sizeof(F) > sizeof(T) && is_signed_v == is_signed_v); + + // 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({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo({{MID, MID}}, {{MID, MID}}); + this->checkCastTo({{B, B}}, {{B, B}}); + this->checkCastTo({{C, C}}, {{C, C}}); + // Two points + // Use `if constexpr` here. + if (is_signed_v) { + this->checkCastTo({{MIN, MIN}, {MAX, MAX}}, {{MAX, MIN}}); + this->checkCastTo({{MID, MID}, {MAX, MAX}}, {{MAX, MID}}); + } else { + this->checkCastTo({{MIN, MIN}, {MAX, MAX}}, + {{MIN, MIN}, {MAX, MAX}}); + this->checkCastTo({{MID, MID}, {MAX, MAX}}, + {{MID, MID}, {MAX, MAX}}); + } + this->checkCastTo({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + constexpr auto ToMIN = TestValues::MIN; + constexpr auto ToMAX = TestValues::MAX; + this->checkCastTo({{MIN, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, MID}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MID, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{B, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, C}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, B}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{B, C}}, {{ToMIN, ToMAX}}); + // Two ranges + this->checkCastTo({{MIN, B}, {C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{B, MID}, {C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, B}, {MID, C}}, {{ToMIN, ToMAX}}); + constexpr auto XAAA = TV::XAAA; + constexpr auto X555 = TV::X555; + constexpr auto ZA = TV::template XAAATruncZeroOf; + constexpr auto Z5 = TV::template X555TruncZeroOf; + this->checkCastTo({{XAAA, ZA}, {X555, Z5}}, + {{ToMIN, 0}, {X555, ToMAX}}); + // Use `if constexpr` here. + if (is_signed_v) { + // One range + this->checkCastTo({{XAAA, ZA}}, {{XAAA, 0}}); + // Two ranges + this->checkCastTo({{XAAA, ZA}, {1, 42}}, {{XAAA, 42}}); + } else { + // One range + this->checkCastTo({{XAAA, ZA}}, {{0, 0}, {XAAA, ToMAX}}); + // Two ranges + this->checkCastTo({{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({{FromA, ToA}, {FromB, ToB}}, {{FromA, ToA}}); + } +} + +template +template +void RangeSetTest::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 != is_signed_v); + + // Use `if constexpr` here. + if (CanConvert) { + using TV = TestValues; + 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({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo({{MID, MID}}, {{MID, MID}}); + this->checkCastTo({{B, B}}, {{B, B}}); + this->checkCastTo({{C, C}}, {{C, C}}); + // Two points + this->checkCastTo({{MIN, MIN}, {MAX, MAX}}, {{MAX, MIN}}); + this->checkCastTo({{MID, MID}, {MAX, MAX}}, {{MID, MID}, {MAX, MAX}}); + this->checkCastTo({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + constexpr auto ToMIN = TestValues::MIN; + constexpr auto ToMAX = TestValues::MAX; + this->checkCastTo({{MIN, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, MID}}, {{ToMIN, ToMIN}, {MIN, ToMAX}}); + this->checkCastTo({{MID, MAX}}, {{MID, MAX}}); + this->checkCastTo({{B, MAX}}, {{ToMIN, MAX}, {B, ToMAX}}); + this->checkCastTo({{C, MAX}}, {{C, MAX}}); + this->checkCastTo({{MIN, C}}, {{ToMIN, C}, {MIN, ToMAX}}); + this->checkCastTo({{MIN, B}}, {{MIN, B}}); + this->checkCastTo({{B, C}}, {{ToMIN, C}, {B, ToMAX}}); + // Two ranges + this->checkCastTo({{MIN, B}, {C, MAX}}, {{C, B}}); + this->checkCastTo({{B, MID}, {C, MAX}}, + {{MID, MID}, {C, MAX}, {B, ToMAX}}); + this->checkCastTo({{MIN, B}, {MID, C}}, {{MID, C}, {MIN, B}}); + } +} + +template +template +void RangeSetTest::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 != is_signed_v); + + // Use `if constexpr` here. + if (CanPromoteAndConvert) { + using TV = TestValues; + 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({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo({{MID, MID}}, {{MID, MID}}); + this->checkCastTo({{B, B}}, {{B, B}}); + this->checkCastTo({{C, C}}, {{C, C}}); + // Two points + this->checkCastTo({{MIN, MIN}, {MAX, MAX}}, {{MAX, MAX}, {MIN, MIN}}); + this->checkCastTo({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo({{MID, MID}, {MAX, MAX}}, {{MID, MID}, {MAX, MAX}}); + this->checkCastTo({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo({{B, B}, {C, C}}, {{B, B}, {C, C}}); + + // Use `if constexpr` here. + if (is_signed_v) { + // One range + this->checkCastTo({{MIN, MAX}}, {{0, MAX}, {MIN, -1}}); + this->checkCastTo({{MIN, MID}}, {{0, 0}, {MIN, -1}}); + this->checkCastTo({{MID, MAX}}, {{0, MAX}}); + this->checkCastTo({{B, MAX}}, {{0, MAX}, {B, -1}}); + this->checkCastTo({{C, MAX}}, {{C, MAX}}); + this->checkCastTo({{MIN, C}}, {{0, C}, {MIN, -1}}); + this->checkCastTo({{MIN, B}}, {{MIN, B}}); + this->checkCastTo({{B, C}}, {{0, C}, {B, -1}}); + // Two ranges + this->checkCastTo({{MIN, B}, {C, MAX}}, {{C, MAX}, {MIN, B}}); + this->checkCastTo({{B, MID}, {C, MAX}}, + {{0, 0}, {C, MAX}, {B, -1}}); + this->checkCastTo({{MIN, B}, {MID, C}}, {{0, C}, {MIN, B}}); + } else { + // One range + this->checkCastTo({{MIN, MAX}}, {{MIN, MAX}}); + this->checkCastTo({{MIN, MID}}, {{MIN, MID}}); + this->checkCastTo({{MID, MAX}}, {{MID, MAX}}); + this->checkCastTo({{B, MAX}}, {{B, MAX}}); + this->checkCastTo({{C, MAX}}, {{C, MAX}}); + this->checkCastTo({{MIN, C}}, {{MIN, C}}); + this->checkCastTo({{MIN, B}}, {{MIN, B}}); + this->checkCastTo({{B, C}}, {{B, C}}); + // Two ranges + this->checkCastTo({{MIN, B}, {C, MAX}}, {{MIN, B}, {C, MAX}}); + this->checkCastTo({{B, MID}, {C, MAX}}, {{B, MID}, {C, MAX}}); + this->checkCastTo({{MIN, B}, {MID, C}}, {{MIN, B}, {MID, C}}); + } + } +} + +template +template +void RangeSetTest::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 != is_signed_v); + + if (CanTruncateAndConvert) { + using TV = TestValues; + 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({{MIN, MIN}}, {{MIN, MIN}}); + this->checkCastTo({{MAX, MAX}}, {{MAX, MAX}}); + this->checkCastTo({{MID, MID}}, {{MID, MID}}); + this->checkCastTo({{B, B}}, {{B, B}}); + this->checkCastTo({{C, C}}, {{C, C}}); + // Two points + // Use `if constexpr` here. + if (is_signed_v) { + this->checkCastTo({{MIN, MIN}, {MAX, MAX}}, + {{MIN, MIN}, {MAX, MAX}}); + this->checkCastTo({{MID, MID}, {MAX, MAX}}, + {{MID, MID}, {MAX, MAX}}); + } else { + this->checkCastTo({{MIN, MIN}, {MAX, MAX}}, {{MAX, MIN}}); + this->checkCastTo({{MID, MID}, {MAX, MAX}}, {{MAX, MIN}}); + } + this->checkCastTo({{MIN, MIN}, {B, B}}, {{MIN, MIN}, {B, B}}); + this->checkCastTo({{C, C}, {MAX, MAX}}, {{C, C}, {MAX, MAX}}); + this->checkCastTo({{MID, MID}, {C, C}}, {{MID, MID}, {C, C}}); + this->checkCastTo({{B, B}, {MID, MID}}, {{B, B}, {MID, MID}}); + this->checkCastTo({{B, B}, {C, C}}, {{B, B}, {C, C}}); + // One range + constexpr auto ToMIN = TestValues::MIN; + constexpr auto ToMAX = TestValues::MAX; + this->checkCastTo({{MIN, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, MID}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MID, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{B, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, C}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, B}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{B, C}}, {{ToMIN, ToMAX}}); + // Two ranges + this->checkCastTo({{MIN, B}, {C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{B, MID}, {C, MAX}}, {{ToMIN, ToMAX}}); + this->checkCastTo({{MIN, B}, {MID, C}}, {{ToMIN, ToMAX}}); + constexpr auto XAAA = TV::XAAA; + constexpr auto X555 = TV::X555; + constexpr auto ZA = TV::template XAAATruncZeroOf; + constexpr auto Z5 = TV::template X555TruncZeroOf; + this->checkCastTo({{XAAA, ZA}, {X555, Z5}}, + {{ToMIN, 0}, {X555, ToMAX}}); + // Use `if constexpr` here. + if (is_signed_v) { + // One range + this->checkCastTo({{XAAA, ZA}}, {{0, 0}, {XAAA, ToMAX}}); + // Two ranges + this->checkCastTo({{XAAA, ZA}, {1, 42}}, {{0, 42}, {XAAA, ToMAX}}); + } else { + // One range + this->checkCastTo({{XAAA, ZA}}, {{XAAA, 0}}); + // Two ranges + this->checkCastTo({{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({{FromA, ToA}, {FromB, ToB}}, {{FromA, ToA}}); + } +} + +} // namespace