//===--- IsolateDeclarationCheck.cpp - clang-tidy -------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "IsolateDeclarationCheck.h" #include "../utils/LexerUtils.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; using namespace clang::tidy::utils::lexer; namespace clang { namespace tidy { namespace readability { namespace { AST_MATCHER(DeclStmt, isSingleDecl) { return Node.isSingleDecl(); } AST_MATCHER(DeclStmt, onlyDeclaresVariables) { return llvm::all_of(Node.decls(), [](Decl *D) { return isa(D); }); } } // namespace void IsolateDeclarationCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(declStmt(onlyDeclaresVariables(), unless(isSingleDecl()), hasParent(compoundStmt())) .bind("decl_stmt"), this); } static SourceLocation findStartOfIndirection(SourceLocation Start, int Indirections, const SourceManager &SM, const LangOptions &LangOpts) { assert(Indirections >= 0 && "Indirections must be non-negative"); if (Indirections == 0) return Start; // Note that the post-fix decrement is necessary to perform the correct // number of transformations. while (Indirections-- != 0) { Start = findPreviousAnyTokenKind(Start, SM, LangOpts, tok::star, tok::amp); if (Start.isInvalid() || Start.isMacroID()) return SourceLocation(); } return Start; } static bool isMacroID(SourceRange R) { return R.getBegin().isMacroID() || R.getEnd().isMacroID(); } /// This function counts the number of written indirections for the given /// Type \p T. It does \b NOT resolve typedefs as it's a helper for lexing /// the source code. /// \see declRanges static int countIndirections(const Type *T, int Indirections = 0) { if (T->isFunctionPointerType()) { const auto *Pointee = T->getPointeeType()->castAs(); return countIndirections( Pointee->getReturnType().IgnoreParens().getTypePtr(), ++Indirections); } // Note: Do not increment the 'Indirections' because it is not yet clear // if there is an indirection added in the source code of the array // declaration. if (const auto *AT = dyn_cast(T)) return countIndirections(AT->getElementType().IgnoreParens().getTypePtr(), Indirections); if (isa(T) || isa(T)) return countIndirections(T->getPointeeType().IgnoreParens().getTypePtr(), ++Indirections); return Indirections; } static bool typeIsMemberPointer(const Type *T) { if (isa(T)) return typeIsMemberPointer(T->getArrayElementTypeNoTypeQual()); if ((isa(T) || isa(T)) && isa(T->getPointeeType())) return typeIsMemberPointer(T->getPointeeType().getTypePtr()); return isa(T); } /// This function tries to extract the SourceRanges that make up all /// declarations in this \c DeclStmt. /// /// The resulting vector has the structure {UnderlyingType, Decl1, Decl2, ...}. /// Each \c SourceRange is of the form [Begin, End). /// If any of the create ranges is invalid or in a macro the result will be /// \c None. /// If the \c DeclStmt contains only one declaration, the result is \c None. /// If the \c DeclStmt contains declarations other than \c VarDecl the result /// is \c None. /// /// \code /// int * ptr1 = nullptr, value = 42; /// // [ ][ ] [ ] - The ranges here are inclusive /// \endcode /// \todo Generalize this function to take other declarations than \c VarDecl. static Optional> declRanges(const DeclStmt *DS, const SourceManager &SM, const LangOptions &LangOpts) { std::size_t DeclCount = std::distance(DS->decl_begin(), DS->decl_end()); if (DeclCount < 2) return None; if (rangeContainsExpansionsOrDirectives(DS->getSourceRange(), SM, LangOpts)) return None; // The initial type of the declaration and each declaration has it's own // slice. This is necessary, because pointers and references bind only // to the local variable and not to all variables in the declaration. // Example: 'int *pointer, value = 42;' std::vector Slices; Slices.reserve(DeclCount + 1); // Calculate the first slice, for now only variables are handled but in the // future this should be relaxed and support various kinds of declarations. const auto *FirstDecl = dyn_cast(*DS->decl_begin()); if (FirstDecl == nullptr) return None; // FIXME: Member pointers are not transformed correctly right now, that's // why they are treated as problematic here. if (typeIsMemberPointer(FirstDecl->getType().IgnoreParens().getTypePtr())) return None; // Consider the following case: 'int * pointer, value = 42;' // Created slices (inclusive) [ ][ ] [ ] // Because 'getBeginLoc' points to the start of the variable *name*, the // location of the pointer must be determined separately. SourceLocation Start = findStartOfIndirection( FirstDecl->getLocation(), countIndirections(FirstDecl->getType().IgnoreParens().getTypePtr()), SM, LangOpts); // Fix function-pointer declarations that have a '(' in front of the // pointer. // Example: 'void (*f2)(int), (*g2)(int, float) = gg;' // Slices: [ ][ ] [ ] if (FirstDecl->getType()->isFunctionPointerType()) Start = findPreviousTokenKind(Start, SM, LangOpts, tok::l_paren); // It is possible that a declarator is wrapped with parens. // Example: 'float (((*f_ptr2)))[42], *f_ptr3, ((f_value2)) = 42.f;' // The slice for the type-part must not contain these parens. Consequently // 'Start' is moved to the most left paren if there are parens. while (true) { if (Start.isInvalid() || Start.isMacroID()) break; Token T = getPreviousToken(Start, SM, LangOpts); if (T.is(tok::l_paren)) { Start = findPreviousTokenStart(Start, SM, LangOpts); continue; } break; } SourceRange DeclRange(DS->getBeginLoc(), Start); if (DeclRange.isInvalid() || isMacroID(DeclRange)) return None; // The first slice, that is prepended to every isolated declaration, is // created. Slices.emplace_back(DeclRange); // Create all following slices that each declare a variable. SourceLocation DeclBegin = Start; for (const auto &Decl : DS->decls()) { const auto *CurrentDecl = cast(Decl); // FIXME: Member pointers are not transformed correctly right now, that's // why they are treated as problematic here. if (typeIsMemberPointer(CurrentDecl->getType().IgnoreParens().getTypePtr())) return None; SourceLocation DeclEnd = CurrentDecl->hasInit() ? findNextTerminator(CurrentDecl->getInit()->getEndLoc(), SM, LangOpts) : findNextTerminator(CurrentDecl->getEndLoc(), SM, LangOpts); SourceRange VarNameRange(DeclBegin, DeclEnd); if (VarNameRange.isInvalid() || isMacroID(VarNameRange)) return None; Slices.emplace_back(VarNameRange); DeclBegin = DeclEnd.getLocWithOffset(1); } return Slices; } static Optional> collectSourceRanges(llvm::ArrayRef Ranges, const SourceManager &SM, const LangOptions &LangOpts) { std::vector Snippets; Snippets.reserve(Ranges.size()); for (const auto &Range : Ranges) { CharSourceRange CharRange = Lexer::getAsCharRange( CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()), SM, LangOpts); if (CharRange.isInvalid()) return None; bool InvalidText = false; StringRef Snippet = Lexer::getSourceText(CharRange, SM, LangOpts, &InvalidText); if (InvalidText) return None; Snippets.emplace_back(Snippet); } return Snippets; } /// Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}. static std::vector createIsolatedDecls(llvm::ArrayRef Snippets) { // The first section is the type snippet, which does not make a decl itself. assert(Snippets.size() > 2 && "Not enough snippets to create isolated decls"); std::vector Decls(Snippets.size() - 1); for (std::size_t I = 1; I < Snippets.size(); ++I) Decls[I - 1] = Twine(Snippets[0]) .concat(Snippets[0].endswith(" ") ? "" : " ") .concat(Snippets[I].ltrim()) .concat(";") .str(); return Decls; } void IsolateDeclarationCheck::check(const MatchFinder::MatchResult &Result) { const auto *WholeDecl = Result.Nodes.getNodeAs("decl_stmt"); auto Diag = diag(WholeDecl->getBeginLoc(), "multiple declarations in a single statement reduces readability"); Optional> PotentialRanges = declRanges(WholeDecl, *Result.SourceManager, getLangOpts()); if (!PotentialRanges) return; Optional> PotentialSnippets = collectSourceRanges( *PotentialRanges, *Result.SourceManager, getLangOpts()); if (!PotentialSnippets) return; std::vector NewDecls = createIsolatedDecls(*PotentialSnippets); std::string Replacement = llvm::join( NewDecls, (Twine("\n") + Lexer::getIndentationForLine(WholeDecl->getBeginLoc(), *Result.SourceManager)) .str()); Diag << FixItHint::CreateReplacement(WholeDecl->getSourceRange(), Replacement); } } // namespace readability } // namespace tidy } // namespace clang