10b57cec5SDimitry Andric //=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
9a7dea167SDimitry Andric // This file defines a checker that checks virtual method calls during
100b57cec5SDimitry Andric // construction or destruction of C++ objects.
110b57cec5SDimitry Andric //
120b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
130b57cec5SDimitry Andric
14480093f4SDimitry Andric #include "clang/AST/Attr.h"
150b57cec5SDimitry Andric #include "clang/AST/DeclCXX.h"
16480093f4SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
170b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
180b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
190b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h"
200b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
210b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
220b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
230b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h"
240b57cec5SDimitry Andric
250b57cec5SDimitry Andric using namespace clang;
260b57cec5SDimitry Andric using namespace ento;
270b57cec5SDimitry Andric
280b57cec5SDimitry Andric namespace {
290b57cec5SDimitry Andric enum class ObjectState : bool { CtorCalled, DtorCalled };
300b57cec5SDimitry Andric } // end namespace
310b57cec5SDimitry Andric // FIXME: Ascending over StackFrameContext maybe another method.
320b57cec5SDimitry Andric
330b57cec5SDimitry Andric namespace llvm {
340b57cec5SDimitry Andric template <> struct FoldingSetTrait<ObjectState> {
Profilellvm::FoldingSetTrait350b57cec5SDimitry Andric static inline void Profile(ObjectState X, FoldingSetNodeID &ID) {
360b57cec5SDimitry Andric ID.AddInteger(static_cast<int>(X));
370b57cec5SDimitry Andric }
380b57cec5SDimitry Andric };
390b57cec5SDimitry Andric } // end namespace llvm
400b57cec5SDimitry Andric
410b57cec5SDimitry Andric namespace {
420b57cec5SDimitry Andric class VirtualCallChecker
430b57cec5SDimitry Andric : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> {
440b57cec5SDimitry Andric public:
45a7dea167SDimitry Andric // These are going to be null if the respective check is disabled.
46a7dea167SDimitry Andric mutable std::unique_ptr<BugType> BT_Pure, BT_Impure;
47a7dea167SDimitry Andric bool ShowFixIts = false;
480b57cec5SDimitry Andric
490b57cec5SDimitry Andric void checkBeginFunction(CheckerContext &C) const;
500b57cec5SDimitry Andric void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
510b57cec5SDimitry Andric void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
520b57cec5SDimitry Andric
530b57cec5SDimitry Andric private:
540b57cec5SDimitry Andric void registerCtorDtorCallInState(bool IsBeginFunction,
550b57cec5SDimitry Andric CheckerContext &C) const;
560b57cec5SDimitry Andric };
570b57cec5SDimitry Andric } // end namespace
580b57cec5SDimitry Andric
590b57cec5SDimitry Andric // GDM (generic data map) to the memregion of this for the ctor and dtor.
REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap,const MemRegion *,ObjectState)600b57cec5SDimitry Andric REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
610b57cec5SDimitry Andric
62a7dea167SDimitry Andric // The function to check if a callexpr is a virtual method call.
630b57cec5SDimitry Andric static bool isVirtualCall(const CallExpr *CE) {
640b57cec5SDimitry Andric bool CallIsNonVirtual = false;
650b57cec5SDimitry Andric
660b57cec5SDimitry Andric if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
670b57cec5SDimitry Andric // The member access is fully qualified (i.e., X::F).
680b57cec5SDimitry Andric // Treat this as a non-virtual call and do not warn.
690b57cec5SDimitry Andric if (CME->getQualifier())
700b57cec5SDimitry Andric CallIsNonVirtual = true;
710b57cec5SDimitry Andric
720b57cec5SDimitry Andric if (const Expr *Base = CME->getBase()) {
730b57cec5SDimitry Andric // The most derived class is marked final.
740b57cec5SDimitry Andric if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>())
750b57cec5SDimitry Andric CallIsNonVirtual = true;
760b57cec5SDimitry Andric }
770b57cec5SDimitry Andric }
780b57cec5SDimitry Andric
790b57cec5SDimitry Andric const CXXMethodDecl *MD =
800b57cec5SDimitry Andric dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
810b57cec5SDimitry Andric if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() &&
820b57cec5SDimitry Andric !MD->getParent()->hasAttr<FinalAttr>())
830b57cec5SDimitry Andric return true;
840b57cec5SDimitry Andric return false;
850b57cec5SDimitry Andric }
860b57cec5SDimitry Andric
870b57cec5SDimitry Andric // The BeginFunction callback when enter a constructor or a destructor.
checkBeginFunction(CheckerContext & C) const880b57cec5SDimitry Andric void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const {
890b57cec5SDimitry Andric registerCtorDtorCallInState(true, C);
900b57cec5SDimitry Andric }
910b57cec5SDimitry Andric
920b57cec5SDimitry Andric // The EndFunction callback when leave a constructor or a destructor.
checkEndFunction(const ReturnStmt * RS,CheckerContext & C) const930b57cec5SDimitry Andric void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS,
940b57cec5SDimitry Andric CheckerContext &C) const {
950b57cec5SDimitry Andric registerCtorDtorCallInState(false, C);
960b57cec5SDimitry Andric }
970b57cec5SDimitry Andric
checkPreCall(const CallEvent & Call,CheckerContext & C) const980b57cec5SDimitry Andric void VirtualCallChecker::checkPreCall(const CallEvent &Call,
990b57cec5SDimitry Andric CheckerContext &C) const {
1000b57cec5SDimitry Andric const auto MC = dyn_cast<CXXMemberCall>(&Call);
1010b57cec5SDimitry Andric if (!MC)
1020b57cec5SDimitry Andric return;
1030b57cec5SDimitry Andric
1040b57cec5SDimitry Andric const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
1050b57cec5SDimitry Andric if (!MD)
1060b57cec5SDimitry Andric return;
1070b57cec5SDimitry Andric
108a7dea167SDimitry Andric ProgramStateRef State = C.getState();
109a7dea167SDimitry Andric // Member calls are always represented by a call-expression.
110a7dea167SDimitry Andric const auto *CE = cast<CallExpr>(Call.getOriginExpr());
1110b57cec5SDimitry Andric if (!isVirtualCall(CE))
1120b57cec5SDimitry Andric return;
1130b57cec5SDimitry Andric
1140b57cec5SDimitry Andric const MemRegion *Reg = MC->getCXXThisVal().getAsRegion();
1150b57cec5SDimitry Andric const ObjectState *ObState = State->get<CtorDtorMap>(Reg);
1160b57cec5SDimitry Andric if (!ObState)
1170b57cec5SDimitry Andric return;
118a7dea167SDimitry Andric
119*7a6dacacSDimitry Andric bool IsPure = MD->isPureVirtual();
120a7dea167SDimitry Andric
121a7dea167SDimitry Andric // At this point we're sure that we're calling a virtual method
122a7dea167SDimitry Andric // during construction or destruction, so we'll emit a report.
123a7dea167SDimitry Andric SmallString<128> Msg;
124a7dea167SDimitry Andric llvm::raw_svector_ostream OS(Msg);
125a7dea167SDimitry Andric OS << "Call to ";
126a7dea167SDimitry Andric if (IsPure)
127a7dea167SDimitry Andric OS << "pure ";
128e8d8bef9SDimitry Andric OS << "virtual method '" << MD->getParent()->getDeclName()
129e8d8bef9SDimitry Andric << "::" << MD->getDeclName() << "' during ";
130a7dea167SDimitry Andric if (*ObState == ObjectState::CtorCalled)
131a7dea167SDimitry Andric OS << "construction ";
1320b57cec5SDimitry Andric else
133a7dea167SDimitry Andric OS << "destruction ";
134a7dea167SDimitry Andric if (IsPure)
135a7dea167SDimitry Andric OS << "has undefined behavior";
136a7dea167SDimitry Andric else
137a7dea167SDimitry Andric OS << "bypasses virtual dispatch";
138a7dea167SDimitry Andric
139a7dea167SDimitry Andric ExplodedNode *N =
140a7dea167SDimitry Andric IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode();
141a7dea167SDimitry Andric if (!N)
142a7dea167SDimitry Andric return;
143a7dea167SDimitry Andric
144a7dea167SDimitry Andric const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure;
145a7dea167SDimitry Andric if (!BT) {
146a7dea167SDimitry Andric // The respective check is disabled.
147a7dea167SDimitry Andric return;
1480b57cec5SDimitry Andric }
1490b57cec5SDimitry Andric
150a7dea167SDimitry Andric auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N);
151a7dea167SDimitry Andric
152a7dea167SDimitry Andric if (ShowFixIts && !IsPure) {
153a7dea167SDimitry Andric // FIXME: These hints are valid only when the virtual call is made
154a7dea167SDimitry Andric // directly from the constructor/destructor. Otherwise the dispatch
155a7dea167SDimitry Andric // will work just fine from other callees, and the fix may break
156a7dea167SDimitry Andric // the otherwise correct program.
157a7dea167SDimitry Andric FixItHint Fixit = FixItHint::CreateInsertion(
158a7dea167SDimitry Andric CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::");
159a7dea167SDimitry Andric Report->addFixItHint(Fixit);
1600b57cec5SDimitry Andric }
161a7dea167SDimitry Andric
162a7dea167SDimitry Andric C.emitReport(std::move(Report));
1630b57cec5SDimitry Andric }
1640b57cec5SDimitry Andric
registerCtorDtorCallInState(bool IsBeginFunction,CheckerContext & C) const1650b57cec5SDimitry Andric void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction,
1660b57cec5SDimitry Andric CheckerContext &C) const {
1670b57cec5SDimitry Andric const auto *LCtx = C.getLocationContext();
1680b57cec5SDimitry Andric const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl());
1690b57cec5SDimitry Andric if (!MD)
1700b57cec5SDimitry Andric return;
1710b57cec5SDimitry Andric
1720b57cec5SDimitry Andric ProgramStateRef State = C.getState();
1730b57cec5SDimitry Andric auto &SVB = C.getSValBuilder();
1740b57cec5SDimitry Andric
1750b57cec5SDimitry Andric // Enter a constructor, set the corresponding memregion be true.
1760b57cec5SDimitry Andric if (isa<CXXConstructorDecl>(MD)) {
1770b57cec5SDimitry Andric auto ThiSVal =
1780b57cec5SDimitry Andric State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
1790b57cec5SDimitry Andric const MemRegion *Reg = ThiSVal.getAsRegion();
1800b57cec5SDimitry Andric if (IsBeginFunction)
1810b57cec5SDimitry Andric State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled);
1820b57cec5SDimitry Andric else
1830b57cec5SDimitry Andric State = State->remove<CtorDtorMap>(Reg);
1840b57cec5SDimitry Andric
1850b57cec5SDimitry Andric C.addTransition(State);
1860b57cec5SDimitry Andric return;
1870b57cec5SDimitry Andric }
1880b57cec5SDimitry Andric
1890b57cec5SDimitry Andric // Enter a Destructor, set the corresponding memregion be true.
1900b57cec5SDimitry Andric if (isa<CXXDestructorDecl>(MD)) {
1910b57cec5SDimitry Andric auto ThiSVal =
1920b57cec5SDimitry Andric State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame()));
1930b57cec5SDimitry Andric const MemRegion *Reg = ThiSVal.getAsRegion();
1940b57cec5SDimitry Andric if (IsBeginFunction)
1950b57cec5SDimitry Andric State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled);
1960b57cec5SDimitry Andric else
1970b57cec5SDimitry Andric State = State->remove<CtorDtorMap>(Reg);
1980b57cec5SDimitry Andric
1990b57cec5SDimitry Andric C.addTransition(State);
2000b57cec5SDimitry Andric return;
2010b57cec5SDimitry Andric }
2020b57cec5SDimitry Andric }
2030b57cec5SDimitry Andric
registerVirtualCallModeling(CheckerManager & Mgr)204a7dea167SDimitry Andric void ento::registerVirtualCallModeling(CheckerManager &Mgr) {
205a7dea167SDimitry Andric Mgr.registerChecker<VirtualCallChecker>();
2060b57cec5SDimitry Andric }
2070b57cec5SDimitry Andric
registerPureVirtualCallChecker(CheckerManager & Mgr)208a7dea167SDimitry Andric void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) {
209a7dea167SDimitry Andric auto *Chk = Mgr.getChecker<VirtualCallChecker>();
210a7dea167SDimitry Andric Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(),
211a7dea167SDimitry Andric "Pure virtual method call",
212a7dea167SDimitry Andric categories::CXXObjectLifecycle);
213a7dea167SDimitry Andric }
2140b57cec5SDimitry Andric
registerVirtualCallChecker(CheckerManager & Mgr)215a7dea167SDimitry Andric void ento::registerVirtualCallChecker(CheckerManager &Mgr) {
216a7dea167SDimitry Andric auto *Chk = Mgr.getChecker<VirtualCallChecker>();
217a7dea167SDimitry Andric if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption(
218a7dea167SDimitry Andric Mgr.getCurrentCheckerName(), "PureOnly")) {
219a7dea167SDimitry Andric Chk->BT_Impure = std::make_unique<BugType>(
220a7dea167SDimitry Andric Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch",
221a7dea167SDimitry Andric categories::CXXObjectLifecycle);
222a7dea167SDimitry Andric Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
223a7dea167SDimitry Andric Mgr.getCurrentCheckerName(), "ShowFixIts");
224a7dea167SDimitry Andric }
225a7dea167SDimitry Andric }
226a7dea167SDimitry Andric
shouldRegisterVirtualCallModeling(const CheckerManager & mgr)2275ffd83dbSDimitry Andric bool ento::shouldRegisterVirtualCallModeling(const CheckerManager &mgr) {
2285ffd83dbSDimitry Andric const LangOptions &LO = mgr.getLangOpts();
229a7dea167SDimitry Andric return LO.CPlusPlus;
230a7dea167SDimitry Andric }
231a7dea167SDimitry Andric
shouldRegisterPureVirtualCallChecker(const CheckerManager & mgr)2325ffd83dbSDimitry Andric bool ento::shouldRegisterPureVirtualCallChecker(const CheckerManager &mgr) {
2335ffd83dbSDimitry Andric const LangOptions &LO = mgr.getLangOpts();
234a7dea167SDimitry Andric return LO.CPlusPlus;
2350b57cec5SDimitry Andric }
2360b57cec5SDimitry Andric
shouldRegisterVirtualCallChecker(const CheckerManager & mgr)2375ffd83dbSDimitry Andric bool ento::shouldRegisterVirtualCallChecker(const CheckerManager &mgr) {
2385ffd83dbSDimitry Andric const LangOptions &LO = mgr.getLangOpts();
239a7dea167SDimitry Andric return LO.CPlusPlus;
2400b57cec5SDimitry Andric }
241