brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.0 KiB · fa5a0b7 Raw
275 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "IsolateDeclarationCheck.h"10#include "../utils/LexerUtils.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include <optional>13 14using namespace clang::ast_matchers;15using namespace clang::tidy::utils::lexer;16 17namespace clang::tidy::readability {18 19namespace {20AST_MATCHER(DeclStmt, isSingleDecl) { return Node.isSingleDecl(); }21AST_MATCHER(DeclStmt, onlyDeclaresVariables) {22  return llvm::all_of(Node.decls(), [](Decl *D) { return isa<VarDecl>(D); });23}24} // namespace25 26void IsolateDeclarationCheck::registerMatchers(MatchFinder *Finder) {27  Finder->addMatcher(declStmt(onlyDeclaresVariables(), unless(isSingleDecl()),28                              hasParent(compoundStmt()))29                         .bind("decl_stmt"),30                     this);31}32 33static 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 correct42  // 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 51static 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 given56/// Type \p T. It does \b NOT resolve typedefs as it's a helper for lexing57/// the source code.58/// \see declRanges59static 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 clear67  // if there is an indirection added in the source code of the array68  // 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 80static 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 all92/// 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 be97/// \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 result100/// is \c None.101///102/// \code103///    int * ptr1 = nullptr, value = 42;104/// // [  ][              ] [         ] - The ranges here are inclusive105/// \endcode106/// \todo Generalize this function to take other declarations than \c VarDecl.107static std::optional<std::vector<SourceRange>>108declRanges(const DeclStmt *DS, const SourceManager &SM,109           const LangOptions &LangOpts) {110  const 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 own118  // slice. This is necessary, because pointers and references bind only119  // 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 the125  // 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's132  // 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*, the139  // 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 the146  // 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. Consequently155  // '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    const 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  const 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, is173  // 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's182    // why they are treated as problematic here.183    if (typeIsMemberPointer(CurrentDecl->getType().IgnoreParens().getTypePtr()))184      return std::nullopt;185 186    const SourceLocation DeclEnd =187        CurrentDecl->hasInit()188            ? findNextTerminator(CurrentDecl->getInit()->getEndLoc(), SM,189                                 LangOpts)190            : findNextTerminator(CurrentDecl->getEndLoc(), SM, LangOpts);191 192    const 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 202static std::optional<std::vector<StringRef>>203collectSourceRanges(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    const 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    const 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, ...}.230static std::vector<std::string>231createIsolatedDecls(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].ends_with(" ") ? "" : " ")239                       .concat(Snippets[I].ltrim())240                       .concat(";")241                       .str();242 243  return Decls;244}245 246void 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  const 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::readability275