1 //=== CXXDeleteChecker.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 the following new checkers for C++ delete expressions: 10 // 11 // * DeleteWithNonVirtualDtorChecker 12 // Defines a checker for the OOP52-CPP CERT rule: Do not delete a 13 // polymorphic object without a virtual destructor. 14 // 15 // Diagnostic flags -Wnon-virtual-dtor and -Wdelete-non-virtual-dtor 16 // report if an object with a virtual function but a non-virtual 17 // destructor exists or is deleted, respectively. 18 // 19 // This check exceeds them by comparing the dynamic and static types of 20 // the object at the point of destruction and only warns if it happens 21 // through a pointer to a base type without a virtual destructor. The 22 // check places a note at the last point where the conversion from 23 // derived to base happened. 24 // 25 // * CXXArrayDeleteChecker 26 // Defines a checker for the EXP51-CPP CERT rule: Do not delete an array 27 // through a pointer of the incorrect type. 28 // 29 //===----------------------------------------------------------------------===// 30 31 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 32 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 33 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 34 #include "clang/StaticAnalyzer/Core/Checker.h" 35 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 36 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 37 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 38 #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" 39 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 40 41 using namespace clang; 42 using namespace ento; 43 44 namespace { 45 class CXXDeleteChecker : public Checker<check::PreStmt<CXXDeleteExpr>> { 46 protected: 47 class PtrCastVisitor : public BugReporterVisitor { 48 public: 49 void Profile(llvm::FoldingSetNodeID &ID) const override { 50 static int X = 0; 51 ID.AddPointer(&X); 52 } 53 PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, 54 BugReporterContext &BRC, 55 PathSensitiveBugReport &BR) override; 56 }; 57 58 virtual void 59 checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, 60 const TypedValueRegion *BaseClassRegion, 61 const SymbolicRegion *DerivedClassRegion) const = 0; 62 63 public: 64 void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; 65 }; 66 67 class DeleteWithNonVirtualDtorChecker : public CXXDeleteChecker { 68 mutable std::unique_ptr<BugType> BT; 69 70 void 71 checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, 72 const TypedValueRegion *BaseClassRegion, 73 const SymbolicRegion *DerivedClassRegion) const override; 74 }; 75 76 class CXXArrayDeleteChecker : public CXXDeleteChecker { 77 mutable std::unique_ptr<BugType> BT; 78 79 void 80 checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, 81 const TypedValueRegion *BaseClassRegion, 82 const SymbolicRegion *DerivedClassRegion) const override; 83 }; 84 } // namespace 85 86 void CXXDeleteChecker::checkPreStmt(const CXXDeleteExpr *DE, 87 CheckerContext &C) const { 88 const Expr *DeletedObj = DE->getArgument(); 89 const MemRegion *MR = C.getSVal(DeletedObj).getAsRegion(); 90 if (!MR) 91 return; 92 93 OverloadedOperatorKind DeleteKind = 94 DE->getOperatorDelete()->getOverloadedOperator(); 95 96 if (DeleteKind != OO_Delete && DeleteKind != OO_Array_Delete) 97 return; 98 99 const auto *BaseClassRegion = MR->getAs<TypedValueRegion>(); 100 const auto *DerivedClassRegion = MR->getBaseRegion()->getAs<SymbolicRegion>(); 101 if (!BaseClassRegion || !DerivedClassRegion) 102 return; 103 104 checkTypedDeleteExpr(DE, C, BaseClassRegion, DerivedClassRegion); 105 } 106 107 void DeleteWithNonVirtualDtorChecker::checkTypedDeleteExpr( 108 const CXXDeleteExpr *DE, CheckerContext &C, 109 const TypedValueRegion *BaseClassRegion, 110 const SymbolicRegion *DerivedClassRegion) const { 111 const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); 112 const auto *DerivedClass = 113 DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); 114 if (!BaseClass || !DerivedClass) 115 return; 116 117 if (BaseClass->getDestructor()->isVirtual()) 118 return; 119 120 if (!DerivedClass->isDerivedFrom(BaseClass)) 121 return; 122 123 if (!BT) 124 BT.reset(new BugType(this, 125 "Destruction of a polymorphic object with no " 126 "virtual destructor", 127 "Logic error")); 128 129 ExplodedNode *N = C.generateNonFatalErrorNode(); 130 if (!N) 131 return; 132 auto R = 133 std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N); 134 135 // Mark region of problematic base class for later use in the BugVisitor. 136 R->markInteresting(BaseClassRegion); 137 R->addVisitor<PtrCastVisitor>(); 138 C.emitReport(std::move(R)); 139 } 140 141 void CXXArrayDeleteChecker::checkTypedDeleteExpr( 142 const CXXDeleteExpr *DE, CheckerContext &C, 143 const TypedValueRegion *BaseClassRegion, 144 const SymbolicRegion *DerivedClassRegion) const { 145 const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); 146 const auto *DerivedClass = 147 DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); 148 if (!BaseClass || !DerivedClass) 149 return; 150 151 if (DE->getOperatorDelete()->getOverloadedOperator() != OO_Array_Delete) 152 return; 153 154 if (!DerivedClass->isDerivedFrom(BaseClass)) 155 return; 156 157 if (!BT) 158 BT.reset(new BugType(this, 159 "Deleting an array of polymorphic objects " 160 "is undefined", 161 "Logic error")); 162 163 ExplodedNode *N = C.generateNonFatalErrorNode(); 164 if (!N) 165 return; 166 167 SmallString<256> Buf; 168 llvm::raw_svector_ostream OS(Buf); 169 170 QualType SourceType = BaseClassRegion->getValueType(); 171 QualType TargetType = 172 DerivedClassRegion->getSymbol()->getType()->getPointeeType(); 173 174 OS << "Deleting an array of '" << TargetType.getAsString() 175 << "' objects as their base class '" 176 << SourceType.getAsString(C.getASTContext().getPrintingPolicy()) 177 << "' is undefined"; 178 179 auto R = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N); 180 181 // Mark region of problematic base class for later use in the BugVisitor. 182 R->markInteresting(BaseClassRegion); 183 R->addVisitor<PtrCastVisitor>(); 184 C.emitReport(std::move(R)); 185 } 186 187 PathDiagnosticPieceRef 188 CXXDeleteChecker::PtrCastVisitor::VisitNode(const ExplodedNode *N, 189 BugReporterContext &BRC, 190 PathSensitiveBugReport &BR) { 191 const Stmt *S = N->getStmtForDiagnostics(); 192 if (!S) 193 return nullptr; 194 195 const auto *CastE = dyn_cast<CastExpr>(S); 196 if (!CastE) 197 return nullptr; 198 199 // FIXME: This way of getting base types does not support reference types. 200 QualType SourceType = CastE->getSubExpr()->getType()->getPointeeType(); 201 QualType TargetType = CastE->getType()->getPointeeType(); 202 203 if (SourceType.isNull() || TargetType.isNull() || SourceType == TargetType) 204 return nullptr; 205 206 // Region associated with the current cast expression. 207 const MemRegion *M = N->getSVal(CastE).getAsRegion(); 208 if (!M) 209 return nullptr; 210 211 // Check if target region was marked as problematic previously. 212 if (!BR.isInteresting(M)) 213 return nullptr; 214 215 SmallString<256> Buf; 216 llvm::raw_svector_ostream OS(Buf); 217 218 OS << "Casting from '" << SourceType.getAsString() << "' to '" 219 << TargetType.getAsString() << "' here"; 220 221 PathDiagnosticLocation Pos(S, BRC.getSourceManager(), 222 N->getLocationContext()); 223 return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), 224 /*addPosRange=*/true); 225 } 226 227 void ento::registerCXXArrayDeleteChecker(CheckerManager &mgr) { 228 mgr.registerChecker<CXXArrayDeleteChecker>(); 229 } 230 231 bool ento::shouldRegisterCXXArrayDeleteChecker(const CheckerManager &mgr) { 232 return true; 233 } 234 235 void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { 236 mgr.registerChecker<DeleteWithNonVirtualDtorChecker>(); 237 } 238 239 bool ento::shouldRegisterDeleteWithNonVirtualDtorChecker( 240 const CheckerManager &mgr) { 241 return true; 242 } 243