Index: clang/lib/StaticAnalyzer/Core/RegionStore.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -437,6 +437,8 @@ RegionBindingsRef removeSubRegionBindings(RegionBindingsConstRef B, const SubRegion *R); + bool canAccessStoredValue(QualType OrigT, QualType ThroughT, + uint64_t Index) const; public: // Part of public interface to class. @@ -1625,6 +1627,56 @@ return Result; } +/// Returns true if the stored value can be accessed through the pointer to +/// another type: +/// const int arr[42] = {}; +/// auto* pchar = (char*)arr; +/// auto* punsigned = (unsigned int*)arr; +/// auto* pshort = (short*)arr; +/// auto x1 = pchar[0]; // valid +/// auto x2 = pchar[1]; // invalid +/// auto x3 = punsigned[0]; // valid +/// auto x4 = pshort; // invalid +bool RegionStoreManager::canAccessStoredValue(QualType OrigT, QualType ThroughT, + uint64_t Index) const { + // Remove cv-qualifiers. + OrigT = OrigT->getCanonicalTypeUnqualified(); + ThroughT = ThroughT->getCanonicalTypeUnqualified(); + + // C++20 7.2.1.11 [basic.lval] (excerpt): + // A program can access the stored value of an object through: + // - the same type of the object; + // - a signed or unsigned type corresponding to the type of the + // object; + // - a char, unsigned char, std::byte. (NOTE: + // Otherwise, the behavior is undefined. + return + // - is same + (OrigT == ThroughT) || + // - is another sign + (((OrigT == Ctx.CharTy && ThroughT == Ctx.UnsignedCharTy) || + (OrigT == Ctx.SignedCharTy && ThroughT == Ctx.UnsignedCharTy) || + (OrigT == Ctx.ShortTy && ThroughT == Ctx.UnsignedShortTy) || + (OrigT == Ctx.IntTy && ThroughT == Ctx.UnsignedIntTy) || + (OrigT == Ctx.LongTy && ThroughT == Ctx.UnsignedLongTy) || + (OrigT == Ctx.LongLongTy && ThroughT == Ctx.UnsignedLongLongTy) || + (ThroughT == Ctx.CharTy && OrigT == Ctx.UnsignedCharTy) || + (ThroughT == Ctx.SignedCharTy && OrigT == Ctx.UnsignedCharTy) || + (ThroughT == Ctx.ShortTy && OrigT == Ctx.UnsignedShortTy) || + (ThroughT == Ctx.IntTy && OrigT == Ctx.UnsignedIntTy) || + (ThroughT == Ctx.LongTy && OrigT == Ctx.UnsignedLongTy) || + (ThroughT == Ctx.LongLongTy && OrigT == Ctx.UnsignedLongLongTy) || + // - is char, uchar, std::byte + (ThroughT == Ctx.CharTy) || (ThroughT == Ctx.UnsignedCharTy) || + ThroughT->isStdByteType()) && + // NOTE: C++20 6.8.2(3.4) [basic.compound]: + // An object of type T that is not an array element is considered to + // belong to an array with one element of type T. + // Hence, the first element can be retrieved only. At least untill a + // paper P1839R0 be considered by the committee. + (Index == 0)); +} + SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, const ElementRegion* R) { // Check if the region has a binding. @@ -1689,7 +1741,8 @@ // int x3 = ptr[3]; // 0 // int x4 = ptr[4]; // UB // TODO: Support multidimensional array. - if (!isa(CAT->getElementType())) { + QualType ArrT = Ctx.getCanonicalType(CAT->getElementType()); + if (!isa(ArrT)) { // One-dimensional array. const llvm::APSInt &Idx = CI->getValue(); const auto I = static_cast(Idx.getExtValue()); @@ -1700,11 +1753,17 @@ if (Idx < 0 || I >= Extent) return UndefinedVal(); + // Check whether a program can access the stored value of another + // type. + QualType ElemT = Ctx.getCanonicalType(R->getElementType()); + if (!canAccessStoredValue(ArrT, ElemT, I)) + return UndefinedVal(); + // C++20 [expr.add] 9.4.17.5 (excerpt): // i-th array element is value-initialized for each k < i ≤ n, // where k is an expression-list size and n is an array extent. if (I >= InitList->getNumInits()) - return svalBuilder.makeZeroVal(R->getElementType()); + return svalBuilder.makeZeroVal(ElemT); // Return a constant value, if it is presented. // FIXME: Support other SVals. Index: clang/test/Analysis/initialization.cpp =================================================================== --- clang/test/Analysis/initialization.cpp +++ clang/test/Analysis/initialization.cpp @@ -2,6 +2,10 @@ void clang_analyzer_eval(int); +namespace std { +enum class byte : unsigned char {}; +}; + struct S { int a = 3; }; @@ -48,6 +52,46 @@ auto x = ptr[idx]; // expected-warning{{garbage or undefined}} } +void glob_cast_same() { + auto *ptr = (int *)glob_arr2; + auto x1 = ptr[0]; // no-warning + auto x2 = ptr[1]; // no-warning +} + +void glob_cast_char() { + const auto *ptr = (char *)glob_arr2; + auto x1 = ptr[0]; // no-warning + auto x2 = ptr[1]; // expected-warning{{garbage or undefined}} +} + +void glob_cast_uchar() { + auto *ptr = (unsigned char *)glob_arr2; + auto x1 = ptr[0]; // no-warning + auto x2 = ptr[1]; // expected-warning{{garbage or undefined}} +} + +void glob_cast_byte() { + auto *ptr = (const std::byte *)glob_arr2; + auto x1 = ptr[0]; // no-warning + auto x2 = ptr[1]; // expected-warning{{garbage or undefined}} +} + +void glob_cast_opposite_sign() { + auto *ptr = (unsigned int *)glob_arr2; + auto x1 = ptr[0]; // no-warning + auto x2 = ptr[1]; // expected-warning{{garbage or undefined}} +} + +void glob_cast_invalid1() { + auto *ptr = (signed char *)glob_arr2; + auto x = ptr[0]; // expected-warning{{garbage or undefined}} +} + +void glob_cast_invalid2() { + using T = short *; + auto x = ((T)glob_arr2)[0]; // expected-warning{{garbage or undefined}} +} + const float glob_arr3[] = { 0.0000, 0.0235, 0.0470, 0.0706, 0.0941, 0.1176}; float no_warn_garbage_value() {