Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/checkio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,28 @@ void CheckIO::checkFileUsage()
} else if (tok->str() == "fclose") {
fileTok = tok->tokAt(2);
operation = Filepointer::Operation::CLOSE;

// #1473 Check if fclose is in a while loop condition
if (fileTok && fileTok->isVariable()) {
const Token* loopTok = tok->astTop()->previous();

if (loopTok && loopTok->str() == "while") {
const Token* bodyEnd = nullptr;
const Token* bodyStart = nullptr;

if (Token::simpleMatch(loopTok->previous(), "}") && loopTok->previous()->scope()->type == ScopeType::eDo) { // Handle do-while loops
bodyEnd = loopTok->previous();
bodyStart = bodyEnd->link();
} else {
bodyStart = loopTok->linkAt(1)->next();
bodyEnd = bodyStart->link();
}

// Do not trigger a warning if the loop always exits or if the file is opened again in the loop.
if (!isReturnScope(bodyEnd, mSettings->library) && Token::findmatch(bodyStart, "%var% =", bodyEnd, fileTok->varId()) == nullptr)
fcloseInLoopConditionError(tok, fileTok->str());
}
}
} else if (whitelist.find(tok->str()) != whitelist.end()) {
fileTok = tok->tokAt(2);
if ((tok->str() == "ungetc" || tok->str() == "ungetwc") && fileTok)
Expand Down Expand Up @@ -392,6 +414,15 @@ void CheckIO::useClosedFileError(const Token *tok)
"useClosedFile", "Used file that is not opened.", CWE910, Certainty::normal);
}

void CheckIO::fcloseInLoopConditionError(const Token *tok, const std::string &varname)
{
reportError(tok, Severity::warning,
"fcloseInLoopCondition",
"fclose() should not be used as a loop condition.\n"
Copy link
Copy Markdown
Collaborator

@danmar danmar May 4, 2026

Choose a reason for hiding this comment

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

could there be some safe usage where fclose is used in the loop condition by intention:

  while (fclose(f)) {
    ..dostuff..
    f = fopen(..);
  }

it would be interesting if you would run test-my-pr.py and see if we generate any warnings..

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.

let's consider how to make the short message a bit more specific about what problems there are.

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 spent a bit of time looking for the right message. In the end, I concluded that we should nearly always warn if fclose is used in a while loop condition simply because it is a code smell. This is why, I also set the severity of the message to warning to better reflect the fact that it may not always cause issues.

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.

it would be interesting if you would run test-my-pr.py and see if we generate any warnings..

Good point, I will try that but it takes a little while to run. By the way, I spent a bit of time improving the various scripts (daca2-download.py, daca2-getpackages.py and test-my-pr.py) but it is not yet ready.

"fclose() closes '" + varname + "' each time it is evaluated. On success the loop body might never execute, on failure fclose() might be called again on the already-closed file handle.",
CWE910, Certainty::normal);
}

void CheckIO::seekOnAppendedFileError(const Token *tok)
{
reportError(tok, Severity::warning,
Expand Down Expand Up @@ -2042,6 +2073,7 @@ void CheckIO::getErrorMessages(ErrorLogger *errorLogger, const Settings *setting
c.readWriteOnlyFileError(nullptr);
c.writeReadOnlyFileError(nullptr);
c.useClosedFileError(nullptr);
c.fcloseInLoopConditionError(nullptr, "fp");
c.seekOnAppendedFileError(nullptr);
c.incompatibleFileOpenError(nullptr, "tmp");
c.invalidScanfError(nullptr);
Expand Down
1 change: 1 addition & 0 deletions lib/checkio.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class CPPCHECKLIB CheckIO : public Check {
void readWriteOnlyFileError(const Token *tok);
void writeReadOnlyFileError(const Token *tok);
void useClosedFileError(const Token *tok);
void fcloseInLoopConditionError(const Token *tok, const std::string &varname);
void seekOnAppendedFileError(const Token *tok);
void incompatibleFileOpenError(const Token *tok, const std::string &filename);
void invalidScanfError(const Token *tok);
Expand Down
24 changes: 23 additions & 1 deletion test/testio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,29 @@ class TestIO : public TestFixture {
" FILE *a = fopen(\"aa\", \"r\");\n"
" while (fclose(a)) {}\n"
"}");
TODO_ASSERT_EQUALS("[test.cpp:3:5]: (error) Used file that is not opened. [useClosedFile]\n", "", errout_str());
ASSERT_EQUALS("[test.cpp:3:12]: (warning) fclose() should not be used as a loop condition. [fcloseInLoopCondition]\n", errout_str());

check("void foo() {\n"
" FILE *a = fopen(\"aa\", \"r\");\n"
" while (fclose(a)) {\n"
" break;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout_str());

check("void foo() {\n"
" FILE *a = fopen(\"aa\", \"r\");\n"
" while (fclose(a)) {\n"
" a = fopen(\"aa\", \"r\");\n"
" }\n"
"}");
ASSERT_EQUALS("", errout_str());

check("void foo() {\n"
" FILE *a = fopen(\"aa\", \"r\");\n"
" do {} while (fclose(a));\n"
"}");
ASSERT_EQUALS("[test.cpp:3:18]: (warning) fclose() should not be used as a loop condition. [fcloseInLoopCondition]\n", errout_str());

// #6823
check("void foo() {\n"
Expand Down
Loading