@@ -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+
748793void StreamChecker::preFseek (const FnDescription *Desc, const CallEvent &Call,
749794 CheckerContext &C) const {
750795 ProgramStateRef State = C.getState ();
0 commit comments