Skip to content

Commit 8550e88

Browse files
authored
[clang][analyzer] Add function 'fprintf' to StreamChecker. (#77613)
[clang][analyzer] Add function 'fprintf' to StreamChecker.
1 parent a300b24 commit 8550e88

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
263263
{{{"fputs"}, 2},
264264
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
265265
std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}},
266+
{{{"fprintf"}},
267+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
268+
std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}},
266269
{{{"ungetc"}, 2},
267270
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
268271
std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}},
@@ -339,6 +342,9 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
339342
void evalFputx(const FnDescription *Desc, const CallEvent &Call,
340343
CheckerContext &C, bool IsSingleChar) const;
341344

345+
void evalFprintf(const FnDescription *Desc, const CallEvent &Call,
346+
CheckerContext &C) const;
347+
342348
void evalUngetc(const FnDescription *Desc, const CallEvent &Call,
343349
CheckerContext &C) const;
344350

@@ -926,6 +932,49 @@ void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call,
926932
C.addTransition(StateFailed);
927933
}
928934

935+
void StreamChecker::evalFprintf(const FnDescription *Desc,
936+
const CallEvent &Call,
937+
CheckerContext &C) const {
938+
ProgramStateRef State = C.getState();
939+
if (Call.getNumArgs() < 2)
940+
return;
941+
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
942+
if (!StreamSym)
943+
return;
944+
945+
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
946+
if (!CE)
947+
return;
948+
949+
const StreamState *OldSS = State->get<StreamMap>(StreamSym);
950+
if (!OldSS)
951+
return;
952+
953+
assertStreamStateOpened(OldSS);
954+
955+
NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
956+
State = State->BindExpr(CE, C.getLocationContext(), RetVal);
957+
SValBuilder &SVB = C.getSValBuilder();
958+
auto &ACtx = C.getASTContext();
959+
auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, SVB.makeZeroVal(ACtx.IntTy),
960+
SVB.getConditionType())
961+
.getAs<DefinedOrUnknownSVal>();
962+
if (!Cond)
963+
return;
964+
ProgramStateRef StateNotFailed, StateFailed;
965+
std::tie(StateNotFailed, StateFailed) = State->assume(*Cond);
966+
967+
StateNotFailed =
968+
StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
969+
C.addTransition(StateNotFailed);
970+
971+
// Add transition for the failed state. The resulting value of the file
972+
// position indicator for the stream is indeterminate.
973+
StateFailed = StateFailed->set<StreamMap>(
974+
StreamSym, StreamState::getOpened(Desc, ErrorFError, true));
975+
C.addTransition(StateFailed);
976+
}
977+
929978
void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call,
930979
CheckerContext &C) const {
931980
ProgramStateRef State = C.getState();

clang/test/Analysis/stream-error.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,23 @@ void error_fputs(void) {
191191
fputs("ABC", F); // expected-warning {{Stream might be already closed}}
192192
}
193193

194+
void error_fprintf(void) {
195+
FILE *F = tmpfile();
196+
if (!F)
197+
return;
198+
int Ret = fprintf(F, "aaa");
199+
if (Ret >= 0) {
200+
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
201+
fprintf(F, "bbb"); // no-warning
202+
} else {
203+
clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
204+
clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
205+
fprintf(F, "bbb"); // expected-warning {{might be 'indeterminate'}}
206+
}
207+
fclose(F);
208+
fprintf(F, "ccc"); // expected-warning {{Stream might be already closed}}
209+
}
210+
194211
void error_ungetc() {
195212
FILE *F = tmpfile();
196213
if (!F)

clang/test/Analysis/stream.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ void check_fputs(void) {
3939
fclose(fp);
4040
}
4141

42+
void check_fprintf(void) {
43+
FILE *fp = tmpfile();
44+
fprintf(fp, "ABC"); // expected-warning {{Stream pointer might be NULL}}
45+
fclose(fp);
46+
}
47+
4248
void check_ungetc(void) {
4349
FILE *fp = tmpfile();
4450
ungetc('A', fp); // expected-warning {{Stream pointer might be NULL}}
@@ -271,9 +277,8 @@ void check_escape4(void) {
271277
return;
272278
fwrite("1", 1, 1, F); // may fail
273279

274-
// no escape at (non-StreamChecker-handled) system call
275-
// FIXME: all such calls should be handled by the checker
276-
fprintf(F, "0");
280+
// no escape at a non-StreamChecker-handled system call
281+
setbuf(F, "0");
277282

278283
fwrite("1", 1, 1, F); // expected-warning {{might be 'indeterminate'}}
279284
fclose(F);

0 commit comments

Comments
 (0)