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