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
12 changes: 12 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ The improvements are...
Improvements to clang-tidy
--------------------------

- New :doc:`bugprone-redundant-branch-condition
<clang-tidy/checks/bugprone-redundant-branch-condition>` check.

Finds condition variables in nested ``if`` statements that were also checked
in the outer ``if`` statement and were not changed.

- New :doc:`cppcoreguidelines-prefer-member-initializer
<clang-tidy/checks/cppcoreguidelines-prefer-member-initializer>` check.

Finds member initializations in the constructor body which can be placed into
the initialization list instead.

Changes in existing checks
^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
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;
}
4 changes: 3 additions & 1 deletion clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ Clang-Tidy Checks
`bugprone-misplaced-widening-cast <bugprone-misplaced-widening-cast.html>`_,
`bugprone-move-forwarding-reference <bugprone-move-forwarding-reference.html>`_, "Yes"
`bugprone-multiple-statement-macro <bugprone-multiple-statement-macro.html>`_,
`bugprone-no-escape <bugprone-no-escape.html>`_, "Yes"
`bugprone-no-escape <bugprone-no-escape.html>`_,
`bugprone-not-null-terminated-result <bugprone-not-null-terminated-result.html>`_, "Yes"
`bugprone-parent-virtual-call <bugprone-parent-virtual-call.html>`_, "Yes"
`bugprone-posix-return <bugprone-posix-return.html>`_, "Yes"
`bugprone-redundant-branch-condition <bugprone-redundant-branch-condition.html>`_, "Yes"
`bugprone-reserved-identifier <bugprone-reserved-identifier.html>`_, "Yes"
`bugprone-signed-char-misuse <bugprone-signed-char-misuse.html>`_,
`bugprone-sizeof-container <bugprone-sizeof-container.html>`_,
Expand Down Expand Up @@ -141,6 +142,7 @@ Clang-Tidy Checks
`cppcoreguidelines-narrowing-conversions <cppcoreguidelines-narrowing-conversions.html>`_,
`cppcoreguidelines-no-malloc <cppcoreguidelines-no-malloc.html>`_,
`cppcoreguidelines-owning-memory <cppcoreguidelines-owning-memory.html>`_,
`cppcoreguidelines-prefer-member-initializer <cppcoreguidelines-prefer-member-initializer.html>`_,
`cppcoreguidelines-pro-bounds-array-to-pointer-decay <cppcoreguidelines-pro-bounds-array-to-pointer-decay.html>`_,
`cppcoreguidelines-pro-bounds-constant-array-index <cppcoreguidelines-pro-bounds-constant-array-index.html>`_, "Yes"
`cppcoreguidelines-pro-bounds-pointer-arithmetic <cppcoreguidelines-pro-bounds-pointer-arithmetic.html>`_,
Expand Down

Large diffs are not rendered by default.

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;
};

Large diffs are not rendered by default.