balazske updated this revision to Diff 263447.
balazske added a comment.

Added state split before fread to make warning for error state possible.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D78374/new/

https://reviews.llvm.org/D78374

Files:
  clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
  clang/test/Analysis/stream-error.c

Index: clang/test/Analysis/stream-error.c
===================================================================
--- clang/test/Analysis/stream-error.c
+++ 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);
+}
Index: clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ 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 <functional>
 
 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<ErrorKindTy> 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<StreamState::ErrorKindTy, 3> PossibleErrors = {};
 };
 
-Optional<StreamState::ErrorKindTy>
-StreamState::getRemainingPossibleError(ErrorKindTy ErrorKind) const {
-  assert(ErrorState == Unknown &&
-         "Function to be used only if error is unknown.");
-  llvm::SmallVector<StreamState::ErrorKindTy, 3> 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<check::PreCall, eval::Call, check::DeadSymbols> {
   mutable std::unique_ptr<BuiltinBug> 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<FnDescription> 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<StreamState::FEof>,
-        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<StreamState::FError>,
-        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<FnDescription> FnTestDescriptions = {
       {{"StreamTesterChecker_make_feof_stream", 1},
-       {nullptr, &StreamChecker::evalSetFeofFerror<StreamState::FEof>, 0}},
+       {nullptr,
+        std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof),
+        0}},
       {{"StreamTesterChecker_make_ferror_stream", 1},
-       {nullptr, &StreamChecker::evalSetFeofFerror<StreamState::FError>, 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 <StreamState::ErrorKindTy ErrorKind>
+  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 <StreamState::ErrorKindTy EK>
   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<ProgramStateRef, ProgramStateRef>
+  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<StreamMap>(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<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return;
+
+  Optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>();
+  if (!SizeVal)
+    return;
+  Optional<NonLoc> CountVal = Call.getArgSVal(2).getAs<NonLoc>();
+  if (!CountVal)
+    return;
+
+  const StreamState *SS = State->get<StreamMap>(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<StreamMap>(
+          StreamSym, StreamState::getOpened(Desc));
+      C.addTransition(StateNotFailed);
+    }
+  }
+
+  // Add transition for the failed state.
+  Optional<NonLoc> RetVal = makeRetVal(C, CE).castAs<NonLoc>();
+  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<DefinedOrUnknownSVal>();
+  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<StreamMap>(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<StreamMap>(StreamSym, StreamState::getOpened(Desc));
   // We get error.
-  StateFailed = StateFailed->set<StreamMap>(
-      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<StreamMap>(StreamSym, NewState);
 
   C.addTransition(StateNotFailed);
   C.addTransition(StateFailed);
@@ -503,14 +675,16 @@
 
   assertStreamStateOpened(SS);
 
-  State = State->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
+  // The FilePositionIndeterminate value does not change!
+  State = State->set<StreamMap>(
+      StreamSym,
+      StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate));
   C.addTransition(State);
 }
 
-template <StreamState::ErrorKindTy ErrorKind>
 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<StreamMap>(StreamSym, SSNew);
-    C.addTransition(State);
-  };
-
   const StreamState *SS = State->get<StreamMap>(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<StreamState::ErrorKindTy> 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<StreamMap>(
+        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<StreamMap>(
+        StreamSym,
+        StreamState::getOpened(Desc, NewES, SS->FilePositionIndeterminate)));
   }
 }
 
@@ -573,17 +732,16 @@
   C.addTransition(State);
 }
 
-template <StreamState::ErrorKindTy EK>
 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<StreamMap>(StreamSym);
   assert(SS && "Stream should be tracked by the checker.");
-  State = State->set<StreamMap>(StreamSym,
-                                StreamState::getOpened(SS->LastOperation, EK));
+  State = State->set<StreamMap>(
+      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<StreamMap>(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<StreamMap>(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<PathSensitiveBugReport>(
+            *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<PathSensitiveBugReport>(
+          *BT_InvalidStreamState, BT_InvalidStreamState->getDescription(), N));
+    }
+    NewState.ErrorState = ErrorFEof;
+    return State->set<StreamMap>(Sym, NewState);
+  }
+
+  return State;
+}
+
 ProgramStateRef
 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C,
                                         ProgramStateRef State) const {
@@ -689,6 +905,43 @@
   return State;
 }
 
+std::pair<ProgramStateRef, ProgramStateRef>
+StreamChecker::splitErrorState(ProgramStateRef State, SymbolRef StreamSym,
+                               StreamErrorState SplitError) const {
+  const StreamState *SS = State->get<StreamMap>(StreamSym);
+  assert(SS && "Function should be called with stream that has state.");
+
+  ProgramStateRef State1, State2;
+  if (StreamErrorState Error1 = SS->ErrorState & SplitError) {
+    State1 = State->set<StreamMap>(
+        StreamSym, StreamState::getOpened(SS->LastOperation, Error1,
+                                          SS->FilePositionIndeterminate));
+  }
+  if (StreamErrorState Error2 = SS->ErrorState & ~SplitError) {
+    State2 = State->set<StreamMap>(
+        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<PathSensitiveBugReport>(
+      *BT_StreamInErrorState, BT_StreamInErrorState->getDescription(), N));
+  return N;
+}
+
 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
                                      CheckerContext &C) const {
   ProgramStateRef State = C.getState();
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to