diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -128,7 +128,7 @@ {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, {{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3}}, {{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3}}, - {{"fseek", 3}, {&StreamChecker::preFseek, nullptr, 0}}, + {{"fseek", 3}, {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0}}, {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0}}, {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, @@ -154,6 +154,8 @@ void preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void evalFseek(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; void preDefault(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; @@ -340,6 +342,48 @@ C.addTransition(State); } +void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); + if (!StreamSym) + return; + + const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + + // Ignore the call if the stream is not tracked. + if (!State->get(StreamSym)) + return; + + DefinedSVal RetVal = makeRetVal(C, CE); + + // Make expression result. + State = State->BindExpr(CE, C.getLocationContext(), RetVal); + + // Bifurcate the state into failed and non-failed. + // Return zero on success, nonzero on error. + ProgramStateRef StateNotFailed, StateFailed; + std::tie(StateFailed, StateNotFailed) = + C.getConstraintManager().assumeDual(State, RetVal); + + // Reset the state to opened with no error. + StateNotFailed = + StateNotFailed->set(StreamSym, StreamState::getOpened()); + // There are two ways of failure: + // We get some error (ferror or feof). + ProgramStateRef StateFailedWithFError = StateFailed->set( + StreamSym, StreamState::getOpenedWithAnyError()); + // We get none of the error flags. + ProgramStateRef StateFailedWithoutFError = + StateFailed->set(StreamSym, StreamState::getOpened()); + + C.addTransition(StateNotFailed); + C.addTransition(StateFailedWithFError); + C.addTransition(StateFailedWithoutFError); +} + void StreamChecker::evalClearerr(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { diff --git a/clang/test/Analysis/stream-error.c b/clang/test/Analysis/stream-error.c --- a/clang/test/Analysis/stream-error.c +++ b/clang/test/Analysis/stream-error.c @@ -38,3 +38,20 @@ } fclose(F); } + +void error_fseek() { + FILE *F = fopen("file", "r"); + if (!F) + return; + int rc = fseek(F, 0, SEEK_SET); + if (rc) { + int Eof = feof(F), Error = ferror(F); + clang_analyzer_eval(Eof || Error); // expected-warning {{FALSE}} \ + // expected-warning {{TRUE}} + clang_analyzer_eval(Eof && Error); // expected-warning {{FALSE}} + } else { + clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}} + clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}} + } + fclose(F); +}