diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -13,6 +13,7 @@ NarrowingConversionsCheck.cpp NoMallocCheck.cpp OwningMemoryCheck.cpp + PreferMemberInitializerCheck.cpp ProBoundsArrayToPointerDecayCheck.cpp ProBoundsConstantArrayIndexCheck.cpp ProBoundsPointerArithmeticCheck.cpp diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -22,6 +22,7 @@ #include "NarrowingConversionsCheck.h" #include "NoMallocCheck.h" #include "OwningMemoryCheck.h" +#include "PreferMemberInitializerCheck.h" #include "ProBoundsArrayToPointerDecayCheck.h" #include "ProBoundsConstantArrayIndexCheck.h" #include "ProBoundsPointerArithmeticCheck.h" @@ -66,6 +67,8 @@ "cppcoreguidelines-non-private-member-variables-in-classes"); CheckFactories.registerCheck( "cppcoreguidelines-owning-memory"); + CheckFactories.registerCheck( + "cppcoreguidelines-prefer-member-initializer"); CheckFactories.registerCheck( "cppcoreguidelines-pro-bounds-array-to-pointer-decay"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.h @@ -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 diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp @@ -0,0 +1,246 @@ +//===--- 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; +} + +static const std::pair +isAssignmentToMemberOf(const RecordDecl *Rec, const Stmt *S) { + 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(Field, BO->getRHS()->IgnoreParenImpCasts()); + } 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(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("ctor"); + const auto *Body = cast(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(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(Class->getDeclContext()) || + !cast(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 diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -85,6 +85,12 @@ Finds structs that are inefficiently packed or aligned, and recommends packing and/or aligning of said structs as needed. +- New :doc:`cppcoreguidelines-prefer-member-initializer + ` check. + + Finds member initializations in the constructor body which can be placed into + the initialization list instead. + - New :doc:`bugprone-misplaced-pointer-arithmetic-in-alloc ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-prefer-member-initializer.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-prefer-member-initializer.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-prefer-member-initializer.rst @@ -0,0 +1,103 @@ +.. 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 `_ 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 `_ 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 `_. + +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; + } + }; diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer-modernize-use-default-member-init-assignment.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer-modernize-use-default-member-init-assignment.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer-modernize-use-default-member-init-assignment.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer-modernize-use-default-member-init.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer-modernize-use-default-member-init.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer-modernize-use-default-member-init.cpp @@ -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; +}; diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-prefer-member-initializer.cpp @@ -0,0 +1,490 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-prefer-member-initializer %t -- -- -fcxx-exceptions + +extern void __assert_fail (__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__ ((__noreturn__)); +#define assert(expr) \ + ((expr) ? (void)(0) : __assert_fail (#expr, __FILE__, __LINE__, __func__)) + +class Simple1 { + int n; + double x; + +public: + Simple1() { + // CHECK-FIXES: Simple1() : n(0), x(0.0) { + n = 0; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + x = 0.0; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x' should be initialized in a member initializer of the constructor [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; +}; + +class Simple2 { + int n; + double x; + +public: + Simple2() : n(0) { + // CHECK-FIXES: Simple2() : n(0), x(0.0) { + x = 0.0; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + } + + Simple2(int nn, double xx) : n(nn) { + // CHECK-FIXES: Simple2(int nn, double xx) : n(nn), x(xx) { + 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: {{^\ *$}} + } + + ~Simple2() = default; +}; + +class Simple3 { + int n; + double x; + +public: + Simple3() : x(0.0) { + // CHECK-FIXES: Simple3() : n(0), x(0.0) { + n = 0; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + } + + Simple3(int nn, double xx) : x(xx) { + // CHECK-FIXES: Simple3(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: {{^\ *$}} + } + + ~Simple3() = default; +}; + +int something_int(); +double something_double(); + +class Simple4 { + int n; + +public: + Simple4() { + // CHECK-FIXES: Simple4() : n(something_int()) { + n = something_int(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + } + + ~Simple4() = default; +}; + +static bool dice(); + +class Complex1 { + int n; + int m; + +public: + Complex1() : n(0) { + if (dice()) + m = 1; + // NO-MESSAGES: initialization of 'm' is nested in a conditional expression + } + + ~Complex1() = default; +}; + +class Complex2 { + int n; + int m; + +public: + Complex2() : n(0) { + if (!dice()) + return; + m = 1; + // NO-MESSAGES: initialization of 'm' follows a conditional expression + } + + ~Complex2() = default; +}; + +class Complex3 { + int n; + int m; + +public: + Complex3() : n(0) { + while (dice()) + m = 1; + // NO-MESSAGES: initialization of 'm' is nested in a conditional loop + } + + ~Complex3() = default; +}; + +class Complex4 { + int n; + int m; + +public: + Complex4() : n(0) { + while (!dice()) + return; + m = 1; + // NO-MESSAGES: initialization of 'm' follows a conditional loop + } + + ~Complex4() = default; +}; + +class Complex5 { + int n; + int m; + +public: + Complex5() : n(0) { + do { + m = 1; + // NO-MESSAGES: initialization of 'm' is nested in a conditional loop + } while (dice()); + } + + ~Complex5() = default; +}; + +class Complex6 { + int n; + int m; + +public: + Complex6() : n(0) { + do { + return; + } while (!dice()); + m = 1; + // NO-MESSAGES: initialization of 'm' follows a conditional loop + } + + ~Complex6() = default; +}; + +class Complex7 { + int n; + int m; + +public: + Complex7() : n(0) { + for (int i = 2; i < 1; ++i) { + m = 1; + } + // NO-MESSAGES: initialization of 'm' is nested into a conditional loop + } + + ~Complex7() = default; +}; + +class Complex8 { + int n; + int m; + +public: + Complex8() : n(0) { + for (int i = 0; i < 2; ++i) { + return; + } + m = 1; + // NO-MESSAGES: initialization of 'm' follows a conditional loop + } + + ~Complex8() = default; +}; + +class Complex9 { + int n; + int m; + +public: + Complex9() : n(0) { + switch (dice()) { + case 1: + m = 1; + // NO-MESSAGES: initialization of 'm' is nested in a conditional expression + break; + default: + break; + } + } + + ~Complex9() = default; +}; + +class Complex10 { + int n; + int m; + +public: + Complex10() : n(0) { + switch (dice()) { + case 1: + return; + break; + default: + break; + } + m = 1; + // NO-MESSAGES: initialization of 'm' follows a conditional expression + } + + ~Complex10() = default; +}; + +class E {}; +int risky(); // may throw + +class Complex11 { + int n; + int m; + +public: + Complex11() : n(0) { + try { + risky(); + m = 1; + // NO-MESSAGES: initialization of 'm' follows is nested in a try-block + } catch (const E& e) { + return; + } + } + + ~Complex11() = default; +}; + +class Complex12 { + int n; + int m; + +public: + Complex12() : n(0) { + try { + risky(); + } catch (const E& e) { + return; + } + m = 1; + // NO-MESSAGES: initialization of 'm' follows a try-block + } + + ~Complex12() = default; +}; + +class Complex13 { + int n; + int m; + +public: + Complex13() : n(0) { + return; + m = 1; + // NO-MESSAGES: initialization of 'm' follows a return statement + } + + ~Complex13() = default; +}; + +class Complex14 { + int n; + int m; + +public: + Complex14() : n(0) { + goto X; + m = 1; + // NO-MESSAGES: initialization of 'm' follows a goto statement + X: + ; + } + + ~Complex14() = default; +}; + +void returning(); + +class Complex15 { + int n; + int m; + +public: + Complex15() : n(0) { + // CHECK-FIXES: Complex15() : n(0), m(1) { + returning(); + m = 1; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'm' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + } + + ~Complex15() = default; +}; + +[[noreturn]] void not_returning(); + +class Complex16 { + int n; + int m; + +public: + Complex16() : n(0) { + not_returning(); + m = 1; + // NO-MESSAGES: initialization of 'm' follows a non-returning function call + } + + ~Complex16() = default; +}; + +class Complex17 { + int n; + int m; + +public: + Complex17() : n(0) { + throw 1; + m = 1; + // NO-MESSAGES: initialization of 'm' follows a 'throw' statement; + } + + ~Complex17() = default; +}; + +class Complex18 { + int n; + +public: + Complex18() try { + n = risky(); + // NO-MESSAGES: initialization of 'n' in a 'try' body; + } catch (const E& e) { + n = 0; + } + + ~Complex18() = default; +}; + +class Complex19 { + int n; +public: + Complex19() { + // CHECK-FIXES: Complex19() : n(0) { + n = 0; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + } + + explicit Complex19(int) { + // CHECK-FIXES: Complex19(int) : n(12) { + n = 12; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + } + + ~Complex19() = default; +}; + +class Complex20 { + int n; + int m; + +public: + Complex20(int k) : n(0) { + assert(k > 0); + m = 1; + // NO-MESSAGES: initialization of 'm' follows an assertion + } + + ~Complex20() = default; +}; + +class VeryComplex1 { + int n1, n2, n3; + double x1, x2, x3; + int n4, n5, n6; + double x4, x5, x6; + + VeryComplex1() : n3(something_int()), x3(something_double()), + n5(something_int()), x4(something_double()), + x5(something_double()) { + // CHECK-FIXES: VeryComplex1() : n2(something_int()), n1(something_int()), n3(something_int()), x2(something_double()), x1(something_double()), x3(something_double()), + // CHECK-FIXES: n4(something_int()), n5(something_int()), n6(something_int()), x4(something_double()), + // CHECK-FIXES: x5(something_double()), x6(something_double()) { + +// FIXME: Order of elements on the constructor initializer list should match +// the order of the declaration of the fields. Thus the correct fixes +// should look like these: +// + // C ECK-FIXES: VeryComplex1() : n2(something_int()), n1(something_int()), n3(something_int()), x2(something_double()), x1(something_double()), x3(something_double()), + // C ECK-FIXES: n4(something_int()), n5(something_int()), n6(something_int()), x4(something_double()), + // C ECK-FIXES: x5(something_double()), x6(something_double()) { +// +// However, the Diagnostics Engine processes fixes in the order of the +// diagnostics and insertions to the same position are handled in left to +// right order thus in the case two adjacent fields are initialized +// inside the constructor in reverse order the provided fix is a +// constructor initializer list that does not match the order of the +// declaration of the fields. + + x2 = something_double(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x2' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + n2 = something_int(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n2' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + x6 = something_double(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x6' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + x1 = something_double(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'x1' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + n6 = something_int(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n6' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + n1 = something_int(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n1' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + n4 = something_int(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: 'n4' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + } +}; + +struct Outside { + int n; + double x; + Outside(); +}; + +Outside::Outside() { + // CHECK-FIXES: Outside::Outside() : n(1), x(1.0) { + n = 1; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'n' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} + x = 1.0; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: 'x' should be initialized in a member initializer of the constructor [cppcoreguidelines-prefer-member-initializer] + // CHECK-FIXES: {{^\ *$}} +}