| //===--- 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, 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(); |
| 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 |