1 //===--- IsolateDeclarationCheck.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 "IsolateDeclarationCheck.h" 10 #include "../utils/LexerUtils.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include <optional> 13 14 using namespace clang::ast_matchers; 15 using namespace clang::tidy::utils::lexer; 16 17 namespace clang::tidy::readability { 18 19 namespace { 20 AST_MATCHER(DeclStmt, isSingleDecl) { return Node.isSingleDecl(); } 21 AST_MATCHER(DeclStmt, onlyDeclaresVariables) { 22 return llvm::all_of(Node.decls(), [](Decl *D) { return isa<VarDecl>(D); }); 23 } 24 } // namespace 25 26 void IsolateDeclarationCheck::registerMatchers(MatchFinder *Finder) { 27 Finder->addMatcher(declStmt(onlyDeclaresVariables(), unless(isSingleDecl()), 28 hasParent(compoundStmt())) 29 .bind("decl_stmt"), 30 this); 31 } 32 33 static SourceLocation findStartOfIndirection(SourceLocation Start, 34 int Indirections, 35 const SourceManager &SM, 36 const LangOptions &LangOpts) { 37 assert(Indirections >= 0 && "Indirections must be non-negative"); 38 if (Indirections == 0) 39 return Start; 40 41 // Note that the post-fix decrement is necessary to perform the correct 42 // number of transformations. 43 while (Indirections-- != 0) { 44 Start = findPreviousAnyTokenKind(Start, SM, LangOpts, tok::star, tok::amp); 45 if (Start.isInvalid() || Start.isMacroID()) 46 return {}; 47 } 48 return Start; 49 } 50 51 static bool isMacroID(SourceRange R) { 52 return R.getBegin().isMacroID() || R.getEnd().isMacroID(); 53 } 54 55 /// This function counts the number of written indirections for the given 56 /// Type \p T. It does \b NOT resolve typedefs as it's a helper for lexing 57 /// the source code. 58 /// \see declRanges 59 static int countIndirections(const Type *T, int Indirections = 0) { 60 if (T->isFunctionPointerType()) { 61 const auto *Pointee = T->getPointeeType()->castAs<FunctionType>(); 62 return countIndirections( 63 Pointee->getReturnType().IgnoreParens().getTypePtr(), ++Indirections); 64 } 65 66 // Note: Do not increment the 'Indirections' because it is not yet clear 67 // if there is an indirection added in the source code of the array 68 // declaration. 69 if (const auto *AT = dyn_cast<ArrayType>(T)) 70 return countIndirections(AT->getElementType().IgnoreParens().getTypePtr(), 71 Indirections); 72 73 if (isa<PointerType>(T) || isa<ReferenceType>(T)) 74 return countIndirections(T->getPointeeType().IgnoreParens().getTypePtr(), 75 ++Indirections); 76 77 return Indirections; 78 } 79 80 static bool typeIsMemberPointer(const Type *T) { 81 if (isa<ArrayType>(T)) 82 return typeIsMemberPointer(T->getArrayElementTypeNoTypeQual()); 83 84 if ((isa<PointerType>(T) || isa<ReferenceType>(T)) && 85 isa<PointerType>(T->getPointeeType())) 86 return typeIsMemberPointer(T->getPointeeType().getTypePtr()); 87 88 return isa<MemberPointerType>(T); 89 } 90 91 /// This function tries to extract the SourceRanges that make up all 92 /// declarations in this \c DeclStmt. 93 /// 94 /// The resulting vector has the structure {UnderlyingType, Decl1, Decl2, ...}. 95 /// Each \c SourceRange is of the form [Begin, End). 96 /// If any of the create ranges is invalid or in a macro the result will be 97 /// \c None. 98 /// If the \c DeclStmt contains only one declaration, the result is \c None. 99 /// If the \c DeclStmt contains declarations other than \c VarDecl the result 100 /// is \c None. 101 /// 102 /// \code 103 /// int * ptr1 = nullptr, value = 42; 104 /// // [ ][ ] [ ] - The ranges here are inclusive 105 /// \endcode 106 /// \todo Generalize this function to take other declarations than \c VarDecl. 107 static std::optional<std::vector<SourceRange>> 108 declRanges(const DeclStmt *DS, const SourceManager &SM, 109 const LangOptions &LangOpts) { 110 std::size_t DeclCount = std::distance(DS->decl_begin(), DS->decl_end()); 111 if (DeclCount < 2) 112 return std::nullopt; 113 114 if (rangeContainsExpansionsOrDirectives(DS->getSourceRange(), SM, LangOpts)) 115 return std::nullopt; 116 117 // The initial type of the declaration and each declaration has it's own 118 // slice. This is necessary, because pointers and references bind only 119 // to the local variable and not to all variables in the declaration. 120 // Example: 'int *pointer, value = 42;' 121 std::vector<SourceRange> Slices; 122 Slices.reserve(DeclCount + 1); 123 124 // Calculate the first slice, for now only variables are handled but in the 125 // future this should be relaxed and support various kinds of declarations. 126 const auto *FirstDecl = dyn_cast<VarDecl>(*DS->decl_begin()); 127 128 if (FirstDecl == nullptr) 129 return std::nullopt; 130 131 // FIXME: Member pointers are not transformed correctly right now, that's 132 // why they are treated as problematic here. 133 if (typeIsMemberPointer(FirstDecl->getType().IgnoreParens().getTypePtr())) 134 return std::nullopt; 135 136 // Consider the following case: 'int * pointer, value = 42;' 137 // Created slices (inclusive) [ ][ ] [ ] 138 // Because 'getBeginLoc' points to the start of the variable *name*, the 139 // location of the pointer must be determined separately. 140 SourceLocation Start = findStartOfIndirection( 141 FirstDecl->getLocation(), 142 countIndirections(FirstDecl->getType().IgnoreParens().getTypePtr()), SM, 143 LangOpts); 144 145 // Fix function-pointer declarations that have a '(' in front of the 146 // pointer. 147 // Example: 'void (*f2)(int), (*g2)(int, float) = gg;' 148 // Slices: [ ][ ] [ ] 149 if (FirstDecl->getType()->isFunctionPointerType()) 150 Start = findPreviousTokenKind(Start, SM, LangOpts, tok::l_paren); 151 152 // It is possible that a declarator is wrapped with parens. 153 // Example: 'float (((*f_ptr2)))[42], *f_ptr3, ((f_value2)) = 42.f;' 154 // The slice for the type-part must not contain these parens. Consequently 155 // 'Start' is moved to the most left paren if there are parens. 156 while (true) { 157 if (Start.isInvalid() || Start.isMacroID()) 158 break; 159 160 Token T = getPreviousToken(Start, SM, LangOpts); 161 if (T.is(tok::l_paren)) { 162 Start = findPreviousTokenStart(Start, SM, LangOpts); 163 continue; 164 } 165 break; 166 } 167 168 SourceRange DeclRange(DS->getBeginLoc(), Start); 169 if (DeclRange.isInvalid() || isMacroID(DeclRange)) 170 return std::nullopt; 171 172 // The first slice, that is prepended to every isolated declaration, is 173 // created. 174 Slices.emplace_back(DeclRange); 175 176 // Create all following slices that each declare a variable. 177 SourceLocation DeclBegin = Start; 178 for (const auto &Decl : DS->decls()) { 179 const auto *CurrentDecl = cast<VarDecl>(Decl); 180 181 // FIXME: Member pointers are not transformed correctly right now, that's 182 // why they are treated as problematic here. 183 if (typeIsMemberPointer(CurrentDecl->getType().IgnoreParens().getTypePtr())) 184 return std::nullopt; 185 186 SourceLocation DeclEnd = 187 CurrentDecl->hasInit() 188 ? findNextTerminator(CurrentDecl->getInit()->getEndLoc(), SM, 189 LangOpts) 190 : findNextTerminator(CurrentDecl->getEndLoc(), SM, LangOpts); 191 192 SourceRange VarNameRange(DeclBegin, DeclEnd); 193 if (VarNameRange.isInvalid() || isMacroID(VarNameRange)) 194 return std::nullopt; 195 196 Slices.emplace_back(VarNameRange); 197 DeclBegin = DeclEnd.getLocWithOffset(1); 198 } 199 return Slices; 200 } 201 202 static std::optional<std::vector<StringRef>> 203 collectSourceRanges(llvm::ArrayRef<SourceRange> Ranges, const SourceManager &SM, 204 const LangOptions &LangOpts) { 205 std::vector<StringRef> Snippets; 206 Snippets.reserve(Ranges.size()); 207 208 for (const auto &Range : Ranges) { 209 CharSourceRange CharRange = Lexer::getAsCharRange( 210 CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()), SM, 211 LangOpts); 212 213 if (CharRange.isInvalid()) 214 return std::nullopt; 215 216 bool InvalidText = false; 217 StringRef Snippet = 218 Lexer::getSourceText(CharRange, SM, LangOpts, &InvalidText); 219 220 if (InvalidText) 221 return std::nullopt; 222 223 Snippets.emplace_back(Snippet); 224 } 225 226 return Snippets; 227 } 228 229 /// Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}. 230 static std::vector<std::string> 231 createIsolatedDecls(llvm::ArrayRef<StringRef> Snippets) { 232 // The first section is the type snippet, which does not make a decl itself. 233 assert(Snippets.size() > 2 && "Not enough snippets to create isolated decls"); 234 std::vector<std::string> Decls(Snippets.size() - 1); 235 236 for (std::size_t I = 1; I < Snippets.size(); ++I) 237 Decls[I - 1] = Twine(Snippets[0]) 238 .concat(Snippets[0].endswith(" ") ? "" : " ") 239 .concat(Snippets[I].ltrim()) 240 .concat(";") 241 .str(); 242 243 return Decls; 244 } 245 246 void IsolateDeclarationCheck::check(const MatchFinder::MatchResult &Result) { 247 const auto *WholeDecl = Result.Nodes.getNodeAs<DeclStmt>("decl_stmt"); 248 249 auto Diag = 250 diag(WholeDecl->getBeginLoc(), 251 "multiple declarations in a single statement reduces readability"); 252 253 std::optional<std::vector<SourceRange>> PotentialRanges = 254 declRanges(WholeDecl, *Result.SourceManager, getLangOpts()); 255 if (!PotentialRanges) 256 return; 257 258 std::optional<std::vector<StringRef>> PotentialSnippets = collectSourceRanges( 259 *PotentialRanges, *Result.SourceManager, getLangOpts()); 260 261 if (!PotentialSnippets) 262 return; 263 264 std::vector<std::string> NewDecls = createIsolatedDecls(*PotentialSnippets); 265 std::string Replacement = llvm::join( 266 NewDecls, 267 (Twine("\n") + Lexer::getIndentationForLine(WholeDecl->getBeginLoc(), 268 *Result.SourceManager)) 269 .str()); 270 271 Diag << FixItHint::CreateReplacement(WholeDecl->getSourceRange(), 272 Replacement); 273 } 274 } // namespace clang::tidy::readability 275