forked from OSchip/llvm-project
278 lines
9.9 KiB
C++
278 lines
9.9 KiB
C++
//===--- 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<VarDecl>(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<FunctionType>();
|
|
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<ArrayType>(T))
|
|
return countIndirections(AT->getElementType().IgnoreParens().getTypePtr(),
|
|
Indirections);
|
|
|
|
if (isa<PointerType>(T) || isa<ReferenceType>(T))
|
|
return countIndirections(T->getPointeeType().IgnoreParens().getTypePtr(),
|
|
++Indirections);
|
|
|
|
return Indirections;
|
|
}
|
|
|
|
static bool typeIsMemberPointer(const Type *T) {
|
|
if (isa<ArrayType>(T))
|
|
return typeIsMemberPointer(T->getArrayElementTypeNoTypeQual());
|
|
|
|
if ((isa<PointerType>(T) || isa<ReferenceType>(T)) &&
|
|
isa<PointerType>(T->getPointeeType()))
|
|
return typeIsMemberPointer(T->getPointeeType().getTypePtr());
|
|
|
|
return isa<MemberPointerType>(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<std::vector<SourceRange>>
|
|
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<SourceRange> 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<VarDecl>(*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<VarDecl>(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<std::vector<StringRef>>
|
|
collectSourceRanges(llvm::ArrayRef<SourceRange> Ranges, const SourceManager &SM,
|
|
const LangOptions &LangOpts) {
|
|
std::vector<StringRef> 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<std::string>
|
|
createIsolatedDecls(llvm::ArrayRef<StringRef> 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<std::string> 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<DeclStmt>("decl_stmt");
|
|
|
|
auto Diag =
|
|
diag(WholeDecl->getBeginLoc(),
|
|
"multiple declarations in a single statement reduces readability");
|
|
|
|
Optional<std::vector<SourceRange>> PotentialRanges =
|
|
declRanges(WholeDecl, *Result.SourceManager, getLangOpts());
|
|
if (!PotentialRanges)
|
|
return;
|
|
|
|
Optional<std::vector<StringRef>> PotentialSnippets = collectSourceRanges(
|
|
*PotentialRanges, *Result.SourceManager, getLangOpts());
|
|
|
|
if (!PotentialSnippets)
|
|
return;
|
|
|
|
std::vector<std::string> 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
|