Skip to content

Commit 6dc5c76

Browse files
chrchr-githubweb-flowdanmar
authored
Fix #14546 FN uninitMemberVar (in-class initializers but no constructor) (#8274)
Co-authored-by: chrchr-github <noreply@github.com> Co-authored-by: Daniel Marjamäki <daniel.marjamaki@gmail.com>
1 parent aec1a7f commit 6dc5c76

6 files changed

Lines changed: 161 additions & 89 deletions

File tree

lib/checkclass.cpp

Lines changed: 136 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,90 @@ CheckClass::CheckClass(const Tokenizer *tokenizer, const Settings *settings, Err
116116
mSymbolDatabase(tokenizer?tokenizer->getSymbolDatabase():nullptr)
117117
{}
118118

119+
bool CheckClass::isInitialized(const CheckClass::Usage& usage, FunctionType funcType) const
120+
{
121+
const Variable& var = *usage.var;
122+
123+
if (usage.assign || usage.init || var.isStatic())
124+
return true;
125+
126+
if (!var.nameToken() || var.nameToken()->isAnonymous())
127+
return true;
128+
129+
if (var.valueType() && var.valueType()->pointer == 0 && var.type() && var.type()->needInitialization == Type::NeedInitialization::False && var.type()->derivedFrom.empty())
130+
return true;
131+
132+
if (var.isConst() && funcType == FunctionType::eOperatorEqual) // We can't set const members in assignment operator
133+
return true;
134+
135+
// Check if this is a class constructor
136+
if (!var.isPointer() && !var.isPointerArray() && var.isClass() && funcType == FunctionType::eConstructor) {
137+
// Unknown type so assume it is initialized
138+
if (!var.type()) {
139+
if (var.isStlType() && var.valueType() && var.valueType()->containerTypeToken) {
140+
if (var.valueType()->type == ValueType::Type::ITERATOR)
141+
{
142+
// needs initialization
143+
}
144+
else if (var.getTypeName() == "std::array") {
145+
const Token* ctt = var.valueType()->containerTypeToken;
146+
if (!ctt->isStandardType() &&
147+
(!ctt->type() || ctt->type()->needInitialization != Type::NeedInitialization::True) &&
148+
!mSettings->library.podtype(ctt->str())) // TODO: handle complex type expression
149+
return true;
150+
}
151+
else
152+
return true;
153+
}
154+
else
155+
return true;
156+
}
157+
158+
// Known type that doesn't need initialization or
159+
// known type that has member variables of an unknown type
160+
else if (var.type()->needInitialization != Type::NeedInitialization::True)
161+
return true;
162+
}
163+
164+
// Check if type can't be copied
165+
if (!var.isPointer() && !var.isPointerArray() && var.typeScope()) {
166+
if (funcType == FunctionType::eMoveConstructor) {
167+
if (canNotMove(var.typeScope()))
168+
return true;
169+
}
170+
else {
171+
if (canNotCopy(var.typeScope()))
172+
return true;
173+
}
174+
}
175+
return false;
176+
}
177+
178+
void CheckClass::handleUnionMembers(std::vector<Usage>& usageList)
179+
{
180+
// Assign 1 union member => assign all union members
181+
for (const Usage& usage : usageList) {
182+
const Variable& var = *usage.var;
183+
if (!usage.assign && !usage.init)
184+
continue;
185+
const Scope* varScope1 = var.nameToken()->scope();
186+
while (varScope1->type == ScopeType::eStruct)
187+
varScope1 = varScope1->nestedIn;
188+
if (varScope1->type == ScopeType::eUnion) {
189+
for (Usage& usage2 : usageList) {
190+
const Variable& var2 = *usage2.var;
191+
if (usage2.assign || usage2.init || var2.isStatic())
192+
continue;
193+
const Scope* varScope2 = var2.nameToken()->scope();
194+
while (varScope2->type == ScopeType::eStruct)
195+
varScope2 = varScope2->nestedIn;
196+
if (varScope1 == varScope2)
197+
usage2.assign = true;
198+
}
199+
}
200+
}
201+
}
202+
119203
//---------------------------------------------------------------------------
120204
// ClassCheck: Check that all class constructors are ok.
121205
//---------------------------------------------------------------------------
@@ -145,6 +229,7 @@ void CheckClass::constructors()
145229
});
146230

147231
// There are no constructors.
232+
std::set<const Variable*> diagVars;
148233
if (scope->numConstructors == 0 && printStyle && !usedInUnion) {
149234
// If there is a private variable, there should be a constructor..
150235
int needInit = 0, haveInit = 0;
@@ -163,8 +248,10 @@ void CheckClass::constructors()
163248
if (haveInit == 0)
164249
noConstructorError(scope->classDef, scope->className, scope->classDef->str() == "struct");
165250
else
166-
for (const Variable* uv : uninitVars)
251+
for (const Variable* uv : uninitVars) {
167252
uninitVarError(uv->typeStartToken(), uv->scope()->className, uv->name());
253+
diagVars.emplace(uv);
254+
}
168255
}
169256
}
170257

@@ -201,85 +288,15 @@ void CheckClass::constructors()
201288
std::list<const Function *> callstack;
202289
initializeVarList(func, callstack, scope, usageList);
203290

204-
// Assign 1 union member => assign all union members
205-
for (const Usage &usage : usageList) {
206-
const Variable& var = *usage.var;
207-
if (!usage.assign && !usage.init)
208-
continue;
209-
const Scope* varScope1 = var.nameToken()->scope();
210-
while (varScope1->type == ScopeType::eStruct)
211-
varScope1 = varScope1->nestedIn;
212-
if (varScope1->type == ScopeType::eUnion) {
213-
for (Usage &usage2 : usageList) {
214-
const Variable& var2 = *usage2.var;
215-
if (usage2.assign || usage2.init || var2.isStatic())
216-
continue;
217-
const Scope* varScope2 = var2.nameToken()->scope();
218-
while (varScope2->type == ScopeType::eStruct)
219-
varScope2 = varScope2->nestedIn;
220-
if (varScope1 == varScope2)
221-
usage2.assign = true;
222-
}
223-
}
224-
}
291+
handleUnionMembers(usageList);
225292

226293
// Check if any variables are uninitialized
227294
for (const Usage &usage : usageList) {
228-
const Variable& var = *usage.var;
229-
230-
if (usage.assign || usage.init || var.isStatic())
231-
continue;
232-
233-
if (!var.nameToken() || var.nameToken()->isAnonymous())
234-
continue;
235-
236-
if (var.valueType() && var.valueType()->pointer == 0 && var.type() && var.type()->needInitialization == Type::NeedInitialization::False && var.type()->derivedFrom.empty())
237-
continue;
238-
239-
if (var.isConst() && func.isOperator()) // We can't set const members in assignment operator
295+
if (isInitialized(usage, func.type))
240296
continue;
241297

242-
// Check if this is a class constructor
243-
if (!var.isPointer() && !var.isPointerArray() && var.isClass() && func.type == FunctionType::eConstructor) {
244-
// Unknown type so assume it is initialized
245-
if (!var.type()) {
246-
if (var.isStlType() && var.valueType() && var.valueType()->containerTypeToken) {
247-
if (var.valueType()->type == ValueType::Type::ITERATOR)
248-
{
249-
// needs initialization
250-
}
251-
else if (var.getTypeName() == "std::array") {
252-
const Token* ctt = var.valueType()->containerTypeToken;
253-
if (!ctt->isStandardType() &&
254-
(!ctt->type() || ctt->type()->needInitialization != Type::NeedInitialization::True) &&
255-
!mSettings->library.podtype(ctt->str())) // TODO: handle complex type expression
256-
continue;
257-
}
258-
else
259-
continue;
260-
}
261-
else
262-
continue;
263-
}
264-
265-
// Known type that doesn't need initialization or
266-
// known type that has member variables of an unknown type
267-
else if (var.type()->needInitialization != Type::NeedInitialization::True)
268-
continue;
269-
}
270-
271-
// Check if type can't be copied
272-
if (!var.isPointer() && !var.isPointerArray() && var.typeScope()) {
273-
if (func.type == FunctionType::eMoveConstructor) {
274-
if (canNotMove(var.typeScope()))
275-
continue;
276-
} else {
277-
if (canNotCopy(var.typeScope()))
278-
continue;
279-
}
280-
}
281-
282298
// Is there missing member copy in copy/move constructor or assignment operator?
299+
const Variable& var = *usage.var;
283300
bool missingCopy = false;
284301

285302
// Don't warn about unknown types in copy constructors since we
@@ -326,6 +343,38 @@ void CheckClass::constructors()
326343
}
327344
}
328345
}
346+
347+
if (scope->numConstructors == 0) {
348+
349+
// Mark all variables not used
350+
clearAllVar(usageList);
351+
352+
// Variables with default initializers
353+
bool hasAnyDefaultInit = false;
354+
for (Usage& usage : usageList) {
355+
const Variable& var = *usage.var;
356+
357+
// check for C++11 initializer
358+
if (var.hasDefault()) {
359+
usage.init = true;
360+
hasAnyDefaultInit = true;
361+
}
362+
}
363+
if (!hasAnyDefaultInit)
364+
continue;
365+
366+
handleUnionMembers(usageList);
367+
368+
// Check if any variables are uninitialized
369+
for (const Usage& usage : usageList) {
370+
if (isInitialized(usage, FunctionType::eConstructor))
371+
continue;
372+
373+
const Variable& var = *usage.var;
374+
if (diagVars.count(&var) == 0)
375+
uninitVarError(scope->bodyStart, false, FunctionType::eConstructor, var.scope()->className, var.name(), false, false, true);
376+
}
377+
}
329378
}
330379
}
331380

@@ -1118,17 +1167,22 @@ void CheckClass::noExplicitConstructorError(const Token *tok, const std::string
11181167
reportError(tok, Severity::style, "noExplicitConstructor", "$symbol:" + classname + '\n' + message + '\n' + verbose, CWE398, Certainty::normal);
11191168
}
11201169

1121-
void CheckClass::uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive)
1170+
void CheckClass::uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive, bool noCtor)
11221171
{
1123-
std::string ctor;
1124-
if (functionType == FunctionType::eCopyConstructor)
1125-
ctor = "copy ";
1126-
else if (functionType == FunctionType::eMoveConstructor)
1127-
ctor = "move ";
1128-
std::string message("Member variable '$symbol' is not initialized in the " + ctor + "constructor.");
1172+
std::string message("Member variable '$symbol' ");
1173+
if (noCtor)
1174+
message += "has no initializer.";
1175+
else {
1176+
message += "is not initialized in the ";
1177+
if (functionType == FunctionType::eCopyConstructor)
1178+
message += "copy ";
1179+
else if (functionType == FunctionType::eMoveConstructor)
1180+
message += "move ";
1181+
message += "constructor.";
1182+
}
11291183
if (derived)
11301184
message += " Maybe it should be initialized directly in the class " + classname + "?";
1131-
std::string id = std::string("uninit") + (derived ? "Derived" : "") + "MemberVar" + (isprivate ? "Private" : "");
1185+
std::string id = std::string("uninit") + (derived ? "Derived" : "") + "MemberVar" + (isprivate ? "Private" : "") + (noCtor ? "NoCtor" : "");
11321186
const std::string verbose {message + " Member variables of native types, pointers, or references are left uninitialized when the class is instantiated. That may cause bugs or undefined behavior."};
11331187
reportError(tok, Severity::warning, id, "$symbol:" + classname + "::" + varname + '\n' + message + '\n' + verbose, CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal);
11341188
}

lib/checkclass.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ class CPPCHECKLIB CheckClass : public Check {
155155
void noCopyConstructorError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive);
156156
void noOperatorEqError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive);
157157
void noDestructorError(const Scope *scope, bool isdefault, const Token *alloc);
158-
void uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive);
158+
void uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive, bool noCtor = false);
159159
void uninitVarError(const Token *tok, const std::string &classname, const std::string &varname);
160160
void missingMemberCopyError(const Token *tok, FunctionType functionType, const std::string& classname, const std::string& varname);
161161
void operatorEqVarError(const Token *tok, const std::string &classname, const std::string &varname, bool inconclusive);
@@ -254,6 +254,10 @@ class CPPCHECKLIB CheckClass : public Check {
254254
bool init{};
255255
};
256256

257+
static void handleUnionMembers(std::vector<Usage>& usageList);
258+
259+
bool isInitialized(const Usage& usage, FunctionType funcType) const;
260+
257261
static bool isBaseClassMutableMemberFunc(const Token *tok, const Scope *scope);
258262

259263
/**

lib/pathmatch.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,9 @@ class PathMatch::PathIterator {
274274
/* Position struct */
275275
struct Pos {
276276
/* String pointer */
277-
const char *p;
277+
const char *p{};
278278
/* Raw characters left */
279-
std::size_t l;
279+
std::size_t l{};
280280
/* Buffered character */
281281
int c {EOF};
282282
};

lib/tokenize.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -700,12 +700,12 @@ class CPPCHECKLIB Tokenizer {
700700
std::string name;
701701
std::string originalName;
702702
std::string filename;
703-
int lineNumber;
704-
int column;
703+
int lineNumber{};
704+
int column{};
705705
int tagLine{-1};
706706
int tagColumn{-1};
707-
bool used;
708-
bool isFunctionPointer;
707+
bool used{};
708+
bool isFunctionPointer{};
709709
std::vector<TypedefToken> typedefInfoTokens;
710710
};
711711
std::vector<TypedefInfo> mTypedefInfo;

releasenotes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Major bug fixes & crashes:
77
New checks:
88
- MISRA C 2012 rule 10.3 now warns on assigning integer literals 0 and 1 to bool in C99 and later while preserving the existing C89 behavior.
99
- funcArgNamesDifferentUnnamed warns on function declarations/definitions where a parameter in either location is unnamed
10+
- uninitMemberVarNoCtor warns on user-defined types where some but not all members requiring initialization have in-class initializers.
1011

1112
C/C++ support:
1213
-

test/testconstructors.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class TestConstructors : public TestFixture {
9292
TEST_CASE(noConstructor13); // #9998
9393
TEST_CASE(noConstructor14); // #10770
9494
TEST_CASE(noConstructor15); // #5499
95+
TEST_CASE(noConstructor16);
9596

9697
TEST_CASE(forwardDeclaration); // ticket #4290/#3190
9798

@@ -758,6 +759,18 @@ class TestConstructors : public TestFixture {
758759
ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'C::i2' is not initialized in the constructor. [uninitMemberVar]\n", errout_str());
759760
}
760761

762+
void noConstructor16() {
763+
check("struct S {\n" // #14546
764+
" int a = 0, b;\n"
765+
"};\n");
766+
ASSERT_EQUALS("[test.cpp:1:10]: (warning) Member variable 'S::b' has no initializer. [uninitMemberVarNoCtor]\n", errout_str());
767+
768+
check("struct S {\n"
769+
" int a, b;\n"
770+
"};\n");
771+
ASSERT_EQUALS("", errout_str());
772+
}
773+
761774
// ticket #4290 "False Positive: style (noConstructor): The class 'foo' does not have a constructor."
762775
// ticket #3190 "SymbolDatabase: Parse of sub class constructor fails"
763776
void forwardDeclaration() {

0 commit comments

Comments
 (0)