Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -428,12 +428,14 @@ def StringPlusChar : DiagGroup<"string-plus-char">; def StrncatSize : DiagGroup<"strncat-size">; def TautologicalUnsignedZeroCompare : DiagGroup<"tautological-unsigned-zero-compare">; +def TautologicalUnsignedEnumZeroCompare : DiagGroup<"tautological-unsigned-enum-zero-compare">; def TautologicalOutOfRangeCompare : DiagGroup<"tautological-constant-out-of-range-compare">; def TautologicalPointerCompare : DiagGroup<"tautological-pointer-compare">; def TautologicalOverlapCompare : DiagGroup<"tautological-overlap-compare">; def TautologicalUndefinedCompare : DiagGroup<"tautological-undefined-compare">; def TautologicalCompare : DiagGroup<"tautological-compare", [TautologicalUnsignedZeroCompare, + TautologicalUnsignedEnumZeroCompare, TautologicalOutOfRangeCompare, TautologicalPointerCompare, TautologicalOverlapCompare, Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -5900,19 +5900,26 @@ "member function %q1 is declared const here|" "%select{|nested }1data member %2 declared const here}0">; +def warn_lunsigned_always_true_comparison : Warning< + "comparison of unsigned expression %0 is always %select{false|true}1">, + InGroup; +def warn_runsigned_always_true_comparison : Warning< + "comparison of %0 unsigned expression is always %select{false|true}1">, + InGroup; +def warn_lunsigned_enum_always_true_comparison : Warning< + "comparison of unsigned enum expression %0 is always %select{false|true}1">, + InGroup; +def warn_runsigned_enum_always_true_comparison : Warning< + "comparison of %0 unsigned enum expression is always %select{false|true}1">, + InGroup; + def warn_mixed_sign_comparison : Warning< "comparison of integers of different signs: %0 and %1">, InGroup, DefaultIgnore; -def warn_lunsigned_always_true_comparison : Warning< - "comparison of unsigned%select{| enum}2 expression %0 is always %1">, - InGroup; def warn_out_of_range_compare : Warning< "comparison of %select{constant %0|true|false}1 with " "%select{expression of type %2|boolean expression}3 is always " "%select{false|true}4">, InGroup; -def warn_runsigned_always_true_comparison : Warning< - "comparison of %0 unsigned%select{| enum}2 expression is always %1">, - InGroup; def warn_comparison_of_mixed_enum_types : Warning< "comparison of two values with different enumeration types" "%diff{ ($ and $)|}0,1">, Index: lib/Sema/SemaChecking.cpp =================================================================== --- lib/Sema/SemaChecking.cpp +++ lib/Sema/SemaChecking.cpp @@ -8583,35 +8583,89 @@ // bool values are handled by DiagnoseOutOfRangeComparison(). - BinaryOperatorKind op = E->getOpcode(); + BinaryOperatorKind Op = E->getOpcode(); if (E->isValueDependent()) return false; Expr *LHS = E->getLHS(); Expr *RHS = E->getRHS(); - bool Match = true; + // Result is used for two purposes. If the Result is empty, then we decided + // that given comparison is non-tautological, and no warning should emitted. + // Alternatively, if the Result is not empty, then it contains the result of + // the comparison. Since the comparison is tautological, it always evaluates + // to the same result - be it either true or false. + llvm::Optional Result; - if (op == BO_LT && isNonBooleanUnsignedValue(LHS) && IsZero(S, RHS)) { - S.Diag(E->getOperatorLoc(), diag::warn_lunsigned_always_true_comparison) - << "< 0" << "false" << HasEnumType(LHS) - << LHS->getSourceRange() << RHS->getSourceRange(); - } else if (op == BO_GE && isNonBooleanUnsignedValue(LHS) && IsZero(S, RHS)) { - S.Diag(E->getOperatorLoc(), diag::warn_lunsigned_always_true_comparison) - << ">= 0" << "true" << HasEnumType(LHS) - << LHS->getSourceRange() << RHS->getSourceRange(); - } else if (op == BO_GT && isNonBooleanUnsignedValue(RHS) && IsZero(S, LHS)) { - S.Diag(E->getOperatorLoc(), diag::warn_runsigned_always_true_comparison) - << "0 >" << "false" << HasEnumType(RHS) - << LHS->getSourceRange() << RHS->getSourceRange(); - } else if (op == BO_LE && isNonBooleanUnsignedValue(RHS) && IsZero(S, LHS)) { - S.Diag(E->getOperatorLoc(), diag::warn_runsigned_always_true_comparison) - << "0 <=" << "true" << HasEnumType(RHS) - << LHS->getSourceRange() << RHS->getSourceRange(); - } else - Match = false; + // The comparison has two sides - left and right. + // This function is called when some side is a constant and other is a + // variable. In Value we store the side which contains the variable. + Expr *Value; - return Match; + const char *Cmp; // Simplified, pretty-printed comparison string + // Similar to E->getOpcodeStr(), but with extra 0 on either LHS or RHS + + if (Op == BO_LT && isNonBooleanUnsignedValue(LHS) && IsZero(S, RHS)) { + Result = false; + Value = LHS; + Cmp = "< 0"; + } else if (Op == BO_GE && isNonBooleanUnsignedValue(LHS) && IsZero(S, RHS)) { + Result = true; + Value = LHS; + Cmp = ">= 0"; + } else if (Op == BO_GT && isNonBooleanUnsignedValue(RHS) && IsZero(S, LHS)) { + Result = false; + Value = RHS; + Cmp = "0 >"; + } else if (Op == BO_LE && isNonBooleanUnsignedValue(RHS) && IsZero(S, LHS)) { + Result = true; + Value = RHS; + Cmp = "0 <="; + } + + if (!Result) + return false; + + const bool ValueHasEnumType = HasEnumType(Value); + enum IsEnum { False, True, SizeOfIsEnum }; + static const struct Diagnostics { + unsigned BO_LT_OP[SizeOfIsEnum]; + unsigned BO_GE_OP[SizeOfIsEnum]; + unsigned BO_GT_OP[SizeOfIsEnum]; + unsigned BO_LE_OP[SizeOfIsEnum]; + } DiagnosticsTable = { + {diag::warn_lunsigned_always_true_comparison, + diag::warn_lunsigned_enum_always_true_comparison}, + {diag::warn_lunsigned_always_true_comparison, + diag::warn_lunsigned_enum_always_true_comparison}, + {diag::warn_runsigned_always_true_comparison, + diag::warn_runsigned_enum_always_true_comparison}, + {diag::warn_runsigned_always_true_comparison, + diag::warn_runsigned_enum_always_true_comparison}, + }; + + unsigned DiagID; + switch (Op) { + case BO_LT: + DiagID = DiagnosticsTable.BO_LT_OP[ValueHasEnumType]; + break; + case BO_GE: + DiagID = DiagnosticsTable.BO_GE_OP[ValueHasEnumType]; + break; + case BO_GT: + DiagID = DiagnosticsTable.BO_GT_OP[ValueHasEnumType]; + break; + case BO_LE: + DiagID = DiagnosticsTable.BO_LE_OP[ValueHasEnumType]; + break; + default: + llvm_unreachable("can't get here"); + } + + S.Diag(E->getOperatorLoc(), DiagID) + << Cmp << *Result << LHS->getSourceRange() << RHS->getSourceRange(); + + return true; } void DiagnoseOutOfRangeComparison(Sema &S, BinaryOperator *E, Expr *Constant, Index: test/Sema/compare.c =================================================================== --- test/Sema/compare.c +++ test/Sema/compare.c @@ -308,8 +308,59 @@ int rdar8511238() { enum A { A_foo, A_bar }; enum A a; + + if (a == 0) + return 0; + if (a != 0) + return 0; if (a < 0) // expected-warning {{comparison of unsigned enum expression < 0 is always false}} - return 0; + return 0; + if (a <= 0) + return 0; + if (a > 0) + return 0; + if (a >= 0) // expected-warning {{comparison of unsigned enum expression >= 0 is always true}} + return 0; + + if (0 == a) + return 0; + if (0 != a) + return 0; + if (0 < a) + return 0; + if (0 <= a) // expected-warning {{comparison of 0 <= unsigned enum expression is always true}} + return 0; + if (0 > a) // expected-warning {{comparison of 0 > unsigned enum expression is always false}} + return 0; + if (0 >= a) + return 0; + + if (a == 0U) + return 0; + if (a != 0U) + return 0; + if (a < 0U) // expected-warning {{comparison of unsigned enum expression < 0 is always false}} + return 0; + if (a <= 0U) + return 0; + if (a > 0U) + return 0; + if (a >= 0U) // expected-warning {{comparison of unsigned enum expression >= 0 is always true}} + return 0; + + if (0U == a) + return 0; + if (0U != a) + return 0; + if (0U < a) + return 0; + if (0U <= a) // expected-warning {{comparison of 0 <= unsigned enum expression is always true}} + return 0; + if (0U > a) // expected-warning {{comparison of 0 > unsigned enum expression is always false}} + return 0; + if (0U >= a) + return 0; + return 20; } Index: test/Sema/tautological-unsigned-enum-zero-compare.c =================================================================== --- /dev/null +++ test/Sema/tautological-unsigned-enum-zero-compare.c @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -fsyntax-only -DTEST -verify %s +// RUN: %clang_cc1 -fsyntax-only -Wno-tautological-unsigned-enum-zero-compare -verify %s + +int main() { + enum A { A_foo, A_bar }; + enum A a; + +#ifdef TEST + if (a < 0) // expected-warning {{comparison of unsigned enum expression < 0 is always false}} + return 0; + if (a >= 0) // expected-warning {{comparison of unsigned enum expression >= 0 is always true}} + return 0; + if (0 <= a) // expected-warning {{comparison of 0 <= unsigned enum expression is always true}} + return 0; + if (0 > a) // expected-warning {{comparison of 0 > unsigned enum expression is always false}} + return 0; + if (a < 0U) // expected-warning {{comparison of unsigned enum expression < 0 is always false}} + return 0; + if (a >= 0U) // expected-warning {{comparison of unsigned enum expression >= 0 is always true}} + return 0; + if (0U <= a) // expected-warning {{comparison of 0 <= unsigned enum expression is always true}} + return 0; + if (0U > a) // expected-warning {{comparison of 0 > unsigned enum expression is always false}} + return 0; +#else + // expected-no-diagnostics + if (a < 0) + return 0; + if (a >= 0) + return 0; + if (0 <= a) + return 0; + if (0 > a) + return 0; + if (a < 0U) + return 0; + if (a >= 0U) + return 0; + if (0U <= a) + return 0; + if (0U > a) + return 0; +#endif + + return 1; +}