forked from OSchip/llvm-project
Add initial version of checker to check if virtual member functions are called transitively
from C++ constructors or destructors. Checker by Lei Zhang with a few tweaks by Ted Kremenek. llvm-svn: 147494
This commit is contained in:
parent
31640e1606
commit
b3512d3a48
|
@ -62,6 +62,7 @@ add_clang_library(clangStaticAnalyzerCheckers
|
|||
UnixAPIChecker.cpp
|
||||
UnreachableCodeChecker.cpp
|
||||
VLASizeChecker.cpp
|
||||
VirtualCallChecker.cpp
|
||||
)
|
||||
|
||||
add_dependencies(clangStaticAnalyzerCheckers
|
||||
|
|
|
@ -164,6 +164,10 @@ def IteratorsChecker : Checker<"Iterators">,
|
|||
HelpText<"Check improper uses of STL vector iterators">,
|
||||
DescFile<"IteratorsChecker.cpp">;
|
||||
|
||||
def VirtualCallChecker : Checker<"VirtualCall">,
|
||||
HelpText<"Check virtual function calls during construction or destruction">,
|
||||
DescFile<"VirtualCallChecker.cpp">;
|
||||
|
||||
} // end: "cplusplus.experimental"
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
//=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file defines a checker that checks virtual function calls during
|
||||
// construction or destruction of C++ objects.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ClangSACheckers.h"
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
#include "clang/AST/StmtVisitor.h"
|
||||
#include "clang/Analysis/Support/SaveAndRestore.h"
|
||||
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
|
||||
#include "clang/StaticAnalyzer/Core/Checker.h"
|
||||
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace ento;
|
||||
|
||||
namespace {
|
||||
|
||||
class WalkAST : public StmtVisitor<WalkAST> {
|
||||
BugReporter &BR;
|
||||
AnalysisDeclContext *AC;
|
||||
|
||||
typedef const CallExpr * WorkListUnit;
|
||||
typedef SmallVector<WorkListUnit, 20> DFSWorkList;
|
||||
|
||||
/// A vector representing the worklist which has a chain of CallExprs.
|
||||
DFSWorkList WList;
|
||||
|
||||
// PreVisited : A CallExpr to this FunctionDecl is in the worklist, but the
|
||||
// body has not been visited yet.
|
||||
// PostVisited : A CallExpr to this FunctionDecl is in the worklist, and the
|
||||
// body has been visited.
|
||||
enum Kind { NotVisited,
|
||||
PreVisited, /**< A CallExpr to this FunctionDecl is in the
|
||||
worklist, but the body has not yet been
|
||||
visited. */
|
||||
PostVisited /**< A CallExpr to this FunctionDecl is in the
|
||||
worklist, and the body has been visited. */
|
||||
} K;
|
||||
|
||||
/// A DenseMap that records visited states of FunctionDecls.
|
||||
llvm::DenseMap<const FunctionDecl *, Kind> VisitedFunctions;
|
||||
|
||||
/// The CallExpr whose body is currently being visited. This is used for
|
||||
/// generating bug reports. This is null while visiting the body of a
|
||||
/// constructor or destructor.
|
||||
const CallExpr *visitingCallExpr;
|
||||
|
||||
public:
|
||||
WalkAST(BugReporter &br, AnalysisDeclContext *ac)
|
||||
: BR(br),
|
||||
AC(ac),
|
||||
visitingCallExpr(0) {}
|
||||
|
||||
bool hasWork() const { return !WList.empty(); }
|
||||
|
||||
/// This method adds a CallExpr to the worklist and marks the callee as
|
||||
/// being PreVisited.
|
||||
void Enqueue(WorkListUnit WLUnit) {
|
||||
const FunctionDecl *FD = WLUnit->getDirectCallee();
|
||||
if (!FD || !FD->getBody())
|
||||
return;
|
||||
Kind &K = VisitedFunctions[FD];
|
||||
if (K != NotVisited)
|
||||
return;
|
||||
K = PreVisited;
|
||||
WList.push_back(WLUnit);
|
||||
}
|
||||
|
||||
/// This method returns an item from the worklist without removing it.
|
||||
WorkListUnit Dequeue() {
|
||||
assert(!WList.empty());
|
||||
return WList.back();
|
||||
}
|
||||
|
||||
void Execute() {
|
||||
while (hasWork()) {
|
||||
WorkListUnit WLUnit = Dequeue();
|
||||
const FunctionDecl *FD = WLUnit->getDirectCallee();
|
||||
assert(FD && FD->getBody());
|
||||
|
||||
if (VisitedFunctions[FD] == PreVisited) {
|
||||
// If the callee is PreVisited, walk its body.
|
||||
// Visit the body.
|
||||
SaveAndRestore<const CallExpr *> SaveCall(visitingCallExpr, WLUnit);
|
||||
Visit(FD->getBody());
|
||||
|
||||
// Mark the function as being PostVisited to indicate we have
|
||||
// scanned the body.
|
||||
VisitedFunctions[FD] = PostVisited;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, the callee is PostVisited.
|
||||
// Remove it from the worklist.
|
||||
assert(VisitedFunctions[FD] == PostVisited);
|
||||
WList.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Stmt visitor methods.
|
||||
void VisitCallExpr(CallExpr *CE);
|
||||
void VisitCXXMemberCallExpr(CallExpr *CE);
|
||||
void VisitStmt(Stmt *S) { VisitChildren(S); }
|
||||
void VisitChildren(Stmt *S);
|
||||
|
||||
void ReportVirtualCall(const CallExpr *CE, bool isPure);
|
||||
|
||||
};
|
||||
} // end anonymous namespace
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// AST walking.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
void WalkAST::VisitChildren(Stmt *S) {
|
||||
for (Stmt::child_iterator I = S->child_begin(), E = S->child_end(); I!=E; ++I)
|
||||
if (Stmt *child = *I)
|
||||
Visit(child);
|
||||
}
|
||||
|
||||
void WalkAST::VisitCallExpr(CallExpr *CE) {
|
||||
VisitChildren(CE);
|
||||
Enqueue(CE);
|
||||
}
|
||||
|
||||
void WalkAST::VisitCXXMemberCallExpr(CallExpr *CE) {
|
||||
VisitChildren(CE);
|
||||
bool callIsNonVirtual = false;
|
||||
|
||||
// Several situations to elide for checking.
|
||||
if (MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
|
||||
// If the member access is fully qualified (i.e., X::F), then treat
|
||||
// this as a non-virtual call and do not warn.
|
||||
if (CME->getQualifier())
|
||||
callIsNonVirtual = true;
|
||||
|
||||
// Elide analyzing the call entirely if the base pointer is not 'this'.
|
||||
if (Expr *base = CME->getBase()->IgnoreImpCasts())
|
||||
if (!isa<CXXThisExpr>(base))
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the callee.
|
||||
const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(CE->getDirectCallee());
|
||||
if (MD && MD->isVirtual() && !callIsNonVirtual)
|
||||
ReportVirtualCall(CE, MD->isPure());
|
||||
|
||||
Enqueue(CE);
|
||||
}
|
||||
|
||||
void WalkAST::ReportVirtualCall(const CallExpr *CE, bool isPure) {
|
||||
llvm::SmallString<100> buf;
|
||||
llvm::raw_svector_ostream os(buf);
|
||||
|
||||
os << "Call Path : ";
|
||||
// Name of current visiting CallExpr.
|
||||
os << CE->getDirectCallee()->getNameAsString();
|
||||
|
||||
// Name of the CallExpr whose body is current walking.
|
||||
if (visitingCallExpr)
|
||||
os << " <-- " << visitingCallExpr->getDirectCallee()->getNameAsString();
|
||||
// Names of FunctionDecls in worklist with state PostVisited.
|
||||
for (SmallVectorImpl<const CallExpr *>::iterator I = WList.end(),
|
||||
E = WList.begin(); I != E; --I) {
|
||||
const FunctionDecl *FD = (*(I-1))->getDirectCallee();
|
||||
assert(FD);
|
||||
if (VisitedFunctions[FD] == PostVisited)
|
||||
os << " <-- " << FD->getNameAsString();
|
||||
}
|
||||
|
||||
PathDiagnosticLocation CELoc =
|
||||
PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);
|
||||
SourceRange R = CE->getCallee()->getSourceRange();
|
||||
|
||||
if (isPure) {
|
||||
os << "\n" << "Call pure virtual functions during construction or "
|
||||
<< "destruction may leads undefined behaviour";
|
||||
BR.EmitBasicReport("Call pure virtual function during construction or "
|
||||
"Destruction",
|
||||
"Cplusplus",
|
||||
os.str(), CELoc, &R, 1);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
os << "\n" << "Call virtual functions during construction or "
|
||||
<< "destruction will never go to a more derived class";
|
||||
BR.EmitBasicReport("Call virtual function during construction or "
|
||||
"Destruction",
|
||||
"Cplusplus",
|
||||
os.str(), CELoc, &R, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VirtualCallChecker
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
class VirtualCallChecker : public Checker<check::ASTDecl<CXXRecordDecl> > {
|
||||
public:
|
||||
void checkASTDecl(const CXXRecordDecl *RD, AnalysisManager& mgr,
|
||||
BugReporter &BR) const {
|
||||
WalkAST walker(BR, mgr.getAnalysisDeclContext(RD));
|
||||
|
||||
// Check the constructors.
|
||||
for (CXXRecordDecl::ctor_iterator I = RD->ctor_begin(), E = RD->ctor_end();
|
||||
I != E; ++I) {
|
||||
if (!I->isCopyOrMoveConstructor())
|
||||
if (Stmt *Body = I->getBody()) {
|
||||
walker.Visit(Body);
|
||||
walker.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
// Check the destructor.
|
||||
if (CXXDestructorDecl *DD = RD->getDestructor())
|
||||
if (Stmt *Body = DD->getBody()) {
|
||||
walker.Visit(Body);
|
||||
walker.Execute();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void ento::registerVirtualCallChecker(CheckerManager &mgr) {
|
||||
mgr.registerChecker<VirtualCallChecker>();
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// RUN: %clang_cc1 -analyze -analyzer-checker=experimental.cplusplus.VirtualCall -analyzer-store region -verify %s
|
||||
|
||||
class A {
|
||||
public:
|
||||
A();
|
||||
~A() {};
|
||||
|
||||
virtual int foo() = 0;
|
||||
virtual void bar() = 0;
|
||||
void f() {
|
||||
foo(); // expected-warning{{Call pure virtual functions during construction or destruction may leads undefined behaviour}}
|
||||
}
|
||||
};
|
||||
|
||||
class B : public A {
|
||||
public:
|
||||
B() {
|
||||
foo(); // expected-warning{{Call virtual functions during construction or destruction will never go to a more derived class}}
|
||||
}
|
||||
~B();
|
||||
|
||||
virtual int foo();
|
||||
virtual void bar() { foo(); } // expected-warning{{Call virtual functions during construction or destruction will never go to a more derived class}}
|
||||
};
|
||||
|
||||
A::A() {
|
||||
f();
|
||||
}
|
||||
|
||||
B::~B() {
|
||||
this->B::foo(); // no-warning
|
||||
this->B::bar();
|
||||
this->foo(); // expected-warning{{Call virtual functions during construction or destruction will never go to a more derived class}}
|
||||
}
|
||||
|
||||
class C : public B {
|
||||
public:
|
||||
C();
|
||||
~C();
|
||||
|
||||
virtual int foo();
|
||||
void f(int i);
|
||||
};
|
||||
|
||||
C::C() {
|
||||
f(foo()); // expected-warning{{Call virtual functions during construction or destruction will never go to a more derived class}}
|
||||
}
|
||||
|
||||
int main() {
|
||||
A *a;
|
||||
B *b;
|
||||
C *c;
|
||||
}
|
Loading…
Reference in New Issue