Expand Up
@@ -14,279 +14,272 @@
#include " ClangSACheckers.h"
#include " clang/AST/DeclCXX.h"
#include " clang/AST/StmtVisitor.h"
#include " clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include " clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include " clang/StaticAnalyzer/Core/Checker.h"
#include " clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager .h"
#include " llvm/ADT/SmallString .h"
#include " llvm/Support/SaveAndRestore .h"
#include " llvm/Support/raw_ostream .h"
#include " clang/StaticAnalyzer/Core/PathSensitive/CallEvent .h"
#include " clang/StaticAnalyzer/Core/PathSensitive/CheckerContext .h"
#include " clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait .h"
#include " clang/StaticAnalyzer/Core/PathSensitive/SValBuilder .h"
using namespace clang ;
using namespace ento ;
namespace {
class WalkAST : public StmtVisitor <WalkAST> {
const CheckerBase *Checker;
BugReporter &BR;
AnalysisDeclContext *AC;
// / The root constructor or destructor whose callees are being analyzed.
const CXXMethodDecl *RootMethod = nullptr ;
// / Whether the checker should walk into bodies of called functions.
// / Controlled by the "Interprocedural" analyzer-config option.
bool IsInterprocedural = false ;
// / Whether the checker should only warn for calls to pure virtual functions
// / (which is undefined behavior) or for all virtual functions (which may
// / may result in unexpected behavior).
bool ReportPureOnly = false ;
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. */
};
// / 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 (const CheckerBase *checker, BugReporter &br, AnalysisDeclContext *ac,
const CXXMethodDecl *rootMethod, bool isInterprocedural,
bool reportPureOnly)
: Checker(checker), BR(br), AC(ac), RootMethod(rootMethod),
IsInterprocedural (isInterprocedural), ReportPureOnly(reportPureOnly),
visitingCallExpr(nullptr ) {
// Walking should always start from either a constructor or a destructor.
assert (isa<CXXConstructorDecl>(rootMethod) ||
isa<CXXDestructorDecl>(rootMethod));
}
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);
enum class ObjectState : bool { CtorCalled, DtorCalled };
} // end namespace
// FIXME: Ascending over StackFrameContext maybe another method.
namespace llvm {
template <> struct FoldingSetTrait <ObjectState> {
static inline void Profile (ObjectState X, FoldingSetNodeID &ID) {
ID.AddInteger (static_cast <int >(X));
}
};
} // end namespace llvm
// / This method returns an item from the worklist without removing it.
WorkListUnit Dequeue () {
assert (!WList.empty ());
return WList.back ();
}
namespace {
class VirtualCallChecker
: public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
mutable std::unique_ptr<BugType> BT;
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 ();
public:
// The flag to determine if pure virtual functions should be issued only.
DefaultBool IsPureOnly;
void checkBeginFunction (CheckerContext &C) const ;
void checkEndFunction (CheckerContext &C) const ;
void checkPreCall (const CallEvent &Call, CheckerContext &C) const ;
private:
void registerCtorDtorCallInState (bool IsBeginFunction,
CheckerContext &C) const ;
void reportBug (StringRef Msg, bool PureError, const MemRegion *Reg,
CheckerContext &C) const ;
class VirtualBugVisitor : public BugReporterVisitorImpl <VirtualBugVisitor> {
private:
const MemRegion *ObjectRegion;
bool Found;
public:
VirtualBugVisitor (const MemRegion *R) : ObjectRegion(R), Found(false ) {}
void Profile (llvm::FoldingSetNodeID &ID) const override {
static int X = 0 ;
ID.AddPointer (&X);
ID.AddPointer (ObjectRegion);
}
}
// 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);
std::shared_ptr<PathDiagnosticPiece> VisitNode (const ExplodedNode *N,
const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override ;
};
};
} // end anonymous namespace
// ===----------------------------------------------------------------------===//
// AST walking.
// ===----------------------------------------------------------------------===//
void WalkAST::VisitChildren (Stmt *S) {
for (Stmt *Child : S->children ())
if (Child)
Visit (Child);
}
void WalkAST::VisitCallExpr (CallExpr *CE) {
VisitChildren (CE);
if (IsInterprocedural)
Enqueue (CE);
} // end namespace
// GDM (generic data map) to the memregion of this for the ctor and dtor.
REGISTER_MAP_WITH_PROGRAMSTATE (CtorDtorMap, const MemRegion *, ObjectState)
std::shared_ptr<PathDiagnosticPiece>
VirtualCallChecker::VirtualBugVisitor::VisitNode(const ExplodedNode *N,
const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) {
// We need the last ctor/dtor which call the virtual function.
// The visitor walks the ExplodedGraph backwards.
if (Found)
return nullptr ;
ProgramStateRef State = N->getState ();
const LocationContext *LCtx = N->getLocationContext ();
const CXXConstructorDecl *CD =
dyn_cast_or_null<CXXConstructorDecl>(LCtx->getDecl ());
const CXXDestructorDecl *DD =
dyn_cast_or_null<CXXDestructorDecl>(LCtx->getDecl ());
if (!CD && !DD)
return nullptr ;
ProgramStateManager &PSM = State->getStateManager ();
auto &SVB = PSM.getSValBuilder ();
const auto *MD = dyn_cast<CXXMethodDecl>(LCtx->getDecl ());
if (!MD)
return nullptr ;
auto ThiSVal =
State->getSVal (SVB.getCXXThis (MD, LCtx->getCurrentStackFrame ()));
const MemRegion *Reg = ThiSVal.castAs <loc::MemRegionVal>().getRegion ();
if (!Reg)
return nullptr ;
if (Reg != ObjectRegion)
return nullptr ;
const Stmt *S = PathDiagnosticLocation::getStmt (N);
if (!S)
return nullptr ;
Found = true ;
std::string InfoText;
if (CD)
InfoText = " This constructor of an object of type '" +
CD->getNameAsString () +
" ' has not returned when the virtual method was called" ;
else
InfoText = " This destructor of an object of type '" +
DD->getNameAsString () +
" ' has not returned when the virtual method was called" ;
// Generate the extra diagnostic.
PathDiagnosticLocation Pos (S, BRC.getSourceManager (),
N->getLocationContext ());
return std::make_shared<PathDiagnosticEventPiece>(Pos, InfoText, true );
}
void WalkAST::VisitCXXMemberCallExpr (CallExpr *CE) {
VisitChildren ( CE);
bool callIsNonVirtual = false ;
// The function to check if a callexpr is a virtual function.
static bool isVirtualCall ( const CallExpr * 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 (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee ())) {
// The member access is fully qualified (i.e., X::F).
// Treat this as a non-virtual call and do not warn.
if (CME->getQualifier ())
callIsNonVirtual = true ;
CallIsNonVirtual = true ;
if (Expr *base = CME->getBase ()->IgnoreImpCasts ()) {
// Elide analyzing the call entirely if the base pointer is not 'this'.
if (!isa<CXXThisExpr>(base))
return ;
// If the most derived class is marked final, we know that now subclass
// can override this member.
if (base->getBestDynamicClassType ()->hasAttr <FinalAttr>())
callIsNonVirtual = true ;
if (const Expr *Base = CME->getBase ()->IgnoreImpCasts ()) {
// The most derived class is marked final.
if (Base->getBestDynamicClassType ()->hasAttr <FinalAttr>())
CallIsNonVirtual = true ;
}
}
// Get the callee.
const CXXMethodDecl *MD =
dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee ());
if (MD && MD->isVirtual () && !callIsNonVirtual && !MD->hasAttr <FinalAttr>() &&
if (MD && MD->isVirtual () && !CallIsNonVirtual && !MD->hasAttr <FinalAttr>() &&
!MD->getParent ()->hasAttr <FinalAttr>())
ReportVirtualCall (CE, MD->isPure ());
return true ;
return false ;
}
// The BeginFunction callback when enter a constructor or a destructor.
void VirtualCallChecker::checkBeginFunction (CheckerContext &C) const {
registerCtorDtorCallInState (true , C);
}
if (IsInterprocedural)
Enqueue (CE);
// The EndFunction callback when leave a constructor or a destructor.
void VirtualCallChecker::checkEndFunction (CheckerContext &C) const {
registerCtorDtorCallInState (false , C);
}
void WalkAST::ReportVirtualCall (const CallExpr *CE, bool isPure) {
if (ReportPureOnly && !isPure)
void VirtualCallChecker::checkPreCall (const CallEvent &Call,
CheckerContext &C) const {
const auto MC = dyn_cast<CXXMemberCall>(&Call);
if (!MC)
return ;
SmallString<100 > buf;
llvm::raw_svector_ostream os (buf);
// FIXME: The interprocedural diagnostic experience here is not good.
// Ultimately this checker should be re-written to be path sensitive.
// For now, only diagnose intraprocedurally, by default.
if (IsInterprocedural) {
os << " Call Path : " ;
// Name of current visiting CallExpr.
os << *CE->getDirectCallee ();
// Name of the CallExpr whose body is current being walked.
if (visitingCallExpr)
os << " <-- " << *visitingCallExpr->getDirectCallee ();
// 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;
}
const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl ());
if (!MD)
return ;
ProgramStateRef State = C.getState ();
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr ());
os << " \n " ;
if (IsPureOnly && !MD->isPure ())
return ;
if (!isVirtualCall (CE))
return ;
const MemRegion *Reg = MC->getCXXThisVal ().getAsRegion ();
const ObjectState *ObState = State->get <CtorDtorMap>(Reg);
if (!ObState)
return ;
// Check if a virtual method is called.
// The GDM of constructor and destructor should be true.
if (*ObState == ObjectState::CtorCalled) {
if (IsPureOnly && MD->isPure ())
reportBug (" Call to pure virtual function during construction" , true , Reg,
C);
else if (!MD->isPure ())
reportBug (" Call to virtual function during construction" , false , Reg, C);
else
reportBug (" Call to pure virtual function during construction" , false , Reg,
C);
}
PathDiagnosticLocation CELoc =
PathDiagnosticLocation::createBegin (CE, BR.getSourceManager (), AC);
SourceRange R = CE->getCallee ()->getSourceRange ();
if (*ObState == ObjectState::DtorCalled) {
if (IsPureOnly && MD->isPure ())
reportBug (" Call to pure virtual function during destruction" , true , Reg,
C);
else if (!MD->isPure ())
reportBug (" Call to virtual function during destruction" , false , Reg, C);
else
reportBug (" Call to pure virtual function during construction" , false , Reg,
C);
}
}
os << " Call to " ;
if (isPure)
os << " pure " ;
void VirtualCallChecker::registerCtorDtorCallInState (bool IsBeginFunction,
CheckerContext &C) const {
const auto *LCtx = C.getLocationContext ();
const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl ());
if (!MD)
return ;
os << " virtual function during " ;
ProgramStateRef State = C.getState ();
auto &SVB = C.getSValBuilder ();
if (isa<CXXConstructorDecl>(RootMethod))
os << " construction " ;
else
os << " destruction " ;
// Enter a constructor, set the corresponding memregion be true.
if (isa<CXXConstructorDecl>(MD)) {
auto ThiSVal =
State->getSVal (SVB.getCXXThis (MD, LCtx->getCurrentStackFrame ()));
const MemRegion *Reg = ThiSVal.getAsRegion ();
if (IsBeginFunction)
State = State->set <CtorDtorMap>(Reg, ObjectState::CtorCalled);
else
State = State->remove <CtorDtorMap>(Reg);
if (isPure)
os << " has undefined behavior" ;
else
os << " will not dispatch to derived class" ;
C.addTransition (State);
return ;
}
BR.EmitBasicReport (AC->getDecl (), Checker,
" Call to virtual function during construction or "
" destruction" ,
" C++ Object Lifecycle" , os.str (), CELoc, R);
// Enter a Destructor, set the corresponding memregion be true.
if (isa<CXXDestructorDecl>(MD)) {
auto ThiSVal =
State->getSVal (SVB.getCXXThis (MD, LCtx->getCurrentStackFrame ()));
const MemRegion *Reg = ThiSVal.getAsRegion ();
if (IsBeginFunction)
State = State->set <CtorDtorMap>(Reg, ObjectState::DtorCalled);
else
State = State->remove <CtorDtorMap>(Reg);
C.addTransition (State);
return ;
}
}
// ===----------------------------------------------------------------------===//
// VirtualCallChecker
// ===----------------------------------------------------------------------===//
namespace {
class VirtualCallChecker : public Checker <check::ASTDecl<CXXRecordDecl> > {
public:
DefaultBool isInterprocedural;
DefaultBool isPureOnly;
void checkASTDecl (const CXXRecordDecl *RD, AnalysisManager& mgr,
BugReporter &BR) const {
AnalysisDeclContext *ADC = mgr.getAnalysisDeclContext (RD);
// Check the constructors.
for (const auto *I : RD->ctors ()) {
if (!I->isCopyOrMoveConstructor ())
if (Stmt *Body = I->getBody ()) {
WalkAST walker (this , BR, ADC, I, isInterprocedural, isPureOnly);
walker.Visit (Body);
walker.Execute ();
}
}
void VirtualCallChecker::reportBug (StringRef Msg, bool IsSink,
const MemRegion *Reg,
CheckerContext &C) const {
ExplodedNode *N;
if (IsSink)
N = C.generateErrorNode ();
else
N = C.generateNonFatalErrorNode ();
// Check the destructor.
if (CXXDestructorDecl *DD = RD->getDestructor ())
if (Stmt *Body = DD->getBody ()) {
WalkAST walker (this , BR, ADC, DD, isInterprocedural, isPureOnly);
walker.Visit (Body);
walker.Execute ();
}
}
};
if (!N)
return ;
if (!BT)
BT.reset (new BugType (
this , " Call to virtual function during construction or destruction" ,
" C++ Object Lifecycle" ));
auto Reporter = llvm::make_unique<BugReport>(*BT, Msg, N);
Reporter->addVisitor (llvm::make_unique<VirtualBugVisitor>(Reg));
C.emitReport (std::move (Reporter));
}
void ento::registerVirtualCallChecker (CheckerManager &mgr) {
VirtualCallChecker *checker = mgr.registerChecker <VirtualCallChecker>();
checker->isInterprocedural =
mgr.getAnalyzerOptions ().getBooleanOption (" Interprocedural" , false ,
checker);
checker->isPureOnly =
mgr.getAnalyzerOptions ().getBooleanOption (" PureOnly" , false ,
checker);
checker->IsPureOnly =
mgr.getAnalyzerOptions ().getBooleanOption (" PureOnly" , false , checker);
}