Skip to content

Commit 2637467

Browse files
authored
[clang-tidy]Fix PreferMemberInitializer false positive for reassignment (#70316)
- assignment twice cannot be simplified to once when assigning to reference type - safe assignment cannot be advanced before unsafe assignment
1 parent 7c90be2 commit 2637467

File tree

3 files changed

+80
-19
lines changed

3 files changed

+80
-19
lines changed

clang-tools-extra/clang-tidy/cppcoreguidelines/PreferMemberInitializerCheck.cpp

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
#include "PreferMemberInitializerCheck.h"
1010
#include "clang/AST/ASTContext.h"
11+
#include "clang/AST/Decl.h"
1112
#include "clang/ASTMatchers/ASTMatchFinder.h"
1213
#include "clang/Lex/Lexer.h"
14+
#include "llvm/ADT/DenseMap.h"
1315

1416
using namespace clang::ast_matchers;
1517

@@ -54,31 +56,66 @@ static bool shouldBeDefaultMemberInitializer(const Expr *Value) {
5456
}
5557

5658
namespace {
59+
5760
AST_MATCHER_P(FieldDecl, indexNotLessThan, unsigned, Index) {
5861
return Node.getFieldIndex() >= Index;
5962
}
63+
64+
enum class AssignedLevel {
65+
// Field is not assigned.
66+
None,
67+
// Field is assigned.
68+
Default,
69+
// Assignment of field has side effect:
70+
// - assign to reference.
71+
// FIXME: support other side effect.
72+
HasSideEffect,
73+
// Assignment of field has data dependence.
74+
HasDependence,
75+
};
76+
6077
} // namespace
6178

79+
static bool canAdvanceAssignment(AssignedLevel Level) {
80+
return Level == AssignedLevel::None || Level == AssignedLevel::Default;
81+
}
82+
6283
// Checks if Field is initialised using a field that will be initialised after
6384
// it.
6485
// TODO: Probably should guard against function calls that could have side
65-
// effects or if they do reference another field that's initialized before this
66-
// field, but is modified before the assignment.
67-
static bool isSafeAssignment(const FieldDecl *Field, const Expr *Init,
68-
const CXXConstructorDecl *Context) {
86+
// effects or if they do reference another field that's initialized before
87+
// this field, but is modified before the assignment.
88+
static void updateAssignmentLevel(
89+
const FieldDecl *Field, const Expr *Init, const CXXConstructorDecl *Ctor,
90+
llvm::DenseMap<const FieldDecl *, AssignedLevel> &AssignedFields) {
91+
auto It = AssignedFields.find(Field);
92+
if (It == AssignedFields.end())
93+
It = AssignedFields.insert({Field, AssignedLevel::None}).first;
94+
95+
if (!canAdvanceAssignment(It->second))
96+
// fast path for already decided field.
97+
return;
98+
99+
if (Field->getType().getCanonicalType()->isReferenceType()) {
100+
// assign to reference type twice cannot be simplified to once.
101+
It->second = AssignedLevel::HasSideEffect;
102+
return;
103+
}
69104

70105
auto MemberMatcher =
71106
memberExpr(hasObjectExpression(cxxThisExpr()),
72107
member(fieldDecl(indexNotLessThan(Field->getFieldIndex()))));
73-
74108
auto DeclMatcher = declRefExpr(
75-
to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Context)))));
76-
77-
return match(expr(anyOf(MemberMatcher, DeclMatcher,
78-
hasDescendant(MemberMatcher),
79-
hasDescendant(DeclMatcher))),
80-
*Init, Field->getASTContext())
81-
.empty();
109+
to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor)))));
110+
const bool HasDependence = !match(expr(anyOf(MemberMatcher, DeclMatcher,
111+
hasDescendant(MemberMatcher),
112+
hasDescendant(DeclMatcher))),
113+
*Init, Field->getASTContext())
114+
.empty();
115+
if (HasDependence) {
116+
It->second = AssignedLevel::HasDependence;
117+
return;
118+
}
82119
}
83120

84121
static std::pair<const FieldDecl *, const Expr *>
@@ -99,9 +136,9 @@ isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S,
99136
if (!isa<CXXThisExpr>(ME->getBase()))
100137
return std::make_pair(nullptr, nullptr);
101138
const Expr *Init = BO->getRHS()->IgnoreParenImpCasts();
102-
if (isSafeAssignment(Field, Init, Ctor))
103-
return std::make_pair(Field, Init);
104-
} else if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) {
139+
return std::make_pair(Field, Init);
140+
}
141+
if (const auto *COCE = dyn_cast<CXXOperatorCallExpr>(S)) {
105142
if (COCE->getOperator() != OO_Equal)
106143
return std::make_pair(nullptr, nullptr);
107144

@@ -117,10 +154,8 @@ isAssignmentToMemberOf(const CXXRecordDecl *Rec, const Stmt *S,
117154
if (!isa<CXXThisExpr>(ME->getBase()))
118155
return std::make_pair(nullptr, nullptr);
119156
const Expr *Init = COCE->getArg(1)->IgnoreParenImpCasts();
120-
if (isSafeAssignment(Field, Init, Ctor))
121-
return std::make_pair(Field, Init);
157+
return std::make_pair(Field, Init);
122158
}
123-
124159
return std::make_pair(nullptr, nullptr);
125160
}
126161

@@ -156,6 +191,12 @@ void PreferMemberInitializerCheck::check(
156191
const CXXRecordDecl *Class = Ctor->getParent();
157192
bool FirstToCtorInits = true;
158193

194+
llvm::DenseMap<const FieldDecl *, AssignedLevel> AssignedFields{};
195+
196+
for (const CXXCtorInitializer *Init : Ctor->inits())
197+
if (FieldDecl *Field = Init->getMember())
198+
updateAssignmentLevel(Field, Init->getInit(), Ctor, AssignedFields);
199+
159200
for (const Stmt *S : Body->body()) {
160201
if (S->getBeginLoc().isMacroID()) {
161202
StringRef MacroName = Lexer::getImmediateMacroName(
@@ -180,6 +221,9 @@ void PreferMemberInitializerCheck::check(
180221
std::tie(Field, InitValue) = isAssignmentToMemberOf(Class, S, Ctor);
181222
if (!Field)
182223
continue;
224+
updateAssignmentLevel(Field, InitValue, Ctor, AssignedFields);
225+
if (!canAdvanceAssignment(AssignedFields[Field]))
226+
continue;
183227
const bool IsInDefaultMemberInitializer =
184228
IsUseDefaultMemberInitEnabled && getLangOpts().CPlusPlus11 &&
185229
Ctor->isDefaultConstructor() &&

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ Changes in existing checks
256256

257257
- Improved :doc:`cppcoreguidelines-prefer-member-initializer
258258
<clang-tidy/checks/cppcoreguidelines/prefer-member-initializer>` check to
259-
ignore delegate constructors.
259+
ignore delegate constructors and ignore re-assignment for reference or when
260+
initialization depend on field that is initialized before.
260261

261262
- Improved :doc:`cppcoreguidelines-pro-bounds-array-to-pointer-decay
262263
<clang-tidy/checks/cppcoreguidelines/pro-bounds-array-to-pointer-decay>` check

clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/prefer-member-initializer.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,3 +570,19 @@ struct PR52818 {
570570

571571
int bar;
572572
};
573+
574+
struct RefReassignment {
575+
RefReassignment(int &i) : m_i{i} {
576+
m_i = 1;
577+
}
578+
int & m_i;
579+
};
580+
581+
struct ReassignmentAfterUnsafetyAssignment {
582+
ReassignmentAfterUnsafetyAssignment() {
583+
int a = 10;
584+
m_i = a;
585+
m_i = 1;
586+
}
587+
int m_i;
588+
};

0 commit comments

Comments
 (0)