Skip to content

Commit 853ba8a

Browse files
fixup! fixup! fixup! fixup! Fix #958: warn when feof() is used as a while loop condition
1 parent e835fca commit 853ba8a

2 files changed

Lines changed: 97 additions & 16 deletions

File tree

lib/checkio.cpp

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -532,34 +532,96 @@ void CheckIO::checkWrongfeofUsage()
532532

533533
for (const Scope * scope : symbolDatabase->functionScopes) {
534534
for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
535-
// TODO: Handle do-while and for loops
536-
if (!Token::simpleMatch(tok, "while ( ! feof ("))
537-
continue;
538-
539-
// Bail out if we reach a do-while loop
540-
if (Token::simpleMatch(tok->previous(), "}") && Token::simpleMatch(tok->linkAt(-1)->previous(), "do"))
535+
// TODO: Handle for loops
536+
if (!Token::Match(tok, "while ( ! feof ( %var% )"))
541537
continue;
542538

543539
// Bail out if we cannot identify file pointer
544540
const int fpVarId = tok->tokAt(5)->varId();
545541
if (fpVarId == 0)
546542
continue;
547543

548-
// Usage of feof is correct if a read happens before and within the loop.
549-
// However, if we find a control flow statement in between the fileReadCall
550-
// token and the while loop condition, then we bail out.
551544
const Token *endCond = tok->linkAt(1);
552-
const Token *endBody = endCond->linkAt(1);
545+
const Token *bodyStart;
546+
const Token *bodyEnd;
553547

554-
const Token *prevFileReadCallTok = findFileReadCall(scope->bodyStart, tok, fpVarId);
555-
const Token *loopFileReadCallTok = findFileReadCall(tok, endBody, fpVarId);
556-
const Token *prevControlFlowTok = Token::findmatch(prevFileReadCallTok, "return|break|goto|continue|throw", tok);
557-
const Token *loopControlFlowTok = Token::findmatch(tok, "return|break|goto|continue|throw", loopFileReadCallTok);
548+
if (Token::simpleMatch(tok->previous(), "}") && tok->previous()->scope()->type == ScopeType::eDo) {
549+
bodyEnd = tok->previous();
550+
bodyStart = bodyEnd->link();
551+
} else {
552+
bodyEnd = endCond->linkAt(1);
553+
bodyStart = endCond->next();
554+
}
558555

559-
if (prevFileReadCallTok && loopFileReadCallTok && !prevControlFlowTok && !loopControlFlowTok)
556+
// Bail out if the loop contains control flow (too complex to analyze)
557+
if (Token::findmatch(bodyStart, "return|break|goto|continue|throw", bodyEnd))
560558
continue;
561559

562-
wrongfeofUsage(getCondTok(tok));
560+
// Bail out if fp is used outside of known file I/O functions.
561+
// If it is passed to an unknown function, reads may occur there.
562+
bool fpUsedElsewhere = false;
563+
for (const Token *t = bodyStart->next(); t && t != bodyEnd; t = t->next()) {
564+
if (t->varId() != fpVarId)
565+
continue;
566+
const Token *p = t->astParent();
567+
while (p && p->str() == ",")
568+
p = p->astParent();
569+
if (!p || !Token::Match(p->astOperand1(), "fgets|fgetc|getc|fread|fscanf|fprintf|fwrite|fputs|fputc|putc")) {
570+
fpUsedElsewhere = true;
571+
break;
572+
}
573+
}
574+
if (fpUsedElsewhere)
575+
continue;
576+
577+
// No file read call in the loop: feof can never become true inside it
578+
const Token *loopFileReadCallTok = findFileReadCall(bodyStart, bodyEnd, fpVarId);
579+
if (!loopFileReadCallTok) {
580+
// TODO: Warn about infinite loop
581+
continue;
582+
}
583+
584+
// Find last file read
585+
const Token *lastLoopFileReadCallTok = loopFileReadCallTok;
586+
while (loopFileReadCallTok) {
587+
lastLoopFileReadCallTok = loopFileReadCallTok;
588+
loopFileReadCallTok = findFileReadCall(lastLoopFileReadCallTok->next(), bodyEnd, fpVarId);
589+
}
590+
591+
// Warn if the destination of the last file read is used after the call before bodyEnd.
592+
// If it is not, the stale buffer is never accessed on the extra iteration at EOF.
593+
594+
if (lastLoopFileReadCallTok->str() == "fgetc" || lastLoopFileReadCallTok->str() == "getc") {
595+
// Warn if the return value feeds into an expression (astParent of the call node)
596+
if (lastLoopFileReadCallTok->astParent() && lastLoopFileReadCallTok->astParent()->astParent())
597+
wrongfeofUsage(getCondTok(tok));
598+
} else {
599+
const std::vector<const Token*> args = getArguments(lastLoopFileReadCallTok);
600+
// Collect destination varIds
601+
std::vector<nonneg int> destVarIds;
602+
if (lastLoopFileReadCallTok->str() == "fscanf") {
603+
// args[0]=fp, args[1]=format, args[2+]=destinations (typically &var)
604+
for (std::size_t i = 2; i < args.size(); ++i) {
605+
const Token *destTok = Token::Match(args[i], "& %var%") ? args[i]->next() : args[i];
606+
if (destTok->varId() != 0)
607+
destVarIds.push_back(destTok->varId());
608+
}
609+
} else {
610+
// Handle fgets, fread
611+
// First argument is the destination buffer
612+
if (!args.empty() && args.front()->varId() != 0)
613+
destVarIds.push_back(args.front()->varId());
614+
}
615+
616+
// Search for any destination use between this call's ';' and endBody
617+
const Token *SemiColonTok = lastLoopFileReadCallTok->linkAt(1)->next();
618+
for (const Token *t = SemiColonTok; t && t != bodyEnd; t = t->next()) {
619+
if (std::find(destVarIds.begin(), destVarIds.end(), t->varId()) != destVarIds.end()) {
620+
wrongfeofUsage(getCondTok(tok));
621+
break;
622+
}
623+
}
624+
}
563625
}
564626
}
565627
}

test/testio.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,7 @@ class TestIO : public TestFixture {
773773
" {\n"
774774
" char line[100];\n"
775775
" fgets(line, sizeof(line), fp);\n"
776+
" dostuff(line);\n"
776777
" }\n"
777778
"}");
778779
ASSERT_EQUALS("[test.cpp:2:10]: (warning) Using feof() as a loop condition causes the last line to be processed twice. [wrongfeofUsage]\n", errout_str());
@@ -795,6 +796,24 @@ class TestIO : public TestFixture {
795796
" }\n"
796797
"}");
797798
ASSERT_EQUALS("", errout_str());
799+
800+
check("void foo(FILE *fp) {\n"
801+
" char line[100];\n"
802+
" do {\n"
803+
" fgets(line, sizeof(line), fp);\n"
804+
" dostuff(line);\n"
805+
" } while (!feof(fp));\n"
806+
"}");
807+
ASSERT_EQUALS("[test.cpp:6:12]: (warning) Using feof() as a loop condition causes the last line to be processed twice. [wrongfeofUsage]\n", errout_str());
808+
809+
check("void foo(FILE *fp) {\n"
810+
" char line[100];\n"
811+
" do {\n"
812+
" dostuff(line);\n"
813+
" fgets(line, sizeof(line), fp);\n"
814+
" } while (!feof(fp));\n"
815+
"}");
816+
ASSERT_EQUALS("", errout_str());
798817
}
799818

800819

0 commit comments

Comments
 (0)