Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -512,6 +512,7 @@ def GNUUnionCast : DiagGroup<"gnu-union-cast">; def GNUVariableSizedTypeNotAtEnd : DiagGroup<"gnu-variable-sized-type-not-at-end">; def Varargs : DiagGroup<"varargs">; +def XorUsedAsPow : DiagGroup<"xor-used-as-pow">; def Unsequenced : DiagGroup<"unsequenced">; // GCC name for -Wunsequenced Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -3322,6 +3322,15 @@ "code; pointer may be assumed to always convert to true">, InGroup; +def warn_xor_used_as_pow_base_extra : Warning< + "result of '%0' is %1; did you mean '%2' (%3)?">, + InGroup; +def warn_xor_used_as_pow_base : Warning< + "result of '%0' is %1; did you mean '%2'?">, + InGroup; +def note_xor_used_as_pow_silence : Note< + "replace expression with '%0' to silence this warning">; + def warn_null_pointer_compare : Warning< "comparison of %select{address of|function|array}0 '%1' %select{not |}2" "equal to a null pointer is always %select{true|false}2">, Index: lib/Sema/SemaExpr.cpp =================================================================== --- lib/Sema/SemaExpr.cpp +++ lib/Sema/SemaExpr.cpp @@ -10960,6 +10960,114 @@ return GetSignedVectorType(vType); } +static void diagnoseXorMisusedAsPow(Sema &S, ExprResult &LHS, ExprResult &RHS, + SourceLocation Loc) { + // Do not diagnose macros. + if (Loc.isMacroID()) + return; + + bool Negative = false; + const auto *LHSInt = dyn_cast(LHS.get()); + const auto *RHSInt = dyn_cast(RHS.get()); + + if (!LHSInt) + return; + if (!RHSInt) { + // Check negative literals. + if (!isa(RHS.get())) + return; + + const auto *UO = cast(RHS.get()); + if (UO->getOpcode() != UO_Minus) + return; + RHSInt = dyn_cast(UO->getSubExpr()); + if (!RHSInt) + return; + Negative = true; + } + + if (LHSInt->getValue().getBitWidth() != RHSInt->getValue().getBitWidth()) + return; + + CharSourceRange ExprRange = CharSourceRange::getCharRange( + LHSInt->getBeginLoc(), S.getLocForEndOfToken(RHSInt->getLocation())); + llvm::StringRef ExprStr = + Lexer::getSourceText(ExprRange, S.getSourceManager(), S.getLangOpts()); + + if (S.getLangOpts().CPlusPlus) { + CharSourceRange XorRange = + CharSourceRange::getCharRange(Loc, S.getLocForEndOfToken(Loc)); + llvm::StringRef XorStr = + Lexer::getSourceText(XorRange, S.getSourceManager(), S.getLangOpts()); + // Do not diagnose if xor keyword is used. + if (XorStr.find("xor") != llvm::StringRef::npos) + return; + } + + const llvm::APInt &LeftSideValue = LHSInt->getValue(); + const llvm::APInt &RightSideValue = RHSInt->getValue(); + const llvm::APInt XorValue = LeftSideValue ^ RightSideValue; + + std::string LHSStr = Lexer::getSourceText( + CharSourceRange::getTokenRange(LHSInt->getSourceRange()), + S.getSourceManager(), S.getLangOpts()); + std::string RHSStr = Lexer::getSourceText( + CharSourceRange::getTokenRange(RHSInt->getSourceRange()), + S.getSourceManager(), S.getLangOpts()); + + int64_t RightSideIntValue = RightSideValue.getSExtValue(); + if (Negative) { + RightSideIntValue = -RightSideIntValue; + RHSStr = "-" + RHSStr; + } + + StringRef LHSStrRef = LHSStr; + StringRef RHSStrRef = RHSStr; + // Do not diagnose binary literals. + if (LHSStrRef.startswith("0b") || LHSStrRef.startswith("0B") || + RHSStrRef.startswith("0b") || RHSStrRef.startswith("0B")) + return; + // Do not diagnose hexadecimal literals. + if (LHSStrRef.startswith("0x") || LHSStrRef.startswith("0X") || + RHSStrRef.startswith("0x") || RHSStrRef.startswith("0X")) + return; + + // Do not diagnose octal literals. + if ((LHSStrRef.size() > 1 && LHSStrRef.startswith("0")) || + (RHSStrRef.size() > 1 && RHSStrRef.startswith("0"))) + return; + + if (LeftSideValue == 2 && RightSideIntValue >= 0) { + std::string SuggestedExpr = "1 << " + RHSStr; + bool Overflow = false; + llvm::APInt One = (LeftSideValue - 1); + llvm::APInt PowValue = One.sshl_ov(RightSideValue, Overflow); + if (Overflow) { + if (RightSideIntValue < 64) + S.Diag(Loc, diag::warn_xor_used_as_pow_base) + << ExprStr << XorValue.toString(10, true) << ("1LL << " + RHSStr) + << FixItHint::CreateReplacement(ExprRange, "1LL << " + RHSStr); + else + // 2 ^ 64 case + return; + } else { + S.Diag(Loc, diag::warn_xor_used_as_pow_base_extra) + << ExprStr << XorValue.toString(10, true) << SuggestedExpr + << PowValue.toString(10, true) + << FixItHint::CreateReplacement( + ExprRange, (RightSideIntValue == 0) ? "1" : SuggestedExpr); + } + + S.Diag(Loc, diag::note_xor_used_as_pow_silence) << ("0x2 ^ " + RHSStr); + } else if (LeftSideValue == 10) { + std::string SuggestedValue = "1e" + std::to_string(RightSideIntValue); + S.Diag(Loc, diag::warn_xor_used_as_pow_base) + << ExprStr << XorValue.toString(10, true) << SuggestedValue + << FixItHint::CreateReplacement(ExprRange, SuggestedValue); + S.Diag(Loc, diag::note_xor_used_as_pow_silence) << ("0xA ^ " + RHSStr); + } +} + QualType Sema::CheckVectorLogicalOperands(ExprResult &LHS, ExprResult &RHS, SourceLocation Loc) { // Ensure that either both operands are of the same vector type, or @@ -11003,6 +11111,9 @@ if (Opc == BO_And) diagnoseLogicalNotOnLHSofCheck(*this, LHS, RHS, Loc, Opc); + if (Opc == BO_Xor) + diagnoseXorMisusedAsPow(*this, LHS, RHS, Loc); + ExprResult LHSResult = LHS, RHSResult = RHS; QualType compType = UsualArithmeticConversions(LHSResult, RHSResult, IsCompAssign); @@ -17586,4 +17697,4 @@ return new (Context) ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy); -} +} \ No newline at end of file Index: test/SemaCXX/warn-xor-as-pow.cpp =================================================================== --- test/SemaCXX/warn-xor-as-pow.cpp +++ test/SemaCXX/warn-xor-as-pow.cpp @@ -0,0 +1,103 @@ +// RUN: %clang_cc1 -x c -fsyntax-only -verify -Wxor-used-as-pow %s +// RUN: %clang_cc1 -x c -fsyntax-only -verify %s +// RUN: %clang_cc1 -x c -fsyntax-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +// RUN: %clang_cc1 -x c++ -DKW_XOR -fsyntax-only -verify -Wxor-used-as-pow %s +// RUN: %clang_cc1 -x c++ -DKW_XOR -fsyntax-only -verify %s +// RUN: %clang_cc1 -x c++ -DKW_XOR -fsyntax-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +#define FOOBAR(x, y) (x * y) +#define XOR(x, y) (x ^ y) +#define TWO 2 +#define TEN 10 +#define TWO_ULL 2ULL +#define EPSILON 10 ^ -300 + +#define flexor 7 + +#if defined(KW_XOR) +constexpr long long operator"" _xor(unsigned long long v) { return v; } + +constexpr long long operator"" _0b(unsigned long long v) { return v; } +constexpr long long operator"" _0X(unsigned long long v) { return v; } +#endif + +void test(unsigned a, unsigned b) { + unsigned res; + res = a ^ 5; + res = 2 ^ b; + res = a ^ b; + res = 2 ^ -1; + res = 2 ^ 0; // expected-warning {{result of '2 ^ 0' is 2; did you mean '1 << 0' (1)?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:14}:"1" + // expected-note@-2 {{replace expression with '0x2 ^ 0' to silence this warning}} + res = 2 ^ 1; // expected-warning {{result of '2 ^ 1' is 3; did you mean '1 << 1' (2)?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:14}:"1 << 1" + // expected-note@-2 {{replace expression with '0x2 ^ 1' to silence this warning}} + res = 2 ^ 2; // expected-warning {{result of '2 ^ 2' is 0; did you mean '1 << 2' (4)?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:14}:"1 << 2" + // expected-note@-2 {{replace expression with '0x2 ^ 2' to silence this warning}} + res = 2 ^ 8; // expected-warning {{result of '2 ^ 8' is 10; did you mean '1 << 8' (256)?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:14}:"1 << 8" + // expected-note@-2 {{replace expression with '0x2 ^ 8' to silence this warning}} + res = TWO ^ 8; // expected-warning {{result of 'TWO ^ 8' is 10; did you mean '1 << 8' (256)?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:16}:"1 << 8" + // expected-note@-2 {{replace expression with '0x2 ^ 8' to silence this warning}} + res = 2 ^ 16; // expected-warning {{result of '2 ^ 16' is 18; did you mean '1 << 16' (65536)?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:15}:"1 << 16" + // expected-note@-2 {{replace expression with '0x2 ^ 16' to silence this warning}} + res = 2 ^ TEN; // expected-warning {{result of '2 ^ TEN' is 8; did you mean '1 << TEN' (1024)?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:16}:"1 << TEN" + // expected-note@-2 {{replace expression with '0x2 ^ TEN' to silence this warning}} + res = 0x2 ^ 16; +#if defined(KW_XOR) + res = 2 xor 16; +#endif + res = 2 ^ 0x4; + res = 2 ^ 04; + res = 0x2 ^ 10; + res = 0X2 ^ 10; + res = 02 ^ 10; + res = FOOBAR(2, 16); + res = 0b10 ^ 16; + res = 0B10 ^ 16; + res = 2 ^ 0b100; + res = XOR(2, 16); + unsigned char two = 2; + res = two ^ 16; + res = TWO_ULL ^ 16; + res = 2 ^ 32; // expected-warning {{result of '2 ^ 32' is 34; did you mean '1LL << 32'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:15}:"1LL << 32" + // expected-note@-2 {{replace expression with '0x2 ^ 32' to silence this warning}} + res = 2 ^ 64; + + res = EPSILON; + res = 10 ^ 0; // expected-warning {{result of '10 ^ 0' is 10; did you mean '1e0'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:15}:"1e0" + // expected-note@-2 {{replace expression with '0xA ^ 0' to silence this warning}} + res = 10 ^ 1; // expected-warning {{result of '10 ^ 1' is 11; did you mean '1e1'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:15}:"1e1" + // expected-note@-2 {{replace expression with '0xA ^ 1' to silence this warning}} + res = 10 ^ 2; // expected-warning {{result of '10 ^ 2' is 8; did you mean '1e2'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:15}:"1e2" + // expected-note@-2 {{replace expression with '0xA ^ 2' to silence this warning}} + res = 10 ^ 4; // expected-warning {{result of '10 ^ 4' is 14; did you mean '1e4'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:15}:"1e4" + // expected-note@-2 {{replace expression with '0xA ^ 4' to silence this warning}} + res = 10 ^ 10; // expected-warning {{result of '10 ^ 10' is 0; did you mean '1e10'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:16}:"1e10" + // expected-note@-2 {{replace expression with '0xA ^ 10' to silence this warning}} + res = TEN ^ 10; // expected-warning {{result of 'TEN ^ 10' is 0; did you mean '1e10'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:17}:"1e10" + // expected-note@-2 {{replace expression with '0xA ^ 10' to silence this warning}} + res = 10 ^ 100; // expected-warning {{result of '10 ^ 100' is 110; did you mean '1e100'?}} + // CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:9-[[@LINE-1]]:17}:"1e100" + // expected-note@-2 {{replace expression with '0xA ^ 100' to silence this warning}} + res = 0xA ^ 10; +#if defined(KW_XOR) + res = 10 xor 10; + res = 10 ^ 5_xor; + res = 10 ^ 5_0b; + res = 10_0X ^ 5; +#endif +}