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