teak-llvm/clang-tools-extra/clang-tidy/modernize/UseEqualsDefaultCheck.cpp
Chandler Carruth 2946cd7010 Update the file headers across all of the LLVM projects in the monorepo
to reflect the new license.

We understand that people may be surprised that we're moving the header
entirely to discuss the new license. We checked this carefully with the
Foundation's lawyer and we believe this is the correct approach.

Essentially, all code in the project is now made available by the LLVM
project under our new license, so you will see that the license headers
include that license only. Some of our contributors have contributed
code under our old license, and accordingly, we have retained a copy of
our old license notice in the top-level files in each project and
repository.

llvm-svn: 351636
2019-01-19 08:50:56 +00:00

313 lines
12 KiB
C++

//===--- UseEqualsDefaultCheck.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 "UseEqualsDefaultCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace modernize {
static const char SpecialFunction[] = "SpecialFunction";
/// \brief Finds all the named non-static fields of \p Record.
static std::set<const FieldDecl *>
getAllNamedFields(const CXXRecordDecl *Record) {
std::set<const FieldDecl *> Result;
for (const auto *Field : Record->fields()) {
// Static data members are not in this range.
if (Field->isUnnamedBitfield())
continue;
Result.insert(Field);
}
return Result;
}
/// \brief Returns the names of the direct bases of \p Record, both virtual and
/// non-virtual.
static std::set<const Type *> getAllDirectBases(const CXXRecordDecl *Record) {
std::set<const Type *> Result;
for (auto Base : Record->bases()) {
// CXXBaseSpecifier.
const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr();
Result.insert(BaseType);
}
return Result;
}
/// \brief Returns a matcher that matches member expressions where the base is
/// the variable declared as \p Var and the accessed member is the one declared
/// as \p Field.
internal::Matcher<Expr> accessToFieldInVar(const FieldDecl *Field,
const ValueDecl *Var) {
return ignoringImpCasts(
memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))),
member(fieldDecl(equalsNode(Field)))));
}
/// \brief Check that the given constructor has copy signature and that it
/// copy-initializes all its bases and members.
static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context,
const CXXConstructorDecl *Ctor) {
// An explicitly-defaulted constructor cannot have default arguments.
if (Ctor->getMinRequiredArguments() != 1)
return false;
const auto *Record = Ctor->getParent();
const auto *Param = Ctor->getParamDecl(0);
// Base classes and members that have to be copied.
auto BasesToInit = getAllDirectBases(Record);
auto FieldsToInit = getAllNamedFields(Record);
// Ensure that all the bases are copied.
for (const auto *Base : BasesToInit) {
// The initialization of a base class should be a call to a copy
// constructor of the base.
if (match(
cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer(
isBaseInitializer(),
withInitializer(cxxConstructExpr(
hasType(equalsNode(Base)),
hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
argumentCountIs(1),
hasArgument(
0, declRefExpr(to(varDecl(equalsNode(Param)))))))))),
*Ctor, *Context)
.empty())
return false;
}
// Ensure that all the members are copied.
for (const auto *Field : FieldsToInit) {
auto AccessToFieldInParam = accessToFieldInVar(Field, Param);
// The initialization is a CXXConstructExpr for class types.
if (match(
cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer(
isMemberInitializer(), forField(equalsNode(Field)),
withInitializer(anyOf(
AccessToFieldInParam,
initListExpr(has(AccessToFieldInParam)),
cxxConstructExpr(
hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
argumentCountIs(1),
hasArgument(0, AccessToFieldInParam))))))),
*Ctor, *Context)
.empty())
return false;
}
// Ensure that we don't do anything else, like initializing an indirect base.
return Ctor->getNumCtorInitializers() ==
BasesToInit.size() + FieldsToInit.size();
}
/// \brief Checks that the given method is an overloading of the assignment
/// operator, has copy signature, returns a reference to "*this" and copies
/// all its members and subobjects.
static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context,
const CXXMethodDecl *Operator) {
const auto *Record = Operator->getParent();
const auto *Param = Operator->getParamDecl(0);
// Base classes and members that have to be copied.
auto BasesToInit = getAllDirectBases(Record);
auto FieldsToInit = getAllNamedFields(Record);
const auto *Compound = cast<CompoundStmt>(Operator->getBody());
// The assignment operator definition has to end with the following return
// statement:
// return *this;
if (Compound->body_empty() ||
match(returnStmt(has(ignoringParenImpCasts(unaryOperator(
hasOperatorName("*"), hasUnaryOperand(cxxThisExpr()))))),
*Compound->body_back(), *Context)
.empty())
return false;
// Ensure that all the bases are copied.
for (const auto *Base : BasesToInit) {
// Assignment operator of a base class:
// Base::operator=(Other);
//
// Clang translates this into:
// ((Base*)this)->operator=((Base)Other);
//
// So we are looking for a member call that fulfills:
if (match(
compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr(
// - The object is an implicit cast of 'this' to a pointer to
// a base class.
onImplicitObjectArgument(
implicitCastExpr(hasImplicitDestinationType(
pointsTo(type(equalsNode(Base)))),
hasSourceExpression(cxxThisExpr()))),
// - The called method is the operator=.
callee(cxxMethodDecl(isCopyAssignmentOperator())),
// - The argument is (an implicit cast to a Base of) the
// argument taken by "Operator".
argumentCountIs(1),
hasArgument(0, declRefExpr(to(varDecl(equalsNode(Param))))))))),
*Compound, *Context)
.empty())
return false;
}
// Ensure that all the members are copied.
for (const auto *Field : FieldsToInit) {
// The assignment of data members:
// Field = Other.Field;
// Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr
// otherwise.
auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()),
member(fieldDecl(equalsNode(Field))));
auto RHS = accessToFieldInVar(Field, Param);
if (match(
compoundStmt(has(ignoringParenImpCasts(stmt(anyOf(
binaryOperator(hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)),
cxxOperatorCallExpr(hasOverloadedOperatorName("="),
argumentCountIs(2), hasArgument(0, LHS),
hasArgument(1, RHS))))))),
*Compound, *Context)
.empty())
return false;
}
// Ensure that we don't do anything else.
return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1;
}
/// \brief Returns false if the body has any non-whitespace character.
static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) {
bool Invalid = false;
StringRef Text = Lexer::getSourceText(
CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1),
Body->getRBracLoc()),
Context->getSourceManager(), Context->getLangOpts(), &Invalid);
return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size();
}
UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true) != 0) {}
void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IgnoreMacros", IgnoreMacros);
}
void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus)
return;
// Destructor.
Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction),
this);
Finder->addMatcher(
cxxConstructorDecl(
isDefinition(),
anyOf(
// Default constructor.
allOf(unless(hasAnyConstructorInitializer(isWritten())),
parameterCountIs(0)),
// Copy constructor.
allOf(isCopyConstructor(),
// Discard constructors that can be used as a copy
// constructor because all the other arguments have
// default values.
parameterCountIs(1))))
.bind(SpecialFunction),
this);
// Copy-assignment operator.
Finder->addMatcher(
cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(),
// isCopyAssignmentOperator() allows the parameter to be
// passed by value, and in this case it cannot be
// defaulted.
hasParameter(0, hasType(lValueReferenceType())))
.bind(SpecialFunction),
this);
}
void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) {
std::string SpecialFunctionName;
// Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl.
const auto *SpecialFunctionDecl =
Result.Nodes.getNodeAs<CXXMethodDecl>(SpecialFunction);
if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID())
return;
// Discard explicitly deleted/defaulted special member functions and those
// that are not user-provided (automatically generated).
if (SpecialFunctionDecl->isDeleted() ||
SpecialFunctionDecl->isExplicitlyDefaulted() ||
SpecialFunctionDecl->isLateTemplateParsed() ||
SpecialFunctionDecl->isTemplateInstantiation() ||
!SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody())
return;
const auto *Body = dyn_cast<CompoundStmt>(SpecialFunctionDecl->getBody());
if (!Body)
return;
// If there is code inside the body, don't warn.
if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty())
return;
// If there are comments inside the body, don't do the change.
bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() ||
bodyEmpty(Result.Context, Body);
std::vector<FixItHint> RemoveInitializers;
if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(SpecialFunctionDecl)) {
if (Ctor->getNumParams() == 0) {
SpecialFunctionName = "default constructor";
} else {
if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor))
return;
SpecialFunctionName = "copy constructor";
// If there are constructor initializers, they must be removed.
for (const auto *Init : Ctor->inits()) {
RemoveInitializers.emplace_back(
FixItHint::CreateRemoval(Init->getSourceRange()));
}
}
} else if (isa<CXXDestructorDecl>(SpecialFunctionDecl)) {
SpecialFunctionName = "destructor";
} else {
if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl))
return;
SpecialFunctionName = "copy-assignment operator";
}
// The location of the body is more useful inside a macro as spelling and
// expansion locations are reported.
SourceLocation Location = SpecialFunctionDecl->getLocation();
if (Location.isMacroID())
Location = Body->getBeginLoc();
auto Diag = diag(Location, "use '= default' to define a trivial " +
SpecialFunctionName);
if (ApplyFix)
Diag << FixItHint::CreateReplacement(Body->getSourceRange(), "= default;")
<< RemoveInitializers;
}
} // namespace modernize
} // namespace tidy
} // namespace clang