//=== CXXDeleteChecker.cpp -------------------------------------*- C++ -*--===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file defines the following new checkers for C++ delete expressions: // // * DeleteWithNonVirtualDtorChecker // Defines a checker for the OOP52-CPP CERT rule: Do not delete a // polymorphic object without a virtual destructor. // // Diagnostic flags -Wnon-virtual-dtor and -Wdelete-non-virtual-dtor // report if an object with a virtual function but a non-virtual // destructor exists or is deleted, respectively. // // This check exceeds them by comparing the dynamic and static types of // the object at the point of destruction and only warns if it happens // through a pointer to a base type without a virtual destructor. The // check places a note at the last point where the conversion from // derived to base happened. // // * CXXArrayDeleteChecker // Defines a checker for the EXP51-CPP CERT rule: Do not delete an array // through a pointer of the incorrect type. // //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; using namespace ento; namespace { class CXXDeleteChecker : public Checker> { protected: class PtrCastVisitor : public BugReporterVisitor { public: void Profile(llvm::FoldingSetNodeID &ID) const override { static int X = 0; ID.AddPointer(&X); } PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) override; }; virtual void checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, const TypedValueRegion *BaseClassRegion, const SymbolicRegion *DerivedClassRegion) const = 0; public: void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; }; class DeleteWithNonVirtualDtorChecker : public CXXDeleteChecker { const BugType BT{ this, "Destruction of a polymorphic object with no virtual destructor"}; void checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, const TypedValueRegion *BaseClassRegion, const SymbolicRegion *DerivedClassRegion) const override; }; class CXXArrayDeleteChecker : public CXXDeleteChecker { const BugType BT{this, "Deleting an array of polymorphic objects is undefined"}; void checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C, const TypedValueRegion *BaseClassRegion, const SymbolicRegion *DerivedClassRegion) const override; }; } // namespace void CXXDeleteChecker::checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const { const Expr *DeletedObj = DE->getArgument(); const MemRegion *MR = C.getSVal(DeletedObj).getAsRegion(); if (!MR) return; OverloadedOperatorKind DeleteKind = DE->getOperatorDelete()->getOverloadedOperator(); if (DeleteKind != OO_Delete && DeleteKind != OO_Array_Delete) return; const auto *BaseClassRegion = MR->getAs(); const auto *DerivedClassRegion = MR->getBaseRegion()->getAs(); if (!BaseClassRegion || !DerivedClassRegion) return; checkTypedDeleteExpr(DE, C, BaseClassRegion, DerivedClassRegion); } void DeleteWithNonVirtualDtorChecker::checkTypedDeleteExpr( const CXXDeleteExpr *DE, CheckerContext &C, const TypedValueRegion *BaseClassRegion, const SymbolicRegion *DerivedClassRegion) const { const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); const auto *DerivedClass = DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); if (!BaseClass || !DerivedClass) return; if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition()) return; if (BaseClass->getDestructor()->isVirtual()) return; if (!DerivedClass->isDerivedFrom(BaseClass)) return; ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) return; auto R = std::make_unique(BT, BT.getDescription(), N); // Mark region of problematic base class for later use in the BugVisitor. R->markInteresting(BaseClassRegion); R->addVisitor(); C.emitReport(std::move(R)); } void CXXArrayDeleteChecker::checkTypedDeleteExpr( const CXXDeleteExpr *DE, CheckerContext &C, const TypedValueRegion *BaseClassRegion, const SymbolicRegion *DerivedClassRegion) const { const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl(); const auto *DerivedClass = DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl(); if (!BaseClass || !DerivedClass) return; if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition()) return; if (DE->getOperatorDelete()->getOverloadedOperator() != OO_Array_Delete) return; if (!DerivedClass->isDerivedFrom(BaseClass)) return; ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) return; SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); QualType SourceType = BaseClassRegion->getValueType(); QualType TargetType = DerivedClassRegion->getSymbol()->getType()->getPointeeType(); OS << "Deleting an array of '" << TargetType.getAsString() << "' objects as their base class '" << SourceType.getAsString(C.getASTContext().getPrintingPolicy()) << "' is undefined"; auto R = std::make_unique(BT, OS.str(), N); // Mark region of problematic base class for later use in the BugVisitor. R->markInteresting(BaseClassRegion); R->addVisitor(); C.emitReport(std::move(R)); } PathDiagnosticPieceRef CXXDeleteChecker::PtrCastVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; const auto *CastE = dyn_cast(S); if (!CastE) return nullptr; // FIXME: This way of getting base types does not support reference types. QualType SourceType = CastE->getSubExpr()->getType()->getPointeeType(); QualType TargetType = CastE->getType()->getPointeeType(); if (SourceType.isNull() || TargetType.isNull() || SourceType == TargetType) return nullptr; // Region associated with the current cast expression. const MemRegion *M = N->getSVal(CastE).getAsRegion(); if (!M) return nullptr; // Check if target region was marked as problematic previously. if (!BR.isInteresting(M)) return nullptr; SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); OS << "Casting from '" << SourceType.getAsString() << "' to '" << TargetType.getAsString() << "' here"; PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); return std::make_shared(Pos, OS.str(), /*addPosRange=*/true); } void ento::registerCXXArrayDeleteChecker(CheckerManager &mgr) { mgr.registerChecker(); } bool ento::shouldRegisterCXXArrayDeleteChecker(const CheckerManager &mgr) { return true; } void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { mgr.registerChecker(); } bool ento::shouldRegisterDeleteWithNonVirtualDtorChecker( const CheckerManager &mgr) { return true; }