Skip to content

Commit a84374d

Browse files
committed
[analyzer] Track class member initializer constructors path-sensitively.
The reasoning behind this change is similar to the previous commit, r334681. Because members are already in scope when construction occurs, we are not suffering from liveness problems, but we still want to figure out if the object was constructed with construction context, because in this case we'll be able to avoid trivial copy, which we don't always model perfectly. It'd also have more importance when copy elision is implemented. This also gets rid of the old CFG look-behind mechanism. Differential Revision: https://reviews.llvm.org/D47350 llvm-svn: 334682
1 parent 1fe5247 commit a84374d

File tree

4 files changed

+148
-67
lines changed

4 files changed

+148
-67
lines changed

clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -762,20 +762,23 @@ class ExprEngine : public SubEngine {
762762
/// made within the constructor alive until its declaration actually
763763
/// goes into scope.
764764
static ProgramStateRef addObjectUnderConstruction(
765-
ProgramStateRef State, const Stmt *S,
765+
ProgramStateRef State,
766+
llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P,
766767
const LocationContext *LC, SVal V);
767768

768769
/// Mark the object sa fully constructed, cleaning up the state trait
769770
/// that tracks objects under construction.
770-
static ProgramStateRef finishObjectConstruction(ProgramStateRef State,
771-
const Stmt *S,
772-
const LocationContext *LC);
771+
static ProgramStateRef finishObjectConstruction(
772+
ProgramStateRef State,
773+
llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P,
774+
const LocationContext *LC);
773775

774776
/// If the given statement corresponds to an object under construction,
775777
/// being part of its construciton context, retrieve that object's location.
776-
static Optional<SVal> getObjectUnderConstruction(ProgramStateRef State,
777-
const Stmt *S,
778-
const LocationContext *LC);
778+
static Optional<SVal> getObjectUnderConstruction(
779+
ProgramStateRef State,
780+
llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P,
781+
const LocationContext *LC);
779782

780783
/// Check if all objects under construction have been fully constructed
781784
/// for the given context range (including FromLC, not including ToLC).

clang/lib/StaticAnalyzer/Core/ExprEngine.cpp

Lines changed: 106 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,75 @@ STATISTIC(NumTimesRetriedWithoutInlining,
109109
// to the object's location, so that on every such statement the location
110110
// could have been retrieved.
111111

112-
typedef std::pair<const Stmt *, const LocationContext *> ConstructedObjectKey;
112+
/// ConstructedObjectKey is used for being able to find the path-sensitive
113+
/// memory region of a freshly constructed object while modeling the AST node
114+
/// that syntactically represents the object that is being constructed.
115+
/// Semantics of such nodes may sometimes require access to the region that's
116+
/// not otherwise present in the program state, or to the very fact that
117+
/// the construction context was present and contained references to these
118+
/// AST nodes.
119+
class ConstructedObjectKey {
120+
typedef std::pair<
121+
llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *>,
122+
const LocationContext *> ConstructedObjectKeyImpl;
123+
124+
ConstructedObjectKeyImpl Impl;
125+
126+
const void *getAnyASTNodePtr() const {
127+
if (const Stmt *S = getStmt())
128+
return S;
129+
else
130+
return getCXXCtorInitializer();
131+
}
132+
133+
public:
134+
ConstructedObjectKey(
135+
llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P,
136+
const LocationContext *LC)
137+
: Impl(P, LC) {
138+
// This is the full list of statements that require additional actions when
139+
// encountered. This list may be expanded when new actions are implemented.
140+
assert(getCXXCtorInitializer() || isa<DeclStmt>(getStmt()) ||
141+
isa<CXXNewExpr>(getStmt()) || isa<CXXBindTemporaryExpr>(getStmt()) ||
142+
isa<MaterializeTemporaryExpr>(getStmt()));
143+
}
144+
145+
const Stmt *getStmt() const {
146+
return Impl.first.dyn_cast<const Stmt *>();
147+
}
148+
149+
const CXXCtorInitializer *getCXXCtorInitializer() const {
150+
return Impl.first.dyn_cast<const CXXCtorInitializer *>();
151+
}
152+
153+
const LocationContext *getLocationContext() const {
154+
return Impl.second;
155+
}
156+
157+
void print(llvm::raw_ostream &OS, PrinterHelper *Helper, PrintingPolicy &PP) {
158+
OS << '(' << getLocationContext() << ',' << getAnyASTNodePtr() << ") ";
159+
if (const Stmt *S = getStmt()) {
160+
S->printPretty(OS, Helper, PP);
161+
} else {
162+
const CXXCtorInitializer *I = getCXXCtorInitializer();
163+
OS << I->getAnyMember()->getNameAsString();
164+
}
165+
}
166+
167+
void Profile(llvm::FoldingSetNodeID &ID) const {
168+
ID.AddPointer(Impl.first.getOpaqueValue());
169+
ID.AddPointer(Impl.second);
170+
}
171+
172+
bool operator==(const ConstructedObjectKey &RHS) const {
173+
return Impl == RHS.Impl;
174+
}
175+
176+
bool operator<(const ConstructedObjectKey &RHS) const {
177+
return Impl < RHS.Impl;
178+
}
179+
};
180+
113181
typedef llvm::ImmutableMap<ConstructedObjectKey, SVal>
114182
ObjectsUnderConstructionMap;
115183
REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction,
@@ -378,26 +446,31 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State,
378446
return State;
379447
}
380448

381-
ProgramStateRef
382-
ExprEngine::addObjectUnderConstruction(ProgramStateRef State, const Stmt *S,
383-
const LocationContext *LC, SVal V) {
384-
ConstructedObjectKey Key(S, LC->getCurrentStackFrame());
449+
ProgramStateRef ExprEngine::addObjectUnderConstruction(
450+
ProgramStateRef State,
451+
llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P,
452+
const LocationContext *LC, SVal V) {
453+
ConstructedObjectKey Key(P, LC->getCurrentStackFrame());
385454
// FIXME: Currently the state might already contain the marker due to
386455
// incorrect handling of temporaries bound to default parameters.
387456
assert(!State->get<ObjectsUnderConstruction>(Key) ||
388-
isa<CXXBindTemporaryExpr>(S));
457+
isa<CXXBindTemporaryExpr>(Key.getStmt()));
389458
return State->set<ObjectsUnderConstruction>(Key, V);
390459
}
391460

392-
Optional<SVal> ExprEngine::getObjectUnderConstruction(ProgramStateRef State,
393-
const Stmt *S, const LocationContext *LC) {
394-
ConstructedObjectKey Key(S, LC->getCurrentStackFrame());
461+
Optional<SVal> ExprEngine::getObjectUnderConstruction(
462+
ProgramStateRef State,
463+
llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P,
464+
const LocationContext *LC) {
465+
ConstructedObjectKey Key(P, LC->getCurrentStackFrame());
395466
return Optional<SVal>::create(State->get<ObjectsUnderConstruction>(Key));
396467
}
397468

398-
ProgramStateRef ExprEngine::finishObjectConstruction(ProgramStateRef State,
399-
const Stmt *S, const LocationContext *LC) {
400-
ConstructedObjectKey Key(S, LC->getCurrentStackFrame());
469+
ProgramStateRef ExprEngine::finishObjectConstruction(
470+
ProgramStateRef State,
471+
llvm::PointerUnion<const Stmt *, const CXXCtorInitializer *> P,
472+
const LocationContext *LC) {
473+
ConstructedObjectKey Key(P, LC->getCurrentStackFrame());
401474
assert(State->contains<ObjectsUnderConstruction>(Key));
402475
return State->remove<ObjectsUnderConstruction>(Key);
403476
}
@@ -409,7 +482,7 @@ bool ExprEngine::areAllObjectsFullyConstructed(ProgramStateRef State,
409482
while (LC != ToLC) {
410483
assert(LC && "ToLC must be a parent of FromLC!");
411484
for (auto I : State->get<ObjectsUnderConstruction>())
412-
if (I.first.second == LC)
485+
if (I.first.getLocationContext() == LC)
413486
return false;
414487

415488
LC = LC->getParent();
@@ -451,10 +524,9 @@ static void printObjectsUnderConstructionForContext(raw_ostream &Out,
451524
for (auto I : State->get<ObjectsUnderConstruction>()) {
452525
ConstructedObjectKey Key = I.first;
453526
SVal Value = I.second;
454-
if (Key.second != LC)
527+
if (Key.getLocationContext() != LC)
455528
continue;
456-
Out << '(' << Key.second << ',' << Key.first << ") ";
457-
Key.first->printPretty(Out, nullptr, PP);
529+
Key.print(Out, nullptr, PP);
458530
Out << " : " << Value << NL;
459531
}
460532
}
@@ -677,9 +749,11 @@ void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) {
677749
Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx);
678750
}
679751

680-
void ExprEngine::ProcessInitializer(const CFGInitializer Init,
752+
void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit,
681753
ExplodedNode *Pred) {
682-
const CXXCtorInitializer *BMI = Init.getInitializer();
754+
const CXXCtorInitializer *BMI = CFGInit.getInitializer();
755+
const Expr *Init = BMI->getInit()->IgnoreImplicit();
756+
const LocationContext *LC = Pred->getLocationContext();
683757

684758
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
685759
BMI->getSourceLocation(),
@@ -692,19 +766,21 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init,
692766
ProgramStateRef State = Pred->getState();
693767
SVal thisVal = State->getSVal(svalBuilder.getCXXThis(decl, stackFrame));
694768

695-
ExplodedNodeSet Tmp(Pred);
769+
ExplodedNodeSet Tmp;
696770
SVal FieldLoc;
697771

698772
// Evaluate the initializer, if necessary
699773
if (BMI->isAnyMemberInitializer()) {
700774
// Constructors build the object directly in the field,
701775
// but non-objects must be copied in from the initializer.
702-
if (const auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) {
703-
assert(BMI->getInit()->IgnoreImplicit() == CtorExpr);
704-
(void)CtorExpr;
776+
if (getObjectUnderConstruction(State, BMI, LC)) {
705777
// The field was directly constructed, so there is no need to bind.
778+
// But we still need to stop tracking the object under construction.
779+
State = finishObjectConstruction(State, BMI, LC);
780+
NodeBuilder Bldr(Pred, Tmp, *currBldrCtx);
781+
PostStore PS(Init, LC, /*Loc*/ nullptr, /*tag*/ nullptr);
782+
Bldr.generateNode(PS, State, Pred);
706783
} else {
707-
const Expr *Init = BMI->getInit()->IgnoreImplicit();
708784
const ValueDecl *Field;
709785
if (BMI->isIndirectMemberInitializer()) {
710786
Field = BMI->getIndirectMember();
@@ -738,15 +814,12 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init,
738814
InitVal = State->getSVal(BMI->getInit(), stackFrame);
739815
}
740816

741-
assert(Tmp.size() == 1 && "have not generated any new nodes yet");
742-
assert(*Tmp.begin() == Pred && "have not generated any new nodes yet");
743-
Tmp.clear();
744-
745817
PostInitializer PP(BMI, FieldLoc.getAsRegion(), stackFrame);
746818
evalBind(Tmp, Init, Pred, FieldLoc, InitVal, /*isInit=*/true, &PP);
747819
}
748820
} else {
749821
assert(BMI->isBaseInitializer() || BMI->isDelegatingInitializer());
822+
Tmp.insert(Pred);
750823
// We already did all the work when visiting the CXXConstructExpr.
751824
}
752825

@@ -755,8 +828,10 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init,
755828
PostInitializer PP(BMI, FieldLoc.getAsRegion(), stackFrame);
756829
ExplodedNodeSet Dst;
757830
NodeBuilder Bldr(Tmp, Dst, *currBldrCtx);
758-
for (const auto I : Tmp)
759-
Bldr.generateNode(PP, I->getState(), I);
831+
for (const auto I : Tmp) {
832+
ProgramStateRef State = I->getState();
833+
Bldr.generateNode(PP, State, I);
834+
}
760835

761836
// Enqueue the new nodes onto the work list.
762837
Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx);
@@ -2119,11 +2194,11 @@ void ExprEngine::processEndOfFunction(NodeBuilderContext& BC,
21192194
while (LC != ToLC) {
21202195
assert(LC && "ToLC must be a parent of FromLC!");
21212196
for (auto I : State->get<ObjectsUnderConstruction>())
2122-
if (I.first.second == LC) {
2197+
if (I.first.getLocationContext() == LC) {
21232198
// The comment above only pardons us for not cleaning up a
21242199
// CXXBindTemporaryExpr. If any other statements are found here,
21252200
// it must be a separate problem.
2126-
assert(isa<CXXBindTemporaryExpr>(I.first.first));
2201+
assert(isa<CXXBindTemporaryExpr>(I.first.getStmt()));
21272202
State = State->remove<ObjectsUnderConstruction>(I.first);
21282203
}
21292204

clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
153153
QualType Ty = Field->getType();
154154
FieldVal = makeZeroElementRegion(State, FieldVal, Ty,
155155
CallOpts.IsArrayCtorOrDtor);
156+
State = addObjectUnderConstruction(State, Init, LCtx, FieldVal);
156157
return std::make_pair(State, FieldVal);
157158
}
158159
case ConstructionContext::NewAllocatedObjectKind: {
@@ -272,35 +273,6 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
272273
State, loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx)));
273274
}
274275

275-
const CXXConstructExpr *
276-
ExprEngine::findDirectConstructorForCurrentCFGElement() {
277-
// Go backward in the CFG to see if the previous element (ignoring
278-
// destructors) was a CXXConstructExpr. If so, that constructor
279-
// was constructed directly into an existing region.
280-
// This process is essentially the inverse of that performed in
281-
// findElementDirectlyInitializedByCurrentConstructor().
282-
if (currStmtIdx == 0)
283-
return nullptr;
284-
285-
const CFGBlock *B = getBuilderContext().getBlock();
286-
287-
unsigned int PreviousStmtIdx = currStmtIdx - 1;
288-
CFGElement Previous = (*B)[PreviousStmtIdx];
289-
290-
while (Previous.getAs<CFGImplicitDtor>() && PreviousStmtIdx > 0) {
291-
--PreviousStmtIdx;
292-
Previous = (*B)[PreviousStmtIdx];
293-
}
294-
295-
if (Optional<CFGStmt> PrevStmtElem = Previous.getAs<CFGStmt>()) {
296-
if (auto *CtorExpr = dyn_cast<CXXConstructExpr>(PrevStmtElem->getStmt())) {
297-
return CtorExpr;
298-
}
299-
}
300-
301-
return nullptr;
302-
}
303-
304276
void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
305277
ExplodedNode *Pred,
306278
ExplodedNodeSet &destNodes) {

clang/test/Analysis/cxx17-mandatory-elision.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,37 @@ struct B {
2121
} // namespace variable_functional_cast_crash
2222

2323

24+
namespace ctor_initializer {
25+
26+
struct S {
27+
int x, y, z;
28+
};
29+
30+
struct T {
31+
S s;
32+
int w;
33+
T(int w): s(), w(w) {}
34+
};
35+
36+
class C {
37+
T t;
38+
public:
39+
C() : t(T(4)) {
40+
S s = {1, 2, 3};
41+
t.s = s;
42+
// FIXME: Should be TRUE in C++11 as well.
43+
clang_analyzer_eval(t.w == 4);
44+
#if __cplusplus >= 201703L
45+
// expected-warning@-2{{TRUE}}
46+
#else
47+
// expected-warning@-4{{UNKNOWN}}
48+
#endif
49+
}
50+
};
51+
52+
} // namespace ctor_initializer
53+
54+
2455
namespace address_vector_tests {
2556

2657
template <typename T> struct AddressVector {

0 commit comments

Comments
 (0)