forked from OSchip/llvm-project
180 lines
7.0 KiB
C++
180 lines
7.0 KiB
C++
//===--- MoveConstructorInitCheck.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 "MoveConstructorInitCheck.h"
|
|
#include "../utils/Matchers.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include "clang/Lex/Preprocessor.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
|
|
namespace {
|
|
|
|
unsigned int
|
|
parmVarDeclRefExprOccurences(const ParmVarDecl &MovableParam,
|
|
const CXXConstructorDecl &ConstructorDecl,
|
|
ASTContext &Context) {
|
|
unsigned int Occurrences = 0;
|
|
auto AllDeclRefs =
|
|
findAll(declRefExpr(to(parmVarDecl(equalsNode(&MovableParam)))));
|
|
Occurrences += match(AllDeclRefs, *ConstructorDecl.getBody(), Context).size();
|
|
for (const auto *Initializer : ConstructorDecl.inits()) {
|
|
Occurrences += match(AllDeclRefs, *Initializer->getInit(), Context).size();
|
|
}
|
|
return Occurrences;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MoveConstructorInitCheck::MoveConstructorInitCheck(StringRef Name,
|
|
ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
IncludeStyle(IncludeSorter::parseIncludeStyle(
|
|
Options.get("IncludeStyle", "llvm"))),
|
|
UseCERTSemantics(Context->isCheckEnabled("cert-oop11-cpp")) {}
|
|
|
|
void MoveConstructorInitCheck::registerMatchers(MatchFinder *Finder) {
|
|
// Only register the matchers for C++11; the functionality currently does not
|
|
// provide any benefit to other languages, despite being benign.
|
|
if (!getLangOpts().CPlusPlus11)
|
|
return;
|
|
|
|
Finder->addMatcher(
|
|
cxxConstructorDecl(
|
|
unless(isImplicit()),
|
|
allOf(isMoveConstructor(),
|
|
hasAnyConstructorInitializer(
|
|
cxxCtorInitializer(
|
|
withInitializer(cxxConstructExpr(hasDeclaration(
|
|
cxxConstructorDecl(isCopyConstructor())
|
|
.bind("ctor")))))
|
|
.bind("move-init")))),
|
|
this);
|
|
|
|
auto NonConstValueMovableAndExpensiveToCopy =
|
|
qualType(allOf(unless(pointerType()), unless(isConstQualified()),
|
|
hasDeclaration(cxxRecordDecl(hasMethod(cxxConstructorDecl(
|
|
isMoveConstructor(), unless(isDeleted()))))),
|
|
matchers::isExpensiveToCopy()));
|
|
|
|
// This checker is also used to implement cert-oop11-cpp, but when using that
|
|
// form of the checker, we do not want to diagnose movable parameters.
|
|
if (!UseCERTSemantics)
|
|
Finder->addMatcher(
|
|
cxxConstructorDecl(
|
|
allOf(
|
|
unless(isMoveConstructor()),
|
|
hasAnyConstructorInitializer(withInitializer(cxxConstructExpr(
|
|
hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
|
|
hasArgument(
|
|
0,
|
|
declRefExpr(
|
|
to(parmVarDecl(
|
|
hasType(
|
|
NonConstValueMovableAndExpensiveToCopy))
|
|
.bind("movable-param")))
|
|
.bind("init-arg")))))))
|
|
.bind("ctor-decl"),
|
|
this);
|
|
}
|
|
|
|
void MoveConstructorInitCheck::check(const MatchFinder::MatchResult &Result) {
|
|
if (Result.Nodes.getNodeAs<CXXCtorInitializer>("move-init") != nullptr)
|
|
handleMoveConstructor(Result);
|
|
if (Result.Nodes.getNodeAs<ParmVarDecl>("movable-param") != nullptr)
|
|
handleParamNotMoved(Result);
|
|
}
|
|
|
|
void MoveConstructorInitCheck::handleParamNotMoved(
|
|
const MatchFinder::MatchResult &Result) {
|
|
const auto *MovableParam =
|
|
Result.Nodes.getNodeAs<ParmVarDecl>("movable-param");
|
|
const auto *ConstructorDecl =
|
|
Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor-decl");
|
|
const auto *InitArg = Result.Nodes.getNodeAs<DeclRefExpr>("init-arg");
|
|
// If the parameter is referenced more than once it is not safe to move it.
|
|
if (parmVarDeclRefExprOccurences(*MovableParam, *ConstructorDecl,
|
|
*Result.Context) > 1)
|
|
return;
|
|
auto DiagOut =
|
|
diag(InitArg->getLocStart(), "value argument can be moved to avoid copy");
|
|
DiagOut << FixItHint::CreateReplacement(
|
|
InitArg->getSourceRange(),
|
|
(Twine("std::move(") + MovableParam->getName() + ")").str());
|
|
if (auto IncludeFixit = Inserter->CreateIncludeInsertion(
|
|
Result.SourceManager->getFileID(InitArg->getLocStart()), "utility",
|
|
/*IsAngled=*/true)) {
|
|
DiagOut << *IncludeFixit;
|
|
}
|
|
}
|
|
|
|
void MoveConstructorInitCheck::handleMoveConstructor(
|
|
const MatchFinder::MatchResult &Result) {
|
|
const auto *CopyCtor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
|
|
const auto *Initializer = Result.Nodes.getNodeAs<CXXCtorInitializer>("move-init");
|
|
|
|
// Do not diagnose if the expression used to perform the initialization is a
|
|
// trivially-copyable type.
|
|
QualType QT = Initializer->getInit()->getType();
|
|
if (QT.isTriviallyCopyableType(*Result.Context))
|
|
return;
|
|
|
|
const auto *RD = QT->getAsCXXRecordDecl();
|
|
if (RD && RD->isTriviallyCopyable())
|
|
return;
|
|
|
|
// Diagnose when the class type has a move constructor available, but the
|
|
// ctor-initializer uses the copy constructor instead.
|
|
const CXXConstructorDecl *Candidate = nullptr;
|
|
for (const auto *Ctor : CopyCtor->getParent()->ctors()) {
|
|
if (Ctor->isMoveConstructor() && Ctor->getAccess() <= AS_protected &&
|
|
!Ctor->isDeleted()) {
|
|
// The type has a move constructor that is at least accessible to the
|
|
// initializer.
|
|
//
|
|
// FIXME: Determine whether the move constructor is a viable candidate
|
|
// for the ctor-initializer, perhaps provide a fixit that suggests
|
|
// using std::move().
|
|
Candidate = Ctor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Candidate) {
|
|
// There's a move constructor candidate that the caller probably intended
|
|
// to call instead.
|
|
diag(Initializer->getSourceLocation(),
|
|
"move constructor initializes %0 by calling a copy constructor")
|
|
<< (Initializer->isBaseInitializer() ? "base class" : "class member");
|
|
diag(CopyCtor->getLocation(), "copy constructor being called",
|
|
DiagnosticIDs::Note);
|
|
diag(Candidate->getLocation(), "candidate move constructor here",
|
|
DiagnosticIDs::Note);
|
|
}
|
|
}
|
|
|
|
void MoveConstructorInitCheck::registerPPCallbacks(CompilerInstance &Compiler) {
|
|
Inserter.reset(new IncludeInserter(Compiler.getSourceManager(),
|
|
Compiler.getLangOpts(), IncludeStyle));
|
|
Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks());
|
|
}
|
|
|
|
void MoveConstructorInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "IncludeStyle", IncludeSorter::toString(IncludeStyle));
|
|
}
|
|
|
|
} // namespace tidy
|
|
} // namespace clang
|