[analyzer] Better detect when C++ object was constructed into existing region.

When the analyzer evaluates a CXXConstructExpr, it looks ahead in the CFG for
the current block to detect what region the object should be constructed into.
If the constructor was directly constructed into a local variable or field
region then there is no need to explicitly bind the constructed value to
the local or field when analyzing the DeclStmt or CXXCtorInitializer that
called the constructor.

Unfortunately, there were situations in which the CXXConstructExpr was
constructed into a temporary region but when evaluating the corresponding
DeclStmt or CXXCtorInitializer the analyzer assumed the object was constructed
into the local or field. This led to spurious warnings about uninitialized
values (PR25777).

To avoid these false positives, this commit factors out the logic for
determining when a CXXConstructExpr will be directly constructed into existing
storage, adds the inverse logic to detect when the corresponding later bind can
be safely skipped, and adds assertions to make sure these two checks are in
sync.

rdar://problem/21947725

llvm-svn: 255859
This commit is contained in:
Devin Coughlin 2015-12-17 00:28:33 +00:00
parent eefff9ccc5
commit 412c0af2b8
5 changed files with 177 additions and 38 deletions

View File

@ -604,6 +604,28 @@ private:
const LocationContext *LC,
const Expr *E,
const Expr *ResultE = nullptr);
/// For a DeclStmt or CXXInitCtorInitializer, walk backward in the current CFG
/// block to find the constructor expression that directly constructed into
/// the storage for this statement. Returns null if the constructor for this
/// statement created a temporary object region rather than directly
/// constructing into an existing region.
const CXXConstructExpr *findDirectConstructorForCurrentCFGElement();
/// For a CXXConstructExpr, walk forward in the current CFG block to find the
/// CFGElement for the DeclStmt or CXXInitCtorInitializer for which is
/// directly constructed by this constructor. Returns None if the current
/// constructor expression did not directly construct into an existing
/// region.
Optional<CFGElement> findElementDirectlyInitializedByCurrentConstructor();
/// For a given constructor, look forward in the current CFG block to
/// determine the region into which an object will be constructed by \p CE.
/// Returns either a field or local variable region if the object will be
/// directly constructed in an existing region or a temporary object region
/// if not.
const MemRegion *getRegionForConstructedObject(const CXXConstructExpr *CE,
ExplodedNode *Pred);
};
/// Traits for storing the call processing policy inside GDM.

View File

@ -476,8 +476,12 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init,
if (BMI->isAnyMemberInitializer()) {
// Constructors build the object directly in the field,
// but non-objects must be copied in from the initializer.
const Expr *Init = BMI->getInit()->IgnoreImplicit();
if (!isa<CXXConstructExpr>(Init)) {
if (auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) {
assert(BMI->getInit()->IgnoreImplicit() == CtorExpr);
(void)CtorExpr;
// The field was directly constructed, so there is no need to bind.
} else {
const Expr *Init = BMI->getInit()->IgnoreImplicit();
const ValueDecl *Field;
if (BMI->isIndirectMemberInitializer()) {
Field = BMI->getIndirectMember();

View File

@ -492,7 +492,10 @@ void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred,
ExplodedNode *UpdatedN = N;
SVal InitVal = state->getSVal(InitEx, LC);
if (isa<CXXConstructExpr>(InitEx->IgnoreImplicit())) {
assert(DS->isSingleDecl());
if (auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) {
assert(InitEx->IgnoreImplicit() == CtorExpr);
(void)CtorExpr;
// We constructed the object directly in the variable.
// No need to bind anything.
B.generateNode(DS, UpdatedN, state);

View File

@ -103,49 +103,32 @@ static SVal makeZeroElementRegion(ProgramStateRef State, SVal LValue,
}
static const MemRegion *getRegionForConstructedObject(
const CXXConstructExpr *CE, ExplodedNode *Pred, ExprEngine &Eng,
unsigned int CurrStmtIdx) {
const MemRegion *
ExprEngine::getRegionForConstructedObject(const CXXConstructExpr *CE,
ExplodedNode *Pred) {
const LocationContext *LCtx = Pred->getLocationContext();
ProgramStateRef State = Pred->getState();
const NodeBuilderContext &CurrBldrCtx = Eng.getBuilderContext();
// See if we're constructing an existing region by looking at the next
// element in the CFG.
const CFGBlock *B = CurrBldrCtx.getBlock();
unsigned int NextStmtIdx = CurrStmtIdx + 1;
if (NextStmtIdx < B->size()) {
CFGElement Next = (*B)[NextStmtIdx];
// Is this a destructor? If so, we might be in the middle of an assignment
// to a local or member: look ahead one more element to see what we find.
while (Next.getAs<CFGImplicitDtor>() && NextStmtIdx + 1 < B->size()) {
++NextStmtIdx;
Next = (*B)[NextStmtIdx];
}
// Is this a constructor for a local variable?
if (Optional<CFGStmt> StmtElem = Next.getAs<CFGStmt>()) {
if (const DeclStmt *DS = dyn_cast<DeclStmt>(StmtElem->getStmt())) {
if (const VarDecl *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) {
if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) {
SVal LValue = State->getLValue(Var, LCtx);
QualType Ty = Var->getType();
LValue = makeZeroElementRegion(State, LValue, Ty);
return LValue.getAsRegion();
}
if (auto Elem = findElementDirectlyInitializedByCurrentConstructor()) {
if (Optional<CFGStmt> StmtElem = Elem->getAs<CFGStmt>()) {
auto *DS = cast<DeclStmt>(StmtElem->getStmt());
if (const auto *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) {
if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) {
SVal LValue = State->getLValue(Var, LCtx);
QualType Ty = Var->getType();
LValue = makeZeroElementRegion(State, LValue, Ty);
return LValue.getAsRegion();
}
}
}
// Is this a constructor for a member?
if (Optional<CFGInitializer> InitElem = Next.getAs<CFGInitializer>()) {
} else if (Optional<CFGInitializer> InitElem = Elem->getAs<CFGInitializer>()) {
const CXXCtorInitializer *Init = InitElem->getInitializer();
assert(Init->isAnyMemberInitializer());
const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl());
Loc ThisPtr = Eng.getSValBuilder().getCXXThis(CurCtor,
LCtx->getCurrentStackFrame());
Loc ThisPtr =
getSValBuilder().getCXXThis(CurCtor, LCtx->getCurrentStackFrame());
SVal ThisVal = State->getSVal(ThisPtr);
const ValueDecl *Field;
@ -167,13 +150,86 @@ static const MemRegion *getRegionForConstructedObject(
// Don't forget to update the pre-constructor initialization code in
// ExprEngine::VisitCXXConstructExpr.
}
// If we couldn't find an existing region to construct into, assume we're
// constructing a temporary.
MemRegionManager &MRMgr = Eng.getSValBuilder().getRegionManager();
MemRegionManager &MRMgr = getSValBuilder().getRegionManager();
return MRMgr.getCXXTempObjectRegion(CE, LCtx);
}
/// Returns true if the initializer for \Elem can be a direct
/// constructor.
static bool canHaveDirectConstructor(CFGElement Elem){
// DeclStmts and CXXCtorInitializers for fields can be directly constructed.
if (Optional<CFGStmt> StmtElem = Elem.getAs<CFGStmt>()) {
if (isa<DeclStmt>(StmtElem->getStmt())) {
return true;
}
}
if (Elem.getKind() == CFGElement::Initializer) {
return true;
}
return false;
}
Optional<CFGElement>
ExprEngine::findElementDirectlyInitializedByCurrentConstructor() {
const NodeBuilderContext &CurrBldrCtx = getBuilderContext();
// See if we're constructing an existing region by looking at the next
// element in the CFG.
const CFGBlock *B = CurrBldrCtx.getBlock();
assert(isa<CXXConstructExpr>(((*B)[currStmtIdx]).castAs<CFGStmt>().getStmt()));
unsigned int NextStmtIdx = currStmtIdx + 1;
if (NextStmtIdx >= B->size())
return None;
CFGElement Next = (*B)[NextStmtIdx];
// Is this a destructor? If so, we might be in the middle of an assignment
// to a local or member: look ahead one more element to see what we find.
while (Next.getAs<CFGImplicitDtor>() && NextStmtIdx + 1 < B->size()) {
++NextStmtIdx;
Next = (*B)[NextStmtIdx];
}
if (canHaveDirectConstructor(Next))
return Next;
return None;
}
const CXXConstructExpr *
ExprEngine::findDirectConstructorForCurrentCFGElement() {
// Go backward in the CFG to see if the previous element (ignoring
// destructors) was a CXXConstructExpr. If so, that constructor
// was constructed directly into an existing region.
// This process is essentially the inverse of that performed in
// findElementDirectlyInitializedByCurrentConstructor().
if (currStmtIdx == 0)
return nullptr;
const CFGBlock *B = getBuilderContext().getBlock();
assert(canHaveDirectConstructor((*B)[currStmtIdx]));
unsigned int PreviousStmtIdx = currStmtIdx - 1;
CFGElement Previous = (*B)[PreviousStmtIdx];
while (Previous.getAs<CFGImplicitDtor>() && PreviousStmtIdx > 0) {
--PreviousStmtIdx;
Previous = (*B)[PreviousStmtIdx];
}
if (Optional<CFGStmt> PrevStmtElem = Previous.getAs<CFGStmt>()) {
if (auto *CtorExpr = dyn_cast<CXXConstructExpr>(PrevStmtElem->getStmt())) {
return CtorExpr;
}
}
return nullptr;
}
void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
ExplodedNode *Pred,
ExplodedNodeSet &destNodes) {
@ -188,7 +244,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
switch (CE->getConstructionKind()) {
case CXXConstructExpr::CK_Complete: {
Target = getRegionForConstructedObject(CE, Pred, *this, currStmtIdx);
Target = getRegionForConstructedObject(CE, Pred);
break;
}
case CXXConstructExpr::CK_VirtualBase:

View File

@ -143,3 +143,57 @@ namespace DefaultMemberInitializers {
clang_analyzer_eval(w.p[1] == 'y'); // expected-warning{{TRUE}}
}
}
namespace ReferenceInitialization {
struct OtherStruct {
OtherStruct(int i);
~OtherStruct();
};
struct MyStruct {
MyStruct(int i);
MyStruct(OtherStruct os);
void method() const;
};
void referenceInitializeLocal() {
const MyStruct &myStruct(5);
myStruct.method(); // no-warning
}
void referenceInitializeMultipleLocals() {
const MyStruct &myStruct1(5), myStruct2(5), &myStruct3(5);
myStruct1.method(); // no-warning
myStruct2.method(); // no-warning
myStruct3.method(); // no-warning
}
void referenceInitializeLocalWithCleanup() {
const MyStruct &myStruct(OtherStruct(5));
myStruct.method(); // no-warning
}
struct HasMyStruct {
const MyStruct &ms; // expected-note {{reference member declared here}}
const MyStruct &msWithCleanups; // expected-note {{reference member declared here}}
// clang's Sema issues a warning when binding a reference member to a
// temporary value.
HasMyStruct() : ms(5), msWithCleanups(OtherStruct(5)) {
// expected-warning@-1 {{binding reference member 'ms' to a temporary value}}
// expected-warning@-2 {{binding reference member 'msWithCleanups' to a temporary value}}
// At this point the members are not garbage so we should not expect an
// analyzer warning here even though binding a reference member
// to a member is a terrible idea.
ms.method(); // no-warning
msWithCleanups.method(); // no-warning
}
};
void referenceInitializeField() {
HasMyStruct hms;
}
};