Skip to content

Commit ffba750

Browse files
committedDec 15, 2018
[analyzer] MoveChecker: Add checks for dereferencing a smart pointer after move.
Calling operator*() or operator->() on a null STL smart pointer is undefined behavior. Smart pointers are specified to become null after being moved from. So we can't warn on arbitrary method calls, but these two operators definitely make no sense. The new bug is fatal because it's an immediate UB, unlike other use-after-move bugs. The work on a more generic null smart pointer dereference checker is still pending. Differential Revision: https://reviews.llvm.org/D55388 llvm-svn: 349226
1 parent b5b974e commit ffba750

File tree

2 files changed

+178
-56
lines changed

2 files changed

+178
-56
lines changed
 

‎clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp

+149-51
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,35 @@ class MoveChecker
6161
const char *NL, const char *Sep) const override;
6262

6363
private:
64-
enum MisuseKind { MK_FunCall, MK_Copy, MK_Move };
64+
enum MisuseKind { MK_FunCall, MK_Copy, MK_Move, MK_Dereference };
65+
enum StdObjectKind { SK_NonStd, SK_Unsafe, SK_Safe, SK_SmartPtr };
66+
67+
static bool misuseCausesCrash(MisuseKind MK) {
68+
return MK == MK_Dereference;
69+
}
6570

6671
struct ObjectKind {
67-
bool Local : 1; // Is this a local variable or a local rvalue reference?
68-
bool STL : 1; // Is this an object of a standard type?
72+
// Is this a local variable or a local rvalue reference?
73+
bool IsLocal : 1;
74+
// Is this an STL object? If so, of what kind?
75+
StdObjectKind StdKind : 2;
76+
};
77+
78+
// STL smart pointers are automatically re-initialized to null when moved
79+
// from. So we can't warn on many methods, but we can warn when it is
80+
// dereferenced, which is UB even if the resulting lvalue never gets read.
81+
const llvm::StringSet<> StdSmartPtrClasses = {
82+
"shared_ptr",
83+
"unique_ptr",
84+
"weak_ptr",
6985
};
7086

7187
// Not all of these are entirely move-safe, but they do provide *some*
7288
// guarantees, and it means that somebody is using them after move
7389
// in a valid manner.
74-
// TODO: We can still try to identify *unsafe* use after move, such as
75-
// dereference of a moved-from smart pointer (which is guaranteed to be null).
76-
const llvm::StringSet<> StandardMoveSafeClasses = {
90+
// TODO: We can still try to identify *unsafe* use after move,
91+
// like we did with smart pointers.
92+
const llvm::StringSet<> StdSafeClasses = {
7793
"basic_filebuf",
7894
"basic_ios",
7995
"future",
@@ -82,11 +98,8 @@ class MoveChecker
8298
"promise",
8399
"shared_future",
84100
"shared_lock",
85-
"shared_ptr",
86101
"thread",
87-
"unique_ptr",
88102
"unique_lock",
89-
"weak_ptr",
90103
};
91104

92105
// Should we bother tracking the state of the object?
@@ -97,28 +110,43 @@ class MoveChecker
97110
// their user to re-use the storage. The latter is possible because STL
98111
// objects are known to end up in a valid but unspecified state after the
99112
// move and their state-reset methods are also known, which allows us to
100-
// predict precisely when use-after-move is invalid. In aggressive mode,
101-
// warn on any use-after-move because the user has intentionally asked us
102-
// to completely eliminate use-after-move in his code.
103-
return IsAggressive || OK.Local || OK.STL;
113+
// predict precisely when use-after-move is invalid.
114+
// Some STL objects are known to conform to additional contracts after move,
115+
// so they are not tracked. However, smart pointers specifically are tracked
116+
// because we can perform extra checking over them.
117+
// In aggressive mode, warn on any use-after-move because the user has
118+
// intentionally asked us to completely eliminate use-after-move
119+
// in his code.
120+
return IsAggressive || OK.IsLocal
121+
|| OK.StdKind == SK_Unsafe || OK.StdKind == SK_SmartPtr;
122+
}
123+
124+
// Some objects only suffer from some kinds of misuses, but we need to track
125+
// them anyway because we cannot know in advance what misuse will we find.
126+
bool shouldWarnAbout(ObjectKind OK, MisuseKind MK) const {
127+
// Additionally, only warn on smart pointers when they are dereferenced (or
128+
// local or we are aggressive).
129+
return shouldBeTracked(OK) &&
130+
(IsAggressive || OK.IsLocal
131+
|| OK.StdKind != SK_SmartPtr || MK == MK_Dereference);
104132
}
105133

106134
// Obtains ObjectKind of an object. Because class declaration cannot always
107135
// be easily obtained from the memory region, it is supplied separately.
108136
ObjectKind classifyObject(const MemRegion *MR, const CXXRecordDecl *RD) const;
109137

110138
// Classifies the object and dumps a user-friendly description string to
111-
// the stream. Return value is equivalent to classifyObject.
112-
ObjectKind explainObject(llvm::raw_ostream &OS,
113-
const MemRegion *MR, const CXXRecordDecl *RD) const;
139+
// the stream.
140+
void explainObject(llvm::raw_ostream &OS, const MemRegion *MR,
141+
const CXXRecordDecl *RD, MisuseKind MK) const;
114142

115-
bool isStandardMoveSafeClass(const CXXRecordDecl *RD) const;
143+
bool belongsTo(const CXXRecordDecl *RD, const llvm::StringSet<> &Set) const;
116144

117145
class MovedBugVisitor : public BugReporterVisitor {
118146
public:
119-
MovedBugVisitor(const MoveChecker &Chk,
120-
const MemRegion *R, const CXXRecordDecl *RD)
121-
: Chk(Chk), Region(R), RD(RD), Found(false) {}
147+
MovedBugVisitor(const MoveChecker &Chk, const MemRegion *R,
148+
const CXXRecordDecl *RD, MisuseKind MK)
149+
: Chk(Chk), Region(R), RD(RD), MK(MK), Found(false) {}
122150

123151
void Profile(llvm::FoldingSetNodeID &ID) const override {
124152
static int X = 0;
@@ -140,6 +168,8 @@ class MoveChecker
140168
const MemRegion *Region;
141169
// The class of the tracked object.
142170
const CXXRecordDecl *RD;
171+
// How exactly the object was misused.
172+
const MisuseKind MK;
143173
bool Found;
144174
};
145175

@@ -232,18 +262,37 @@ MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N,
232262
SmallString<128> Str;
233263
llvm::raw_svector_ostream OS(Str);
234264

235-
OS << "Object";
236-
ObjectKind OK = Chk.explainObject(OS, Region, RD);
237-
if (OK.STL)
238-
OS << " is left in a valid but unspecified state after move";
239-
else
240-
OS << " is moved";
265+
ObjectKind OK = Chk.classifyObject(Region, RD);
266+
switch (OK.StdKind) {
267+
case SK_SmartPtr:
268+
if (MK == MK_Dereference) {
269+
OS << "Smart pointer";
270+
Chk.explainObject(OS, Region, RD, MK);
271+
OS << " is reset to null when moved from";
272+
break;
273+
}
274+
275+
// If it's not a dereference, we don't care if it was reset to null
276+
// or that it is even a smart pointer.
277+
LLVM_FALLTHROUGH;
278+
case SK_NonStd:
279+
case SK_Safe:
280+
OS << "Object";
281+
Chk.explainObject(OS, Region, RD, MK);
282+
OS << " is moved";
283+
break;
284+
case SK_Unsafe:
285+
OS << "Object";
286+
Chk.explainObject(OS, Region, RD, MK);
287+
OS << " is left in a valid but unspecified state after move";
288+
break;
289+
}
241290

242291
// Generate the extra diagnostic.
243292
PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
244293
N->getLocationContext());
245294
return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
246-
}
295+
}
247296

248297
const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N,
249298
const MemRegion *Region,
@@ -267,25 +316,48 @@ void MoveChecker::modelUse(ProgramStateRef State, const MemRegion *Region,
267316
CheckerContext &C) const {
268317
assert(!C.isDifferent() && "No transitions should have been made by now");
269318
const RegionState *RS = State->get<TrackedRegionMap>(Region);
319+
ObjectKind OK = classifyObject(Region, RD);
320+
321+
// Just in case: if it's not a smart pointer but it does have operator *,
322+
// we shouldn't call the bug a dereference.
323+
if (MK == MK_Dereference && OK.StdKind != SK_SmartPtr)
324+
MK = MK_FunCall;
270325

271-
if (!RS || isAnyBaseRegionReported(State, Region)
326+
if (!RS || !shouldWarnAbout(OK, MK)
272327
|| isInMoveSafeContext(C.getLocationContext())) {
273328
// Finalize changes made by the caller.
274329
C.addTransition(State);
275330
return;
276331
}
277332

333+
// Don't report it in case if any base region is already reported.
334+
// But still generate a sink in case of UB.
335+
// And still finalize changes made by the caller.
336+
if (isAnyBaseRegionReported(State, Region)) {
337+
if (misuseCausesCrash(MK)) {
338+
C.generateSink(State, C.getPredecessor());
339+
} else {
340+
C.addTransition(State);
341+
}
342+
return;
343+
}
344+
278345
ExplodedNode *N = reportBug(Region, RD, C, MK);
279346

347+
// If the program has already crashed on this path, don't bother.
348+
if (N->isSink())
349+
return;
350+
280351
State = State->set<TrackedRegionMap>(Region, RegionState::getReported());
281352
C.addTransition(State, N);
282353
}
283354

284355
ExplodedNode *MoveChecker::reportBug(const MemRegion *Region,
285-
const CXXRecordDecl *RD,
286-
CheckerContext &C,
356+
const CXXRecordDecl *RD, CheckerContext &C,
287357
MisuseKind MK) const {
288-
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
358+
if (ExplodedNode *N = misuseCausesCrash(MK) ? C.generateErrorNode()
359+
: C.generateNonFatalErrorNode()) {
360+
289361
if (!BT)
290362
BT.reset(new BugType(this, "Use-after-move",
291363
"C++ move semantics"));
@@ -304,24 +376,28 @@ ExplodedNode *MoveChecker::reportBug(const MemRegion *Region,
304376
switch(MK) {
305377
case MK_FunCall:
306378
OS << "Method called on moved-from object";
307-
explainObject(OS, Region, RD);
379+
explainObject(OS, Region, RD, MK);
308380
break;
309381
case MK_Copy:
310382
OS << "Moved-from object";
311-
explainObject(OS, Region, RD);
383+
explainObject(OS, Region, RD, MK);
312384
OS << " is copied";
313385
break;
314386
case MK_Move:
315387
OS << "Moved-from object";
316-
explainObject(OS, Region, RD);
388+
explainObject(OS, Region, RD, MK);
317389
OS << " is moved";
318390
break;
391+
case MK_Dereference:
392+
OS << "Dereference of null smart pointer";
393+
explainObject(OS, Region, RD, MK);
394+
break;
319395
}
320396

321397
auto R =
322398
llvm::make_unique<BugReport>(*BT, OS.str(), N, LocUsedForUniqueing,
323399
MoveNode->getLocationContext()->getDecl());
324-
R->addVisitor(llvm::make_unique<MovedBugVisitor>(*this, Region, RD));
400+
R->addVisitor(llvm::make_unique<MovedBugVisitor>(*this, Region, RD, MK));
325401
C.emitReport(std::move(R));
326402
return N;
327403
}
@@ -433,9 +509,10 @@ bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const {
433509
return false;
434510
}
435511

436-
bool MoveChecker::isStandardMoveSafeClass(const CXXRecordDecl *RD) const {
512+
bool MoveChecker::belongsTo(const CXXRecordDecl *RD,
513+
const llvm::StringSet<> &Set) const {
437514
const IdentifierInfo *II = RD->getIdentifier();
438-
return II && StandardMoveSafeClasses.count(II->getName());
515+
return II && Set.count(II->getName());
439516
}
440517

441518
MoveChecker::ObjectKind
@@ -445,30 +522,46 @@ MoveChecker::classifyObject(const MemRegion *MR,
445522
// For the purposes of this checker, we classify move-safe STL types
446523
// as not-"STL" types, because that's how the checker treats them.
447524
MR = unwrapRValueReferenceIndirection(MR);
448-
return {
449-
/*Local=*/
450-
MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace()),
451-
/*STL=*/
452-
RD && RD->getDeclContext()->isStdNamespace() &&
453-
!isStandardMoveSafeClass(RD)
454-
};
525+
bool IsLocal =
526+
MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace());
527+
528+
if (!RD || !RD->getDeclContext()->isStdNamespace())
529+
return { IsLocal, SK_NonStd };
530+
531+
if (belongsTo(RD, StdSmartPtrClasses))
532+
return { IsLocal, SK_SmartPtr };
533+
534+
if (belongsTo(RD, StdSafeClasses))
535+
return { IsLocal, SK_Safe };
536+
537+
return { IsLocal, SK_Unsafe };
455538
}
456539

457-
MoveChecker::ObjectKind
458-
MoveChecker::explainObject(llvm::raw_ostream &OS, const MemRegion *MR,
459-
const CXXRecordDecl *RD) const {
540+
void MoveChecker::explainObject(llvm::raw_ostream &OS, const MemRegion *MR,
541+
const CXXRecordDecl *RD, MisuseKind MK) const {
460542
// We may need a leading space every time we actually explain anything,
461543
// and we never know if we are to explain anything until we try.
462544
if (const auto DR =
463545
dyn_cast_or_null<DeclRegion>(unwrapRValueReferenceIndirection(MR))) {
464546
const auto *RegionDecl = cast<NamedDecl>(DR->getDecl());
465547
OS << " '" << RegionDecl->getNameAsString() << "'";
466548
}
549+
467550
ObjectKind OK = classifyObject(MR, RD);
468-
if (OK.STL) {
469-
OS << " of type '" << RD->getQualifiedNameAsString() << "'";
470-
}
471-
return OK;
551+
switch (OK.StdKind) {
552+
case SK_NonStd:
553+
case SK_Safe:
554+
break;
555+
case SK_SmartPtr:
556+
if (MK != MK_Dereference)
557+
break;
558+
559+
// We only care about the type if it's a dereference.
560+
LLVM_FALLTHROUGH;
561+
case SK_Unsafe:
562+
OS << " of type '" << RD->getQualifiedNameAsString() << "'";
563+
break;
564+
};
472565
}
473566

474567
void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
@@ -543,6 +636,11 @@ void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
543636
C.addTransition(State);
544637
return;
545638
}
639+
640+
if (OOK == OO_Star || OOK == OO_Arrow) {
641+
modelUse(State, ThisRegion, RD, MK_Dereference, C);
642+
return;
643+
}
546644
}
547645

548646
modelUse(State, ThisRegion, RD, MK_FunCall, C);

‎clang/test/Analysis/use-after-move.cpp

+29-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.Move -verify %s\
22
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
3-
// RUN: -analyzer-config exploration_strategy=unexplored_first_queue
3+
// RUN: -analyzer-config exploration_strategy=unexplored_first_queue\
4+
// RUN: -analyzer-checker debug.ExprInspection
45
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.Move -verify %s\
56
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
6-
// RUN: -analyzer-config exploration_strategy=dfs -DDFS=1
7+
// RUN: -analyzer-config exploration_strategy=dfs -DDFS=1\
8+
// RUN: -analyzer-checker debug.ExprInspection
79
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.Move -verify %s\
810
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
911
// RUN: -analyzer-config exploration_strategy=unexplored_first_queue\
10-
// RUN: -analyzer-config alpha.cplusplus.Move:Aggressive=true -DAGGRESSIVE
12+
// RUN: -analyzer-config alpha.cplusplus.Move:Aggressive=true -DAGGRESSIVE\
13+
// RUN: -analyzer-checker debug.ExprInspection
1114
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.Move -verify %s\
1215
// RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\
1316
// RUN: -analyzer-config exploration_strategy=dfs -DDFS=1\
14-
// RUN: -analyzer-config alpha.cplusplus.Move:Aggressive=true -DAGGRESSIVE
17+
// RUN: -analyzer-config alpha.cplusplus.Move:Aggressive=true -DAGGRESSIVE\
18+
// RUN: -analyzer-checker debug.ExprInspection
1519

1620
#include "Inputs/system-header-simulator-cxx.h"
1721

22+
void clang_analyzer_warnIfReached();
23+
1824
class B {
1925
public:
2026
B() = default;
@@ -810,7 +816,19 @@ class HasSTLField {
810816
// expected-note@-4{{Object 'P' is moved}}
811817
// expected-note@-4{{Method called on moved-from object 'P'}}
812818
#endif
813-
*P += 1; // FIXME: Should warn that the pointer is null.
819+
820+
// Because that well-defined state is null, dereference is still UB.
821+
// Note that in aggressive mode we already warned about 'P',
822+
// so no extra warning is generated.
823+
*P += 1;
824+
#ifndef AGGRESSIVE
825+
// expected-warning@-2{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}}
826+
// expected-note@-14{{Smart pointer 'P' of type 'std::unique_ptr' is reset to null when moved from}}
827+
// expected-note@-4{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}}
828+
#endif
829+
830+
// The program should have crashed by now.
831+
clang_analyzer_warnIfReached(); // no-warning
814832
}
815833
};
816834

@@ -827,3 +845,9 @@ void localUniquePtr(std::unique_ptr<int> P) {
827845
P.get(); // expected-warning{{Method called on moved-from object 'P'}}
828846
// expected-note@-1{{Method called on moved-from object 'P'}}
829847
}
848+
849+
void localUniquePtrWithArrow(std::unique_ptr<A> P) {
850+
std::unique_ptr<A> Q = std::move(P); // expected-note{{Smart pointer 'P' of type 'std::unique_ptr' is reset to null when moved from}}
851+
P->foo(); // expected-warning{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}}
852+
// expected-note@-1{{Dereference of null smart pointer 'P' of type 'std::unique_ptr'}}
853+
}

0 commit comments

Comments
 (0)
Please sign in to comment.