Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

675 lines
22 KiB
C++
Raw Normal View History

2011-08-15 23:05:22 +00:00
//= ProgramState.cpp - Path-Sensitive "State" for tracking values --*- C++ -*--=
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2011-08-15 23:05:22 +00:00
//
//===----------------------------------------------------------------------===//
//
// This file implements ProgramState and ProgramStateManager.
//
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
2011-08-15 23:05:22 +00:00
#include "clang/Analysis/CFG.h"
#include "clang/Basic/JsonSupport.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
2011-08-15 23:05:22 +00:00
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
2011-08-15 23:05:22 +00:00
using namespace clang;
using namespace ento;
namespace clang { namespace ento {
/// Increments the number of times this state is referenced.
void ProgramStateRetain(const ProgramState *state) {
++const_cast<ProgramState*>(state)->refCount;
}
/// Decrement the number of times this state is referenced.
void ProgramStateRelease(const ProgramState *state) {
assert(state->refCount > 0);
ProgramState *s = const_cast<ProgramState*>(state);
if (--s->refCount == 0) {
ProgramStateManager &Mgr = s->getStateManager();
Mgr.StateSet.RemoveNode(s);
s->~ProgramState();
Mgr.freeStates.push_back(s);
}
}
}}
2011-08-15 23:05:22 +00:00
ProgramState::ProgramState(ProgramStateManager *mgr, const Environment& env,
StoreRef st, GenericDataMap gdm)
: stateMgr(mgr),
Env(env),
store(st.getStore()),
GDM(gdm),
refCount(0) {
stateMgr->getStoreManager().incrementReferenceCount(store);
}
ProgramState::ProgramState(const ProgramState &RHS)
: stateMgr(RHS.stateMgr), Env(RHS.Env), store(RHS.store), GDM(RHS.GDM),
PosteriorlyOverconstrained(RHS.PosteriorlyOverconstrained), refCount(0) {
2011-08-15 23:05:22 +00:00
stateMgr->getStoreManager().incrementReferenceCount(store);
}
ProgramState::~ProgramState() {
if (store)
stateMgr->getStoreManager().decrementReferenceCount(store);
}
int64_t ProgramState::getID() const {
return getStateManager().Alloc.identifyKnownAlignedObject<ProgramState>(this);
}
ProgramStateManager::ProgramStateManager(ASTContext &Ctx,
StoreManagerCreator CreateSMgr,
ConstraintManagerCreator CreateCMgr,
llvm::BumpPtrAllocator &alloc,
ExprEngine *ExprEng)
: Eng(ExprEng), EnvMgr(alloc), GDMFactory(alloc),
svalBuilder(createSimpleSValBuilder(alloc, Ctx, *this)),
CallEventMgr(new CallEventManager(alloc)), Alloc(alloc) {
StoreMgr = (*CreateSMgr)(*this);
ConstraintMgr = (*CreateCMgr)(*this, ExprEng);
}
2011-08-15 23:05:22 +00:00
ProgramStateManager::~ProgramStateManager() {
for (GDMContextsTy::iterator I=GDMContexts.begin(), E=GDMContexts.end();
I!=E; ++I)
I->second.second(I->second.first);
}
ProgramStateRef ProgramStateManager::removeDeadBindingsFromEnvironmentAndStore(
ProgramStateRef state, const StackFrameContext *LCtx,
SymbolReaper &SymReaper) {
2011-08-15 23:05:22 +00:00
// This code essentially performs a "mark-and-sweep" of the VariableBindings.
// The roots are any Block-level exprs and Decls that our liveness algorithm
// tells us are live. We then see what Decls they may reference, and keep
// those around. This code more than likely can be made faster, and the
// frequency of which this method is called should be experimented with
// for optimum performance.
ProgramState NewState = *state;
NewState.Env = EnvMgr.removeDeadBindings(NewState.Env, SymReaper, state);
// Clean up the store.
StoreRef newStore = StoreMgr->removeDeadBindings(NewState.getStore(), LCtx,
SymReaper);
NewState.setStore(newStore);
SymReaper.setReapedStore(newStore);
return getPersistentState(NewState);
2011-08-15 23:05:22 +00:00
}
ProgramStateRef ProgramState::bindLoc(Loc LV,
SVal V,
const LocationContext *LCtx,
bool notifyChanges) const {
2011-08-15 23:05:22 +00:00
ProgramStateManager &Mgr = getStateManager();
ProgramStateRef newState = makeWithStore(Mgr.StoreMgr->Bind(getStore(),
2011-08-15 23:05:22 +00:00
LV, V));
const MemRegion *MR = LV.getAsRegion();
if (MR && notifyChanges)
return Mgr.getOwningEngine().processRegionChange(newState, MR, LCtx);
2011-08-15 23:05:22 +00:00
return newState;
}
ProgramStateRef
ProgramState::bindDefaultInitial(SVal loc, SVal V,
const LocationContext *LCtx) const {
ProgramStateManager &Mgr = getStateManager();
const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion();
const StoreRef &newStore = Mgr.StoreMgr->BindDefaultInitial(getStore(), R, V);
ProgramStateRef new_state = makeWithStore(newStore);
return Mgr.getOwningEngine().processRegionChange(new_state, R, LCtx);
}
ProgramStateRef
ProgramState::bindDefaultZero(SVal loc, const LocationContext *LCtx) const {
2011-08-15 23:05:22 +00:00
ProgramStateManager &Mgr = getStateManager();
const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion();
const StoreRef &newStore = Mgr.StoreMgr->BindDefaultZero(getStore(), R);
ProgramStateRef new_state = makeWithStore(newStore);
return Mgr.getOwningEngine().processRegionChange(new_state, R, LCtx);
2011-08-15 23:05:22 +00:00
}
typedef ArrayRef<const MemRegion *> RegionList;
typedef ArrayRef<SVal> ValueList;
ProgramStateRef ProgramState::invalidateRegions(
RegionList Regions, const Stmt *S, unsigned Count,
const LocationContext *LCtx, bool CausedByPointerEscape,
InvalidatedSymbols *IS, const CallEvent *Call,
RegionAndSymbolInvalidationTraits *ITraits) const {
SmallVector<SVal, 8> Values;
for (const MemRegion *Reg : Regions)
Values.push_back(loc::MemRegionVal(Reg));
return invalidateRegions(Values, S, Count, LCtx, CausedByPointerEscape, IS,
Call, ITraits);
2011-08-15 23:05:22 +00:00
}
ProgramStateRef ProgramState::invalidateRegions(
ValueList Values, const Stmt *S, unsigned Count,
const LocationContext *LCtx, bool CausedByPointerEscape,
InvalidatedSymbols *IS, const CallEvent *Call,
RegionAndSymbolInvalidationTraits *ITraits) const {
2011-08-15 23:05:22 +00:00
ProgramStateManager &Mgr = getStateManager();
ExprEngine &Eng = Mgr.getOwningEngine();
InvalidatedSymbols InvalidatedSyms;
if (!IS)
IS = &InvalidatedSyms;
RegionAndSymbolInvalidationTraits ITraitsLocal;
if (!ITraits)
ITraits = &ITraitsLocal;
StoreManager::InvalidatedRegions TopLevelInvalidated;
StoreManager::InvalidatedRegions Invalidated;
const StoreRef &NewStore = Mgr.StoreMgr->invalidateRegions(
getStore(), Values, S, Count, LCtx, Call, *IS, *ITraits,
&TopLevelInvalidated, &Invalidated);
ProgramStateRef NewState = makeWithStore(NewStore);
if (CausedByPointerEscape) {
NewState = Eng.notifyCheckersOfPointerEscape(
NewState, IS, TopLevelInvalidated, Call, *ITraits);
2011-08-15 23:05:22 +00:00
}
return Eng.processRegionChanges(NewState, IS, TopLevelInvalidated,
Invalidated, LCtx, Call);
2011-08-15 23:05:22 +00:00
}
ProgramStateRef ProgramState::killBinding(Loc LV) const {
2011-08-15 23:05:22 +00:00
Store OldStore = getStore();
const StoreRef &newStore =
getStateManager().StoreMgr->killBinding(OldStore, LV);
2011-08-15 23:05:22 +00:00
if (newStore.getStore() == OldStore)
return this;
return makeWithStore(newStore);
}
[analyzer] Never create Regions wrapping reference TypedValueRegions (NFCI) (#118096) Like in the test case: ```c++ struct String { String(const String &) {} }; struct MatchComponent { unsigned numbers[2]; String prerelease; MatchComponent(MatchComponent const &) = default; }; MatchComponent get(); void consume(MatchComponent const &); MatchComponent parseMatchComponent() { MatchComponent component = get(); component.numbers[0] = 10; component.numbers[1] = 20; return component; // We should have no stack addr escape warning here. } void top() { consume(parseMatchComponent()); } ``` When calling `consume(parseMatchComponent())` the `parseMatchComponent()` would return a copy of a temporary of `component`. That copy would invoke the `MatchComponent::MatchComponent(const MatchComponent &)` ctor. That ctor would have a (reference typed) ParamVarRegion, holding the location (lvalue) of the object we are about to copy (&component). So far so good, but just before evaluating the binding operation for initializing the `numbers` field of the temporary, we evaluate the ArrayInitLoopExpr representing the by-value elementwise copy of the array `component.numbers`. This is represented by a LazyCompoundVal, because we (usually) don't just copy large arrays and bind many individual direct bindings. Rather, we take a snapshot by using a LCV. However, notice that the LCV representing this copy would look like this: lazyCompoundVal{ParamVarRegion{"reference param of cctor"}.numbers} Notice that it refers to the numbers field of a reference. It would be much better to desugar the reference to the actual object, thus it should be: `lazyCompoundVal{component.numbers}` Actually, when binding the result of the ArrayInitLoopExpr to the `temp_object.numbers` in the compiler-generated member initializer of the cctor, we should have two individual direct bindings because this is a "small array": ``` binding &Element{temp_object.numbers, 0} <- loadFrom(&Element{component.numbers, 0}) binding &Element{temp_object.numbers, 1} <- loadFrom(&Element{component.numbers, 1}) ``` Where `loadFrom(...)` would be: ``` loadFrom(&Element{component.numbers, 0}): 10 U32b loadFrom(&Element{component.numbers, 1}): 20 U32b ``` So the store should look like this, after PostInitializer of `temp_object.numbers`: ``` temp_object at offset 0: 10 U32b temp_object at offset 32: 20 U32b ``` The lesson is that it's okay to have TypedValueRegions of references as long as we don't form subregions of those. If we ever want to refer to a subregion of a "reference" we actually meant to "desugar" the reference and slice a subregion of the pointee of the reference instead. Once this canonicalization is in place, we can also drop the special handling of references in `ProcessInitializer`, because now reference TypedValueRegions are eagerly desugared into their referee region when forming a subregion of it. There should be no practical differences, but there are of course bugs that this patch may surface.
2024-11-29 20:36:24 +01:00
/// We should never form a MemRegion that would wrap a TypedValueRegion of a
/// reference type. What we actually wanted was to create a MemRegion refering
/// to the pointee of that reference.
SVal ProgramState::desugarReference(SVal Val) const {
const auto *TyReg = dyn_cast_or_null<TypedValueRegion>(Val.getAsRegion());
if (!TyReg || !TyReg->getValueType()->isReferenceType())
return Val;
return getSVal(TyReg);
}
[analyzer] Wrap SymbolicRegions by ElementRegions before getting a FieldRegion (#85211) Inside the ExprEngine when we process the initializers, we create a PostInitializer program-point, which will refer to the field being initialized, see `FieldLoc` inside `ExprEngine::ProcessInitializer`. When a constructor (of which we evaluate the initializer-list) is analyzed in top-level context, then the `this` pointer will be represented by a `SymbolicRegion`, (as it should be). This means that we will form a `FieldRegion{SymbolicRegion{.}}` as the initialized region. ```c++ class Bear { public: void brum() const; }; class Door { public: // PostInitializer would refer to "FieldRegion{SymRegion{this}}" // whereas in the store and everywhere else it would be: // "FieldRegion{ELementRegion{SymRegion{Ty*, this}, 0, Ty}". Door() : ptr(nullptr) { ptr->brum(); // Bug } private: Bear* ptr; }; ``` We (as CSA folks) decided to avoid the creation of FieldRegions directly of symbolic regions in the past: https://github.com/llvm/llvm-project/commit/f8643a9b31c4029942f67d4534c9139b45173504 --- In this patch, I propose to also canonicalize it as in the mentioned patch, into this: `FieldRegion{ElementRegion{SymbolicRegion{Ty*, .}, 0, Ty}` This would mean that FieldRegions will/should never simply wrap a SymbolicRegion directly, but rather an ElementRegion that is sitting in between. This patch should have practically no observable effects, as the store (due to the mentioned patch) was made resilient to this issue, but we use `PostInitializer::getLocationValue()` for an alternative reporting, where we faced this issue. Note that in really rare cases it suppresses now dereference bugs, as demonstrated in the test. It is because in the past we failed to follow the region of the PostInitializer inside the StoreSiteFinder visitor - because it was using this code: ```c++ // If this is a post initializer expression, initializing the region, we // should track the initializer expression. if (std::optional<PostInitializer> PIP = Pred->getLocationAs<PostInitializer>()) { const MemRegion *FieldReg = (const MemRegion *)PIP->getLocationValue(); if (FieldReg == R) { StoreSite = Pred; InitE = PIP->getInitializer()->getInit(); } } ``` Notice that the equality check didn't pass for the regions I'm canonicalizing in this patch. Given the nature of this change, we would rather upstream this patch. CPP-4954
2024-03-21 18:22:22 +01:00
/// SymbolicRegions are expected to be wrapped by an ElementRegion as a
/// canonical representation. As a canonical representation, SymbolicRegions
/// should be wrapped by ElementRegions before getting a FieldRegion.
/// See f8643a9b31c4029942f67d4534c9139b45173504 why.
SVal ProgramState::wrapSymbolicRegion(SVal Val) const {
const auto *BaseReg = dyn_cast_or_null<SymbolicRegion>(Val.getAsRegion());
if (!BaseReg)
return Val;
StoreManager &SM = getStateManager().getStoreManager();
QualType ElemTy = BaseReg->getPointeeStaticType();
return loc::MemRegionVal{SM.GetElementZeroRegion(BaseReg, ElemTy)};
}
ProgramStateRef
ProgramState::enterStackFrame(const CallEvent &Call,
const StackFrameContext *CalleeCtx) const {
const StoreRef &NewStore =
getStateManager().StoreMgr->enterStackFrame(getStore(), Call, CalleeCtx);
return makeWithStore(NewStore);
2011-08-15 23:05:22 +00:00
}
SVal ProgramState::getSelfSVal(const LocationContext *LCtx) const {
const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl();
if (!SelfDecl)
return SVal();
return getSVal(getRegion(SelfDecl, LCtx));
}
2011-08-15 23:05:22 +00:00
SVal ProgramState::getSValAsScalarOrLoc(const MemRegion *R) const {
// We only want to do fetches from regions that we can actually bind
// values. For example, SymbolicRegions of type 'id<...>' cannot
// have direct bindings (but their can be bindings on their subregions).
if (!R->isBoundable())
return UnknownVal();
if (const TypedValueRegion *TR = dyn_cast<TypedValueRegion>(R)) {
QualType T = TR->getValueType();
if (Loc::isLocType(T) || T->isIntegralOrEnumerationType())
2011-08-15 23:05:22 +00:00
return getSVal(R);
}
return UnknownVal();
}
SVal ProgramState::getSVal(Loc location, QualType T) const {
SVal V = getRawSVal(location, T);
2011-08-15 23:05:22 +00:00
// If 'V' is a symbolic value that is *perfectly* constrained to
// be a constant value, use that value instead to lessen the burden
// on later analysis stages (so we have less symbolic values to reason
// about).
// We only go into this branch if we can convert the APSInt value we have
// to the type of T, which is not always the case (e.g. for void).
if (!T.isNull() && (T->isIntegralOrEnumerationType() || Loc::isLocType(T))) {
2011-08-15 23:05:22 +00:00
if (SymbolRef sym = V.getAsSymbol()) {
if (const llvm::APSInt *Int = getStateManager()
.getConstraintManager()
.getSymVal(this, sym)) {
2011-08-15 23:05:22 +00:00
// FIXME: Because we don't correctly model (yet) sign-extension
// and truncation of symbolic values, we need to convert
// the integer value to the correct signedness and bitwidth.
//
// This shows up in the following:
//
// char foo();
// unsigned x = foo();
// if (x == 54)
// ...
//
// The symbolic value stored to 'x' is actually the conjured
// symbol for the call to foo(); the type of that symbol is 'char',
// not unsigned.
APSIntPtr NewV = getBasicVals().Convert(T, *Int);
if (V.getAs<Loc>())
2011-08-15 23:05:22 +00:00
return loc::ConcreteInt(NewV);
return nonloc::ConcreteInt(NewV);
2011-08-15 23:05:22 +00:00
}
}
}
2011-08-15 23:05:22 +00:00
return V;
}
ProgramStateRef ProgramState::BindExpr(const Stmt *S,
const LocationContext *LCtx,
SVal V, bool Invalidate) const{
Environment NewEnv =
getStateManager().EnvMgr.bindExpr(Env, EnvironmentEntry(S, LCtx), V,
Invalidate);
2011-08-15 23:05:22 +00:00
if (NewEnv == Env)
return this;
ProgramState NewSt = *this;
NewSt.Env = NewEnv;
return getStateManager().getPersistentState(NewSt);
}
[[nodiscard]] std::pair<ProgramStateRef, ProgramStateRef>
ProgramState::assumeInBoundDual(DefinedOrUnknownSVal Idx,
DefinedOrUnknownSVal UpperBound,
QualType indexTy) const {
2011-08-15 23:05:22 +00:00
if (Idx.isUnknown() || UpperBound.isUnknown())
return {this, this};
2011-08-15 23:05:22 +00:00
// Build an expression for 0 <= Idx < UpperBound.
// This is the same as Idx + MIN < UpperBound + MIN, if overflow is allowed.
// FIXME: This should probably be part of SValBuilder.
ProgramStateManager &SM = getStateManager();
SValBuilder &svalBuilder = SM.getSValBuilder();
ASTContext &Ctx = svalBuilder.getContext();
// Get the offset: the minimum value of the array index type.
BasicValueFactory &BVF = svalBuilder.getBasicValueFactory();
if (indexTy.isNull())
indexTy = svalBuilder.getArrayIndexType();
2011-08-15 23:05:22 +00:00
nonloc::ConcreteInt Min(BVF.getMinValue(indexTy));
// Adjust the index.
SVal newIdx = svalBuilder.evalBinOpNN(this, BO_Add,
Idx.castAs<NonLoc>(), Min, indexTy);
2011-08-15 23:05:22 +00:00
if (newIdx.isUnknownOrUndef())
return {this, this};
2011-08-15 23:05:22 +00:00
// Adjust the upper bound.
SVal newBound =
svalBuilder.evalBinOpNN(this, BO_Add, UpperBound.castAs<NonLoc>(),
2011-08-15 23:05:22 +00:00
Min, indexTy);
if (newBound.isUnknownOrUndef())
return {this, this};
2011-08-15 23:05:22 +00:00
// Build the actual comparison.
SVal inBound = svalBuilder.evalBinOpNN(this, BO_LT, newIdx.castAs<NonLoc>(),
newBound.castAs<NonLoc>(), Ctx.IntTy);
2011-08-15 23:05:22 +00:00
if (inBound.isUnknownOrUndef())
return {this, this};
2011-08-15 23:05:22 +00:00
// Finally, let the constraint manager take care of it.
ConstraintManager &CM = SM.getConstraintManager();
return CM.assumeDual(this, inBound.castAs<DefinedSVal>());
}
ProgramStateRef ProgramState::assumeInBound(DefinedOrUnknownSVal Idx,
DefinedOrUnknownSVal UpperBound,
bool Assumption,
QualType indexTy) const {
std::pair<ProgramStateRef, ProgramStateRef> R =
assumeInBoundDual(Idx, UpperBound, indexTy);
return Assumption ? R.first : R.second;
2011-08-15 23:05:22 +00:00
}
ConditionTruthVal ProgramState::isNonNull(SVal V) const {
ConditionTruthVal IsNull = isNull(V);
if (IsNull.isUnderconstrained())
return IsNull;
return ConditionTruthVal(!IsNull.getValue());
}
ConditionTruthVal ProgramState::areEqual(SVal Lhs, SVal Rhs) const {
return stateMgr->getSValBuilder().areEqual(this, Lhs, Rhs);
}
ConditionTruthVal ProgramState::isNull(SVal V) const {
if (V.isZeroConstant())
return true;
if (V.isConstant())
return false;
SymbolRef Sym = V.getAsSymbol(/* IncludeBaseRegion */ true);
if (!Sym)
return ConditionTruthVal();
return getStateManager().ConstraintMgr->isNull(this, Sym);
}
ProgramStateRef ProgramStateManager::getInitialState(const LocationContext *InitLoc) {
2011-08-15 23:05:22 +00:00
ProgramState State(this,
EnvMgr.getInitialEnvironment(),
StoreMgr->getInitialStore(InitLoc),
GDMFactory.getEmptyMap());
return getPersistentState(State);
}
ProgramStateRef ProgramStateManager::getPersistentStateWithGDM(
ProgramStateRef FromState,
ProgramStateRef GDMState) {
ProgramState NewState(*FromState);
2011-08-15 23:05:22 +00:00
NewState.GDM = GDMState->GDM;
return getPersistentState(NewState);
}
ProgramStateRef ProgramStateManager::getPersistentState(ProgramState &State) {
2011-08-15 23:05:22 +00:00
llvm::FoldingSetNodeID ID;
State.Profile(ID);
void *InsertPos;
if (ProgramState *I = StateSet.FindNodeOrInsertPos(ID, InsertPos))
return I;
ProgramState *newState = nullptr;
2011-08-15 23:05:22 +00:00
if (!freeStates.empty()) {
newState = freeStates.back();
freeStates.pop_back();
2011-08-15 23:05:22 +00:00
}
else {
newState = Alloc.Allocate<ProgramState>();
2011-08-15 23:05:22 +00:00
}
new (newState) ProgramState(State);
StateSet.InsertNode(newState, InsertPos);
return newState;
}
ProgramStateRef ProgramState::makeWithStore(const StoreRef &store) const {
ProgramState NewSt(*this);
2011-08-15 23:05:22 +00:00
NewSt.setStore(store);
return getStateManager().getPersistentState(NewSt);
}
ProgramStateRef ProgramState::cloneAsPosteriorlyOverconstrained() const {
ProgramState NewSt(*this);
NewSt.PosteriorlyOverconstrained = true;
return getStateManager().getPersistentState(NewSt);
}
2011-08-15 23:05:22 +00:00
void ProgramState::setStore(const StoreRef &newStore) {
Store newStoreStore = newStore.getStore();
if (newStoreStore)
stateMgr->getStoreManager().incrementReferenceCount(newStoreStore);
if (store)
stateMgr->getStoreManager().decrementReferenceCount(store);
store = newStoreStore;
}
[analyzer] Wrap SymbolicRegions by ElementRegions before getting a FieldRegion (#85211) Inside the ExprEngine when we process the initializers, we create a PostInitializer program-point, which will refer to the field being initialized, see `FieldLoc` inside `ExprEngine::ProcessInitializer`. When a constructor (of which we evaluate the initializer-list) is analyzed in top-level context, then the `this` pointer will be represented by a `SymbolicRegion`, (as it should be). This means that we will form a `FieldRegion{SymbolicRegion{.}}` as the initialized region. ```c++ class Bear { public: void brum() const; }; class Door { public: // PostInitializer would refer to "FieldRegion{SymRegion{this}}" // whereas in the store and everywhere else it would be: // "FieldRegion{ELementRegion{SymRegion{Ty*, this}, 0, Ty}". Door() : ptr(nullptr) { ptr->brum(); // Bug } private: Bear* ptr; }; ``` We (as CSA folks) decided to avoid the creation of FieldRegions directly of symbolic regions in the past: https://github.com/llvm/llvm-project/commit/f8643a9b31c4029942f67d4534c9139b45173504 --- In this patch, I propose to also canonicalize it as in the mentioned patch, into this: `FieldRegion{ElementRegion{SymbolicRegion{Ty*, .}, 0, Ty}` This would mean that FieldRegions will/should never simply wrap a SymbolicRegion directly, but rather an ElementRegion that is sitting in between. This patch should have practically no observable effects, as the store (due to the mentioned patch) was made resilient to this issue, but we use `PostInitializer::getLocationValue()` for an alternative reporting, where we faced this issue. Note that in really rare cases it suppresses now dereference bugs, as demonstrated in the test. It is because in the past we failed to follow the region of the PostInitializer inside the StoreSiteFinder visitor - because it was using this code: ```c++ // If this is a post initializer expression, initializing the region, we // should track the initializer expression. if (std::optional<PostInitializer> PIP = Pred->getLocationAs<PostInitializer>()) { const MemRegion *FieldReg = (const MemRegion *)PIP->getLocationValue(); if (FieldReg == R) { StoreSite = Pred; InitE = PIP->getInitializer()->getInit(); } } ``` Notice that the equality check didn't pass for the regions I'm canonicalizing in this patch. Given the nature of this change, we would rather upstream this patch. CPP-4954
2024-03-21 18:22:22 +01:00
SVal ProgramState::getLValue(const FieldDecl *D, SVal Base) const {
[analyzer] Never create Regions wrapping reference TypedValueRegions (NFCI) (#118096) Like in the test case: ```c++ struct String { String(const String &) {} }; struct MatchComponent { unsigned numbers[2]; String prerelease; MatchComponent(MatchComponent const &) = default; }; MatchComponent get(); void consume(MatchComponent const &); MatchComponent parseMatchComponent() { MatchComponent component = get(); component.numbers[0] = 10; component.numbers[1] = 20; return component; // We should have no stack addr escape warning here. } void top() { consume(parseMatchComponent()); } ``` When calling `consume(parseMatchComponent())` the `parseMatchComponent()` would return a copy of a temporary of `component`. That copy would invoke the `MatchComponent::MatchComponent(const MatchComponent &)` ctor. That ctor would have a (reference typed) ParamVarRegion, holding the location (lvalue) of the object we are about to copy (&component). So far so good, but just before evaluating the binding operation for initializing the `numbers` field of the temporary, we evaluate the ArrayInitLoopExpr representing the by-value elementwise copy of the array `component.numbers`. This is represented by a LazyCompoundVal, because we (usually) don't just copy large arrays and bind many individual direct bindings. Rather, we take a snapshot by using a LCV. However, notice that the LCV representing this copy would look like this: lazyCompoundVal{ParamVarRegion{"reference param of cctor"}.numbers} Notice that it refers to the numbers field of a reference. It would be much better to desugar the reference to the actual object, thus it should be: `lazyCompoundVal{component.numbers}` Actually, when binding the result of the ArrayInitLoopExpr to the `temp_object.numbers` in the compiler-generated member initializer of the cctor, we should have two individual direct bindings because this is a "small array": ``` binding &Element{temp_object.numbers, 0} <- loadFrom(&Element{component.numbers, 0}) binding &Element{temp_object.numbers, 1} <- loadFrom(&Element{component.numbers, 1}) ``` Where `loadFrom(...)` would be: ``` loadFrom(&Element{component.numbers, 0}): 10 U32b loadFrom(&Element{component.numbers, 1}): 20 U32b ``` So the store should look like this, after PostInitializer of `temp_object.numbers`: ``` temp_object at offset 0: 10 U32b temp_object at offset 32: 20 U32b ``` The lesson is that it's okay to have TypedValueRegions of references as long as we don't form subregions of those. If we ever want to refer to a subregion of a "reference" we actually meant to "desugar" the reference and slice a subregion of the pointee of the reference instead. Once this canonicalization is in place, we can also drop the special handling of references in `ProcessInitializer`, because now reference TypedValueRegions are eagerly desugared into their referee region when forming a subregion of it. There should be no practical differences, but there are of course bugs that this patch may surface.
2024-11-29 20:36:24 +01:00
Base = desugarReference(Base);
[analyzer] Wrap SymbolicRegions by ElementRegions before getting a FieldRegion (#85211) Inside the ExprEngine when we process the initializers, we create a PostInitializer program-point, which will refer to the field being initialized, see `FieldLoc` inside `ExprEngine::ProcessInitializer`. When a constructor (of which we evaluate the initializer-list) is analyzed in top-level context, then the `this` pointer will be represented by a `SymbolicRegion`, (as it should be). This means that we will form a `FieldRegion{SymbolicRegion{.}}` as the initialized region. ```c++ class Bear { public: void brum() const; }; class Door { public: // PostInitializer would refer to "FieldRegion{SymRegion{this}}" // whereas in the store and everywhere else it would be: // "FieldRegion{ELementRegion{SymRegion{Ty*, this}, 0, Ty}". Door() : ptr(nullptr) { ptr->brum(); // Bug } private: Bear* ptr; }; ``` We (as CSA folks) decided to avoid the creation of FieldRegions directly of symbolic regions in the past: https://github.com/llvm/llvm-project/commit/f8643a9b31c4029942f67d4534c9139b45173504 --- In this patch, I propose to also canonicalize it as in the mentioned patch, into this: `FieldRegion{ElementRegion{SymbolicRegion{Ty*, .}, 0, Ty}` This would mean that FieldRegions will/should never simply wrap a SymbolicRegion directly, but rather an ElementRegion that is sitting in between. This patch should have practically no observable effects, as the store (due to the mentioned patch) was made resilient to this issue, but we use `PostInitializer::getLocationValue()` for an alternative reporting, where we faced this issue. Note that in really rare cases it suppresses now dereference bugs, as demonstrated in the test. It is because in the past we failed to follow the region of the PostInitializer inside the StoreSiteFinder visitor - because it was using this code: ```c++ // If this is a post initializer expression, initializing the region, we // should track the initializer expression. if (std::optional<PostInitializer> PIP = Pred->getLocationAs<PostInitializer>()) { const MemRegion *FieldReg = (const MemRegion *)PIP->getLocationValue(); if (FieldReg == R) { StoreSite = Pred; InitE = PIP->getInitializer()->getInit(); } } ``` Notice that the equality check didn't pass for the regions I'm canonicalizing in this patch. Given the nature of this change, we would rather upstream this patch. CPP-4954
2024-03-21 18:22:22 +01:00
Base = wrapSymbolicRegion(Base);
return getStateManager().StoreMgr->getLValueField(D, Base);
}
SVal ProgramState::getLValue(const IndirectFieldDecl *D, SVal Base) const {
StoreManager &SM = *getStateManager().StoreMgr;
[analyzer] Never create Regions wrapping reference TypedValueRegions (NFCI) (#118096) Like in the test case: ```c++ struct String { String(const String &) {} }; struct MatchComponent { unsigned numbers[2]; String prerelease; MatchComponent(MatchComponent const &) = default; }; MatchComponent get(); void consume(MatchComponent const &); MatchComponent parseMatchComponent() { MatchComponent component = get(); component.numbers[0] = 10; component.numbers[1] = 20; return component; // We should have no stack addr escape warning here. } void top() { consume(parseMatchComponent()); } ``` When calling `consume(parseMatchComponent())` the `parseMatchComponent()` would return a copy of a temporary of `component`. That copy would invoke the `MatchComponent::MatchComponent(const MatchComponent &)` ctor. That ctor would have a (reference typed) ParamVarRegion, holding the location (lvalue) of the object we are about to copy (&component). So far so good, but just before evaluating the binding operation for initializing the `numbers` field of the temporary, we evaluate the ArrayInitLoopExpr representing the by-value elementwise copy of the array `component.numbers`. This is represented by a LazyCompoundVal, because we (usually) don't just copy large arrays and bind many individual direct bindings. Rather, we take a snapshot by using a LCV. However, notice that the LCV representing this copy would look like this: lazyCompoundVal{ParamVarRegion{"reference param of cctor"}.numbers} Notice that it refers to the numbers field of a reference. It would be much better to desugar the reference to the actual object, thus it should be: `lazyCompoundVal{component.numbers}` Actually, when binding the result of the ArrayInitLoopExpr to the `temp_object.numbers` in the compiler-generated member initializer of the cctor, we should have two individual direct bindings because this is a "small array": ``` binding &Element{temp_object.numbers, 0} <- loadFrom(&Element{component.numbers, 0}) binding &Element{temp_object.numbers, 1} <- loadFrom(&Element{component.numbers, 1}) ``` Where `loadFrom(...)` would be: ``` loadFrom(&Element{component.numbers, 0}): 10 U32b loadFrom(&Element{component.numbers, 1}): 20 U32b ``` So the store should look like this, after PostInitializer of `temp_object.numbers`: ``` temp_object at offset 0: 10 U32b temp_object at offset 32: 20 U32b ``` The lesson is that it's okay to have TypedValueRegions of references as long as we don't form subregions of those. If we ever want to refer to a subregion of a "reference" we actually meant to "desugar" the reference and slice a subregion of the pointee of the reference instead. Once this canonicalization is in place, we can also drop the special handling of references in `ProcessInitializer`, because now reference TypedValueRegions are eagerly desugared into their referee region when forming a subregion of it. There should be no practical differences, but there are of course bugs that this patch may surface.
2024-11-29 20:36:24 +01:00
Base = desugarReference(Base);
[analyzer] Wrap SymbolicRegions by ElementRegions before getting a FieldRegion (#85211) Inside the ExprEngine when we process the initializers, we create a PostInitializer program-point, which will refer to the field being initialized, see `FieldLoc` inside `ExprEngine::ProcessInitializer`. When a constructor (of which we evaluate the initializer-list) is analyzed in top-level context, then the `this` pointer will be represented by a `SymbolicRegion`, (as it should be). This means that we will form a `FieldRegion{SymbolicRegion{.}}` as the initialized region. ```c++ class Bear { public: void brum() const; }; class Door { public: // PostInitializer would refer to "FieldRegion{SymRegion{this}}" // whereas in the store and everywhere else it would be: // "FieldRegion{ELementRegion{SymRegion{Ty*, this}, 0, Ty}". Door() : ptr(nullptr) { ptr->brum(); // Bug } private: Bear* ptr; }; ``` We (as CSA folks) decided to avoid the creation of FieldRegions directly of symbolic regions in the past: https://github.com/llvm/llvm-project/commit/f8643a9b31c4029942f67d4534c9139b45173504 --- In this patch, I propose to also canonicalize it as in the mentioned patch, into this: `FieldRegion{ElementRegion{SymbolicRegion{Ty*, .}, 0, Ty}` This would mean that FieldRegions will/should never simply wrap a SymbolicRegion directly, but rather an ElementRegion that is sitting in between. This patch should have practically no observable effects, as the store (due to the mentioned patch) was made resilient to this issue, but we use `PostInitializer::getLocationValue()` for an alternative reporting, where we faced this issue. Note that in really rare cases it suppresses now dereference bugs, as demonstrated in the test. It is because in the past we failed to follow the region of the PostInitializer inside the StoreSiteFinder visitor - because it was using this code: ```c++ // If this is a post initializer expression, initializing the region, we // should track the initializer expression. if (std::optional<PostInitializer> PIP = Pred->getLocationAs<PostInitializer>()) { const MemRegion *FieldReg = (const MemRegion *)PIP->getLocationValue(); if (FieldReg == R) { StoreSite = Pred; InitE = PIP->getInitializer()->getInit(); } } ``` Notice that the equality check didn't pass for the regions I'm canonicalizing in this patch. Given the nature of this change, we would rather upstream this patch. CPP-4954
2024-03-21 18:22:22 +01:00
Base = wrapSymbolicRegion(Base);
// FIXME: This should work with `SM.getLValueField(D->getAnonField(), Base)`,
// but that would break some tests. There is probably a bug somewhere that it
// would expose.
for (const auto *I : D->chain()) {
Base = SM.getLValueField(cast<FieldDecl>(I), Base);
}
return Base;
}
2011-08-15 23:05:22 +00:00
//===----------------------------------------------------------------------===//
// State pretty-printing.
//===----------------------------------------------------------------------===//
void ProgramState::printJson(raw_ostream &Out, const LocationContext *LCtx,
const char *NL, unsigned int Space,
bool IsDot) const {
Indent(Out, Space, IsDot) << "\"program_state\": {" << NL;
++Space;
2011-08-15 23:05:22 +00:00
ProgramStateManager &Mgr = getStateManager();
// Print the store.
Mgr.getStoreManager().printJson(Out, getStore(), NL, Space, IsDot);
2011-08-15 23:05:22 +00:00
// Print out the environment.
Env.printJson(Out, Mgr.getContext(), LCtx, NL, Space, IsDot);
2011-08-15 23:05:22 +00:00
// Print out the constraints.
Mgr.getConstraintManager().printJson(Out, this, NL, Space, IsDot);
2011-08-15 23:05:22 +00:00
// Print out the tracked dynamic types.
printDynamicTypeInfoJson(Out, this, NL, Space, IsDot);
2011-08-15 23:05:22 +00:00
// Print checker-specific data.
Mgr.getOwningEngine().printJson(Out, this, LCtx, NL, Space, IsDot);
--Space;
Indent(Out, Space, IsDot) << '}';
2011-08-15 23:05:22 +00:00
}
void ProgramState::printDOT(raw_ostream &Out, const LocationContext *LCtx,
unsigned int Space) const {
printJson(Out, LCtx, /*NL=*/"\\l", Space, /*IsDot=*/true);
2011-08-15 23:05:22 +00:00
}
LLVM_DUMP_METHOD void ProgramState::dump() const {
printJson(llvm::errs());
2011-08-15 23:05:22 +00:00
}
AnalysisManager& ProgramState::getAnalysisManager() const {
return stateMgr->getOwningEngine().getAnalysisManager();
}
2011-08-15 23:05:22 +00:00
//===----------------------------------------------------------------------===//
// Generic Data Map.
//===----------------------------------------------------------------------===//
void *const* ProgramState::FindGDM(void *K) const {
return GDM.lookup(K);
}
void*
ProgramStateManager::FindGDMContext(void *K,
void *(*CreateContext)(llvm::BumpPtrAllocator&),
void (*DeleteContext)(void*)) {
std::pair<void*, void (*)(void*)>& p = GDMContexts[K];
if (!p.first) {
p.first = CreateContext(Alloc);
p.second = DeleteContext;
}
return p.first;
}
ProgramStateRef ProgramStateManager::addGDM(ProgramStateRef St, void *Key, void *Data){
2011-08-15 23:05:22 +00:00
ProgramState::GenericDataMap M1 = St->getGDM();
ProgramState::GenericDataMap M2 = GDMFactory.add(M1, Key, Data);
if (M1 == M2)
return St;
ProgramState NewSt = *St;
NewSt.GDM = M2;
return getPersistentState(NewSt);
}
ProgramStateRef ProgramStateManager::removeGDM(ProgramStateRef state, void *Key) {
2011-08-15 23:05:22 +00:00
ProgramState::GenericDataMap OldM = state->getGDM();
ProgramState::GenericDataMap NewM = GDMFactory.remove(OldM, Key);
if (NewM == OldM)
return state;
ProgramState NewState = *state;
NewState.GDM = NewM;
return getPersistentState(NewState);
}
bool ScanReachableSymbols::scan(nonloc::LazyCompoundVal val) {
bool wasVisited = !visited.insert(val.getCVData()).second;
if (wasVisited)
return true;
StoreManager &StoreMgr = state->getStateManager().getStoreManager();
// FIXME: We don't really want to use getBaseRegion() here because pointer
// arithmetic doesn't apply, but scanReachableSymbols only accepts base
// regions right now.
const MemRegion *R = val.getRegion()->getBaseRegion();
return StoreMgr.scanReachableSymbols(val.getStore(), R, *this);
}
2011-08-15 23:05:22 +00:00
bool ScanReachableSymbols::scan(nonloc::CompoundVal val) {
for (SVal V : val)
if (!scan(V))
2011-08-15 23:05:22 +00:00
return false;
return true;
}
bool ScanReachableSymbols::scan(const SymExpr *sym) {
for (SymbolRef SubSym : sym->symbols()) {
bool wasVisited = !visited.insert(SubSym).second;
if (wasVisited)
continue;
if (!visitor.VisitSymbol(SubSym))
return false;
2011-08-15 23:05:22 +00:00
}
2011-08-15 23:05:22 +00:00
return true;
}
bool ScanReachableSymbols::scan(SVal val) {
if (std::optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>())
2011-08-15 23:05:22 +00:00
return scan(X->getRegion());
if (std::optional<nonloc::LazyCompoundVal> X =
val.getAs<nonloc::LazyCompoundVal>())
return scan(*X);
if (std::optional<nonloc::LocAsInteger> X = val.getAs<nonloc::LocAsInteger>())
2011-08-15 23:05:22 +00:00
return scan(X->getLoc());
if (SymbolRef Sym = val.getAsSymbol())
return scan(Sym);
if (std::optional<nonloc::CompoundVal> X = val.getAs<nonloc::CompoundVal>())
2011-08-15 23:05:22 +00:00
return scan(*X);
return true;
}
bool ScanReachableSymbols::scan(const MemRegion *R) {
if (isa<MemSpaceRegion>(R))
return true;
bool wasVisited = !visited.insert(R).second;
if (wasVisited)
2011-08-15 23:05:22 +00:00
return true;
if (!visitor.VisitMemRegion(R))
return false;
2011-08-15 23:05:22 +00:00
// If this is a symbolic region, visit the symbol for the region.
if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(R))
if (!visitor.VisitSymbol(SR->getSymbol()))
return false;
// If this is a subregion, also visit the parent regions.
if (const SubRegion *SR = dyn_cast<SubRegion>(R)) {
const MemRegion *Super = SR->getSuperRegion();
if (!scan(Super))
2011-08-15 23:05:22 +00:00
return false;
// When we reach the topmost region, scan all symbols in it.
if (isa<MemSpaceRegion>(Super)) {
StoreManager &StoreMgr = state->getStateManager().getStoreManager();
if (!StoreMgr.scanReachableSymbols(state->getStore(), SR, *this))
return false;
}
}
// Regions captured by a block are also implicitly reachable.
if (const BlockDataRegion *BDR = dyn_cast<BlockDataRegion>(R)) {
for (auto Var : BDR->referenced_vars()) {
if (!scan(Var.getCapturedRegion()))
return false;
}
}
return true;
2011-08-15 23:05:22 +00:00
}
bool ProgramState::scanReachableSymbols(SVal val, SymbolVisitor& visitor) const {
ScanReachableSymbols S(this, visitor);
return S.scan(val);
}
bool ProgramState::scanReachableSymbols(
llvm::iterator_range<region_iterator> Reachable,
SymbolVisitor &visitor) const {
2011-08-15 23:05:22 +00:00
ScanReachableSymbols S(this, visitor);
for (const MemRegion *R : Reachable) {
if (!S.scan(R))
2011-08-15 23:05:22 +00:00
return false;
}
return true;
}