forked from OSchip/llvm-project
288 lines
9.6 KiB
C++
288 lines
9.6 KiB
C++
//=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines a checker that checks virtual function calls during
|
|
// construction or destruction of C++ objects.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
|
|
#include "clang/AST/DeclCXX.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/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.
|
|
|
|
namespace llvm {
|
|
template <> struct FoldingSetTrait<ObjectState> {
|
|
static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
|
|
ID.AddInteger(static_cast<int>(X));
|
|
}
|
|
};
|
|
} // end namespace llvm
|
|
|
|
namespace {
|
|
class VirtualCallChecker
|
|
: public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
|
|
mutable std::unique_ptr<BugType> BT;
|
|
|
|
public:
|
|
// The flag to determine if pure virtual functions should be issued only.
|
|
DefaultBool IsPureOnly;
|
|
|
|
void checkBeginFunction(CheckerContext &C) const;
|
|
void checkEndFunction(const ReturnStmt *RS, 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 BugReporterVisitor {
|
|
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);
|
|
}
|
|
|
|
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
|
|
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,
|
|
BugReporterContext &BRC,
|
|
BugReport &) {
|
|
// 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->getStackFrame()));
|
|
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()) {
|
|
// 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(const ReturnStmt *RS,
|
|
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->getStackFrame()));
|
|
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->getStackFrame()));
|
|
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->IsPureOnly =
|
|
mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "PureOnly");
|
|
}
|
|
|
|
bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) {
|
|
return true;
|
|
}
|