xref: /llvm-project/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp (revision f5ff3a560fe247206814792a7f8efef8215f7689)
1 //===--- EnumInitialValueCheck.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 "EnumInitialValueCheck.h"
10 #include "../utils/LexerUtils.h"
11 #include "clang/AST/Decl.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Basic/Diagnostic.h"
15 #include "clang/Basic/SourceLocation.h"
16 #include "llvm/ADT/STLExtras.h"
17 #include "llvm/ADT/SmallString.h"
18 
19 using namespace clang::ast_matchers;
20 
21 namespace clang::tidy::readability {
22 
23 static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) {
24   return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
25     return ECD->getInitExpr() == nullptr;
26   });
27 }
28 
29 static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) {
30   bool IsFirst = true;
31   for (const EnumConstantDecl *ECD : Node.enumerators()) {
32     if ((IsFirst && ECD->getInitExpr() == nullptr) ||
33         (!IsFirst && ECD->getInitExpr() != nullptr))
34       return false;
35     IsFirst = false;
36   }
37   return !IsFirst;
38 }
39 
40 static bool areAllEnumeratorsInitialized(const EnumDecl &Node) {
41   return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
42     return ECD->getInitExpr() != nullptr;
43   });
44 }
45 
46 /// Check if \p Enumerator is initialized with a (potentially negated) \c
47 /// IntegerLiteral.
48 static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {
49   const Expr *const Init = Enumerator->getInitExpr();
50   if (!Init)
51     return false;
52   return Init->isIntegerConstantExpr(Enumerator->getASTContext());
53 }
54 
55 static void cleanInitialValue(DiagnosticBuilder &Diag,
56                               const EnumConstantDecl *ECD,
57                               const SourceManager &SM,
58                               const LangOptions &LangOpts) {
59   const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange();
60   if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() ||
61       InitExprRange.getEnd().isMacroID())
62     return;
63   std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments(
64       ECD->getLocation(), SM, LangOpts);
65   if (!EqualToken.has_value() ||
66       EqualToken.value().getKind() != tok::TokenKind::equal)
67     return;
68   const SourceLocation EqualLoc{EqualToken->getLocation()};
69   if (EqualLoc.isInvalid() || EqualLoc.isMacroID())
70     return;
71   Diag << FixItHint::CreateRemoval(EqualLoc)
72        << FixItHint::CreateRemoval(InitExprRange);
73   return;
74 }
75 
76 namespace {
77 
78 AST_MATCHER(EnumDecl, isMacro) {
79   SourceLocation Loc = Node.getBeginLoc();
80   return Loc.isMacroID();
81 }
82 
83 AST_MATCHER(EnumDecl, hasConsistentInitialValues) {
84   return isNoneEnumeratorsInitialized(Node) ||
85          isOnlyFirstEnumeratorInitialized(Node) ||
86          areAllEnumeratorsInitialized(Node);
87 }
88 
89 AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {
90   const EnumDecl::enumerator_range Enumerators = Node.enumerators();
91   if (Enumerators.empty())
92     return false;
93   const EnumConstantDecl *ECD = *Enumerators.begin();
94   return isOnlyFirstEnumeratorInitialized(Node) &&
95          isInitializedByLiteral(ECD) && ECD->getInitVal().isZero();
96 }
97 
98 /// Excludes bitfields because enumerators initialized with the result of a
99 /// bitwise operator on enumeration values or any other expr that is not a
100 /// potentially negative integer literal.
101 /// Enumerations where it is not directly clear if they are used with
102 /// bitmask, evident when enumerators are only initialized with (potentially
103 /// negative) integer literals, are ignored. This is also the case when all
104 /// enumerators are powers of two (e.g., 0, 1, 2).
105 AST_MATCHER(EnumDecl, hasSequentialInitialValues) {
106   const EnumDecl::enumerator_range Enumerators = Node.enumerators();
107   if (Enumerators.empty())
108     return false;
109   const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin();
110   llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
111   if (!isInitializedByLiteral(FirstEnumerator))
112     return false;
113   bool AllEnumeratorsArePowersOfTwo = true;
114   for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) {
115     const llvm::APSInt NewValue = Enumerator->getInitVal();
116     if (NewValue != ++PrevValue)
117       return false;
118     if (!isInitializedByLiteral(Enumerator))
119       return false;
120     PrevValue = NewValue;
121     AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();
122   }
123   return !AllEnumeratorsArePowersOfTwo;
124 }
125 
126 std::string getName(const EnumDecl *Decl) {
127   if (!Decl->getDeclName())
128     return "<unnamed>";
129 
130   return Decl->getQualifiedNameAsString();
131 }
132 
133 } // namespace
134 
135 EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,
136                                              ClangTidyContext *Context)
137     : ClangTidyCheck(Name, Context),
138       AllowExplicitZeroFirstInitialValue(
139           Options.get("AllowExplicitZeroFirstInitialValue", true)),
140       AllowExplicitSequentialInitialValues(
141           Options.get("AllowExplicitSequentialInitialValues", true)) {}
142 
143 void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
144   Options.store(Opts, "AllowExplicitZeroFirstInitialValue",
145                 AllowExplicitZeroFirstInitialValue);
146   Options.store(Opts, "AllowExplicitSequentialInitialValues",
147                 AllowExplicitSequentialInitialValues);
148 }
149 
150 void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
151   Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
152                               unless(hasConsistentInitialValues()))
153                          .bind("inconsistent"),
154                      this);
155   if (!AllowExplicitZeroFirstInitialValue)
156     Finder->addMatcher(
157         enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator())
158             .bind("zero_first"),
159         this);
160   if (!AllowExplicitSequentialInitialValues)
161     Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
162                                 hasSequentialInitialValues())
163                            .bind("sequential"),
164                        this);
165 }
166 
167 void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
168   if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) {
169     DiagnosticBuilder Diag =
170         diag(
171             Enum->getBeginLoc(),
172             "initial values in enum '%0' are not consistent, consider explicit "
173             "initialization of all, none or only the first enumerator")
174         << getName(Enum);
175     for (const EnumConstantDecl *ECD : Enum->enumerators())
176       if (ECD->getInitExpr() == nullptr) {
177         const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
178             ECD->getLocation(), 0, *Result.SourceManager, getLangOpts());
179         if (EndLoc.isMacroID())
180           continue;
181         llvm::SmallString<8> Str{" = "};
182         ECD->getInitVal().toString(Str);
183         Diag << FixItHint::CreateInsertion(EndLoc, Str);
184       }
185     return;
186   }
187 
188   if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) {
189     const EnumConstantDecl *ECD = *Enum->enumerator_begin();
190     const SourceLocation Loc = ECD->getLocation();
191     if (Loc.isInvalid() || Loc.isMacroID())
192       return;
193     DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first "
194                                        "enumerator in '%0' can be disregarded")
195                              << getName(Enum);
196     cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
197     return;
198   }
199   if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) {
200     DiagnosticBuilder Diag =
201         diag(Enum->getBeginLoc(),
202              "sequential initial value in '%0' can be ignored")
203         << getName(Enum);
204     for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators()))
205       cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
206     return;
207   }
208 }
209 
210 } // namespace clang::tidy::readability
211