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 @@ -19,15 +19,66 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include using namespace clang; using namespace ento; +using namespace std::placeholders; namespace { struct FnDescription; +/// State of the stream related error flags. +/// It indicates possibility of different error states. +/// In other sense, every set flag means a separate execution path. +/// At least one of the bool flags must be set to have a valid StreamErrorState. +/// Note: A stream has either FEOF or FERROR set but not both at the same time. +struct StreamErrorState { + /// There is an execution path with none of the error flags set. + bool NoError = true; + /// There is an execution path with EOF indicator set. + bool FEof = false; + /// There is an execution path with error indicator set. + bool FError = false; + // bool FErrorIndeterminate = false; + // bool NoErrorIndeterminate = false; + + bool isNoError() const { return NoError && !FEof && !FError; } + bool isFEof() const { return !NoError && FEof && !FError; } + bool isFError() const { return !NoError && !FEof && FError; } + + bool operator==(const StreamErrorState &ES) const { + return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError; + } + + StreamErrorState operator|(const StreamErrorState &E) const { + return {NoError || E.NoError, FEof || E.FEof, FError || E.FError}; + } + + StreamErrorState operator&(const StreamErrorState &E) const { + return {NoError && E.NoError, FEof && E.FEof, FError && E.FError}; + } + + StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; } + + /// Returns if the StreamErrorState is a valid object. + operator bool() const { return NoError || FEof || FError; } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddBoolean(NoError); + ID.AddBoolean(FEof); + ID.AddBoolean(FError); + } +}; + +const StreamErrorState ErrorNone{true, false, false}; +const StreamErrorState ErrorFEof{false, true, false}; +const StreamErrorState ErrorFError{false, false, true}; + /// Full state information about a stream pointer. +/// ErrorState and FilePositionIndeterminate are only used if the stream is in +/// Opened state. Otherwise these should be set to the default value. struct StreamState { /// The last file operation called in the stream. const FnDescription *LastOperation; @@ -41,52 +92,35 @@ } State; /// The error state of a stream. - /// Valid only if the stream is opened. - /// It is assumed that feof and ferror flags are never true at the same time. - enum ErrorKindTy { - /// No error flag is set (or stream is not open). - NoError, - /// EOF condition (`feof` is true). - FEof, - /// Other generic (non-EOF) error (`ferror` is true). - FError, - /// Unknown error flag is set (or none), the meaning depends on the last - /// operation. - Unknown - } ErrorState = NoError; + /// Used to store a bundle of execution paths if more than one error kind is + /// set here. + StreamErrorState ErrorState; + + /// Indicate if the file has an "indeterminate file position indicator". + /// This can be set at a failing read or write or seek operation. + /// If it is set no more read or write is allowed until the position is set + /// in other way. + /// This value is not dependent on the stream error flags: + /// The error flag may be cleared with `clearerr` but the file position + /// remains still indeterminate. + /// This value applies to all execution paths in ErrorState except the FEOF + /// case. In more detail, an EOF+indeterminate state is the same as EOF state. + bool FilePositionIndeterminate = false; bool isOpened() const { return State == Opened; } bool isClosed() const { return State == Closed; } bool isOpenFailed() const { return State == OpenFailed; } - bool isNoError() const { - assert(State == Opened && "Error undefined for closed stream."); - return ErrorState == NoError; - } - bool isFEof() const { - assert(State == Opened && "Error undefined for closed stream."); - return ErrorState == FEof; - } - bool isFError() const { - assert(State == Opened && "Error undefined for closed stream."); - return ErrorState == FError; - } - bool isUnknown() const { - assert(State == Opened && "Error undefined for closed stream."); - return ErrorState == Unknown; - } - bool operator==(const StreamState &X) const { - // In not opened state error should always NoError. return LastOperation == X.LastOperation && State == X.State && - ErrorState == X.ErrorState; + ErrorState == X.ErrorState && + FilePositionIndeterminate == X.FilePositionIndeterminate; } - static StreamState getOpened(const FnDescription *L) { - return StreamState{L, Opened}; - } - static StreamState getOpened(const FnDescription *L, ErrorKindTy E) { - return StreamState{L, Opened, E}; + static StreamState getOpened(const FnDescription *L, + const StreamErrorState &ES = ErrorNone, + bool FPI = false) { + return StreamState{L, Opened, ES, FPI}; } static StreamState getClosed(const FnDescription *L) { return StreamState{L, Closed}; @@ -95,19 +129,11 @@ return StreamState{L, OpenFailed}; } - /// Return if the specified error kind is possible on the stream in the - /// current state. - /// This depends on the stored `LastOperation` value. - /// If the error is not possible returns empty value. - /// If the error is possible returns the remaining possible error type - /// (after taking out `ErrorKind`). If a single error is possible it will - /// return that value, otherwise unknown error. - Optional getRemainingPossibleError(ErrorKindTy ErrorKind) const; - void Profile(llvm::FoldingSetNodeID &ID) const { + ErrorState.Profile(ID); ID.AddPointer(LastOperation); ID.AddInteger(State); - ID.AddInteger(ErrorState); + ID.AddBoolean(FilePositionIndeterminate); } }; @@ -122,28 +148,8 @@ 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. - llvm::SmallVector PossibleErrors = {}; }; -Optional -StreamState::getRemainingPossibleError(ErrorKindTy ErrorKind) const { - assert(ErrorState == Unknown && - "Function to be used only if error is unknown."); - llvm::SmallVector NewPossibleErrors; - for (StreamState::ErrorKindTy E : LastOperation->PossibleErrors) - if (E != ErrorKind) - NewPossibleErrors.push_back(E); - if (NewPossibleErrors.size() == LastOperation->PossibleErrors.size()) - return {}; - if (NewPossibleErrors.size() == 1) - return NewPossibleErrors.front(); - return Unknown; -} - /// Get the value of the stream argument out of the passed call event. /// The call should contain a function that is described by Desc. SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) { @@ -181,7 +187,8 @@ class StreamChecker : public Checker { mutable std::unique_ptr BT_nullfp, BT_illegalwhence, - BT_UseAfterClose, BT_UseAfterOpenFailed, BT_ResourceLeak; + BT_UseAfterClose, BT_UseAfterOpenFailed, BT_ResourceLeak, + BT_InvalidStreamState, BT_StreamInErrorState; public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; @@ -193,49 +200,48 @@ private: CallDescriptionMap FnDescriptions = { - {{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone, {}}}, + {{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, {{"freopen", 3}, - {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2, {}}}, - {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone, {}}}, + {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, + {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, {{"fclose", 1}, - {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0, {}}}, - {{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3, {}}}, - {{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3, {}}}, - {{"fseek", 3}, - {&StreamChecker::preFseek, - &StreamChecker::evalFseek, - 0, - {StreamState::FEof, StreamState::FError, StreamState::NoError}}}, - {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}}, - {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}}, - {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0, {}}}, - {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0, {}}}, + {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, + {{"fread", 4}, + {&StreamChecker::preFread, + std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}}, + {{"fwrite", 4}, + {&StreamChecker::preFwrite, + std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}}, + {{"fseek", 3}, {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, + {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0}}, + {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0}}, + // Note: fgetpos sets errno only. + {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, + // Note: fsetpos sets errno only. + {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, {{"clearerr", 1}, - {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0, {}}}, - // Note: feof can result in Unknown if at the call there is a - // PossibleErrors with all 3 error states (including NoError). - // Then if feof is false the remaining error could be FError or NoError. + {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}}, {{"feof", 1}, {&StreamChecker::preDefault, - &StreamChecker::evalFeofFerror, - 0, - {StreamState::FError, StreamState::NoError}}}, - // Note: ferror can result in Unknown if at the call there is a - // PossibleErrors with all 3 error states (including NoError). - // Then if ferror is false the remaining error could be FEof or NoError. + std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof), + 0}}, {{"ferror", 1}, {&StreamChecker::preDefault, - &StreamChecker::evalFeofFerror, - 0, - {StreamState::FEof, StreamState::NoError}}}, - {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}}, + std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError), + 0}}, + {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}}, }; CallDescriptionMap FnTestDescriptions = { {{"StreamTesterChecker_make_feof_stream", 1}, - {nullptr, &StreamChecker::evalSetFeofFerror, 0}}, + {nullptr, + std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof), + 0}}, {{"StreamTesterChecker_make_ferror_stream", 1}, - {nullptr, &StreamChecker::evalSetFeofFerror, 0}}, + {nullptr, + std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, + ErrorFError), + 0}}, }; void evalFopen(const FnDescription *Desc, const CallEvent &Call, @@ -249,24 +255,33 @@ void evalFclose(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const; + void preFread(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void preFwrite(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + + void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C, bool IsFread) 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; - template + void preDefault(const FnDescription *Desc, const CallEvent &Call, + CheckerContext &C) const; + void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call, - CheckerContext &C) const; + CheckerContext &C, + const StreamErrorState &ErrorKind) const; - template void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call, - CheckerContext &C) const; + CheckerContext &C, + const StreamErrorState &ErrorKind) const; /// Check that the stream (in StreamVal) is not NULL. /// If it can only be NULL a fatal error is emitted and nullptr returned. @@ -281,6 +296,16 @@ ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, ProgramStateRef State) const; + /// Check that the stream has not an invalid ("indeterminate") file position, + /// generate warning for it. + /// (EOF is not an invalid position.) + /// The returned state can be nullptr if a fatal error was generated. + /// It can return non-null state if the stream has not an invalid position or + /// there is execution path with non-invalid position. + ProgramStateRef + ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C, + ProgramStateRef State) const; + /// Check the legality of the 'whence' argument of 'fseek'. /// Generate error and return nullptr if it is found to be illegal. /// Otherwise returns the state. @@ -288,6 +313,20 @@ ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, ProgramStateRef State) const; + /// Create two states where the original error state of the stream is + /// differentiated. First member contains error state with bits from + /// 'SplitError' and original error state. Second member contains error state + /// with bits from complement of 'SplitError' and original error state. Both + /// can be null. + std::pair + splitErrorState(ProgramStateRef State, SymbolRef StreamSym, + StreamErrorState SplitError) const; + + /// Report a 'BT_StreamInErrorState' type non-fatal error. + /// Returns the new error node, or null. + ExplodedNode *reportStreamInErrorState(CheckerContext &C, + ProgramStateRef State) const; + /// Find the description data of the function called by a call event. /// Returns nullptr if no function is recognized. const FnDescription *lookupFn(const CallEvent &Call) const { @@ -435,6 +474,137 @@ C.addTransition(State); } +void StreamChecker::preFread(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 = ensureNoFilePositionIndeterminate(StreamVal, C, State); + if (!State) + return; + + SymbolRef Sym = StreamVal.getAsSymbol(); + if (Sym && State->get(Sym)) { + ProgramStateRef StateError, StateNoError; + std::tie(StateError, StateNoError) = + splitErrorState(State, Sym, ErrorFEof | ErrorFError); + if (StateError) { + if (!reportStreamInErrorState(C, StateError)) + C.addTransition(StateError); + } + if (StateNoError) + C.addTransition(StateNoError); + } else { + C.addTransition(State); + } +} + +void StreamChecker::preFwrite(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 = ensureNoFilePositionIndeterminate(StreamVal, C, State); + if (!State) + return; + + C.addTransition(State); +} + +void StreamChecker::evalFreadFwrite(const FnDescription *Desc, + const CallEvent &Call, CheckerContext &C, + bool IsFread) 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 SizeVal = Call.getArgSVal(1).getAs(); + if (!SizeVal) + return; + Optional CountVal = Call.getArgSVal(2).getAs(); + if (!CountVal) + return; + + const StreamState *SS = State->get(StreamSym); + if (!SS) + return; + + assertStreamStateOpened(SS); + + // C'99 standard, ยง7.19.8.1.3, the return value of fread: + // The fread function returns the number of elements successfully read, which + // may be less than nmemb if a read error or end-of-file is encountered. If + // size or nmemb is zero, fread returns zero and the contents of the array and + // the state of the stream remain unchanged. + + if (State->isNull(*SizeVal).isConstrainedTrue() || + State->isNull(*CountVal).isConstrainedTrue()) { + // This is the "size or nmemb is zero" case. + // Just return 0, do nothing more (not clear the error flags). + State = bindInt(0, State, C, CE); + C.addTransition(State); + return; + } + + // Generate a transition for the success state. + // The fread call fails if there is already error, in this case make no + // success transition. + if (!IsFread || SS->ErrorState & ErrorNone) { + ProgramStateRef StateNotFailed = + State->BindExpr(CE, C.getLocationContext(), *CountVal); + if (StateNotFailed) { + StateNotFailed = StateNotFailed->set( + StreamSym, StreamState::getOpened(Desc)); + C.addTransition(StateNotFailed); + } + } + + // Add transition for the failed state. + 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; + + StreamErrorState NewES; + if (IsFread) + NewES = (SS->ErrorState & ErrorNone) ? (ErrorFEof | ErrorFError) + : SS->ErrorState; + else + NewES = ErrorFError; + StreamState NewState = StreamState::getOpened(Desc, NewES, true); + // If an error occurs, the resulting value of the file position indicator for + // the stream is indeterminate. This flag is set now for both the EOF and + // FERROR cases. But for EOF it is to be ignored. + StateFailed = StateFailed->set(StreamSym, NewState); + C.addTransition(StateFailed); +} + void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C) const { ProgramStateRef State = C.getState(); @@ -482,8 +652,10 @@ StateNotFailed = StateNotFailed->set(StreamSym, StreamState::getOpened(Desc)); // We get error. - StateFailed = StateFailed->set( - StreamSym, StreamState::getOpened(Desc, StreamState::Unknown)); + // ErrorNone is there because fseek can fail without setting any error flags. + StreamState NewState = + StreamState::getOpened(Desc, ErrorNone | ErrorFEof | ErrorFError, true); + StateFailed = StateFailed->set(StreamSym, NewState); C.addTransition(StateNotFailed); C.addTransition(StateFailed); @@ -503,14 +675,16 @@ assertStreamStateOpened(SS); - State = State->set(StreamSym, StreamState::getOpened(Desc)); + // The FilePositionIndeterminate value does not change! + State = State->set( + StreamSym, + StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate)); C.addTransition(State); } -template void StreamChecker::evalFeofFerror(const FnDescription *Desc, - const CallEvent &Call, - CheckerContext &C) const { + const CallEvent &Call, CheckerContext &C, + const StreamErrorState &ErrorKind) const { ProgramStateRef State = C.getState(); SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); if (!StreamSym) @@ -520,42 +694,27 @@ if (!CE) return; - auto AddTransition = [&C, Desc, StreamSym](ProgramStateRef State, - StreamState::ErrorKindTy SSError) { - StreamState SSNew = StreamState::getOpened(Desc, SSError); - State = State->set(StreamSym, SSNew); - C.addTransition(State); - }; - const StreamState *SS = State->get(StreamSym); if (!SS) return; assertStreamStateOpened(SS); - if (!SS->isUnknown()) { - // The stream is in exactly known state (error or not). - // Check if it is in error of kind `ErrorKind`. - if (SS->ErrorState == ErrorKind) - State = bindAndAssumeTrue(State, C, CE); - else - State = bindInt(0, State, C, CE); - // Error state is not changed in the new state. - AddTransition(State, SS->ErrorState); - } else { - // Stream is in unknown state, check if error `ErrorKind` is possible. - Optional NewError = - SS->getRemainingPossibleError(ErrorKind); - if (!NewError) { - // This kind of error is not possible, function returns zero. - // Error state remains unknown. - AddTransition(bindInt(0, State, C, CE), StreamState::Unknown); - } else { - // Add state with true returned. - AddTransition(bindAndAssumeTrue(State, C, CE), ErrorKind); - // Add state with false returned and the new remaining error type. - AddTransition(bindInt(0, State, C, CE), *NewError); - } + if (SS->ErrorState & ErrorKind) { + // Found an execution path with ErrorKind, split state for this and return + // "true". + ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE); + C.addTransition(TrueState->set( + StreamSym, StreamState::getOpened(Desc, ErrorKind, + SS->FilePositionIndeterminate))); + } + if (StreamErrorState NewES = SS->ErrorState & (~ErrorKind)) { + // Found execution path(s) with ErrorKind not set, split state for this and + // return "false". + ProgramStateRef FalseState = bindInt(0, State, C, CE); + C.addTransition(FalseState->set( + StreamSym, + StreamState::getOpened(Desc, NewES, SS->FilePositionIndeterminate))); } } @@ -573,17 +732,16 @@ C.addTransition(State); } -template void StreamChecker::evalSetFeofFerror(const FnDescription *Desc, - const CallEvent &Call, - CheckerContext &C) const { + const CallEvent &Call, CheckerContext &C, + const StreamErrorState &ErrorKind) const { ProgramStateRef State = C.getState(); SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); assert(StreamSym && "Operation not permitted on non-symbolic stream value."); const StreamState *SS = State->get(StreamSym); assert(SS && "Stream should be tracked by the checker."); - State = State->set(StreamSym, - StreamState::getOpened(SS->LastOperation, EK)); + State = State->set( + StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind)); C.addTransition(State); } @@ -664,6 +822,64 @@ return State; } +ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate( + SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { + SymbolRef Sym = StreamVal.getAsSymbol(); + if (!Sym) + return State; + + const StreamState *SS = State->get(Sym); + if (!SS) + return State; + + assert(SS->isOpened() && "First ensure that stream is opened."); + + if (SS->FilePositionIndeterminate) { + if (!BT_InvalidStreamState) + BT_InvalidStreamState.reset( + new BuiltinBug(this, "Invalid stream state", + "Stream might be in indeterminate state after " + "a failed operation. " + "Can cause undefined behaviour.")); + + // Clear FilePositionIndeterminate from state. + StreamState NewState = + StreamState::getOpened(SS->LastOperation, SS->ErrorState, false); + + // If only FEof is possible, report no warning (indeterminate position in + // FEof is ignored). + if (SS->ErrorState.isFEof()) + return State->set(Sym, NewState); + + // If FEof is not possible, warn for indeterminate position and stop + // analysis. + if (!(SS->ErrorState & ErrorFEof)) { + ExplodedNode *N = C.generateErrorNode(State); + if (N) { + C.emitReport(std::make_unique( + *BT_InvalidStreamState, BT_InvalidStreamState->getDescription(), + N)); + return nullptr; + } + return State; // FIXME: nullptr? + } + + // FEof and other states are possible. + // The path with FEof is the one that can continue. + // For this reason a non-fatal error is generated to continue the analysis + // with only FEof state set. + ExplodedNode *N = C.generateNonFatalErrorNode(State); + if (N) { + C.emitReport(std::make_unique( + *BT_InvalidStreamState, BT_InvalidStreamState->getDescription(), N)); + } + NewState.ErrorState = ErrorFEof; + return State->set(Sym, NewState); + } + + return State; +} + ProgramStateRef StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, ProgramStateRef State) const { @@ -689,6 +905,43 @@ return State; } +std::pair +StreamChecker::splitErrorState(ProgramStateRef State, SymbolRef StreamSym, + StreamErrorState SplitError) const { + const StreamState *SS = State->get(StreamSym); + assert(SS && "Function should be called with stream that has state."); + + ProgramStateRef State1, State2; + if (StreamErrorState Error1 = SS->ErrorState & SplitError) { + State1 = State->set( + StreamSym, StreamState::getOpened(SS->LastOperation, Error1, + SS->FilePositionIndeterminate)); + } + if (StreamErrorState Error2 = SS->ErrorState & ~SplitError) { + State2 = State->set( + StreamSym, StreamState::getOpened(SS->LastOperation, Error2, + SS->FilePositionIndeterminate)); + } + return std::make_pair(State1, State2); +} + +ExplodedNode * +StreamChecker::reportStreamInErrorState(CheckerContext &C, + ProgramStateRef State) const { + ExplodedNode *N = C.generateNonFatalErrorNode(State); + if (!N) + return nullptr; + + if (!BT_StreamInErrorState) + BT_StreamInErrorState.reset( + new BuiltinBug(this, "Stream is in error state", + "Function called when stream has already error state " + "flag (EOF or error) set.")); + C.emitReport(std::make_unique( + *BT_StreamInErrorState, BT_StreamInErrorState->getDescription(), N)); + return N; +} + void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 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,80 @@ 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}} + fread(0, 1, 10, F); // expected-warning {{Function called when stream has already error state}} + } + if (ferror(F)) { + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + fread(0, 1, 10, F); // expected-warning {{Stream might be in indeterminate state}} + } + } + 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}} + fwrite(0, 1, 10, F); // expected-warning {{Stream might be in indeterminate state}} + } + fclose(F); + Ret = fwrite(0, 1, 10, F); // expected-warning {{Stream might be already closed}} +} + +void freadwrite_zerosize(FILE *F) { + fwrite(0, 1, 0, F); + fwrite(0, 0, 1, F); + fread(0, 1, 0, F); + fread(0, 0, 1, F); +} + +void freadwrite_zerosize_errorstate(FILE *F) { + fwrite(0, 1, 0, F); + fwrite(0, 0, 1, F); + fread(0, 1, 0, F); // expected-warning {{Function called when stream has already error state}} + fread(0, 0, 1, F); // expected-warning {{Function called when stream has already error state}} +} + +void error_fread_fwrite_zerosize() { + FILE *F = fopen("file", "r"); + if (!F) + return; + + freadwrite_zerosize(F); + clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}} + clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}} + + StreamTesterChecker_make_ferror_stream(F); + freadwrite_zerosize_errorstate(F); + clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}} + clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}} + + StreamTesterChecker_make_feof_stream(F); + freadwrite_zerosize_errorstate(F); + clang_analyzer_eval(feof(F)); // expected-warning {{TRUE}} + clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}} + + fclose(F); +} + void error_fseek() { FILE *F = fopen("file", "r"); if (!F) @@ -83,3 +158,34 @@ } fclose(F); } + +void error_fseek_indeterminate() { + FILE *F = fopen("file", "r"); + if (!F) + return; + int rc = fseek(F, 0, SEEK_SET); + if (rc) { + if (feof(F)) { + fwrite(0, 1, 10, F); // no warning + } else if (ferror(F)) { + fwrite(0, 1, 10, F); // expected-warning {{Stream might be in indeterminate state}} + } else { + fwrite(0, 1, 10, F); // expected-warning {{Stream might be in indeterminate state}} + } + } + fclose(F); +} + +void error_clearerr_indeterminate() { + FILE *F = fopen("file", "r"); + if (!F) + return; + int rc = fseek(F, 0, SEEK_SET); + if (rc) { + if (!feof(F)) { + clearerr(F); + fwrite(0, 1, 10, F); // expected-warning {{Stream might be in indeterminate state}} + } + } + fclose(F); +}