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

Reply via email to