Skip to content
Open
31 changes: 31 additions & 0 deletions lib/checkio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,36 @@ void CheckIO::invalidScanfError(const Token *tok)
CWE119, Certainty::normal);
}

void CheckIO::checkWrongfeofUsage()
{
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();

logChecker("CheckIO::checkWrongfeofUsage");

for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (Token::simpleMatch(tok, "while (")) {
const Token* cond = getCondTok(tok);
if (!cond)
continue;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code is redundant. simpleMatch returns false if the token is null

Suggested change
if (!cond)
continue;

if (Token::Match(cond, "! feof (")) {
Comment thread
chrchr-github marked this conversation as resolved.
Outdated
wrongfeofUsage(cond);
}
}
}
}
}

void CheckIO::wrongfeofUsage(const Token * tok)
{
reportError(tok, Severity::warning,
"wrongfeofUsage",
"Using feof() as a loop condition may cause the last line to be processed twice.\n"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want that you remove the word "may". Can you somehow check if it is actually processed twice.

if there is no bug we shouldn't warn. example:

   char line[100];
   fgets(line, sizeof(line), f);
   while (!feof(f)) {
        dostuff(line);
        fgets(line, sizeof(line), f);
    }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I improved this check by looking for a file read call before the loop and within the loop. This should cover this case but I am aware that it will lead to some FP/FN (cause by break statements in the loop body).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should cover this case but I am aware that it will lead to some FP/FN (cause by break statements in the loop body).

I would like that you try to implement it conservatively to avoid false positives. If breaks can cause false positives in some cases then a bailout if there is a break is OK.

"feof() returns true only after a read has failed due to end-of-file, so the loop "
"body executes once more after the last successful read. Check the return value of "
"the read function instead (e.g. fgets, fread, fscanf).");
}

//---------------------------------------------------------------------------
// printf("%u", "xyz"); // Wrong argument type
// printf("%u%s", 1); // Too few arguments
Expand Down Expand Up @@ -2031,6 +2061,7 @@ void CheckIO::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger)
checkIO.checkWrongPrintfScanfArguments();
checkIO.checkCoutCerrMisusage();
checkIO.checkFileUsage();
checkIO.checkWrongfeofUsage();
checkIO.invalidScanf();
}

Expand Down
4 changes: 4 additions & 0 deletions lib/checkio.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class CPPCHECKLIB CheckIO : public Check {
/** @brief scanf can crash if width specifiers are not used */
void invalidScanf();

/** @brief %Check wrong usage of feof */
void checkWrongfeofUsage();

/** @brief %Checks type and number of arguments given to functions like printf or scanf*/
void checkWrongPrintfScanfArguments();

Expand Down Expand Up @@ -108,6 +111,7 @@ class CPPCHECKLIB CheckIO : public Check {
void seekOnAppendedFileError(const Token *tok);
void incompatibleFileOpenError(const Token *tok, const std::string &filename);
void invalidScanfError(const Token *tok);
void wrongfeofUsage(const Token *tok);
void wrongPrintfScanfArgumentsError(const Token* tok,
const std::string &functionName,
nonneg int numFormat,
Expand Down
14 changes: 14 additions & 0 deletions test/testio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class TestIO : public TestFixture {
TEST_CASE(seekOnAppendedFile);
TEST_CASE(fflushOnInputStream);
TEST_CASE(incompatibleFileOpen);
TEST_CASE(testWrongfeofUsage); // #958

TEST_CASE(testScanf1); // Scanf without field limiters
TEST_CASE(testScanf2);
Expand Down Expand Up @@ -743,6 +744,19 @@ class TestIO : public TestFixture {
ASSERT_EQUALS("[test.cpp:3:16]: (warning) The file '\"tmp\"' is opened for read and write access at the same time on different streams [incompatibleFileOpen]\n", errout_str());
}

void testWrongfeofUsage() { // ticket #958
check("void foo() {\n"
Comment thread
chrchr-github marked this conversation as resolved.
Outdated
" FILE * fp = fopen(\"test.txt\", \"r\");\n"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pass fp as parameter. then the testcase can be 2 lines shorter (no open/close).

" while (!feof(fp)) \n"
" {\n"
" char line[100];\n"
" fgets(line, sizeof(line), fp);\n"
" }\n"
" fclose(fp);\n"
"}");
ASSERT_EQUALS("[test.cpp:3:10]: (warning) Using feof() as a loop condition may cause the last line to be processed twice. [wrongfeofUsage]\n", errout_str());
}


void testScanf1() {
check("void foo() {\n"
Expand Down
Loading