mirror of https://github.com/microsoft/clang.git
[analyzer][GSoC] Re-implemente current virtual calls checker in a path-sensitive way
Patch by: Xin Wang Differential Revision: https://reviews.llvm.org/D34275 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@311877 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
86736b95f3
commit
f068b5f616
|
@ -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 {
|
||||
enum class ObjectState : bool { CtorCalled, DtorCalled };
|
||||
} // end namespace
|
||||
// FIXME: Ascending over StackFrameContext maybe another method.
|
||||
|
||||
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));
|
||||
namespace llvm {
|
||||
template <> struct FoldingSetTrait<ObjectState> {
|
||||
static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
|
||||
ID.AddInteger(static_cast<int>(X));
|
||||
}
|
||||
|
||||
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 : S->children())
|
||||
if (Child)
|
||||
Visit(Child);
|
||||
}
|
||||
|
||||
void WalkAST::VisitCallExpr(CallExpr *CE) {
|
||||
VisitChildren(CE);
|
||||
if (IsInterprocedural)
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the callee.
|
||||
const CXXMethodDecl *MD =
|
||||
dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
|
||||
if (MD && MD->isVirtual() && !callIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
|
||||
!MD->getParent()->hasAttr<FinalAttr>())
|
||||
ReportVirtualCall(CE, MD->isPure());
|
||||
|
||||
if (IsInterprocedural)
|
||||
Enqueue(CE);
|
||||
}
|
||||
|
||||
void WalkAST::ReportVirtualCall(const CallExpr *CE, bool isPure) {
|
||||
if (ReportPureOnly && !isPure)
|
||||
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;
|
||||
}
|
||||
|
||||
os << "\n";
|
||||
}
|
||||
|
||||
PathDiagnosticLocation CELoc =
|
||||
PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);
|
||||
SourceRange R = CE->getCallee()->getSourceRange();
|
||||
|
||||
os << "Call to ";
|
||||
if (isPure)
|
||||
os << "pure ";
|
||||
|
||||
os << "virtual function during ";
|
||||
|
||||
if (isa<CXXConstructorDecl>(RootMethod))
|
||||
os << "construction ";
|
||||
else
|
||||
os << "destruction ";
|
||||
|
||||
if (isPure)
|
||||
os << "has undefined behavior";
|
||||
else
|
||||
os << "will not dispatch to derived class";
|
||||
|
||||
BR.EmitBasicReport(AC->getDecl(), Checker,
|
||||
"Call to virtual function during construction or "
|
||||
"destruction",
|
||||
"C++ Object Lifecycle", os.str(), CELoc, R);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VirtualCallChecker
|
||||
//===----------------------------------------------------------------------===//
|
||||
} // end namespace llvm
|
||||
|
||||
namespace {
|
||||
class VirtualCallChecker : public Checker<check::ASTDecl<CXXRecordDecl> > {
|
||||
class VirtualCallChecker
|
||||
: public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
|
||||
mutable std::unique_ptr<BugType> BT;
|
||||
|
||||
public:
|
||||
DefaultBool isInterprocedural;
|
||||
DefaultBool isPureOnly;
|
||||
// The flag to determine if pure virtual functions should be issued only.
|
||||
DefaultBool IsPureOnly;
|
||||
|
||||
void checkASTDecl(const CXXRecordDecl *RD, AnalysisManager& mgr,
|
||||
BugReporter &BR) const {
|
||||
AnalysisDeclContext *ADC = mgr.getAnalysisDeclContext(RD);
|
||||
void checkBeginFunction(CheckerContext &C) const;
|
||||
void checkEndFunction(CheckerContext &C) const;
|
||||
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
|
||||
|
||||
// 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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
|
||||
const ExplodedNode *PrevN,
|
||||
BugReporterContext &BRC,
|
||||
BugReport &BR) override;
|
||||
};
|
||||
};
|
||||
} // 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);
|
||||
}
|
||||
|
||||
// The function to check if a callexpr is a virtual function.
|
||||
static bool isVirtualCall(const CallExpr *CE) {
|
||||
bool CallIsNonVirtual = false;
|
||||
|
||||
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;
|
||||
|
||||
if (const Expr *Base = CME->getBase()->IgnoreImpCasts()) {
|
||||
// The most derived class is marked final.
|
||||
if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
|
||||
CallIsNonVirtual = true;
|
||||
}
|
||||
}
|
||||
|
||||
const CXXMethodDecl *MD =
|
||||
dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
|
||||
if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
|
||||
!MD->getParent()->hasAttr<FinalAttr>())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// The BeginFunction callback when enter a constructor or a destructor.
|
||||
void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
|
||||
registerCtorDtorCallInState(true, C);
|
||||
}
|
||||
|
||||
// The EndFunction callback when leave a constructor or a destructor.
|
||||
void VirtualCallChecker::checkEndFunction(CheckerContext &C) const {
|
||||
registerCtorDtorCallInState(false, C);
|
||||
}
|
||||
|
||||
void VirtualCallChecker::checkPreCall(const CallEvent &Call,
|
||||
CheckerContext &C) const {
|
||||
const auto MC = dyn_cast<CXXMemberCall>(&Call);
|
||||
if (!MC)
|
||||
return;
|
||||
|
||||
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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
ProgramStateRef State = C.getState();
|
||||
auto &SVB = C.getSValBuilder();
|
||||
|
||||
// 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);
|
||||
|
||||
C.addTransition(State);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualCallChecker::reportBug(StringRef Msg, bool IsSink,
|
||||
const MemRegion *Reg,
|
||||
CheckerContext &C) const {
|
||||
ExplodedNode *N;
|
||||
if (IsSink)
|
||||
N = C.generateErrorNode();
|
||||
else
|
||||
N = C.generateNonFatalErrorNode();
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,98 +1,83 @@
|
|||
// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -verify -std=c++11 %s
|
||||
// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -analyzer-config optin.cplusplus.VirtualCall:Interprocedural=true -DINTERPROCEDURAL=1 -verify -std=c++11 %s
|
||||
// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -analyzer-config optin.cplusplus.VirtualCall:PureOnly=true -DPUREONLY=1 -verify -std=c++11 %s
|
||||
// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -analyzer-output=text -verify -std=c++11 %s
|
||||
|
||||
/* When INTERPROCEDURAL is set, we expect diagnostics in all functions reachable
|
||||
from a constructor or destructor. If it is not set, we expect diagnostics
|
||||
only in the constructor or destructor.
|
||||
// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -analyzer-config optin.cplusplus.VirtualCall:PureOnly=true -DPUREONLY=1 -analyzer-output=text -verify -std=c++11 %s
|
||||
|
||||
When PUREONLY is set, we expect diagnostics only for calls to pure virtual
|
||||
functions not to non-pure virtual functions.
|
||||
*/
|
||||
#include "virtualcall.h"
|
||||
|
||||
class A {
|
||||
public:
|
||||
A();
|
||||
A(int i);
|
||||
|
||||
~A() {};
|
||||
|
||||
virtual int foo() = 0; // from Sema: expected-note {{'foo' declared here}}
|
||||
~A(){};
|
||||
|
||||
virtual int foo() = 0;
|
||||
virtual void bar() = 0;
|
||||
void f() {
|
||||
foo();
|
||||
#if INTERPROCEDURAL
|
||||
// expected-warning-re@-2 {{{{^}}Call Path : foo <-- fCall to pure virtual function during construction has undefined behavior}}
|
||||
#endif
|
||||
// expected-warning-re@-1 {{{{^}}Call to pure virtual function during construction}}
|
||||
// expected-note-re@-2 {{{{^}}Call to pure virtual function during construction}}
|
||||
}
|
||||
};
|
||||
|
||||
class B : public A {
|
||||
public:
|
||||
B() {
|
||||
foo();
|
||||
B() { // expected-note {{Calling default constructor for 'A'}}
|
||||
foo();
|
||||
#if !PUREONLY
|
||||
#if INTERPROCEDURAL
|
||||
// expected-warning-re@-3 {{{{^}}Call Path : fooCall to virtual function during construction will not dispatch to derived class}}
|
||||
#else
|
||||
// expected-warning-re@-5 {{{{^}}Call to virtual function during construction will not dispatch to derived class}}
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}This constructor of an object of type 'B' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-4 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
}
|
||||
~B();
|
||||
|
||||
|
||||
virtual int foo();
|
||||
virtual void bar() { foo(); }
|
||||
#if INTERPROCEDURAL
|
||||
// expected-warning-re@-2 {{{{^}}Call Path : foo <-- barCall to virtual function during destruction will not dispatch to derived class}}
|
||||
virtual void bar() {
|
||||
foo();
|
||||
#if !PUREONLY
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during destruction}}
|
||||
// expected-note-re@-3 {{{{^}}Call to virtual function during destruction}}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
A::A() {
|
||||
f();
|
||||
}
|
||||
|
||||
A::A(int i) {
|
||||
foo(); // From Sema: expected-warning {{call to pure virtual member function 'foo' has undefined behavior}}
|
||||
#if INTERPROCEDURAL
|
||||
// expected-warning-re@-2 {{{{^}}Call Path : fooCall to pure virtual function during construction has undefined behavior}}
|
||||
#else
|
||||
// expected-warning-re@-4 {{{{^}}Call to pure virtual function during construction has undefined behavior}}
|
||||
#endif
|
||||
A::A() {
|
||||
f();
|
||||
// expected-note-re@-1 {{{{^}}This constructor of an object of type 'A' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-2 {{{{^}}Calling 'A::f'}}
|
||||
}
|
||||
|
||||
B::~B() {
|
||||
this->B::foo(); // no-warning
|
||||
this->B::bar();
|
||||
this->foo();
|
||||
#if !PUREONLY
|
||||
#if INTERPROCEDURAL
|
||||
// expected-warning-re@-3 {{{{^}}Call Path : fooCall to virtual function during destruction will not dispatch to derived class}}
|
||||
#else
|
||||
// expected-warning-re@-5 {{{{^}}Call to virtual function during destruction will not dispatch to derived class}}
|
||||
// expected-note-re@-2 {{{{^}}This destructor of an object of type '~B' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-3 {{{{^}}Calling 'B::bar'}}
|
||||
#endif
|
||||
this->foo();
|
||||
#if !PUREONLY
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during destruction}}
|
||||
// expected-note-re@-3 {{{{^}}This destructor of an object of type '~B' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-4 {{{{^}}Call to virtual function during destruction}}
|
||||
#endif
|
||||
|
||||
|
||||
}
|
||||
|
||||
class C : public B {
|
||||
public:
|
||||
C();
|
||||
~C();
|
||||
|
||||
|
||||
virtual int foo();
|
||||
void f(int i);
|
||||
};
|
||||
|
||||
C::C() {
|
||||
f(foo());
|
||||
f(foo());
|
||||
#if !PUREONLY
|
||||
#if INTERPROCEDURAL
|
||||
// expected-warning-re@-3 {{{{^}}Call Path : fooCall to virtual function during construction will not dispatch to derived class}}
|
||||
#else
|
||||
// expected-warning-re@-5 {{{{^}}Call to virtual function during construction will not dispatch to derived class}}
|
||||
#endif
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}This constructor of an object of type 'C' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-4 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -112,30 +97,177 @@ public:
|
|||
foo(); // no-warning
|
||||
}
|
||||
~E() { bar(); }
|
||||
#if !PUREONLY
|
||||
// expected-note-re@-2 2{{{{^}}Calling '~B'}}
|
||||
#endif
|
||||
int foo() override;
|
||||
};
|
||||
|
||||
// Regression test: don't crash when there's no direct callee.
|
||||
class F {
|
||||
public:
|
||||
F() {
|
||||
void (F::* ptr)() = &F::foo;
|
||||
void (F::*ptr)() = &F::foo;
|
||||
(this->*ptr)();
|
||||
}
|
||||
void foo();
|
||||
};
|
||||
|
||||
int main() {
|
||||
A *a;
|
||||
B *b;
|
||||
C *c;
|
||||
D *d;
|
||||
E *e;
|
||||
F *f;
|
||||
class G {
|
||||
public:
|
||||
G() {}
|
||||
virtual void bar();
|
||||
void foo() {
|
||||
bar(); // no warning
|
||||
}
|
||||
};
|
||||
|
||||
class H {
|
||||
public:
|
||||
H() : initState(0) { init(); }
|
||||
int initState;
|
||||
virtual void f() const;
|
||||
void init() {
|
||||
if (initState)
|
||||
f(); // no warning
|
||||
}
|
||||
|
||||
H(int i) {
|
||||
G g;
|
||||
g.foo();
|
||||
g.bar(); // no warning
|
||||
f();
|
||||
#if !PUREONLY
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}This constructor of an object of type 'H' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-4 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
H &h = *this;
|
||||
h.f();
|
||||
#if !PUREONLY
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}This constructor of an object of type 'H' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-4 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
class X {
|
||||
public:
|
||||
X() {
|
||||
g();
|
||||
#if !PUREONLY
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}This constructor of an object of type 'X' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-4 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
}
|
||||
X(int i) {
|
||||
if (i > 0) {
|
||||
#if !PUREONLY
|
||||
// expected-note-re@-2 {{{{^}}Taking true branch}}
|
||||
// expected-note-re@-3 {{{{^}}Taking false branch}}
|
||||
#endif
|
||||
X x(i - 1);
|
||||
#if !PUREONLY
|
||||
// expected-note-re@-2 {{{{^}}Calling constructor for 'X'}}
|
||||
#endif
|
||||
x.g(); // no warning
|
||||
}
|
||||
g();
|
||||
#if !PUREONLY
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}This constructor of an object of type 'X' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-4 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
}
|
||||
virtual void g();
|
||||
};
|
||||
|
||||
class M;
|
||||
class N {
|
||||
public:
|
||||
virtual void virtualMethod();
|
||||
void callFooOfM(M *);
|
||||
};
|
||||
class M {
|
||||
public:
|
||||
M() {
|
||||
N n;
|
||||
n.virtualMethod(); // no warning
|
||||
n.callFooOfM(this);
|
||||
#if !PUREONLY
|
||||
// expected-note-re@-2 {{{{^}}This constructor of an object of type 'M' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-3 {{{{^}}Calling 'N::callFooOfM'}}
|
||||
#endif
|
||||
}
|
||||
virtual void foo();
|
||||
};
|
||||
void N::callFooOfM(M *m) {
|
||||
m->foo();
|
||||
#if !PUREONLY
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "virtualcall.h"
|
||||
class Y {
|
||||
public:
|
||||
virtual void foobar();
|
||||
void fooY() {
|
||||
F f1;
|
||||
foobar();
|
||||
#if !PUREONLY
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
}
|
||||
Y() { fooY(); }
|
||||
#if !PUREONLY
|
||||
// expected-note-re@-2 {{{{^}}This constructor of an object of type 'Y' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-3 {{{{^}}Calling 'Y::fooY'}}
|
||||
#endif
|
||||
};
|
||||
|
||||
#define AS_SYSTEM
|
||||
#include "virtualcall.h"
|
||||
#undef AS_SYSTEM
|
||||
int main() {
|
||||
B b;
|
||||
#if PUREONLY
|
||||
//expected-note-re@-2 {{{{^}}Calling default constructor for 'B'}}
|
||||
#else
|
||||
//expected-note-re@-4 2{{{{^}}Calling default constructor for 'B'}}
|
||||
#endif
|
||||
C c;
|
||||
#if !PUREONLY
|
||||
//expected-note-re@-2 {{{{^}}Calling default constructor for 'C'}}
|
||||
#endif
|
||||
D d;
|
||||
E e;
|
||||
F f;
|
||||
G g;
|
||||
H h;
|
||||
H h1(1);
|
||||
#if !PUREONLY
|
||||
//expected-note-re@-2 {{{{^}}Calling constructor for 'H'}}
|
||||
//expected-note-re@-3 {{{{^}}Calling constructor for 'H'}}
|
||||
#endif
|
||||
X x;
|
||||
#if !PUREONLY
|
||||
//expected-note-re@-2 {{{{^}}Calling default constructor for 'X'}}
|
||||
#endif
|
||||
X x1(1);
|
||||
#if !PUREONLY
|
||||
//expected-note-re@-2 {{{{^}}Calling constructor for 'X'}}
|
||||
#endif
|
||||
M m;
|
||||
#if !PUREONLY
|
||||
//expected-note-re@-2 {{{{^}}Calling default constructor for 'M'}}
|
||||
#endif
|
||||
Y *y = new Y;
|
||||
delete y;
|
||||
header::Z z;
|
||||
#if !PUREONLY
|
||||
// expected-note-re@-2 {{{{^}}Calling default constructor for 'Z'}}
|
||||
#endif
|
||||
}
|
||||
#if !PUREONLY
|
||||
//expected-note-re@-2 2{{{{^}}Calling '~E'}}
|
||||
#endif
|
||||
|
|
|
@ -1,36 +1,14 @@
|
|||
#ifdef AS_SYSTEM
|
||||
#pragma clang system_header
|
||||
|
||||
namespace system {
|
||||
class A {
|
||||
public:
|
||||
A() {
|
||||
foo(); // no-warning
|
||||
}
|
||||
|
||||
virtual int foo();
|
||||
};
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
namespace header {
|
||||
class A {
|
||||
class Z {
|
||||
public:
|
||||
A() {
|
||||
Z() {
|
||||
foo();
|
||||
#if !PUREONLY
|
||||
#if INTERPROCEDURAL
|
||||
// expected-warning-re@-3 {{{{^}}Call Path : fooCall to virtual function during construction will not dispatch to derived class}}
|
||||
#else
|
||||
// expected-warning-re@-5 {{{{^}}Call to virtual function during construction will not dispatch to derived class}}
|
||||
// expected-warning-re@-2 {{{{^}}Call to virtual function during construction}}
|
||||
// expected-note-re@-3 {{{{^}}This constructor of an object of type 'Z' has not returned when the virtual method was called}}
|
||||
// expected-note-re@-4 {{{{^}}Call to virtual function during construction}}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
virtual int foo();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue