balazske updated this revision to Diff 251112.
balazske added a comment.
Herald added a subscriber: ASDenysPetrov.

Rebased.


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D75851

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
@@ -38,3 +38,48 @@
   }
   fclose(F);
 }
+
+void error_fseek() {
+  FILE *F = fopen("file", "r");
+  if (!F)
+    return;
+  int rc = fseek(F, 0, SEEK_SET);
+  if (rc) {
+    int Eof = feof(F), Error = ferror(F);
+    // Get feof or ferror or no error.
+    clang_analyzer_eval(Eof || Error); // expected-warning {{FALSE}} \
+                                       // expected-warning {{TRUE}}
+    clang_analyzer_eval(Eof && Error); // expected-warning {{FALSE}}
+    // Error flags should not change.
+    if (Eof)
+      clang_analyzer_eval(feof(F)); // expected-warning {{TRUE}}
+    else
+      clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
+    if (Error)
+      clang_analyzer_eval(ferror(F)); // expected-warning {{TRUE}}
+    else
+      clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
+  } else {
+    clang_analyzer_eval(feof(F));   // expected-warning {{FALSE}}
+    clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
+    // Error flags should not change.
+    clang_analyzer_eval(feof(F));   // expected-warning {{FALSE}}
+    clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
+  }
+  fclose(F);
+}
+
+void error_fseek_clearerr() {
+  FILE *F = fopen("file", "r");
+  if (!F)
+    return;
+  int rc = fseek(F, 0, SEEK_SET);
+  if (rc && feof(F)) {
+    clearerr(F);
+    clang_analyzer_eval(feof(F)); // expected-warning {{FALSE}}
+  } else if (rc && ferror(F)) {
+    clearerr(F);
+    clang_analyzer_eval(ferror(F)); // expected-warning {{FALSE}}
+  }
+  fclose(F);
+}
Index: clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -30,8 +30,12 @@
 
 namespace {
 
+struct FnDescription;
+
 /// Full state information about a stream pointer.
 struct StreamState {
+  const FnDescription *LastOperation;
+
   /// State of a stream symbol.
   /// FIXME: We need maybe an "escaped" state later.
   enum KindTy {
@@ -49,6 +53,8 @@
     FEof,
     /// Other generic (non-EOF) error (`ferror` is true).
     FError,
+    /// Unknown error, the meaning depends on the last operation.
+    UnknownError
   } ErrorState = NoError;
 
   bool isOpened() const { return State == Opened; }
@@ -67,21 +73,35 @@
     assert(State == Opened && "Error undefined for closed stream.");
     return ErrorState == FError;
   }
+  bool isUnknownError() const {
+    assert(State == Opened && "Error undefined for closed stream.");
+    return ErrorState == UnknownError;
+  }
 
   bool operator==(const StreamState &X) const {
     // In not opened state error should always NoError.
-    return State == X.State && ErrorState == X.ErrorState;
+    return LastOperation == X.LastOperation && 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 getOpenedWithFEof() { return StreamState{Opened, FEof}; }
-  static StreamState getOpenedWithFError() {
-    return StreamState{Opened, FError};
+  static StreamState getOpened(const FnDescription *L) {
+    return StreamState{L, Opened};
+  }
+  static StreamState getClosed(const FnDescription *L) {
+    return StreamState{L, Closed};
+  }
+  static StreamState getOpenFailed(const FnDescription *L) {
+    return StreamState{L, OpenFailed};
+  }
+  static StreamState getOpened(const FnDescription *L, ErrorKindTy E) {
+    return StreamState{L, Opened, E};
+  }
+  static StreamState getOpenedWithUnknownError(const FnDescription *L) {
+    return StreamState{L, Opened, UnknownError};
   }
 
   void Profile(llvm::FoldingSetNodeID &ID) const {
+    ID.AddPointer(LastOperation);
     ID.AddInteger(State);
     ID.AddInteger(ErrorState);
   }
@@ -99,6 +119,11 @@
   FnCheck PreFn;
   FnCheck EvalFn;
   ArgNoTy StreamArgNo;
+  // What errors are possible after this operation.
+  // Used only if this operation resulted in UnknownError
+  // (otherwise there is a known single error).
+  // Must contain 2 or 3 elements, or zero.
+  llvm::SmallVector<StreamState::ErrorKindTy, 3> PossibleErrors = {};
 };
 
 /// Get the value of the stream argument out of the passed call event.
@@ -119,6 +144,20 @@
       .castAs<DefinedSVal>();
 }
 
+ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C, const CallExpr *CE) {
+  DefinedSVal RetVal = makeRetVal(C, CE);
+  State = State->BindExpr(CE, C.getLocationContext(), RetVal);
+  State = State->assume(RetVal, true);
+  assert(State && "Assumption on new value should not fail.");
+  return State;
+}
+
+ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State, CheckerContext &C, const CallExpr *CE) {
+  State = State->BindExpr(CE, C.getLocationContext(),
+                          C.getSValBuilder().makeIntVal(Value, false));
+  return State;
+}
+
 class StreamChecker
     : public Checker<check::PreCall, eval::Call, check::DeadSymbols> {
   mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
@@ -131,28 +170,42 @@
 
 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, nullptr, 0}},
-      {{"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::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, {}}},
       {{"clearerr", 1},
-       {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
+       {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0, {}}},
+      // Note: feof can result in UnknownError 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.
       {{"feof", 1},
        {&StreamChecker::preDefault,
-        &StreamChecker::evalFeofFerror<&StreamState::isFEof>, 0}},
+        &StreamChecker::evalFeofFerror<StreamState::FEof>,
+        0,
+        {StreamState::FError, StreamState::NoError}}},
+      // Note: ferror can result in UnknownError 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.
       {{"ferror", 1},
        {&StreamChecker::preDefault,
-        &StreamChecker::evalFeofFerror<&StreamState::isFError>, 0}},
-      {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}},
+        &StreamChecker::evalFeofFerror<StreamState::FError>,
+        0,
+        {StreamState::FEof, StreamState::NoError}}},
+      {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}},
   };
 
   void evalFopen(const FnDescription *Desc, const CallEvent &Call,
@@ -168,6 +221,8 @@
 
   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;
@@ -175,7 +230,7 @@
   void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
                     CheckerContext &C) const;
 
-  template <bool (StreamState::*IsOfError)() const>
+  template <StreamState::ErrorKindTy ErrorKind>
   void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
                       CheckerContext &C) const;
 
@@ -258,8 +313,10 @@
   std::tie(StateNotNull, StateNull) =
       C.getConstraintManager().assumeDual(State, RetVal);
 
-  StateNotNull = StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened());
-  StateNull = StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed());
+  StateNotNull =
+      StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc));
+  StateNull =
+      StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc));
 
   C.addTransition(StateNotNull);
   C.addTransition(StateNull);
@@ -308,9 +365,9 @@
                                                  C.getSValBuilder().makeNull());
 
   StateRetNotNull =
-      StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened());
+      StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
   StateRetNull =
-      StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed());
+      StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc));
 
   C.addTransition(StateRetNotNull);
   C.addTransition(StateRetNull);
@@ -332,7 +389,7 @@
   // Close the File Descriptor.
   // Regardless if the close fails or not, stream becomes "closed"
   // and can not be used any more.
-  State = State->set<StreamMap>(Sym, StreamState::getClosed());
+  State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc));
 
   C.addTransition(State);
 }
@@ -354,6 +411,43 @@
   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<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return;
+
+  // Ignore the call if the stream is not tracked.
+  if (!State->get<StreamMap>(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<StreamMap>(StreamSym, StreamState::getOpened(Desc));
+  // We get error.
+  StateFailed = StateFailed->set<StreamMap>(
+      StreamSym, StreamState::getOpenedWithUnknownError(Desc));
+
+  C.addTransition(StateNotFailed);
+  C.addTransition(StateFailed);
+}
+
 void StreamChecker::evalClearerr(const FnDescription *Desc,
                                  const CallEvent &Call,
                                  CheckerContext &C) const {
@@ -371,11 +465,11 @@
   if (SS->isNoError())
     return;
 
-  State = State->set<StreamMap>(StreamSym, StreamState::getOpened());
+  State = State->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
   C.addTransition(State);
 }
 
-template <bool (StreamState::*IsOfError)() const>
+template <StreamState::ErrorKindTy ErrorKind>
 void StreamChecker::evalFeofFerror(const FnDescription *Desc,
                                    const CallEvent &Call,
                                    CheckerContext &C) const {
@@ -395,18 +489,51 @@
 
   ASSERT_STREAMSTATE_OPENED;
 
-  if ((SS->*IsOfError)()) {
-    // Function returns nonzero.
-    DefinedSVal RetVal = makeRetVal(C, CE);
-    State = State->BindExpr(CE, C.getLocationContext(), RetVal);
-    State = State->assume(RetVal, true);
-    assert(State && "Assumption on new value should not fail.");
+  if (SS->isUnknownError()) {
+    llvm::SmallVector<StreamState::ErrorKindTy, 3> NewPossibleErrors;
+    for (StreamState::ErrorKindTy E : SS->LastOperation->PossibleErrors)
+      if (E != ErrorKind)
+        NewPossibleErrors.push_back(E);
+    if (NewPossibleErrors.size() == SS->LastOperation->PossibleErrors.size()) {
+      // This kind of error is not possible, function returns zero.
+      // Error state remains unknown (we know only that it is not ErrorKind).
+      State = bindInt(0, State, C, CE);
+      State = State->set<StreamMap>(
+          StreamSym, StreamState::getOpenedWithUnknownError(Desc));
+      C.addTransition(State);
+    } else {
+      // The previously unknown error could be ErrorKind.
+      ProgramStateRef StateTrue = bindAndAssumeTrue(State, C, CE);
+      ProgramStateRef StateFalse = bindInt(0, State, C, CE);
+      // If return true, set error state to ErrorKind.
+      StateTrue = StateTrue->set<StreamMap>(
+          StreamSym, StreamState::getOpened(Desc, ErrorKind));
+      // Return false
+      if (NewPossibleErrors.size() == 1)
+        // One other error is possible, new error kind is fixed to that.
+        StateFalse = StateFalse->set<StreamMap>(
+            StreamSym, StreamState::getOpened(Desc, NewPossibleErrors.front()));
+      else
+        // Multiple other "errors" possible, new error kind is unknown.
+        // In this case we known later from the last operation's (will be feof
+        // or ferror) PossibleErrors list what errors are still possible. This
+        // should be the same as the content of PossibleErrors at this point.
+        StateFalse = StateFalse->set<StreamMap>(
+            StreamSym, StreamState::getOpened(Desc, StreamState::UnknownError));
+      C.addTransition(StateTrue);
+      C.addTransition(StateFalse);
+    }
   } else {
-    // Return zero.
-    State = State->BindExpr(CE, C.getLocationContext(),
-                            C.getSValBuilder().makeIntVal(0, false));
+    // We know exactly what the error is.
+    // Make the return value accordingly to the error.
+    if (SS->ErrorState == ErrorKind)
+      State = bindAndAssumeTrue(State, C, CE);
+    else
+      State = bindInt(0, State, C, CE);
+    State = State->set<StreamMap>(StreamSym,
+                                  StreamState::getOpened(Desc, SS->ErrorState));
+    C.addTransition(State);
   }
-  C.addTransition(State);
 }
 
 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call,
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to