Skip to content

Commit 2da57f8

Browse files
authored
[Clang] Improve -Wtautological-overlap-compare diagnostics flag (#133653)
This PR attempts to improve the diagnostics flag `-Wtautological-overlap-compare` (#13473). I have added code to warn about float-point literals and character literals. I have also changed the warning message for the non-overlapping case to provide a more correct hint to the user. Fixes #13473.
1 parent 8836d68 commit 2da57f8

File tree

5 files changed

+217
-98
lines changed

5 files changed

+217
-98
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,9 @@ Improvements to Clang's diagnostics
512512
- Several compatibility diagnostics that were incorrectly being grouped under
513513
``-Wpre-c++20-compat`` are now part of ``-Wc++20-compat``. (#GH138775)
514514

515+
- Improved the ``-Wtautological-overlap-compare`` diagnostics to warn about overlapping and non-overlapping ranges involving character literals and floating-point literals.
516+
The warning message for non-overlapping cases has also been improved (#GH13473).
517+
515518
Improvements to Clang's time-trace
516519
----------------------------------
517520

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10531,7 +10531,7 @@ def warn_tautological_negation_or_compare: Warning<
1053110531
"'||' of a value and its negation always evaluates to true">,
1053210532
InGroup<TautologicalNegationCompare>, DefaultIgnore;
1053310533
def warn_tautological_overlap_comparison : Warning<
10534-
"overlapping comparisons always evaluate to %select{false|true}0">,
10534+
"%select{non-|}0overlapping comparisons always evaluate to %select{false|true}0">,
1053510535
InGroup<TautologicalOverlapCompare>, DefaultIgnore;
1053610536
def warn_depr_array_comparison : Warning<
1053710537
"comparison between two arrays is deprecated; "

clang/lib/Analysis/CFG.cpp

Lines changed: 112 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "clang/Basic/LangOptions.h"
3737
#include "clang/Basic/SourceLocation.h"
3838
#include "clang/Basic/Specifiers.h"
39+
#include "llvm/ADT/APFloat.h"
3940
#include "llvm/ADT/APInt.h"
4041
#include "llvm/ADT/APSInt.h"
4142
#include "llvm/ADT/ArrayRef.h"
@@ -70,19 +71,19 @@ static SourceLocation GetEndLoc(Decl *D) {
7071
return D->getLocation();
7172
}
7273

73-
/// Returns true on constant values based around a single IntegerLiteral.
74-
/// Allow for use of parentheses, integer casts, and negative signs.
75-
/// FIXME: it would be good to unify this function with
76-
/// getIntegerLiteralSubexpressionValue at some point given the similarity
77-
/// between the functions.
74+
/// Returns true on constant values based around a single IntegerLiteral,
75+
/// CharacterLiteral, or FloatingLiteral. Allow for use of parentheses, integer
76+
/// casts, and negative signs.
7877

79-
static bool IsIntegerLiteralConstantExpr(const Expr *E) {
78+
static bool IsLiteralConstantExpr(const Expr *E) {
8079
// Allow parentheses
8180
E = E->IgnoreParens();
8281

83-
// Allow conversions to different integer kind.
82+
// Allow conversions to different integer kind, and integer to floating point
83+
// (to account for float comparing with int).
8484
if (const auto *CE = dyn_cast<CastExpr>(E)) {
85-
if (CE->getCastKind() != CK_IntegralCast)
85+
if (CE->getCastKind() != CK_IntegralCast &&
86+
CE->getCastKind() != CK_IntegralToFloating)
8687
return false;
8788
E = CE->getSubExpr();
8889
}
@@ -93,16 +94,15 @@ static bool IsIntegerLiteralConstantExpr(const Expr *E) {
9394
return false;
9495
E = UO->getSubExpr();
9596
}
96-
97-
return isa<IntegerLiteral>(E);
97+
return isa<IntegerLiteral, CharacterLiteral, FloatingLiteral>(E);
9898
}
9999

100100
/// Helper for tryNormalizeBinaryOperator. Attempts to extract an IntegerLiteral
101-
/// constant expression or EnumConstantDecl from the given Expr. If it fails,
102-
/// returns nullptr.
103-
static const Expr *tryTransformToIntOrEnumConstant(const Expr *E) {
101+
/// FloatingLiteral, CharacterLiteral or EnumConstantDecl from the given Expr.
102+
/// If it fails, returns nullptr.
103+
static const Expr *tryTransformToLiteralConstant(const Expr *E) {
104104
E = E->IgnoreParens();
105-
if (IsIntegerLiteralConstantExpr(E))
105+
if (IsLiteralConstantExpr(E))
106106
return E;
107107
if (auto *DR = dyn_cast<DeclRefExpr>(E->IgnoreParenImpCasts()))
108108
return isa<EnumConstantDecl>(DR->getDecl()) ? DR : nullptr;
@@ -119,7 +119,7 @@ tryNormalizeBinaryOperator(const BinaryOperator *B) {
119119
BinaryOperatorKind Op = B->getOpcode();
120120

121121
const Expr *MaybeDecl = B->getLHS();
122-
const Expr *Constant = tryTransformToIntOrEnumConstant(B->getRHS());
122+
const Expr *Constant = tryTransformToLiteralConstant(B->getRHS());
123123
// Expr looked like `0 == Foo` instead of `Foo == 0`
124124
if (Constant == nullptr) {
125125
// Flip the operator
@@ -133,7 +133,7 @@ tryNormalizeBinaryOperator(const BinaryOperator *B) {
133133
Op = BO_GE;
134134

135135
MaybeDecl = B->getRHS();
136-
Constant = tryTransformToIntOrEnumConstant(B->getLHS());
136+
Constant = tryTransformToLiteralConstant(B->getLHS());
137137
}
138138

139139
return std::make_tuple(MaybeDecl, Op, Constant);
@@ -1082,10 +1082,10 @@ class CFGBuilder {
10821082
return std::nullopt;
10831083
}
10841084

1085+
template <typename APFloatOrInt>
10851086
TryResult analyzeLogicOperatorCondition(BinaryOperatorKind Relation,
1086-
const llvm::APSInt &Value1,
1087-
const llvm::APSInt &Value2) {
1088-
assert(Value1.isSigned() == Value2.isSigned());
1087+
const APFloatOrInt &Value1,
1088+
const APFloatOrInt &Value2) {
10891089
switch (Relation) {
10901090
default:
10911091
return TryResult();
@@ -1170,82 +1170,120 @@ class CFGBuilder {
11701170
if (!areExprTypesCompatible(NumExpr1, NumExpr2))
11711171
return {};
11721172

1173+
// Check that the two expressions are of the same type.
11731174
Expr::EvalResult L1Result, L2Result;
1174-
if (!NumExpr1->EvaluateAsInt(L1Result, *Context) ||
1175-
!NumExpr2->EvaluateAsInt(L2Result, *Context))
1176-
return {};
1177-
1178-
llvm::APSInt L1 = L1Result.Val.getInt();
1179-
llvm::APSInt L2 = L2Result.Val.getInt();
1180-
1181-
// Can't compare signed with unsigned or with different bit width.
1182-
if (L1.isSigned() != L2.isSigned() || L1.getBitWidth() != L2.getBitWidth())
1175+
if (!NumExpr1->EvaluateAsRValue(L1Result, *Context) ||
1176+
!NumExpr2->EvaluateAsRValue(L2Result, *Context))
11831177
return {};
11841178

1185-
// Values that will be used to determine if result of logical
1186-
// operator is always true/false
1187-
const llvm::APSInt Values[] = {
1188-
// Value less than both Value1 and Value2
1189-
llvm::APSInt::getMinValue(L1.getBitWidth(), L1.isUnsigned()),
1190-
// L1
1191-
L1,
1192-
// Value between Value1 and Value2
1193-
((L1 < L2) ? L1 : L2) + llvm::APSInt(llvm::APInt(L1.getBitWidth(), 1),
1194-
L1.isUnsigned()),
1195-
// L2
1196-
L2,
1197-
// Value greater than both Value1 and Value2
1198-
llvm::APSInt::getMaxValue(L1.getBitWidth(), L1.isUnsigned()),
1199-
};
1200-
1201-
// Check whether expression is always true/false by evaluating the following
1179+
// Check whether expression is always true/false by evaluating the
1180+
// following
12021181
// * variable x is less than the smallest literal.
12031182
// * variable x is equal to the smallest literal.
12041183
// * Variable x is between smallest and largest literal.
12051184
// * Variable x is equal to the largest literal.
12061185
// * Variable x is greater than largest literal.
1207-
bool AlwaysTrue = true, AlwaysFalse = true;
1208-
// Track value of both subexpressions. If either side is always
1209-
// true/false, another warning should have already been emitted.
1210-
bool LHSAlwaysTrue = true, LHSAlwaysFalse = true;
1211-
bool RHSAlwaysTrue = true, RHSAlwaysFalse = true;
1212-
for (const llvm::APSInt &Value : Values) {
1213-
TryResult Res1, Res2;
1214-
Res1 = analyzeLogicOperatorCondition(BO1, Value, L1);
1215-
Res2 = analyzeLogicOperatorCondition(BO2, Value, L2);
1216-
1217-
if (!Res1.isKnown() || !Res2.isKnown())
1218-
return {};
1186+
// This isn't technically correct, as it doesn't take into account the
1187+
// possibility that the variable could be NaN. However, this is a very rare
1188+
// case.
1189+
auto AnalyzeConditions = [&](const auto &Values,
1190+
const BinaryOperatorKind *BO1,
1191+
const BinaryOperatorKind *BO2) -> TryResult {
1192+
bool AlwaysTrue = true, AlwaysFalse = true;
1193+
// Track value of both subexpressions. If either side is always
1194+
// true/false, another warning should have already been emitted.
1195+
bool LHSAlwaysTrue = true, LHSAlwaysFalse = true;
1196+
bool RHSAlwaysTrue = true, RHSAlwaysFalse = true;
1197+
1198+
for (const auto &Value : Values) {
1199+
TryResult Res1 =
1200+
analyzeLogicOperatorCondition(*BO1, Value, Values[1] /* L1 */);
1201+
TryResult Res2 =
1202+
analyzeLogicOperatorCondition(*BO2, Value, Values[3] /* L2 */);
1203+
1204+
if (!Res1.isKnown() || !Res2.isKnown())
1205+
return {};
1206+
1207+
const bool IsAnd = B->getOpcode() == BO_LAnd;
1208+
const bool Combine = IsAnd ? (Res1.isTrue() && Res2.isTrue())
1209+
: (Res1.isTrue() || Res2.isTrue());
1210+
1211+
AlwaysTrue &= Combine;
1212+
AlwaysFalse &= !Combine;
1213+
1214+
LHSAlwaysTrue &= Res1.isTrue();
1215+
LHSAlwaysFalse &= Res1.isFalse();
1216+
RHSAlwaysTrue &= Res2.isTrue();
1217+
RHSAlwaysFalse &= Res2.isFalse();
1218+
}
12191219

1220-
if (B->getOpcode() == BO_LAnd) {
1221-
AlwaysTrue &= (Res1.isTrue() && Res2.isTrue());
1222-
AlwaysFalse &= !(Res1.isTrue() && Res2.isTrue());
1223-
} else {
1224-
AlwaysTrue &= (Res1.isTrue() || Res2.isTrue());
1225-
AlwaysFalse &= !(Res1.isTrue() || Res2.isTrue());
1220+
if (AlwaysTrue || AlwaysFalse) {
1221+
if (!LHSAlwaysTrue && !LHSAlwaysFalse && !RHSAlwaysTrue &&
1222+
!RHSAlwaysFalse && BuildOpts.Observer) {
1223+
BuildOpts.Observer->compareAlwaysTrue(B, AlwaysTrue);
1224+
}
1225+
return TryResult(AlwaysTrue);
12261226
}
1227+
return {};
1228+
};
12271229

1228-
LHSAlwaysTrue &= Res1.isTrue();
1229-
LHSAlwaysFalse &= Res1.isFalse();
1230-
RHSAlwaysTrue &= Res2.isTrue();
1231-
RHSAlwaysFalse &= Res2.isFalse();
1230+
// Handle integer comparison.
1231+
if (L1Result.Val.getKind() == APValue::Int &&
1232+
L2Result.Val.getKind() == APValue::Int) {
1233+
llvm::APSInt L1 = L1Result.Val.getInt();
1234+
llvm::APSInt L2 = L2Result.Val.getInt();
1235+
1236+
// Can't compare signed with unsigned or with different bit width.
1237+
if (L1.isSigned() != L2.isSigned() ||
1238+
L1.getBitWidth() != L2.getBitWidth())
1239+
return {};
1240+
1241+
// Values that will be used to determine if result of logical
1242+
// operator is always true/false
1243+
const llvm::APSInt Values[] = {
1244+
// Value less than both Value1 and Value2
1245+
llvm::APSInt::getMinValue(L1.getBitWidth(), L1.isUnsigned()),
1246+
// L1
1247+
L1,
1248+
// Value between Value1 and Value2
1249+
((L1 < L2) ? L1 : L2) +
1250+
llvm::APSInt(llvm::APInt(L1.getBitWidth(), 1), L1.isUnsigned()),
1251+
// L2
1252+
L2,
1253+
// Value greater than both Value1 and Value2
1254+
llvm::APSInt::getMaxValue(L1.getBitWidth(), L1.isUnsigned()),
1255+
};
1256+
1257+
return AnalyzeConditions(Values, &BO1, &BO2);
12321258
}
12331259

1234-
if (AlwaysTrue || AlwaysFalse) {
1235-
if (!LHSAlwaysTrue && !LHSAlwaysFalse && !RHSAlwaysTrue &&
1236-
!RHSAlwaysFalse && BuildOpts.Observer)
1237-
BuildOpts.Observer->compareAlwaysTrue(B, AlwaysTrue);
1238-
return TryResult(AlwaysTrue);
1260+
// Handle float comparison.
1261+
if (L1Result.Val.getKind() == APValue::Float &&
1262+
L2Result.Val.getKind() == APValue::Float) {
1263+
llvm::APFloat L1 = L1Result.Val.getFloat();
1264+
llvm::APFloat L2 = L2Result.Val.getFloat();
1265+
llvm::APFloat MidValue = L1;
1266+
MidValue.add(L2, llvm::APFloat::rmNearestTiesToEven);
1267+
MidValue.divide(llvm::APFloat(MidValue.getSemantics(), "2.0"),
1268+
llvm::APFloat::rmNearestTiesToEven);
1269+
1270+
const llvm::APFloat Values[] = {
1271+
llvm::APFloat::getSmallest(L1.getSemantics(), true), L1, MidValue, L2,
1272+
llvm::APFloat::getLargest(L2.getSemantics(), false),
1273+
};
1274+
1275+
return AnalyzeConditions(Values, &BO1, &BO2);
12391276
}
1277+
12401278
return {};
12411279
}
12421280

12431281
/// A bitwise-or with a non-zero constant always evaluates to true.
12441282
TryResult checkIncorrectBitwiseOrOperator(const BinaryOperator *B) {
12451283
const Expr *LHSConstant =
1246-
tryTransformToIntOrEnumConstant(B->getLHS()->IgnoreParenImpCasts());
1284+
tryTransformToLiteralConstant(B->getLHS()->IgnoreParenImpCasts());
12471285
const Expr *RHSConstant =
1248-
tryTransformToIntOrEnumConstant(B->getRHS()->IgnoreParenImpCasts());
1286+
tryTransformToLiteralConstant(B->getRHS()->IgnoreParenImpCasts());
12491287

12501288
if ((LHSConstant && RHSConstant) || (!LHSConstant && !RHSConstant))
12511289
return {};

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,14 @@ class LogicalErrorHandler : public CFGCallback {
166166
S.Diag(B->getExprLoc(), DiagID) << DiagRange;
167167
}
168168

169-
void compareAlwaysTrue(const BinaryOperator *B, bool isAlwaysTrue) override {
169+
void compareAlwaysTrue(const BinaryOperator *B,
170+
bool isAlwaysTrueOrFalse) override {
170171
if (HasMacroID(B))
171172
return;
172173

173174
SourceRange DiagRange = B->getSourceRange();
174175
S.Diag(B->getExprLoc(), diag::warn_tautological_overlap_comparison)
175-
<< DiagRange << isAlwaysTrue;
176+
<< DiagRange << isAlwaysTrueOrFalse;
176177
}
177178

178179
void compareBitwiseEquality(const BinaryOperator *B,

0 commit comments

Comments
 (0)