Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,10 @@ Improvements to Clang's diagnostics
- Clang now emits a diagnostic in case `vector_size` or `ext_vector_type`
attributes are used with a negative size (#GH165463).

- A new warning ``-Wenum-compare-typo`` has been added to detect potential erroneous
comparison operators when mixed with bitwise operators in enum value initializers.
This can be locally disabled by explicitly casting the initializer value.

Improvements to Clang's time-trace
----------------------------------

Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -13707,4 +13707,12 @@ def err_amdgcn_load_lds_size_invalid_value : Error<"invalid size value">;
def note_amdgcn_load_lds_size_valid_value : Note<"size must be %select{1, 2, or 4|1, 2, 4, 12 or 16}0">;

def err_amdgcn_coop_atomic_invalid_as : Error<"cooperative atomic requires a global or generic pointer">;

def warn_comparison_in_enum_initializer : Warning<
"comparison operator '%0' is potentially a typo for a shift operator '%1'">,
InGroup<DiagGroup<"enum-compare-typo">>;

def note_enum_compare_typo_suggest : Note<
"use '%0' to perform a bitwise shift">;

} // end of sema component.
55 changes: 55 additions & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/SaveAndRestore.h"
#include "llvm/TargetParser/Triple.h"
#include <algorithm>
Expand Down Expand Up @@ -20584,6 +20585,59 @@ bool Sema::IsValueInFlagEnum(const EnumDecl *ED, const llvm::APInt &Val,
return !(FlagMask & Val) || (AllowMask && !(FlagMask & ~Val));
}

// Emits a warning when a suspicious comparison operator is used along side
// binary operators in enum initializers.
static void CheckForComparisonInEnumInitializer(SemaBase &Sema,
const EnumDecl *Enum) {
bool HasBitwiseOp = false;
SmallVector<const BinaryOperator *, 4> SuspiciousCompares;

// Iterate over all the enum values, gather suspisious comparison ops and
// whether any enum initialisers contain a binary operator.
for (const auto *ECD : Enum->enumerators()) {
const Expr *InitExpr = ECD->getInitExpr();
if (!InitExpr)
continue;

const Expr *E = InitExpr->IgnoreParenImpCasts();

if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
BinaryOperatorKind Op = BinOp->getOpcode();

// Check for bitwise ops (<<, >>, &, |)
if (BinOp->isBitwiseOp() || BinOp->isShiftOp()) {
HasBitwiseOp = true;
} else if (Op == BO_LT || Op == BO_GT) {
// Check for the typo pattern (Comparison < or >)
const Expr *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
if (const auto *IntLiteral = dyn_cast<IntegerLiteral>(LHS)) {
// Specifically looking for accidental bitshifts "1 < X" or "1 > X"
if (IntLiteral->getValue() == 1)
SuspiciousCompares.push_back(BinOp);
}
}
}
}

// If we found a bitwise op and some sus compares, iterate over the compares
// and warn.
if (HasBitwiseOp) {
for (const auto *BinOp : SuspiciousCompares) {
StringRef SuggestedOp = (BinOp->getOpcode() == BO_LT)
? BinaryOperator::getOpcodeStr(BO_Shl)
: BinaryOperator::getOpcodeStr(BO_Shr);
SourceLocation OperatorLoc = BinOp->getOperatorLoc();

Sema.Diag(OperatorLoc, diag::warn_comparison_in_enum_initializer)
<< BinOp->getOpcodeStr() << SuggestedOp;

Sema.Diag(OperatorLoc, diag::note_enum_compare_typo_suggest)
<< SuggestedOp
<< FixItHint::CreateReplacement(OperatorLoc, SuggestedOp);
}
}
}

void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
Decl *EnumDeclX, ArrayRef<Decl *> Elements, Scope *S,
const ParsedAttributesView &Attrs) {
Expand Down Expand Up @@ -20721,6 +20775,7 @@ void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
NumPositiveBits, NumNegativeBits);

CheckForDuplicateEnumValues(*this, Elements, Enum, EnumType);
CheckForComparisonInEnumInitializer(*this, Enum);

if (Enum->isClosedFlag()) {
for (Decl *D : Elements) {
Expand Down
98 changes: 98 additions & 0 deletions clang/test/Sema/warn-enum-compare-typo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// RUN: %clang_cc1 -fsyntax-only -Wenum-compare-typo -verify %s
// RUN: %clang_cc1 -fsyntax-only -Wenum-compare-typo -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s


enum PossibleTypoLeft {
Val1 = 1 << 0,
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:12-[[@LINE+1]]:13}:"<<"
Bad1 = 1 < 2,
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:12-[[@LINE+1]]:13}:">>"
Bad2 = 1 > 3,
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:13-[[@LINE+1]]:14}:">>"
Bad3 = (1 > 3)
};

enum PossibleTypoRight {
Val2 = 1 >> 0,
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:12-[[@LINE+1]]:13}:"<<"
Bad4 = 1 < 2,
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:12-[[@LINE+1]]:13}:">>"
Bad5 = 1 > 3,
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:13-[[@LINE+1]]:14}:"<<"
Bad6 = (1 < 3)
};

// Case 3: Context provided by other bitwise operators (&, |)
// Even though there are no shifts, the presence of '|' implies flags.
enum PossibleTypoBitwiseOr {
FlagA = 0x1,
FlagB = 0x2,
FlagCombo = FlagA | FlagB,
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:17-[[@LINE+1]]:18}:"<<"
FlagTypo1 = 1 < FlagCombo,
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:17-[[@LINE+1]]:18}:">>"
FlagTypo2 = 1 > FlagCombo
};

enum PossibleTypoBitwiseAnd {
FlagAnd = FlagA & FlagB,
// expected-warning@+3 {{comparison operator '<' is potentially a typo for a shift operator '<<'}}
// expected-note@+2 {{use '<<' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:17-[[@LINE+1]]:18}:"<<"
FlagTypo3 = 1 < FlagAnd,
// expected-warning@+3 {{comparison operator '>' is potentially a typo for a shift operator '>>'}}
// expected-note@+2 {{use '>>' to perform a bitwise shift}}
// CHECK: fix-it:"{{.*}}":{[[@LINE+1]]:17-[[@LINE+1]]:18}:">>"
FlagTypo4 = 1 > FlagAnd
};

enum NoWarningOnDirectInit {
A = 0,
B = 1,
Ok1 = 1 < 2, // No warning expected
Ok2 = 1 > 2 // No warning expected
};

enum NoWarningOnArith {
D = 0 + 1,
E = D * 10,
F = E - D,
G = F / D,
Ok3 = 1 < E, // No warning expected
Ok4 = 1 > E // No warning expected
};

enum NoWarningOnExplicitCast {
Bit1 = 1 << 0,
Ok5 = (int)(1 < 2) // No warning expected
};

enum NoWarningOnNoneBitShift {
Bit2 = 1 << 0,
Ok6 = (3 < 2) // No warning expected
};

// Ensure the diagnostic group works
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wenum-compare-typo"
enum IGNORED {
Ok7 = 1 << 1,
Ignored3 = 1 < 10 // No warning
};
#pragma clang diagnostic pop