Fix issue 9428: FP uninitvar for pointer passed to sscanf (#2344)

* Add indirect to library cfg files

* Check indirect for non null arguments

* Reenable subfunction analysis

* Use indirect 1 when using not-null

* Parse correct string name

* Update documentation

* Make attribute optional
This commit is contained in:
Paul Fultz II 2019-11-13 05:46:54 -06:00 committed by Daniel Marjamäki
parent 83095129d2
commit 7841430793
9 changed files with 47 additions and 22 deletions

View File

@ -220,7 +220,11 @@
<element name="not-null"><empty/></element>
</optional>
<optional>
<element name="not-uninit"><empty/></element>
<element name="not-uninit">
<optional>
<attribute name="indirect"><ref name="INDIRECT"/></attribute>
</optional>
</element>
</optional>
<optional>
<element name="valid">
@ -504,6 +508,13 @@
<param name="maxInclusive">20</param>
</data>
</define>
<define name="INDIRECT">
<data type="integer">
<param name="minInclusive">0</param>
<param name="maxInclusive">2</param>
</data>
</define>
<define name="ARG-DIRECTION">
<choice>

View File

@ -946,7 +946,7 @@ void CheckUninitVar::checkRhs(const Token *tok, const Variable &var, Alloc alloc
}
}
bool CheckUninitVar::isVariableUsage(const Token *vartok, bool pointer, Alloc alloc) const
bool CheckUninitVar::isVariableUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect) const
{
if (!pointer && Token::Match(vartok, "%name% ("))
return false;
@ -999,7 +999,7 @@ bool CheckUninitVar::isVariableUsage(const Token *vartok, bool pointer, Alloc al
if (Token::Match(possibleParent, "[(,]")) {
if (unknown)
return false; // TODO: output some info message?
const int use = isFunctionParUsage(vartok, pointer, alloc);
const int use = isFunctionParUsage(vartok, pointer, alloc, indirect);
if (use >= 0)
return (use>0);
}
@ -1127,7 +1127,7 @@ bool CheckUninitVar::isVariableUsage(const Token *vartok, bool pointer, Alloc al
* is passed "by reference" then it is not necessarily "used".
* @return -1 => unknown 0 => not used 1 => used
*/
int CheckUninitVar::isFunctionParUsage(const Token *vartok, bool pointer, Alloc alloc) const
int CheckUninitVar::isFunctionParUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect) const
{
bool unknown = false;
const Token *parent = getAstParentSkipPossibleCastAndAddressOf(vartok, &unknown);
@ -1183,9 +1183,9 @@ int CheckUninitVar::isFunctionParUsage(const Token *vartok, bool pointer, Alloc
return alloc == NO_ALLOC;
} else {
const bool isnullbad = mSettings->library.isnullargbad(start->previous(), argumentNumber + 1);
if (pointer && !address && isnullbad && alloc == NO_ALLOC)
if (indirect == 0 && pointer && !address && isnullbad && alloc == NO_ALLOC)
return 1;
const bool isuninitbad = mSettings->library.isuninitargbad(start->previous(), argumentNumber + 1);
const bool isuninitbad = mSettings->library.isuninitargbad(start->previous(), argumentNumber + 1, indirect);
if (alloc != NO_ALLOC)
return isnullbad && isuninitbad;
return isuninitbad && (!address || isnullbad);
@ -1366,7 +1366,7 @@ void CheckUninitVar::valueFlowUninit()
continue;
bool uninitderef = false;
if (tok->variable()) {
if (!isVariableUsage(tok, tok->variable()->isPointer(), tok->variable()->isArray() ? ARRAY : NO_ALLOC))
if (!isVariableUsage(tok, tok->variable()->isPointer(), tok->variable()->isArray() ? ARRAY : NO_ALLOC, v->indirect))
continue;
bool unknown;
const bool deref = CheckNullPointer::isPointerDeRef(tok, unknown, mSettings);

View File

@ -76,8 +76,8 @@ public:
bool checkIfForWhileHead(const Token *startparentheses, const Variable& var, bool suppressErrors, bool isuninit, Alloc alloc, const std::string &membervar);
bool checkLoopBody(const Token *tok, const Variable& var, const Alloc alloc, const std::string &membervar, const bool suppressErrors);
void checkRhs(const Token *tok, const Variable &var, Alloc alloc, nonneg int number_of_if, const std::string &membervar);
bool isVariableUsage(const Token *vartok, bool pointer, Alloc alloc) const;
int isFunctionParUsage(const Token *vartok, bool pointer, Alloc alloc) const;
bool isVariableUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect = 0) const;
int isFunctionParUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect = 0) const;
bool isMemberVariableAssignment(const Token *tok, const std::string &membervar) const;
bool isMemberVariableUsage(const Token *tok, bool isPointer, Alloc alloc, const std::string &membervar) const;

View File

@ -663,12 +663,16 @@ Library::Error Library::loadFunction(const tinyxml2::XMLElement * const node, co
}
for (const tinyxml2::XMLElement *argnode = functionnode->FirstChildElement(); argnode; argnode = argnode->NextSiblingElement()) {
const std::string argnodename = argnode->Name();
int indirect = 0;
const char * const indirectStr = node->Attribute("indirect");
if (indirectStr)
indirect = atoi(indirectStr);
if (argnodename == "not-bool")
ac.notbool = true;
else if (argnodename == "not-null")
ac.notnull = true;
else if (argnodename == "not-uninit")
ac.notuninit = true;
ac.notuninit = indirect;
else if (argnodename == "formatstr")
ac.formatstr = true;
else if (argnodename == "strz")
@ -775,6 +779,8 @@ Library::Error Library::loadFunction(const tinyxml2::XMLElement * const node, co
else
unknown_elements.insert(argnodename);
}
if (ac.notuninit == 0)
ac.notuninit = ac.notnull ? 1 : 0;
} else if (functionnodename == "ignorefunction") {
func.ignore = true;
} else if (functionnodename == "formatstr") {
@ -951,7 +957,7 @@ bool Library::isnullargbad(const Token *ftok, int argnr) const
return arg && arg->notnull;
}
bool Library::isuninitargbad(const Token *ftok, int argnr) const
bool Library::isuninitargbad(const Token *ftok, int argnr, int indirect) const
{
const ArgumentChecks *arg = getarg(ftok, argnr);
if (!arg) {
@ -961,7 +967,7 @@ bool Library::isuninitargbad(const Token *ftok, int argnr) const
if (it != functions.cend() && it->second.formatstr && !it->second.formatstr_scan)
return true;
}
return arg && arg->notuninit;
return arg && arg->notuninit >= indirect;
}

View File

@ -242,7 +242,7 @@ public:
ArgumentChecks() :
notbool(false),
notnull(false),
notuninit(false),
notuninit(-1),
formatstr(false),
strz(false),
optional(false),
@ -253,7 +253,7 @@ public:
bool notbool;
bool notnull;
bool notuninit;
int notuninit;
bool formatstr;
bool strz;
bool optional;
@ -318,7 +318,7 @@ public:
}
bool isnullargbad(const Token *ftok, int argnr) const;
bool isuninitargbad(const Token *ftok, int argnr) const;
bool isuninitargbad(const Token *ftok, int argnr, int indirect = 0) const;
bool isargformatstr(const Token *ftok, int argnr) const {
const ArgumentChecks *arg = getarg(ftok, argnr);

View File

@ -5140,9 +5140,6 @@ static void valueFlowSubFunction(TokenList* tokenlist, ErrorLogger* errorLogger,
// Don't forward lifetime values
argvalues.remove_if(std::mem_fn(&ValueFlow::Value::isLifetimeValue));
// Don't forward <Uninit> values, this is handled by CTU. We also had a FP #9347
argvalues.remove_if(std::mem_fn(&ValueFlow::Value::isUninitValue));
if (argvalues.empty())
continue;

View File

@ -172,6 +172,10 @@ Here is the minimal windows.cfg:
</function>
</def>
The `indirect` attribute can be set to control the indirection of uninitialized memory allowed. Setting `indirect` to `0` means no uninitialized memory is allowed. Setting it to `1` allows a pointer to uninitialized memory. Setting it to `2` allows a pointer to pointer to uninitialized memory.
By default, cppcheck will use an indirect value of `0` unless `not-null` is used. When `not-null` is used, then `indirect` will default to `1`.
### Null pointers
Cppcheck assumes it's ok to pass NULL pointers to functions. Here is an example program:

View File

@ -229,7 +229,7 @@ private:
Library library;
ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode);
ASSERT_EQUALS(true, library.functions["foo"].argumentChecks[1].notuninit);
ASSERT_EQUALS(0, library.functions["foo"].argumentChecks[1].notuninit);
ASSERT_EQUALS(true, library.functions["foo"].argumentChecks[2].notnull);
ASSERT_EQUALS(true, library.functions["foo"].argumentChecks[3].formatstr);
ASSERT_EQUALS(true, library.functions["foo"].argumentChecks[4].strz);
@ -248,7 +248,7 @@ private:
Library library;
ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode);
ASSERT_EQUALS(true, library.functions["foo"].argumentChecks[-1].notuninit);
ASSERT_EQUALS(0, library.functions["foo"].argumentChecks[-1].notuninit);
}
void function_arg_variadic() const {
@ -262,7 +262,7 @@ private:
Library library;
ASSERT_EQUALS(true, Library::OK == (readLibrary(library, xmldata)).errorcode);
ASSERT_EQUALS(true, library.functions["foo"].argumentChecks[-1].notuninit);
ASSERT_EQUALS(0, library.functions["foo"].argumentChecks[-1].notuninit);
TokenList tokenList(nullptr);
std::istringstream istr("foo(a,b,c,d,e);");

View File

@ -4285,6 +4285,13 @@ private:
"}");
ASSERT_EQUALS("[test.cpp:8]: (error) Uninitialized variable: a\n", errout.str());
valueFlowUninit("void h() {\n"
" int i;\n"
" int* v = &i;\n"
" sscanf(\"0\", \"%d\", v);\n"
"}\n");
ASSERT_EQUALS("", errout.str());
valueFlowUninit("void test(int p) {\n"
" int f;\n"
" if (p > 0)\n"
@ -4307,7 +4314,7 @@ private:
" someType_t gVar;\n"
" bar(&gVar);\n"
"}\n");
TODO_ASSERT_EQUALS("[test.cpp:9] -> [test.cpp:5]: (error) Uninitialized variable: p->flags\n", "", errout.str());
ASSERT_EQUALS("[test.cpp:9] -> [test.cpp:5]: (error) Uninitialized variable: flags\n", errout.str());
valueFlowUninit("typedef struct \n"
"{\n"