Fixed #4964 (printf format argument check only supports simple variables)

This commit is contained in:
Robert Reif 2013-09-08 20:21:00 +02:00 committed by Daniel Marjamäki
parent 4b1254bc8d
commit 9be2f6b5d4
3 changed files with 140 additions and 97 deletions

View File

@ -554,8 +554,7 @@ void CheckIO::checkWrongPrintfScanfArguments()
switch (*i) {
case 's':
if (argInfo.variableInfo && argListTok->type() != Token::eString &&
argInfo.isKnownType() &&
!argInfo.isArrayOrPointer())
argInfo.isKnownType() && !argInfo.isArrayOrPointer())
invalidPrintfArgTypeError_s(tok, numFormat);
done = true;
break;
@ -569,14 +568,18 @@ void CheckIO::checkWrongPrintfScanfArguments()
case 'X':
case 'o':
specifier += *i;
if (argInfo.functionInfo || argInfo.variableInfo) {
if ((argInfo.isKnownType() && !argInfo.isArrayOrPointer()) &&
(!Token::Match(argInfo.typeToken, "bool|short|long|int|char|size_t") ||
(specifier[0] == 'l' && (argInfo.typeToken->str() != "long" || (specifier[1] == 'l' && !argInfo.typeToken->isLong()))) ||
(specifier.find("I64") != std::string::npos && (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong())))) {
if (argInfo.typeToken->type() == Token::eString) {
invalidPrintfArgTypeError_int(tok, numFormat, specifier, &argInfo);
} else if (argInfo.isKnownType() && !argInfo.isArrayOrPointer()) {
if (!Token::Match(argInfo.typeToken, "bool|short|long|int|char")) {
invalidPrintfArgTypeError_int(tok, numFormat, specifier, &argInfo);
} else if ((specifier[0] == 'l' && (argInfo.typeToken->str() != "long" || (specifier[1] == 'l' && !argInfo.typeToken->isLong()))) ||
(specifier.find("I64") != std::string::npos && (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()))) {
invalidPrintfArgTypeError_int(tok, numFormat, specifier, &argInfo);
}
} else if (argInfo.typeToken->type() == Token::eString) {
} else if ((!argInfo.element && argInfo.isArrayOrPointer()) ||
(argInfo.element && !argInfo.isArrayOrPointer())) {
// use %p on pointers and arrays
invalidPrintfArgTypeError_int(tok, numFormat, specifier, &argInfo);
}
done = true;
@ -584,8 +587,9 @@ void CheckIO::checkWrongPrintfScanfArguments()
case 'd':
case 'i':
specifier += *i;
if (argInfo.functionInfo || argInfo.variableInfo) {
if (argInfo.isKnownType() && !argInfo.isArrayOrPointer()) {
if (argInfo.typeToken->type() == Token::eString) {
invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
} else if (argInfo.isKnownType() && !argInfo.isArrayOrPointer()) {
if ((argInfo.typeToken->isUnsigned() || !Token::Match(argInfo.typeToken, "bool|short|long|int")) && !Token::Match(argInfo.typeToken, "char|short")) {
invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
} else if ((specifier[0] == 'l' && (argInfo.typeToken->str() != "long" || (specifier[1] == 'l' && !argInfo.typeToken->isLong()))) ||
@ -606,22 +610,13 @@ void CheckIO::checkWrongPrintfScanfArguments()
// use %p on pointers and arrays
invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
}
} else if (argInfo.typeToken->type() == Token::eString) {
invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
} else {
if ((argInfo.typeToken->isUnsigned() || !Token::Match(argInfo.typeToken, "bool|short|int|long")) && !Token::Match(argInfo.typeToken, "char|short")) {
invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
} else if ((specifier[0] == 'l' && (argInfo.typeToken->str() != "long" || (specifier[1] == 'l' && !argInfo.typeToken->isLong()))) ||
(specifier.find("I64") != std::string::npos && (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()))) {
invalidPrintfArgTypeError_sint(tok, numFormat, specifier, &argInfo);
}
}
done = true;
break;
case 'u':
specifier += *i;
if (argInfo.functionInfo || argInfo.variableInfo) {
if (argInfo.isKnownType() && !argInfo.isArrayOrPointer()) {
if (argInfo.typeToken->type() == Token::eString) {
invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
} else if (argInfo.isKnownType() && !argInfo.isArrayOrPointer()) {
if ((!argInfo.typeToken->isUnsigned() || !Token::Match(argInfo.typeToken, "char|short|long|int")) && argInfo.typeToken->str() != "bool") {
invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
} else if ((specifier[0] == 'l' && (argInfo.typeToken->str() != "long" || (specifier[1] == 'l' && !argInfo.typeToken->isLong()))) ||
@ -640,30 +635,12 @@ void CheckIO::checkWrongPrintfScanfArguments()
// use %p on pointers and arrays
invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
}
} else if (argInfo.typeToken->type() == Token::eString) {
invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
} else {
if ((!argInfo.typeToken->isUnsigned() || !Token::Match(argInfo.typeToken, "char|short|long|int")) && argInfo.typeToken->str() != "bool") {
invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
} else if ((specifier[0] == 'l' && (argInfo.typeToken->str() != "long" || (specifier[1] == 'l' && !argInfo.typeToken->isLong()))) ||
(specifier.find("I64") != std::string::npos && (argInfo.typeToken->str() != "long" || !argInfo.typeToken->isLong()))) {
invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
} else if ((specifier[0] == 'z' || (specifier[0] == 'I' && specifier[1] == 'u')) && argInfo.typeToken->originalName() != "size_t") {
// use %z or %I on size_t
invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
} else if (argInfo.typeToken->originalName() == "size_t" && (specifier[0] != 'z' && !(specifier[0] == 'I' && specifier[1] == 'u'))) {
// size_t requires %z or %I
invalidPrintfArgTypeError_uint(tok, numFormat, specifier, &argInfo);
}
}
done = true;
break;
case 'p':
if (argInfo.functionInfo && argInfo.typeToken->type() == Token::eType && !argInfo.isArrayOrPointer())
if (argInfo.typeToken->type() == Token::eString)
invalidPrintfArgTypeError_p(tok, numFormat, &argInfo);
else if (argInfo.variableInfo && argInfo.typeToken && argInfo.isKnownType() && !argInfo.isArrayOrPointer())
invalidPrintfArgTypeError_p(tok, numFormat, &argInfo);
else if (argInfo.typeToken->type() == Token::eString)
else if (argInfo.isKnownType() && !argInfo.isArrayOrPointer())
invalidPrintfArgTypeError_p(tok, numFormat, &argInfo);
done = true;
break;
@ -673,24 +650,20 @@ void CheckIO::checkWrongPrintfScanfArguments()
case 'g':
case 'G':
specifier += *i;
if (argInfo.functionInfo) {
if (((argInfo.typeToken->isStandardType() || argInfo.functionInfo->retType) && !Token::Match(argInfo.typeToken, "float|double")) ||
(!argInfo.element && argInfo.isArrayOrPointer()) ||
(argInfo.element && !argInfo.isArrayOrPointer()) ||
(specifier[0] == 'l' && (!argInfo.typeToken->isLong() || argInfo.typeToken->str() != "double")) ||
(specifier[0] != 'l' && argInfo.typeToken->isLong())) {
if (argInfo.typeToken->type() == Token::eString)
invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
else if (argInfo.isKnownType() && (!argInfo.isArrayOrPointer() || argInfo.element)) {
if (!Token::Match(argInfo.typeToken, "float|double")) {
invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
} else if (((specifier[0] == 'l' || specifier[0] == 'L') && (!argInfo.typeToken->isLong() || argInfo.typeToken->str() != "double")) ||
((specifier[0] != 'l' && specifier[0] != 'L') && argInfo.typeToken->isLong())) {
invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
}
} else if (argInfo.variableInfo) {
if ((argInfo.isKnownType() && !Token::Match(argInfo.typeToken, "float|double")) ||
(!argInfo.element && argInfo.isArrayOrPointer()) ||
} else if ((!argInfo.element && argInfo.isArrayOrPointer()) ||
(argInfo.element && !argInfo.isArrayOrPointer())) {
// use %p on pointers and arrays
invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
}
} else if (argInfo.typeToken->type() == Token::eString)
invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
else if (!Token::Match(argInfo.typeToken, "float|double"))
invalidPrintfArgTypeError_float(tok, numFormat, specifier, &argInfo);
done = true;
break;
case 'h': // Can be 'hh' (signed char or unsigned char) or 'h' (short int or unsigned short int)
@ -788,6 +761,7 @@ CheckIO::ArgumentInfo::ArgumentInfo(const Token * tok, const Settings *settings)
, typeToken(0)
, functionInfo(0)
, element(false)
, _template(false)
, tempToken(0)
{
if (tok) {
@ -833,7 +807,8 @@ CheckIO::ArgumentInfo::ArgumentInfo(const Token * tok, const Settings *settings)
tok1 = tok1->link();
// check for some common well known functions
else if (Token::Match(tok1->previous(), "%var% . size|empty|c_str ( )") && isStdContainer(tok1->previous())) {
else if ((Token::Match(tok1->previous(), "%var% . size|empty|c_str ( )") && isStdContainer(tok1->previous())) ||
(Token::Match(tok1->previous(), "] . size|empty|c_str ( )") && Token::Match(tok1->previous()->link()->previous(), "%var%") && isStdContainer(tok1->previous()->link()->previous()))) {
tempToken = new Token(0);
tempToken->fileIndex(tok1->fileIndex());
tempToken->linenr(tok1->linenr());
@ -855,7 +830,7 @@ CheckIO::ArgumentInfo::ArgumentInfo(const Token * tok, const Settings *settings)
} else if (tok1->next()->str() == "c_str") {
tempToken->str("const");
tempToken->insertToken("*");
if (tok1->previous()->variable()->typeStartToken()->strAt(2) == "string")
if (typeToken->strAt(2) == "string")
tempToken->insertToken("char");
else
tempToken->insertToken("wchar_t");
@ -872,7 +847,7 @@ CheckIO::ArgumentInfo::ArgumentInfo(const Token * tok, const Settings *settings)
// look for std::vector operator [] and use template type as return type
if (variableInfo) {
if (element && isStdVector()) { // isStdVector sets type token if true
if (element && isStdVectorOrString()) { // isStdVectorOrString sets type token if true
element = false; // not really an array element
} else
typeToken = variableInfo->typeStartToken();
@ -884,15 +859,37 @@ CheckIO::ArgumentInfo::ArgumentInfo(const Token * tok, const Settings *settings)
}
}
bool CheckIO::ArgumentInfo::isStdVector()
bool CheckIO::ArgumentInfo::isStdVectorOrString()
{
if (Token::Match(variableInfo->typeStartToken(), "std :: vector|array <")) {
typeToken = variableInfo->typeStartToken()->tokAt(4);
_template = true;
return true;
} else if (Token::Match(variableInfo->typeStartToken(), "std :: string|wstring")) {
tempToken = new Token(0);
tempToken->fileIndex(variableInfo->typeStartToken()->fileIndex());
tempToken->linenr(variableInfo->typeStartToken()->linenr());
if (variableInfo->typeStartToken()->strAt(2) == "string")
tempToken->str("char");
else
tempToken->str("wchar_t");
typeToken = tempToken;
return true;
} else if (variableInfo->type() && !variableInfo->type()->derivedFrom.empty()) {
for (std::size_t i = 0, e = variableInfo->type()->derivedFrom.size(); i != e; ++i) {
if (Token::Match(variableInfo->type()->derivedFrom[i].nameTok, "std :: vector|array <")) {
typeToken = variableInfo->type()->derivedFrom[i].nameTok->tokAt(4);
_template = true;
return true;
} else if (Token::Match(variableInfo->type()->derivedFrom[i].nameTok, "std :: string|wstring")) {
tempToken = new Token(0);
tempToken->fileIndex(variableInfo->typeStartToken()->fileIndex());
tempToken->linenr(variableInfo->typeStartToken()->linenr());
if (variableInfo->type()->derivedFrom[i].nameTok->strAt(2) == "string")
tempToken->str("char");
else
tempToken->str("wchar_t");
typeToken = tempToken;
return true;
}
}
@ -904,13 +901,19 @@ bool CheckIO::ArgumentInfo::isStdVector()
bool CheckIO::ArgumentInfo::isStdContainer(const Token *tok)
{
if (tok && tok->variable()) {
if (Token::Match(tok->variable()->typeStartToken(), "std :: vector|array|bitset|deque|list|forward_list|map|multimap|multiset|priority_queue|queue|set|stack|hash_map|hash_multimap|hash_set|unordered_map|unordered_multimap|unordered_set|unordered_multiset <") ||
Token::Match(tok->variable()->typeStartToken(), "std :: string|wstring")) {
if (Token::Match(tok->variable()->typeStartToken(), "std :: vector|array|bitset|deque|list|forward_list|map|multimap|multiset|priority_queue|queue|set|stack|hash_map|hash_multimap|hash_set|unordered_map|unordered_multimap|unordered_set|unordered_multiset <")) {
typeToken = tok->variable()->typeStartToken()->tokAt(4);
return true;
} else if (Token::Match(tok->variable()->typeStartToken(), "std :: string|wstring")) {
typeToken = tok->variable()->typeStartToken();
return true;
} else if (tok->variable()->type() && !tok->variable()->type()->derivedFrom.empty()) {
for (std::size_t i = 0, e = tok->variable()->type()->derivedFrom.size(); i != e; ++i) {
if (Token::Match(tok->variable()->type()->derivedFrom[i].nameTok, "std :: vector|array|bitset|deque|list|forward_list|map|multimap|multiset|priority_queue|queue|set|stack|hash_map|hash_multimap|hash_set|unordered_map|unordered_multimap|unordered_set|unordered_multiset <") ||
Token::Match(tok->variable()->type()->derivedFrom[i].nameTok, "std :: string|wstring")) {
if (Token::Match(tok->variable()->type()->derivedFrom[i].nameTok, "std :: vector|array|bitset|deque|list|forward_list|map|multimap|multiset|priority_queue|queue|set|stack|hash_map|hash_multimap|hash_set|unordered_map|unordered_multimap|unordered_set|unordered_multiset <")) {
typeToken = tok->variable()->type()->derivedFrom[i].nameTok->tokAt(4);
return true;
} else if (Token::Match(tok->variable()->type()->derivedFrom[i].nameTok, "std :: string|wstring")) {
typeToken = tok->variable()->type()->derivedFrom[i].nameTok;
return true;
}
}
@ -920,6 +923,20 @@ bool CheckIO::ArgumentInfo::isStdContainer(const Token *tok)
return false;
}
bool CheckIO::ArgumentInfo::isArrayOrPointer() const
{
if (variableInfo && !_template) {
return variableInfo->isArrayOrPointer();
} else {
const Token *tok = typeToken;
while (tok && Token::Match(tok, "const|struct"))
tok = tok->next();
if (tok && tok->strAt(1) == "*")
return true;
}
return false;
}
bool CheckIO::ArgumentInfo::isComplexType() const
{
if (variableInfo->type())
@ -944,6 +961,8 @@ bool CheckIO::ArgumentInfo::isKnownType() const
return (typeToken->isStandardType() || typeToken->next()->isStandardType() || isComplexType());
else if (functionInfo)
return (typeToken->isStandardType() || functionInfo->retType);
else
return typeToken->isStandardType() || Token::Match(typeToken, "std :: string|wstring");
return false;
}
@ -1071,15 +1090,15 @@ void CheckIO::argumentType(std::ostream& os, const ArgumentInfo * argInfo)
os << "const char *";
} else {
if (type->originalName().empty()) {
if (type->str() == "const") {
os << "const ";
while (Token::Match(type, "const|struct")) {
os << type->str() << " ";
type = type->next();
}
type->stringify(os, false, true);
if (type->strAt(1) == "*" ||
(argInfo->variableInfo && !argInfo->element && argInfo->variableInfo->isArray())) {
if (type->strAt(1) == "*" && !(argInfo->functionInfo && argInfo->element))
os << " *";
else if (argInfo->variableInfo && !argInfo->element && argInfo->variableInfo->isArray())
os << " *";
}
} else {
if ((type->originalName() == "__int64" || type->originalName() == "__int32") && type->isUnsigned())
os << "unsigned ";

View File

@ -75,22 +75,17 @@ private:
~ArgumentInfo() {
delete tempToken;
}
bool isArrayOrPointer() const {
if (variableInfo)
return variableInfo->isArrayOrPointer();
else if (functionInfo)
return typeToken->next()->str() == "*";
return false;
}
bool isArrayOrPointer() const;
bool isComplexType() const;
bool isKnownType() const;
bool isStdVector();
bool isStdVectorOrString();
bool isStdContainer(const Token *tok);
const Variable *variableInfo;
const Token *typeToken;
const Function *functionInfo;
bool element;
bool _template;
Token *tempToken;
private:

View File

@ -650,7 +650,9 @@ private:
"}");
ASSERT_EQUALS("[test.cpp:3]: (warning) %X in format string (no. 1) requires an integer but the argument type is 'foo'.\n"
"[test.cpp:4]: (warning) %c in format string (no. 1) requires an integer but the argument type is 'const char *'.\n"
"[test.cpp:5]: (warning) %o in format string (no. 1) requires an integer but the argument type is 'double'.\n", errout.str());
"[test.cpp:5]: (warning) %o in format string (no. 1) requires an integer but the argument type is 'double'.\n"
"[test.cpp:6]: (warning) %x in format string (no. 1) requires an integer but the argument type is 'int *'.\n"
"[test.cpp:8]: (warning) %X in format string (no. 1) requires an integer but the argument type is 'bar *'.\n", errout.str());
check("class foo {};\n"
"void foo(const int* cpi, foo f, bar b, bar* bp, double d, unsigned int u, unsigned char uc) {\n"
@ -1062,7 +1064,7 @@ private:
"void f() {\n"
" printf(\"%f\", foo()[0]);\n"
"}\n");
ASSERT_EQUALS("[test.cpp:4]: (warning) %f in format string (no. 1) requires a floating point number but the argument type is 'int *'.\n", errout.str());
ASSERT_EQUALS("[test.cpp:4]: (warning) %f in format string (no. 1) requires a floating point number but the argument type is 'int'.\n", errout.str());
check("struct Base { int length() { } };\n"
"struct Derived : public Base { };\n"
@ -1225,6 +1227,33 @@ private:
"[test.cpp:16]: (warning) %ld in format string (no. 8) requires a signed long integer but the argument type is 'ptrdiff_t {aka long}'.\n"
"[test.cpp:16]: (warning) %ld in format string (no. 9) requires a signed long integer but the argument type is 'char *'.\n", errout.str());
check("struct A {};\n"
"class B : public std::vector<const int *> {} b;\n"
"class C : public std::vector<const struct A *> {} c;\n"
"std::string s;\n"
"void foo() {\n"
" printf(\"%zu %u\", b.size(), b.size());\n"
" printf(\"%p %d\", b[0], b[0]);\n"
" printf(\"%p %d\", c[0], c[0]);\n"
" printf(\"%p %d\", s.c_str(), s.c_str());\n"
"}\n", false, false, Settings::Unix64);
ASSERT_EQUALS("[test.cpp:6]: (warning) %u in format string (no. 2) requires an unsigned integer but the argument type is 'size_t {aka unsigned long}'.\n"
"[test.cpp:7]: (warning) %d in format string (no. 2) requires a signed integer but the argument type is 'const int *'.\n"
"[test.cpp:8]: (warning) %d in format string (no. 2) requires a signed integer but the argument type is 'const struct A *'.\n"
"[test.cpp:9]: (warning) %d in format string (no. 2) requires a signed integer but the argument type is 'const char *'.\n", errout.str());
check("class A : public std::vector<std::string> {} a;\n"
"class B : public std::string {} b;\n"
"std::string s;\n"
"void foo() {\n"
" printf(\"%p %d\", a[0].c_str(), a[0].c_str());\n"
" printf(\"%c %p\", b[0], b[0]);\n"
" printf(\"%c %p\", s[0], s[0]);\n"
"}\n", false, false, Settings::Unix64);
ASSERT_EQUALS("[test.cpp:5]: (warning) %d in format string (no. 2) requires a signed integer but the argument type is 'const char *'.\n"
"[test.cpp:6]: (warning) %p in format string (no. 2) requires an address but the argument type is 'char'.\n"
"[test.cpp:7]: (warning) %p in format string (no. 2) requires an address but the argument type is 'char'.\n", errout.str());
}
void testPosixPrintfScanfParameterPosition() { // #4900 - No support for parameters in format strings