1 //=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines a checker that checks virtual method calls during 10 // construction or destruction of C++ objects. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 15 #include "clang/AST/DeclCXX.h" 16 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 17 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 18 #include "clang/StaticAnalyzer/Core/Checker.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 20 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 22 #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" 23 24 using namespace clang; 25 using namespace ento; 26 27 namespace { 28 enum class ObjectState : bool { CtorCalled, DtorCalled }; 29 } // end namespace 30 // FIXME: Ascending over StackFrameContext maybe another method. 31 32 namespace llvm { 33 template <> struct FoldingSetTrait<ObjectState> { 34 static inline void Profile(ObjectState X, FoldingSetNodeID &ID) { 35 ID.AddInteger(static_cast<int>(X)); 36 } 37 }; 38 } // end namespace llvm 39 40 namespace { 41 class VirtualCallChecker 42 : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> { 43 public: 44 // These are going to be null if the respective check is disabled. 45 mutable std::unique_ptr<BugType> BT_Pure, BT_Impure; 46 bool ShowFixIts = false; 47 48 void checkBeginFunction(CheckerContext &C) const; 49 void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; 50 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 51 52 private: 53 void registerCtorDtorCallInState(bool IsBeginFunction, 54 CheckerContext &C) const; 55 }; 56 } // end namespace 57 58 // GDM (generic data map) to the memregion of this for the ctor and dtor. 59 REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState) 60 61 // The function to check if a callexpr is a virtual method call. 62 static bool isVirtualCall(const CallExpr *CE) { 63 bool CallIsNonVirtual = false; 64 65 if (const MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) { 66 // The member access is fully qualified (i.e., X::F). 67 // Treat this as a non-virtual call and do not warn. 68 if (CME->getQualifier()) 69 CallIsNonVirtual = true; 70 71 if (const Expr *Base = CME->getBase()) { 72 // The most derived class is marked final. 73 if (Base->getBestDynamicClassType()->hasAttr<FinalAttr>()) 74 CallIsNonVirtual = true; 75 } 76 } 77 78 const CXXMethodDecl *MD = 79 dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee()); 80 if (MD && MD->isVirtual() && !CallIsNonVirtual && !MD->hasAttr<FinalAttr>() && 81 !MD->getParent()->hasAttr<FinalAttr>()) 82 return true; 83 return false; 84 } 85 86 // The BeginFunction callback when enter a constructor or a destructor. 87 void VirtualCallChecker::checkBeginFunction(CheckerContext &C) const { 88 registerCtorDtorCallInState(true, C); 89 } 90 91 // The EndFunction callback when leave a constructor or a destructor. 92 void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS, 93 CheckerContext &C) const { 94 registerCtorDtorCallInState(false, C); 95 } 96 97 void VirtualCallChecker::checkPreCall(const CallEvent &Call, 98 CheckerContext &C) const { 99 const auto MC = dyn_cast<CXXMemberCall>(&Call); 100 if (!MC) 101 return; 102 103 const CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl()); 104 if (!MD) 105 return; 106 107 ProgramStateRef State = C.getState(); 108 // Member calls are always represented by a call-expression. 109 const auto *CE = cast<CallExpr>(Call.getOriginExpr()); 110 if (!isVirtualCall(CE)) 111 return; 112 113 const MemRegion *Reg = MC->getCXXThisVal().getAsRegion(); 114 const ObjectState *ObState = State->get<CtorDtorMap>(Reg); 115 if (!ObState) 116 return; 117 118 bool IsPure = MD->isPure(); 119 120 // At this point we're sure that we're calling a virtual method 121 // during construction or destruction, so we'll emit a report. 122 SmallString<128> Msg; 123 llvm::raw_svector_ostream OS(Msg); 124 OS << "Call to "; 125 if (IsPure) 126 OS << "pure "; 127 OS << "virtual method '" << MD->getParent()->getNameAsString() 128 << "::" << MD->getNameAsString() << "' during "; 129 if (*ObState == ObjectState::CtorCalled) 130 OS << "construction "; 131 else 132 OS << "destruction "; 133 if (IsPure) 134 OS << "has undefined behavior"; 135 else 136 OS << "bypasses virtual dispatch"; 137 138 ExplodedNode *N = 139 IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode(); 140 if (!N) 141 return; 142 143 const std::unique_ptr<BugType> &BT = IsPure ? BT_Pure : BT_Impure; 144 if (!BT) { 145 // The respective check is disabled. 146 return; 147 } 148 149 auto Report = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N); 150 151 if (ShowFixIts && !IsPure) { 152 // FIXME: These hints are valid only when the virtual call is made 153 // directly from the constructor/destructor. Otherwise the dispatch 154 // will work just fine from other callees, and the fix may break 155 // the otherwise correct program. 156 FixItHint Fixit = FixItHint::CreateInsertion( 157 CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::"); 158 Report->addFixItHint(Fixit); 159 } 160 161 C.emitReport(std::move(Report)); 162 } 163 164 void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, 165 CheckerContext &C) const { 166 const auto *LCtx = C.getLocationContext(); 167 const auto *MD = dyn_cast_or_null<CXXMethodDecl>(LCtx->getDecl()); 168 if (!MD) 169 return; 170 171 ProgramStateRef State = C.getState(); 172 auto &SVB = C.getSValBuilder(); 173 174 // Enter a constructor, set the corresponding memregion be true. 175 if (isa<CXXConstructorDecl>(MD)) { 176 auto ThiSVal = 177 State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); 178 const MemRegion *Reg = ThiSVal.getAsRegion(); 179 if (IsBeginFunction) 180 State = State->set<CtorDtorMap>(Reg, ObjectState::CtorCalled); 181 else 182 State = State->remove<CtorDtorMap>(Reg); 183 184 C.addTransition(State); 185 return; 186 } 187 188 // Enter a Destructor, set the corresponding memregion be true. 189 if (isa<CXXDestructorDecl>(MD)) { 190 auto ThiSVal = 191 State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); 192 const MemRegion *Reg = ThiSVal.getAsRegion(); 193 if (IsBeginFunction) 194 State = State->set<CtorDtorMap>(Reg, ObjectState::DtorCalled); 195 else 196 State = State->remove<CtorDtorMap>(Reg); 197 198 C.addTransition(State); 199 return; 200 } 201 } 202 203 void ento::registerVirtualCallModeling(CheckerManager &Mgr) { 204 Mgr.registerChecker<VirtualCallChecker>(); 205 } 206 207 void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { 208 auto *Chk = Mgr.getChecker<VirtualCallChecker>(); 209 Chk->BT_Pure = std::make_unique<BugType>(Mgr.getCurrentCheckerName(), 210 "Pure virtual method call", 211 categories::CXXObjectLifecycle); 212 } 213 214 void ento::registerVirtualCallChecker(CheckerManager &Mgr) { 215 auto *Chk = Mgr.getChecker<VirtualCallChecker>(); 216 if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( 217 Mgr.getCurrentCheckerName(), "PureOnly")) { 218 Chk->BT_Impure = std::make_unique<BugType>( 219 Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch", 220 categories::CXXObjectLifecycle); 221 Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( 222 Mgr.getCurrentCheckerName(), "ShowFixIts"); 223 } 224 } 225 226 bool ento::shouldRegisterVirtualCallModeling(const LangOptions &LO) { 227 return LO.CPlusPlus; 228 } 229 230 bool ento::shouldRegisterPureVirtualCallChecker(const LangOptions &LO) { 231 return LO.CPlusPlus; 232 } 233 234 bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) { 235 return LO.CPlusPlus; 236 } 237