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