Skip to content

Commit 142c79c

Browse files
committed
Review 1
* Add handling for `!= nullptr`, `!= ptr` operators * Re-add TK_IgnoreUnlessSpelledInSource * Refactor handling for null-check expressions (transfer instead of branchTransfer) * Fix crash from `&(PrVar)` not always being PrVar * Fix formatting and docs
1 parent 68a903e commit 142c79c

File tree

5 files changed

+172
-44
lines changed

5 files changed

+172
-44
lines changed

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ New checks
130130

131131
Finds nondeterministic usages of pointers in unordered containers.
132132

133+
- New :doc:`bugprone-null-check-after-dereference
134+
<clang-tidy/checks/bugprone/null-check-after-dereference>` check.
135+
136+
This check identifies redundant pointer null-checks, by finding cases where
137+
the pointer cannot be null at the location of the null-check.
138+
133139
- New :doc:`bugprone-tagged-union-member-count
134140
<clang-tidy/checks/bugprone/tagged-union-member-count>` check.
135141

@@ -148,12 +154,6 @@ New checks
148154
Finds cases when an uninstantiated virtual member function in a template class
149155
causes cross-compiler incompatibility.
150156

151-
- New :doc:`bugprone-null-check-after-dereference
152-
<clang-tidy/checks/bugprone/null-check-after-dereference>` check.
153-
154-
Finds locations where a pointer's nullability is checked after it has already
155-
been dereferenced.
156-
157157
New check aliases
158158
^^^^^^^^^^^^^^^^^
159159

clang-tools-extra/docs/clang-tidy/checks/bugprone/null-check-after-dereference.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ Pointer null-checks
3535

3636
The check currently supports null-checks on pointers that use
3737
``operator bool``, such as when being used as the condition
38-
for an ``if`` statement.
38+
for an ``if`` statement. It also supports comparisons such as ``!= nullptr``, and
39+
``== other_ptr``.
3940

4041
.. code-block:: c++
4142

clang-tools-extra/test/clang-tidy/checkers/bugprone/null-check-after-dereference.cpp

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,44 @@ void no_warning(int *p, bool b) {
4343
}
4444

4545
if (p) {
46+
// no-warning
4647
*p += 20;
4748
}
4849
}
4950

51+
void equals_nullptr(int *p) {
52+
*p = 42;
53+
54+
if (p == nullptr) {
55+
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
56+
// CHECK-MESSAGES: :[[@LINE-4]]:3: note: one of the locations where the pointer's value cannot be null
57+
return;
58+
}
59+
60+
if (p != nullptr) {
61+
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
62+
// CHECK-MESSAGES: :[[@LINE-10]]:3: note: one of the locations where the pointer's value cannot be null
63+
*p += 20;
64+
}
65+
66+
if (nullptr != p) {
67+
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: pointer value is checked even though it cannot be null at this point
68+
// CHECK-MESSAGES: :[[@LINE-16]]:3: note: one of the locations where the pointer's value cannot be null
69+
*p += 20;
70+
}
71+
}
72+
73+
void equals_other_ptr(int *p, int *q) {
74+
if (q)
75+
return;
76+
77+
if (p == q) {
78+
// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: pointer value is checked but it can only be null at this point
79+
// CHECK-MESSAGES: :[[@LINE-5]]:7: note: one of the locations where the pointer's value can only be null
80+
return;
81+
}
82+
}
83+
5084
int else_branch_warning(int *p, bool b) {
5185
if (b) {
5286
*p = 42;
@@ -105,6 +139,7 @@ int nullptr_assignment(int *nullptr_param, bool b) {
105139
}
106140

107141
if (nullptr_assigned) {
142+
// no-warning
108143
return *nullptr_assigned;
109144
} else {
110145
return 0;
@@ -197,9 +232,8 @@ int chained_if(int *a) {
197232
return 0;
198233
}
199234

200-
// FIXME: Negations are not tracked properly when the previous conditional returns
201235
if (a) {
202-
// --CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
236+
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer value is checked even though it cannot be null at this point
203237
*a += 20;
204238
return *a;
205239
} else {
@@ -212,8 +246,7 @@ int double_if(int *a) {
212246
if (a) {
213247
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: pointer value is checked even though it cannot be null at this point
214248
// --CHECK-MESSAGES: :[[@LINE-3]]:5: note: one of the locations where the pointer's value cannot be null
215-
// FIXME: Add warning for branch statements where pointer is not null afterwards
216-
*a += 20;
249+
// FIXME: Add warning for branch satements where pointer is not null afterwards
217250
return *a;
218251
} else {
219252
return 0;

clang/include/clang/Analysis/FlowSensitive/Models/NullPointerAnalysisModel.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ class NullCheckAfterDereferenceDiagnoser {
8383
const Environment &Env;
8484
};
8585

86-
/// Checked when known to be null, and checked after already dereferenced,
87-
/// respectively.
86+
/// Returns source locations for pointers that were checked when known to be
87+
// null, and checked after already dereferenced, respectively.
8888
using ResultType =
8989
std::pair<std::vector<SourceLocation>, std::vector<SourceLocation>>;
9090

clang/lib/Analysis/FlowSensitive/Models/NullPointerAnalysisModel.cpp

Lines changed: 125 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ using SR = SatisfiabilityResult;
6464
// FIXME: These AST matchers should also be exported via the
6565
// NullPointerAnalysisModel class, for tests
6666
auto ptrWithBinding(llvm::StringRef VarName = kVar) {
67-
return expr(hasType(isAnyPointer())).bind(VarName);
67+
return traverse(TK_IgnoreUnlessSpelledInSource,
68+
expr(hasType(isAnyPointer())).bind(VarName));
6869
}
6970

7071
auto derefMatcher() {
@@ -81,8 +82,8 @@ auto castExprMatcher() {
8182
.bind(kCond);
8283
}
8384

84-
auto nullptrMatcher() {
85-
return castExpr(hasCastKind(CK_NullToPointer)).bind(kVar);
85+
auto nullptrMatcher(llvm::StringRef VarName = kVar) {
86+
return castExpr(hasCastKind(CK_NullToPointer)).bind(VarName);
8687
}
8788

8889
auto addressofMatcher() {
@@ -99,6 +100,19 @@ auto assignMatcher() {
99100
hasRHS(expr().bind(kValue)));
100101
}
101102

103+
auto nullCheckExprMatcher() {
104+
return binaryOperator(hasAnyOperatorName("==", "!="),
105+
hasOperands(ptrWithBinding(), nullptrMatcher(kValue)));
106+
}
107+
108+
// FIXME: When TK_IgnoreUnlessSpelledInSource is removed from ptrWithBinding(),
109+
// this matcher should be merged with nullCheckExprMatcher().
110+
auto equalExprMatcher() {
111+
return binaryOperator(hasAnyOperatorName("==", "!="),
112+
hasOperands(ptrWithBinding(kVar),
113+
ptrWithBinding(kValue)));
114+
}
115+
102116
auto anyPointerMatcher() { return expr(hasType(isAnyPointer())).bind(kVar); }
103117

104118
// Only computes simple comparisons against the literals True and False in the
@@ -217,34 +231,72 @@ void matchDereferenceExpr(const Stmt *stmt,
217231
Env.assume(Env.arena().makeNot(getVal(kIsNull, *RootValue).formula()));
218232
}
219233

220-
void matchCastExpr(const CastExpr *cond, const MatchFinder::MatchResult &Result,
221-
NullPointerAnalysisModel::TransferArgs &Data) {
222-
auto [IsNonnullBranch, Env] = Data;
223-
234+
void matchNullCheckExpr(const Expr *NullCheck,
235+
const MatchFinder::MatchResult &Result,
236+
Environment &Env) {
224237
const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
225238
assert(Var != nullptr);
226239

227-
Value *RootValue = getValue(*Var, Env);
228-
229-
Value *NewRootValue = Env.createValue(Var->getType());
240+
// (bool)p or (p != nullptr)
241+
bool IsNonnullOp = true;
242+
if (auto *BinOp = dyn_cast<BinaryOperator>(NullCheck);
243+
BinOp->getOpcode() == BO_EQ) {
244+
IsNonnullOp = false;
245+
}
230246

231-
setGLValue(*Var, *NewRootValue, Env);
247+
Value *RootValue = getValue(*Var, Env);
232248

233249
Arena &A = Env.arena();
234250
BoolValue &IsNonnull = getVal(kIsNonnull, *RootValue);
235251
BoolValue &IsNull = getVal(kIsNull, *RootValue);
252+
BoolValue *CondValue = cast_or_null<BoolValue>(Env.getValue(*NullCheck));
253+
if (!CondValue) {
254+
CondValue = &A.makeAtomValue();
255+
Env.setValue(*NullCheck, *CondValue);
256+
}
257+
const Formula &CondFormula = IsNonnullOp ? CondValue->formula()
258+
: A.makeNot(CondValue->formula());
259+
260+
Env.assume(A.makeImplies(CondFormula, A.makeNot(IsNull.formula())));
261+
Env.assume(A.makeImplies(A.makeNot(CondFormula),
262+
A.makeNot(IsNonnull.formula())));
263+
}
264+
265+
void matchEqualExpr(const BinaryOperator *EqualExpr,
266+
const MatchFinder::MatchResult &Result,
267+
Environment &Env) {
268+
bool IsNotEqualsOp = EqualExpr->getOpcode() == BO_NE;
269+
270+
const auto *LHSVar = Result.Nodes.getNodeAs<Expr>(kVar);
271+
assert(LHSVar != nullptr);
236272

237-
if (IsNonnullBranch) {
238-
Env.assume(A.makeNot(IsNull.formula()));
239-
NewRootValue->setProperty(kIsNull, Env.getBoolLiteralValue(false));
273+
const auto *RHSVar = Result.Nodes.getNodeAs<Expr>(kValue);
274+
assert(RHSVar != nullptr);
240275

241-
NewRootValue->setProperty(kIsNonnull, IsNonnull);
242-
} else {
243-
NewRootValue->setProperty(kIsNull, IsNull);
276+
Arena &A = Env.arena();
277+
Value *LHSValue = getValue(*LHSVar, Env);
278+
Value *RHSValue = getValue(*RHSVar, Env);
244279

245-
Env.assume(A.makeNot(IsNonnull.formula()));
246-
NewRootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(false));
280+
BoolValue *CondValue = cast_or_null<BoolValue>(Env.getValue(*EqualExpr));
281+
if (!CondValue) {
282+
CondValue = &A.makeAtomValue();
283+
Env.setValue(*EqualExpr, *CondValue);
247284
}
285+
286+
const Formula &CondFormula = IsNotEqualsOp ? A.makeNot(CondValue->formula())
287+
: CondValue->formula();
288+
289+
// If the pointers are equal, the nullability properties are the same.
290+
Env.assume(A.makeImplies(CondFormula,
291+
A.makeAnd(A.makeEquals(getVal(kIsNull, *LHSValue).formula(),
292+
getVal(kIsNull, *RHSValue).formula()),
293+
A.makeEquals(getVal(kIsNonnull, *LHSValue).formula(),
294+
getVal(kIsNonnull, *RHSValue).formula()))));
295+
296+
// If the pointers are not equal, at most one of the pointers is null.
297+
Env.assume(A.makeImplies(A.makeNot(CondFormula),
298+
A.makeNot(A.makeAnd(getVal(kIsNull, *LHSValue).formula(),
299+
getVal(kIsNull, *RHSValue).formula()))));
248300
}
249301

250302
void matchNullptrExpr(const Expr *expr, const MatchFinder::MatchResult &Result,
@@ -255,24 +307,31 @@ void matchNullptrExpr(const Expr *expr, const MatchFinder::MatchResult &Result,
255307
Value *RootValue = Env.getValue(*PrVar);
256308
if (!RootValue) {
257309
RootValue = Env.createValue(PrVar->getType());
310+
Env.setValue(*PrVar, *RootValue);
258311
}
259312

260313
RootValue->setProperty(kIsNull, Env.getBoolLiteralValue(true));
261314
RootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(false));
262-
Env.setValue(*PrVar, *RootValue);
263315
}
264316

265317
void matchAddressofExpr(const Expr *expr,
266318
const MatchFinder::MatchResult &Result,
267319
Environment &Env) {
268-
const auto *PrVar = Result.Nodes.getNodeAs<Expr>(kVar);
269-
assert(PrVar != nullptr);
320+
const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
321+
assert(Var != nullptr);
322+
323+
Value *RootValue = Env.getValue(*Var);
324+
if (!RootValue) {
325+
RootValue = Env.createValue(Var->getType());
270326

271-
Value *RootValue = Env.createValue(PrVar->getType());
327+
if (!RootValue)
328+
return;
329+
330+
setUnknownValue(*Var, *RootValue, Env);
331+
}
272332

273333
RootValue->setProperty(kIsNull, Env.getBoolLiteralValue(false));
274334
RootValue->setProperty(kIsNonnull, Env.getBoolLiteralValue(true));
275-
Env.setValue(*PrVar, *RootValue);
276335
}
277336

278337
void matchAnyPointerExpr(const Expr *fncall,
@@ -336,9 +395,9 @@ diagnoseAssignLocation(const Expr *Assign,
336395
}
337396

338397
NullCheckAfterDereferenceDiagnoser::ResultType
339-
diagnoseCastExpr(const CastExpr *Stmt, const MatchFinder::MatchResult &Result,
340-
const NullCheckAfterDereferenceDiagnoser::DiagnoseArgs &Data) {
341-
398+
diagnoseNullCheckExpr(const Expr *NullCheck,
399+
const MatchFinder::MatchResult &Result,
400+
const NullCheckAfterDereferenceDiagnoser::DiagnoseArgs &Data) {
342401
auto [ValToDerefLoc, WarningLocToVal, Env] = Data;
343402

344403
const auto *Var = Result.Nodes.getNodeAs<Expr>(kVar);
@@ -352,6 +411,7 @@ diagnoseCastExpr(const CastExpr *Stmt, const MatchFinder::MatchResult &Result,
352411
bool IsNonnull = Env.allows(getVal(kIsNonnull, *RootValue).formula());
353412

354413
if (!IsNull && IsNonnull) {
414+
// FIXME: Separate function
355415
bool Inserted =
356416
WarningLocToVal.try_emplace(Var->getBeginLoc(), RootValue).second;
357417
assert(Inserted && "multiple warnings at the same source location");
@@ -370,16 +430,44 @@ diagnoseCastExpr(const CastExpr *Stmt, const MatchFinder::MatchResult &Result,
370430
}
371431
}
372432

373-
// If no matches are found, the cast itself signals a special location
433+
// If no matches are found, the null-check itself signals a special location
374434
auto [It, Inserted] = ValToDerefLoc.try_emplace(RootValue, nullptr);
375435

376436
if (Inserted)
377-
It->second = Stmt;
437+
It->second = NullCheck;
378438
}
379439

380440
return {};
381441
}
382442

443+
NullCheckAfterDereferenceDiagnoser::ResultType
444+
diagnoseEqualExpr(const Expr *PtrCheck, const MatchFinder::MatchResult &Result,
445+
NullCheckAfterDereferenceDiagnoser::DiagnoseArgs &Data) {
446+
auto [ValToDerefLoc, WarningLocToVal, Env] = Data;
447+
448+
const auto *LHSVar = Result.Nodes.getNodeAs<Expr>(kVar);
449+
assert(LHSVar != nullptr);
450+
const auto *RHSVar = Result.Nodes.getNodeAs<Expr>(kValue);
451+
assert(RHSVar != nullptr);
452+
453+
Arena &A = Env.arena();
454+
std::vector<SourceLocation> NullVarLocations;
455+
456+
if (Value *LHSValue = Env.getValue(*LHSVar);
457+
Env.proves(A.makeNot(getVal(kIsNonnull, *LHSValue).formula()))) {
458+
WarningLocToVal.try_emplace(LHSVar->getBeginLoc(), LHSValue);
459+
NullVarLocations.push_back(LHSVar->getBeginLoc());
460+
}
461+
462+
if (Value *RHSValue = Env.getValue(*RHSVar);
463+
Env.proves(A.makeNot(getVal(kIsNonnull, *RHSValue).formula()))) {
464+
WarningLocToVal.try_emplace(RHSVar->getBeginLoc(), RHSValue);
465+
NullVarLocations.push_back(RHSVar->getBeginLoc());
466+
}
467+
468+
return {NullVarLocations, {}};
469+
}
470+
383471
auto buildTransferMatchSwitch() {
384472
return CFGMatchSwitchBuilder<Environment>()
385473
.CaseOfCFGStmt<Stmt>(derefMatcher(), matchDereferenceExpr)
@@ -388,12 +476,16 @@ auto buildTransferMatchSwitch() {
388476
.CaseOfCFGStmt<Expr>(addressofMatcher(), matchAddressofExpr)
389477
.CaseOfCFGStmt<Expr>(functionCallMatcher(), matchAnyPointerExpr)
390478
.CaseOfCFGStmt<Expr>(anyPointerMatcher(), matchAnyPointerExpr)
479+
.CaseOfCFGStmt<Expr>(castExprMatcher(), matchNullCheckExpr)
480+
.CaseOfCFGStmt<Expr>(nullCheckExprMatcher(), matchNullCheckExpr)
481+
.CaseOfCFGStmt<BinaryOperator>(equalExprMatcher(), matchEqualExpr)
391482
.Build();
392483
}
393484

394485
auto buildBranchTransferMatchSwitch() {
395486
return ASTMatchSwitchBuilder<Stmt, NullPointerAnalysisModel::TransferArgs>()
396-
.CaseOf<CastExpr>(castExprMatcher(), matchCastExpr)
487+
// .CaseOf<CastExpr>(castExprMatcher(), matchNullCheckExpr)
488+
// .CaseOf<BinaryOperator>(equalExprMatcher(), matchEqualExpr)
397489
.Build();
398490
}
399491

@@ -403,7 +495,9 @@ auto buildDiagnoseMatchSwitch() {
403495
.CaseOfCFGStmt<Expr>(derefMatcher(), diagnoseDerefLocation)
404496
.CaseOfCFGStmt<Expr>(arrowMatcher(), diagnoseDerefLocation)
405497
.CaseOfCFGStmt<Expr>(assignMatcher(), diagnoseAssignLocation)
406-
.CaseOfCFGStmt<CastExpr>(castExprMatcher(), diagnoseCastExpr)
498+
.CaseOfCFGStmt<Expr>(castExprMatcher(), diagnoseNullCheckExpr)
499+
.CaseOfCFGStmt<Expr>(nullCheckExprMatcher(), diagnoseNullCheckExpr)
500+
.CaseOfCFGStmt<Expr>(equalExprMatcher(), diagnoseEqualExpr)
407501
.Build();
408502
}
409503

0 commit comments

Comments
 (0)