//===--- 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 { 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->getAs()->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->getAs()->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->getAs()->getElementType()); case Type::STK_FixedPoint: switch (InitType->getAs()->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(E)->getNumInits() == 0; case Stmt::CharacterLiteralClass: return !cast(E)->getValue(); case Stmt::CXXBoolLiteralExprClass: return !cast(E)->getValue(); case Stmt::IntegerLiteralClass: return !cast(E)->getValue(); case Stmt::FloatingLiteralClass: { llvm::APFloat Value = cast(E)->getValue(); return Value.isZero() && !Value.isNegative(); } default: return false; } } static const Expr *ignoreUnaryPlus(const Expr *E) { auto *UnaryOp = dyn_cast(E); if (UnaryOp && UnaryOp->getOpcode() == UO_Plus) return UnaryOp->getSubExpr(); return E; } static const Expr *getInitializer(const Expr *E) { auto *InitList = dyn_cast(E); if (InitList && InitList->getNumInits() == 1) return InitList->getInit(0); 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(E1)->getSubExpr(), cast(E2)->getSubExpr()); case Stmt::CharacterLiteralClass: return cast(E1)->getValue() == cast(E2)->getValue(); case Stmt::CXXBoolLiteralExprClass: return cast(E1)->getValue() == cast(E2)->getValue(); case Stmt::IntegerLiteralClass: return cast(E1)->getValue() == cast(E2)->getValue(); case Stmt::FloatingLiteralClass: return cast(E1)->getValue().bitwiseIsEqual( cast(E2)->getValue()); case Stmt::StringLiteralClass: return cast(E1)->getString() == cast(E2)->getString(); case Stmt::DeclRefExprClass: return cast(E1)->getDecl() == cast(E2)->getDecl(); default: return false; } } UseDefaultMemberInitCheck::UseDefaultMemberInitCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), UseAssignment(Options.get("UseAssignment", 0) != 0), IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true) != 0) {} void UseDefaultMemberInitCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "UseAssignment", UseAssignment); Options.store(Opts, "IgnoreMacros", IgnoreMacros); } void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) { if (!getLangOpts().CPlusPlus11) return; auto Init = anyOf(stringLiteral(), characterLiteral(), integerLiteral(), unaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("-")), hasUnaryOperand(integerLiteral())), floatLiteral(), unaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("-")), hasUnaryOperand(floatLiteral())), cxxBoolLiteral(), cxxNullPtrLiteralExpr(), implicitValueInitExpr(), declRefExpr(to(enumConstantDecl()))); Finder->addMatcher( cxxConstructorDecl( isDefaultConstructor(), unless(isInstantiated()), forEachConstructorInitializer( cxxCtorInitializer( forField(unless(anyOf(getLangOpts().CPlusPlus2a ? unless(anything()) : isBitField(), hasInClassInitializer(anything()), hasParent(recordDecl(isUnion()))))), isWritten(), withInitializer(ignoringImplicit(Init))) .bind("default"))), this); Finder->addMatcher( cxxConstructorDecl( unless(ast_matchers::isTemplateInstantiation()), forEachConstructorInitializer( cxxCtorInitializer(forField(hasInClassInitializer(anything())), isWritten(), withInitializer(ignoringImplicit(Init))) .bind("existing"))), this); } void UseDefaultMemberInitCheck::check(const MatchFinder::MatchResult &Result) { if (const auto *Default = Result.Nodes.getNodeAs("default")) checkDefaultInit(Result, Default); else if (const auto *Existing = Result.Nodes.getNodeAs("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(); 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(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