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 @@ -27,24 +27,45 @@ namespace { +/// Full state information about a stream pointer. struct StreamState { - enum Kind { Opened, Closed, OpenFailed, Escaped } K; - - StreamState(Kind k) : K(k) {} - - bool isOpened() const { return K == Opened; } - bool isClosed() const { return K == Closed; } - bool isOpenFailed() const { return K == OpenFailed; } - // bool isEscaped() const { return K == Escaped; } - - bool operator==(const StreamState &X) const { return K == X.K; } + /// State of a stream symbol. + enum KindTy { + Opened, /// Stream is opened. + Closed, /// Closed stream (an invalid stream pointer after it was closed). + OpenFailed /// The last open operation has failed. + } State; + + /// The error state of a stream. + enum ErrorKindTy { + NoError, /// No error flag is set or stream is not open. + EofError, /// EOF condition (`feof` is true). + OtherError, /// other (non-EOF) error (`ferror` is true). + AnyError /// EofError or OtherError, used if exact error is unknown. + } ErrorState = NoError; + + bool isOpened() const { return State == Opened; } + bool isClosed() const { return State == Closed; } + bool isOpenFailed() const { return State == OpenFailed; } + + bool operator==(const StreamState &X) const { + return State == X.State && ErrorState == X.ErrorState; + } - static StreamState getOpened() { return StreamState(Opened); } - static StreamState getClosed() { return StreamState(Closed); } - static StreamState getOpenFailed() { return StreamState(OpenFailed); } - static StreamState getEscaped() { return StreamState(Escaped); } + static StreamState getOpened() { return StreamState{Opened}; } + static StreamState getClosed() { return StreamState{Closed}; } + static StreamState getOpenFailed() { return StreamState{OpenFailed}; } + static StreamState getOpenedWithError() { + return StreamState{Opened, AnyError}; + } + static StreamState getOpenedWithOtherError() { + return StreamState{Opened, OtherError}; + } - void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(State); + ID.AddInteger(ErrorState); + } }; class StreamChecker; @@ -69,6 +90,16 @@ return Call.getArgSVal(Desc->StreamArgNo); } +/// Create a conjured symbol return value for a call expression. +DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) { + assert(CE && "Expecting a call expression."); + + const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); + return C.getSValBuilder() + .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) + .castAs(); +} + class StreamChecker : public Checker { mutable std::unique_ptr BT_nullfp, BT_illegalwhence, @@ -89,7 +120,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}}, @@ -102,15 +133,20 @@ void preDefault(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; - void preFseek(const FnDescription *Desc, const CallEvent &Call, - CheckerContext &C) const; - void preFreopen(const FnDescription *Desc, const CallEvent &Call, - CheckerContext &C) const; void evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + + void preFreopen(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; void evalFreopen(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 evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; @@ -153,6 +189,7 @@ } // end anonymous namespace +// Store the state of the stream. REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) void StreamChecker::checkPreCall(const CallEvent &Call, @@ -188,48 +225,15 @@ C.addTransition(State); } -void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - SVal StreamVal = getStreamArg(Desc, Call); - State = ensureStreamNonNull(StreamVal, C, State); - if (!State) - return; - State = ensureStreamOpened(StreamVal, C, State); - if (!State) - return; - State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); - if (!State) - return; - - C.addTransition(State); -} - -void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, - CheckerContext &C) const { - // Do not allow NULL as passed stream pointer but allow a closed stream. - ProgramStateRef State = C.getState(); - State = ensureStreamNonNull(getStreamArg(Desc, Call), C, State); - if (!State) - return; - - C.addTransition(State); -} - void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); - SValBuilder &SVB = C.getSValBuilder(); - const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - - auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); if (!CE) return; - DefinedSVal RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) - .castAs(); + DefinedSVal RetVal = makeRetVal(C, CE); SymbolRef RetSym = RetVal.getAsSymbol(); - assert(RetSym && "RetVal must be a symbol here."); State = State->BindExpr(CE, C.getLocationContext(), RetVal); @@ -246,6 +250,17 @@ C.addTransition(StateNull); } +void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + // Do not allow NULL as passed stream pointer but allow a closed stream. + ProgramStateRef State = C.getState(); + State = ensureStreamNonNull(getStreamArg(Desc, Call), C, State); + if (!State) + return; + + C.addTransition(State); +} + void StreamChecker::evalFreopen(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { @@ -286,6 +301,61 @@ C.addTransition(StateRetNull); } +void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SVal StreamVal = getStreamArg(Desc, Call); + State = ensureStreamNonNull(StreamVal, C, State); + if (!State) + return; + State = ensureStreamOpened(StreamVal, C, State); + if (!State) + return; + State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); + if (!State) + return; + + 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 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()); + // If fseek fails it can fail only with non-EOF error. + // According to documentation it is no error to seek past the end of file. + StateFailed = StateFailed->set( + StreamSym, StreamState::getOpenedWithOtherError()); + + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState();