forked from OSchip/llvm-project
221 lines
7.8 KiB
C++
221 lines
7.8 KiB
C++
//===--- PassByValueCheck.cpp - clang-tidy---------------------------------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "PassByValueCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/RecursiveASTVisitor.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include "clang/Lex/Preprocessor.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
using namespace llvm;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
namespace modernize {
|
|
|
|
/// \brief Matches move-constructible classes.
|
|
///
|
|
/// Given
|
|
/// \code
|
|
/// // POD types are trivially move constructible.
|
|
/// struct Foo { int a; };
|
|
///
|
|
/// struct Bar {
|
|
/// Bar(Bar &&) = deleted;
|
|
/// int a;
|
|
/// };
|
|
/// \endcode
|
|
/// recordDecl(isMoveConstructible())
|
|
/// matches "Foo".
|
|
AST_MATCHER(CXXRecordDecl, isMoveConstructible) {
|
|
for (const CXXConstructorDecl *Ctor : Node.ctors()) {
|
|
if (Ctor->isMoveConstructor() && !Ctor->isDeleted())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static TypeMatcher constRefType() {
|
|
return lValueReferenceType(pointee(isConstQualified()));
|
|
}
|
|
|
|
static TypeMatcher nonConstValueType() {
|
|
return qualType(unless(anyOf(referenceType(), isConstQualified())));
|
|
}
|
|
|
|
/// \brief Whether or not \p ParamDecl is used exactly one time in \p Ctor.
|
|
///
|
|
/// Checks both in the init-list and the body of the constructor.
|
|
static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor,
|
|
const ParmVarDecl *ParamDecl) {
|
|
/// \brief \c clang::RecursiveASTVisitor that checks that the given
|
|
/// \c ParmVarDecl is used exactly one time.
|
|
///
|
|
/// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn()
|
|
class ExactlyOneUsageVisitor
|
|
: public RecursiveASTVisitor<ExactlyOneUsageVisitor> {
|
|
friend class RecursiveASTVisitor<ExactlyOneUsageVisitor>;
|
|
|
|
public:
|
|
ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl)
|
|
: ParamDecl(ParamDecl) {}
|
|
|
|
/// \brief Whether or not the parameter variable is referred only once in
|
|
/// the
|
|
/// given constructor.
|
|
bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) {
|
|
Count = 0;
|
|
TraverseDecl(const_cast<CXXConstructorDecl *>(Ctor));
|
|
return Count == 1;
|
|
}
|
|
|
|
private:
|
|
/// \brief Counts the number of references to a variable.
|
|
///
|
|
/// Stops the AST traversal if more than one usage is found.
|
|
bool VisitDeclRefExpr(DeclRefExpr *D) {
|
|
if (const ParmVarDecl *To = dyn_cast<ParmVarDecl>(D->getDecl())) {
|
|
if (To == ParamDecl) {
|
|
++Count;
|
|
if (Count > 1) {
|
|
// No need to look further, used more than once.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const ParmVarDecl *ParamDecl;
|
|
unsigned Count;
|
|
};
|
|
|
|
return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor);
|
|
}
|
|
|
|
/// \brief Find all references to \p ParamDecl across all of the
|
|
/// redeclarations of \p Ctor.
|
|
static SmallVector<const ParmVarDecl *, 2>
|
|
collectParamDecls(const CXXConstructorDecl *Ctor,
|
|
const ParmVarDecl *ParamDecl) {
|
|
SmallVector<const ParmVarDecl *, 2> Results;
|
|
unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
|
|
|
|
for (const FunctionDecl *Redecl : Ctor->redecls())
|
|
Results.push_back(Redecl->getParamDecl(ParamIdx));
|
|
return Results;
|
|
}
|
|
|
|
PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
IncludeStyle(IncludeSorter::parseIncludeStyle(
|
|
Options.get("IncludeStyle", "llvm"))) {}
|
|
|
|
void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "IncludeStyle", IncludeSorter::toString(IncludeStyle));
|
|
}
|
|
|
|
void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
|
|
// Only register the matchers for C++; the functionality currently does not
|
|
// provide any benefit to other languages, despite being benign.
|
|
if (getLangOpts().CPlusPlus) {
|
|
Finder->addMatcher(
|
|
cxxConstructorDecl(
|
|
forEachConstructorInitializer(
|
|
cxxCtorInitializer(
|
|
// Clang builds a CXXConstructExpr only whin it knows which
|
|
// constructor will be called. In dependent contexts a
|
|
// ParenListExpr is generated instead of a CXXConstructExpr,
|
|
// filtering out templates automatically for us.
|
|
withInitializer(cxxConstructExpr(
|
|
has(declRefExpr(to(
|
|
parmVarDecl(
|
|
hasType(qualType(
|
|
// Match only const-ref or a non-const value
|
|
// parameters. Rvalues and const-values
|
|
// shouldn't be modified.
|
|
anyOf(constRefType(),
|
|
nonConstValueType()))))
|
|
.bind("Param")))),
|
|
hasDeclaration(cxxConstructorDecl(
|
|
isCopyConstructor(), unless(isDeleted()),
|
|
hasDeclContext(
|
|
cxxRecordDecl(isMoveConstructible())))))))
|
|
.bind("Initializer")))
|
|
.bind("Ctor"),
|
|
this);
|
|
}
|
|
}
|
|
|
|
void PassByValueCheck::registerPPCallbacks(CompilerInstance &Compiler) {
|
|
// Only register the preprocessor callbacks for C++; the functionality
|
|
// currently does not provide any benefit to other languages, despite being
|
|
// benign.
|
|
if (getLangOpts().CPlusPlus) {
|
|
Inserter.reset(new IncludeInserter(Compiler.getSourceManager(),
|
|
Compiler.getLangOpts(), IncludeStyle));
|
|
Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks());
|
|
}
|
|
}
|
|
|
|
void PassByValueCheck::check(const MatchFinder::MatchResult &Result) {
|
|
const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor");
|
|
const auto *ParamDecl = Result.Nodes.getNodeAs<ParmVarDecl>("Param");
|
|
const auto *Initializer =
|
|
Result.Nodes.getNodeAs<CXXCtorInitializer>("Initializer");
|
|
SourceManager &SM = *Result.SourceManager;
|
|
|
|
// If the parameter is used or anything other than the copy, do not apply
|
|
// the changes.
|
|
if (!paramReferredExactlyOnce(Ctor, ParamDecl))
|
|
return;
|
|
|
|
auto Diag = diag(ParamDecl->getLocStart(), "pass by value and use std::move");
|
|
|
|
// Iterate over all declarations of the constructor.
|
|
for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) {
|
|
auto ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc();
|
|
auto RefTL = ParamTL.getAs<ReferenceTypeLoc>();
|
|
|
|
// Do not replace if it is already a value, skip.
|
|
if (RefTL.isNull())
|
|
continue;
|
|
|
|
TypeLoc ValueTL = RefTL.getPointeeLoc();
|
|
auto TypeRange = CharSourceRange::getTokenRange(ParmDecl->getLocStart(),
|
|
ParamTL.getLocEnd());
|
|
std::string ValueStr =
|
|
Lexer::getSourceText(
|
|
CharSourceRange::getTokenRange(ValueTL.getSourceRange()), SM,
|
|
Result.Context->getLangOpts())
|
|
.str();
|
|
ValueStr += ' ';
|
|
Diag << FixItHint::CreateReplacement(TypeRange, ValueStr);
|
|
}
|
|
|
|
// Use std::move in the initialization list.
|
|
Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")")
|
|
<< FixItHint::CreateInsertion(
|
|
Initializer->getLParenLoc().getLocWithOffset(1), "std::move(");
|
|
|
|
auto Insertion =
|
|
Inserter->CreateIncludeInsertion(SM.getMainFileID(), "utility",
|
|
/*IsAngled=*/true);
|
|
if (Insertion.hasValue())
|
|
Diag << Insertion.getValue();
|
|
}
|
|
|
|
} // namespace modernize
|
|
} // namespace tidy
|
|
} // namespace clang
|