Skip to content

Commit d5af076

Browse files
authored
[clang][analyzer] Support fputc in StreamChecker (#71518)
1 parent 3b48a7a commit d5af076

File tree

3 files changed

+94
-8
lines changed

3 files changed

+94
-8
lines changed

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,14 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
245245
{{{"fclose"}, 1},
246246
{&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}},
247247
{{{"fread"}, 4},
248-
{std::bind(&StreamChecker::preFreadFwrite, _1, _2, _3, _4, true),
248+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true),
249249
std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}},
250250
{{{"fwrite"}, 4},
251-
{std::bind(&StreamChecker::preFreadFwrite, _1, _2, _3, _4, false),
251+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
252252
std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}},
253+
{{{"fputc"}, 2},
254+
{std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false),
255+
&StreamChecker::evalFputc, 1}},
253256
{{{"fseek"}, 3},
254257
{&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}},
255258
{{{"ftell"}, 1},
@@ -305,12 +308,15 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
305308
void evalFclose(const FnDescription *Desc, const CallEvent &Call,
306309
CheckerContext &C) const;
307310

308-
void preFreadFwrite(const FnDescription *Desc, const CallEvent &Call,
309-
CheckerContext &C, bool IsFread) const;
311+
void preReadWrite(const FnDescription *Desc, const CallEvent &Call,
312+
CheckerContext &C, bool IsRead) const;
310313

311314
void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call,
312315
CheckerContext &C, bool IsFread) const;
313316

317+
void evalFputc(const FnDescription *Desc, const CallEvent &Call,
318+
CheckerContext &C) const;
319+
314320
void preFseek(const FnDescription *Desc, const CallEvent &Call,
315321
CheckerContext &C) const;
316322
void evalFseek(const FnDescription *Desc, const CallEvent &Call,
@@ -634,9 +640,9 @@ void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call,
634640
C.addTransition(StateFailure);
635641
}
636642

637-
void StreamChecker::preFreadFwrite(const FnDescription *Desc,
638-
const CallEvent &Call, CheckerContext &C,
639-
bool IsFread) const {
643+
void StreamChecker::preReadWrite(const FnDescription *Desc,
644+
const CallEvent &Call, CheckerContext &C,
645+
bool IsRead) const {
640646
ProgramStateRef State = C.getState();
641647
SVal StreamVal = getStreamArg(Desc, Call);
642648
State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C,
@@ -650,7 +656,7 @@ void StreamChecker::preFreadFwrite(const FnDescription *Desc,
650656
if (!State)
651657
return;
652658

653-
if (!IsFread) {
659+
if (!IsRead) {
654660
C.addTransition(State);
655661
return;
656662
}
@@ -745,6 +751,45 @@ void StreamChecker::evalFreadFwrite(const FnDescription *Desc,
745751
C.addTransition(StateFailed);
746752
}
747753

754+
void StreamChecker::evalFputc(const FnDescription *Desc, const CallEvent &Call,
755+
CheckerContext &C) const {
756+
ProgramStateRef State = C.getState();
757+
SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
758+
if (!StreamSym)
759+
return;
760+
761+
const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
762+
if (!CE)
763+
return;
764+
765+
const StreamState *OldSS = State->get<StreamMap>(StreamSym);
766+
if (!OldSS)
767+
return;
768+
769+
assertStreamStateOpened(OldSS);
770+
771+
// `fputc` returns the written character on success, otherwise returns EOF.
772+
773+
// Generate a transition for the success state.
774+
std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>();
775+
if (!PutVal)
776+
return;
777+
ProgramStateRef StateNotFailed =
778+
State->BindExpr(CE, C.getLocationContext(), *PutVal);
779+
StateNotFailed =
780+
StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
781+
C.addTransition(StateNotFailed);
782+
783+
// Add transition for the failed state.
784+
// If a (non-EOF) error occurs, the resulting value of the file position
785+
// indicator for the stream is indeterminate.
786+
ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE);
787+
StreamState NewSS = StreamState::getOpened(
788+
Desc, ErrorFError, /*IsFilePositionIndeterminate*/ true);
789+
StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS);
790+
C.addTransition(StateFailed);
791+
}
792+
748793
void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
749794
CheckerContext &C) const {
750795
ProgramStateRef State = C.getState();

clang/test/Analysis/stream-error.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,24 @@ void error_fwrite(void) {
101101
Ret = fwrite(0, 1, 10, F); // expected-warning {{Stream might be already closed}}
102102
}
103103

104+
void error_fputc(void) {
105+
FILE *F = tmpfile();
106+
if (!F)
107+
return;
108+
int Ret = fputc('X', F);
109+
if (Ret == EOF) {
110+
clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
111+
clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
112+
fputc('Y', F); // expected-warning {{might be 'indeterminate'}}
113+
} else {
114+
clang_analyzer_eval(Ret == 'X'); // expected-warning {{TRUE}}
115+
clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}}
116+
fputc('Y', F); // no-warning
117+
}
118+
fclose(F);
119+
fputc('A', F); // expected-warning {{Stream might be already closed}}
120+
}
121+
104122
void freadwrite_zerosize(FILE *F) {
105123
size_t Ret;
106124
Ret = fwrite(0, 1, 0, F);
@@ -241,6 +259,23 @@ void error_indeterminate_clearerr(void) {
241259
fclose(F);
242260
}
243261

262+
void error_indeterminate_fputc(void) {
263+
FILE *F = fopen("file", "r+");
264+
if (!F)
265+
return;
266+
int rc = fseek(F, 0, SEEK_SET);
267+
if (rc) {
268+
if (feof(F)) {
269+
fputc('X', F); // no warning
270+
} else if (ferror(F)) {
271+
fputc('C', F); // expected-warning {{might be 'indeterminate'}}
272+
} else {
273+
fputc('E', F); // expected-warning {{might be 'indeterminate'}}
274+
}
275+
}
276+
fclose(F);
277+
}
278+
244279
void error_indeterminate_feof1(void) {
245280
FILE *F = fopen("file", "r+");
246281
if (!F)

clang/test/Analysis/stream.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ void check_fwrite(void) {
1414
fclose(fp);
1515
}
1616

17+
void check_fputc(void) {
18+
FILE *fp = tmpfile();
19+
fputc('A', fp); // expected-warning {{Stream pointer might be NULL}}
20+
fclose(fp);
21+
}
22+
1723
void check_fseek(void) {
1824
FILE *fp = tmpfile();
1925
fseek(fp, 0, 0); // expected-warning {{Stream pointer might be NULL}}

0 commit comments

Comments
 (0)