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->hasDefinition() || !DerivedClass->hasDefinition()) 118 return; 119 120 if (BaseClass->getDestructor()->isVirtual()) 121 return; 122 123 if (!DerivedClass->isDerivedFrom(BaseClass)) 124 return; 125 126 if (!BT) 127 BT.reset(new BugType(this, 128 "Destruction of a polymorphic object with no " 129 "virtual destructor", 130 "Logic error")); 131 132 ExplodedNode *N = C.generateNonFatalErrorNode(); 133 if (!N) 134 return; 135 auto R = 136 std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N); 137 138 // Mark region of problematic base class for later use in the BugVisitor. 139 R->markInteresting(BaseClassRegion); 140 R->addVisitor<PtrCastVisitor>(); 141 C.emitReport(std::move(R)); 142 } 143 144 void CXXArrayDeleteChecker::checkTypedDeleteExpr( 145 const CXXDeleteExpr *DE, CheckerContext &C, 146 const TypedValueRegion *BaseClassRegion, 147 const SymbolicRegion *DerivedClassRegion) const { 148 const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); 149 const auto *DerivedClass = 150 DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); 151 if (!BaseClass || !DerivedClass) 152 return; 153 154 if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition()) 155 return; 156 157 if (DE->getOperatorDelete()->getOverloadedOperator() != OO_Array_Delete) 158 return; 159 160 if (!DerivedClass->isDerivedFrom(BaseClass)) 161 return; 162 163 if (!BT) 164 BT.reset(new BugType(this, 165 "Deleting an array of polymorphic objects " 166 "is undefined", 167 "Logic error")); 168 169 ExplodedNode *N = C.generateNonFatalErrorNode(); 170 if (!N) 171 return; 172 173 SmallString<256> Buf; 174 llvm::raw_svector_ostream OS(Buf); 175 176 QualType SourceType = BaseClassRegion->getValueType(); 177 QualType TargetType = 178 DerivedClassRegion->getSymbol()->getType()->getPointeeType(); 179 180 OS << "Deleting an array of '" << TargetType.getAsString() 181 << "' objects as their base class '" 182 << SourceType.getAsString(C.getASTContext().getPrintingPolicy()) 183 << "' is undefined"; 184 185 auto R = std::make_unique<PathSensitiveBugReport>(*BT, OS.str(), N); 186 187 // Mark region of problematic base class for later use in the BugVisitor. 188 R->markInteresting(BaseClassRegion); 189 R->addVisitor<PtrCastVisitor>(); 190 C.emitReport(std::move(R)); 191 } 192 193 PathDiagnosticPieceRef 194 CXXDeleteChecker::PtrCastVisitor::VisitNode(const ExplodedNode *N, 195 BugReporterContext &BRC, 196 PathSensitiveBugReport &BR) { 197 const Stmt *S = N->getStmtForDiagnostics(); 198 if (!S) 199 return nullptr; 200 201 const auto *CastE = dyn_cast<CastExpr>(S); 202 if (!CastE) 203 return nullptr; 204 205 // FIXME: This way of getting base types does not support reference types. 206 QualType SourceType = CastE->getSubExpr()->getType()->getPointeeType(); 207 QualType TargetType = CastE->getType()->getPointeeType(); 208 209 if (SourceType.isNull() || TargetType.isNull() || SourceType == TargetType) 210 return nullptr; 211 212 // Region associated with the current cast expression. 213 const MemRegion *M = N->getSVal(CastE).getAsRegion(); 214 if (!M) 215 return nullptr; 216 217 // Check if target region was marked as problematic previously. 218 if (!BR.isInteresting(M)) 219 return nullptr; 220 221 SmallString<256> Buf; 222 llvm::raw_svector_ostream OS(Buf); 223 224 OS << "Casting from '" << SourceType.getAsString() << "' to '" 225 << TargetType.getAsString() << "' here"; 226 227 PathDiagnosticLocation Pos(S, BRC.getSourceManager(), 228 N->getLocationContext()); 229 return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), 230 /*addPosRange=*/true); 231 } 232 233 void ento::registerCXXArrayDeleteChecker(CheckerManager &mgr) { 234 mgr.registerChecker<CXXArrayDeleteChecker>(); 235 } 236 237 bool ento::shouldRegisterCXXArrayDeleteChecker(const CheckerManager &mgr) { 238 return true; 239 } 240 241 void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { 242 mgr.registerChecker<DeleteWithNonVirtualDtorChecker>(); 243 } 244 245 bool ento::shouldRegisterDeleteWithNonVirtualDtorChecker( 246 const CheckerManager &mgr) { 247 return true; 248 } 249