Skip to content

Commit d3971fe

Browse files
committed
[analyzer] Improve VirtualCallChecker and enable parts of it by default.
Calling a pure virtual method during construction or destruction is undefined behavior. It's worth it to warn about it by default. That part is now known as the cplusplus.PureVirtualCall checker. Calling a normal virtual method during construction or destruction may be fine, but does behave unexpectedly, as it skips virtual dispatch. Do not warn about this by default, but let projects opt in into it by enabling the optin.cplusplus.VirtualCall checker manually. Give the two parts differentiated warning text: Before: Call to virtual function during construction or destruction: Call to pure virtual function during construction Call to virtual function during construction or destruction: Call to virtual function during destruction After: Pure virtual method call: Call to pure virtual method 'X::foo' during construction has undefined behavior Unexpected loss of virtual dispatch: Call to virtual method 'Y::bar' during construction bypasses virtual dispatch Also fix checker names in consumers that support them (eg., clang-tidy) because we now have different checker names for pure virtual calls and regular virtual calls. Also fix capitalization in the bug category. Differential Revision: https://reviews.llvm.org/D64274 llvm-svn: 369449
1 parent 7fa6865 commit d3971fe

File tree

8 files changed

+153
-260
lines changed

8 files changed

+153
-260
lines changed

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,15 @@ def MoveChecker: Checker<"Move">,
504504
]>,
505505
Documentation<HasDocumentation>;
506506

507+
def VirtualCallModeling : Checker<"VirtualCallModeling">,
508+
HelpText<"Auxiliary modeling for the virtual method call checkers">,
509+
Documentation<NotDocumented>,
510+
Hidden;
511+
512+
def PureVirtualCallChecker : Checker<"PureVirtualCall">,
513+
HelpText<"Check pure virtual function calls during construction/destruction">,
514+
Dependencies<[VirtualCallModeling]>,
515+
Documentation<HasDocumentation>;
507516
} // end: "cplusplus"
508517

509518
let ParentPackage = CplusplusOptIn in {
@@ -552,14 +561,17 @@ def UninitializedObjectChecker: Checker<"UninitializedObject">,
552561
Documentation<HasAlphaDocumentation>;
553562

554563
def VirtualCallChecker : Checker<"VirtualCall">,
555-
HelpText<"Check virtual function calls during construction or destruction">,
564+
HelpText<"Check virtual function calls during construction/destruction">,
556565
CheckerOptions<[
557566
CmdLineOption<Boolean,
558567
"PureOnly",
559-
"Whether to only report calls to pure virtual methods.",
568+
"Disables the checker. Keeps cplusplus.PureVirtualCall "
569+
"enabled. This option is only provided for backwards "
570+
"compatibility.",
560571
"false",
561-
Released>
572+
InAlpha>
562573
]>,
574+
Dependencies<[VirtualCallModeling]>,
563575
Documentation<HasDocumentation>;
564576

565577
} // end: "optin.cplusplus"

clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace clang {
1818
extern const char * const MemoryRefCount;
1919
extern const char * const MemoryError;
2020
extern const char * const UnixAPI;
21+
extern const char * const CXXObjectLifecycle;
2122
}
2223
}
2324
}

clang/include/clang/StaticAnalyzer/Core/CheckerManager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class CheckName {
105105
CheckName() = default;
106106

107107
StringRef getName() const { return Name; }
108+
operator StringRef() const { return Name; }
108109
};
109110

110111
enum class ObjCMessageVisitKind {

clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp

Lines changed: 61 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88
//
9-
// This file defines a checker that checks virtual function calls during
9+
// This file defines a checker that checks virtual method calls during
1010
// construction or destruction of C++ objects.
1111
//
1212
//===----------------------------------------------------------------------===//
@@ -40,11 +40,9 @@ template <> struct FoldingSetTrait<ObjectState> {
4040
namespace {
4141
class VirtualCallChecker
4242
: public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
43-
mutable std::unique_ptr<BugType> BT;
44-
4543
public:
46-
// The flag to determine if pure virtual functions should be issued only.
47-
DefaultBool IsPureOnly;
44+
// These are going to be null if the respective check is disabled.
45+
mutable std::unique_ptr<BugType> BT_Pure, BT_Impure;
4846

4947
void checkBeginFunction(CheckerContext &C) const;
5048
void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
@@ -53,85 +51,13 @@ class VirtualCallChecker
5351
private:
5452
void registerCtorDtorCallInState(bool IsBeginFunction,
5553
CheckerContext &C) const;
56-
void reportBug(StringRef Msg, bool PureError, const MemRegion *Reg,
57-
CheckerContext &C) const;
58-
59-
class VirtualBugVisitor : public BugReporterVisitor {
60-
private:
61-
const MemRegion *ObjectRegion;
62-
bool Found;
63-
64-
public:
65-
VirtualBugVisitor(const MemRegion *R) : ObjectRegion(R), Found(false) {}
66-
67-
void Profile(llvm::FoldingSetNodeID &ID) const override {
68-
static int X = 0;
69-
ID.AddPointer(&X);
70-
ID.AddPointer(ObjectRegion);
71-
}
72-
73-
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
74-
BugReporterContext &BRC,
75-
BugReport &BR) override;
76-
};
7754
};
7855
} // end namespace
7956

8057
// GDM (generic data map) to the memregion of this for the ctor and dtor.
8158
REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
8259

83-
PathDiagnosticPieceRef VirtualCallChecker::VirtualBugVisitor::VisitNode(
84-
const ExplodedNode *N, BugReporterContext &BRC, BugReport &) {
85-
// We need the last ctor/dtor which call the virtual function.
86-
// The visitor walks the ExplodedGraph backwards.
87-
if (Found)
88-
return nullptr;
89-
90-
ProgramStateRef State = N->getState();
91-
const LocationContext *LCtx = N->getLocationContext();
92-
const CXXConstructorDecl *CD =
93-
dyn_cast_or_null<CXXConstructorDecl>(LCtx->getDecl());
94-
const CXXDestructorDecl *DD =
95-
dyn_cast_or_null<CXXDestructorDecl>(LCtx->getDecl());
96-
97-
if (!CD && !DD)
98-
return nullptr;
99-
100-
ProgramStateManager &PSM = State->getStateManager();
101-
auto &SVB = PSM.getSValBuilder();
102-
const auto *MD = dyn_cast<CXXMethodDecl>(LCtx->getDecl());
103-
if (!MD)
104-
return nullptr;
105-
auto ThiSVal =
106-
State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
107-
const MemRegion *Reg = ThiSVal.castAs<loc::MemRegionVal>().getRegion();
108-
if (!Reg)
109-
return nullptr;
110-
if (Reg != ObjectRegion)
111-
return nullptr;
112-
113-
const Stmt *S = PathDiagnosticLocation::getStmt(N);
114-
if (!S)
115-
return nullptr;
116-
Found = true;
117-
118-
std::string InfoText;
119-
if (CD)
120-
InfoText = "This constructor of an object of type '" +
121-
CD->getNameAsString() +
122-
"' has not returned when the virtual method was called";
123-
else
124-
InfoText = "This destructor of an object of type '" +
125-
DD->getNameAsString() +
126-
"' has not returned when the virtual method was called";
127-
128-
// Generate the extra diagnostic.
129-
PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
130-
N->getLocationContext());
131-
return std::make_shared<PathDiagnosticEventPiece>(Pos, InfoText, true);
132-
}
133-
134-
// The function to check if a callexpr is a virtual function.
60+
// The function to check if a callexpr is a virtual method call.
13561
static bool isVirtualCall(const CallExpr *CE) {
13662
bool CallIsNonVirtual = false;
13763

@@ -176,41 +102,50 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call,
176102
const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
177103
if (!MD)
178104
return;
105+
179106
ProgramStateRef State = C.getState();
180107
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
181-
182-
if (IsPureOnly && !MD->isPure())
183-
return;
184108
if (!isVirtualCall(CE))
185109
return;
186110

187111
const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
188112
const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
189113
if (!ObState)
190114
return;
191-
// Check if a virtual method is called.
192-
// The GDM of constructor and destructor should be true.
193-
if (*ObState == ObjectState::CtorCalled) {
194-
if (IsPureOnly && MD->isPure())
195-
reportBug("Call to pure virtual function during construction", true, Reg,
196-
C);
197-
else if (!MD->isPure())
198-
reportBug("Call to virtual function during construction", false, Reg, C);
199-
else
200-
reportBug("Call to pure virtual function during construction", false, Reg,
201-
C);
202-
}
203115

204-
if (*ObState == ObjectState::DtorCalled) {
205-
if (IsPureOnly && MD->isPure())
206-
reportBug("Call to pure virtual function during destruction", true, Reg,
207-
C);
208-
else if (!MD->isPure())
209-
reportBug("Call to virtual function during destruction", false, Reg, C);
210-
else
211-
reportBug("Call to pure virtual function during construction", false, Reg,
212-
C);
116+
bool IsPure = MD->isPure();
117+
118+
// At this point we're sure that we're calling a virtual method
119+
// during construction or destruction, so we'll emit a report.
120+
SmallString<128> Msg;
121+
llvm::raw_svector_ostream OS(Msg);
122+
OS << "Call to ";
123+
if (IsPure)
124+
OS << "pure ";
125+
OS << "virtual method '" << MD->getParent()->getNameAsString()
126+
<< "::" << MD->getNameAsString() << "' during ";
127+
if (*ObState == ObjectState::CtorCalled)
128+
OS << "construction ";
129+
else
130+
OS << "destruction ";
131+
if (IsPure)
132+
OS << "has undefined behavior";
133+
else
134+
OS << "bypasses virtual dispatch";
135+
136+
ExplodedNode *N =
137+
IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
138+
if (!N)
139+
return;
140+
141+
const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
142+
if (!BT) {
143+
// The respective check is disabled.
144+
return;
213145
}
146+
147+
auto Report = std::make_unique<BugReport>(*BT, OS.str(), N);
148+
C.emitReport(std::move(Report));
214149
}
215150

216151
void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
@@ -252,34 +187,35 @@ void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
252187
}
253188
}
254189

255-
void VirtualCallChecker::reportBug(StringRef Msg, bool IsSink,
256-
const MemRegion *Reg,
257-
CheckerContext &C) const {
258-
ExplodedNode *N;
259-
if (IsSink)
260-
N = C.generateErrorNode();
261-
else
262-
N = C.generateNonFatalErrorNode();
190+
void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
191+
Mgr.registerChecker<VirtualCallChecker>();
192+
}
263193

264-
if (!N)
265-
return;
266-
if (!BT)
267-
BT.reset(new BugType(
268-
this, "Call to virtual function during construction or destruction",
269-
"C++ Object Lifecycle"));
270-
271-
auto Reporter = std::make_unique<BugReport>(*BT, Msg, N);
272-
Reporter->addVisitor(std::make_unique<VirtualBugVisitor>(Reg));
273-
C.emitReport(std::move(Reporter));
194+
void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
195+
auto *Chk = Mgr.getChecker<VirtualCallChecker>();
196+
Chk->BT_Pure = std::make_unique<BugType>(
197+
Mgr.getCurrentCheckName(), "Pure virtual method call",
198+
categories::CXXObjectLifecycle);
274199
}
275200

276-
void ento::registerVirtualCallChecker(CheckerManager &mgr) {
277-
VirtualCallChecker *checker = mgr.registerChecker<VirtualCallChecker>();
201+
void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
202+
auto *Chk = Mgr.getChecker<VirtualCallChecker>();
203+
if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
204+
Mgr.getCurrentCheckName(), "PureOnly")) {
205+
Chk->BT_Impure = std::make_unique<BugType>(
206+
Mgr.getCurrentCheckName(), "Unexpected loss of virtual dispatch",
207+
categories::CXXObjectLifecycle);
208+
}
209+
}
210+
211+
bool ento::shouldRegisterVirtualCallModeling(const LangOptions &LO) {
212+
return LO.CPlusPlus;
213+
}
278214

279-
checker->IsPureOnly =
280-
mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "PureOnly");
215+
bool ento::shouldRegisterPureVirtualCallChecker(const LangOptions &LO) {
216+
return LO.CPlusPlus;
281217
}
282218

283219
bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) {
284-
return true;
220+
return LO.CPlusPlus;
285221
}

clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ const char * const MemoryRefCount =
1717
"Memory (Core Foundation/Objective-C/OSObject)";
1818
const char * const MemoryError = "Memory error";
1919
const char * const UnixAPI = "Unix API";
20+
const char * const CXXObjectLifecycle = "C++ object lifecycle";
2021
}}}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus \
2+
// RUN: -analyzer-output=plist -o %t.plist -w -verify=pure %s
3+
// RUN: cat %t.plist | FileCheck --check-prefixes=PURE %s
4+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus \
5+
// RUN: -analyzer-output=plist -o %t.plist -w -verify=impure %s
6+
// RUN: cat %t.plist | FileCheck --check-prefixes=IMPURE %s
7+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus,optin.cplusplus \
8+
// RUN: -analyzer-output=plist -o %t.plist -w -verify=pure,impure %s
9+
// RUN: cat %t.plist | FileCheck --check-prefixes=PURE,IMPURE %s
10+
11+
struct S {
12+
virtual void foo();
13+
virtual void bar() = 0;
14+
15+
S() {
16+
// IMPURE: Call to virtual method &apos;S::foo&apos; during construction bypasses virtual dispatch
17+
// IMPURE: optin.cplusplus.VirtualCall
18+
foo(); // impure-warning{{Call to virtual method 'S::foo' during construction bypasses virtual dispatch}}
19+
// PURE: Call to pure virtual method &apos;S::bar&apos; during construction has undefined behavior
20+
// PURE: cplusplus.PureVirtualCall
21+
bar(); // pure-warning{{Call to pure virtual method 'S::bar' during construction has undefined behavior}}
22+
}
23+
};

0 commit comments

Comments
 (0)