//===--- 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(S); } static bool isNoReturnCallStatement(const Stmt *S) { const auto *Call = dyn_cast(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(E); } static bool isUnaryExprOfLiteral(const Expr *E) { if (const auto *UnOp = dyn_cast(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(Value)) return isa(DRE->getDecl()); return false; } namespace { AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) { return Node.getFieldIndex() >= Index; } } // namespace // Checks if Field is initialised using a field that will be initialised after // it. // TODO: Probably should guard against function calls that could have side // effects or if they do reference another field that's initialized before this // field, but is modified before the assignment. static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Context) { auto MemberMatcher = memberExpr(hasObjectExpression(cxxThisExpr()), member(fieldDecl(indexNotLessThan(Field->getFieldIndex())))); auto DeclMatcher = declRefExpr( to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Context))))); return match(expr(anyOf(MemberMatcher, DeclMatcher, hasDescendant(MemberMatcher), hasDescendant(DeclMatcher))), *Init, Field->getASTContext()) .empty(); } static const std::pair isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, const CXXConstructorDecl *Ctor) { if (const auto *BO = dyn_cast(S)) { if (BO->getOpcode() != BO_Assign) return std::make_pair(nullptr, nullptr); const auto *ME = dyn_cast(BO->getLHS()->IgnoreParenImpCasts()); if (!ME) return std::make_pair(nullptr, nullptr); const auto *Field = dyn_cast(ME->getMemberDecl()); if (!Field) return std::make_pair(nullptr, nullptr); if (!isa(ME->getBase())) return std::make_pair(nullptr, nullptr); const Expr *Init = BO->getRHS()->IgnoreParenImpCasts(); if (isSafeAssignment(Field, Init, Ctor)) return std::make_pair(Field, Init); } else if (const auto *COCE = dyn_cast(S)) { if (COCE->getOperator() != OO_Equal) return std::make_pair(nullptr, nullptr); const auto *ME = dyn_cast(COCE->getArg(0)->IgnoreParenImpCasts()); if (!ME) return std::make_pair(nullptr, nullptr); const auto *Field = dyn_cast(ME->getMemberDecl()); if (!Field) return std::make_pair(nullptr, nullptr); if (!isa(ME->getBase())) return std::make_pair(nullptr, nullptr); const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts(); if (isSafeAssignment(Field, Init, Ctor)) return std::make_pair(Field, Init); } 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, Context) .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("ctor"); const auto *Body = cast(Ctor->getBody()); const CXXRecordDecl *Class = Ctor->getParent(); 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(S)) { if (isNoReturnCallStatement(CondOp->getLHS()) || isNoReturnCallStatement(CondOp->getRHS())) return; } const FieldDecl *Field; const Expr *InitValue; std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor); if (Field) { if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && Ctor->isDefaultConstructor() && (getLangOpts().CPlusPlus20 || !Field->isBitField()) && !Field->hasInClassInitializer() && (!isa(Class->getDeclContext()) || !cast(Class->getDeclContext())->isUnion()) && shouldBeDefaultMemberInitializer(InitValue)) { bool InvalidFix = false; SourceLocation FieldEnd = Lexer::getLocForEndOfToken(Field->getSourceRange().getEnd(), 0, *Result.SourceManager, getLangOpts()); InvalidFix |= FieldEnd.isInvalid() || FieldEnd.isMacroID(); SourceLocation SemiColonEnd; if (auto NextToken = Lexer::findNextToken( S->getEndLoc(), *Result.SourceManager, getLangOpts())) SemiColonEnd = NextToken->getEndLoc(); else InvalidFix = true; auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in an in-class" " default member initializer") << Field; if (!InvalidFix) { CharSourceRange StmtRange = CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); SmallString<128> Insertion( {UseAssignment ? " = " : "{", Lexer::getSourceText( CharSourceRange(InitValue->getSourceRange(), true), *Result.SourceManager, getLangOpts()), UseAssignment ? "" : "}"}); Diag << FixItHint::CreateInsertion(FieldEnd, Insertion) << FixItHint::CreateRemoval(StmtRange); } } else { StringRef InsertPrefix = ""; SourceLocation InsertPos; bool AddComma = false; bool InvalidFix = false; unsigned Index = Field->getFieldIndex(); const CXXCtorInitializer *LastInListInit = nullptr; for (const CXXCtorInitializer *Init : Ctor->inits()) { if (!Init->isWritten()) continue; if (Init->isMemberInitializer() && Index < Init->getMember()->getFieldIndex()) { InsertPos = Init->getSourceLocation(); // There are initializers after the one we are inserting, so add a // comma after this insertion in order to not break anything. AddComma = true; break; } LastInListInit = Init; } if (InsertPos.isInvalid()) { if (LastInListInit) { InsertPos = Lexer::getLocForEndOfToken( LastInListInit->getRParenLoc(), 0, *Result.SourceManager, getLangOpts()); // Inserting after the last constructor initializer, so we need a // comma. InsertPrefix = ", "; } else { InsertPos = Lexer::getLocForEndOfToken( Ctor->getTypeSourceInfo() ->getTypeLoc() .getAs() .getLocalRangeEnd(), 0, *Result.SourceManager, getLangOpts()); // If this is first time in the loop, there are no initializers so // `:` declares member initialization list. If this is a subsequent // pass then we have already inserted a `:` so continue with a // comma. InsertPrefix = FirstToCtorInits ? " : " : ", "; } } InvalidFix |= InsertPos.isMacroID(); SourceLocation SemiColonEnd; if (auto NextToken = Lexer::findNextToken( S->getEndLoc(), *Result.SourceManager, getLangOpts())) SemiColonEnd = NextToken->getEndLoc(); else InvalidFix = true; auto Diag = diag(S->getBeginLoc(), "%0 should be initialized in a member" " initializer of the constructor") << Field; if (!InvalidFix) { CharSourceRange StmtRange = CharSourceRange::getCharRange(S->getBeginLoc(), SemiColonEnd); SmallString<128> Insertion( {InsertPrefix, Field->getName(), "(", Lexer::getSourceText( CharSourceRange(InitValue->getSourceRange(), true), *Result.SourceManager, getLangOpts()), AddComma ? "), " : ")"}); Diag << FixItHint::CreateInsertion(InsertPos, Insertion, FirstToCtorInits) << FixItHint::CreateRemoval(StmtRange); FirstToCtorInits = false; } } } } } } // namespace cppcoreguidelines } // namespace tidy } // namespace clang