diff --git a/lib/checkio.cpp b/lib/checkio.cpp index 0dd29f1cb..df0932ede 100644 --- a/lib/checkio.cpp +++ b/lib/checkio.cpp @@ -560,6 +560,35 @@ void CheckIO::checkWrongPrintfScanfArguments() else if (argListTok->type() == Token::eString) invalidPrintfArgTypeError_float(tok, numFormat, *i); break; + case 'h': // Can be 'hh' (signed char or unsigned char) or 'h' (short int or unsigned short int) + case 'l': // Can be 'll' (long long int or unsigned long long int) or 'l' (long int or unsigned long int) + // If the next character is the same (which makes 'hh' or 'll') then expect another alphabetical character + if (i != formatString.end() && *(i+1) == *i) { + if (i+1 != formatString.end() && !isalpha(*(i+2))) { + std::string modifier; + modifier += *i; + modifier += *(i+1); + invalidLengthModifierError(tok, numFormat, modifier); + } + } else { + if (i != formatString.end() && !isalpha(*(i+1))) { + std::string modifier; + modifier += *i; + invalidLengthModifierError(tok, numFormat, modifier); + } + } + break; + case 'j': // intmax_t or uintmax_t + case 'z': // size_t + case 't': // ptrdiff_t + case 'L': // long double + // Expect an alphabetical character after these specifiers + if (i != formatString.end() && !isalpha(*(i+1))) { + std::string modifier; + modifier += *i; + invalidLengthModifierError(tok, numFormat, modifier); + } + break; default: break; } @@ -654,6 +683,13 @@ void CheckIO::invalidPrintfArgTypeError_float(const Token* tok, unsigned int num errmsg << "%" << c << " in format string (no. " << numFormat << ") requires a floating point number given in the argument list."; reportError(tok, Severity::warning, "invalidPrintfArgType_float", errmsg.str()); } +void CheckIO::invalidLengthModifierError(const Token* tok, unsigned int numFormat, std::string& modifier) +{ + std::ostringstream errmsg; + errmsg << "'" << modifier << "' in format string (no. " << numFormat << ") is a length modifier and cannot be used without a conversion specifier."; + reportError(tok, Severity::warning, "invalidLengthModifierError", errmsg.str()); +} + void CheckIO::invalidScanfFormatWidthError(const Token* tok, unsigned int numFormat, int width, const Variable *var) { std::ostringstream errmsg; diff --git a/lib/checkio.h b/lib/checkio.h index 87d10f89b..54084dbee 100644 --- a/lib/checkio.h +++ b/lib/checkio.h @@ -90,6 +90,7 @@ private: void invalidPrintfArgTypeError_uint(const Token* tok, unsigned int numFormat, char c); void invalidPrintfArgTypeError_sint(const Token* tok, unsigned int numFormat, char c); void invalidPrintfArgTypeError_float(const Token* tok, unsigned int numFormat, char c); + void invalidLengthModifierError(const Token* tok, unsigned int numFormat, std::string& modifier); void invalidScanfFormatWidthError(const Token* tok, unsigned int numFormat, int width, const Variable *var); void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const { diff --git a/test/testio.cpp b/test/testio.cpp index d2e42f516..8772bc91d 100644 --- a/test/testio.cpp +++ b/test/testio.cpp @@ -640,6 +640,48 @@ private: TODO_ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 1) requires an integer given in the argument list.\n" "[test.cpp:4]: (warning) %f in format string (no. 1) requires an integer given in the argument list.\n" "[test.cpp:5]: (warning) %p in format string (no. 1) requires an integer given in the argument list.\n", "", errout.str()); + + // Ticket #4189 (Improve check (printf("%l") not detected)) tests (according to C99 7.19.6.1.7) + // False positive tests + check("void foo(signed char sc, unsigned char uc, short int si, unsigned short int usi) {\n" + " printf(\"%hhx %hhd\", sc, uc);\n" + " printf(\"%hd %hi\", si, usi);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + check("void foo(long long int lli, unsigned long long int ulli, long int li, unsigned long int uli) {\n" + " printf(\"%llo %llx\", lli, ulli);\n" + " printf(\"%ld %lu\", li, uli);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + check("void foo(intmax_t im, uintmax_t uim, size_t s, ptrdiff_t p, long double ld) {\n" + " printf(\"%jd %jo\", im, uim);\n" + " printf(\"%zx\", s);\n" + " printf(\"%ti\", p);\n" + " printf(\"%La\", ld);\n" + "}"); + ASSERT_EQUALS("", errout.str()); + + // False negative test + check("void foo(unsigned int i) {\n" + " printf(\"%h\", i);\n" + " printf(\"%hh\", i);\n" + " printf(\"%l\", i);\n" + " printf(\"%ll\", i);\n" + " printf(\"%j\", i);\n" + " printf(\"%z\", i);\n" + " printf(\"%t\", i);\n" + " printf(\"%L\", i);\n" + "}"); + ASSERT_EQUALS("[test.cpp:2]: (warning) 'h' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" + "[test.cpp:3]: (warning) 'hh' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" + "[test.cpp:4]: (warning) 'l' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" + "[test.cpp:5]: (warning) 'll' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" + "[test.cpp:6]: (warning) 'j' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" + "[test.cpp:7]: (warning) 'z' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" + "[test.cpp:8]: (warning) 't' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" + "[test.cpp:9]: (warning) 'L' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n", errout.str()); } };