Skip to content

Commit

Permalink
[analyzer] Wrap SymbolicRegions by ElementRegions before getting a Fi…
Browse files Browse the repository at this point in the history
…eldRegion (#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:

f8643a9

---

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
  • Loading branch information
steakhal committed Mar 21, 2024
1 parent 797336b commit c877294
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ class ProgramState : public llvm::FoldingSetNode {
InvalidatedSymbols *IS,
RegionAndSymbolInvalidationTraits *HTraits,
const CallEvent *Call) const;

SVal wrapSymbolicRegion(SVal Base) const;
};

//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -782,20 +784,6 @@ inline SVal ProgramState::getLValue(const ObjCIvarDecl *D, SVal Base) const {
return getStateManager().StoreMgr->getLValueIvar(D, Base);
}

inline SVal ProgramState::getLValue(const FieldDecl *D, SVal Base) const {
return getStateManager().StoreMgr->getLValueField(D, Base);
}

inline SVal ProgramState::getLValue(const IndirectFieldDecl *D,
SVal Base) const {
StoreManager &SM = *getStateManager().StoreMgr;
for (const auto *I : D->chain()) {
Base = SM.getLValueField(cast<FieldDecl>(I), Base);
}

return Base;
}

inline SVal ProgramState::getLValue(QualType ElementType, SVal Idx, SVal Base) const{
if (std::optional<NonLoc> N = Idx.getAs<NonLoc>())
return getStateManager().StoreMgr->getLValueElement(ElementType, *N, Base);
Expand Down
32 changes: 32 additions & 0 deletions clang/lib/StaticAnalyzer/Core/ProgramState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,20 @@ ProgramStateRef ProgramState::killBinding(Loc LV) const {
return makeWithStore(newStore);
}

/// 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 {
Expand Down Expand Up @@ -451,6 +465,24 @@ void ProgramState::setStore(const StoreRef &newStore) {
store = newStoreStore;
}

SVal ProgramState::getLValue(const FieldDecl *D, SVal Base) const {
Base = wrapSymbolicRegion(Base);
return getStateManager().StoreMgr->getLValueField(D, Base);
}

SVal ProgramState::getLValue(const IndirectFieldDecl *D, SVal Base) const {
StoreManager &SM = *getStateManager().StoreMgr;
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;
}

//===----------------------------------------------------------------------===//
// State pretty-printing.
//===----------------------------------------------------------------------===//
Expand Down
17 changes: 17 additions & 0 deletions clang/test/Analysis/inlining/false-positive-suppression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,20 @@ namespace Cleanups {
testArgumentHelper(NonTrivial().getNull());
}
}

class Bear *getNullBear() { return nullptr; }
class Bear {
public:
void brum() const;
};
class Door {
public:
Door() : ptr(getNullBear()) {
ptr->brum();
#ifndef SUPPRESSED
// expected-warning@-2 {{Called C++ object pointer is null}}
#endif
}
private:
Bear* ptr;
};

0 comments on commit c877294

Please sign in to comment.