@@ -250,9 +250,12 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
250250 {{{" fwrite" }, 4 },
251251 {std::bind (&StreamChecker::preReadWrite, _1, _2, _3, _4, false ),
252252 std::bind (&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false ), 3 }},
253+ {{{" fgetc" }, 1 },
254+ {std::bind (&StreamChecker::preReadWrite, _1, _2, _3, _4, true ),
255+ std::bind (&StreamChecker::evalFgetcFputc, _1, _2, _3, _4, true ), 0 }},
253256 {{{" fputc" }, 2 },
254257 {std::bind (&StreamChecker::preReadWrite, _1, _2, _3, _4, false ),
255- &StreamChecker::evalFputc , 1 }},
258+ std::bind ( &StreamChecker::evalFgetcFputc, _1, _2, _3, _4, false ) , 1 }},
256259 {{{" fseek" }, 3 },
257260 {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0 }},
258261 {{{" ftell" }, 1 },
@@ -314,8 +317,8 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
314317 void evalFreadFwrite (const FnDescription *Desc, const CallEvent &Call,
315318 CheckerContext &C, bool IsFread) const ;
316319
317- void evalFputc (const FnDescription *Desc, const CallEvent &Call,
318- CheckerContext &C) const ;
320+ void evalFgetcFputc (const FnDescription *Desc, const CallEvent &Call,
321+ CheckerContext &C, bool IsRead ) const ;
319322
320323 void preFseek (const FnDescription *Desc, const CallEvent &Call,
321324 CheckerContext &C) const ;
@@ -751,8 +754,9 @@ void StreamChecker::evalFreadFwrite(const FnDescription *Desc,
751754 C.addTransition (StateFailed);
752755}
753756
754- void StreamChecker::evalFputc (const FnDescription *Desc, const CallEvent &Call,
755- CheckerContext &C) const {
757+ void StreamChecker::evalFgetcFputc (const FnDescription *Desc,
758+ const CallEvent &Call, CheckerContext &C,
759+ bool IsRead) const {
756760 ProgramStateRef State = C.getState ();
757761 SymbolRef StreamSym = getStreamArg (Desc, Call).getAsSymbol ();
758762 if (!StreamSym)
@@ -768,26 +772,70 @@ void StreamChecker::evalFputc(const FnDescription *Desc, const CallEvent &Call,
768772
769773 assertStreamStateOpened (OldSS);
770774
775+ // `fgetc` returns the read character on success, otherwise returns EOF.
771776 // `fputc` returns the written character on success, otherwise returns EOF.
772777
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);
778+ // Generate a transition for the success state of fputc.
779+ if (!IsRead) {
780+ std::optional<NonLoc> PutVal = Call.getArgSVal (0 ).getAs <NonLoc>();
781+ if (!PutVal)
782+ return ;
783+ ProgramStateRef StateNotFailed =
784+ State->BindExpr (CE, C.getLocationContext (), *PutVal);
785+ StateNotFailed =
786+ StateNotFailed->set <StreamMap>(StreamSym, StreamState::getOpened (Desc));
787+ C.addTransition (StateNotFailed);
788+ }
789+ // Generate a transition for the success state of fgetc.
790+ // If we know the state to be FEOF at fgetc, do not add a success state.
791+ else if (OldSS->ErrorState != ErrorFEof) {
792+ NonLoc RetVal = makeRetVal (C, CE).castAs <NonLoc>();
793+ ProgramStateRef StateNotFailed =
794+ State->BindExpr (CE, C.getLocationContext (), RetVal);
795+ SValBuilder &SVB = C.getSValBuilder ();
796+ auto &ASTC = C.getASTContext ();
797+ // The returned 'unsigned char' of `fgetc` is converted to 'int',
798+ // so we need to check if it is in range [0, 255].
799+ auto CondLow =
800+ SVB.evalBinOp (State, BO_GE, RetVal, SVB.makeZeroVal (ASTC.IntTy ),
801+ SVB.getConditionType ())
802+ .getAs <DefinedOrUnknownSVal>();
803+ auto CondHigh =
804+ SVB.evalBinOp (State, BO_LE, RetVal,
805+ SVB.makeIntVal (SVB.getBasicValueFactory ()
806+ .getMaxValue (ASTC.UnsignedCharTy )
807+ .getLimitedValue (),
808+ ASTC.IntTy ),
809+ SVB.getConditionType ())
810+ .getAs <DefinedOrUnknownSVal>();
811+ if (!CondLow || !CondHigh)
812+ return ;
813+ StateNotFailed = StateNotFailed->assume (*CondLow, true );
814+ if (!StateNotFailed)
815+ return ;
816+ StateNotFailed = StateNotFailed->assume (*CondHigh, true );
817+ if (!StateNotFailed)
818+ return ;
819+ C.addTransition (StateNotFailed);
820+ }
782821
783822 // Add transition for the failed state.
823+ ProgramStateRef StateFailed = bindInt (*EofVal, State, C, CE);
824+
784825 // If a (non-EOF) error occurs, the resulting value of the file position
785826 // indicator for the stream is indeterminate.
786- ProgramStateRef StateFailed = bindInt (*EofVal, State, C, CE);
787- StreamState NewSS = StreamState::getOpened (
788- Desc, ErrorFError, /* IsFilePositionIndeterminate*/ true );
827+ StreamErrorState NewES;
828+ if (IsRead)
829+ NewES =
830+ OldSS->ErrorState == ErrorFEof ? ErrorFEof : ErrorFEof | ErrorFError;
831+ else
832+ NewES = ErrorFError;
833+ StreamState NewSS = StreamState::getOpened (Desc, NewES, !NewES.isFEof ());
789834 StateFailed = StateFailed->set <StreamMap>(StreamSym, NewSS);
790- C.addTransition (StateFailed);
835+ if (IsRead && OldSS->ErrorState != ErrorFEof)
836+ C.addTransition (StateFailed, constructSetEofNoteTag (C, StreamSym));
837+ else
838+ C.addTransition (StateFailed);
791839}
792840
793841void StreamChecker::preFseek (const FnDescription *Desc, const CallEvent &Call,
0 commit comments