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