/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2012 Daniel Marjamäki and Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "checkio.h" #include "testsuite.h" #include "tokenize.h" #include extern std::ostringstream errout; class TestIO : public TestFixture { public: TestIO() : TestFixture("TestIO") { } private: void run() { TEST_CASE(coutCerrMisusage); TEST_CASE(wrongMode_simple); TEST_CASE(wrongMode_complex); TEST_CASE(useClosedFile); TEST_CASE(fileIOwithoutPositioning); TEST_CASE(fflushOnInputStream); TEST_CASE(testScanf1); // Scanf without field limiters TEST_CASE(testScanf2); TEST_CASE(testScanf3); TEST_CASE(testScanf4); TEST_CASE(testScanfArgument); TEST_CASE(testPrintfArgument); } void check(const char code[]) { // Clear the error buffer.. errout.str(""); Settings settings; settings.addEnabled("style"); // Tokenize.. Tokenizer tokenizer(&settings, this); std::istringstream istr(code); tokenizer.tokenize(istr, "test.cpp"); // Check.. CheckIO checkIO(&tokenizer, &settings, this); checkIO.checkWrongPrintfScanfArguments(); // Simplify token list.. tokenizer.simplifyTokenList(); checkIO.checkCoutCerrMisusage(); checkIO.checkFileUsage(); checkIO.invalidScanf(); } void coutCerrMisusage() { check( "void foo() {\n" " std::cout << std::cout;\n" "}"); ASSERT_EQUALS("[test.cpp:2]: (error) Invalid usage of output stream: '<< std::cout'.\n", errout.str()); check( "void foo() {\n" " std::cout << \"xyz\" << std::cout;\n" "}"); ASSERT_EQUALS("[test.cpp:2]: (error) Invalid usage of output stream: '<< std::cout'.\n", errout.str()); check( "void foo(int i) {\n" " std::cout << i << std::cerr;\n" "}"); ASSERT_EQUALS("[test.cpp:2]: (error) Invalid usage of output stream: '<< std::cerr'.\n", errout.str()); check( "void foo() {\n" " std::cout << \"xyz\";\n" " std::cout << \"xyz\";\n" "}"); ASSERT_EQUALS("", errout.str()); check( "void foo() {\n" " std::cout << std::cout.good();\n" "}"); ASSERT_EQUALS("", errout.str()); check( "void foo() {\n" " MACRO(std::cout <<, << std::cout)\n" "}"); ASSERT_EQUALS("", errout.str()); } void wrongMode_simple() { // Read mode check("void foo(FILE*& f) {\n" " f = fopen(name, \"r\");\n" " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:5]: (error) Write operation on a file that was only opened for reading.\n", errout.str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"r+\");\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); // Write mode check("void foo(FILE*& f) {\n" " f = fopen(name, \"w\");\n" " fwrite(buffer, 5, 6, f);\n" " rewind(f);\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:5]: (error) Read operation on a file that was only opened for writing.\n", errout.str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"w+\");\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); // Append mode check("void foo(FILE*& f) {\n" " f = fopen(name, \"a\");\n" " fwrite(buffer, 5, 6, f);\n" " rewind(f);\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:5]: (error) Read operation on a file that was only opened for writing.\n", errout.str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"a+\");\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); // Variable declared locally check("void foo() {\n" " FILE* f = fopen(name, \"r\");\n" " fwrite(buffer, 5, 6, f);\n" " fclose(f);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (error) Write operation on a file that was only opened for reading.\n", errout.str()); // Call unknown function check("void foo(FILE*& f) {\n" " f = fopen(name, \"a\");\n" " fwrite(buffer, 5, 6, f);\n" " bar(f);\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"a\");\n" " fwrite(buffer, 5, 6, f);\n" " clearerr(f);\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:5]: (error) Read operation on a file that was only opened for writing.\n", errout.str()); // freopen and tmpfile check("void foo(FILE*& f) {\n" " f = freopen(name, \"r\", f);\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (error) Write operation on a file that was only opened for reading.\n", errout.str()); check("void foo(FILE*& f) {\n" " f = tmpfile();\n" // tmpfile opens as wb+ " fwrite(buffer, 5, 6, f);\n" " rewind(f);\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); } void wrongMode_complex() { check("void foo(FILE* f) {\n" " if(a) f = fopen(name, \"w\");\n" " else f = fopen(name, \"r\");\n" " if(a) fwrite(buffer, 5, 6, f);\n" " else fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo() {\n" " FILE* f;\n" " if(a) f = fopen(name, \"w\");\n" " else f = fopen(name, \"r\");\n" " if(a) fwrite(buffer, 5, 6, f);\n" " else fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo() {\n" " FILE* f = fopen(name, \"w\");\n" " if(a) fwrite(buffer, 5, 6, f);\n" " else fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:4]: (error) Read operation on a file that was only opened for writing.\n", errout.str()); } void useClosedFile() { check("void foo(FILE*& f) {\n" " fclose(f);\n" " fwrite(buffer, 5, 6, f);\n" " clearerr(f);\n" " fread(buffer, 5, 6, f);\n" " ungetc('a', f);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (error) Used file that is not opened.\n" "[test.cpp:4]: (error) Used file that is not opened.\n" "[test.cpp:5]: (error) Used file that is not opened.\n" "[test.cpp:6]: (error) Used file that is not opened.\n", errout.str()); check("void foo(FILE*& f) {\n" " if(!ferror(f)) {\n" " fclose(f);\n" " return;" " }\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo(FILE*& f) {\n" " fclose(f);\n" " f = fopen(name, \"r\");\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"r\");\n" " f = g;\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); } void fileIOwithoutPositioning() { check("void foo(FILE* f) {\n" " fwrite(buffer, 5, 6, f);\n" " fread(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (error) Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush inbetween result in undefined behaviour.\n", errout.str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (error) Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush inbetween result in undefined behaviour.\n", errout.str()); check("void foo(FILE* f, bool read) {\n" " if(read)\n" " fread(buffer, 5, 6, f);\n" " else\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " fflush(f);\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " fsetpos(f, pos);\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " fseek(f, 0, SEEK_SET);\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("", errout.str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " long pos = ftell(f);\n" " fwrite(buffer, 5, 6, f);\n" "}"); ASSERT_EQUALS("[test.cpp:4]: (error) Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush inbetween result in undefined behaviour.\n", errout.str()); } void fflushOnInputStream() { check("void foo()\n" "{\n" " fflush(stdin);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (error) fflush() called on input stream \"stdin\" may result in undefined behaviour.\n", errout.str()); check("void foo()\n" "{\n" " fflush(stdout);\n" "}"); ASSERT_EQUALS("", errout.str()); } void testScanf1() { check("#include \n" "int main(int argc, char **argv)\n" "{\n" " int a, b;\n" " FILE *file = fopen(\"test\", \"r\");\n" " b = fscanf(file, \"aa %ds\", &a);\n" " c = scanf(\"aa %ds\", &a);\n" " b = fscanf(file, \"aa%%ds\", &a);\n" " fclose(file);\n" " return b;\n" "}"); ASSERT_EQUALS("[test.cpp:8]: (warning) fscanf format string has 0 parameters but 1 are given\n" "[test.cpp:6]: (warning) scanf without field width limits can crash with huge input data\n" "[test.cpp:7]: (warning) scanf without field width limits can crash with huge input data\n", errout.str()); } void testScanf2() { check("#include \n" "int main(int argc, char **argv)\n" "{\n" " int a, b;\n" " FILE *file = fopen(\"test\", \"r\");\n" " b = fscanf(file, \"aa%%%ds\", &a);\n" " c = scanf(\"aa %%%ds\", &a);\n" " b = fscanf(file, \"aa%%ds\", &a);\n" " fclose(file);\n" " return b;\n" "}"); ASSERT_EQUALS("[test.cpp:8]: (warning) fscanf format string has 0 parameters but 1 are given\n" "[test.cpp:6]: (warning) scanf without field width limits can crash with huge input data\n" "[test.cpp:7]: (warning) scanf without field width limits can crash with huge input data\n", errout.str()); } void testScanf3() { check("#include \n" "int main(int argc, char **argv)\n" "{\n" " char a[32];\n" " int b, c;\n" " FILE *file = fopen(\"test\", \"r\");\n" " c = fscanf(file, \"%[^ ] %d\n\", a, &b);\n" " fclose(file);\n" " return c;\n" "}"); ASSERT_EQUALS("", errout.str()); check("#include \n" "int main(int argc, char **argv)\n" "{\n" " char a[32];\n" " int b;\n" " FILE *file = fopen(\"test\", \"r\");\n" " b = fscanf(file, \"%[^ \n\", a);\n" " fclose(file);\n" " return b;\n" "}"); ASSERT_EQUALS("[test.cpp:7]: (warning) fscanf format string has 0 parameters but 1 are given\n", errout.str()); } void testScanf4() { check("void f() {\n" " char c;\n" " scanf(\"%c\", &c);\n" "}"); ASSERT_EQUALS("", errout.str()); } void testScanfArgument() { check("void foo() {\n" " scanf(\"%1d\", &foo);\n" " sscanf(bar, \"%1d\", &foo);\n" " scanf(\"%1u%1u\", &foo, bar());\n" " scanf(\"%*1x %1x %29s\", &count, KeyName);\n" // #3373 " fscanf(f, \"%7ms\", &ref);\n" // #3461 " sscanf(ip_port, \"%*[^:]:%d\", &port);\n" // #3468 "}"); ASSERT_EQUALS("", errout.str()); check("void foo() {\n" " scanf(\"\", &foo);\n" " scanf(\"%1d\", &foo, &bar);\n" " 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()); check("void foo() {\n" " scanf(\"%1d\");\n" " scanf(\"%1u%1u\", bar());\n" " 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()); } void testPrintfArgument() { check("void foo() {\n" " printf(\"%u\");\n" " printf(\"%u%s\", 123);\n" " printf(\"%u%s%d\", 0, bar());\n" " printf(\"%u%%%s%d\", 0, bar());\n" " printf(\"%udfd%%dfa%s%d\", 0, bar());\n" " fprintf(stderr,\"%u%s\");\n" " snprintf(str,10,\"%u%s\");\n" " 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()); 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()); check("void foo(char *str) {\n" " printf(\"%u\", 0);\n" " printf(\"%u%s\", 123, bar());\n" " printf(\"%u%s%d\", 0, bar(), 43123);\n" " printf(\"%u%%%s%d\", 0, bar(), 43123);\n" " printf(\"%udfd%%dfa%s%d\", 0, bar(), 43123);\n" " printf(\"%\"PRId64\"\n\", 123);\n" " fprintf(stderr,\"%\"PRId64\"\n\", 123);\n" " snprintf(str,10,\"%\"PRId64\"\n\", 123);\n" " fprintf(stderr, \"error: %m\n\");\n" // #3339 " printf(\"string: %.*s\n\", len, string);\n" // #3311 " fprintf(stderr, \"%*cText.\n\", indent, ' ');\n" // #3313 " sprintf(string1, \"%*\", 32);\n" // #3364 "}"); ASSERT_EQUALS("", errout.str()); check("void foo(char* s, const char* s2, std::string s3, int i) {\n" " printf(\"%s%s\", s, s2);\n" " printf(\"%s\", i);\n" " printf(\"%i%s\", i, i);\n" " printf(\"%s\", s3);\n" " printf(\"%s\", \"s4\");\n" " printf(\"%u\", s);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (warning) %s in format string (no. 1) requires a char* given in the argument list\n" "[test.cpp:4]: (warning) %s in format string (no. 2) requires a char* given in the argument list\n" "[test.cpp:5]: (warning) %s in format string (no. 1) requires a char* given in the argument list\n", errout.str()); check("void foo(const int* cpi, const int ci, int i, int* pi, std::string s) {\n" " printf(\"%n\", cpi);\n" " printf(\"%n\", ci);\n" " printf(\"%n\", i);\n" " printf(\"%n\", pi);\n" " printf(\"%n\", s);\n" " printf(\"%n\", \"s4\");\n" "}"); ASSERT_EQUALS("[test.cpp:2]: (warning) %n in format string (no. 1) requires a pointer to an non-const integer given in the argument list\n" "[test.cpp:3]: (warning) %n in format string (no. 1) requires a pointer to an non-const integer given in the argument list\n" "[test.cpp:4]: (warning) %n in format string (no. 1) requires a pointer to an non-const integer given in the argument list\n" "[test.cpp:6]: (warning) %n in format string (no. 1) requires a pointer to an non-const integer given in the argument list\n" "[test.cpp:7]: (warning) %n in format string (no. 1) requires a pointer to an non-const integer given in the argument list\n", errout.str()); check("class foo {};\n" "void foo(const int* cpi, foo f, bar b, bar* bp, double d) {\n" " printf(\"%i\", f);\n" " printf(\"%c\", \"s4\");\n" " printf(\"%o\", d);\n" " printf(\"%i\", cpi);\n" " printf(\"%u\", b);\n" " printf(\"%u\", bp);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (warning) %i in format string (no. 1) requires an integer given in the argument list\n" "[test.cpp:4]: (warning) %c in format string (no. 1) requires an integer given in the argument list\n" "[test.cpp:5]: (warning) %o in format string (no. 1) requires an integer given in the argument list\n", errout.str()); check("class foo {};\n" "void foo(const int* cpi, foo f, bar b, bar* bp, char c) {\n" " printf(\"%p\", f);\n" " printf(\"%p\", c);\n" " printf(\"%p\", bp);\n" " printf(\"%p\", cpi);\n" " printf(\"%p\", b);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (warning) %p in format string (no. 1) requires an integer or pointer given in the argument list\n" "[test.cpp:4]: (warning) %p in format string (no. 1) requires an integer or pointer given in the argument list\n", errout.str()); check("class foo {};\n" "void foo(const int* cpi, foo f, bar b, bar* bp, double d) {\n" " printf(\"%e\", f);\n" " printf(\"%E\", \"s4\");\n" " printf(\"%f\", cpi);\n" " printf(\"%G\", bp);\n" " printf(\"%f\", d);\n" " printf(\"%f\", b);\n" " printf(\"%f\", (float)cpi);\n" "}"); ASSERT_EQUALS("[test.cpp:3]: (warning) %e in format string (no. 1) requires a floating point number given in the argument list\n" "[test.cpp:4]: (warning) %E in format string (no. 1) requires a floating point number given in the argument list\n" "[test.cpp:5]: (warning) %f in format string (no. 1) requires a floating point number given in the argument list\n" "[test.cpp:6]: (warning) %G in format string (no. 1) requires a floating point number given in the argument list\n", errout.str()); check("class foo;\n" "void foo(foo f) {\n" " printf(\"%u\", f);\n" " printf(\"%f\", f);\n" " printf(\"%p\", f);\n" "}"); 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()); } }; REGISTER_TEST(TestIO)