Skip to content

Commit

Permalink
[clang][analyzer] Add function 'fscanf' to StreamChecker. (#78180)
Browse files Browse the repository at this point in the history
  • Loading branch information
balazske committed Jan 22, 2024
1 parent 68a5261 commit 0845514
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 0 deletions.
69 changes: 69 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
{{{"fprintf"}},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
{{{"fscanf"}},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
{{{"ungetc"}, 2},
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
Expand Down Expand Up @@ -345,6 +348,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
void evalFprintf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;

void evalFscanf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;

void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const;

Expand Down Expand Up @@ -975,6 +981,69 @@ void StreamChecker::evalFprintf(const FnDescription *Desc,
C.addTransition(StateFailed);
}

void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
if (Call.getNumArgs() < 2)
return;
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
if (!StreamSym)
return;

const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
if (!CE)
return;

const StreamState *OldSS = State->get<StreamMap>(StreamSym);
if (!OldSS)
return;

assertStreamStateOpened(OldSS);

SValBuilder &SVB = C.getSValBuilder();
ASTContext &ACtx = C.getASTContext();

// Add the success state.
// In this context "success" means there is not an EOF or other read error
// before any item is matched in 'fscanf'. But there may be match failure,
// therefore return value can be 0 or greater.
// It is not specified what happens if some items (not all) are matched and
// then EOF or read error happens. Now this case is handled like a "success"
// case, and no error flags are set on the stream. This is probably not
// accurate, and the POSIX documentation does not tell more.
if (OldSS->ErrorState != ErrorFEof) {
NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
ProgramStateRef StateNotFailed =
State->BindExpr(CE, C.getLocationContext(), RetVal);
auto RetGeZero =
SVB.evalBinOp(StateNotFailed, BO_GE, RetVal,
SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType())
.getAs<DefinedOrUnknownSVal>();
if (!RetGeZero)
return;
StateNotFailed = StateNotFailed->assume(*RetGeZero, true);

C.addTransition(StateNotFailed);
}

// Add transition for the failed state.
// Error occurs if nothing is matched yet and reading the input fails.
// Error can be EOF, or other error. At "other error" FERROR or 'errno' can
// be set but it is not further specified if all are required to be set.
// Documentation does not mention, but file position will be set to
// indeterminate similarly as at 'fread'.
ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof)
? ErrorFEof
: ErrorNone | ErrorFEof | ErrorFError;
StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
if (OldSS->ErrorState != ErrorFEof)
C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
else
C.addTransition(StateFailed);
}

void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
Expand Down
25 changes: 25 additions & 0 deletions clang/test/Analysis/stream-error.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,31 @@ void error_fprintf(void) {
fprintf(F, "ccc"); // expected-warning {{Stream might be already closed}}
}

void error_fscanf(int *A) {
FILE *F = tmpfile();
if (!F)
return;
int Ret = fscanf(F, "a%ib", A);
if (Ret >= 0) {
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
fscanf(F, "bbb"); // no-warning
} else {
if (ferror(F)) {
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
fscanf(F, "bbb"); // expected-warning {{might be 'indeterminate'}}
} else if (feof(F)) {
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
fscanf(F, "bbb"); // expected-warning {{is in EOF state}}
clang_analyzer_eval(feof(F)); // expected-warning {{TRUE}}
} else {
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
fscanf(F, "bbb"); // expected-warning {{might be 'indeterminate'}}
}
}
fclose(F);
fscanf(F, "ccc"); // expected-warning {{Stream might be already closed}}
}

void error_ungetc() {
FILE *F = tmpfile();
if (!F)
Expand Down
6 changes: 6 additions & 0 deletions clang/test/Analysis/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ void check_fprintf(void) {
fclose(fp);
}

void check_fscanf(void) {
FILE *fp = tmpfile();
fscanf(fp, "ABC"); // expected-warning {{Stream pointer might be NULL}}
fclose(fp);
}

void check_ungetc(void) {
FILE *fp = tmpfile();
ungetc('A', fp); // expected-warning {{Stream pointer might be NULL}}
Expand Down

0 comments on commit 0845514

Please sign in to comment.