diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index 5081ff63102b3..bad86682c91e2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -285,7 +285,14 @@ class StreamChecker : public Checker EofVal; + /// Expanded value of SEEK_SET, 0 if not found. + mutable int SeekSetVal = 0; + /// Expanded value of SEEK_CUR, 1 if not found. + mutable int SeekCurVal = 1; + /// Expanded value of SEEK_END, 2 if not found. + mutable int SeekEndVal = 2; void evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; @@ -432,7 +439,7 @@ class StreamChecker : public Checker OptInt = + tryExpandAsInteger("SEEK_SET", C.getPreprocessor())) + SeekSetVal = *OptInt; + if (const std::optional OptInt = + tryExpandAsInteger("SEEK_END", C.getPreprocessor())) + SeekEndVal = *OptInt; + if (const std::optional OptInt = + tryExpandAsInteger("SEEK_CUR", C.getPreprocessor())) + SeekCurVal = *OptInt; } /// Searches for the ExplodedNode where the file descriptor was acquired for @@ -488,7 +504,7 @@ const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, void StreamChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - initEof(C); + initMacroValues(C); const FnDescription *Desc = lookupFn(Call); if (!Desc || !Desc->PreFn) @@ -786,6 +802,11 @@ void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, if (!State->get(StreamSym)) return; + const llvm::APSInt *PosV = + C.getSValBuilder().getKnownValue(State, Call.getArgSVal(1)); + const llvm::APSInt *WhenceV = + C.getSValBuilder().getKnownValue(State, Call.getArgSVal(2)); + DefinedSVal RetVal = makeRetVal(C, CE); // Make expression result. @@ -804,9 +825,12 @@ void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, // It is possible that fseek fails but sets none of the error flags. // If fseek failed, assume that the file position becomes indeterminate in any // case. + StreamErrorState NewErrS = ErrorNone | ErrorFError; + // Setting the position to start of file never produces EOF error. + if (!(PosV && *PosV == 0 && WhenceV && *WhenceV == SeekSetVal)) + NewErrS = NewErrS | ErrorFEof; StateFailed = StateFailed->set( - StreamSym, - StreamState::getOpened(Desc, ErrorNone | ErrorFEof | ErrorFError, true)); + StreamSym, StreamState::getOpened(Desc, NewErrS, true)); C.addTransition(StateNotFailed); C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); @@ -1153,7 +1177,7 @@ StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, return State; int64_t X = CI->getValue().getSExtValue(); - if (X >= 0 && X <= 2) + if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal) return State; if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { diff --git a/clang/test/Analysis/stream-error.c b/clang/test/Analysis/stream-error.c index cfb642926a3ff..3c00e59bb6bd1 100644 --- a/clang/test/Analysis/stream-error.c +++ b/clang/test/Analysis/stream-error.c @@ -146,7 +146,7 @@ void error_fseek(void) { FILE *F = fopen("file", "r"); if (!F) return; - int rc = fseek(F, 0, SEEK_SET); + int rc = fseek(F, 1, SEEK_SET); if (rc) { int IsFEof = feof(F), IsFError = ferror(F); // Get feof or ferror or no error. @@ -173,6 +173,35 @@ void error_fseek(void) { fclose(F); } +void error_fseek_0(void) { + FILE *F = fopen("file", "r"); + if (!F) + return; + int rc = fseek(F, 0, SEEK_SET); + if (rc) { + int IsFEof = feof(F), IsFError = ferror(F); + // Get ferror or no error, but not feof. + clang_analyzer_eval(IsFError); + // expected-warning@-1 {{FALSE}} + // expected-warning@-2 {{TRUE}} + clang_analyzer_eval(IsFEof); + // expected-warning@-1 {{FALSE}} + // Error flags should not change. + clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}} + if (IsFError) + clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}} + else + clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}} + } else { + clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}} + clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}} + // Error flags should not change. + clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}} + clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}} + } + fclose(F); +} + void error_indeterminate(void) { FILE *F = fopen("file", "r+"); if (!F)