Skip to content

Commit aeba7a8

Browse files
[clang][diagnostics] added warning for possible enum compare typo (#168445)
Added diagnosis and fixit comment for possible accidental comparison operator in an enum. Closes: #168146
1 parent 150d9b7 commit aeba7a8

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ Improvements to Clang's diagnostics
441441
- Clang no longer emits ``-Wmissing-noreturn`` for virtual methods where
442442
the function body consists of a `throw` expression (#GH167247).
443443

444+
- A new warning ``-Wenum-compare-typo`` has been added to detect potential erroneous
445+
comparison operators when mixed with bitwise operators in enum value initializers.
446+
This can be locally disabled by explicitly casting the initializer value.
447+
444448
Improvements to Clang's time-trace
445449
----------------------------------
446450

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13739,4 +13739,12 @@ def err_amdgcn_load_lds_size_invalid_value : Error<"invalid size value">;
1373913739
def note_amdgcn_load_lds_size_valid_value : Note<"size must be %select{1, 2, or 4|1, 2, 4, 12 or 16}0">;
1374013740

1374113741
def err_amdgcn_coop_atomic_invalid_as : Error<"cooperative atomic requires a global or generic pointer">;
13742+
13743+
def warn_comparison_in_enum_initializer : Warning<
13744+
"comparison operator '%0' is potentially a typo for a shift operator '%1'">,
13745+
InGroup<DiagGroup<"enum-compare-typo">>;
13746+
13747+
def note_enum_compare_typo_suggest : Note<
13748+
"use '%0' to perform a bitwise shift">;
13749+
1374213750
} // end of sema component.

clang/lib/Sema/SemaDecl.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include "llvm/ADT/SmallPtrSet.h"
6464
#include "llvm/ADT/SmallString.h"
6565
#include "llvm/ADT/StringExtras.h"
66+
#include "llvm/ADT/StringRef.h"
6667
#include "llvm/Support/SaveAndRestore.h"
6768
#include "llvm/TargetParser/Triple.h"
6869
#include <algorithm>
@@ -20610,6 +20611,59 @@ bool Sema::IsValueInFlagEnum(const EnumDecl *ED, const llvm::APInt &Val,
2061020611
return !(FlagMask & Val) || (AllowMask && !(FlagMask & ~Val));
2061120612
}
2061220613

20614+
// Emits a warning when a suspicious comparison operator is used along side
20615+
// binary operators in enum initializers.
20616+
static void CheckForComparisonInEnumInitializer(SemaBase &Sema,
20617+
const EnumDecl *Enum) {
20618+
bool HasBitwiseOp = false;
20619+
SmallVector<const BinaryOperator *, 4> SuspiciousCompares;
20620+
20621+
// Iterate over all the enum values, gather suspisious comparison ops and
20622+
// whether any enum initialisers contain a binary operator.
20623+
for (const auto *ECD : Enum->enumerators()) {
20624+
const Expr *InitExpr = ECD->getInitExpr();
20625+
if (!InitExpr)
20626+
continue;
20627+
20628+
const Expr *E = InitExpr->IgnoreParenImpCasts();
20629+
20630+
if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
20631+
BinaryOperatorKind Op = BinOp->getOpcode();
20632+
20633+
// Check for bitwise ops (<<, >>, &, |)
20634+
if (BinOp->isBitwiseOp() || BinOp->isShiftOp()) {
20635+
HasBitwiseOp = true;
20636+
} else if (Op == BO_LT || Op == BO_GT) {
20637+
// Check for the typo pattern (Comparison < or >)
20638+
const Expr *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
20639+
if (const auto *IntLiteral = dyn_cast<IntegerLiteral>(LHS)) {
20640+
// Specifically looking for accidental bitshifts "1 < X" or "1 > X"
20641+
if (IntLiteral->getValue() == 1)
20642+
SuspiciousCompares.push_back(BinOp);
20643+
}
20644+
}
20645+
}
20646+
}
20647+
20648+
// If we found a bitwise op and some sus compares, iterate over the compares
20649+
// and warn.
20650+
if (HasBitwiseOp) {
20651+
for (const auto *BinOp : SuspiciousCompares) {
20652+
StringRef SuggestedOp = (BinOp->getOpcode() == BO_LT)
20653+
? BinaryOperator::getOpcodeStr(BO_Shl)
20654+
: BinaryOperator::getOpcodeStr(BO_Shr);
20655+
SourceLocation OperatorLoc = BinOp->getOperatorLoc();
20656+
20657+
Sema.Diag(OperatorLoc, diag::warn_comparison_in_enum_initializer)
20658+
<< BinOp->getOpcodeStr() << SuggestedOp;
20659+
20660+
Sema.Diag(OperatorLoc, diag::note_enum_compare_typo_suggest)
20661+
<< SuggestedOp
20662+
<< FixItHint::CreateReplacement(OperatorLoc, SuggestedOp);
20663+
}
20664+
}
20665+
}
20666+
2061320667
void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
2061420668
Decl *EnumDeclX, ArrayRef<Decl *> Elements, Scope *S,
2061520669
const ParsedAttributesView &Attrs) {
@@ -20747,6 +20801,7 @@ void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
2074720801
NumPositiveBits, NumNegativeBits);
2074820802

2074920803
CheckForDuplicateEnumValues(*this, Elements, Enum, EnumType);
20804+
CheckForComparisonInEnumInitializer(*this, Enum);
2075020805

2075120806
if (Enum->isClosedFlag()) {
2075220807
for (Decl *D : Elements) {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// RUN: %clang_cc1 -fsyntax-only -Wenum-compare-typo -verify %s
2+
// RUN: %clang_cc1 -fsyntax-only -Wenum-compare-typo -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
3+
4+
5+
enum PossibleTypoLeft {
6+
Val1 = 1 << 0,
7+
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
8+
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
9+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:12-[[@LINE+1]]:13}:"<<"
10+
Bad1 = 1 < 2,
11+
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
12+
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
13+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:12-[[@LINE+1]]:13}:">>"
14+
Bad2 = 1 > 3,
15+
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
16+
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
17+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:13-[[@LINE+1]]:14}:">>"
18+
Bad3 = (1 > 3)
19+
};
20+
21+
enum PossibleTypoRight {
22+
Val2 = 1 >> 0,
23+
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
24+
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
25+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:12-[[@LINE+1]]:13}:"<<"
26+
Bad4 = 1 < 2,
27+
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
28+
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
29+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:12-[[@LINE+1]]:13}:">>"
30+
Bad5 = 1 > 3,
31+
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
32+
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
33+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:13-[[@LINE+1]]:14}:"<<"
34+
Bad6 = (1 < 3)
35+
};
36+
37+
// Case 3: Context provided by other bitwise operators (&, |)
38+
// Even though there are no shifts, the presence of '|' implies flags.
39+
enum PossibleTypoBitwiseOr {
40+
FlagA = 0x1,
41+
FlagB = 0x2,
42+
FlagCombo = FlagA | FlagB,
43+
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
44+
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
45+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:17-[[@LINE+1]]:18}:"<<"
46+
FlagTypo1 = 1 < FlagCombo,
47+
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
48+
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
49+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:17-[[@LINE+1]]:18}:">>"
50+
FlagTypo2 = 1 > FlagCombo
51+
};
52+
53+
enum PossibleTypoBitwiseAnd {
54+
FlagAnd = FlagA & FlagB,
55+
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
56+
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
57+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:17-[[@LINE+1]]:18}:"<<"
58+
FlagTypo3 = 1 < FlagAnd,
59+
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
60+
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
61+
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:17-[[@LINE+1]]:18}:">>"
62+
FlagTypo4 = 1 > FlagAnd
63+
};
64+
65+
enum NoWarningOnDirectInit {
66+
A = 0,
67+
B = 1,
68+
Ok1 = 1 < 2, // No warning expected
69+
Ok2 = 1 > 2 // No warning expected
70+
};
71+
72+
enum NoWarningOnArith {
73+
D = 0 + 1,
74+
E = D * 10,
75+
F = E - D,
76+
G = F / D,
77+
Ok3 = 1 < E, // No warning expected
78+
Ok4 = 1 > E // No warning expected
79+
};
80+
81+
enum NoWarningOnExplicitCast {
82+
Bit1 = 1 << 0,
83+
Ok5 = (int)(1 < 2) // No warning expected
84+
};
85+
86+
enum NoWarningOnNoneBitShift {
87+
Bit2 = 1 << 0,
88+
Ok6 = (3 < 2) // No warning expected
89+
};
90+
91+
// Ensure the diagnostic group works
92+
#pragma clang diagnostic push
93+
#pragma clang diagnostic ignored "-Wenum-compare-typo"
94+
enum IGNORED {
95+
Ok7 = 1 << 1,
96+
Ignored3 = 1 < 10 // No warning
97+
};
98+
#pragma clang diagnostic pop

0 commit comments

Comments
 (0)