| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,233 @@ | ||
| //===--- 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>(S) || isa<SwitchStmt>(S) || isa<ForStmt>(S) || | ||
| isa<WhileStmt>(S) || isa<DoStmt>(S) || isa<ReturnStmt>(S) || | ||
| isa<GotoStmt>(S) || isa<CXXTryStmt>(S) || isa<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>(E) || isa<CharacterLiteral>(E) || | ||
| isa<IntegerLiteral>(E) || isa<FloatingLiteral>(E) || | ||
| isa<CXXBoolLiteralExpr>(E) || isa<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) | ||
| .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 auto *S : Body->body()) { | ||
| if (isControlStatement(S)) | ||
| return; | ||
|
|
||
| if (isNoReturnCallStatement(S)) | ||
| 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 (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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //===--- PreferMemberInitializerCheck.h - clang-tidy ------------*- C++ -*-===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PREFERMEMBERINITIALIZERCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PREFERMEMBERINITIALIZERCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang { | ||
| namespace tidy { | ||
| namespace cppcoreguidelines { | ||
|
|
||
| /// Finds member initializations in the constructor body which can be placed | ||
| /// into the initialization list instead. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-prefer-member-initializer.html | ||
| class PreferMemberInitializerCheck : public ClangTidyCheck { | ||
| public: | ||
| PreferMemberInitializerCheck(StringRef Name, ClangTidyContext *Context); | ||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
| return LangOpts.CPlusPlus; | ||
| } | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
|
|
||
| const bool IsUseDefaultMemberInitEnabled; | ||
| const bool UseAssignment; | ||
| }; | ||
|
|
||
| } // namespace cppcoreguidelines | ||
| } // namespace tidy | ||
| } // namespace clang | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PREFERMEMBERINITIALIZERCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| .. title:: clang-tidy - bugprone-redundant-branch-condition | ||
|
|
||
| bugprone-redundant-branch-condition | ||
| =================================== | ||
|
|
||
| Finds condition variables in nested ``if`` statements that were also checked in | ||
| the outer ``if`` statement and were not changed. | ||
|
|
||
| Simple example: | ||
|
|
||
| .. code-block:: c | ||
| bool onFire = isBurning(); | ||
| if (onFire) { | ||
| if (onFire) | ||
| scream(); | ||
| } | ||
| Here `onFire` is checked both in the outer ``if`` and the inner ``if`` statement | ||
| without a possible change between the two checks. The check warns for this code | ||
| and suggests removal of the second checking of variable `onFire`. | ||
|
|
||
| The checker also detects redundant condition checks if the condition variable | ||
| is an operand of a logical "and" (``&&``) or a logical "or" (``||``) operator: | ||
|
|
||
| .. code-block:: c | ||
| bool onFire = isBurning(); | ||
| if (onFire) { | ||
| if (onFire && peopleInTheBuilding > 0) | ||
| scream(); | ||
| } | ||
| .. code-block:: c | ||
| bool onFire = isBurning(); | ||
| if (onFire) { | ||
| if (onFire || isCollapsing()) | ||
| scream(); | ||
| } | ||
| In the first case (logical "and") the suggested fix is to remove the redundant | ||
| condition variable and keep the other side of the ``&&``. In the second case | ||
| (logical "or") the whole ``if`` is removed similarily to the simple case on the | ||
| top. | ||
|
|
||
| The condition of the outer ``if`` statement may also be a logical "and" (``&&``) | ||
| expression: | ||
|
|
||
| .. code-block:: c | ||
| bool onFire = isBurning(); | ||
| if (onFire && fireFighters < 10) { | ||
| if (someOtherCondition()) { | ||
| if (onFire) | ||
| scream(); | ||
| } | ||
| } | ||
| The error is also detected if both the outer statement is a logical "and" | ||
| (``&&``) and the inner statement is a logical "and" (``&&``) or "or" (``||``). | ||
| The inner ``if`` statement does not have to be a direct descendant of the outer | ||
| one. | ||
|
|
||
| No error is detected if the condition variable may have been changed between the | ||
| two checks: | ||
|
|
||
| .. code-block:: c | ||
| bool onFire = isBurning(); | ||
| if (onFire) { | ||
| tryToExtinguish(onFire); | ||
| if (onFire && peopleInTheBuilding > 0) | ||
| scream(); | ||
| } | ||
| Every possible change is considered, thus if the condition variable is not | ||
| a local variable of the function, it is a volatile or it has an alias (pointer | ||
| or reference) then no warning is issued. | ||
|
|
||
| Known limitations | ||
| ^^^^^^^^^^^^^^^^^ | ||
|
|
||
| The ``else`` branch is not checked currently for negated condition variable: | ||
|
|
||
| bool onFire = isBurning(); | ||
| if (onFire) { | ||
| scream(); | ||
| } else { | ||
| if (!onFire) { | ||
| continueWork(); | ||
| } | ||
| } | ||
|
|
||
| The checker currently only detects redundant checking of single condition | ||
| variables. More complex expressions are not checked: | ||
|
|
||
| .. code-block:: c | ||
| if (peopleInTheBuilding == 1) { | ||
| if (peopleInTheBuilding == 1) { | ||
| doSomething(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| .. title:: clang-tidy - cppcoreguidelines-prefer-member-initializer | ||
|
|
||
| cppcoreguidelines-prefer-member-initializer | ||
| =========================================== | ||
|
|
||
| Finds member initializations in the constructor body which can be converted | ||
| into member initializers of the constructor instead. This not only improves | ||
| the readability of the code but also positively affects its performance. | ||
| Class-member assignments inside a control statement or following the first | ||
| control statement are ignored. | ||
|
|
||
| This check implements `C.49 <https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c49-prefer-initialization-to-assignment-in-constructors>`_ from the CppCoreGuidelines. | ||
|
|
||
| If the language version is `C++ 11` or above, the constructor is the default | ||
| constructor of the class, the field is not a bitfield (only in case of earlier | ||
| language version than `C++ 20`), furthermore the assigned value is a literal, | ||
| negated literal or ``enum`` constant then the preferred place of the | ||
| initialization is at the class member declaration. | ||
|
|
||
| This latter rule is `C.48 <https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c48-prefer-in-class-initializers-to-member-initializers-in-constructors-for-constant-initializers>`_ from CppCoreGuidelines. | ||
|
|
||
| Please note, that this check does not enforce this latter rule for | ||
| initializations already implemented as member initializers. For that purpose | ||
| see check `modernize-use-default-member-init <modernize-use-default-member-init.html>`_. | ||
|
|
||
| Example 1 | ||
| --------- | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| class C { | ||
| int n; | ||
| int m; | ||
| public: | ||
| C() { | ||
| n = 1; // Literal in default constructor | ||
| if (dice()) | ||
| return; | ||
| m = 1; | ||
| } | ||
| }; | ||
|
|
||
| Here ``n`` can be initialized using a default member initializer, unlike | ||
| ``m``, as ``m``'s initialization follows a control statement (``if``): | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| class C { | ||
| int n{1}; | ||
| int m; | ||
| public: | ||
| C() { | ||
| if (dice()) | ||
| return; | ||
| m = 1; | ||
| } | ||
|
|
||
| Example 2 | ||
| --------- | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| class C { | ||
| int n; | ||
| int m; | ||
| public: | ||
| C(int nn, int mm) { | ||
| n = nn; // Neither default constructor nor literal | ||
| if (dice()) | ||
| return; | ||
| m = mm; | ||
| } | ||
| }; | ||
|
|
||
| Here ``n`` can be initialized in the constructor initialization list, unlike | ||
| ``m``, as ``m``'s initialization follows a control statement (``if``): | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| C(int nn, int mm) : n(nn) { | ||
| if (dice()) | ||
| return; | ||
| m = mm; | ||
| } | ||
|
|
||
| .. option:: UseAssignment | ||
|
|
||
| If this option is set to non-zero (default is `0`), the check will initialize | ||
| members with an assignment. In this case the fix of the first example looks | ||
| like this: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| class C { | ||
| int n = 1; | ||
| int m; | ||
| public: | ||
| C() { | ||
| if (dice()) | ||
| return; | ||
| m = 1; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // RUN: %check_clang_tidy %s cppcoreguidelines-prefer-member-initializer,modernize-use-default-member-init %t -- \ | ||
| // RUN: -config="{CheckOptions: [{key: modernize-use-default-member-init.UseAssignment, value: 1}]}" | ||
|
|
||
| class Simple1 { | ||
| int n; | ||
| // CHECK-FIXES: int n = 0; | ||
| double x; | ||
| // CHECK-FIXES: double x = 0.0; | ||
|
|
||
| public: | ||
| Simple1() { | ||
| n = 0; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in an in-class default member initializer [cppcoreguidelines-prefer-member-initializer] | ||
| // CHECK-FIXES: {{^\ *$}} | ||
| x = 0.0; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x' should be initialized in an in-class default member initializer [cppcoreguidelines-prefer-member-initializer] | ||
| // CHECK-FIXES: {{^\ *$}} | ||
| } | ||
|
|
||
| Simple1(int nn, double xx) { | ||
| // CHECK-FIXES: Simple1(int nn, double xx) : n(nn), x(xx) { | ||
| n = nn; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] | ||
| // CHECK-FIXES: {{^\ *$}} | ||
| x = xx; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] | ||
| // CHECK-FIXES: {{^\ *$}} | ||
| } | ||
|
|
||
| ~Simple1() = default; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // RUN: %check_clang_tidy %s cppcoreguidelines-prefer-member-initializer,modernize-use-default-member-init %t | ||
|
|
||
| class Simple1 { | ||
| int n; | ||
| // CHECK-FIXES: int n{0}; | ||
| double x; | ||
| // CHECK-FIXES: double x{0.0}; | ||
|
|
||
| public: | ||
| Simple1() { | ||
| n = 0; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in an in-class default member initializer [cppcoreguidelines-prefer-member-initializer] | ||
| // CHECK-FIXES: {{^\ *$}} | ||
| x = 0.0; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x' should be initialized in an in-class default member initializer [cppcoreguidelines-prefer-member-initializer] | ||
| // CHECK-FIXES: {{^\ *$}} | ||
| } | ||
|
|
||
| Simple1(int nn, double xx) { | ||
| // CHECK-FIXES: Simple1(int nn, double xx) : n(nn), x(xx) { | ||
| n = nn; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] | ||
| // CHECK-FIXES: {{^\ *$}} | ||
| x = xx; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] | ||
| // CHECK-FIXES: {{^\ *$}} | ||
| } | ||
|
|
||
| ~Simple1() = default; | ||
| }; |