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 @@ -47,6 +47,26 @@ void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } }; +struct StreamErrorState { + // The error state of an opened stream. + // EofError: EOF condition (feof returns true) + // OtherError: other (non-EOF) error (ferror returns true) + // AnyError: EofError or OtherError + enum Kind { EofError, OtherError, AnyError } K; + + StreamErrorState(Kind k) : K(k) {} + + bool isEof() const { return K == EofError; } + bool isOther() const { return K == OtherError; } + bool isAny() const { return K == AnyError; } + + bool operator==(const StreamErrorState &X) const { return K == X.K; } + + static StreamErrorState getOther() { return StreamErrorState(OtherError); } + + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } +}; + class StreamChecker; struct FnDescription; using FnCheck = std::functionStreamArgNo); } +/// 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 +119,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 +132,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; @@ -156,8 +191,14 @@ } // end anonymous namespace +// Store the state of the stream. REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) +// Store the error state of the stream. +// Used only for streams in opened state. +// The entry exists only if there is error for the stream. +REGISTER_MAP_WITH_PROGRAMSTATE(StreamErrorMap, SymbolRef, StreamErrorState) + void StreamChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { const FnDescription *Desc = lookupFn(Call); @@ -191,48 +232,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); @@ -249,6 +257,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 { @@ -269,6 +288,10 @@ if (!StreamSym) return; + // Reset the error state. + // After reopen, the EOF and I/O error is not set. + State = State->remove(StreamSym); + // Generate state for non-failed case. // Return value is the passed stream pointer. // According to the documentations, the stream is closed first @@ -289,6 +312,57 @@ 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; + + DefinedSVal RetVal = makeRetVal(C, CE); + + // Clear error state. + State = State->remove(StreamSym); + + // 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); + + // Record the failed status, only if failed. + // fseek clears the EOF flag, sets only error flag. + StateFailed = + StateFailed->set(StreamSym, StreamErrorState::getOther()); + + C.addTransition(StateNotFailed); + C.addTransition(StateFailed); +} + void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); @@ -300,6 +374,9 @@ if (!SS) return; + // Clear error state. + State = State->remove(Sym); + // Close the File Descriptor. // Regardless if the close fails or not, stream becomes "closed" // and can not be used any more.