Fix #12322 detect usage of partially-initialized object in constructor (#5851)

This commit is contained in:
chrchr-github 2024-01-09 12:28:22 +01:00 committed by GitHub
parent 8903f0e4b1
commit c21166565f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 86 additions and 17 deletions

View File

@ -2633,6 +2633,7 @@ namespace { // avoid one-definition-rule violation
const Variable *var; const Variable *var;
const Token *tok; const Token *tok;
std::vector<const Variable*> initArgs;
}; };
} }
@ -2663,25 +2664,38 @@ void CheckClass::initializerListOrder()
tok = tok->next(); tok = tok->next();
// find all variable initializations in list // find all variable initializations in list
while (tok && tok != func->functionScope->bodyStart) { for (; tok && tok != func->functionScope->bodyStart; tok = tok->next()) {
if (Token::Match(tok, "%name% (|{")) { if (Token::Match(tok, "%name% (|{")) {
const Variable *var = scope->getVariable(tok->str()); const Variable *var = scope->getVariable(tok->str());
if (var) if (var)
vars.emplace_back(var, tok); vars.emplace_back(var, tok);
else
continue;
if (Token::Match(tok->tokAt(2), "%name% =")) { const Token* const end = tok->next()->link();
var = scope->getVariable(tok->strAt(2)); for (; tok != end; tok = tok->next()) {
if (const Variable* argVar = scope->getVariable(tok->str())) {
if (var) if (var->isPointer() && (argVar->isArray() || Token::simpleMatch(tok->astParent(), "&")))
vars.emplace_back(var, tok->tokAt(2)); continue;
if (var->isReference())
continue;
if (Token::simpleMatch(tok->astParent(), "="))
continue;
vars.back().initArgs.emplace_back(argVar);
}
} }
tok = tok->next()->link()->next(); }
} else
tok = tok->next();
} }
// need at least 2 members to have out of order initialization for (int j = 0; j < vars.size(); j++) {
for (int j = 1; j < vars.size(); j++) { // check for use of uninitialized arguments
for (const auto& arg : vars[j].initArgs)
if (vars[j].var->index() < arg->index())
initializerListError(vars[j].tok, vars[j].var->nameToken(), scope->className, vars[j].var->name(), /*isArgument*/ true);
// need at least 2 members to have out of order initialization
if (j == 0)
continue;
// check for out of order initialization // check for out of order initialization
if (vars[j].var->index() < vars[j - 1].var->index()) if (vars[j].var->index() < vars[j - 1].var->index())
initializerListError(vars[j].tok,vars[j].var->nameToken(), scope->className, vars[j].var->name()); initializerListError(vars[j].tok,vars[j].var->nameToken(), scope->className, vars[j].var->name());
@ -2692,15 +2706,18 @@ void CheckClass::initializerListOrder()
} }
} }
void CheckClass::initializerListError(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &varname) void CheckClass::initializerListError(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &varname, bool isArgument)
{ {
std::list<const Token *> toks = { tok1, tok2 }; std::list<const Token *> toks = { tok1, tok2 };
const std::string msg = isArgument ?
"Member variable '$symbol' uses an uninitialized argument due to the order of declarations." :
"Member variable '$symbol' is in the wrong place in the initializer list.";
reportError(toks, Severity::style, "initializerList", reportError(toks, Severity::style, "initializerList",
"$symbol:" + classname + "::" + varname +"\n" "$symbol:" + classname + "::" + varname + '\n' +
"Member variable '$symbol' is in the wrong place in the initializer list.\n" msg + '\n' +
"Member variable '$symbol' is in the wrong place in the initializer list. " msg + ' ' +
"Members are initialized in the order they are declared, not in the " "Members are initialized in the order they are declared, not in the "
"order they are in the initializer list. Keeping the initializer list " "order they are in the initializer list. Keeping the initializer list "
"in the same order that the members were declared prevents order dependent " "in the same order that the members were declared prevents order dependent "
"initialization errors.", CWE398, Certainty::inconclusive); "initialization errors.", CWE398, Certainty::inconclusive);
} }

View File

@ -199,7 +199,7 @@ private:
void operatorEqToSelfError(const Token *tok); void operatorEqToSelfError(const Token *tok);
void checkConstError(const Token *tok, const std::string &classname, const std::string &funcname, bool suggestStatic); void checkConstError(const Token *tok, const std::string &classname, const std::string &funcname, bool suggestStatic);
void checkConstError2(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &funcname, bool suggestStatic); void checkConstError2(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &funcname, bool suggestStatic);
void initializerListError(const Token *tok1,const Token *tok2, const std::string & classname, const std::string &varname); void initializerListError(const Token *tok1,const Token *tok2, const std::string & classname, const std::string &varname, bool isArgument = false);
void suggestInitializationList(const Token *tok, const std::string& varname); void suggestInitializationList(const Token *tok, const std::string& varname);
void selfInitializationError(const Token* tok, const std::string& varname); void selfInitializationError(const Token* tok, const std::string& varname);
void pureVirtualFunctionCallInConstructorError(const Function * scopeFunction, const std::list<const Token *> & tokStack, const std::string &purefuncname); void pureVirtualFunctionCallInConstructorError(const Function * scopeFunction, const std::list<const Token *> & tokStack, const std::string &purefuncname);

View File

@ -215,6 +215,7 @@ private:
TEST_CASE(qualifiedNameMember); // #10872 TEST_CASE(qualifiedNameMember); // #10872
TEST_CASE(initializerListOrder); TEST_CASE(initializerListOrder);
TEST_CASE(initializerListArgument);
TEST_CASE(initializerListUsage); TEST_CASE(initializerListUsage);
TEST_CASE(selfInitialization); TEST_CASE(selfInitialization);
@ -7571,6 +7572,57 @@ private:
"};"); "};");
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:2]: (style, inconclusive) Member variable 'Fred::b' is in the wrong place in the initializer list.\n" ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:2]: (style, inconclusive) Member variable 'Fred::b' is in the wrong place in the initializer list.\n"
"[test.cpp:4] -> [test.cpp:2]: (style, inconclusive) Member variable 'Fred::a' is in the wrong place in the initializer list.\n", errout.str()); "[test.cpp:4] -> [test.cpp:2]: (style, inconclusive) Member variable 'Fred::a' is in the wrong place in the initializer list.\n", errout.str());
checkInitializerListOrder("struct S {\n"
" S() : b(a = 1) {}\n"
" int a, b;\n"
"};");
ASSERT_EQUALS("", errout.str());
}
void initializerListArgument() {
checkInitializerListOrder("struct A { A(); };\n" // #12322
"struct B { explicit B(const A* a); };\n"
"struct C {\n"
" C() : b(&a) {}\n"
" B b;\n"
" const A a;\n"
"};");
ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:5]: (style, inconclusive) Member variable 'C::b' uses an uninitialized argument due to the order of declarations.\n",
errout.str());
checkInitializerListOrder("struct S {\n"
" S(const std::string& f, std::string i, int b, int c) : a(0), b(b), c(c) {}\n"
" int a, b, c;\n"
"};");
ASSERT_EQUALS("", errout.str());
checkInitializerListOrder("struct S {\n"
" S() : p(a) {}\n"
" int* p;\n"
" int a[1];\n"
"};");
ASSERT_EQUALS("", errout.str());
checkInitializerListOrder("struct S {\n"
" S() : p(&i) {}\n"
" int* p;\n"
" int i;\n"
"};");
ASSERT_EQUALS("", errout.str());
checkInitializerListOrder("struct S {\n"
" S() : a(b = 1) {}\n"
" int a, b;\n"
"};");
ASSERT_EQUALS("", errout.str());
checkInitializerListOrder("struct S {\n"
" S() : r(i) {}\n"
" int& r;\n"
" int i{};\n"
"};");
ASSERT_EQUALS("", errout.str());
} }
#define checkInitializationListUsage(code) checkInitializationListUsage_(code, __FILE__, __LINE__) #define checkInitializationListUsage(code) checkInitializationListUsage_(code, __FILE__, __LINE__)