forked from OSchip/llvm-project
302 lines
9.9 KiB
C++
302 lines
9.9 KiB
C++
//===--- UseDefaultMemberInitCheck.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 "UseDefaultMemberInitCheck.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 {
|
|
|
|
namespace {
|
|
AST_MATCHER_P(InitListExpr, initCountIs, unsigned, N) {
|
|
return Node.getNumInits() == N;
|
|
}
|
|
} // namespace
|
|
|
|
static StringRef getValueOfValueInit(const QualType InitType) {
|
|
switch (InitType->getScalarTypeKind()) {
|
|
case Type::STK_CPointer:
|
|
case Type::STK_BlockPointer:
|
|
case Type::STK_ObjCObjectPointer:
|
|
case Type::STK_MemberPointer:
|
|
return "nullptr";
|
|
|
|
case Type::STK_Bool:
|
|
return "false";
|
|
|
|
case Type::STK_Integral:
|
|
switch (InitType->castAs<BuiltinType>()->getKind()) {
|
|
case BuiltinType::Char_U:
|
|
case BuiltinType::UChar:
|
|
case BuiltinType::Char_S:
|
|
case BuiltinType::SChar:
|
|
return "'\\0'";
|
|
case BuiltinType::WChar_U:
|
|
case BuiltinType::WChar_S:
|
|
return "L'\\0'";
|
|
case BuiltinType::Char16:
|
|
return "u'\\0'";
|
|
case BuiltinType::Char32:
|
|
return "U'\\0'";
|
|
default:
|
|
return "0";
|
|
}
|
|
|
|
case Type::STK_Floating:
|
|
switch (InitType->castAs<BuiltinType>()->getKind()) {
|
|
case BuiltinType::Half:
|
|
case BuiltinType::Float:
|
|
return "0.0f";
|
|
default:
|
|
return "0.0";
|
|
}
|
|
|
|
case Type::STK_FloatingComplex:
|
|
case Type::STK_IntegralComplex:
|
|
return getValueOfValueInit(
|
|
InitType->castAs<ComplexType>()->getElementType());
|
|
|
|
case Type::STK_FixedPoint:
|
|
switch (InitType->castAs<BuiltinType>()->getKind()) {
|
|
case BuiltinType::ShortAccum:
|
|
case BuiltinType::SatShortAccum:
|
|
return "0.0hk";
|
|
case BuiltinType::Accum:
|
|
case BuiltinType::SatAccum:
|
|
return "0.0k";
|
|
case BuiltinType::LongAccum:
|
|
case BuiltinType::SatLongAccum:
|
|
return "0.0lk";
|
|
case BuiltinType::UShortAccum:
|
|
case BuiltinType::SatUShortAccum:
|
|
return "0.0uhk";
|
|
case BuiltinType::UAccum:
|
|
case BuiltinType::SatUAccum:
|
|
return "0.0uk";
|
|
case BuiltinType::ULongAccum:
|
|
case BuiltinType::SatULongAccum:
|
|
return "0.0ulk";
|
|
case BuiltinType::ShortFract:
|
|
case BuiltinType::SatShortFract:
|
|
return "0.0hr";
|
|
case BuiltinType::Fract:
|
|
case BuiltinType::SatFract:
|
|
return "0.0r";
|
|
case BuiltinType::LongFract:
|
|
case BuiltinType::SatLongFract:
|
|
return "0.0lr";
|
|
case BuiltinType::UShortFract:
|
|
case BuiltinType::SatUShortFract:
|
|
return "0.0uhr";
|
|
case BuiltinType::UFract:
|
|
case BuiltinType::SatUFract:
|
|
return "0.0ur";
|
|
case BuiltinType::ULongFract:
|
|
case BuiltinType::SatULongFract:
|
|
return "0.0ulr";
|
|
default:
|
|
llvm_unreachable("Unhandled fixed point BuiltinType");
|
|
}
|
|
}
|
|
llvm_unreachable("Invalid scalar type kind");
|
|
}
|
|
|
|
static bool isZero(const Expr *E) {
|
|
switch (E->getStmtClass()) {
|
|
case Stmt::CXXNullPtrLiteralExprClass:
|
|
case Stmt::ImplicitValueInitExprClass:
|
|
return true;
|
|
case Stmt::InitListExprClass:
|
|
return cast<InitListExpr>(E)->getNumInits() == 0;
|
|
case Stmt::CharacterLiteralClass:
|
|
return !cast<CharacterLiteral>(E)->getValue();
|
|
case Stmt::CXXBoolLiteralExprClass:
|
|
return !cast<CXXBoolLiteralExpr>(E)->getValue();
|
|
case Stmt::IntegerLiteralClass:
|
|
return !cast<IntegerLiteral>(E)->getValue();
|
|
case Stmt::FloatingLiteralClass: {
|
|
llvm::APFloat Value = cast<FloatingLiteral>(E)->getValue();
|
|
return Value.isZero() && !Value.isNegative();
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const Expr *ignoreUnaryPlus(const Expr *E) {
|
|
auto *UnaryOp = dyn_cast<UnaryOperator>(E);
|
|
if (UnaryOp && UnaryOp->getOpcode() == UO_Plus)
|
|
return UnaryOp->getSubExpr();
|
|
return E;
|
|
}
|
|
|
|
static const Expr *getInitializer(const Expr *E) {
|
|
auto *InitList = dyn_cast<InitListExpr>(E);
|
|
if (InitList && InitList->getNumInits() == 1)
|
|
return InitList->getInit(0)->IgnoreParenImpCasts();
|
|
return E;
|
|
}
|
|
|
|
static bool sameValue(const Expr *E1, const Expr *E2) {
|
|
E1 = ignoreUnaryPlus(getInitializer(E1->IgnoreParenImpCasts()));
|
|
E2 = ignoreUnaryPlus(getInitializer(E2->IgnoreParenImpCasts()));
|
|
|
|
if (isZero(E1) && isZero(E2))
|
|
return true;
|
|
|
|
if (E1->getStmtClass() != E2->getStmtClass())
|
|
return false;
|
|
|
|
switch (E1->getStmtClass()) {
|
|
case Stmt::UnaryOperatorClass:
|
|
return sameValue(cast<UnaryOperator>(E1)->getSubExpr(),
|
|
cast<UnaryOperator>(E2)->getSubExpr());
|
|
case Stmt::CharacterLiteralClass:
|
|
return cast<CharacterLiteral>(E1)->getValue() ==
|
|
cast<CharacterLiteral>(E2)->getValue();
|
|
case Stmt::CXXBoolLiteralExprClass:
|
|
return cast<CXXBoolLiteralExpr>(E1)->getValue() ==
|
|
cast<CXXBoolLiteralExpr>(E2)->getValue();
|
|
case Stmt::IntegerLiteralClass:
|
|
return cast<IntegerLiteral>(E1)->getValue() ==
|
|
cast<IntegerLiteral>(E2)->getValue();
|
|
case Stmt::FloatingLiteralClass:
|
|
return cast<FloatingLiteral>(E1)->getValue().bitwiseIsEqual(
|
|
cast<FloatingLiteral>(E2)->getValue());
|
|
case Stmt::StringLiteralClass:
|
|
return cast<StringLiteral>(E1)->getString() ==
|
|
cast<StringLiteral>(E2)->getString();
|
|
case Stmt::DeclRefExprClass:
|
|
return cast<DeclRefExpr>(E1)->getDecl() == cast<DeclRefExpr>(E2)->getDecl();
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
UseDefaultMemberInitCheck::UseDefaultMemberInitCheck(StringRef Name,
|
|
ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
UseAssignment(Options.get("UseAssignment", false)),
|
|
IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {}
|
|
|
|
void UseDefaultMemberInitCheck::storeOptions(
|
|
ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "UseAssignment", UseAssignment);
|
|
Options.store(Opts, "IgnoreMacros", IgnoreMacros);
|
|
}
|
|
|
|
void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) {
|
|
auto InitBase =
|
|
anyOf(stringLiteral(), characterLiteral(), integerLiteral(),
|
|
unaryOperator(hasAnyOperatorName("+", "-"),
|
|
hasUnaryOperand(integerLiteral())),
|
|
floatLiteral(),
|
|
unaryOperator(hasAnyOperatorName("+", "-"),
|
|
hasUnaryOperand(floatLiteral())),
|
|
cxxBoolLiteral(), cxxNullPtrLiteralExpr(), implicitValueInitExpr(),
|
|
declRefExpr(to(enumConstantDecl())));
|
|
|
|
auto Init =
|
|
anyOf(initListExpr(anyOf(allOf(initCountIs(1), hasInit(0, InitBase)),
|
|
initCountIs(0))),
|
|
InitBase);
|
|
|
|
Finder->addMatcher(
|
|
cxxConstructorDecl(forEachConstructorInitializer(
|
|
cxxCtorInitializer(
|
|
forField(unless(anyOf(
|
|
getLangOpts().CPlusPlus20 ? unless(anything()) : isBitField(),
|
|
hasInClassInitializer(anything()),
|
|
hasParent(recordDecl(isUnion()))))),
|
|
withInitializer(Init))
|
|
.bind("default"))),
|
|
this);
|
|
|
|
Finder->addMatcher(
|
|
cxxConstructorDecl(forEachConstructorInitializer(
|
|
cxxCtorInitializer(forField(hasInClassInitializer(anything())),
|
|
withInitializer(Init))
|
|
.bind("existing"))),
|
|
this);
|
|
}
|
|
|
|
void UseDefaultMemberInitCheck::check(const MatchFinder::MatchResult &Result) {
|
|
if (const auto *Default =
|
|
Result.Nodes.getNodeAs<CXXCtorInitializer>("default"))
|
|
checkDefaultInit(Result, Default);
|
|
else if (const auto *Existing =
|
|
Result.Nodes.getNodeAs<CXXCtorInitializer>("existing"))
|
|
checkExistingInit(Result, Existing);
|
|
else
|
|
llvm_unreachable("Bad Callback. No node provided.");
|
|
}
|
|
|
|
void UseDefaultMemberInitCheck::checkDefaultInit(
|
|
const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) {
|
|
const FieldDecl *Field = Init->getAnyMember();
|
|
|
|
// Check whether we have multiple hand-written constructors and bomb out, as
|
|
// it is hard to reconcile their sets of member initializers.
|
|
const auto *ClassDecl = cast<CXXRecordDecl>(Field->getParent());
|
|
if (llvm::count_if(ClassDecl->ctors(), [](const CXXConstructorDecl *Ctor) {
|
|
return !Ctor->isCopyOrMoveConstructor();
|
|
}) > 1)
|
|
return;
|
|
|
|
SourceLocation StartLoc = Field->getBeginLoc();
|
|
if (StartLoc.isMacroID() && IgnoreMacros)
|
|
return;
|
|
|
|
SourceLocation FieldEnd =
|
|
Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0,
|
|
*Result.SourceManager, getLangOpts());
|
|
SourceLocation LParenEnd = Lexer::getLocForEndOfToken(
|
|
Init->getLParenLoc(), 0, *Result.SourceManager, getLangOpts());
|
|
CharSourceRange InitRange =
|
|
CharSourceRange::getCharRange(LParenEnd, Init->getRParenLoc());
|
|
|
|
bool ValueInit = isa<ImplicitValueInitExpr>(Init->getInit());
|
|
bool CanAssign = UseAssignment && (!ValueInit || !Init->getInit()->getType()->isEnumeralType());
|
|
|
|
auto Diag =
|
|
diag(Field->getLocation(), "use default member initializer for %0")
|
|
<< Field
|
|
<< FixItHint::CreateInsertion(FieldEnd, CanAssign ? " = " : "{")
|
|
<< FixItHint::CreateInsertionFromRange(FieldEnd, InitRange);
|
|
|
|
if (CanAssign && ValueInit)
|
|
Diag << FixItHint::CreateInsertion(
|
|
FieldEnd, getValueOfValueInit(Init->getInit()->getType()));
|
|
|
|
if (!CanAssign)
|
|
Diag << FixItHint::CreateInsertion(FieldEnd, "}");
|
|
|
|
Diag << FixItHint::CreateRemoval(Init->getSourceRange());
|
|
}
|
|
|
|
void UseDefaultMemberInitCheck::checkExistingInit(
|
|
const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) {
|
|
const FieldDecl *Field = Init->getAnyMember();
|
|
|
|
if (!sameValue(Field->getInClassInitializer(), Init->getInit()))
|
|
return;
|
|
|
|
diag(Init->getSourceLocation(), "member initializer for %0 is redundant")
|
|
<< Field
|
|
<< FixItHint::CreateRemoval(Init->getSourceRange());
|
|
}
|
|
|
|
} // namespace modernize
|
|
} // namespace tidy
|
|
} // namespace clang
|