Skip to content

Commit 1454b5b

Browse files
committed
[clang][analyzer]Add C++ polymorphic ptr arithmetic checker
1 parent 38c706e commit 1454b5b

File tree

5 files changed

+358
-0
lines changed

5 files changed

+358
-0
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,39 @@ store combinations of flag values:
581581
582582
Projects that use this pattern should not enable this optin checker.
583583
584+
.. _optin-cplusplus-PolymorphicPtrArithmetic:
585+
586+
optin.cplusplus.PolymorphicPtrArithmetic (C++)
587+
""""""""""""""""""""""""""""""""""""""""""""""
588+
589+
This checker reports pointer arithmetic operations on arrays of polymorphic
590+
objects, where the array has the type of its base class.
591+
Deleting an array where the array's static type is different from its dynamic
592+
type is undefined.
593+
594+
This checker corresponds to the CERT rule `CTR56-CPP: Do not use pointer arithmetic on polymorphic objects <https://wiki.sei.cmu.edu/confluence/display/cplusplus/CTR56-CPP.+Do+not+use+pointer+arithmetic+on+polymorphic+objects>`_.
595+
596+
.. code-block:: cpp
597+
598+
class Base {
599+
public:
600+
int member = 0;
601+
virtual ~Base() {}
602+
};
603+
class Derived : public Base {}
604+
605+
Base *create() {
606+
Base *x = new Derived[10]; // note: Casting from 'Derived' to 'Base' here
607+
return x;
608+
}
609+
610+
void foo() {
611+
Base *x = create();
612+
(x + 3)->member += 1; // warn: Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined
613+
x[3].member += 1; // warn: Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined
614+
delete[] static_cast<Derived*>(x);
615+
}
616+
584617
.. _optin-cplusplus-UninitializedObject:
585618
586619
optin.cplusplus.UninitializedObject (C++)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,11 @@ def PureVirtualCallChecker : Checker<"PureVirtualCall">,
706706

707707
let ParentPackage = CplusplusOptIn in {
708708

709+
def PolymorphicPtrArithmeticChecker : Checker<"PolymorphicPtrArithmetic">,
710+
HelpText<"Reports doing pointer arithmetic on arrays of polymorphic objects "
711+
"with the type of their base class">,
712+
Documentation<HasDocumentation>;
713+
709714
def UninitializedObjectChecker: Checker<"UninitializedObject">,
710715
HelpText<"Reports uninitialized fields after object construction">,
711716
CheckerOptions<[

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_clang_library(clangStaticAnalyzerCheckers
3131
ContainerModeling.cpp
3232
ConversionChecker.cpp
3333
CXXDeleteChecker.cpp
34+
PolymorphicPtrArithmetic.cpp
3435
CXXSelfAssignmentChecker.cpp
3536
DeadStoresChecker.cpp
3637
DebugCheckers.cpp
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
//=== PolymorphicPtrArithmetic.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 checker reports pointer arithmetic operations on arrays of
10+
// polymorphic objects, where the array has the type of its base class.
11+
// Corresponds to the CTR56-CPP. Do not use pointer arithmetic on
12+
// polymorphic objects
13+
//
14+
//===----------------------------------------------------------------------===//
15+
16+
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
17+
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
18+
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19+
#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
20+
#include "clang/StaticAnalyzer/Core/Checker.h"
21+
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
22+
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
23+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
24+
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h"
25+
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
26+
27+
using namespace clang;
28+
using namespace ento;
29+
30+
namespace {
31+
class PolymorphicPtrArithmeticChecker
32+
: public Checker<check::PreStmt<BinaryOperator>,
33+
check::PreStmt<ArraySubscriptExpr>> {
34+
const BugType BT{this,
35+
"Pointer arithmetic on polymorphic objects is undefined"};
36+
37+
protected:
38+
class PtrCastVisitor : public BugReporterVisitor {
39+
public:
40+
void Profile(llvm::FoldingSetNodeID &ID) const override {
41+
static int X = 0;
42+
ID.AddPointer(&X);
43+
}
44+
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
45+
BugReporterContext &BRC,
46+
PathSensitiveBugReport &BR) override;
47+
};
48+
49+
void checkTypedExpr(const Expr *E, CheckerContext &C) const;
50+
51+
public:
52+
void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
53+
void checkPreStmt(const ArraySubscriptExpr *B, CheckerContext &C) const;
54+
};
55+
} // namespace
56+
57+
void PolymorphicPtrArithmeticChecker::checkPreStmt(const BinaryOperator *B,
58+
CheckerContext &C) const {
59+
if (!B->isAdditiveOp())
60+
return;
61+
62+
bool IsLHSPtr = B->getLHS()->getType()->isAnyPointerType();
63+
bool IsRHSPtr = B->getRHS()->getType()->isAnyPointerType();
64+
if (!IsLHSPtr && !IsRHSPtr)
65+
return;
66+
67+
const Expr *E = IsLHSPtr ? B->getLHS() : B->getRHS();
68+
69+
checkTypedExpr(E, C);
70+
}
71+
72+
void PolymorphicPtrArithmeticChecker::checkPreStmt(const ArraySubscriptExpr *B,
73+
CheckerContext &C) const {
74+
bool IsLHSPtr = B->getLHS()->getType()->isAnyPointerType();
75+
bool IsRHSPtr = B->getRHS()->getType()->isAnyPointerType();
76+
if (!IsLHSPtr && !IsRHSPtr)
77+
return;
78+
79+
const Expr *E = IsLHSPtr ? B->getLHS() : B->getRHS();
80+
81+
checkTypedExpr(E, C);
82+
}
83+
84+
void PolymorphicPtrArithmeticChecker::checkTypedExpr(const Expr *E,
85+
CheckerContext &C) const {
86+
const MemRegion *MR = C.getSVal(E).getAsRegion();
87+
if (!MR)
88+
return;
89+
90+
const auto *BaseClassRegion = MR->getAs<TypedValueRegion>();
91+
const auto *DerivedClassRegion = MR->getBaseRegion()->getAs<SymbolicRegion>();
92+
if (!BaseClassRegion || !DerivedClassRegion)
93+
return;
94+
95+
const auto *BaseClass = BaseClassRegion->getValueType()->getAsCXXRecordDecl();
96+
const auto *DerivedClass =
97+
DerivedClassRegion->getSymbol()->getType()->getPointeeCXXRecordDecl();
98+
if (!BaseClass || !DerivedClass)
99+
return;
100+
101+
if (!BaseClass->hasDefinition() || !DerivedClass->hasDefinition())
102+
return;
103+
104+
if (!DerivedClass->isDerivedFrom(BaseClass))
105+
return;
106+
107+
ExplodedNode *N = C.generateNonFatalErrorNode();
108+
if (!N)
109+
return;
110+
111+
SmallString<256> Buf;
112+
llvm::raw_svector_ostream OS(Buf);
113+
114+
QualType SourceType = BaseClassRegion->getValueType();
115+
QualType TargetType =
116+
DerivedClassRegion->getSymbol()->getType()->getPointeeType();
117+
118+
OS << "Doing pointer arithmetic with '" << TargetType.getAsString()
119+
<< "' objects as their base class '"
120+
<< SourceType.getAsString(C.getASTContext().getPrintingPolicy())
121+
<< "' is undefined";
122+
123+
auto R = std::make_unique<PathSensitiveBugReport>(BT, OS.str(), N);
124+
125+
// Mark region of problematic base class for later use in the BugVisitor.
126+
R->markInteresting(BaseClassRegion);
127+
R->addVisitor<PtrCastVisitor>();
128+
C.emitReport(std::move(R));
129+
}
130+
131+
PathDiagnosticPieceRef
132+
PolymorphicPtrArithmeticChecker::PtrCastVisitor::VisitNode(
133+
const ExplodedNode *N, BugReporterContext &BRC,
134+
PathSensitiveBugReport &BR) {
135+
const Stmt *S = N->getStmtForDiagnostics();
136+
if (!S)
137+
return nullptr;
138+
139+
const auto *CastE = dyn_cast<CastExpr>(S);
140+
if (!CastE)
141+
return nullptr;
142+
143+
// FIXME: This way of getting base types does not support reference types.
144+
QualType SourceType = CastE->getSubExpr()->getType()->getPointeeType();
145+
QualType TargetType = CastE->getType()->getPointeeType();
146+
147+
if (SourceType.isNull() || TargetType.isNull() || SourceType == TargetType)
148+
return nullptr;
149+
150+
// Region associated with the current cast expression.
151+
const MemRegion *M = N->getSVal(CastE).getAsRegion();
152+
if (!M)
153+
return nullptr;
154+
155+
// Check if target region was marked as problematic previously.
156+
if (!BR.isInteresting(M))
157+
return nullptr;
158+
159+
SmallString<256> Buf;
160+
llvm::raw_svector_ostream OS(Buf);
161+
162+
OS << "Casting from '" << SourceType.getAsString() << "' to '"
163+
<< TargetType.getAsString() << "' here";
164+
165+
PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
166+
N->getLocationContext());
167+
return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(),
168+
/*addPosRange=*/true);
169+
}
170+
171+
void ento::registerPolymorphicPtrArithmeticChecker(CheckerManager &mgr) {
172+
mgr.registerChecker<PolymorphicPtrArithmeticChecker>();
173+
}
174+
175+
bool ento::shouldRegisterPolymorphicPtrArithmeticChecker(
176+
const CheckerManager &mgr) {
177+
return true;
178+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// RUN: %clang_cc1 -analyze -analyzer-checker=optin.cplusplus.PolymorphicPtrArithmetic -std=c++11 -verify -analyzer-output=text %s
2+
3+
struct Base {
4+
int x = 0;
5+
virtual ~Base() = default;
6+
};
7+
8+
struct Derived : public Base {};
9+
10+
struct DoubleDerived : public Derived {};
11+
12+
Derived *get();
13+
14+
Base *create() {
15+
Base *b = new Derived[3]; // expected-note{{Casting from 'Derived' to 'Base' here}}
16+
return b;
17+
}
18+
19+
void sink(Base *b) {
20+
b[1].x++; // expected-warning{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
21+
// expected-note@-1{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
22+
delete[] static_cast<Derived*>(b);
23+
}
24+
25+
void sink_cast(Base *b) {
26+
static_cast<Derived*>(b)[1].x++; // no-warning
27+
delete[] static_cast<Derived*>(b);
28+
}
29+
30+
void sink_derived(Derived *d) {
31+
d[1].x++; // no-warning
32+
delete[] static_cast<Derived*>(d);
33+
}
34+
35+
void same_function() {
36+
Base *sd = new Derived[10]; // expected-note{{Casting from 'Derived' to 'Base' here}}
37+
// expected-note@-1{{Casting from 'Derived' to 'Base' here}}
38+
sd[1].x++; // expected-warning{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
39+
// expected-note@-1{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
40+
(sd + 1)->x++; // expected-warning{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
41+
// expected-note@-1{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
42+
delete[] static_cast<Derived*>(sd);
43+
44+
Base *dd = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}}
45+
dd[1].x++; // expected-warning{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Base' is undefined}}
46+
// expected-note@-1{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Base' is undefined}}
47+
delete[] static_cast<DoubleDerived*>(dd);
48+
49+
}
50+
51+
void different_function() {
52+
Base *assigned = get(); // expected-note{{Casting from 'Derived' to 'Base' here}}
53+
assigned[1].x++; // expected-warning{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
54+
// expected-note@-1{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
55+
delete[] static_cast<Derived*>(assigned);
56+
57+
Base *indirect;
58+
indirect = get(); // expected-note{{Casting from 'Derived' to 'Base' here}}
59+
indirect[1].x++; // expected-warning{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
60+
// expected-note@-1{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
61+
delete[] static_cast<Derived*>(indirect);
62+
63+
Base *created = create(); // expected-note{{Calling 'create'}}
64+
// expected-note@-1{{Returning from 'create'}}
65+
created[1].x++; // expected-warning{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
66+
// expected-note@-1{{Doing pointer arithmetic with 'Derived' objects as their base class 'Base' is undefined}}
67+
delete[] static_cast<Derived*>(created);
68+
69+
Base *sb = new Derived[10]; // expected-note{{Casting from 'Derived' to 'Base' here}}
70+
sink(sb); // expected-note{{Calling 'sink'}}
71+
}
72+
73+
void safe_function() {
74+
Derived *d = new Derived[10];
75+
d[1].x++; // no-warning
76+
delete[] static_cast<Derived*>(d);
77+
78+
Base *b = new Derived[10];
79+
static_cast<Derived*>(b)[1].x++; // no-warning
80+
delete[] static_cast<Derived*>(b);
81+
82+
Base *sb = new Derived[10];
83+
sink_cast(sb); // no-warning
84+
85+
Derived *sd = new Derived[10];
86+
sink_derived(sd); // no-warning
87+
}
88+
89+
void multiple_derived() {
90+
Base *b = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}}
91+
b[1].x++; // expected-warning{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Base' is undefined}}
92+
// expected-note@-1{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Base' is undefined}}
93+
delete[] static_cast<DoubleDerived*>(b);
94+
95+
Base *b2 = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}}
96+
Derived *d2 = static_cast<Derived*>(b2); // expected-note{{Casting from 'Base' to 'Derived' here}}
97+
d2[1].x++; // expected-warning{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Derived' is undefined}}
98+
// expected-note@-1{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Derived' is undefined}}
99+
delete[] static_cast<DoubleDerived*>(d2);
100+
101+
Derived *d3 = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Derived' here}}
102+
Base *b3 = d3; // expected-note{{Casting from 'Derived' to 'Base' here}}
103+
b3[1].x++; // expected-warning{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Base' is undefined}}
104+
// expected-note@-1{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Base' is undefined}}
105+
delete[] static_cast<DoubleDerived*>(b3);
106+
107+
Base *b4 = new DoubleDerived[10];
108+
Derived *d4 = static_cast<Derived*>(b4);
109+
DoubleDerived *dd4 = static_cast<DoubleDerived*>(d4);
110+
dd4[1].x++; // no-warning
111+
delete[] static_cast<DoubleDerived*>(dd4);
112+
113+
Base *b5 = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}}
114+
DoubleDerived *dd5 = static_cast<DoubleDerived*>(b5); // expected-note{{Casting from 'Base' to 'DoubleDerived' here}}
115+
Derived *d5 = dd5; // expected-note{{Casting from 'DoubleDerived' to 'Derived' here}}
116+
d5[1].x++; // expected-warning{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Derived' is undefined}}
117+
// expected-note@-1{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Derived' is undefined}}
118+
delete[] static_cast<DoubleDerived*>(d5);
119+
}
120+
121+
void other_operators() {
122+
Derived *d = new Derived[10];
123+
Base *b1 = d;
124+
Base *b2 = d + 1;
125+
Base *b3 = new Derived[10];
126+
if (b1 < b2) return; // no-warning
127+
if (b1 == b3) return; // no-warning
128+
}
129+
130+
void unrelated_casts() {
131+
Base *b = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}}
132+
Base &b2 = *b; // no-note: See the FIXME.
133+
134+
// FIXME: Displaying casts of reference types is not supported.
135+
Derived &d2 = static_cast<Derived&>(b2); // no-note: See the FIXME.
136+
137+
Derived *d = &d2; // no-note: See the FIXME.
138+
d[1].x++; // expected-warning{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Derived' is undefined}}
139+
// expected-note@-1{{Doing pointer arithmetic with 'DoubleDerived' objects as their base class 'Derived' is undefined}}
140+
delete[] static_cast<DoubleDerived*>(d);
141+
}

0 commit comments

Comments
 (0)