xref: /llvm-project/clang-tools-extra/clang-tidy/readability/EnumInitialValueCheck.cpp (revision f5ff3a560fe247206814792a7f8efef8215f7689)
13365d621SCongcong Cai //===--- EnumInitialValueCheck.cpp - clang-tidy ---------------------------===//
23365d621SCongcong Cai //
33365d621SCongcong Cai // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43365d621SCongcong Cai // See https://llvm.org/LICENSE.txt for license information.
53365d621SCongcong Cai // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63365d621SCongcong Cai //
73365d621SCongcong Cai //===----------------------------------------------------------------------===//
83365d621SCongcong Cai 
93365d621SCongcong Cai #include "EnumInitialValueCheck.h"
103365d621SCongcong Cai #include "../utils/LexerUtils.h"
113365d621SCongcong Cai #include "clang/AST/Decl.h"
123365d621SCongcong Cai #include "clang/ASTMatchers/ASTMatchFinder.h"
133365d621SCongcong Cai #include "clang/ASTMatchers/ASTMatchers.h"
143365d621SCongcong Cai #include "clang/Basic/Diagnostic.h"
153365d621SCongcong Cai #include "clang/Basic/SourceLocation.h"
163365d621SCongcong Cai #include "llvm/ADT/STLExtras.h"
173365d621SCongcong Cai #include "llvm/ADT/SmallString.h"
183365d621SCongcong Cai 
193365d621SCongcong Cai using namespace clang::ast_matchers;
203365d621SCongcong Cai 
213365d621SCongcong Cai namespace clang::tidy::readability {
223365d621SCongcong Cai 
233365d621SCongcong Cai static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) {
243365d621SCongcong Cai   return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
253365d621SCongcong Cai     return ECD->getInitExpr() == nullptr;
263365d621SCongcong Cai   });
273365d621SCongcong Cai }
283365d621SCongcong Cai 
293365d621SCongcong Cai static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) {
303365d621SCongcong Cai   bool IsFirst = true;
313365d621SCongcong Cai   for (const EnumConstantDecl *ECD : Node.enumerators()) {
323365d621SCongcong Cai     if ((IsFirst && ECD->getInitExpr() == nullptr) ||
333365d621SCongcong Cai         (!IsFirst && ECD->getInitExpr() != nullptr))
343365d621SCongcong Cai       return false;
353365d621SCongcong Cai     IsFirst = false;
363365d621SCongcong Cai   }
373365d621SCongcong Cai   return !IsFirst;
383365d621SCongcong Cai }
393365d621SCongcong Cai 
403365d621SCongcong Cai static bool areAllEnumeratorsInitialized(const EnumDecl &Node) {
413365d621SCongcong Cai   return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) {
423365d621SCongcong Cai     return ECD->getInitExpr() != nullptr;
433365d621SCongcong Cai   });
443365d621SCongcong Cai }
453365d621SCongcong Cai 
463365d621SCongcong Cai /// Check if \p Enumerator is initialized with a (potentially negated) \c
473365d621SCongcong Cai /// IntegerLiteral.
483365d621SCongcong Cai static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) {
493365d621SCongcong Cai   const Expr *const Init = Enumerator->getInitExpr();
503365d621SCongcong Cai   if (!Init)
513365d621SCongcong Cai     return false;
523365d621SCongcong Cai   return Init->isIntegerConstantExpr(Enumerator->getASTContext());
533365d621SCongcong Cai }
543365d621SCongcong Cai 
553365d621SCongcong Cai static void cleanInitialValue(DiagnosticBuilder &Diag,
563365d621SCongcong Cai                               const EnumConstantDecl *ECD,
573365d621SCongcong Cai                               const SourceManager &SM,
583365d621SCongcong Cai                               const LangOptions &LangOpts) {
593365d621SCongcong Cai   const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange();
603365d621SCongcong Cai   if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() ||
613365d621SCongcong Cai       InitExprRange.getEnd().isMacroID())
623365d621SCongcong Cai     return;
633365d621SCongcong Cai   std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments(
643365d621SCongcong Cai       ECD->getLocation(), SM, LangOpts);
653365d621SCongcong Cai   if (!EqualToken.has_value() ||
663365d621SCongcong Cai       EqualToken.value().getKind() != tok::TokenKind::equal)
673365d621SCongcong Cai     return;
683365d621SCongcong Cai   const SourceLocation EqualLoc{EqualToken->getLocation()};
693365d621SCongcong Cai   if (EqualLoc.isInvalid() || EqualLoc.isMacroID())
703365d621SCongcong Cai     return;
713365d621SCongcong Cai   Diag << FixItHint::CreateRemoval(EqualLoc)
723365d621SCongcong Cai        << FixItHint::CreateRemoval(InitExprRange);
733365d621SCongcong Cai   return;
743365d621SCongcong Cai }
753365d621SCongcong Cai 
763365d621SCongcong Cai namespace {
773365d621SCongcong Cai 
783365d621SCongcong Cai AST_MATCHER(EnumDecl, isMacro) {
793365d621SCongcong Cai   SourceLocation Loc = Node.getBeginLoc();
803365d621SCongcong Cai   return Loc.isMacroID();
813365d621SCongcong Cai }
823365d621SCongcong Cai 
833365d621SCongcong Cai AST_MATCHER(EnumDecl, hasConsistentInitialValues) {
843365d621SCongcong Cai   return isNoneEnumeratorsInitialized(Node) ||
853365d621SCongcong Cai          isOnlyFirstEnumeratorInitialized(Node) ||
863365d621SCongcong Cai          areAllEnumeratorsInitialized(Node);
873365d621SCongcong Cai }
883365d621SCongcong Cai 
893365d621SCongcong Cai AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) {
903365d621SCongcong Cai   const EnumDecl::enumerator_range Enumerators = Node.enumerators();
913365d621SCongcong Cai   if (Enumerators.empty())
923365d621SCongcong Cai     return false;
933365d621SCongcong Cai   const EnumConstantDecl *ECD = *Enumerators.begin();
943365d621SCongcong Cai   return isOnlyFirstEnumeratorInitialized(Node) &&
953365d621SCongcong Cai          isInitializedByLiteral(ECD) && ECD->getInitVal().isZero();
963365d621SCongcong Cai }
973365d621SCongcong Cai 
983365d621SCongcong Cai /// Excludes bitfields because enumerators initialized with the result of a
993365d621SCongcong Cai /// bitwise operator on enumeration values or any other expr that is not a
1003365d621SCongcong Cai /// potentially negative integer literal.
1013365d621SCongcong Cai /// Enumerations where it is not directly clear if they are used with
1023365d621SCongcong Cai /// bitmask, evident when enumerators are only initialized with (potentially
1033365d621SCongcong Cai /// negative) integer literals, are ignored. This is also the case when all
1043365d621SCongcong Cai /// enumerators are powers of two (e.g., 0, 1, 2).
1053365d621SCongcong Cai AST_MATCHER(EnumDecl, hasSequentialInitialValues) {
1063365d621SCongcong Cai   const EnumDecl::enumerator_range Enumerators = Node.enumerators();
1073365d621SCongcong Cai   if (Enumerators.empty())
1083365d621SCongcong Cai     return false;
1093365d621SCongcong Cai   const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin();
1103365d621SCongcong Cai   llvm::APSInt PrevValue = FirstEnumerator->getInitVal();
1113365d621SCongcong Cai   if (!isInitializedByLiteral(FirstEnumerator))
1123365d621SCongcong Cai     return false;
1133365d621SCongcong Cai   bool AllEnumeratorsArePowersOfTwo = true;
1143365d621SCongcong Cai   for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) {
1153365d621SCongcong Cai     const llvm::APSInt NewValue = Enumerator->getInitVal();
1163365d621SCongcong Cai     if (NewValue != ++PrevValue)
1173365d621SCongcong Cai       return false;
1183365d621SCongcong Cai     if (!isInitializedByLiteral(Enumerator))
1193365d621SCongcong Cai       return false;
1203365d621SCongcong Cai     PrevValue = NewValue;
1213365d621SCongcong Cai     AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2();
1223365d621SCongcong Cai   }
1233365d621SCongcong Cai   return !AllEnumeratorsArePowersOfTwo;
1243365d621SCongcong Cai }
1253365d621SCongcong Cai 
126*f5ff3a56SDiscookie std::string getName(const EnumDecl *Decl) {
127*f5ff3a56SDiscookie   if (!Decl->getDeclName())
128*f5ff3a56SDiscookie     return "<unnamed>";
129*f5ff3a56SDiscookie 
130*f5ff3a56SDiscookie   return Decl->getQualifiedNameAsString();
131*f5ff3a56SDiscookie }
132*f5ff3a56SDiscookie 
1333365d621SCongcong Cai } // namespace
1343365d621SCongcong Cai 
1353365d621SCongcong Cai EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name,
1363365d621SCongcong Cai                                              ClangTidyContext *Context)
1373365d621SCongcong Cai     : ClangTidyCheck(Name, Context),
1383365d621SCongcong Cai       AllowExplicitZeroFirstInitialValue(
1393365d621SCongcong Cai           Options.get("AllowExplicitZeroFirstInitialValue", true)),
1403365d621SCongcong Cai       AllowExplicitSequentialInitialValues(
1413365d621SCongcong Cai           Options.get("AllowExplicitSequentialInitialValues", true)) {}
1423365d621SCongcong Cai 
1433365d621SCongcong Cai void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
1443365d621SCongcong Cai   Options.store(Opts, "AllowExplicitZeroFirstInitialValue",
1453365d621SCongcong Cai                 AllowExplicitZeroFirstInitialValue);
1463365d621SCongcong Cai   Options.store(Opts, "AllowExplicitSequentialInitialValues",
1473365d621SCongcong Cai                 AllowExplicitSequentialInitialValues);
1483365d621SCongcong Cai }
1493365d621SCongcong Cai 
1503365d621SCongcong Cai void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
151caaac84aSJulian Schmidt   Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
152caaac84aSJulian Schmidt                               unless(hasConsistentInitialValues()))
1533365d621SCongcong Cai                          .bind("inconsistent"),
1543365d621SCongcong Cai                      this);
1553365d621SCongcong Cai   if (!AllowExplicitZeroFirstInitialValue)
1563365d621SCongcong Cai     Finder->addMatcher(
157caaac84aSJulian Schmidt         enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator())
158caaac84aSJulian Schmidt             .bind("zero_first"),
1593365d621SCongcong Cai         this);
1603365d621SCongcong Cai   if (!AllowExplicitSequentialInitialValues)
161caaac84aSJulian Schmidt     Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
162caaac84aSJulian Schmidt                                 hasSequentialInitialValues())
1633365d621SCongcong Cai                            .bind("sequential"),
1643365d621SCongcong Cai                        this);
1653365d621SCongcong Cai }
1663365d621SCongcong Cai 
1673365d621SCongcong Cai void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
1683365d621SCongcong Cai   if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) {
1693365d621SCongcong Cai     DiagnosticBuilder Diag =
170*f5ff3a56SDiscookie         diag(
171*f5ff3a56SDiscookie             Enum->getBeginLoc(),
172*f5ff3a56SDiscookie             "initial values in enum '%0' are not consistent, consider explicit "
1733365d621SCongcong Cai             "initialization of all, none or only the first enumerator")
174*f5ff3a56SDiscookie         << getName(Enum);
1753365d621SCongcong Cai     for (const EnumConstantDecl *ECD : Enum->enumerators())
1763365d621SCongcong Cai       if (ECD->getInitExpr() == nullptr) {
1773365d621SCongcong Cai         const SourceLocation EndLoc = Lexer::getLocForEndOfToken(
1783365d621SCongcong Cai             ECD->getLocation(), 0, *Result.SourceManager, getLangOpts());
1793365d621SCongcong Cai         if (EndLoc.isMacroID())
1803365d621SCongcong Cai           continue;
1813365d621SCongcong Cai         llvm::SmallString<8> Str{" = "};
1823365d621SCongcong Cai         ECD->getInitVal().toString(Str);
1833365d621SCongcong Cai         Diag << FixItHint::CreateInsertion(EndLoc, Str);
1843365d621SCongcong Cai       }
1853365d621SCongcong Cai     return;
1863365d621SCongcong Cai   }
1873365d621SCongcong Cai 
1883365d621SCongcong Cai   if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) {
1893365d621SCongcong Cai     const EnumConstantDecl *ECD = *Enum->enumerator_begin();
1903365d621SCongcong Cai     const SourceLocation Loc = ECD->getLocation();
1913365d621SCongcong Cai     if (Loc.isInvalid() || Loc.isMacroID())
1923365d621SCongcong Cai       return;
1933365d621SCongcong Cai     DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first "
194*f5ff3a56SDiscookie                                        "enumerator in '%0' can be disregarded")
195*f5ff3a56SDiscookie                              << getName(Enum);
1963365d621SCongcong Cai     cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
1973365d621SCongcong Cai     return;
1983365d621SCongcong Cai   }
1993365d621SCongcong Cai   if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) {
2003365d621SCongcong Cai     DiagnosticBuilder Diag =
2013365d621SCongcong Cai         diag(Enum->getBeginLoc(),
202*f5ff3a56SDiscookie              "sequential initial value in '%0' can be ignored")
203*f5ff3a56SDiscookie         << getName(Enum);
2043365d621SCongcong Cai     for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators()))
2053365d621SCongcong Cai       cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts());
2063365d621SCongcong Cai     return;
2073365d621SCongcong Cai   }
2083365d621SCongcong Cai }
2093365d621SCongcong Cai 
2103365d621SCongcong Cai } // namespace clang::tidy::readability
211