Skip to content

Commit 6d34cfa

Browse files
authored
[Sema] Diagnose tautological bounds checks (#120222)
This diagnoses comparisons like `ptr + unsigned_index < ptr` and `ptr + unsigned_index >= ptr`, which are always false/true because addition of a pointer and an unsigned index cannot wrap (or the behavior is undefined). This warning is intended to help find broken bounds checks (which must be implemented in terms of uintptr_t instead). Fixes #120214.
1 parent c2a879e commit 6d34cfa

File tree

4 files changed

+135
-1
lines changed

4 files changed

+135
-1
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,16 @@ Improvements to Clang's diagnostics
683683
views.push_back(std::string("123")); // warning
684684
}
685685

686+
- Clang now emits a ``-Wtautological-compare`` diagnostic when a check for
687+
pointer addition overflow is always true or false, because overflow would
688+
be undefined behavior.
689+
690+
.. code-block:: c++
691+
692+
bool incorrect_overflow_check(const char *ptr, size_t index) {
693+
return ptr + index < ptr; // warning
694+
}
695+
686696
Improvements to Clang's time-trace
687697
----------------------------------
688698

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10246,7 +10246,7 @@ def warn_dangling_reference_captured_by_unknown : Warning<
1024610246
// should result in a warning, since these always evaluate to a constant.
1024710247
// Array comparisons have similar warnings
1024810248
def warn_comparison_always : Warning<
10249-
"%select{self-|array }0comparison always evaluates to "
10249+
"%select{self-|array |pointer }0comparison always evaluates to "
1025010250
"%select{a constant|true|false|'std::strong_ordering::equal'}1">,
1025110251
InGroup<TautologicalCompare>;
1025210252
def warn_comparison_bitwise_always : Warning<

clang/lib/Sema/SemaExpr.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11786,6 +11786,50 @@ static bool checkForArray(const Expr *E) {
1178611786
return D->getType()->isArrayType() && !D->isWeak();
1178711787
}
1178811788

11789+
/// Detect patterns ptr + size >= ptr and ptr + size < ptr, where ptr is a
11790+
/// pointer and size is an unsigned integer. Return whether the result is
11791+
/// always true/false.
11792+
static std::optional<bool> isTautologicalBoundsCheck(const Expr *LHS,
11793+
const Expr *RHS,
11794+
BinaryOperatorKind Opc) {
11795+
if (!LHS->getType()->isPointerType())
11796+
return std::nullopt;
11797+
11798+
// Canonicalize to >= or < predicate.
11799+
switch (Opc) {
11800+
case BO_GE:
11801+
case BO_LT:
11802+
break;
11803+
case BO_GT:
11804+
std::swap(LHS, RHS);
11805+
Opc = BO_LT;
11806+
break;
11807+
case BO_LE:
11808+
std::swap(LHS, RHS);
11809+
Opc = BO_GE;
11810+
break;
11811+
default:
11812+
return std::nullopt;
11813+
}
11814+
11815+
auto *BO = dyn_cast<BinaryOperator>(LHS);
11816+
if (!BO || BO->getOpcode() != BO_Add)
11817+
return std::nullopt;
11818+
11819+
Expr *Other;
11820+
if (Expr::isSameComparisonOperand(BO->getLHS(), RHS))
11821+
Other = BO->getRHS();
11822+
else if (Expr::isSameComparisonOperand(BO->getRHS(), RHS))
11823+
Other = BO->getLHS();
11824+
else
11825+
return std::nullopt;
11826+
11827+
if (!Other->getType()->isUnsignedIntegerType())
11828+
return std::nullopt;
11829+
11830+
return Opc == BO_GE;
11831+
}
11832+
1178911833
/// Diagnose some forms of syntactically-obvious tautological comparison.
1179011834
static void diagnoseTautologicalComparison(Sema &S, SourceLocation Loc,
1179111835
Expr *LHS, Expr *RHS,
@@ -11895,6 +11939,12 @@ static void diagnoseTautologicalComparison(Sema &S, SourceLocation Loc,
1189511939
S.PDiag(diag::warn_comparison_always)
1189611940
<< 1 /*array comparison*/
1189711941
<< Result);
11942+
} else if (std::optional<bool> Res =
11943+
isTautologicalBoundsCheck(LHS, RHS, Opc)) {
11944+
S.DiagRuntimeBehavior(Loc, nullptr,
11945+
S.PDiag(diag::warn_comparison_always)
11946+
<< 2 /*pointer comparison*/
11947+
<< (*Res ? AlwaysTrue : AlwaysFalse));
1189811948
}
1189911949
}
1190011950

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s
2+
3+
int add_ptr_idx_ult_ptr(const char *ptr, unsigned index) {
4+
return ptr + index < ptr; // expected-warning {{pointer comparison always evaluates to false}}
5+
}
6+
7+
int add_idx_ptr_ult_ptr(const char *ptr, unsigned index) {
8+
return index + ptr < ptr; // expected-warning {{pointer comparison always evaluates to false}}
9+
}
10+
11+
int ptr_ugt_add_ptr_idx(const char *ptr, unsigned index) {
12+
return ptr > ptr + index; // expected-warning {{pointer comparison always evaluates to false}}
13+
}
14+
15+
int ptr_ugt_add_idx_ptr(const char *ptr, unsigned index) {
16+
return ptr > index + ptr; // expected-warning {{pointer comparison always evaluates to false}}
17+
}
18+
19+
int add_ptr_idx_uge_ptr(const char *ptr, unsigned index) {
20+
return ptr + index >= ptr; // expected-warning {{pointer comparison always evaluates to true}}
21+
}
22+
23+
int add_idx_ptr_uge_ptr(const char *ptr, unsigned index) {
24+
return index + ptr >= ptr; // expected-warning {{pointer comparison always evaluates to true}}
25+
}
26+
27+
int ptr_ule_add_ptr_idx(const char *ptr, unsigned index) {
28+
return ptr <= ptr + index; // expected-warning {{pointer comparison always evaluates to true}}
29+
}
30+
31+
int ptr_ule_add_idx_ptr(const char *ptr, unsigned index) {
32+
return ptr <= index + ptr; // expected-warning {{pointer comparison always evaluates to true}}
33+
}
34+
35+
int add_ptr_idx_ult_ptr_array(unsigned index) {
36+
char ptr[10];
37+
return ptr + index < ptr; // expected-warning {{pointer comparison always evaluates to false}}
38+
}
39+
40+
// Negative tests with wrong predicate.
41+
42+
int add_ptr_idx_ule_ptr(const char *ptr, unsigned index) {
43+
return ptr + index <= ptr;
44+
}
45+
46+
int add_ptr_idx_ugt_ptr(const char *ptr, unsigned index) {
47+
return ptr + index > ptr;
48+
}
49+
50+
int ptr_uge_add_idx_ptr(const char *ptr, unsigned index) {
51+
return ptr >= index + ptr;
52+
}
53+
54+
int ptr_ult_add_idx_ptr(const char *ptr, unsigned index) {
55+
return ptr < index + ptr;
56+
}
57+
58+
// Negative test with signed index.
59+
60+
int add_ptr_idx_ult_ptr_signed(const char *ptr, int index) {
61+
return ptr + index < ptr;
62+
}
63+
64+
// Negative test with unrelated pointers.
65+
66+
int add_ptr_idx_ult_ptr2(const char *ptr, const char *ptr2, unsigned index) {
67+
return ptr + index < ptr2;
68+
}
69+
70+
// Negative test with non-pointer operands.
71+
72+
int add_ptr_idx_ult_ptr_not_pointer(unsigned ptr, unsigned index) {
73+
return ptr + index < ptr;
74+
}

0 commit comments

Comments
 (0)