summaryrefslogtreecommitdiffstats
path: root/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp
blob: 53537005fa10e86e57e525f65019fdba251ad5a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//===--- SuspiciousEnumUsageCheck.cpp - clang-tidy-------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "SuspiciousEnumUsageCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include <algorithm>

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace bugprone {

static const char DifferentEnumErrorMessage[] =
    "enum values are from different enum types";

static const char BitmaskErrorMessage[] =
    "enum type seems like a bitmask (contains mostly "
    "power-of-2 literals), but this literal is not a "
    "power-of-2";

static const char BitmaskVarErrorMessage[] =
    "enum type seems like a bitmask (contains mostly "
    "power-of-2 literals) but %plural{1:a literal is|:some literals are}0 not "
    "power-of-2";

static const char BitmaskNoteMessage[] = "used here as a bitmask";

/// Stores a min and a max value which describe an interval.
struct ValueRange {
  llvm::APSInt MinVal;
  llvm::APSInt MaxVal;

  ValueRange(const EnumDecl *EnumDec) {
    const auto MinMaxVal = std::minmax_element(
        EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
        [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
          return llvm::APSInt::compareValues(E1->getInitVal(),
                                             E2->getInitVal()) < 0;
        });
    MinVal = MinMaxVal.first->getInitVal();
    MaxVal = MinMaxVal.second->getInitVal();
  }
};

/// Return the number of EnumConstantDecls in an EnumDecl.
static int enumLength(const EnumDecl *EnumDec) {
  return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end());
}

static bool hasDisjointValueRange(const EnumDecl *Enum1,
                                  const EnumDecl *Enum2) {
  ValueRange Range1(Enum1), Range2(Enum2);
  return llvm::APSInt::compareValues(Range1.MaxVal, Range2.MinVal) < 0 ||
         llvm::APSInt::compareValues(Range2.MaxVal, Range1.MinVal) < 0;
}

static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) {
  llvm::APSInt Val = EnumConst->getInitVal();
  if (Val.isPowerOf2() || !Val.getBoolValue())
    return false;
  const Expr *InitExpr = EnumConst->getInitExpr();
  if (!InitExpr)
    return true;
  return isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());
}

static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) {
  auto EnumConst = std::max_element(
      EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
      [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) {
        return E1->getInitVal() < E2->getInitVal();
      });

  if (const Expr *InitExpr = EnumConst->getInitExpr()) {
    return EnumConst->getInitVal().countTrailingOnes() ==
               EnumConst->getInitVal().getActiveBits() &&
           isa<IntegerLiteral>(InitExpr->IgnoreImpCasts());
  }
  return false;
}

static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) {
  return std::count_if(
      EnumDec->enumerator_begin(), EnumDec->enumerator_end(),
      [](const EnumConstantDecl *E) { return isNonPowerOf2NorNullLiteral(E); });
}

/// Check if there is one or two enumerators that are not a power of 2 and are
/// initialized by a literal in the enum type, and that the enumeration contains
/// enough elements to reasonably act as a bitmask. Exclude the case where the
/// last enumerator is the sum of the lesser values (and initialized by a
/// literal) or when it could contain consecutive values.
static bool isPossiblyBitMask(const EnumDecl *EnumDec) {
  ValueRange VR(EnumDec);
  int EnumLen = enumLength(EnumDec);
  int NonPowOfTwoCounter = countNonPowOfTwoLiteralNum(EnumDec);
  return NonPowOfTwoCounter >= 1 && NonPowOfTwoCounter <= 2 &&
         NonPowOfTwoCounter < EnumLen / 2 &&
         (VR.MaxVal - VR.MinVal != EnumLen - 1) &&
         !(NonPowOfTwoCounter == 1 && isMaxValAllBitSetLiteral(EnumDec));
}

SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name,
                                                   ClangTidyContext *Context)
    : ClangTidyCheck(Name, Context),
      StrictMode(Options.getLocalOrGlobal("StrictMode", 0)) {}

void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  Options.store(Opts, "StrictMode", StrictMode);
}

void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) {
  const auto enumExpr = [](StringRef RefName, StringRef DeclName) {
    return expr(ignoringImpCasts(expr().bind(RefName)),
                ignoringImpCasts(hasType(enumDecl().bind(DeclName))));
  };

  Finder->addMatcher(
      binaryOperator(hasOperatorName("|"), hasLHS(enumExpr("", "enumDecl")),
                     hasRHS(expr(enumExpr("", "otherEnumDecl"),
                                 ignoringImpCasts(hasType(enumDecl(
                                     unless(equalsBoundNode("enumDecl"))))))))
          .bind("diffEnumOp"),
      this);

  Finder->addMatcher(
      binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|")),
                     hasLHS(enumExpr("lhsExpr", "enumDecl")),
                     hasRHS(expr(enumExpr("rhsExpr", ""),
                                 ignoringImpCasts(hasType(
                                     enumDecl(equalsBoundNode("enumDecl"))))))),
      this);

  Finder->addMatcher(
      binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|")),
                     hasEitherOperand(
                         expr(hasType(isInteger()), unless(enumExpr("", "")))),
                     hasEitherOperand(enumExpr("enumExpr", "enumDecl"))),
      this);

  Finder->addMatcher(
      binaryOperator(anyOf(hasOperatorName("|="), hasOperatorName("+=")),
                     hasRHS(enumExpr("enumExpr", "enumDecl"))),
      this);
}

void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage(
    const Expr *NodeExpr, const EnumDecl *EnumDec) {
  const auto *EnumExpr = dyn_cast<DeclRefExpr>(NodeExpr);
  const auto *EnumConst =
      EnumExpr ? dyn_cast<EnumConstantDecl>(EnumExpr->getDecl()) : nullptr;

  // Report the parameter if neccessary.
  if (!EnumConst) {
    diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage)
        << countNonPowOfTwoLiteralNum(EnumDec);
    diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
  } else if (isNonPowerOf2NorNullLiteral(EnumConst)) {
    diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage);
    diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note);
  }
}

void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) {
  // Case 1: The two enum values come from different types.
  if (const auto *DiffEnumOp =
          Result.Nodes.getNodeAs<BinaryOperator>("diffEnumOp")) {
    const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
    const auto *OtherEnumDec =
        Result.Nodes.getNodeAs<EnumDecl>("otherEnumDecl");
    // Skip when one of the parameters is an empty enum. The
    // hasDisjointValueRange function could not decide the values properly in
    // case of an empty enum.
    if (EnumDec->enumerator_begin() == EnumDec->enumerator_end() ||
        OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end())
      return;

    if (!hasDisjointValueRange(EnumDec, OtherEnumDec))
      diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage);
    return;
  }

  // Case 2 and 3 only checked in strict mode. The checker tries to detect
  // suspicious bitmasks which contains values initialized by non power-of-2
  // literals.
  if (!StrictMode)
    return;
  const auto *EnumDec = Result.Nodes.getNodeAs<EnumDecl>("enumDecl");
  if (!isPossiblyBitMask(EnumDec))
    return;

  // Case 2:
  //   a. Investigating the right hand side of `+=` or `|=` operator.
  //   b. When the operator is `|` or `+` but only one of them is an EnumExpr
  if (const auto *EnumExpr = Result.Nodes.getNodeAs<Expr>("enumExpr")) {
    checkSuspiciousBitmaskUsage(EnumExpr, EnumDec);
    return;
  }

  // Case 3:
  // '|' or '+' operator where both argument comes from the same enum type
  const auto *LhsExpr = Result.Nodes.getNodeAs<Expr>("lhsExpr");
  checkSuspiciousBitmaskUsage(LhsExpr, EnumDec);

  const auto *RhsExpr = Result.Nodes.getNodeAs<Expr>("rhsExpr");
  checkSuspiciousBitmaskUsage(RhsExpr, EnumDec);
}

} // namespace bugprone
} // namespace tidy
} // namespace clang