Skip to content

Commit 0845514

Browse files
authored
[clang][analyzer] Add function 'fscanf' to StreamChecker. (#78180)
1 parent 68a5261 commit 0845514

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
266266
{{{"fprintf"}},
267267
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
268268
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
269+
{{{"fscanf"}},
270+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
271+
std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}},
269272
{{{"ungetc"}, 2},
270273
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
271274
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
@@ -345,6 +348,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
345348
void evalFprintf(const FnDescription *Desc, const CallEvent &Call,
346349
CheckerContext &C) const;
347350

351+
void evalFscanf(const FnDescription *Desc, const CallEvent &Call,
352+
CheckerContext &C) const;
353+
348354
void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
349355
CheckerContext &C) const;
350356

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

984+
void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call,
985+
CheckerContext &C) const {
986+
ProgramStateRef State = C.getState();
987+
if (Call.getNumArgs() < 2)
988+
return;
989+
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
990+
if (!StreamSym)
991+
return;
992+
993+
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
994+
if (!CE)
995+
return;
996+
997+
const StreamState *OldSS = State->get<StreamMap>(StreamSym);
998+
if (!OldSS)
999+
return;
1000+
1001+
assertStreamStateOpened(OldSS);
1002+
1003+
SValBuilder &SVB = C.getSValBuilder();
1004+
ASTContext &ACtx = C.getASTContext();
1005+
1006+
// Add the success state.
1007+
// In this context "success" means there is not an EOF or other read error
1008+
// before any item is matched in 'fscanf'. But there may be match failure,
1009+
// therefore return value can be 0 or greater.
1010+
// It is not specified what happens if some items (not all) are matched and
1011+
// then EOF or read error happens. Now this case is handled like a "success"
1012+
// case, and no error flags are set on the stream. This is probably not
1013+
// accurate, and the POSIX documentation does not tell more.
1014+
if (OldSS->ErrorState != ErrorFEof) {
1015+
NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
1016+
ProgramStateRef StateNotFailed =
1017+
State->BindExpr(CE, C.getLocationContext(), RetVal);
1018+
auto RetGeZero =
1019+
SVB.evalBinOp(StateNotFailed, BO_GE, RetVal,
1020+
SVB.makeZeroVal(ACtx.IntTy), SVB.getConditionType())
1021+
.getAs<DefinedOrUnknownSVal>();
1022+
if (!RetGeZero)
1023+
return;
1024+
StateNotFailed = StateNotFailed->assume(*RetGeZero, true);
1025+
1026+
C.addTransition(StateNotFailed);
1027+
}
1028+
1029+
// Add transition for the failed state.
1030+
// Error occurs if nothing is matched yet and reading the input fails.
1031+
// Error can be EOF, or other error. At "other error" FERROR or 'errno' can
1032+
// be set but it is not further specified if all are required to be set.
1033+
// Documentation does not mention, but file position will be set to
1034+
// indeterminate similarly as at 'fread'.
1035+
ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
1036+
StreamErrorState NewES = (OldSS->ErrorState == ErrorFEof)
1037+
? ErrorFEof
1038+
: ErrorNone | ErrorFEof | ErrorFError;
1039+
StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof());
1040+
StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
1041+
if (OldSS->ErrorState != ErrorFEof)
1042+
C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
1043+
else
1044+
C.addTransition(StateFailed);
1045+
}
1046+
9781047
void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
9791048
CheckerContext &C) const {
9801049
ProgramStateRef State = C.getState();

clang/test/Analysis/stream-error.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,31 @@ void error_fprintf(void) {
208208
fprintf(F, "ccc"); // expected-warning {{Stream might be already closed}}
209209
}
210210

211+
void error_fscanf(int *A) {
212+
FILE *F = tmpfile();
213+
if (!F)
214+
return;
215+
int Ret = fscanf(F, "a%ib", A);
216+
if (Ret >= 0) {
217+
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
218+
fscanf(F, "bbb"); // no-warning
219+
} else {
220+
if (ferror(F)) {
221+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
222+
fscanf(F, "bbb"); // expected-warning {{might be 'indeterminate'}}
223+
} else if (feof(F)) {
224+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
225+
fscanf(F, "bbb"); // expected-warning {{is in EOF state}}
226+
clang_analyzer_eval(feof(F)); // expected-warning {{TRUE}}
227+
} else {
228+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
229+
fscanf(F, "bbb"); // expected-warning {{might be 'indeterminate'}}
230+
}
231+
}
232+
fclose(F);
233+
fscanf(F, "ccc"); // expected-warning {{Stream might be already closed}}
234+
}
235+
211236
void error_ungetc() {
212237
FILE *F = tmpfile();
213238
if (!F)

clang/test/Analysis/stream.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ void check_fprintf(void) {
4545
fclose(fp);
4646
}
4747

48+
void check_fscanf(void) {
49+
FILE *fp = tmpfile();
50+
fscanf(fp, "ABC"); // expected-warning {{Stream pointer might be NULL}}
51+
fclose(fp);
52+
}
53+
4854
void check_ungetc(void) {
4955
FILE *fp = tmpfile();
5056
ungetc('A', fp); // expected-warning {{Stream pointer might be NULL}}

0 commit comments

Comments
 (0)