Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h @@ -147,6 +147,8 @@ /// Compute the offset within the top level memory object. RegionOffset getAsOffset() const; + int64_t getExtent() const; + /// Get a string representation of a region for debug use. std::string getString() const; Index: clang/lib/StaticAnalyzer/Core/MemRegion.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/MemRegion.cpp +++ clang/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -1617,6 +1617,69 @@ return *cachedOffset; } +// FIXME: This function is similar to MemRegionManager::getStaticSize(). In the +// future they could be merged somehow. +int64_t MemRegion::getExtent() const { + const MemRegion *R = this; + + // Prior to this patch we assumed the + // extent of all region is infinite, so in case + // we fail to find the extent, we default to + // "infinity". + int64_t Extent = INT64_MAX; + + if (const auto *SR = dyn_cast(R)) + return SR->getStringLiteral()->getByteLength() + 1; + + if (const auto *FR = dyn_cast(R)) { + const FieldDecl *FD = FR->getDecl(); + if (FD->isBitField()) + return FD->getBitWidthValue(getContext()); + + // FIXME: This check for flexible array members could be improved by gaining + // access to the analyzer options. See MemRegionManager::getStaticSize() for + // more information. + const auto Ty = FR->getDesugaredValueType(getContext()); + if (isa(Ty)) + return Extent; + + if (const auto *CAT = dyn_cast(Ty); + CAT && CAT->getSize() == 0) + return Extent; + } + + if (const auto *TVR = dyn_cast(R)) { + const auto Ty = TVR->getDesugaredValueType(getContext()); + + if (isa(Ty)) + return Extent; + + if (Ty->isIncompleteType() || Ty->isDependentType()) + return Extent; + + Extent = getContext().getTypeSize(Ty); + } else if (const auto *SR = dyn_cast(R)) { + const auto Ty = SR->getSymbol()->getType().getDesugaredType(getContext()); + + if (!Ty->isIncompleteType() && !Ty->isDependentType() && + !Ty->isVoidPointerType()) + Extent = getContext().getTypeSize(Ty); + } + + // We either failed to find the extent or we assign something to a + // placeholder. E.g.: + // struct hh { + // char s1[0]; + // char *s2; + // }; + // + // memcpy(h0.s1, input, 4); + if (Extent == 0) + return INT64_MAX; + + return Extent; +} + //===----------------------------------------------------------------------===// // BlockDataRegion //===----------------------------------------------------------------------===// Index: clang/lib/StaticAnalyzer/Core/RegionStore.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -47,35 +47,55 @@ enum { Symbolic = 0x2 }; llvm::PointerIntPair P; - uint64_t Data; + int64_t Data; + int64_t Extent; /// Create a key for a binding to region \p r, which has a symbolic offset /// from region \p Base. - explicit BindingKey(const SubRegion *r, const SubRegion *Base, Kind k) - : P(r, k | Symbolic), Data(reinterpret_cast(Base)) { + explicit BindingKey(const SubRegion *r, const SubRegion *Base, Kind k, + int64_t extent) + : P(r, k | Symbolic), Data(reinterpret_cast(Base)), + Extent(extent) { assert(r && Base && "Must have known regions."); assert(getConcreteOffsetRegion() == Base && "Failed to store base region"); } /// Create a key for a binding at \p offset from base region \p r. - explicit BindingKey(const MemRegion *r, uint64_t offset, Kind k) - : P(r, k), Data(offset) { + explicit BindingKey(const MemRegion *r, int64_t offset, Kind k, + int64_t extent) + : P(r, k), Data(offset), Extent(extent) { assert(r && "Must have known regions."); assert(getOffset() == offset && "Failed to store offset"); assert((r == r->getBaseRegion() || isa(r)) && "Not a base"); } + public: bool isDirect() const { return P.getInt() & Direct; } bool hasSymbolicOffset() const { return P.getInt() & Symbolic; } const MemRegion *getRegion() const { return P.getPointer(); } - uint64_t getOffset() const { + int64_t getOffset() const { assert(!hasSymbolicOffset()); return Data; } + int64_t getExtent() const { return Extent; } + + int64_t getRangeBegin() const { + assert(!hasSymbolicOffset()); + return getOffset(); + } + int64_t getRangeEnd() const { + assert(!hasSymbolicOffset()); + + // Try to avoid overflow. + if (getExtent() == INT64_MAX) + return getExtent(); + + return getOffset() + getExtent(); + } const SubRegion *getConcreteOffsetRegion() const { assert(hasSymbolicOffset()); @@ -91,21 +111,63 @@ void Profile(llvm::FoldingSetNodeID& ID) const { ID.AddPointer(P.getOpaqueValue()); ID.AddInteger(Data); + ID.AddInteger(Extent); } static BindingKey Make(const MemRegion *R, Kind k); + static BindingKey Make(const MemRegion *R, int64_t offset, int64_t extent, + Kind k); + bool operator<(const BindingKey &X) const { if (P.getOpaqueValue() < X.P.getOpaqueValue()) return true; if (P.getOpaqueValue() > X.P.getOpaqueValue()) return false; - return Data < X.Data; + + return Data == X.Data ? Extent < X.Extent : Data < X.Data; } bool operator==(const BindingKey &X) const { - return P.getOpaqueValue() == X.P.getOpaqueValue() && - Data == X.Data; + return P.getOpaqueValue() == X.P.getOpaqueValue() && Data == X.Data && + Extent == X.Extent; + } + + bool contains(const BindingKey &X) const { + if (hasSymbolicOffset() || X.hasSymbolicOffset()) + return false; + + if (getRegion() != X.getRegion()) + return false; + + return X.getRangeBegin() >= getRangeBegin() && + X.getRangeBegin() < getRangeEnd() && + X.getRangeEnd() <= getRangeEnd() && + X.getRangeEnd() > getRangeBegin(); + } + + bool overlapsWith(const BindingKey &X) const { + if (hasSymbolicOffset() || X.hasSymbolicOffset()) + return false; + + if (getRegion() != X.getRegion()) + return false; + + uint64_t keyBegin = getRangeBegin(); + uint64_t keyEnd = getRangeEnd(); + uint64_t KBegin = X.getRangeBegin(); + uint64_t KEnd = X.getRangeEnd(); + + // this: |-----| + // X: |-----| + if (keyBegin <= KBegin && KBegin < keyEnd) + return true; + // this: |-----| + // X: |-----| + if (keyBegin >= KBegin && keyBegin < KEnd) + return true; + + return contains(X) || X.contains(*this); } LLVM_DUMP_METHOD void dump() const; @@ -115,11 +177,16 @@ BindingKey BindingKey::Make(const MemRegion *R, Kind k) { const RegionOffset &RO = R->getAsOffset(); if (RO.hasSymbolicOffset()) - return BindingKey(cast(R), cast(RO.getRegion()), k); + return BindingKey(cast(R), cast(RO.getRegion()), k, + R->getExtent()); - return BindingKey(RO.getRegion(), RO.getOffset(), k); + return BindingKey(RO.getRegion(), RO.getOffset(), k, R->getExtent()); } +BindingKey BindingKey::Make(const MemRegion *R, int64_t offset, int64_t extent, + Kind k) { + return BindingKey(R, offset, k, extent); +} namespace llvm { static inline raw_ostream &operator<<(raw_ostream &Out, BindingKey K) { Out << "\"kind\": \"" << (K.isDirect() ? "Direct" : "Default") @@ -130,6 +197,9 @@ else Out << "null"; + Out << ", \"extent\": "; + Out << K.getExtent(); + return Out; } @@ -143,7 +213,201 @@ // Actual Store type. //===----------------------------------------------------------------------===// -typedef llvm::ImmutableMap ClusterBindings; +// FIXME: Remove this artifact once the patch is finalized. +#define RSPPDEBUG 0 + +class ClusterBindings : public llvm::ImmutableMap { +public: + ClusterBindings(const llvm::ImmutableMap &m) + : llvm::ImmutableMap(m) {} + ClusterBindings(llvm::ImmutableMap &&m) + : llvm::ImmutableMap(m) {} + +public: + const SVal *lookup(const BindingKey &K) const { + +#if RSPPDEBUG + llvm::errs() << "Lookup key: "; + K.dump(); + llvm::errs() << "\n"; + dump(); +#endif + + auto res = llvm::ImmutableMap::lookup(K); + +#if RSPPDEBUG + llvm::errs() << "Found: " << res << '\n'; + llvm::errs() << "--------------------------------------------------\n"; +#endif + + return res; + } + + const SVal *lookupPartial(const BindingKey &K) const { +#if RSPPDEBUG + llvm::errs() << "Partial lookup key: "; + K.dump(); + llvm::errs() << "\n"; + dump(); +#endif + + if (K.hasSymbolicOffset()) + return nullptr; + + // Try to find the smallest binding, the key is the part of. + int64_t lowerBound = 0; + int64_t upperBound = INT64_MAX; + const SVal *smallest = nullptr; + for (auto &[Key, Val] : *this) + if (Key.isDirect() == K.isDirect() && Key.contains(K) && + Key.getRangeBegin() >= lowerBound && + Key.getRangeEnd() <= upperBound) { + lowerBound = Key.getRangeBegin(); + upperBound = Key.getRangeEnd(); + smallest = &Val; + } + +#if RSPPDEBUG + llvm::errs() << "hit: "; + if (smallest) + smallest->dump(); + else + llvm::errs() << "nullptr"; + + llvm::errs() << "\n--------------------------------------------------\n"; +#endif + + return smallest; + } + + using Binding = std::pair; + std::vector getBindingsCoveringBinding(const BindingKey &K) const { + // We can't reason about symbolic offsets. + if (K.hasSymbolicOffset()) + return {}; + + // The algorithm used in this function is heavy and we only + // want it to run on bindings that overlap the one we look for. + using BindingRef = const Binding *; + std::vector filteredCluster; + + for (auto &B : *this) + if (B.first.overlapsWith(K)) + filteredCluster.emplace_back(&B); + + // If there are no bindings in the cluster, that overlap the + // current one, we return here. + if (filteredCluster.empty()) + return {}; + + // Sort the bidnings by their ranges in ascending order. + std::sort(filteredCluster.begin(), filteredCluster.end(), + [](BindingRef lhs, BindingRef rhs) { + // Note that we want the wider binding to be placed before the + // narrower. + if (lhs->first.getRangeBegin() == rhs->first.getRangeBegin()) + return lhs->first.getRangeEnd() > rhs->first.getRangeEnd(); + + return lhs->first.getRangeBegin() < rhs->first.getRangeBegin(); + }); + + // Concatenate the ranges and values. + std::vector out; + out.reserve(filteredCluster.size()); + + std::vector stack; + stack.reserve(filteredCluster.size()); + + auto lastCovered = [&] { + return out.empty() ? K.getRangeBegin() : out.back().first.getRangeEnd(); + }; + + auto insertUpTo = [&](int64_t pos, BindingRef entry) { + if (pos > lastCovered()) { + // FIXME: Ideally a new key for the UndefinedVal{} should be created + // here. + out.push_back(entry ? *entry : Binding(K, UndefinedVal{})); + } + }; + + auto pushOverlapingStartingOn = [&](auto it, int64_t pos) { + auto last = it; + do { + // Append all binding starting at ths point + stack.push_back(*it); + last = it; + + if (++it == filteredCluster.end()) + break; + + } while ((*it)->first.getRangeBegin() == pos); + return last; + }; + + auto popFinishedBindignsAndUpdateCoverage = [&](int64_t pos) { + // Pop all binidings, that are not longer overlaping with pos + while (!stack.empty() && (stack.back()->first.getRangeEnd() <= pos)) { + BindingRef top = stack.back(); + insertUpTo(top->first.getRangeEnd(), top); + stack.pop_back(); + } + }; + + auto bindingIt = filteredCluster.begin(); + for (int64_t pos = (*bindingIt)->first.getRangeBegin(); + pos < K.getRangeEnd(); ++pos) { + // Find the first binding with getRangBegin() >= pos. + bindingIt = std::lower_bound( + bindingIt, filteredCluster.end(), pos, + [](BindingRef e, int64_t v) { return e->first.getRangeBegin() < v; }); + if (bindingIt == filteredCluster.end()) + break; + + pos = (*bindingIt)->first.getRangeBegin(); + popFinishedBindignsAndUpdateCoverage(pos); + + auto const *top = stack.empty() ? nullptr : stack.back(); + bindingIt = pushOverlapingStartingOn(bindingIt, pos); + + if (pos >= K.getRangeBegin()) { + insertUpTo((*bindingIt)->first.getRangeBegin(), top); + out.push_back(**bindingIt); + } + } + + popFinishedBindignsAndUpdateCoverage(K.getRangeEnd()); + insertUpTo(K.getRangeEnd(), stack.empty() ? nullptr : stack.back()); + +#if RSPPDEBUG + llvm::errs() << "Key: " << K << '\n'; + dump(); + + llvm::errs() << "Overlapping bindings: \n"; + llvm::errs() << "{\n"; + for (auto &&v : out) { + llvm::errs() << " " << v.second << '\n'; + } + llvm::errs() << "}\n"; + llvm::errs() + << "---------------------------------------------------------\n"; +#endif + + return out; + }; + +#if RSPPDEBUG + void dump() const { + for (auto &&[Key, Val] : *this) { + llvm::errs() << '{'; + Key.dump(); + llvm::errs() << " value: "; + Val.dump(); + llvm::errs() << '}' << '\n'; + } + } +#endif +}; + typedef llvm::ImmutableMapRef ClusterBindingsRef; typedef std::pair BindingPair; @@ -198,7 +462,7 @@ RegionBindingsRef addBinding(const MemRegion *R, BindingKey::Kind k, SVal V) const; - const SVal *lookup(BindingKey K) const; + const SVal *lookup(BindingKey K, bool partial = false) const; const SVal *lookup(const MemRegion *R, BindingKey::Kind k) const; using llvm::ImmutableMapRef::lookup; @@ -212,12 +476,19 @@ removeBinding(R, BindingKey::Default); } - std::optional getDirectBinding(const MemRegion *R) const; + std::optional getDirectBinding(const MemRegion *R, + SValBuilder &SVB) const; /// getDefaultBinding - Returns an SVal* representing an optional default /// binding associated with a region and its subregions. std::optional getDefaultBinding(const MemRegion *R) const; + std::optional getPartialBinding(const MemRegion *R, + BindingKey::Kind Kind) const; + + std::vector> + getBindingsCoveringRegion(const MemRegion *R, BindingKey::Kind Kind) const; + /// Return the internal tree as a Store. Store asStore() const { llvm::PointerIntPair Ptr = { @@ -263,9 +534,44 @@ typedef const RegionBindingsRef& RegionBindingsConstRef; std::optional -RegionBindingsRef::getDirectBinding(const MemRegion *R) const { - const SVal *V = lookup(R, BindingKey::Direct); - return V ? std::optional(*V) : std::nullopt; +RegionBindingsRef::getDirectBinding(const MemRegion *R, + SValBuilder &SVB) const { + auto K = BindingKey::Make(R, BindingKey::Direct); + + if (K.hasSymbolicOffset()) { + const SVal *V = lookup(K); + return V ? std::optional(*V) : std::nullopt; + } + + auto Bindings = getBindingsCoveringRegion(R, BindingKey::Direct); + if (Bindings.empty()) + return std::nullopt; + + if (Bindings.size() > 1) { + // FIXME: Find a more efficient way to handle this. + for (auto &&[Key, Val] : Bindings) + if (Val.isUndef()) + return std::nullopt; + + return UnknownVal{}; + } + + const auto &[Key, Val] = Bindings[0]; + + if (!Key.isDirect()) + return std::nullopt; + + if (!(Key == K)) { + assert(!Val.isUndef() && "Undefined binding returned when not expected!"); + + if (SymbolRef parentSym = Val.getAsSymbol()) + return SVB.getDerivedRegionValueSymbolVal(parentSym, + cast(R)); + + return UnknownVal{}; + } + + return Val; } std::optional @@ -274,29 +580,76 @@ return V ? std::optional(*V) : std::nullopt; } +std::optional +RegionBindingsRef::getPartialBinding(const MemRegion *R, + BindingKey::Kind Kind) const { + const SVal *V = lookup(BindingKey::Make(R, Kind), true); + return V ? std::optional(*V) : std::nullopt; +} + +std::vector> +RegionBindingsRef::getBindingsCoveringRegion(const MemRegion *R, + BindingKey::Kind Kind) const { + auto K = BindingKey::Make(R, Kind); + const ClusterBindings *Cluster = lookup(K.getBaseRegion()); + if (!Cluster) + return {}; + return Cluster->getBindingsCoveringBinding(K); +} + RegionBindingsRef RegionBindingsRef::addBinding(BindingKey K, SVal V) const { const MemRegion *Base = K.getBaseRegion(); const ClusterBindings *ExistingCluster = lookup(Base); ClusterBindings Cluster = - (ExistingCluster ? *ExistingCluster : CBFactory->getEmptyMap()); + (ExistingCluster + ? *ExistingCluster + : static_cast(CBFactory->getEmptyMap())); + + auto tmpCluster = Cluster; + if (!K.hasSymbolicOffset() && K.isDirect()) { + auto begin = K.getRangeBegin(); + auto end = K.getRangeEnd(); + for (auto &&B : Cluster) { + if (!K.overlapsWith(B.first) || B.first.hasSymbolicOffset()) + continue; + + if (K.contains(B.first)) + tmpCluster = CBFactory->remove(tmpCluster, B.first); + if (B.first.contains(K)) + continue; + + begin = std::min(begin, B.first.getRangeBegin()); + end = std::max(end, B.first.getRangeEnd()); + tmpCluster = CBFactory->remove(tmpCluster, B.first); + } + + auto key = BindingKey::Make(K.getRegion(), begin, end - begin, + BindingKey::Default); + if (!K.contains(key)) { + tmpCluster = + CBFactory->add(tmpCluster, + BindingKey::Make(K.getRegion(), begin, end - begin, + BindingKey::Default), + UnknownVal{}); + } + } - ClusterBindings NewCluster = CBFactory->add(Cluster, K, V); + ClusterBindings NewCluster = CBFactory->add(tmpCluster, K, V); return add(Base, NewCluster); } - RegionBindingsRef RegionBindingsRef::addBinding(const MemRegion *R, BindingKey::Kind k, SVal V) const { return addBinding(BindingKey::Make(R, k), V); } -const SVal *RegionBindingsRef::lookup(BindingKey K) const { +const SVal *RegionBindingsRef::lookup(BindingKey K, bool partial) const { const ClusterBindings *Cluster = lookup(K.getBaseRegion()); if (!Cluster) return nullptr; - return Cluster->lookup(K); + return partial ? Cluster->lookupPartial(K) : Cluster->lookup(K); } const SVal *RegionBindingsRef::lookup(const MemRegion *R, @@ -450,7 +803,7 @@ RegionBindingsRef B = getRegionBindings(store); // Use other APIs when you have to wipe the region that was initialized // earlier. - assert(!(B.getDefaultBinding(R) || B.getDirectBinding(R)) && + assert(!(B.getDefaultBinding(R) || B.getDirectBinding(R, svalBuilder)) && "Double initialization!"); B = B.addBinding(BindingKey::Make(R, BindingKey::Default), V); return StoreRef(B.asImmutableMap().getRootWithoutRetain(), *this); @@ -860,62 +1213,29 @@ TopKey = BindingKey::Make(Top, BindingKey::Default); } - // Find the length (in bits) of the region being invalidated. - uint64_t Length = UINT64_MAX; - SVal Extent = Top->getMemRegionManager().getStaticSize(Top, SVB); - if (std::optional ExtentCI = - Extent.getAs()) { - const llvm::APSInt &ExtentInt = ExtentCI->getValue(); - assert(ExtentInt.isNonNegative() || ExtentInt.isUnsigned()); - // Extents are in bytes but region offsets are in bits. Be careful! - Length = ExtentInt.getLimitedValue() * SVB.getContext().getCharWidth(); - } else if (const FieldRegion *FR = dyn_cast(Top)) { - if (FR->getDecl()->isBitField()) - Length = FR->getDecl()->getBitWidthValue(SVB.getContext()); - } - for (ClusterBindings::iterator I = Cluster.begin(), E = Cluster.end(); I != E; ++I) { BindingKey NextKey = I.getKey(); - if (NextKey.getRegion() == TopKey.getRegion()) { - // FIXME: This doesn't catch the case where we're really invalidating a - // region with a symbolic offset. Example: - // R: points[i].y - // Next: points[0].x - - if (NextKey.getOffset() > TopKey.getOffset() && - NextKey.getOffset() - TopKey.getOffset() < Length) { - // Case 1: The next binding is inside the region we're invalidating. - // Include it. - Bindings.push_back(*I); - - } else if (NextKey.getOffset() == TopKey.getOffset()) { - // Case 2: The next binding is at the same offset as the region we're - // invalidating. In this case, we need to leave default bindings alone, - // since they may be providing a default value for a regions beyond what - // we're invalidating. - // FIXME: This is probably incorrect; consider invalidating an outer - // struct whose first field is bound to a LazyCompoundVal. - if (IncludeAllDefaultBindings || NextKey.isDirect()) - Bindings.push_back(*I); - } - + if (TopKey.hasSymbolicOffset()) { + Bindings.push_back(*I); } else if (NextKey.hasSymbolicOffset()) { const MemRegion *Base = NextKey.getConcreteOffsetRegion(); if (Top->isSubRegionOf(Base) && Top != Base) { - // Case 3: The next key is symbolic and we just changed something within + // The next key is symbolic and we just changed something within // its concrete region. We don't know if the binding is still valid, so // we'll be conservative and include it. if (IncludeAllDefaultBindings || NextKey.isDirect()) if (isCompatibleWithFields(NextKey, FieldsInSymbolicSubregions)) Bindings.push_back(*I); } else if (const SubRegion *BaseSR = dyn_cast(Base)) { - // Case 4: The next key is symbolic, but we changed a known + // The next key is symbolic, but we changed a known // super-region. In this case the binding is certainly included. - if (BaseSR->isSubRegionOf(Top)) - if (isCompatibleWithFields(NextKey, FieldsInSymbolicSubregions)) - Bindings.push_back(*I); + if (BaseSR->isSubRegionOf(Top) && + isCompatibleWithFields(NextKey, FieldsInSymbolicSubregions)) + Bindings.push_back(*I); } + } else if (TopKey.contains(NextKey)) { + Bindings.push_back(*I); } } } @@ -1902,7 +2222,7 @@ if (BaseTy->isScalarType() && Ty->isScalarType()) { if (Ctx.getTypeSizeInChars(BaseTy) >= Ctx.getTypeSizeInChars(Ty)) { if (const std::optional &ParentValue = - B.getDirectBinding(BaseRegion)) { + B.getDirectBinding(BaseRegion, SVB)) { if (SymbolRef ParentValueAsSym = ParentValue->getAsSymbol()) return SVB.getDerivedRegionValueSymbolVal(ParentValueAsSym, SubReg); @@ -1921,7 +2241,7 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, const ElementRegion* R) { // Check if the region has a binding. - if (const std::optional &V = B.getDirectBinding(R)) + if (const std::optional &V = B.getDirectBinding(R, svalBuilder)) return *V; const MemRegion* superR = R->getSuperRegion(); @@ -1973,7 +2293,7 @@ const FieldRegion* R) { // Check if the region has a binding. - if (const std::optional &V = B.getDirectBinding(R)) + if (const std::optional &V = B.getDirectBinding(R, svalBuilder)) return *V; // If the containing record was initialized, try to get its constant value. @@ -2028,7 +2348,11 @@ RegionBindingsConstRef B, const MemRegion *superR, const TypedValueRegion *R, QualType Ty) { - if (const std::optional &D = B.getDefaultBinding(superR)) { + std::optional D = B.getDefaultBinding(superR); + if (!D) + D = B.getPartialBinding(superR, BindingKey::Default); + + if (D) { const SVal &val = *D; if (SymbolRef parentSym = val.getAsSymbol()) return svalBuilder.getDerivedRegionValueSymbolVal(parentSym, R); @@ -2056,26 +2380,8 @@ if (const ElementRegion *ER = dyn_cast(LazyBindingRegion)) Result = getBindingForElement(LazyBinding, ER); else - Result = getBindingForField(LazyBinding, - cast(LazyBindingRegion)); - - // FIXME: This is a hack to deal with RegionStore's inability to distinguish a - // default value for /part/ of an aggregate from a default value for the - // /entire/ aggregate. The most common case of this is when struct Outer - // has as its first member a struct Inner, which is copied in from a stack - // variable. In this case, even if the Outer's default value is symbolic, 0, - // or unknown, it gets overridden by the Inner's default value of undefined. - // - // This is a general problem -- if the Inner is zero-initialized, the Outer - // will now look zero-initialized. The proper way to solve this is with a - // new version of RegionStore that tracks the extent of a binding as well - // as the offset. - // - // This hack only takes care of the undefined case because that can very - // quickly result in a warning. - if (Result.isUndef()) - Result = UnknownVal(); - + Result = + getBindingForField(LazyBinding, cast(LazyBindingRegion)); return Result; } @@ -2163,6 +2469,9 @@ if (!hasPartialLazyBinding && !isa(R->getBaseRegion())) { if (const std::optional &V = B.getDefaultBinding(R)) return *V; + if (const std::optional &V = + B.getPartialBinding(R, BindingKey::Default)) + return *V; return UndefinedVal(); } } @@ -2174,13 +2483,17 @@ SVal RegionStoreManager::getBindingForObjCIvar(RegionBindingsConstRef B, const ObjCIvarRegion* R) { // Check if the region has a binding. - if (const std::optional &V = B.getDirectBinding(R)) + if (const std::optional &V = B.getDirectBinding(R, svalBuilder)) return *V; const MemRegion *superR = R->getSuperRegion(); // Check if the super region has a default binding. - if (const std::optional &V = B.getDefaultBinding(superR)) { + std::optional V = B.getDefaultBinding(superR); + if (!V) + V = B.getPartialBinding(superR, BindingKey::Default); + + if (V) { if (SymbolRef parentSym = V->getAsSymbol()) return svalBuilder.getDerivedRegionValueSymbolVal(parentSym, R); @@ -2195,12 +2508,15 @@ const VarRegion *R) { // Check if the region has a binding. - if (std::optional V = B.getDirectBinding(R)) + if (std::optional V = B.getDirectBinding(R, svalBuilder)) return *V; if (std::optional V = B.getDefaultBinding(R)) return *V; + if (std::optional V = B.getPartialBinding(R, BindingKey::Default)) + return *V; + // Lazily derive a value for the VarRegion. const VarDecl *VD = R->getDecl(); const MemSpaceRegion *MS = R->getMemorySpace(); Index: clang/test/Analysis/array-init-loop.cpp =================================================================== --- clang/test/Analysis/array-init-loop.cpp +++ clang/test/Analysis/array-init-loop.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection -std=c++17 -verify %s void clang_analyzer_eval(bool); @@ -19,7 +19,11 @@ auto [a, b, c, d, e] = arr; - int x = e; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(b); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(c); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(d); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(e); // expected-warning{{UNDEFINED}} } void lambda_init() { @@ -44,21 +48,20 @@ void lambda_uninit() { int arr[5]; - // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal - int l = [arr] { return arr[0]; }(); - clang_analyzer_eval(l); // expected-warning{{UNKNOWN}} + int l = [arr] { return arr[0]; }(); + clang_analyzer_eval(l); // expected-warning{{UNDEFINED}} l = [arr] { return arr[1]; }(); - clang_analyzer_eval(l); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(l); // expected-warning{{UNDEFINED}} l = [arr] { return arr[2]; }(); - clang_analyzer_eval(l); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(l); // expected-warning{{UNDEFINED}} l = [arr] { return arr[3]; }(); - clang_analyzer_eval(l); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(l); // expected-warning{{UNDEFINED}} l = [arr] { return arr[4]; }(); - clang_analyzer_eval(l); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(l); // expected-warning{{UNDEFINED}} } struct S { @@ -86,14 +89,11 @@ S copy = orig; - // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal. - // If the struct is not considered a small struct, instead of a copy, we store a lazy compound value. - // As the struct has an array data member, it is not considered small. - clang_analyzer_eval(copy.arr[0]); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(copy.arr[1]); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(copy.arr[2]); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(copy.arr[3]); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(copy.arr[4]); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(copy.arr[0]); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(copy.arr[1]); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(copy.arr[2]); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(copy.arr[3]); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(copy.arr[4]); // expected-warning{{UNDEFINED}} } void move_ctor_init() { @@ -118,12 +118,11 @@ S moved = (S &&) orig; - // FIXME: These should be Undefined, but we fail to read Undefined from a lazyCompoundVal. - clang_analyzer_eval(moved.arr[0]); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(moved.arr[1]); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(moved.arr[2]); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(moved.arr[3]); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(moved.arr[4]); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(moved.arr[0]); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(moved.arr[1]); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(moved.arr[2]); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(moved.arr[3]); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(moved.arr[4]); // expected-warning{{UNDEFINED}} } // The struct has a user defined copy and move ctor, which allow us to @@ -168,7 +167,8 @@ void array_uninit_non_pod() { S3 arr[1]; - auto [a] = arr; // expected-warning@159{{ in implicit constructor is garbage or undefined }} + auto [a] = arr; + clang_analyzer_eval(a.i); // expected-warning{{UNDEFINED}} } void lambda_init_non_pod() { @@ -191,7 +191,8 @@ void lambda_uninit_non_pod() { S3_duplicate arr[4]; - int l = [arr] { return arr[3].i; }(); // expected-warning@164{{ in implicit constructor is garbage or undefined }} + int l = [arr] { return arr[3].i; }(); + clang_analyzer_eval(l); // expected-warning{{UNDEFINED}} } // If this struct is being copy/move constructed by the implicit ctors, ArrayInitLoopExpr Index: clang/test/Analysis/array-punned-region.c =================================================================== --- clang/test/Analysis/array-punned-region.c +++ clang/test/Analysis/array-punned-region.c @@ -20,7 +20,8 @@ int array_struct_bitfield_2() { BITFIELD_CAST ff = {0}; BITFIELD_CAST *pff = &ff; - int a = *((int *)pff + 2); // expected-warning{{Assigned value is garbage or undefined [core.uninitialized.Assign]}} + int a = *((int *)pff + 2); + clang_analyzer_eval(a == 0); // expected-warning{{TRUE}} return a; } Index: clang/test/Analysis/bstring_UninitRead.c =================================================================== --- clang/test/Analysis/bstring_UninitRead.c +++ clang/test/Analysis/bstring_UninitRead.c @@ -1,5 +1,5 @@ // RUN: %clang_analyze_cc1 -verify %s \ -// RUN: -analyzer-checker=core,alpha.unix.cstring +// RUN: -analyzer-checker=core,alpha.unix.cstring,debug.ExprInspection // This file is generally for the alpha.unix.cstring.UninitializedRead Checker, the reason for putting it into @@ -31,12 +31,9 @@ int dst[5] = {0}; int *p; - p = mempcpy(dst, src, 4 * sizeof(int)); // expected-warning{{Bytes string function accesses uninitialized/garbage values}} - // FIXME: This behaviour is actually surprising and needs to be fixed, - // mempcpy seems to consider the very last byte of the src buffer uninitialized - // and returning undef unfortunately. It should have returned unknown or a conjured value instead. + p = mempcpy(dst, src, 4 * sizeof(int)); - clang_analyzer_eval(p == &dst[4]); // no-warning (above is fatal) + clang_analyzer_eval(p == &dst[4]); // expected-warning{{TRUE}} } struct st { @@ -52,8 +49,7 @@ struct st *p2; p1 = (&s2) + 1; - p2 = mempcpy(&s2, &s1, sizeof(struct st)); // expected-warning{{Bytes string function accesses uninitialized/garbage values}} - // FIXME: It seems same as mempcpy14() case. + p2 = mempcpy(&s2, &s1, sizeof(struct st)); - clang_analyzer_eval(p1 == p2); // no-warning (above is fatal) + clang_analyzer_eval(p1 == p2); // expected-warning{{TRUE}} } Index: clang/test/Analysis/casts.c =================================================================== --- clang/test/Analysis/casts.c +++ clang/test/Analysis/casts.c @@ -1,5 +1,5 @@ // RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin9 -fenable-matrix -analyzer-checker=core,alpha.core,debug.ExprInspection -Wno-pointer-to-int-cast -Wno-strict-prototypes -verify -analyzer-config eagerly-assume=false %s -// RUN: %clang_analyze_cc1 -triple i386-apple-darwin9 -fenable-matrix -analyzer-checker=core,alpha.core,debug.ExprInspection -Wno-pointer-to-int-cast -Wno-strict-prototypes -verify -analyzer-config eagerly-assume=false %s +// RUN: %clang_analyze_cc1 -triple i386-apple-darwin9 -fenable-matrix -analyzer-checker=core,alpha.core,debug.ExprInspection -Wno-pointer-to-int-cast -Wno-strict-prototypes -verify -analyzer-config eagerly-assume=false -DBIT32TEST=1 %s // RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin9 -fenable-matrix -analyzer-checker=core,alpha.core,debug.ExprInspection -Wno-pointer-to-int-cast -Wno-strict-prototypes -verify -DEAGERLY_ASSUME=1 -w %s // RUN: %clang_analyze_cc1 -triple i386-apple-darwin9 -fenable-matrix -analyzer-checker=core,alpha.core,debug.ExprInspection -Wno-pointer-to-int-cast -Wno-strict-prototypes -verify -DEAGERLY_ASSUME=1 -DBIT32=1 -w %s @@ -159,21 +159,33 @@ void testCastVoidPtrToIntPtrThroughIntTypedAssignment(void) { int *x; (*((int *)(&x))) = (int)getVoidPtr(); + #if defined I386 || defined BIT32TEST *x = 1; // no-crash + #else + *x = 1; // expected-warning{{undefined}} + #endif } void testCastUIntPtrToIntPtrThroughIntTypedAssignment(void) { unsigned u; int *x; (*((int *)(&x))) = (int)&u; + #if defined I386 || defined BIT32TEST *x = 1; clang_analyzer_eval(u == 1); // expected-warning{{TRUE}} + #else + *x = 1; // expected-warning{{undefined}} + #endif } void testCastVoidPtrToIntPtrThroughUIntTypedAssignment(void) { int *x; (*((int *)(&x))) = (int)(unsigned *)getVoidPtr(); + #if defined I386 || defined BIT32TEST *x = 1; // no-crash + #else + *x = 1; // expected-warning{{undefined}} + #endif } void testLocNonLocSymbolAssume(int a, int *b) { Index: clang/test/Analysis/copy-elision.cpp =================================================================== --- clang/test/Analysis/copy-elision.cpp +++ clang/test/Analysis/copy-elision.cpp @@ -58,13 +58,8 @@ C() : t(T(4)) { S s = {1, 2, 3}; t.s = s; - // FIXME: Should be TRUE regardless of copy elision. clang_analyzer_eval(t.w == 4); -#ifdef ELIDE - // expected-warning@-2{{TRUE}} -#else - // expected-warning@-4{{UNKNOWN}} -#endif + // expected-warning@-1{{TRUE}} } }; Index: clang/test/Analysis/ctor.mm =================================================================== --- clang/test/Analysis/ctor.mm +++ clang/test/Analysis/ctor.mm @@ -831,8 +831,7 @@ #endif clang_analyzer_eval(w == 4); // expected-warning{{TRUE}} - // FIXME: Should be UNKNOWN. Changed in B() since glob_y was assigned. - clang_analyzer_eval(y == glob_y); // expected-warning{{TRUE}} + clang_analyzer_eval(y == glob_y); // expected-warning{{UNKNOWN}} #ifdef I386 clang_analyzer_eval(z == glob_z); // expected-warning{{UNKNOWN}} Index: clang/test/Analysis/cxx-uninitialized-object-ptr-ref.cpp =================================================================== --- clang/test/Analysis/cxx-uninitialized-object-ptr-ref.cpp +++ clang/test/Analysis/cxx-uninitialized-object-ptr-ref.cpp @@ -289,10 +289,12 @@ } struct CyclicPointerTest1 { - int *ptr; // expected-note{{object references itself 'this->ptr'}} + // FIXME: Should be a note for self reference. + int *ptr; int dontGetFilteredByNonPedanticMode = 0; - CyclicPointerTest1() : ptr(reinterpret_cast(&ptr)) {} // expected-warning{{1 uninitialized field}} + // FIXME: Should be uninitialized field. + CyclicPointerTest1() : ptr(reinterpret_cast(&ptr)) {} }; void fCyclicPointerTest1() { Index: clang/test/Analysis/cxx-uninitialized-object.cpp =================================================================== --- clang/test/Analysis/cxx-uninitialized-object.cpp +++ clang/test/Analysis/cxx-uninitialized-object.cpp @@ -871,12 +871,13 @@ Callable functor; int dontGetFilteredByNonPedanticMode = 0; - MultipleLambdaCapturesTest2(const Callable &functor, int) : functor(functor) {} // expected-warning{{1 uninitialized field}} + MultipleLambdaCapturesTest2(const Callable &functor, int) : functor(functor) {} // expected-warning{{2 uninitialized field}} }; void fMultipleLambdaCapturesTest2() { int b1, b2 = 3, b3; auto equals = [b1, &b2, &b3](int a) { return a == b1 == b2 == b3; }; // expected-note{{uninitialized pointee 'this->functor./*captured variable*/b3'}} + // expected-note@-1{{uninitialized field 'this->functor./*captured variable*/b1'}} MultipleLambdaCapturesTest2(equals, int()); } Index: clang/test/Analysis/dump_egraph.cpp =================================================================== --- clang/test/Analysis/dump_egraph.cpp +++ clang/test/Analysis/dump_egraph.cpp @@ -21,7 +21,7 @@ // CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"location\": \{ \"line\": 15, \"column\": 5, \"file\": \"{{.*}}dump_egraph.cpp\" \}, \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t.s\" -// CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" +// CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"extent\": {{[0-9]+}}, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" // CHECK: \"dynamic_types\": [\l      \{ \"region\": \"HeapSymRegion\{conj_$1\{S *, LC1, S{{[0-9]+}}, #1\}\}\", \"dyn_type\": \"S\", \"sub_classable\": false \}\l Index: clang/test/Analysis/expr-inspection.c =================================================================== --- clang/test/Analysis/expr-inspection.c +++ clang/test/Analysis/expr-inspection.c @@ -31,7 +31,7 @@ // CHECK: "program_state": { // CHECK-NEXT: "store": { "pointer": "{{0x[0-9a-f]+}}", "items": [ // CHECK-NEXT: { "cluster": "y", "pointer": "{{0x[0-9a-f]+}}", "items": [ -// CHECK-NEXT: { "kind": "Direct", "offset": {{[0-9]+}}, "value": "2 S32b" } +// CHECK-NEXT: { "kind": "Direct", "offset": {{[0-9]+}}, "extent": {{[0-9]+}}, "value": "2 S32b" } // CHECK-NEXT: ]} // CHECK-NEXT: ]}, // CHECK-NEXT: "environment": { "pointer": "{{0x[0-9a-f]+}}", "items": [ Index: clang/test/Analysis/pr22954.c =================================================================== --- clang/test/Analysis/pr22954.c +++ clang/test/Analysis/pr22954.c @@ -827,7 +827,7 @@ z37 = (struct zz *)((char*)(z37) + 4); // Increment back. - clang_analyzer_eval(z37->s1[0] == 11); // expected-warning{{TRUE}} + clang_analyzer_eval(z37->s1[0] == 11); // expected-warning{{UNKNOWN}} clang_analyzer_eval(z37->s1[1] == 1); // expected-warning{{UNKNOWN}} clang_analyzer_eval(z37->s1[2] == 1); // expected-warning{{UNKNOWN}} clang_analyzer_eval(z37->s1[3] == 1); // expected-warning{{UNKNOWN}} @@ -865,7 +865,7 @@ clang_analyzer_eval(z38->s1[0] == 1); // expected-warning{{UNKNOWN}} clang_analyzer_eval(z38->s1[1] == 1); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(z38->s1[2] == 11); // expected-warning{{TRUE}} + clang_analyzer_eval(z38->s1[2] == 11); // expected-warning{{UNKNOWN}} clang_analyzer_eval(z38->s1[3] == 1); // expected-warning{{UNKNOWN}} clang_analyzer_eval(z38->s2 == 10); // expected-warning{{UNKNOWN}} @@ -912,7 +912,7 @@ // FIXME: d39->s2 should evaluate to at least UNKNOWN or FALSE, // 'collectSubRegionBindings(...)' in RegionStore.cpp will need to // handle a regions' upper boundary overflowing. - clang_analyzer_eval(d39->s2 == 10); // expected-warning{{TRUE}} + clang_analyzer_eval(d39->s2 == 10); // expected-warning{{UNKNOWN}} return 0; } Index: clang/test/Analysis/pr37802.cpp =================================================================== --- clang/test/Analysis/pr37802.cpp +++ clang/test/Analysis/pr37802.cpp @@ -1,7 +1,5 @@ // RUN: %clang_analyze_cc1 -w -analyzer-checker=core -verify %s -// expected-no-diagnostics - typedef __typeof(sizeof(int)) size_t; void *operator new(size_t, void *h) { return h; } @@ -25,7 +23,7 @@ template void f(T &&); void f(J t) { - f(*t.p); + f(*t.p); // expected-warning{{function call argument is an uninitialized value}} } }; class Z { @@ -73,7 +71,7 @@ public: void boolf(bool &&); void f(J &&); - void f(J t) { boolf(*t.p); } + void f(J t) { boolf(*t.p); } // expected-warning{{Dereference of undefined pointer}} }; class Z { Index: clang/test/Analysis/store-extent-rework.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/store-extent-rework.cpp @@ -0,0 +1,166 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection,alpha.unix.cstring -verify %s + +template +void clang_analyzer_eval(T); + +template +void clang_analyzer_explain(T); + +template +void clang_analyzer_dump(T); + +typedef __typeof__(sizeof(int)) size_t; +void *memset(void *dest, int ch, size_t count); +void *calloc(size_t count, size_t size); + +namespace multipleDefaults { +struct Point +{ + int x; + int y; + int z; +}; + +struct Circle +{ + Point origin; + int size; +}; + +Point makePoint(int x, int y) +{ + Point result; + result.x = x; + result.y = y; + result.z = 3; + return result; +} + +void multipleDefaultOnStack() { + Circle testObj; + memset(&testObj, 0, sizeof(testObj)); + + clang_analyzer_eval(testObj.size == 0); //expected-warning{{TRUE}} + testObj.origin = makePoint(1, 2); + clang_analyzer_eval(testObj.size == 0); //expected-warning{{TRUE}} + + clang_analyzer_eval(testObj.origin.x == 1); //expected-warning{{TRUE}} + clang_analyzer_eval(testObj.origin.y == 2); //expected-warning{{TRUE}} + clang_analyzer_eval(testObj.origin.z == 3); //expected-warning{{TRUE}} +} +} // namespace multipleDefaults + +namespace multipleCovered { +struct S { + char a; + char b; + char c; + char d; +}; + +void everyByteInitialized() { + S s = {1, 2, 3, 4}; + int x = *((int *)&s); + + // FIXME: Every byte inside 'x' is initialized, we just can't concatenate them. + clang_analyzer_eval(x); //expected-warning{{UNKNOWN}} +} + +void garbageByte1() { + S s; + s.b = 2; + s.c = 3; + s.d = 4; + + // Here one byte of 'x' is a garbage value. + int x = *((int *)&s); //expected-warning{{Assigned value is garbage or undefined}} + (void) x; +} + +void garbageByte2() { + S s; + s.a = 1; + s.c = 3; + s.d = 4; + + int x = *((int *)&s); //expected-warning{{Assigned value is garbage or undefined}} + (void) x; +} + +void garbageByte3() { + S s; + s.a = 1; + s.b = 2; + s.d = 4; + + int x = *((int *)&s); //expected-warning{{Assigned value is garbage or undefined}} + (void) x; +} + +void garbageByte4() { + S s; + s.a = 1; + s.b = 2; + s.c = 3; + + int x = *((int *)&s); //expected-warning{{Assigned value is garbage or undefined}} + (void) x; +} +} // namespace multipleCovered + +namespace Overlapping { +void overlapping1() { + int x = 0x01020304; + *((char *)&x + 1) = 1; + + // FIXME: Should be 0x01020301 + clang_analyzer_eval(x); //expected-warning{{UNKNOWN}} + + clang_analyzer_dump(*((char *)&x + 1)); //expected-warning{{1}} +} + +void overlapping2() { + int x = 0x01020304; + *(char *)&x = 0; + + clang_analyzer_dump(*(char *)&x); //expected-warning{{0}} + + // FIXME: Should be 0x01020300. + clang_analyzer_eval(x); //expected-warning{{UNKNOWN}} +} +} // namespace Overlapping + +namespace PartialLCV { +struct A { + int x; + int y; +}; + +struct B { + int z; + A a; +}; + +struct C { + B b; + int x; +}; + +void top() { + C c{}; // bind zero default + B b{1, {2, 3}}; + c.b = b; + A a = c.b.a; + clang_analyzer_dump(a.x); //expected-warning{{2}} +} +} // namespace PartialLCV + +namespace MixingDirectAndDefault { +void mix() { + int *arr[4] = {0}; + arr[3] = (int*)calloc(1, sizeof(char)); + int x = **(int **)((int *)&arr[2] + 1); + + clang_analyzer_eval(x); // expected-warning{{UNKNOWN}} +} +} // namespace MixingDirectAndDefault Index: clang/test/Analysis/uninit-structured-binding-array.cpp =================================================================== --- clang/test/Analysis/uninit-structured-binding-array.cpp +++ clang/test/Analysis/uninit-structured-binding-array.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection -std=c++17 -verify %s void clang_analyzer_eval(bool); @@ -7,7 +7,7 @@ auto [a, b] = arr; arr[0] = 0; - int x = a; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} } void array_value_b(void) { @@ -16,8 +16,6 @@ clang_analyzer_eval(a == 1); // expected-warning{{TRUE}} clang_analyzer_eval(b == 2); // expected-warning{{TRUE}} - - int x = a; // no-warning } void array_value_c(void) { @@ -28,9 +26,7 @@ auto [a, b, c] = arr; clang_analyzer_eval(b == arr[1]); // expected-warning{{TRUE}} - - int y = b; // no-warning - int x = a; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} } void array_value_d(void) { @@ -41,9 +37,7 @@ auto [a, b, c] = arr; clang_analyzer_eval(b == arr[1]); // expected-warning{{TRUE}} - - int y = b; // no-warning - int x = c; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(c); // expected-warning{{UNDEFINED}} } void array_value_e(void) { @@ -56,9 +50,6 @@ clang_analyzer_eval(i == 0); // expected-warning{{TRUE}} clang_analyzer_eval(j == 0); // expected-warning{{TRUE}} - - int a = i; // no-warning - int b = j; // no-warning } void array_value_f(void) { @@ -70,15 +61,15 @@ auto [i, j] = uninit; clang_analyzer_eval(i == 0); // expected-warning{{TRUE}} - - int a = i; // no-warning - int b = j; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } void array_lref_a(void) { int arr[2]; auto &[a, b] = arr; - int x = a; // expected-warning{{Assigned value is garbage or undefined}} + + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(b); // expected-warning{{UNDEFINED}} } void array_lref_b(void) { @@ -87,8 +78,6 @@ clang_analyzer_eval(a == 1); // expected-warning{{TRUE}} clang_analyzer_eval(b == 2); // expected-warning{{TRUE}} - - int x = a; // no-warning } void array_lref_c(void) { @@ -98,9 +87,7 @@ arr[0] = 1; clang_analyzer_eval(a == 1); // expected-warning{{TRUE}} - - int x = a; // no-warning - int y = b; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(b); // expected-warning{{UNDEFINED}} } void array_lref_d(void) { @@ -111,9 +98,7 @@ auto &[a, b, c] = arr; clang_analyzer_eval(b == 1); // expected-warning{{TRUE}} - - int y = b; // no-warning - int x = a; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} } void array_lref_e(void) { @@ -124,9 +109,7 @@ auto &[a, b, c] = arr; clang_analyzer_eval(b == 1); // expected-warning{{TRUE}} - - int y = b; // no-warning - int x = c; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(c); // expected-warning{{UNDEFINED}} } void array_lref_f(void) { @@ -139,9 +122,6 @@ clang_analyzer_eval(i == 0); // expected-warning{{TRUE}} clang_analyzer_eval(j == 0); // expected-warning{{TRUE}} - - int a = i; // no-warning - int b = j; // no-warning } void array_lref_g(void) { @@ -153,15 +133,14 @@ auto &[i, j] = uninit; clang_analyzer_eval(i == 0); // expected-warning{{TRUE}} - - int a = i; // no-warning - int b = j; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } void array_rref_a(void) { int arr[2]; auto &&[a, b] = arr; - int x = a; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(b); // expected-warning{{UNDEFINED}} } void array_rref_b(void) { @@ -181,9 +160,7 @@ arr[0] = 1; clang_analyzer_eval(a == 1); // expected-warning{{TRUE}} - - int x = a; // no-warning - int y = b; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(b); // expected-warning{{UNDEFINED}} } void array_rref_d(void) { @@ -194,22 +171,8 @@ auto &&[a, b, c] = arr; clang_analyzer_eval(b == 1); // expected-warning{{TRUE}} - - int y = b; // no-warning - int x = a; // expected-warning{{Assigned value is garbage or undefined}} -} - -void array_rref_e(void) { - int arr[3]; - - arr[1] = 1; - - auto &&[a, b, c] = arr; - - clang_analyzer_eval(b == 1); // expected-warning{{TRUE}} - - int y = b; // no-warning - int x = c; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(c); // expected-warning{{UNDEFINED}} } void array_rref_f(void) { @@ -222,9 +185,6 @@ clang_analyzer_eval(i == 0); // expected-warning{{TRUE}} clang_analyzer_eval(j == 0); // expected-warning{{TRUE}} - - int a = i; // no-warning - int b = j; // no-warning } void array_rref_g(void) { @@ -236,9 +196,7 @@ auto &&[i, j] = uninit; clang_analyzer_eval(i == 0); // expected-warning{{TRUE}} - - int a = i; // no-warning - int b = j; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } void array_change_a(void) { @@ -276,7 +234,11 @@ auto [a, b, c, d, e] = arr; - int x = e; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(b); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(c); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(d); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(e); // expected-warning{{UNDEFINED}} } void array_big_a(void) { @@ -284,13 +246,12 @@ auto [a, b, c, d, e, f] = arr; - // FIXME: These will be Undefined when we handle reading Undefined values from lazyCompoundVal. - clang_analyzer_eval(a == 1); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(b == 2); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(c == 3); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(d == 4); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(e == 5); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(f == 6); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(a); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(b); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(c); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(d); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(e); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(f); // expected-warning{{UNDEFINED}} } struct S { Index: clang/test/Analysis/uninit-structured-binding-struct.cpp =================================================================== --- clang/test/Analysis/uninit-structured-binding-struct.cpp +++ clang/test/Analysis/uninit-structured-binding-struct.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++17 -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection -std=c++17 -verify %s void clang_analyzer_eval(bool); @@ -12,7 +12,8 @@ auto [i, j] = tst; - int x = i; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(i); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } void b(void) { @@ -22,7 +23,7 @@ auto [i, j] = tst; clang_analyzer_eval(i == 1); // expected-warning{{TRUE}} - int y = j; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } void c(void) { @@ -30,7 +31,8 @@ auto &[i, j] = tst; - int x = i; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(i); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } void d(void) { @@ -43,7 +45,7 @@ i = 2; clang_analyzer_eval(tst.a == 2); // expected-warning{{TRUE}} - int y = j; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } void e(void) { @@ -63,7 +65,8 @@ auto &&[i, j] = tst; - int x = i; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(i); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } void g(void) { @@ -73,7 +76,7 @@ auto &&[i, j] = tst; clang_analyzer_eval(i == 1); // expected-warning{{TRUE}} - int y = j; // expected-warning{{Assigned value is garbage or undefined}} + clang_analyzer_eval(j); // expected-warning{{UNDEFINED}} } struct s2 { @@ -93,10 +96,8 @@ auto [i, j] = tst; - // FIXME: These should be undefined, but we have to fix - // reading undefined from lazy compound values first. - clang_analyzer_eval(i.a); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(i.b); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(i.a); // expected-warning{{UNDEFINED}} + clang_analyzer_eval(i.b); // expected-warning{{UNDEFINED}} clang_analyzer_eval(j.a == 1); // expected-warning{{TRUE}} clang_analyzer_eval(j.b == 2); // expected-warning{{TRUE}} Index: clang/test/Analysis/uninit-vals.m =================================================================== --- clang/test/Analysis/uninit-vals.m +++ clang/test/Analysis/uninit-vals.m @@ -164,15 +164,11 @@ // expected-note@-1{{TRUE}} testObj->origin = makePoint(0.0, 0.0); - if (testObj->size > 0) { ; } // expected-note{{Assuming field 'size' is <= 0}} + if (testObj->size > 0) { ; } // expected-note{{Field 'size' is <= 0}} // expected-note@-1{{Taking false branch}} - // FIXME: Assigning to 'testObj->origin' kills the default binding for the - // whole region, meaning that we've forgotten that testObj->size should also - // default to 0. Tracked by . - // This should be TRUE. - clang_analyzer_eval(testObj->size == 0); // expected-warning{{UNKNOWN}} - // expected-note@-1{{UNKNOWN}} + clang_analyzer_eval(testObj->size == 0); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} free(testObj); } @@ -219,21 +215,17 @@ // expected-note@-1{{TRUE}} testObj->origin = makeIntPoint(1, 2); - if (testObj->size > 0) { ; } // expected-note{{Assuming field 'size' is <= 0}} + if (testObj->size > 0) { ; } // expected-note{{Field 'size' is <= 0}} // expected-note@-1{{Taking false branch}} - // expected-note@-2{{Assuming field 'size' is <= 0}} + // expected-note@-2{{Field 'size' is <= 0}} // expected-note@-3{{Taking false branch}} - // expected-note@-4{{Assuming field 'size' is <= 0}} + // expected-note@-4{{Field 'size' is <= 0}} // expected-note@-5{{Taking false branch}} - // expected-note@-6{{Assuming field 'size' is <= 0}} + // expected-note@-6{{Field 'size' is <= 0}} // expected-note@-7{{Taking false branch}} - // FIXME: Assigning to 'testObj->origin' kills the default binding for the - // whole region, meaning that we've forgotten that testObj->size should also - // default to 0. Tracked by . - // This should be TRUE. - clang_analyzer_eval(testObj->size == 0); // expected-warning{{UNKNOWN}} - // expected-note@-1{{UNKNOWN}} + clang_analyzer_eval(testObj->size == 0); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} clang_analyzer_eval(testObj->origin.x == 1); // expected-warning{{TRUE}} // expected-note@-1{{TRUE}} clang_analyzer_eval(testObj->origin.y == 2); // expected-warning{{TRUE}} @@ -307,7 +299,8 @@ IntPoint b = a; extern void useInt(int); useInt(b.x); // no-warning - useInt(b.y); // no-warning + useInt(b.y); // expected-warning{{uninitialized}} + // expected-note@-1{{uninitialized}} } void testSmallStructInLargerStruct(void) { Index: clang/utils/analyzer/exploded-graph-rewriter.py =================================================================== --- clang/utils/analyzer/exploded-graph-rewriter.py +++ clang/utils/analyzer/exploded-graph-rewriter.py @@ -186,11 +186,12 @@ # A single binding key in a deserialized RegionStore cluster. class StoreBindingKey: def __init__(self, json_sk): - self.kind = json_sk["kind"] - self.offset = json_sk["offset"] + self.kind = json_sk['kind'] + self.offset = json_sk['offset'] + self.extent = json_sk['extent'] if 'extent' in json_sk else None def _key(self): - return (self.kind, self.offset) + return (self.kind, self.offset, self.extent) def __eq__(self, other): return self._key() == other._key()