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