diff --git a/lib/checkio.cpp b/lib/checkio.cpp index 9f7bd0532..ac7219276 100644 --- a/lib/checkio.cpp +++ b/lib/checkio.cpp @@ -439,9 +439,10 @@ void CheckIO::checkWrongPrintfScanfArguments() if (!formatString.empty()) { /* formatstring found in library */ } else if (Token::Match(tok, "printf|scanf|wprintf|wscanf ( %str%") || - (windows && (Token::Match(tok, "Format|AppendFormat ( %str%") && - Token::Match(tok->tokAt(-2), "%var% .") && tok->tokAt(-2)->variable() && - tok->tokAt(-2)->variable()->typeStartToken()->str() == "CString"))) { + (windows && (Token::Match(tok, "printf_s|wprintf_s|scanf_s|wscanf_s ( %str%") || + (Token::Match(tok, "Format|AppendFormat ( %str%") && + Token::Match(tok->tokAt(-2), "%var% .") && tok->tokAt(-2)->variable() && + tok->tokAt(-2)->variable()->typeStartToken()->str() == "CString")))) { formatString = tok->strAt(2); if (tok->strAt(3) == ",") { @@ -451,7 +452,9 @@ void CheckIO::checkWrongPrintfScanfArguments() } else { continue; } - } else if (Token::Match(tok, "sprintf|fprintf|sscanf|fscanf|swscanf|fwprintf|fwscanf ( %any%") || (Token::simpleMatch(tok, "swprintf (") && Token::Match(tok->tokAt(2)->nextArgument(), "%str%"))) { + } else if (Token::Match(tok, "sprintf|fprintf|sscanf|fscanf|swscanf|fwprintf|fwscanf ( %any%") || + (Token::simpleMatch(tok, "swprintf (") && Token::Match(tok->tokAt(2)->nextArgument(), "%str%")) || + (windows && Token::Match(tok, "sscanf_s|swscanf_s ( %any%"))) { const Token* formatStringTok = tok->tokAt(2)->nextArgument(); // Find second parameter (format string) if (Token::Match(formatStringTok, "%str% [,)]")) { argListTok = formatStringTok->nextArgument(); // Find third parameter (first argument of va_args) @@ -459,7 +462,9 @@ void CheckIO::checkWrongPrintfScanfArguments() } else { continue; } - } else if (Token::Match(tok, "snprintf|fnprintf (") || (Token::simpleMatch(tok, "swprintf (") && !Token::Match(tok->tokAt(2)->nextArgument(), "%str%"))) { + } else if (Token::Match(tok, "snprintf|fnprintf (") || + (Token::simpleMatch(tok, "swprintf (") && !Token::Match(tok->tokAt(2)->nextArgument(), "%str%")) || + (windows && Token::Match(tok, "sprintf_s|swprintf_s ("))) { const Token* formatStringTok = tok->tokAt(2); for (int i = 0; i < 2 && formatStringTok; i++) { formatStringTok = formatStringTok->nextArgument(); // Find third parameter (format string) @@ -470,13 +475,27 @@ void CheckIO::checkWrongPrintfScanfArguments() } else { continue; } + } else if (windows && Token::Match(tok, "snprintf_s|snwprintf_s (")) { + const Token* formatStringTok = tok->tokAt(2); + for (int i = 0; i < 3 && formatStringTok; i++) { + formatStringTok = formatStringTok->nextArgument(); // Find forth parameter (format string) + } + if (Token::Match(formatStringTok, "%str% [,)]")) { + argListTok = formatStringTok->nextArgument(); // Find fourth parameter (first argument of va_args) + formatString = formatStringTok->str(); + } else { + continue; + } + } else { continue; } // Count format string parameters.. - bool scan = Token::Match(tok, "sscanf|fscanf|scanf|swscanf|fwscanf|wscanf"); + bool scanf_s = windows ? Token::Match(tok, "scanf_s|wscanf_s|sscanf_s|swscanf_s") : false; + bool scan = Token::Match(tok, "sscanf|fscanf|scanf|swscanf|fwscanf|wscanf") || scanf_s; unsigned int numFormat = 0; + unsigned int numSecure = 0; bool percent = false; const Token* argListTok2 = argListTok; std::set parameterPositionsUsed; @@ -561,6 +580,21 @@ void CheckIO::checkWrongPrintfScanfArguments() argInfo.typeToken->strAt(-1) == "const")) { invalidScanfArgTypeError_s(tok, numFormat, specifier, &argInfo); } + if (scanf_s) { + numSecure++; + if (argListTok) { + argListTok = argListTok->nextArgument(); + } + } + done = true; + break; + case 'c': + if (scanf_s) { + numSecure++; + if (argListTok) { + argListTok = argListTok->nextArgument(); + } + } done = true; break; case 'x': @@ -1134,8 +1168,8 @@ void CheckIO::checkWrongPrintfScanfArguments() } // Mismatching number of parameters => warning - if (numFormat != numFunction) - wrongPrintfScanfArgumentsError(tok, tok->str(), numFormat, numFunction); + if ((numFormat + numSecure) != numFunction) + wrongPrintfScanfArgumentsError(tok, tok->originalName().empty() ? tok->str() : tok->originalName(), numFormat + numSecure, numFunction); } } } @@ -1374,12 +1408,13 @@ void CheckIO::wrongPrintfScanfArgumentsError(const Token* tok, std::ostringstream errmsg; errmsg << functionName - << " format string has " + << " format string requires " << numFormat - << " parameters but " + << " parameter" << (numFormat != 1 ? "s" : "") << " but " << (numFormat > numFunction ? "only " : "") << numFunction - << " are given."; + << (numFunction != 1 ? " are" : " is") + << " given."; reportError(tok, severity, "wrongPrintfScanfArgNum", errmsg.str()); } diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 0f2406f74..6facd1728 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -9562,14 +9562,34 @@ void Tokenizer::simplifyMicrosoftStringFunctions() tok->str("strtok"); } else if (Token::simpleMatch(tok, "_tprintf (")) { tok->str("printf"); + tok->originalName("_tprintf"); } else if (Token::simpleMatch(tok, "_stprintf (")) { tok->str("sprintf"); + tok->originalName("_stprintf"); } else if (Token::simpleMatch(tok, "_sntprintf (")) { tok->str("snprintf"); + tok->originalName("_sntprintf"); } else if (Token::simpleMatch(tok, "_tscanf (")) { tok->str("scanf"); + tok->originalName("_tscanf"); } else if (Token::simpleMatch(tok, "_stscanf (")) { tok->str("sscanf"); + tok->originalName("_stscanf"); + } else if (Token::simpleMatch(tok, "_tprintf_s (")) { + tok->str("printf_s"); + tok->originalName("_tprintf_s"); + } else if (Token::simpleMatch(tok, "_stprintf_s (")) { + tok->str("sprintf_s"); + tok->originalName("_stprintf_s"); + } else if (Token::simpleMatch(tok, "_sntprintf_s (")) { + tok->str("snprintf_s"); + tok->originalName("_sntprintf_s"); + } else if (Token::simpleMatch(tok, "_tscanf_s (")) { + tok->str("scanf_s"); + tok->originalName("_tscanf_s"); + } else if (Token::simpleMatch(tok, "_stscanf_s (")) { + tok->str("sscanf_s"); + tok->originalName("_stscanf_s"); } else if (Token::Match(tok, "_T ( %char%|%str% )")) { tok->deleteNext(); tok->deleteThis(); @@ -9612,14 +9632,34 @@ void Tokenizer::simplifyMicrosoftStringFunctions() tok->str("wcstok"); } else if (Token::simpleMatch(tok, "_tprintf (")) { tok->str("wprintf"); + tok->originalName("_tprintf"); } else if (Token::simpleMatch(tok, "_stprintf (")) { tok->str("swprintf"); + tok->originalName("_stprintf"); } else if (Token::simpleMatch(tok, "_sntprintf (")) { tok->str("snwprintf"); + tok->originalName("_sntprintf"); } else if (Token::simpleMatch(tok, "_tscanf (")) { tok->str("wscanf"); + tok->originalName("_tscanf"); } else if (Token::simpleMatch(tok, "_stscanf (")) { tok->str("swscanf"); + tok->originalName("_stscanf"); + } else if (Token::simpleMatch(tok, "_tprintf_s (")) { + tok->str("wprintf_s"); + tok->originalName("_tprintf_s"); + } else if (Token::simpleMatch(tok, "_stprintf_s (")) { + tok->str("swprintf_s"); + tok->originalName("_stprintf_s"); + } else if (Token::simpleMatch(tok, "_sntprintf_s (")) { + tok->str("snwprintf_s"); + tok->originalName("_sntprintf_s"); + } else if (Token::simpleMatch(tok, "_tscanf_s (")) { + tok->str("wscanf_s"); + tok->originalName("_tscanf_s"); + } else if (Token::simpleMatch(tok, "_stscanf_s (")) { + tok->str("swscanf_s"); + tok->originalName("_stscanf_s"); } else if (Token::Match(tok, "_T ( %char%|%str% )")) { tok->deleteNext(); tok->deleteThis(); diff --git a/test/testio.cpp b/test/testio.cpp index cc1f4594f..859026c2c 100644 --- a/test/testio.cpp +++ b/test/testio.cpp @@ -50,6 +50,8 @@ private: TEST_CASE(testMicrosoftPrintfArgument); // ticket #4902 TEST_CASE(testMicrosoftCStringFormatArguments); // ticket #4920 + TEST_CASE(testMicrosoftSecurePrintfArgument); + TEST_CASE(testMicrosoftSecureScanfArgument); TEST_CASE(testlibrarycfg); // library configuration } @@ -460,7 +462,7 @@ private: " scanf(\"aa%ld\", &a);\n" // No %s " scanf(\"%*[^~]\");\n" // Ignore input "}"); - ASSERT_EQUALS("[test.cpp:4]: (warning) scanf format string has 0 parameters but 1 are given.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:4]: (warning) scanf format string requires 0 parameters but 1 is given.\n", errout.str()); } void testScanf3() { @@ -507,10 +509,10 @@ private: " fscanf(bar, \"%1d\", &foo, &bar);\n" " scanf(\"%*1x %1x %29s\", &count, KeyName, foo);\n" "}"); - ASSERT_EQUALS("[test.cpp:2]: (warning) scanf format string has 0 parameters but 1 are given.\n" - "[test.cpp:3]: (warning) scanf format string has 1 parameters but 2 are given.\n" - "[test.cpp:4]: (warning) fscanf format string has 1 parameters but 2 are given.\n" - "[test.cpp:5]: (warning) scanf format string has 2 parameters but 3 are given.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (warning) scanf format string requires 0 parameters but 1 is given.\n" + "[test.cpp:3]: (warning) scanf format string requires 1 parameter but 2 are given.\n" + "[test.cpp:4]: (warning) fscanf format string requires 1 parameter but 2 are given.\n" + "[test.cpp:5]: (warning) scanf format string requires 2 parameters but 3 are given.\n", errout.str()); check("void foo() {\n" " scanf(\"%1d\");\n" @@ -518,10 +520,10 @@ private: " sscanf(bar, \"%1d%1d\", &foo);\n" " scanf(\"%*1x %1x %29s\", &count);\n" "}"); - ASSERT_EQUALS("[test.cpp:2]: (error) scanf format string has 1 parameters but only 0 are given.\n" - "[test.cpp:3]: (error) scanf format string has 2 parameters but only 1 are given.\n" - "[test.cpp:4]: (error) sscanf format string has 2 parameters but only 1 are given.\n" - "[test.cpp:5]: (error) scanf format string has 2 parameters but only 1 are given.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (error) scanf format string requires 1 parameter but only 0 are given.\n" + "[test.cpp:3]: (error) scanf format string requires 2 parameters but only 1 is given.\n" + "[test.cpp:4]: (error) sscanf format string requires 2 parameters but only 1 is given.\n" + "[test.cpp:5]: (error) scanf format string requires 2 parameters but only 1 is given.\n", errout.str()); check("void foo() {\n" " char input[10];\n" @@ -1316,24 +1318,24 @@ private: " sprintf(string1, \"%-*.*s\", 32, string2);\n" // #3364 " snprintf(a, 9, \"%s%d\", \"11223344\");\n" // #3655 "}"); - ASSERT_EQUALS("[test.cpp:2]: (error) printf format string has 1 parameters but only 0 are given.\n" - "[test.cpp:3]: (error) printf format string has 2 parameters but only 1 are given.\n" - "[test.cpp:4]: (error) printf format string has 3 parameters but only 2 are given.\n" - "[test.cpp:5]: (error) printf format string has 3 parameters but only 2 are given.\n" - "[test.cpp:6]: (error) printf format string has 3 parameters but only 2 are given.\n" - "[test.cpp:7]: (error) fprintf format string has 2 parameters but only 0 are given.\n" - "[test.cpp:8]: (error) snprintf format string has 2 parameters but only 0 are given.\n" - "[test.cpp:9]: (error) sprintf format string has 3 parameters but only 2 are given.\n" - "[test.cpp:10]: (error) snprintf format string has 2 parameters but only 1 are given.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (error) printf format string requires 1 parameter but only 0 are given.\n" + "[test.cpp:3]: (error) printf format string requires 2 parameters but only 1 is given.\n" + "[test.cpp:4]: (error) printf format string requires 3 parameters but only 2 are given.\n" + "[test.cpp:5]: (error) printf format string requires 3 parameters but only 2 are given.\n" + "[test.cpp:6]: (error) printf format string requires 3 parameters but only 2 are given.\n" + "[test.cpp:7]: (error) fprintf format string requires 2 parameters but only 0 are given.\n" + "[test.cpp:8]: (error) snprintf format string requires 2 parameters but only 0 are given.\n" + "[test.cpp:9]: (error) sprintf format string requires 3 parameters but only 2 are given.\n" + "[test.cpp:10]: (error) snprintf format string requires 2 parameters but only 1 is given.\n", errout.str()); check("void foo(char *str) {\n" " printf(\"\", 0);\n" " printf(\"%u\", 123, bar());\n" " printf(\"%u%s\", 0, bar(), 43123);\n" "}"); - ASSERT_EQUALS("[test.cpp:2]: (warning) printf format string has 0 parameters but 1 are given.\n" - "[test.cpp:3]: (warning) printf format string has 1 parameters but 2 are given.\n" - "[test.cpp:4]: (warning) printf format string has 2 parameters but 3 are given.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (warning) printf format string requires 0 parameters but 1 is given.\n" + "[test.cpp:3]: (warning) printf format string requires 1 parameter but 2 are given.\n" + "[test.cpp:4]: (warning) printf format string requires 2 parameters but 3 are given.\n", errout.str()); check("void foo() {\n" // swprintf exists as MSVC extension and as standard function: #4790 " swprintf(string1, L\"%u\", 32, string2);\n" // MSVC implementation @@ -1341,8 +1343,8 @@ private: " swprintf(string1, 6, L\"%u\", 32, string2);\n" // Standard implementation " swprintf(string1, 6, L\"%u%s\", 32, string2);\n" // Standard implementation "}"); - ASSERT_EQUALS("[test.cpp:2]: (warning) swprintf format string has 1 parameters but 2 are given.\n" - "[test.cpp:4]: (warning) swprintf format string has 1 parameters but 2 are given.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (warning) swprintf format string requires 1 parameter but 2 are given.\n" + "[test.cpp:4]: (warning) swprintf format string requires 1 parameter but 2 are given.\n", errout.str()); check("void foo(char *str) {\n" " printf(\"%u\", 0);\n" @@ -2052,7 +2054,7 @@ private: " scanf(\"%2$d\", &bar);\n" " printf(\"%0$f\", 0.0);\n" "}"); - ASSERT_EQUALS("[test.cpp:3]: (error) printf format string has 1 parameters but only 0 are given.\n" + ASSERT_EQUALS("[test.cpp:3]: (error) printf format string requires 1 parameter but only 0 are given.\n" "[test.cpp:4]: (warning) printf: referencing parameter 4 while 3 arguments given\n" "[test.cpp:5]: (warning) scanf: referencing parameter 2 while 1 arguments given\n" "[test.cpp:6]: (warning) printf: parameter positions start at 1, not 0\n" @@ -2164,6 +2166,110 @@ private: "[test.cpp:5]: (warning) %I32d in format string (no. 1) requires '__int32' but the argument type is 'unsigned __int32 {aka unsigned int}'.\n", errout.str()); } + void testMicrosoftSecurePrintfArgument() { + check("void foo() {\n" + " int i;\n" + " unsigned int u;\n" + " _tprintf_s(_T(\"%d %u\"), u, i, 0);\n" + "}\n", false, false, Settings::Win32A); + ASSERT_EQUALS("[test.cpp:4]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'.\n" + "[test.cpp:4]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'int'.\n" + "[test.cpp:4]: (warning) _tprintf_s format string requires 2 parameters but 3 are given.\n", errout.str()); + + check("void foo() {\n" + " int i;\n" + " unsigned int u;\n" + " _tprintf_s(_T(\"%d %u\"), u, i, 0);\n" + "}\n", false, false, Settings::Win32W); + ASSERT_EQUALS("[test.cpp:4]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'.\n" + "[test.cpp:4]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'int'.\n" + "[test.cpp:4]: (warning) _tprintf_s format string requires 2 parameters but 3 are given.\n", errout.str()); + + check("void foo() {\n" + " TCHAR str[10];\n" + " int i;\n" + " unsigned int u;\n" + " _stprintf_s(str, sizeof(str) / sizeof(TCHAR), _T(\"%d %u\"), u, i, 0);\n" + "}\n", false, false, Settings::Win32A); + ASSERT_EQUALS("[test.cpp:5]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'.\n" + "[test.cpp:5]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'int'.\n" + "[test.cpp:5]: (warning) _stprintf_s format string requires 2 parameters but 3 are given.\n", errout.str()); + + check("void foo() {\n" + " TCHAR str[10];\n" + " int i;\n" + " unsigned int u;\n" + " _stprintf_s(str, sizeof(str) / sizeof(TCHAR), _T(\"%d %u\"), u, i, 0);\n" + "}\n", false, false, Settings::Win32W); + ASSERT_EQUALS("[test.cpp:5]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'.\n" + "[test.cpp:5]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'int'.\n" + "[test.cpp:5]: (warning) _stprintf_s format string requires 2 parameters but 3 are given.\n", errout.str()); + + check("void foo() {\n" + " TCHAR str[10];\n" + " int i;\n" + " unsigned int u;\n" + " _sntprintf_s(str, sizeof(str) / sizeof(TCHAR), _TRUNCATE, _T(\"%d %u\"), u, i, 0);\n" + "}\n", false, false, Settings::Win32A); + ASSERT_EQUALS("[test.cpp:5]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'.\n" + "[test.cpp:5]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'int'.\n" + "[test.cpp:5]: (warning) _sntprintf_s format string requires 2 parameters but 3 are given.\n", errout.str()); + + check("void foo() {\n" + " TCHAR str[10];\n" + " int i;\n" + " unsigned int u;\n" + " _sntprintf_s(str, sizeof(str) / sizeof(TCHAR), _TRUNCATE, _T(\"%d %u\"), u, i, 0);\n" + "}\n", false, false, Settings::Win32W); + ASSERT_EQUALS("[test.cpp:5]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'.\n" + "[test.cpp:5]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'int'.\n" + "[test.cpp:5]: (warning) _sntprintf_s format string requires 2 parameters but 3 are given.\n", errout.str()); + } + + void testMicrosoftSecureScanfArgument() { + check("void foo() {\n" + " int i;\n" + " unsigned int u;\n" + " TCHAR str[10];\n" + " _tscanf_s(\"%s %d %u\", str, 10, &u, &i, 0)\n" + "}\n", false, false, Settings::Win32A); + ASSERT_EQUALS("[test.cpp:5]: (warning) %d in format string (no. 2) requires 'int *' but the argument type is 'unsigned int *'.\n" + "[test.cpp:5]: (warning) %u in format string (no. 3) requires 'unsigned int *' but the argument type is 'int *'.\n" + "[test.cpp:5]: (warning) _tscanf_s format string requires 4 parameters but 5 are given.\n", errout.str()); + + check("void foo() {\n" + " int i;\n" + " unsigned int u;\n" + " TCHAR str[10];\n" + " _tscanf_s(\"%s %d %u\", str, 10, &u, &i, 0)\n" + "}\n", false, false, Settings::Win32W); + ASSERT_EQUALS("[test.cpp:5]: (warning) %d in format string (no. 2) requires 'int *' but the argument type is 'unsigned int *'.\n" + "[test.cpp:5]: (warning) %u in format string (no. 3) requires 'unsigned int *' but the argument type is 'int *'.\n" + "[test.cpp:5]: (warning) _tscanf_s format string requires 4 parameters but 5 are given.\n", errout.str()); + + check("void foo() {\n" + " TCHAR txt[100];\n" + " int i;\n" + " unsigned int u;\n" + " TCHAR str[10];\n" + " _stscanf_s(txt, \"%s %d %u\", str, 10, &u, &i, 0)\n" + "}\n", false, false, Settings::Win32A); + ASSERT_EQUALS("[test.cpp:6]: (warning) %d in format string (no. 2) requires 'int *' but the argument type is 'unsigned int *'.\n" + "[test.cpp:6]: (warning) %u in format string (no. 3) requires 'unsigned int *' but the argument type is 'int *'.\n" + "[test.cpp:6]: (warning) _stscanf_s format string requires 4 parameters but 5 are given.\n", errout.str()); + + check("void foo() {\n" + " TCHAR txt[100];\n" + " int i;\n" + " unsigned int u;\n" + " TCHAR str[10];\n" + " _stscanf_s(txt, \"%s %d %u\", str, 10, &u, &i, 0)\n" + "}\n", false, false, Settings::Win32W); + ASSERT_EQUALS("[test.cpp:6]: (warning) %d in format string (no. 2) requires 'int *' but the argument type is 'unsigned int *'.\n" + "[test.cpp:6]: (warning) %u in format string (no. 3) requires 'unsigned int *' but the argument type is 'int *'.\n" + "[test.cpp:6]: (warning) _stscanf_s format string requires 4 parameters but 5 are given.\n", errout.str()); + } + void testlibrarycfg() { const char code[] = "void f() {\n" " format(\"%s\");\n" @@ -2177,7 +2283,7 @@ private: Library lib; lib.argumentChecks["format"][1].formatstr = true; check(code, false, false, Settings::Unspecified, &lib); - ASSERT_EQUALS("[test.cpp:2]: (error) format format string has 1 parameters but only 0 are given.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (error) format format string requires 1 parameter but only 0 are given.\n", errout.str()); } };