mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-16 16:56:35 +00:00
[clang][Analysis] Handle && and || against variable and its negation as tautology
This patch introduces a new warning flag -Wtautological-negation-compare grouped in -Wtautological-compare that warns on the use of && or || operators against a variable and its negation. e.g. x || !x and !x && x This also makes the -Winfinite-recursion diagnose more cases. Fixes https://github.com/llvm/llvm-project/issues/56035 Differential Revision: https://reviews.llvm.org/D152093
This commit is contained in:
parent
b719e41078
commit
b3469ce6f8
@ -132,6 +132,10 @@ Improvements to Clang's diagnostics
|
||||
of a base class is not called in the constructor of its derived class.
|
||||
- Clang no longer emits ``-Wmissing-variable-declarations`` for variables declared
|
||||
with the ``register`` storage class.
|
||||
- Clang's ``-Wtautological-negation-compare`` flag now diagnoses logical
|
||||
tautologies like ``x && !x`` and ``!x || x`` in expressions. This also
|
||||
makes ``-Winfinite-recursion`` diagnose more cases.
|
||||
(`#56035: <https://github.com/llvm/llvm-project/issues/56035>`_).
|
||||
|
||||
Bug Fixes in This Version
|
||||
-------------------------
|
||||
|
@ -1162,6 +1162,7 @@ public:
|
||||
CFGCallback() = default;
|
||||
virtual ~CFGCallback() = default;
|
||||
|
||||
virtual void logicAlwaysTrue(const BinaryOperator *B, bool isAlwaysTrue) {}
|
||||
virtual void compareAlwaysTrue(const BinaryOperator *B, bool isAlwaysTrue) {}
|
||||
virtual void compareBitwiseEquality(const BinaryOperator *B,
|
||||
bool isAlwaysTrue) {}
|
||||
|
@ -678,13 +678,15 @@ def TautologicalOverlapCompare : DiagGroup<"tautological-overlap-compare">;
|
||||
def TautologicalBitwiseCompare : DiagGroup<"tautological-bitwise-compare">;
|
||||
def TautologicalUndefinedCompare : DiagGroup<"tautological-undefined-compare">;
|
||||
def TautologicalObjCBoolCompare : DiagGroup<"tautological-objc-bool-compare">;
|
||||
def TautologicalNegationCompare : DiagGroup<"tautological-negation-compare">;
|
||||
def TautologicalCompare : DiagGroup<"tautological-compare",
|
||||
[TautologicalConstantCompare,
|
||||
TautologicalPointerCompare,
|
||||
TautologicalOverlapCompare,
|
||||
TautologicalBitwiseCompare,
|
||||
TautologicalUndefinedCompare,
|
||||
TautologicalObjCBoolCompare]>;
|
||||
TautologicalObjCBoolCompare,
|
||||
TautologicalNegationCompare]>;
|
||||
def HeaderHygiene : DiagGroup<"header-hygiene">;
|
||||
def DuplicateDeclSpecifier : DiagGroup<"duplicate-decl-specifier">;
|
||||
def CompareDistinctPointerType : DiagGroup<"compare-distinct-pointer-types">;
|
||||
|
@ -9796,6 +9796,12 @@ def warn_comparison_bitwise_always : Warning<
|
||||
def warn_comparison_bitwise_or : Warning<
|
||||
"bitwise or with non-zero value always evaluates to true">,
|
||||
InGroup<TautologicalBitwiseCompare>, DefaultIgnore;
|
||||
def warn_tautological_negation_and_compare: Warning<
|
||||
"'&&' of a value and its negation always evaluates to false">,
|
||||
InGroup<TautologicalNegationCompare>, DefaultIgnore;
|
||||
def warn_tautological_negation_or_compare: Warning<
|
||||
"'||' of a value and its negation always evaluates to true">,
|
||||
InGroup<TautologicalNegationCompare>, DefaultIgnore;
|
||||
def warn_tautological_overlap_comparison : Warning<
|
||||
"overlapping comparisons always evaluate to %select{false|true}0">,
|
||||
InGroup<TautologicalOverlapCompare>, DefaultIgnore;
|
||||
|
@ -1070,16 +1070,41 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
/// Find a pair of comparison expressions with or without parentheses
|
||||
/// There are two checks handled by this function:
|
||||
/// 1. Find a law-of-excluded-middle or law-of-noncontradiction expression
|
||||
/// e.g. if (x || !x), if (x && !x)
|
||||
/// 2. Find a pair of comparison expressions with or without parentheses
|
||||
/// with a shared variable and constants and a logical operator between them
|
||||
/// that always evaluates to either true or false.
|
||||
/// e.g. if (x != 3 || x != 4)
|
||||
TryResult checkIncorrectLogicOperator(const BinaryOperator *B) {
|
||||
assert(B->isLogicalOp());
|
||||
const BinaryOperator *LHS =
|
||||
dyn_cast<BinaryOperator>(B->getLHS()->IgnoreParens());
|
||||
const BinaryOperator *RHS =
|
||||
dyn_cast<BinaryOperator>(B->getRHS()->IgnoreParens());
|
||||
const Expr *LHSExpr = B->getLHS()->IgnoreParens();
|
||||
const Expr *RHSExpr = B->getRHS()->IgnoreParens();
|
||||
|
||||
auto CheckLogicalOpWithNegatedVariable = [this, B](const Expr *E1,
|
||||
const Expr *E2) {
|
||||
if (const auto *Negate = dyn_cast<UnaryOperator>(E1)) {
|
||||
if (Negate->getOpcode() == UO_LNot &&
|
||||
Expr::isSameComparisonOperand(Negate->getSubExpr(), E2)) {
|
||||
bool AlwaysTrue = B->getOpcode() == BO_LOr;
|
||||
if (BuildOpts.Observer)
|
||||
BuildOpts.Observer->logicAlwaysTrue(B, AlwaysTrue);
|
||||
return TryResult(AlwaysTrue);
|
||||
}
|
||||
}
|
||||
return TryResult();
|
||||
};
|
||||
|
||||
TryResult Result = CheckLogicalOpWithNegatedVariable(LHSExpr, RHSExpr);
|
||||
if (Result.isKnown())
|
||||
return Result;
|
||||
Result = CheckLogicalOpWithNegatedVariable(RHSExpr, LHSExpr);
|
||||
if (Result.isKnown())
|
||||
return Result;
|
||||
|
||||
const auto *LHS = dyn_cast<BinaryOperator>(LHSExpr);
|
||||
const auto *RHS = dyn_cast<BinaryOperator>(RHSExpr);
|
||||
if (!LHS || !RHS)
|
||||
return {};
|
||||
|
||||
|
@ -158,6 +158,17 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
void logicAlwaysTrue(const BinaryOperator *B, bool isAlwaysTrue) override {
|
||||
if (HasMacroID(B))
|
||||
return;
|
||||
|
||||
unsigned DiagID = isAlwaysTrue
|
||||
? diag::warn_tautological_negation_or_compare
|
||||
: diag::warn_tautological_negation_and_compare;
|
||||
SourceRange DiagRange = B->getSourceRange();
|
||||
S.Diag(B->getExprLoc(), DiagID) << DiagRange;
|
||||
}
|
||||
|
||||
void compareAlwaysTrue(const BinaryOperator *B, bool isAlwaysTrue) override {
|
||||
if (HasMacroID(B))
|
||||
return;
|
||||
@ -188,7 +199,8 @@ public:
|
||||
static bool hasActiveDiagnostics(DiagnosticsEngine &Diags,
|
||||
SourceLocation Loc) {
|
||||
return !Diags.isIgnored(diag::warn_tautological_overlap_comparison, Loc) ||
|
||||
!Diags.isIgnored(diag::warn_comparison_bitwise_or, Loc);
|
||||
!Diags.isIgnored(diag::warn_comparison_bitwise_or, Loc) ||
|
||||
!Diags.isIgnored(diag::warn_tautological_negation_and_compare, Loc);
|
||||
}
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
@ -1393,13 +1393,13 @@ const C &bar3(bool coin) {
|
||||
// CHECK: Succs (2): B2 B1
|
||||
// CHECK: [B4 (NORETURN)]
|
||||
// CHECK: 1: ~NoReturn() (Temporary object destructor)
|
||||
// CHECK: Preds (1): B5
|
||||
// CHECK: Preds (1): B5(Unreachable)
|
||||
// CHECK: Succs (1): B0
|
||||
// CHECK: [B5]
|
||||
// CHECK: 1: [B8.3] || [B7.2] || [B6.7]
|
||||
// CHECK: T: (Temp Dtor) [B6.4]
|
||||
// CHECK: Preds (3): B6 B7 B8
|
||||
// CHECK: Succs (2): B4 B3
|
||||
// CHECK: Succs (2): B4(Unreachable) B3
|
||||
// CHECK: [B6]
|
||||
// CHECK: 1: check
|
||||
// CHECK: 2: [B6.1] (ImplicitCastExpr, FunctionToPointerDecay, _Bool (*)(const NoReturn &))
|
||||
|
@ -55,6 +55,7 @@ CHECK-NEXT: -Wtautological-overlap-compare
|
||||
CHECK-NEXT: -Wtautological-bitwise-compare
|
||||
CHECK-NEXT: -Wtautological-undefined-compare
|
||||
CHECK-NEXT: -Wtautological-objc-bool-compare
|
||||
CHECK-NEXT: -Wtautological-negation-compare
|
||||
CHECK-NEXT: -Wtrigraphs
|
||||
CHECK-NEXT: -Wuninitialized
|
||||
CHECK-NEXT: -Wsometimes-uninitialized
|
||||
|
41
clang/test/SemaCXX/tautological-negation-compare.cpp
Normal file
41
clang/test/SemaCXX/tautological-negation-compare.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -Wtautological-negation-compare -Wno-constant-logical-operand %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -Wtautological-compare -Wno-constant-logical-operand %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -Wall -Wno-unused -Wno-loop-analysis -Wno-constant-logical-operand %s
|
||||
|
||||
#define COPY(x) x
|
||||
|
||||
void test_int(int x) {
|
||||
if (x || !x) {} // expected-warning {{'||' of a value and its negation always evaluates to true}}
|
||||
if (!x || x) {} // expected-warning {{'||' of a value and its negation always evaluates to true}}
|
||||
if (x && !x) {} // expected-warning {{'&&' of a value and its negation always evaluates to false}}
|
||||
if (!x && x) {} // expected-warning {{'&&' of a value and its negation always evaluates to false}}
|
||||
|
||||
// parentheses are ignored
|
||||
if (x || (!x)) {} // expected-warning {{'||' of a value and its negation always evaluates to true}}
|
||||
if (!(x) || x) {} // expected-warning {{'||' of a value and its negation always evaluates to true}}
|
||||
|
||||
// don't warn on macros
|
||||
if (COPY(x) || !x) {}
|
||||
if (!x || COPY(x)) {}
|
||||
if (x && COPY(!x)) {}
|
||||
if (COPY(!x && x)) {}
|
||||
|
||||
// dont' warn on literals
|
||||
if (1 || !1) {}
|
||||
if (!42 && 42) {}
|
||||
|
||||
|
||||
// don't warn on overloads
|
||||
struct Foo{
|
||||
int val;
|
||||
Foo operator!() const { return Foo{!val}; }
|
||||
bool operator||(const Foo other) const { return val || other.val; }
|
||||
bool operator&&(const Foo other) const { return val && other.val; }
|
||||
};
|
||||
|
||||
Foo f{3};
|
||||
if (f || !f) {}
|
||||
if (!f || f) {}
|
||||
if (f.val || !f.val) {} // expected-warning {{'||' of a value and its negation always evaluates to true}}
|
||||
if (!f.val && f.val) {} // expected-warning {{'&&' of a value and its negation always evaluates to false}}
|
||||
}
|
@ -203,3 +203,13 @@ int unevaluated_recursive_function() {
|
||||
(void)typeid(unevaluated_recursive_function());
|
||||
return 0;
|
||||
}
|
||||
|
||||
void func1(int i) { // expected-warning {{call itself}}
|
||||
if (i || !i)
|
||||
func1(i);
|
||||
}
|
||||
void func2(int i) { // expected-warning {{call itself}}
|
||||
if (!i && i) {}
|
||||
else
|
||||
func2(i);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user