Skip to content

Commit cab91ec

Browse files
authored
[clang][analyzer] Improve PointerSubChecker (#96501)
The checker could report false positives if pointer arithmetic was done on pointers to non-array data before pointer subtraction. Another problem is fixed that could cause false positive if members of the same structure but in different memory objects are subtracted.
1 parent 05d3f5e commit cab91ec

File tree

3 files changed

+117
-22
lines changed

3 files changed

+117
-22
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2498,15 +2498,49 @@ Check for pointer arithmetic on locations other than array elements.
24982498
24992499
alpha.core.PointerSub (C)
25002500
"""""""""""""""""""""""""
2501-
Check for pointer subtractions on two pointers pointing to different memory chunks.
2501+
Check for pointer subtractions on two pointers pointing to different memory
2502+
chunks. According to the C standard §6.5.6 only subtraction of pointers that
2503+
point into (or one past the end) the same array object is valid (for this
2504+
purpose non-array variables are like arrays of size 1).
25022505
25032506
.. code-block:: c
25042507
25052508
void test() {
2506-
int x, y;
2507-
int d = &y - &x; // warn
2509+
int a, b, c[10], d[10];
2510+
int x = &c[3] - &c[1];
2511+
x = &d[4] - &c[1]; // warn: 'c' and 'd' are different arrays
2512+
x = (&a + 1) - &a;
2513+
x = &b - &a; // warn: 'a' and 'b' are different variables
2514+
x = (&a + 2) - &a; // warn: for a variable it is only valid to have a pointer
2515+
// to one past the address of it
2516+
x = &c[10] - &c[0];
2517+
x = &c[11] - &c[0]; // warn: index larger than one past the end
2518+
x = &c[-1] - &c[0]; // warn: negative index
25082519
}
25092520
2521+
struct S {
2522+
int x[10];
2523+
int y[10];
2524+
};
2525+
2526+
void test1() {
2527+
struct S a[10];
2528+
struct S b;
2529+
int d = &a[4] - &a[6];
2530+
d = &a[0].x[3] - &a[0].x[1];
2531+
d = a[0].y - a[0].x; // warn: 'S.b' and 'S.a' are different objects
2532+
d = (char *)&b.y - (char *)&b.x; // warn: different members of the same object
2533+
d = (char *)&b.y - (char *)&b; // warn: object of type S is not the same array as a member of it
2534+
}
2535+
2536+
There may be existing applications that use code like above for calculating
2537+
offsets of members in a structure, using pointer subtractions. This is still
2538+
undefined behavior according to the standard and code like this can be replaced
2539+
with the `offsetof` macro.
2540+
2541+
The checker only reports cases where stack-allocated objects are involved (no
2542+
warnings on pointers to memory allocated by `malloc`).
2543+
25102544
.. _alpha-core-StackAddressAsyncEscape:
25112545
25122546
alpha.core.StackAddressAsyncEscape (C)

clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
2020
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
2121
#include "llvm/ADT/StringRef.h"
22+
#include "llvm/Support/FormatVariadic.h"
2223

2324
using namespace clang;
2425
using namespace ento;
@@ -40,6 +41,11 @@ class PointerSubChecker
4041
"Indexing the address of a variable with other than 1 at this place "
4142
"is undefined behavior.";
4243

44+
/// Check that an array is indexed in the allowed range that is 0 to "one
45+
/// after the end". The "array" can be address of a non-array variable.
46+
/// @param E Expression of the pointer subtraction.
47+
/// @param ElemReg An indexed region in the subtraction expression.
48+
/// @param Reg Region of the other side of the expression.
4349
bool checkArrayBounds(CheckerContext &C, const Expr *E,
4450
const ElementRegion *ElemReg,
4551
const MemRegion *Reg) const;
@@ -49,12 +55,28 @@ class PointerSubChecker
4955
};
5056
}
5157

58+
static bool isArrayVar(const MemRegion *R) {
59+
while (R) {
60+
if (isa<VarRegion>(R))
61+
return true;
62+
if (const auto *ER = dyn_cast<ElementRegion>(R))
63+
R = ER->getSuperRegion();
64+
else
65+
return false;
66+
}
67+
return false;
68+
}
69+
5270
bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
5371
const ElementRegion *ElemReg,
5472
const MemRegion *Reg) const {
5573
if (!ElemReg)
5674
return true;
5775

76+
const MemRegion *SuperReg = ElemReg->getSuperRegion();
77+
if (!isArrayVar(SuperReg))
78+
return true;
79+
5880
auto ReportBug = [&](const llvm::StringLiteral &Msg) {
5981
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
6082
auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
@@ -64,10 +86,10 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
6486
};
6587

6688
ProgramStateRef State = C.getState();
67-
const MemRegion *SuperReg = ElemReg->getSuperRegion();
6889
SValBuilder &SVB = C.getSValBuilder();
6990

7091
if (SuperReg == Reg) {
92+
// Case like `(&x + 1) - &x`. Only 1 or 0 is allowed as index.
7193
if (const llvm::APSInt *I = SVB.getKnownValue(State, ElemReg->getIndex());
7294
I && (!I->isOne() && !I->isZero()))
7395
ReportBug(Msg_BadVarIndex);
@@ -121,8 +143,9 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
121143
if (LR == RR)
122144
return;
123145

124-
// No warning if one operand is unknown.
125-
if (isa<SymbolicRegion>(LR) || isa<SymbolicRegion>(RR))
146+
// No warning if one operand is unknown or resides in a region that could be
147+
// equal to the other.
148+
if (LR->getSymbolicBase() || RR->getSymbolicBase())
126149
return;
127150

128151
const auto *ElemLR = dyn_cast<ElementRegion>(LR);
@@ -159,12 +182,16 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
159182
// do_something(&a.array[5] - &b.array[5]);
160183
// In this case don't emit notes.
161184
if (DiffDeclL != DiffDeclR) {
162-
if (DiffDeclL)
163-
R->addNote("Array at the left-hand side of subtraction",
164-
{DiffDeclL, C.getSourceManager()});
165-
if (DiffDeclR)
166-
R->addNote("Array at the right-hand side of subtraction",
167-
{DiffDeclR, C.getSourceManager()});
185+
auto AddNote = [&R, &C](const ValueDecl *D, StringRef SideStr) {
186+
if (D) {
187+
std::string Msg = llvm::formatv(
188+
"{0} at the {1}-hand side of subtraction",
189+
D->getType()->isArrayType() ? "Array" : "Object", SideStr);
190+
R->addNote(Msg, {D, C.getSourceManager()});
191+
}
192+
};
193+
AddNote(DiffDeclL, "left");
194+
AddNote(DiffDeclR, "right");
168195
}
169196
C.emitReport(std::move(R));
170197
}

clang/test/Analysis/pointer-sub.c

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.core.PointerSub -verify %s
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core.PointerSub -analyzer-output=text-minimal -verify %s
22

33
void f1(void) {
44
int x, y, z[10];
@@ -73,15 +73,15 @@ void f4(void) {
7373
d = a[2] - a[1]; // expected-warning{{Subtraction of two pointers that}}
7474
}
7575

76-
typedef struct {
76+
struct S {
7777
int a;
7878
int b;
7979
int c[10]; // expected-note2{{Array at the right-hand side of subtraction}}
8080
int d[10]; // expected-note2{{Array at the left-hand side of subtraction}}
81-
} S;
81+
};
8282

8383
void f5(void) {
84-
S s;
84+
struct S s;
8585
int y;
8686
int d;
8787

@@ -92,18 +92,18 @@ void f5(void) {
9292
d = &s.d[3] - &s.c[2]; // expected-warning{{Subtraction of two pointers that}}
9393
d = s.d - s.c; // expected-warning{{Subtraction of two pointers that}}
9494

95-
S sa[10];
95+
struct S sa[10];
9696
d = &sa[2] - &sa[1];
9797
d = &sa[2].a - &sa[1].b; // expected-warning{{Subtraction of two pointers that}}
9898
}
9999

100100
void f6(void) {
101-
long long l;
101+
long long l = 2;
102102
char *a1 = (char *)&l;
103103
int d = a1[3] - l;
104104

105-
long long la1[3]; // expected-note{{Array at the right-hand side of subtraction}}
106-
long long la2[3]; // expected-note{{Array at the left-hand side of subtraction}}
105+
long long la1[3] = {1}; // expected-note{{Array at the right-hand side of subtraction}}
106+
long long la2[3] = {1}; // expected-note{{Array at the left-hand side of subtraction}}
107107
char *pla1 = (char *)la1;
108108
char *pla2 = (char *)la2;
109109
d = pla1[1] - pla1[0];
@@ -117,6 +117,40 @@ void f7(int *p) {
117117
}
118118

119119
void f8(int n) {
120-
int a[10];
121-
int d = a[n] - a[0];
120+
int a[10] = {1};
121+
int d = a[n] - a[0]; // no-warning
122+
}
123+
124+
int f9(const char *p1) {
125+
const char *p2 = p1;
126+
--p1;
127+
++p2;
128+
return p1 - p2; // no-warning
129+
}
130+
131+
int f10(struct S *p1, struct S *p2) {
132+
return &p1->c[5] - &p2->c[5]; // no-warning
133+
}
134+
135+
struct S1 {
136+
int a;
137+
int b; // expected-note{{Object at the right-hand side of subtraction}}
138+
};
139+
140+
int f11() {
141+
struct S1 s; // expected-note{{Object at the left-hand side of subtraction}}
142+
return (char *)&s - (char *)&s.b; // expected-warning{{Subtraction of two pointers that}}
143+
}
144+
145+
struct S2 {
146+
char *p1;
147+
char *p2;
148+
};
149+
150+
void init_S2(struct S2 *);
151+
152+
int f12() {
153+
struct S2 s;
154+
init_S2(&s);
155+
return s.p1 - s.p2; // no-warning (pointers are unknown)
122156
}

0 commit comments

Comments
 (0)