| 1 | //===--- SignedBitwiseCheck.cpp - clang-tidy-------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "SignedBitwiseCheck.h" |
| 10 | #include "clang/AST/ASTContext.h" |
| 11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 12 | #include "clang/ASTMatchers/ASTMatchers.h" |
| 13 | |
| 14 | using namespace clang::ast_matchers; |
| 15 | using namespace clang::ast_matchers::internal; |
| 16 | |
| 17 | namespace clang::tidy::hicpp { |
| 18 | |
| 19 | SignedBitwiseCheck::SignedBitwiseCheck(StringRef Name, |
| 20 | ClangTidyContext *Context) |
| 21 | : ClangTidyCheck(Name, Context), |
| 22 | IgnorePositiveIntegerLiterals( |
| 23 | Options.get(LocalName: "IgnorePositiveIntegerLiterals" , Default: false)) {} |
| 24 | |
| 25 | void SignedBitwiseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| 26 | Options.store(Options&: Opts, LocalName: "IgnorePositiveIntegerLiterals" , |
| 27 | Value: IgnorePositiveIntegerLiterals); |
| 28 | } |
| 29 | |
| 30 | void SignedBitwiseCheck::registerMatchers(MatchFinder *Finder) { |
| 31 | const auto SignedIntegerOperand = |
| 32 | (IgnorePositiveIntegerLiterals |
| 33 | ? expr(ignoringImpCasts( |
| 34 | InnerMatcher: allOf(hasType(InnerMatcher: isSignedInteger()), unless(integerLiteral())))) |
| 35 | : expr(ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: isSignedInteger())))) |
| 36 | .bind(ID: "signed-operand" ); |
| 37 | |
| 38 | // The standard [bitmask.types] allows some integral types to be implemented |
| 39 | // as signed types. Exclude these types from diagnosing for bitwise or(|) and |
| 40 | // bitwise and(&). Shifting and complementing such values is still not |
| 41 | // allowed. |
| 42 | const auto BitmaskType = namedDecl( |
| 43 | hasAnyName("::std::locale::category" , "::std::ctype_base::mask" , |
| 44 | "::std::ios_base::fmtflags" , "::std::ios_base::iostate" , |
| 45 | "::std::ios_base::openmode" )); |
| 46 | const auto IsStdBitmask = ignoringImpCasts(InnerMatcher: declRefExpr(hasType(InnerMatcher: BitmaskType))); |
| 47 | |
| 48 | // Match binary bitwise operations on signed integer arguments. |
| 49 | Finder->addMatcher( |
| 50 | NodeMatch: binaryOperator(hasAnyOperatorName("^" , "|" , "&" , "^=" , "|=" , "&=" ), |
| 51 | |
| 52 | unless(allOf(hasLHS(InnerMatcher: IsStdBitmask), hasRHS(InnerMatcher: IsStdBitmask))), |
| 53 | |
| 54 | hasEitherOperand(InnerMatcher: SignedIntegerOperand), |
| 55 | hasLHS(InnerMatcher: hasType(InnerMatcher: isInteger())), hasRHS(InnerMatcher: hasType(InnerMatcher: isInteger()))) |
| 56 | .bind(ID: "binary-no-sign-interference" ), |
| 57 | Action: this); |
| 58 | |
| 59 | // Shifting and complement is not allowed for any signed integer type because |
| 60 | // the sign bit may corrupt the result. |
| 61 | Finder->addMatcher( |
| 62 | NodeMatch: binaryOperator(hasAnyOperatorName("<<" , ">>" , "<<=" , ">>=" ), |
| 63 | hasEitherOperand(InnerMatcher: SignedIntegerOperand), |
| 64 | hasLHS(InnerMatcher: hasType(InnerMatcher: isInteger())), hasRHS(InnerMatcher: hasType(InnerMatcher: isInteger()))) |
| 65 | .bind(ID: "binary-sign-interference" ), |
| 66 | Action: this); |
| 67 | |
| 68 | // Match unary operations on signed integer types. |
| 69 | Finder->addMatcher( |
| 70 | NodeMatch: unaryOperator(hasOperatorName(Name: "~" ), hasUnaryOperand(InnerMatcher: SignedIntegerOperand)) |
| 71 | .bind(ID: "unary-signed" ), |
| 72 | Action: this); |
| 73 | } |
| 74 | |
| 75 | void SignedBitwiseCheck::check(const MatchFinder::MatchResult &Result) { |
| 76 | const ast_matchers::BoundNodes &N = Result.Nodes; |
| 77 | const auto *SignedOperand = N.getNodeAs<Expr>(ID: "signed-operand" ); |
| 78 | assert(SignedOperand && |
| 79 | "No signed operand found in problematic bitwise operations" ); |
| 80 | |
| 81 | bool IsUnary = false; |
| 82 | SourceLocation OperatorLoc; |
| 83 | |
| 84 | if (const auto *UnaryOp = N.getNodeAs<UnaryOperator>(ID: "unary-signed" )) { |
| 85 | IsUnary = true; |
| 86 | OperatorLoc = UnaryOp->getOperatorLoc(); |
| 87 | } else { |
| 88 | if (const auto *BinaryOp = |
| 89 | N.getNodeAs<BinaryOperator>(ID: "binary-no-sign-interference" )) |
| 90 | OperatorLoc = BinaryOp->getOperatorLoc(); |
| 91 | else if (const auto *BinaryOp = |
| 92 | N.getNodeAs<BinaryOperator>(ID: "binary-sign-interference" )) |
| 93 | OperatorLoc = BinaryOp->getOperatorLoc(); |
| 94 | else |
| 95 | llvm_unreachable("unexpected matcher result" ); |
| 96 | } |
| 97 | diag(SignedOperand->getBeginLoc(), "use of a signed integer operand with a " |
| 98 | "%select{binary|unary}0 bitwise operator" ) |
| 99 | << IsUnary << SignedOperand->getSourceRange() << OperatorLoc; |
| 100 | } |
| 101 | |
| 102 | } // namespace clang::tidy::hicpp |
| 103 | |