Skip to content

Fix #958: warn when feof() is used as a while loop condition#8422

Open
francois-berder wants to merge 4 commits intocppcheck-opensource:mainfrom
francois-berder:pr-958
Open

Fix #958: warn when feof() is used as a while loop condition#8422
francois-berder wants to merge 4 commits intocppcheck-opensource:mainfrom
francois-berder:pr-958

Conversation

@francois-berder
Copy link
Copy Markdown
Collaborator

feof() only returns true after a read has already failed, causing the loop body to execute once more after the last successful read. Read errors also go undetected since feof() does not distinguish them from EOF.

Comment thread test/testio.cpp Outdated
Comment thread lib/checkio.cpp
Comment thread lib/checkio.cpp Outdated
@chrchr-github
Copy link
Copy Markdown
Collaborator

How does the check handle do ... while loops? Should we skip those?
There are obviously other ways to check the return value of feof, e.g. using ==. Another extension would be handling of for loops, but maybe that could be added later. @danmar

Comment thread lib/checkio.cpp Outdated
Comment thread lib/checkio.cpp Outdated
Comment thread test/testio.cpp Outdated
@danmar
Copy link
Copy Markdown
Collaborator

danmar commented Apr 9, 2026

thank you for adding this checker! it's good work. small and simple. just try to make it more precise.
please add a line in the release notes.

@sonarqubecloud
Copy link
Copy Markdown

@danmar
Copy link
Copy Markdown
Collaborator

danmar commented Apr 25, 2026

I hope some of those CI failures are relatively easy to fix. When you added a checker the checker count increased..

Can you please add a document that explains this checker in cppcheck/man/checkers folder. My long term goal is that all checkers will have a document there.

… condition

feof() only returns true after a read has already failed, causing
the loop body to execute once more after the last successful read.
Read errors also go undetected since feof() does not distinguish
them from EOF.

Signed-off-by: Francois Berder <fberder@outlook.fr>
@francois-berder
Copy link
Copy Markdown
Collaborator Author

francois-berder commented Apr 29, 2026

@danmar I rebased the PR on top of main and fixed the issues you mentioned. I think this PR is now again ready for review. The single CI failure seems to be a timeout unrelated to this PR.

Comment thread lib/checkio.cpp
// Usage of feof is correct if a read happens before and within the loop.
// However, if we find a control flow statement in between the fileReadCall
// token and the while loop condition, then we bail out.
const Token *endCond = tok->linkAt(1);
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 would prefer an explicit check somewhere that the code is as expected.

    if (!Token::simpleMatch(endCond, ") {"))
        continue;

Comment thread lib/checkio.cpp
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
// TODO: Handle do-while and for loops
if (!Token::simpleMatch(tok, "while ( ! feof ("))
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.

your code expects that the argument is just a variable. I like to make match patterns explicit..

    Token::simpleMatch(tok, "while ( ! feof ( %var% )")

Comment thread lib/checkio.cpp

// Bail out if we cannot identify file pointer
const int fpVarId = tok->tokAt(5)->varId();
if (fpVarId == 0)
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 does not identify the file pointer very well. varid can be nonzero in some such patterns:

    feof(files[10])
    feof(index + files);
    feof(data.fp);

Comment thread lib/checkio.cpp

static const Token* findFileReadCall(const Token *start, const Token *end, int varid)
{
const Token* found = Token::findmatch(start, "fgets|fgetc|getc|fread|fscanf (", end);
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.

Imagine such function call:

    skip_empty_lines(f);

If skip_empty_lines is not defined in the current TU (translation unit) then you need to bailout somehow so there are not false positives.
If skip_empty_lines is defined in the current TU you could look into that and determine if there is file read but I am not against that we bailout for that case also.

If you pass f to a function it's highly likely it is to read it.. why else would it be passed to the function since you use feof on f also..

@danmar
Copy link
Copy Markdown
Collaborator

danmar commented May 1, 2026

The single CI failure seems to be a timeout unrelated to this PR.

That CI test is a bit flaky. :-(

So if that happens some time again you can assume that it's not related.

Comment thread lib/checkio.cpp
const Token *endCond = tok->linkAt(1);
const Token *endBody = endCond->linkAt(1);

const Token *prevFileReadCallTok = findFileReadCall(scope->bodyStart, tok, fpVarId);
Copy link
Copy Markdown
Collaborator

@danmar danmar May 1, 2026

Choose a reason for hiding this comment

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

Sorry for misleading you but it doesn't matter if the file is read before the loop. What matters is the order of feof, read, use in the loop..

No matter if the file is read before the loop or not..

  // this loop is safe
  while (!feof(f)) {
    puts(line); // use
    fgets(line, sizeof(line), f); // read
  }

  // I guess this loop is "safe". just skip some number of lines..
  while (!feof(f) && --count > 0) {
    fgets(line, sizeof(line), f);  // read
  }

  // you can warn about this:
  while (!feof(f)) {
    fgets(line, sizeof(line), f); // read
    puts(line); // use
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants