Jonas Toth | 0ea5af7 | 2018-10-31 16:50:44 +0000 | [diff] [blame^] | 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 |