Skip to content

Commit 8570ba2

Browse files
authored
[clang][analyzer] Add checker 'core.NullPointerArithm' (#157129)
1 parent 06cc20c commit 8570ba2

File tree

6 files changed

+255
-15
lines changed

6 files changed

+255
-15
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,50 @@ pointers with a specified address space. If the option is set to false, then
205205
reports from the specific x86 address spaces 256, 257 and 258 are still
206206
suppressed, but null dereferences from other address spaces are reported.
207207
208+
.. _core-NullPointerArithm:
209+
210+
core.NullPointerArithm (C, C++)
211+
"""""""""""""""""""""""""""""""
212+
Check for undefined arithmetic operations with null pointers.
213+
214+
The checker can detect the following cases:
215+
216+
- ``p + x`` and ``x + p`` where ``p`` is a null pointer and ``x`` is a nonzero
217+
integer value.
218+
- ``p - x`` where ``p`` is a null pointer and ``x`` is a nonzero integer
219+
value.
220+
- ``p1 - p2`` where one of ``p1`` and ``p2`` is null and the other a
221+
non-null pointer.
222+
223+
Result of these operations is undefined according to the standard.
224+
In the above listed cases, the checker will warn even if the expression
225+
described to be "nonzero" or "non-null" has unknown value, because it is likely
226+
that it can have non-zero value during the program execution.
227+
228+
.. code-block:: c
229+
230+
void test1(int *p, int offset) {
231+
if (p)
232+
return;
233+
234+
int *p1 = p + offset; // warn: 'p' is null, 'offset' is unknown but likely non-zero
235+
}
236+
237+
void test2(int *p, int offset) {
238+
if (p) { } // this indicates that it is possible for 'p' to be null
239+
if (offset == 0)
240+
return;
241+
242+
int *p1 = p - offset; // warn: 'p' is null, 'offset' is known to be non-zero
243+
}
244+
245+
void test3(char *p1, char *p2) {
246+
if (p1)
247+
return;
248+
249+
int a = p1 - p2; // warn: 'p1' is null, 'p2' can be likely non-null
250+
}
251+
208252
.. _core-StackAddressEscape:
209253
210254
core.StackAddressEscape (C)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ def NullDereferenceChecker
195195
HelpText<"Check for dereferences of null pointers">,
196196
Documentation<HasDocumentation>;
197197

198+
def NullPointerArithmChecker
199+
: Checker<"NullPointerArithm">,
200+
HelpText<"Check for undefined arithmetic operations on null pointers">,
201+
Documentation<HasDocumentation>;
202+
198203
def NonNullParamChecker : Checker<"NonNullParamChecker">,
199204
HelpText<"Check for null pointers passed as arguments to a function whose "
200205
"arguments are references or marked with the 'nonnull' attribute">,

clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp

Lines changed: 128 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
2020
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
2121
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
22+
#include "llvm/Support/FormatVariadic.h"
2223
#include "llvm/Support/raw_ostream.h"
2324

2425
using namespace clang;
@@ -39,9 +40,10 @@ class DerefBugType : public BugType {
3940

4041
class DereferenceChecker
4142
: public CheckerFamily<check::Location, check::Bind,
43+
check::PreStmt<BinaryOperator>,
4244
EventDispatcher<ImplicitNullDerefEvent>> {
43-
void reportBug(const DerefBugType &BT, ProgramStateRef State, const Stmt *S,
44-
CheckerContext &C) const;
45+
void reportDerefBug(const DerefBugType &BT, ProgramStateRef State,
46+
const Stmt *S, CheckerContext &C) const;
4547

4648
bool suppressReport(CheckerContext &C, const Expr *E) const;
4749

@@ -50,14 +52,15 @@ class DereferenceChecker
5052
CheckerContext &C) const;
5153
void checkBind(SVal L, SVal V, const Stmt *S, bool AtDeclInit,
5254
CheckerContext &C) const;
55+
void checkPreStmt(const BinaryOperator *Op, CheckerContext &C) const;
5356

5457
static void AddDerefSource(raw_ostream &os,
5558
SmallVectorImpl<SourceRange> &Ranges,
5659
const Expr *Ex, const ProgramState *state,
5760
const LocationContext *LCtx,
5861
bool loadedFrom = false);
5962

60-
CheckerFrontend NullDerefChecker, FixedDerefChecker;
63+
CheckerFrontend NullDerefChecker, FixedDerefChecker, NullPointerArithmChecker;
6164
const DerefBugType NullBug{&NullDerefChecker, "Dereference of null pointer",
6265
"a null pointer dereference",
6366
"a dereference of a null pointer"};
@@ -72,9 +75,22 @@ class DereferenceChecker
7275
const DerefBugType FixedAddressBug{&FixedDerefChecker,
7376
"Dereference of a fixed address",
7477
"a dereference of a fixed address"};
78+
const BugType NullPointerArithmBug{
79+
&NullPointerArithmChecker,
80+
"Possibly undefined arithmetic operation involving a null pointer"};
7581

7682
StringRef getDebugTag() const override { return "DereferenceChecker"; }
7783
};
84+
85+
struct ValueDescStr {
86+
SmallVectorImpl<SourceRange> &Ranges;
87+
const Expr *Ex;
88+
const ProgramState *State;
89+
const LocationContext *LCtx;
90+
bool IsPointer;
91+
ConditionTruthVal IsNull;
92+
};
93+
7894
} // end anonymous namespace
7995

8096
void
@@ -173,9 +189,9 @@ static bool isDeclRefExprToReference(const Expr *E) {
173189
return false;
174190
}
175191

176-
void DereferenceChecker::reportBug(const DerefBugType &BT,
177-
ProgramStateRef State, const Stmt *S,
178-
CheckerContext &C) const {
192+
void DereferenceChecker::reportDerefBug(const DerefBugType &BT,
193+
ProgramStateRef State, const Stmt *S,
194+
CheckerContext &C) const {
179195
if (&BT == &FixedAddressBug) {
180196
if (!FixedDerefChecker.isEnabled())
181197
// Deliberately don't add a sink node if check is disabled.
@@ -249,9 +265,8 @@ void DereferenceChecker::reportBug(const DerefBugType &BT,
249265

250266
bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *BR);
251267

252-
for (SmallVectorImpl<SourceRange>::iterator
253-
I = Ranges.begin(), E = Ranges.end(); I!=E; ++I)
254-
BR->addRange(*I);
268+
for (const auto &R : Ranges)
269+
BR->addRange(R);
255270

256271
C.emitReport(std::move(BR));
257272
}
@@ -262,7 +277,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
262277
if (l.isUndef()) {
263278
const Expr *DerefExpr = getDereferenceExpr(S);
264279
if (!suppressReport(C, DerefExpr))
265-
reportBug(UndefBug, C.getState(), DerefExpr, C);
280+
reportDerefBug(UndefBug, C.getState(), DerefExpr, C);
266281
return;
267282
}
268283

@@ -283,7 +298,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
283298
// we call an "explicit" null dereference.
284299
const Expr *expr = getDereferenceExpr(S);
285300
if (!suppressReport(C, expr)) {
286-
reportBug(NullBug, nullState, expr, C);
301+
reportDerefBug(NullBug, nullState, expr, C);
287302
return;
288303
}
289304
}
@@ -301,7 +316,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
301316
if (location.isConstant()) {
302317
const Expr *DerefExpr = getDereferenceExpr(S, isLoad);
303318
if (!suppressReport(C, DerefExpr))
304-
reportBug(FixedAddressBug, notNullState, DerefExpr, C);
319+
reportDerefBug(FixedAddressBug, notNullState, DerefExpr, C);
305320
return;
306321
}
307322

@@ -317,7 +332,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
317332

318333
// One should never write to label addresses.
319334
if (auto Label = L.getAs<loc::GotoLabel>()) {
320-
reportBug(LabelBug, C.getState(), S, C);
335+
reportDerefBug(LabelBug, C.getState(), S, C);
321336
return;
322337
}
323338

@@ -338,7 +353,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
338353
if (!StNonNull) {
339354
const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true);
340355
if (!suppressReport(C, expr)) {
341-
reportBug(NullBug, StNull, expr, C);
356+
reportDerefBug(NullBug, StNull, expr, C);
342357
return;
343358
}
344359
}
@@ -356,7 +371,7 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
356371
if (V.isConstant()) {
357372
const Expr *DerefExpr = getDereferenceExpr(S, true);
358373
if (!suppressReport(C, DerefExpr))
359-
reportBug(FixedAddressBug, State, DerefExpr, C);
374+
reportDerefBug(FixedAddressBug, State, DerefExpr, C);
360375
return;
361376
}
362377

@@ -379,6 +394,96 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
379394
C.addTransition(State, this);
380395
}
381396

397+
namespace llvm {
398+
template <> struct format_provider<ValueDescStr> {
399+
static void format(const ValueDescStr &V, raw_ostream &Stream,
400+
StringRef Style) {
401+
static const char *ValueStr[2][3] = {
402+
{"zero", "nonzero integer value", "probably nonzero integer value"},
403+
{"null pointer", "non-null pointer", "probably non-null pointer"},
404+
};
405+
Stream
406+
<< ValueStr[V.IsPointer][V.IsNull.isConstrainedTrue()
407+
? 0
408+
: (V.IsNull.isConstrainedFalse() ? 1 : 2)];
409+
DereferenceChecker::AddDerefSource(Stream, V.Ranges, V.Ex, V.State, V.LCtx,
410+
false);
411+
}
412+
};
413+
} // namespace llvm
414+
415+
void DereferenceChecker::checkPreStmt(const BinaryOperator *Op,
416+
CheckerContext &C) const {
417+
if (!Op->isAdditiveOp() || !NullPointerArithmChecker.isEnabled())
418+
return;
419+
const Expr *E1 = Op->getLHS();
420+
const Expr *E2 = Op->getRHS();
421+
QualType T1 = E1->getType().getCanonicalType();
422+
QualType T2 = E2->getType().getCanonicalType();
423+
bool T1IsPointer = T1->isPointerType();
424+
bool T2IsPointer = T2->isPointerType();
425+
if (T1->isIntegerType() && T2->isIntegerType())
426+
return;
427+
if (!T1IsPointer && !T1->isIntegerType() && !T2IsPointer &&
428+
!T2->isIntegerType())
429+
return;
430+
431+
ProgramStateRef State = C.getState();
432+
ConditionTruthVal V1IsNull = State->isNull(C.getSVal(E1));
433+
ConditionTruthVal V2IsNull = State->isNull(C.getSVal(E2));
434+
bool IsConstrained = true;
435+
436+
// Check cases 'NULL + x' and 'NULL - x'
437+
if (T1IsPointer && !T2IsPointer) {
438+
if (!V1IsNull.isConstrainedTrue() || V2IsNull.isConstrainedTrue())
439+
return;
440+
IsConstrained = V2IsNull.isConstrainedFalse();
441+
}
442+
443+
// Check case 'x + NULL'
444+
if (!T1IsPointer && T2IsPointer) {
445+
if (V1IsNull.isConstrainedTrue() || !V2IsNull.isConstrainedTrue())
446+
return;
447+
IsConstrained = V1IsNull.isConstrainedFalse();
448+
}
449+
450+
// Check case 'NULL - p' or 'p - NULL'
451+
if (T1IsPointer && T2IsPointer) {
452+
if (!V1IsNull.isConstrainedTrue() && !V2IsNull.isConstrainedTrue())
453+
return;
454+
if (V1IsNull.isConstrainedTrue() && V2IsNull.isConstrainedTrue())
455+
return;
456+
IsConstrained =
457+
V1IsNull.isConstrainedFalse() || V2IsNull.isConstrainedFalse();
458+
}
459+
460+
SmallVector<SourceRange, 2> Ranges;
461+
const char *OpcodeStr =
462+
Op->getOpcode() == BO_Add ? "Addition" : "Subtraction";
463+
const char *ResultStr = IsConstrained ? "results" : "may result";
464+
ValueDescStr DerefArg1{
465+
Ranges, E1, State.get(), C.getLocationContext(), T1IsPointer, V1IsNull};
466+
ValueDescStr DerefArg2{
467+
Ranges, E2, State.get(), C.getLocationContext(), T2IsPointer, V2IsNull};
468+
std::string Msg =
469+
llvm::formatv("{0} of a {1} and a {2} {3} in undefined behavior",
470+
OpcodeStr, DerefArg1, DerefArg2, ResultStr);
471+
472+
ExplodedNode *N = C.generateErrorNode(State);
473+
if (!N)
474+
return;
475+
auto BR =
476+
std::make_unique<PathSensitiveBugReport>(NullPointerArithmBug, Msg, N);
477+
if (V1IsNull.isConstrainedTrue())
478+
bugreporter::trackExpressionValue(N, E1, *BR);
479+
if (V2IsNull.isConstrainedTrue())
480+
bugreporter::trackExpressionValue(N, E2, *BR);
481+
for (const auto &R : Ranges)
482+
BR->addRange(R);
483+
484+
C.emitReport(std::move(BR));
485+
}
486+
382487
void ento::registerNullDereferenceChecker(CheckerManager &Mgr) {
383488
Mgr.getChecker<DereferenceChecker>()->NullDerefChecker.enable(Mgr);
384489
}
@@ -395,3 +500,11 @@ bool ento::shouldRegisterFixedAddressDereferenceChecker(
395500
const CheckerManager &) {
396501
return true;
397502
}
503+
504+
void ento::registerNullPointerArithmChecker(CheckerManager &Mgr) {
505+
Mgr.getChecker<DereferenceChecker>()->NullPointerArithmChecker.enable(Mgr);
506+
}
507+
508+
bool ento::shouldRegisterNullPointerArithmChecker(const CheckerManager &) {
509+
return true;
510+
}

clang/test/Analysis/analyzer-enabled-checkers.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
// CHECK-NEXT: core.NonNullParamChecker
2020
// CHECK-NEXT: core.NonnilStringConstants
2121
// CHECK-NEXT: core.NullDereference
22+
// CHECK-NEXT: core.NullPointerArithm
2223
// CHECK-NEXT: core.StackAddressEscape
2324
// CHECK-NEXT: core.UndefinedBinaryOperatorResult
2425
// CHECK-NEXT: core.VLASize
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// RUN: %clang_analyze_cc1 -verify %s \
2+
// RUN: -analyzer-checker=core
3+
4+
extern int *get_pointer();
5+
6+
int *test_add1(int offset) {
7+
int *p = get_pointer();
8+
if (p) {}
9+
return p + offset; // expected-warning{{Addition of a null pointer (from variable 'p') and a probably nonzero integer value (from variable 'offset') may result in undefined behavior}}
10+
}
11+
12+
int *test_add2(int offset) {
13+
int *p = get_pointer();
14+
if (p) {}
15+
if (offset) {}
16+
return p + offset; // expected-warning{{Addition of a null pointer (from variable 'p') and a nonzero integer value (from variable 'offset') results in undefined behavior}}
17+
}
18+
19+
int *test_add3(int offset) {
20+
int *p = get_pointer();
21+
if (p) {}
22+
if (offset != 0) return 0;
23+
return p + offset;
24+
}
25+
26+
int *test_add4(int offset) {
27+
int *p = get_pointer();
28+
if (p) {}
29+
if (offset == 0) return 0;
30+
return p + offset; // expected-warning{{Addition of a null pointer (from variable 'p') and a nonzero integer value (from variable 'offset') results in undefined behavior}}
31+
}
32+
33+
int *test_add5(int offset) {
34+
int *p = get_pointer();
35+
if (p) {}
36+
return offset + p; // expected-warning{{Addition of a probably nonzero integer value (from variable 'offset') and a null pointer (from variable 'p') may result in undefined behavior}}
37+
}
38+
39+
int *test_sub1(int offset) {
40+
int *p = get_pointer();
41+
if (p) {}
42+
return p - offset; // expected-warning{{Subtraction of a null pointer (from variable 'p') and a probably nonzero integer value (from variable 'offset') may result in undefined behavior}}
43+
}
44+
45+
int test_sub_p1() {
46+
int *p = get_pointer();
47+
if (p) {}
48+
return p - p;
49+
}
50+
51+
int test_sub_p2() {
52+
int *p1 = get_pointer();
53+
int *p2 = get_pointer();
54+
if (p1) {}
55+
if (p2) {}
56+
return p1 - p2;
57+
// expected-warning@-1{{Subtraction of a non-null pointer (from variable 'p1') and a null pointer (from variable 'p2') results in undefined behavior}}
58+
// expected-warning@-2{{Subtraction of a null pointer (from variable 'p1') and a non-null pointer (from variable 'p2') results in undefined behavior}}
59+
}
60+
61+
int test_sub_p3() {
62+
int *p1 = get_pointer();
63+
int *p2 = get_pointer();
64+
if (p1) {}
65+
return p1 - p2; // expected-warning{{Subtraction of a null pointer (from variable 'p1') and a probably non-null pointer (from variable 'p2') may result in undefined behavior}}
66+
}
67+
68+
struct S {
69+
char *p;
70+
int offset;
71+
};
72+
73+
char *test_struct(struct S s) {
74+
if (s.p) {}
75+
return s.p + s.offset; // expected-warning{{Addition of a null pointer (via field 'p') and a probably nonzero integer value (via field 'offset') may result in undefined behavior}}
76+
}

0 commit comments

Comments
 (0)