brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.9 KiB · ef7f0b5 Raw
209 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "SuspiciousEnumUsageCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include <algorithm>13 14using namespace clang::ast_matchers;15 16namespace clang::tidy::bugprone {17 18static const char DifferentEnumErrorMessage[] =19    "enum values are from different enum types";20 21static const char BitmaskErrorMessage[] =22    "enum type seems like a bitmask (contains mostly "23    "power-of-2 literals), but this literal is not a "24    "power-of-2";25 26static const char BitmaskVarErrorMessage[] =27    "enum type seems like a bitmask (contains mostly "28    "power-of-2 literals) but %plural{1:a literal is|:some literals are}0 not "29    "power-of-2";30 31static const char BitmaskNoteMessage[] = "used here as a bitmask";32 33/// Stores a min and a max value which describe an interval.34struct ValueRange {35  llvm::APSInt MinVal;36  llvm::APSInt MaxVal;37 38  ValueRange(const EnumDecl *EnumDec) {39    const auto MinMaxVal = std::minmax_element(40        EnumDec->enumerator_begin(), EnumDec->enumerator_end(),41        [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {42          return llvm::APSInt::compareValues(E1->getInitVal(),43                                             E2->getInitVal()) < 0;44        });45    MinVal = MinMaxVal.first->getInitVal();46    MaxVal = MinMaxVal.second->getInitVal();47  }48};49 50/// Return the number of EnumConstantDecls in an EnumDecl.51static int enumLength(const EnumDecl *EnumDec) {52  return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end());53}54 55static bool hasDisjointValueRange(const EnumDecl *Enum1,56                                  const EnumDecl *Enum2) {57  const ValueRange Range1(Enum1), Range2(Enum2);58  return llvm::APSInt::compareValues(Range1.MaxVal, Range2.MinVal) < 0 ||59         llvm::APSInt::compareValues(Range2.MaxVal, Range1.MinVal) < 0;60}61 62static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) {63  const llvm::APSInt &Val = EnumConst->getInitVal();64  if (Val.isPowerOf2() || !Val.getBoolValue())65    return false;66  const Expr *InitExpr = EnumConst->getInitExpr();67  if (!InitExpr)68    return true;69  return isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());70}71 72static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) {73  auto EnumConst = std::max_element(74      EnumDec->enumerator_begin(), EnumDec->enumerator_end(),75      [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {76        return E1->getInitVal() < E2->getInitVal();77      });78 79  if (const Expr *InitExpr = EnumConst->getInitExpr()) {80    return EnumConst->getInitVal().countr_one() ==81               EnumConst->getInitVal().getActiveBits() &&82           isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());83  }84  return false;85}86 87static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) {88  return llvm::count_if(EnumDec->enumerators(), isNonPowerOf2NorNullLiteral);89}90 91/// Check if there is one or two enumerators that are not a power of 2 and are92/// initialized by a literal in the enum type, and that the enumeration contains93/// enough elements to reasonably act as a bitmask. Exclude the case where the94/// last enumerator is the sum of the lesser values (and initialized by a95/// literal) or when it could contain consecutive values.96static bool isPossiblyBitMask(const EnumDecl *EnumDec) {97  const ValueRange VR(EnumDec);98  const int EnumLen = enumLength(EnumDec);99  const int NonPowOfTwoCounter = countNonPowOfTwoLiteralNum(EnumDec);100  return NonPowOfTwoCounter >= 1 && NonPowOfTwoCounter <= 2 &&101         NonPowOfTwoCounter < EnumLen / 2 &&102         (VR.MaxVal - VR.MinVal != EnumLen - 1) &&103         !(NonPowOfTwoCounter == 1 && isMaxValAllBitSetLiteral(EnumDec));104}105 106SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name,107                                                   ClangTidyContext *Context)108    : ClangTidyCheck(Name, Context),109      StrictMode(Options.get("StrictMode", false)) {}110 111void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {112  Options.store(Opts, "StrictMode", StrictMode);113}114 115void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) {116  const auto EnumExpr = [](StringRef RefName, StringRef DeclName) {117    return expr(hasType(enumDecl().bind(DeclName))).bind(RefName);118  };119 120  Finder->addMatcher(121      binaryOperator(122          hasOperatorName("|"), hasLHS(hasType(enumDecl().bind("enumDecl"))),123          hasRHS(hasType(enumDecl(unless(equalsBoundNode("enumDecl")))124                             .bind("otherEnumDecl"))))125          .bind("diffEnumOp"),126      this);127 128  Finder->addMatcher(129      binaryOperator(hasAnyOperatorName("+", "|"),130                     hasLHS(EnumExpr("lhsExpr", "enumDecl")),131                     hasRHS(expr(hasType(enumDecl(equalsBoundNode("enumDecl"))))132                                .bind("rhsExpr"))),133      this);134 135  Finder->addMatcher(136      binaryOperator(137          hasAnyOperatorName("+", "|"),138          hasOperands(expr(hasType(isInteger()), unless(hasType(enumDecl()))),139                      EnumExpr("enumExpr", "enumDecl"))),140      this);141 142  Finder->addMatcher(binaryOperator(hasAnyOperatorName("|=", "+="),143                                    hasRHS(EnumExpr("enumExpr", "enumDecl"))),144                     this);145}146 147void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage(148    const Expr *NodeExpr, const EnumDecl *EnumDec) {149  const auto *EnumExpr = dyn_cast<DeclRefExpr>(NodeExpr);150  const auto *EnumConst =151      EnumExpr ? dyn_cast<EnumConstantDecl>(EnumExpr->getDecl()) : nullptr;152 153  // Report the parameter if necessary.154  if (!EnumConst) {155    diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage)156        << countNonPowOfTwoLiteralNum(EnumDec);157    diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);158  } else if (isNonPowerOf2NorNullLiteral(EnumConst)) {159    diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage);160    diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);161  }162}163 164void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) {165  // Case 1: The two enum values come from different types.166  if (const auto *DiffEnumOp =167          Result.Nodes.getNodeAs<BinaryOperator>("diffEnumOp")) {168    const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");169    const auto *OtherEnumDec =170        Result.Nodes.getNodeAs<EnumDecl>("otherEnumDecl");171    // Skip when one of the parameters is an empty enum. The172    // hasDisjointValueRange function could not decide the values properly in173    // case of an empty enum.174    if (EnumDec->enumerators().empty() || OtherEnumDec->enumerators().empty())175      return;176 177    if (!hasDisjointValueRange(EnumDec, OtherEnumDec))178      diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage);179    return;180  }181 182  // Case 2 and 3 only checked in strict mode. The checker tries to detect183  // suspicious bitmasks which contains values initialized by non power-of-2184  // literals.185  if (!StrictMode)186    return;187  const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");188  if (!isPossiblyBitMask(EnumDec))189    return;190 191  // Case 2:192  //   a. Investigating the right hand side of `+=` or `|=` operator.193  //   b. When the operator is `|` or `+` but only one of them is an EnumExpr194  if (const auto *EnumExpr = Result.Nodes.getNodeAs<Expr>("enumExpr")) {195    checkSuspiciousBitmaskUsage(EnumExpr, EnumDec);196    return;197  }198 199  // Case 3:200  // '|' or '+' operator where both argument comes from the same enum type201  const auto *LhsExpr = Result.Nodes.getNodeAs<Expr>("lhsExpr");202  checkSuspiciousBitmaskUsage(LhsExpr, EnumDec);203 204  const auto *RhsExpr = Result.Nodes.getNodeAs<Expr>("rhsExpr");205  checkSuspiciousBitmaskUsage(RhsExpr, EnumDec);206}207 208} // namespace clang::tidy::bugprone209