Skip to content

Commit 0e246bb

Browse files
committed
[clang][analyzer] Add C++ array delete checker
This checker reports cases where an array of polymorphic objects are deleted as their base class. Deleting an array where the array's static type is different from its dynamic type is undefined. Since the checker is similar to DeleteWithNonVirtualDtorChecker, I refactored that checker to support more detection types. This checker corresponds to the SEI Cert rule EXP51-CPP: Do not delete an array through a pointer of the incorrect type. Differential Revision: https://reviews.llvm.org/D158156
1 parent 71ae858 commit 0e246bb

File tree

6 files changed

+319
-68
lines changed

6 files changed

+319
-68
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,30 @@ Either the comparison is useless or there is division by zero.
18341834
alpha.cplusplus
18351835
^^^^^^^^^^^^^^^
18361836
1837+
.. _alpha-cplusplus-ArrayDelete:
1838+
1839+
alpha.cplusplus.ArrayDelete (C++)
1840+
"""""""""""""""""""""""""""""""""
1841+
Reports destructions of arrays of polymorphic objects that are destructed as their base class.
1842+
This checker corresponds to the CERT rule `EXP51-CPP: Do not delete an array through a pointer of the incorrect type <https://wiki.sei.cmu.edu/confluence/display/cplusplus/EXP51-CPP.+Do+not+delete+an+array+through+a+pointer+of+the+incorrect+type>`_.
1843+
1844+
.. code-block:: cpp
1845+
1846+
class Base {
1847+
virtual ~Base() {}
1848+
};
1849+
class Derived : public Base {}
1850+
1851+
Base *create() {
1852+
Base *x = new Derived[10]; // note: Casting from 'Derived' to 'Base' here
1853+
return x;
1854+
}
1855+
1856+
void foo() {
1857+
Base *x = create();
1858+
delete[] x; // warn: Deleting an array of 'Derived' objects as their base class 'Base' is undefined
1859+
}
1860+
18371861
.. _alpha-cplusplus-DeleteWithNonVirtualDtor:
18381862
18391863
alpha.cplusplus.DeleteWithNonVirtualDtor (C++)
@@ -1842,13 +1866,17 @@ Reports destructions of polymorphic objects with a non-virtual destructor in the
18421866
18431867
.. code-block:: cpp
18441868
1869+
class NonVirtual {};
1870+
class NVDerived : public NonVirtual {};
1871+
18451872
NonVirtual *create() {
1846-
NonVirtual *x = new NVDerived(); // note: conversion from derived to base
1847-
// happened here
1873+
NonVirtual *x = new NVDerived(); // note: Casting from 'NVDerived' to
1874+
// 'NonVirtual' here
18481875
return x;
18491876
}
18501877
1851-
void sink(NonVirtual *x) {
1878+
void foo() {
1879+
NonVirtual *x = create();
18521880
delete x; // warn: destruction of a polymorphic object with no virtual
18531881
// destructor
18541882
}

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,11 @@ def ContainerModeling : Checker<"ContainerModeling">,
758758
Documentation<NotDocumented>,
759759
Hidden;
760760

761+
def CXXArrayDeleteChecker : Checker<"ArrayDelete">,
762+
HelpText<"Reports destructions of arrays of polymorphic objects that are "
763+
"destructed as their base class.">,
764+
Documentation<HasDocumentation>;
765+
761766
def DeleteWithNonVirtualDtorChecker : Checker<"DeleteWithNonVirtualDtor">,
762767
HelpText<"Reports destructions of polymorphic objects with a non-virtual "
763768
"destructor in their base class">,
Lines changed: 133 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
1-
//===-- DeleteWithNonVirtualDtorChecker.cpp -----------------------*- C++ -*--//
1+
//=== CXXDeleteChecker.cpp -------------------------------------*- C++ -*--===//
22
//
33
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
44
// See https://llvm.org/LICENSE.txt for license information.
55
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66
//
77
//===----------------------------------------------------------------------===//
88
//
9-
// Defines a checker for the OOP52-CPP CERT rule: Do not delete a polymorphic
10-
// object without a virtual destructor.
9+
// This file defines the following new checkers for C++ delete expressions:
1110
//
12-
// Diagnostic flags -Wnon-virtual-dtor and -Wdelete-non-virtual-dtor report if
13-
// an object with a virtual function but a non-virtual destructor exists or is
14-
// deleted, respectively.
11+
// * DeleteWithNonVirtualDtorChecker
12+
// Defines a checker for the OOP52-CPP CERT rule: Do not delete a
13+
// polymorphic object without a virtual destructor.
1514
//
16-
// This check exceeds them by comparing the dynamic and static types of the
17-
// object at the point of destruction and only warns if it happens through a
18-
// pointer to a base type without a virtual destructor. The check places a note
19-
// at the last point where the conversion from derived to base happened.
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.
2028
//
2129
//===----------------------------------------------------------------------===//
2230

@@ -34,51 +42,78 @@ using namespace clang;
3442
using namespace ento;
3543

3644
namespace {
37-
class DeleteWithNonVirtualDtorChecker
38-
: public Checker<check::PreStmt<CXXDeleteExpr>> {
39-
mutable std::unique_ptr<BugType> BT;
40-
41-
class DeleteBugVisitor : public BugReporterVisitor {
45+
class CXXDeleteChecker : public Checker<check::PreStmt<CXXDeleteExpr>> {
46+
protected:
47+
class PtrCastVisitor : public BugReporterVisitor {
4248
public:
43-
DeleteBugVisitor() = default;
4449
void Profile(llvm::FoldingSetNodeID &ID) const override {
4550
static int X = 0;
4651
ID.AddPointer(&X);
4752
}
4853
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
4954
BugReporterContext &BRC,
5055
PathSensitiveBugReport &BR) override;
51-
52-
private:
53-
bool Satisfied = false;
5456
};
5557

58+
virtual void
59+
checkTypedDeleteExpr(const CXXDeleteExpr *DE, CheckerContext &C,
60+
const TypedValueRegion *BaseClassRegion,
61+
const SymbolicRegion *DerivedClassRegion) const = 0;
62+
5663
public:
5764
void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const;
5865
};
59-
} // end anonymous namespace
6066

61-
void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE,
62-
CheckerContext &C) const {
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 {
6388
const Expr *DeletedObj = DE->getArgument();
6489
const MemRegion *MR = C.getSVal(DeletedObj).getAsRegion();
6590
if (!MR)
6691
return;
6792

93+
OverloadedOperatorKind DeleteKind =
94+
DE->getOperatorDelete()->getOverloadedOperator();
95+
96+
if (DeleteKind != OO_Delete && DeleteKind != OO_Array_Delete)
97+
return;
98+
6899
const auto *BaseClassRegion = MR->getAs<TypedValueRegion>();
69100
const auto *DerivedClassRegion = MR->getBaseRegion()->getAs<SymbolicRegion>();
70101
if (!BaseClassRegion || !DerivedClassRegion)
71102
return;
72103

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 {
73111
const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl();
74112
const auto *DerivedClass =
75113
DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl();
76114
if (!BaseClass || !DerivedClass)
77115
return;
78116

79-
if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition())
80-
return;
81-
82117
if (BaseClass->getDestructor()->isVirtual())
83118
return;
84119

@@ -94,22 +129,65 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE,
94129
ExplodedNode *N = C.generateNonFatalErrorNode();
95130
if (!N)
96131
return;
97-
auto R = std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N);
132+
auto R =
133+
std::make_unique<PathSensitiveBugReport>(*BT, BT->getDescription(), N);
98134

99135
// Mark region of problematic base class for later use in the BugVisitor.
100136
R->markInteresting(BaseClassRegion);
101-
R->addVisitor(std::make_unique<DeleteBugVisitor>());
137+
R->addVisitor<PtrCastVisitor>();
102138
C.emitReport(std::move(R));
103139
}
104140

105-
PathDiagnosticPieceRef
106-
DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode(
107-
const ExplodedNode *N, BugReporterContext &BRC,
108-
PathSensitiveBugReport &BR) {
109-
// Stop traversal after the first conversion was found on a path.
110-
if (Satisfied)
111-
return nullptr;
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;
112150

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) {
113191
const Stmt *S = N->getStmtForDiagnostics();
114192
if (!S)
115193
return nullptr;
@@ -118,12 +196,12 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode(
118196
if (!CastE)
119197
return nullptr;
120198

121-
// Only interested in DerivedToBase implicit casts.
122-
// Explicit casts can have different CastKinds.
123-
if (const auto *ImplCastE = dyn_cast<ImplicitCastExpr>(CastE)) {
124-
if (ImplCastE->getCastKind() != CK_DerivedToBase)
125-
return nullptr;
126-
}
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;
127205

128206
// Region associated with the current cast expression.
129207
const MemRegion *M = N->getSVal(CastE).getAsRegion();
@@ -134,22 +212,31 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode(
134212
if (!BR.isInteresting(M))
135213
return nullptr;
136214

137-
// Stop traversal on this path.
138-
Satisfied = true;
139-
140215
SmallString<256> Buf;
141216
llvm::raw_svector_ostream OS(Buf);
142-
OS << "Conversion from derived to base happened here";
217+
218+
OS << "Casting from '" << SourceType.getAsString() << "' to '"
219+
<< TargetType.getAsString() << "' here";
220+
143221
PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
144222
N->getLocationContext());
145-
return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
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;
146233
}
147234

148235
void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) {
149236
mgr.registerChecker<DeleteWithNonVirtualDtorChecker>();
150237
}
151238

152239
bool ento::shouldRegisterDeleteWithNonVirtualDtorChecker(
153-
const CheckerManager &mgr) {
240+
const CheckerManager &mgr) {
154241
return true;
155242
}

0 commit comments

Comments
 (0)