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