| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 1 | //=======- VirtualCallChecker.cpp --------------------------------*- C++ -*-==// | 
|  | 2 | // | 
| Chandler Carruth | 2946cd7 | 2019-01-19 08:50:56 +0000 | [diff] [blame] | 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 | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 6 | // | 
|  | 7 | //===----------------------------------------------------------------------===// | 
|  | 8 | // | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 9 | //  This file defines a checker that checks virtual method calls during | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 10 | //  construction or destruction of C++ objects. | 
|  | 11 | // | 
|  | 12 | //===----------------------------------------------------------------------===// | 
|  | 13 |  | 
| Kristof Umann | 76a2150 | 2018-12-15 16:23:51 +0000 | [diff] [blame] | 14 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 15 | #include "clang/AST/DeclCXX.h" | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 16 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 17 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" | 
| Chandler Carruth | 3a02247 | 2012-12-04 09:13:33 +0000 | [diff] [blame] | 18 | #include "clang/StaticAnalyzer/Core/Checker.h" | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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" | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 23 |  | 
|  | 24 | using namespace clang; | 
|  | 25 | using namespace ento; | 
|  | 26 |  | 
|  | 27 | namespace { | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 28 | enum class ObjectState : bool { CtorCalled, DtorCalled }; | 
|  | 29 | } // end namespace | 
|  | 30 | // FIXME: Ascending over StackFrameContext maybe another method. | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 31 |  | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 32 | namespace llvm { | 
|  | 33 | template <> struct FoldingSetTrait<ObjectState> { | 
|  | 34 | static inline void Profile(ObjectState X, FoldingSetNodeID &ID) { | 
|  | 35 | ID.AddInteger(static_cast<int>(X)); | 
| Devin Coughlin | 3e5f047 | 2016-12-10 01:16:09 +0000 | [diff] [blame] | 36 | } | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 37 | }; | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 38 | } // end namespace llvm | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 39 |  | 
|  | 40 | namespace { | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 41 | class VirtualCallChecker | 
|  | 42 | : public Checker<check::BeginFunction, check::EndFunction, check::PreCall> { | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 43 | public: | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 44 | // These are going to be null if the respective check is disabled. | 
|  | 45 | mutable std::unique_ptr<BugType> BT_Pure, BT_Impure; | 
| Artem Dergachev | 6cee434 | 2019-09-06 20:55:29 +0000 | [diff] [blame] | 46 | bool ShowFixIts = false; | 
| Devin Coughlin | 3e5f047 | 2016-12-10 01:16:09 +0000 | [diff] [blame] | 47 |  | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 48 | void checkBeginFunction(CheckerContext &C) const; | 
| Reka Kovacs | ed8c05c | 2018-07-16 20:47:45 +0000 | [diff] [blame] | 49 | void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 50 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 51 |  | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 52 | private: | 
|  | 53 | void registerCtorDtorCallInState(bool IsBeginFunction, | 
|  | 54 | CheckerContext &C) const; | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 55 | }; | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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 |  | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 61 | // The function to check if a callexpr is a virtual method call. | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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 |  | 
| Gabor Horvath | 5536a01 | 2017-09-21 08:18:59 +0000 | [diff] [blame] | 71 | if (const Expr *Base = CME->getBase()) { | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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. | 
| Reka Kovacs | ed8c05c | 2018-07-16 20:47:45 +0000 | [diff] [blame] | 92 | void VirtualCallChecker::checkEndFunction(const ReturnStmt *RS, | 
|  | 93 | CheckerContext &C) const { | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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; | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 106 |  | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 107 | ProgramStateRef State = C.getState(); | 
| Artem Dergachev | 630f7da | 2019-08-28 18:44:38 +0000 | [diff] [blame] | 108 | // Member calls are always represented by a call-expression. | 
|  | 109 | const auto *CE = cast<CallExpr>(Call.getOriginExpr()); | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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; | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 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; | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 147 | } | 
|  | 148 |  | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 149 | auto Report = std::make_unique<BugReport>(*BT, OS.str(), N); | 
| Artem Dergachev | 6cee434 | 2019-09-06 20:55:29 +0000 | [diff] [blame] | 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 |  | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 161 | C.emitReport(std::move(Report)); | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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 = | 
| George Karpenkov | dd18b11 | 2018-06-27 01:51:55 +0000 | [diff] [blame] | 177 | State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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 = | 
| George Karpenkov | dd18b11 | 2018-06-27 01:51:55 +0000 | [diff] [blame] | 191 | State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); | 
| Gabor Horvath | 857ccd2 | 2017-08-28 08:44:43 +0000 | [diff] [blame] | 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 |  | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 203 | void ento::registerVirtualCallModeling(CheckerManager &Mgr) { | 
|  | 204 | Mgr.registerChecker<VirtualCallChecker>(); | 
| Alexander Kornienko | ab9db51 | 2015-06-22 23:07:51 +0000 | [diff] [blame] | 205 | } | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 206 |  | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 207 | void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { | 
|  | 208 | auto *Chk = Mgr.getChecker<VirtualCallChecker>(); | 
|  | 209 | Chk->BT_Pure = std::make_unique<BugType>( | 
|  | 210 | Mgr.getCurrentCheckName(), "Pure virtual method call", | 
|  | 211 | categories::CXXObjectLifecycle); | 
|  | 212 | } | 
| Devin Coughlin | 3e5f047 | 2016-12-10 01:16:09 +0000 | [diff] [blame] | 213 |  | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 214 | void ento::registerVirtualCallChecker(CheckerManager &Mgr) { | 
|  | 215 | auto *Chk = Mgr.getChecker<VirtualCallChecker>(); | 
|  | 216 | if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( | 
|  | 217 | Mgr.getCurrentCheckName(), "PureOnly")) { | 
|  | 218 | Chk->BT_Impure = std::make_unique<BugType>( | 
|  | 219 | Mgr.getCurrentCheckName(), "Unexpected loss of virtual dispatch", | 
|  | 220 | categories::CXXObjectLifecycle); | 
| Artem Dergachev | 6cee434 | 2019-09-06 20:55:29 +0000 | [diff] [blame] | 221 | Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( | 
|  | 222 | Mgr.getCurrentCheckName(), "ShowFixIts"); | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 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; | 
| Ted Kremenek | b3512d3 | 2012-01-03 23:18:57 +0000 | [diff] [blame] | 232 | } | 
| Kristof Umann | 058a7a4 | 2019-01-26 14:23:08 +0000 | [diff] [blame] | 233 |  | 
|  | 234 | bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) { | 
| Artem Dergachev | d3971fe | 2019-08-20 21:41:14 +0000 | [diff] [blame] | 235 | return LO.CPlusPlus; | 
| Kristof Umann | 058a7a4 | 2019-01-26 14:23:08 +0000 | [diff] [blame] | 236 | } |