| //===--- 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; |
| } |
| |
| 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<const FieldDecl *, const Expr *> |
| isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S, |
| const CXXConstructorDecl *Ctor) { |
| 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(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<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(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<CXXConstructorDecl>("ctor"); |
| const auto *Body = cast<CompoundStmt>(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_insensitive("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, Ctor); |
| if (Field) { |
| if (IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 && |
| Ctor->isDefaultConstructor() && |
| (getLangOpts().CPlusPlus20 || !Field->isBitField()) && |
| !Field->hasInClassInitializer() && |
| (!isa<RecordDecl>(Class->getDeclContext()) || |
| !cast<RecordDecl>(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<clang::FunctionTypeLoc>() |
| .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 |