
Finds member initializations in the constructor body which can be placed into the initialization list instead. This does not only improves the readability of the code but also affects positively its performance. Class-member assignments inside a control statement or following the first control statement are ignored. Differential Revision: https://reviews.llvm.org/D71199
247 lines
8.7 KiB
C++
247 lines
8.7 KiB
C++
//===--- PreferMemberInitializerCheck.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 "PreferMemberInitializerCheck.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 cppcoreguidelines {
|
|
|
|
static bool isControlStatement(const Stmt *S) {
|
|
return isa<IfStmt, SwitchStmt, ForStmt, WhileStmt, DoStmt, ReturnStmt,
|
|
GotoStmt, CXXTryStmt, CXXThrowExpr>(S);
|
|
}
|
|
|
|
static bool isNoReturnCallStatement(const Stmt *S) {
|
|
const auto *Call = dyn_cast<CallExpr>(S);
|
|
if (!Call)
|
|
return false;
|
|
|
|
const FunctionDecl *Func = Call->getDirectCallee();
|
|
if (!Func)
|
|
return false;
|
|
|
|
return Func->isNoReturn();
|
|
}
|
|
|
|
static bool isLiteral(const Expr *E) {
|
|
return isa<StringLiteral, CharacterLiteral, IntegerLiteral, FloatingLiteral,
|
|
CXXBoolLiteralExpr, CXXNullPtrLiteralExpr>(E);
|
|
}
|
|
|
|
static bool isUnaryExprOfLiteral(const Expr *E) {
|
|
if (const auto *UnOp = dyn_cast<UnaryOperator>(E))
|
|
return isLiteral(UnOp->getSubExpr());
|
|
return false;
|
|
}
|
|
|
|
static bool shouldBeDefaultMemberInitializer(const Expr *Value) {
|
|
if (isLiteral(Value) || isUnaryExprOfLiteral(Value))
|
|
return true;
|
|
|
|
if (const auto *DRE = dyn_cast<DeclRefExpr>(Value))
|
|
return isa<EnumConstantDecl>(DRE->getDecl());
|
|
|
|
return false;
|
|
}
|
|
|
|
static const std::pair<const FieldDecl *, const Expr *>
|
|
isAssignmentToMemberOf(const RecordDecl *Rec, const Stmt *S) {
|
|
if (const auto *BO = dyn_cast<BinaryOperator>(S)) {
|
|
if (BO->getOpcode() != BO_Assign)
|
|
return std::make_pair(nullptr, nullptr);
|
|
|
|
const auto *ME = dyn_cast<MemberExpr>(BO->getLHS()->IgnoreParenImpCasts());
|
|
if (!ME)
|
|
return std::make_pair(nullptr, nullptr);
|
|
|
|
const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
|
|
if (!Field)
|
|
return std::make_pair(nullptr, nullptr);
|
|
|
|
if (isa<CXXThisExpr>(ME->getBase()))
|
|
return std::make_pair(Field, BO->getRHS()->IgnoreParenImpCasts());
|
|
} else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) {
|
|
if (COCE->getOperator() != OO_Equal)
|
|
return std::make_pair(nullptr, nullptr);
|
|
|
|
const auto *ME =
|
|
dyn_cast<MemberExpr>(COCE->getArg(0)->IgnoreParenImpCasts());
|
|
if (!ME)
|
|
return std::make_pair(nullptr, nullptr);
|
|
|
|
const auto *Field = dyn_cast<FieldDecl>(ME->getMemberDecl());
|
|
if (!Field)
|
|
return std::make_pair(nullptr, nullptr);
|
|
|
|
if (isa<CXXThisExpr>(ME->getBase()))
|
|
return std::make_pair(Field, COCE->getArg(1)->IgnoreParenImpCasts());
|
|
}
|
|
|
|
return std::make_pair(nullptr, nullptr);
|
|
}
|
|
|
|
PreferMemberInitializerCheck::PreferMemberInitializerCheck(
|
|
StringRef Name, ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
IsUseDefaultMemberInitEnabled(
|
|
Context->isCheckEnabled("modernize-use-default-member-init")),
|
|
UseAssignment(OptionsView("modernize-use-default-member-init",
|
|
Context->getOptions().CheckOptions)
|
|
.get("UseAssignment", false)) {}
|
|
|
|
void PreferMemberInitializerCheck::storeOptions(
|
|
ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "UseAssignment", UseAssignment);
|
|
}
|
|
|
|
void PreferMemberInitializerCheck::registerMatchers(MatchFinder *Finder) {
|
|
Finder->addMatcher(
|
|
cxxConstructorDecl(hasBody(compoundStmt()), unless(isInstantiated()))
|
|
.bind("ctor"),
|
|
this);
|
|
}
|
|
|
|
void PreferMemberInitializerCheck::check(
|
|
const MatchFinder::MatchResult &Result) {
|
|
const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor");
|
|
const auto *Body = cast<CompoundStmt>(Ctor->getBody());
|
|
|
|
const CXXRecordDecl *Class = Ctor->getParent();
|
|
SourceLocation InsertPos;
|
|
bool FirstToCtorInits = true;
|
|
|
|
for (const Stmt *S : Body->body()) {
|
|
if (S->getBeginLoc().isMacroID()) {
|
|
StringRef MacroName =
|
|
Lexer::getImmediateMacroName(S->getBeginLoc(), *Result.SourceManager,
|
|
getLangOpts());
|
|
if (MacroName.contains_lower("assert"))
|
|
return;
|
|
}
|
|
if (isControlStatement(S))
|
|
return;
|
|
|
|
if (isNoReturnCallStatement(S))
|
|
return;
|
|
|
|
if (const auto *CondOp = dyn_cast<ConditionalOperator>(S)) {
|
|
if (isNoReturnCallStatement(CondOp->getLHS()) ||
|
|
isNoReturnCallStatement(CondOp->getRHS()))
|
|
return;
|
|
}
|
|
|
|
const FieldDecl *Field;
|
|
const Expr *InitValue;
|
|
std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S);
|
|
if (Field) {
|
|
if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 &&
|
|
Ctor->isDefaultConstructor() &&
|
|
(getLangOpts().CPlusPlus20 || !Field->isBitField()) &&
|
|
(!isa<RecordDecl>(Class->getDeclContext()) ||
|
|
!cast<RecordDecl>(Class->getDeclContext())->isUnion()) &&
|
|
shouldBeDefaultMemberInitializer(InitValue)) {
|
|
auto Diag =
|
|
diag(S->getBeginLoc(), "%0 should be initialized in an in-class"
|
|
" default member initializer")
|
|
<< Field;
|
|
|
|
SourceLocation FieldEnd =
|
|
Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
|
|
*Result.SourceManager, getLangOpts());
|
|
Diag << FixItHint::CreateInsertion(FieldEnd,
|
|
UseAssignment ? " = " : "{")
|
|
<< FixItHint::CreateInsertionFromRange(
|
|
FieldEnd,
|
|
CharSourceRange(InitValue->getSourceRange(), true))
|
|
<< FixItHint::CreateInsertion(FieldEnd, UseAssignment ? "" : "}");
|
|
|
|
SourceLocation SemiColonEnd =
|
|
Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager,
|
|
getLangOpts())
|
|
->getEndLoc();
|
|
CharSourceRange StmtRange =
|
|
CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
|
|
|
|
Diag << FixItHint::CreateRemoval(StmtRange);
|
|
} else {
|
|
auto Diag =
|
|
diag(S->getBeginLoc(), "%0 should be initialized in a member"
|
|
" initializer of the constructor")
|
|
<< Field;
|
|
|
|
bool AddComma = false;
|
|
if (!Ctor->getNumCtorInitializers() && FirstToCtorInits) {
|
|
SourceLocation BodyPos = Ctor->getBody()->getBeginLoc();
|
|
SourceLocation NextPos = Ctor->getBeginLoc();
|
|
do {
|
|
InsertPos = NextPos;
|
|
NextPos = Lexer::findNextToken(NextPos, *Result.SourceManager,
|
|
getLangOpts())
|
|
->getLocation();
|
|
} while (NextPos != BodyPos);
|
|
InsertPos = Lexer::getLocForEndOfToken(
|
|
InsertPos, 0, *Result.SourceManager, getLangOpts());
|
|
|
|
Diag << FixItHint::CreateInsertion(InsertPos, " : ");
|
|
} else {
|
|
bool Found = false;
|
|
for (const auto *Init : Ctor->inits()) {
|
|
if (Init->isMemberInitializer()) {
|
|
if (Result.SourceManager->isBeforeInTranslationUnit(
|
|
Field->getLocation(), Init->getMember()->getLocation())) {
|
|
InsertPos = Init->getSourceLocation();
|
|
Found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Found) {
|
|
if (Ctor->getNumCtorInitializers()) {
|
|
InsertPos = Lexer::getLocForEndOfToken(
|
|
(*Ctor->init_rbegin())->getSourceRange().getEnd(), 0,
|
|
*Result.SourceManager, getLangOpts());
|
|
}
|
|
Diag << FixItHint::CreateInsertion(InsertPos, ", ");
|
|
} else {
|
|
AddComma = true;
|
|
}
|
|
}
|
|
Diag << FixItHint::CreateInsertion(InsertPos, Field->getName())
|
|
<< FixItHint::CreateInsertion(InsertPos, "(")
|
|
<< FixItHint::CreateInsertionFromRange(
|
|
InsertPos,
|
|
CharSourceRange(InitValue->getSourceRange(), true))
|
|
<< FixItHint::CreateInsertion(InsertPos, ")");
|
|
if (AddComma)
|
|
Diag << FixItHint::CreateInsertion(InsertPos, ", ");
|
|
|
|
SourceLocation SemiColonEnd =
|
|
Lexer::findNextToken(S->getEndLoc(), *Result.SourceManager,
|
|
getLangOpts())
|
|
->getEndLoc();
|
|
CharSourceRange StmtRange =
|
|
CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd);
|
|
|
|
Diag << FixItHint::CreateRemoval(StmtRange);
|
|
FirstToCtorInits = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace cppcoreguidelines
|
|
} // namespace tidy
|
|
} // namespace clang
|