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 @@ -122,11 +122,16 @@ FnCheck PreFn; FnCheck EvalFn; ArgNoTy StreamArgNo; - // What errors are possible after this operation. - // Used only if this operation resulted in Unknown state - // (otherwise there is a known single error). - // Must contain 2 or 3 elements, or zero. + // What errors are possible after the operation has failed. llvm::SmallVector PossibleErrors = {}; + + StreamState::ErrorKindTy getErrorKindAfterFailedOperation() const { + assert(!PossibleErrors.empty() && + "Function must be called on operation that can fail."); + if (PossibleErrors.size() == 1) + return PossibleErrors.front(); + return StreamState::Unknown; + } }; Optional @@ -199,17 +204,31 @@ {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone, {}}}, {{"fclose", 1}, {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0, {}}}, - {{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3, {}}}, - {{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3, {}}}, + {{"fread", 4}, + {&StreamChecker::preDefault, + &StreamChecker::evalFreadFwrite, + 3, + {StreamState::FError, StreamState::FEof}}}, + {{"fwrite", 4}, + {&StreamChecker::preDefault, + &StreamChecker::evalFreadFwrite, + 3, + {StreamState::FError}}}, {{"fseek", 3}, {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0, {StreamState::FEof, StreamState::FError, StreamState::NoError}}}, - {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}}, + // Note: ftell sets errno only. + {{"ftell", 1}, + {&StreamChecker::preDefault, nullptr, 0, {StreamState::NoError}}}, {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}}, - {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0, {}}}, - {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0, {}}}, + // Note: fgetpos sets errno only. + {{"fgetpos", 2}, + {&StreamChecker::preDefault, nullptr, 0, {StreamState::NoError}}}, + // Note: fsetpos sets errno only. + {{"fsetpos", 2}, + {&StreamChecker::preDefault, nullptr, 0, {StreamState::NoError}}}, {{"clearerr", 1}, {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0, {}}}, // Note: feof can result in Unknown if at the call there is a @@ -249,17 +268,20 @@ void evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + 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; - void evalClearerr(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void preDefault(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + template void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; @@ -435,6 +457,65 @@ C.addTransition(State); } +void StreamChecker::evalFreadFwrite(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; + + Optional CountVal = Call.getArgSVal(2).castAs(); + if (!CountVal) + return; + + const StreamState *SS = State->get(StreamSym); + if (!SS) + return; + + assertStreamStateOpened(SS); + + if (State->isNull(*CountVal).isConstrainedTrue()) { + // If `count` is zero, return 0. + State = bindInt(0, State, C, CE); + State = State->set(StreamSym, StreamState::getOpened(Desc)); + C.addTransition(State); + return; + } + + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), *CountVal); + if (StateNotFailed) { + StateNotFailed = + StateNotFailed->set(StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateNotFailed); + } + + Optional RetVal = makeRetVal(C, CE).castAs(); + assert(RetVal && "Value should be NonLoc."); + ProgramStateRef StateFailed = + State->BindExpr(CE, C.getLocationContext(), *RetVal); + if (!StateFailed) + return; + auto Cond = C.getSValBuilder() + .evalBinOpNN(State, BO_LT, *RetVal, *CountVal, + C.getASTContext().IntTy) + .getAs(); + if (!Cond) + return; + StateFailed = StateFailed->assume(*Cond, true); + if (!StateFailed) + return; + StateFailed = StateFailed->set( + StreamSym, + StreamState::getOpened(Desc, Desc->getErrorKindAfterFailedOperation())); + C.addTransition(StateFailed); +} + void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); 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 @@ -3,6 +3,7 @@ #include "Inputs/system-header-simulator.h" void clang_analyzer_eval(int); +void clang_analyzer_warnIfReached(); void StreamTesterChecker_make_feof_stream(FILE *); void StreamTesterChecker_make_ferror_stream(FILE *); @@ -53,6 +54,39 @@ fclose(F); } +void error_fread() { + FILE *F = tmpfile(); + if (!F) + return; + int Ret = fread(0, 1, 10, F); + if (Ret == 10) { + clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}} + } else { + clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{TRUE}} + if (feof(F)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + if (ferror(F)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + } + fclose(F); + Ret = fread(0, 1, 10, F); // expected-warning {{Stream might be already closed}} +} + +void error_fwrite() { + FILE *F = tmpfile(); + if (!F) + return; + int Ret = fwrite(0, 1, 10, F); + if (Ret == 10) { + clang_analyzer_eval(feof(F) || ferror(F)); // expected-warning {{FALSE}} + } else { + clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}} + clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}} + } + fclose(F); + Ret = fwrite(0, 1, 10, F); // expected-warning {{Stream might be already closed}} +} + void error_fseek() { FILE *F = fopen("file", "r"); if (!F)