balazske created this revision.
Herald added subscribers: cfe-commits, ASDenysPetrov, martong, Charusso, 
gamesh411, dkrupp, donat.nagy, Szelethus, mikhail.ramalho, a.sidorin, szepet, 
baloghadamsoftware, xazax.hun.
Herald added a reviewer: Szelethus.
Herald added a project: clang.

Functions fread and fwrite are now modeled with the possible
return values and stream error flags on error.


Repository:
  rG LLVM Github Monorepo

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,39 @@
   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}}
+    if (ferror(F))
+      clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+  }
+  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}}
+  }
+  fclose(F);
+  Ret = fwrite(0, 1, 10, F); // expected-warning {{Stream might be already closed}}
+}
+
 void error_fseek() {
   FILE *F = fopen("file", "r");
   if (!F)
Index: clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -122,11 +122,16 @@
   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.
+  // What errors are possible after the operation has failed.
   llvm::SmallVector<StreamState::ErrorKindTy, 3> PossibleErrors = {};
+
+  StreamState::ErrorKindTy getErrorKindAfterFailedOperation() const {
+    assert(!PossibleErrors.empty() &&
+           "Function must be called on operation that can fail.");
+    if (PossibleErrors.size() == 1)
+      return PossibleErrors.front();
+    return StreamState::Unknown;
+  }
 };
 
 Optional<StreamState::ErrorKindTy>
@@ -199,17 +204,31 @@
       {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone, {}}},
       {{"fclose", 1},
        {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0, {}}},
-      {{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3, {}}},
-      {{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3, {}}},
+      {{"fread", 4},
+       {&StreamChecker::preDefault,
+        &StreamChecker::evalFreadFwrite,
+        3,
+        {StreamState::FError, StreamState::FEof}}},
+      {{"fwrite", 4},
+       {&StreamChecker::preDefault,
+        &StreamChecker::evalFreadFwrite,
+        3,
+        {StreamState::FError}}},
       {{"fseek", 3},
        {&StreamChecker::preFseek,
         &StreamChecker::evalFseek,
         0,
         {StreamState::FEof, StreamState::FError, StreamState::NoError}}},
-      {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}},
+      // Note: ftell sets errno only.
+      {{"ftell", 1},
+       {&StreamChecker::preDefault, nullptr, 0, {StreamState::NoError}}},
       {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0, {}}},
-      {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0, {}}},
-      {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0, {}}},
+      // Note: fgetpos sets errno only.
+      {{"fgetpos", 2},
+       {&StreamChecker::preDefault, nullptr, 0, {StreamState::NoError}}},
+      // Note: fsetpos sets errno only.
+      {{"fsetpos", 2},
+       {&StreamChecker::preDefault, nullptr, 0, {StreamState::NoError}}},
       {{"clearerr", 1},
        {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0, {}}},
       // Note: feof can result in Unknown if at the call there is a
@@ -249,17 +268,20 @@
   void evalFclose(const FnDescription *Desc, const CallEvent &Call,
                   CheckerContext &C) const;
 
+  void evalFreadFwrite(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 preDefault(const FnDescription *Desc, const CallEvent &Call,
-                  CheckerContext &C) const;
-
   void evalClearerr(const FnDescription *Desc, const CallEvent &Call,
                     CheckerContext &C) const;
 
+  void preDefault(const FnDescription *Desc, const CallEvent &Call,
+                  CheckerContext &C) const;
+
   template <StreamState::ErrorKindTy ErrorKind>
   void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call,
                       CheckerContext &C) const;
@@ -435,6 +457,65 @@
   C.addTransition(State);
 }
 
+void StreamChecker::evalFreadFwrite(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;
+
+  Optional<NonLoc> CountVal = Call.getArgSVal(2).castAs<NonLoc>();
+  if (!CountVal)
+    return;
+
+  const StreamState *SS = State->get<StreamMap>(StreamSym);
+  if (!SS)
+    return;
+
+  assertStreamStateOpened(SS);
+
+  if (State->isNull(*CountVal).isConstrainedTrue()) {
+    // If `count` is zero, return 0.
+    State = bindInt(0, State, C, CE);
+    State = State->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
+    C.addTransition(State);
+    return;
+  }
+
+  ProgramStateRef StateNotFailed =
+      State->BindExpr(CE, C.getLocationContext(), *CountVal);
+  if (StateNotFailed) {
+    StateNotFailed =
+        StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc));
+    C.addTransition(StateNotFailed);
+  }
+
+  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;
+  StateFailed = StateFailed->set<StreamMap>(
+      StreamSym,
+      StreamState::getOpened(Desc, Desc->getErrorKindAfterFailedOperation()));
+  C.addTransition(StateFailed);
+}
+
 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call,
                              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