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:
parent
83095129d2
commit
7841430793
|
@ -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">
|
||||
|
@ -505,6 +509,13 @@
|
|||
</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>
|
||||
<value>in</value>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);");
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue