balazske updated this revision to Diff 485928.
balazske marked 2 inline comments as done.
balazske added a comment.

Removed old commment, updated a test run line.


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D140395

Files:
  clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
  clang/test/Analysis/Inputs/system-header-simulator.h
  clang/test/Analysis/stream-errno-note.c
  clang/test/Analysis/stream-errno.c
  clang/test/Analysis/stream-error.c
  clang/test/Analysis/stream-noopen.c

Index: clang/test/Analysis/stream-noopen.c
===================================================================
--- /dev/null
+++ clang/test/Analysis/stream-noopen.c
@@ -0,0 +1,157 @@
+// RUN: %clang_analyze_cc1 -verify %s \
+// RUN:   -analyzer-checker=core \
+// RUN:   -analyzer-checker=alpha.unix.Errno \
+// RUN:   -analyzer-checker=alpha.unix.Stream \
+// RUN:   -analyzer-checker=apiModeling.StdCLibraryFunctions \
+// RUN:   -analyzer-config apiModeling.StdCLibraryFunctions:ModelPOSIX=true \
+// RUN:   -analyzer-checker=debug.ExprInspection
+
+// enable only StdCLibraryFunctions checker
+// RUN: %clang_analyze_cc1 -verify %s \
+// RUN:   -analyzer-checker=core \
+// RUN:   -analyzer-checker=alpha.unix.Errno \
+// RUN:   -analyzer-checker=apiModeling.StdCLibraryFunctions \
+// RUN:   -analyzer-config apiModeling.StdCLibraryFunctions:ModelPOSIX=true \
+// RUN:   -analyzer-checker=debug.ExprInspection
+
+#include "Inputs/system-header-simulator.h"
+#include "Inputs/errno_var.h"
+
+void clang_analyzer_eval(int);
+
+const char *WBuf = "123456789";
+char RBuf[10];
+
+void test_freopen(FILE *F) {
+  F = freopen("xxx", "w", F);
+  if (F) {
+    if (errno) {} // expected-warning{{undefined}}
+  } else {
+    clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
+  }
+}
+
+void test_fread(FILE *F) {
+  size_t Ret = fread(RBuf, 1, 10, F);
+  if (Ret == 10) {
+    if (errno) {} // expected-warning{{undefined}}
+  } else {
+    clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
+  }
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+}
+
+void test_fwrite(FILE *F) {
+  size_t Ret = fwrite(WBuf, 1, 10, F);
+  if (Ret == 10) {
+    if (errno) {} // expected-warning{{undefined}}
+  } else {
+    clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
+  }
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+}
+
+void test_fclose(FILE *F) {
+  int Ret = fclose(F);
+  if (Ret == 0) {
+    if (errno) {} // expected-warning{{undefined}}
+  } else {
+    clang_analyzer_eval(Ret == EOF); // expected-warning {{TRUE}}
+    clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
+  }
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+}
+
+void test_fseek(FILE *F) {
+  int Ret = fseek(F, SEEK_SET, 1);
+  if (Ret == 0) {
+    if (errno) {} // expected-warning{{undefined}}
+  } else {
+    clang_analyzer_eval(Ret == -1); // expected-warning {{TRUE}}
+    clang_analyzer_eval(errno != 0); // expected-warning {{TRUE}}
+  }
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+}
+
+void check_fgetpos(FILE *F) {
+  errno = 0;
+  fpos_t Pos;
+  int Ret = fgetpos(F, &Pos);
+  if (Ret)
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+  else
+    clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}}
+                                     // expected-warning@-1{{FALSE}}
+  if (errno) {} // no-warning
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+}
+
+void check_fsetpos(FILE *F) {
+  errno = 0;
+  fpos_t Pos;
+  int Ret = fsetpos(F, &Pos);
+  if (Ret)
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+  else
+    clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}}
+                                     // expected-warning@-1{{FALSE}}
+  if (errno) {} // no-warning
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+}
+
+void check_ftell(FILE *F) {
+  errno = 0;
+  long Ret = ftell(F);
+  if (Ret == -1) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+  } else {
+    clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}}
+                                     // expected-warning@-1{{FALSE}}
+    clang_analyzer_eval(Ret >= 0); // expected-warning{{TRUE}}
+  }
+  if (errno) {} // no-warning
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+}
+
+void freadwrite_zerosize(FILE *F) {
+  fwrite(WBuf, 1, 0, F);
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+  fwrite(WBuf, 0, 1, F);
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+  fread(RBuf, 1, 0, F);
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+  fread(RBuf, 0, 1, F);
+  clang_analyzer_eval(feof(F)); // expected-warning {{UNKNOWN}}
+  clang_analyzer_eval(ferror(F)); // expected-warning {{UNKNOWN}}
+}
+
+void freadwrite_zerosize_errno(FILE *F, int A) {
+  switch (A) {
+  case 1:
+    fwrite(WBuf, 1, 0, F);
+    if (errno) {} // expected-warning{{undefined}}
+    break;
+  case 2:
+    fwrite(WBuf, 0, 1, F);
+    if (errno) {} // expected-warning{{undefined}}
+    break;
+  case 3:
+    fread(RBuf, 1, 0, F);
+    if (errno) {} // expected-warning{{undefined}}
+    break;
+  case 4:
+    fread(RBuf, 0, 1, F);
+    if (errno) {} // expected-warning{{undefined}}
+    break;
+  }
+}
Index: clang/test/Analysis/stream-error.c
===================================================================
--- clang/test/Analysis/stream-error.c
+++ clang/test/Analysis/stream-error.c
@@ -7,6 +7,7 @@
 #include "Inputs/system-header-simulator.h"
 
 void clang_analyzer_eval(int);
+void clang_analyzer_dump(int);
 void clang_analyzer_warnIfReached(void);
 void StreamTesterChecker_make_feof_stream(FILE *);
 void StreamTesterChecker_make_ferror_stream(FILE *);
@@ -101,10 +102,15 @@
 }
 
 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);
+  size_t Ret;
+  Ret = fwrite(0, 1, 0, F);
+  clang_analyzer_dump(Ret); // expected-warning {{0 }}
+  Ret = fwrite(0, 0, 1, F);
+  clang_analyzer_dump(Ret); // expected-warning {{0 }}
+  Ret = fread(0, 1, 0, F);
+  clang_analyzer_dump(Ret); // expected-warning {{0 }}
+  Ret = fread(0, 0, 1, F);
+  clang_analyzer_dump(Ret); // expected-warning {{0 }}
 }
 
 void freadwrite_zerosize_eofstate(FILE *F) {
Index: clang/test/Analysis/stream-errno.c
===================================================================
--- /dev/null
+++ clang/test/Analysis/stream-errno.c
@@ -0,0 +1,224 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.unix.Stream,alpha.unix.Errno,apiModeling.StdCLibraryFunctions,debug.ExprInspection \
+// RUN:   -analyzer-config apiModeling.StdCLibraryFunctions:ModelPOSIX=true -verify %s
+
+#include "Inputs/system-header-simulator.h"
+#include "Inputs/errno_func.h"
+
+extern void clang_analyzer_eval(int);
+extern void clang_analyzer_dump(int);
+extern void clang_analyzer_printState();
+
+void check_fopen(void) {
+  FILE *F = fopen("xxx", "r");
+  if (!F) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+    if (errno) {} // no-warning
+    return;
+  }
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}}
+}
+
+void check_tmpfile(void) {
+  FILE *F = tmpfile();
+  if (!F) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+    if (errno) {} // no-warning
+    return;
+  }
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}}
+}
+
+void check_freopen(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  F = freopen("xxx", "w", F);
+  if (!F) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+    if (errno) {} // no-warning
+    return;
+  }
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_fclose(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  int Ret = fclose(F);
+  if (Ret == EOF) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+    if (errno) {} // no-warning
+    return;
+  }
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_fread_size0(void) {
+  char Buf[10];
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  fread(Buf, 0, 1, F);
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_fread_nmemb0(void) {
+  char Buf[10];
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  fread(Buf, 1, 0, F);
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_fread(void) {
+  char Buf[10];
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+
+  int R = fread(Buf, 1, 10, F);
+  if (R < 10) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+    if (errno) {} // no-warning
+    fclose(F);
+    return;
+  }
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_fwrite_size0(void) {
+  char Buf[] = "0123456789";
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  fwrite(Buf, 0, 1, F);
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_fwrite_nmemb0(void) {
+  char Buf[] = "0123456789";
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  fwrite(Buf, 1, 0, F);
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_fwrite(void) {
+  char Buf[] = "0123456789";
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+
+  int R = fwrite(Buf, 1, 10, F);
+  if (R < 10) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+    if (errno) {} // no-warning
+    fclose(F);
+    return;
+  }
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_fseek(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  int S = fseek(F, 11, SEEK_SET);
+  if (S != 0) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+    if (errno) {} // no-warning
+    fclose(F);
+    return;
+  }
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
+
+void check_no_errno_change(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  errno = 1;
+  clearerr(F);
+  if (errno) {} // no-warning
+  feof(F);
+  if (errno) {} // no-warning
+  ferror(F);
+  if (errno) {} // no-warning
+  clang_analyzer_eval(errno == 1); // expected-warning{{TRUE}}
+  fclose(F);
+}
+
+void check_fgetpos(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  errno = 0;
+  fpos_t Pos;
+  int Ret = fgetpos(F, &Pos);
+  if (Ret)
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+  else
+    clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}}
+  if (errno) {} // no-warning
+  fclose(F);
+}
+
+void check_fsetpos(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  errno = 0;
+  fpos_t Pos;
+  int Ret = fsetpos(F, &Pos);
+  if (Ret)
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+  else
+    clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}}
+  if (errno) {} // no-warning
+  fclose(F);
+}
+
+void check_ftell(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  errno = 0;
+  long Ret = ftell(F);
+  if (Ret == -1) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+  } else {
+    clang_analyzer_eval(errno == 0); // expected-warning{{TRUE}}
+    clang_analyzer_eval(Ret >= 0); // expected-warning{{TRUE}}
+  }
+  if (errno) {} // no-warning
+  fclose(F);
+}
+
+void check_rewind(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  errno = 0;
+  rewind(F);
+  clang_analyzer_eval(errno == 0);
+  // expected-warning@-1{{FALSE}}
+  // expected-warning@-2{{TRUE}}
+  fclose(F);
+}
+
+void check_fileno(void) {
+  FILE *F = tmpfile();
+  if (!F)
+    return;
+  int N = fileno(F);
+  if (N == -1) {
+    clang_analyzer_eval(errno != 0); // expected-warning{{TRUE}}
+    if (errno) {} // no-warning
+    fclose(F);
+    return;
+  }
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+}
Index: clang/test/Analysis/stream-errno-note.c
===================================================================
--- /dev/null
+++ clang/test/Analysis/stream-errno-note.c
@@ -0,0 +1,128 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core \
+// RUN:   -analyzer-checker=alpha.unix.Stream \
+// RUN:   -analyzer-checker=alpha.unix.Errno \
+// RUN:   -analyzer-checker=apiModeling.StdCLibraryFunctions \
+// RUN:   -analyzer-config apiModeling.StdCLibraryFunctions:ModelPOSIX=true \
+// RUN:   -analyzer-output text -verify %s
+
+#include "Inputs/system-header-simulator.h"
+#include "Inputs/errno_func.h"
+
+void check_fopen(void) {
+  FILE *F = fopen("xxx", "r");
+  // expected-note@-1{{Assuming that function 'fopen' is successful, in this case the value 'errno' may be undefined after the call and should not be used}}
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}}
+  // expected-note@-1{{An undefined value may be read from 'errno'}}
+  fclose(F);
+}
+
+void check_tmpfile(void) {
+  FILE *F = tmpfile();
+  // expected-note@-1{{Assuming that function 'tmpfile' is successful, in this case the value 'errno' may be undefined after the call and should not be used}}
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno' [alpha.unix.Errno]}}
+  // expected-note@-1{{An undefined value may be read from 'errno'}}
+  fclose(F);
+}
+
+void check_freopen(void) {
+  FILE *F = tmpfile();
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  F = freopen("xxx", "w", F);
+  // expected-note@-1{{Assuming that function 'freopen' is successful}}
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+  // expected-note@-1{{An undefined value may be read from 'errno'}}
+  fclose(F);
+}
+
+void check_fclose(void) {
+  FILE *F = tmpfile();
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  (void)fclose(F);
+  // expected-note@-1{{Assuming that function 'fclose' is successful}}
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+  // expected-note@-1{{An undefined value may be read from 'errno'}}
+}
+
+void check_fread(void) {
+  char Buf[10];
+  FILE *F = tmpfile();
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  (void)fread(Buf, 1, 10, F);
+  // expected-note@-1{{Assuming that function 'fread' is successful}}
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+  // expected-note@-1{{An undefined value may be read from 'errno'}}
+  (void)fclose(F);
+}
+
+void check_fwrite(void) {
+  char Buf[] = "0123456789";
+  FILE *F = tmpfile();
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  int R = fwrite(Buf, 1, 10, F);
+  // expected-note@-1{{Assuming that function 'fwrite' is successful}}
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+  // expected-note@-1{{An undefined value may be read from 'errno'}}
+  (void)fclose(F);
+}
+
+void check_fseek(void) {
+  FILE *F = tmpfile();
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  (void)fseek(F, 11, SEEK_SET);
+  // expected-note@-1{{Assuming that function 'fseek' is successful}}
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+  // expected-note@-1{{An undefined value may be read from 'errno'}}
+  (void)fclose(F);
+}
+
+void check_rewind_errnocheck(void) {
+  FILE *F = tmpfile();
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  errno = 0;
+  rewind(F); // expected-note{{Function 'rewind' indicates failure only by setting of 'errno'}}
+  fclose(F); // expected-warning{{Value of 'errno' was not checked and may be overwritten by function 'fclose' [alpha.unix.Errno]}}
+  // expected-note@-1{{Value of 'errno' was not checked and may be overwritten by function 'fclose'}}
+}
+
+void check_fileno(void) {
+  FILE *F = tmpfile();
+  // expected-note@+2{{'F' is non-null}}
+  // expected-note@+1{{Taking false branch}}
+  if (!F)
+    return;
+  fileno(F);
+  // expected-note@-1{{Assuming that function 'fileno' is successful}}
+  if (errno) {} // expected-warning{{An undefined value may be read from 'errno'}}
+  // expected-note@-1{{An undefined value may be read from 'errno'}}
+  (void)fclose(F);
+}
Index: clang/test/Analysis/Inputs/system-header-simulator.h
===================================================================
--- clang/test/Analysis/Inputs/system-header-simulator.h
+++ clang/test/Analysis/Inputs/system-header-simulator.h
@@ -42,9 +42,9 @@
               fpos_t (*)(void *, fpos_t, int),
               int (*)(void *));
 
-FILE *fopen(const char *path, const char *mode);
+FILE *fopen(const char *restrict path, const char *restrict mode);
 FILE *tmpfile(void);
-FILE *freopen(const char *pathname, const char *mode, FILE *stream);
+FILE *freopen(const char *restrict pathname, const char *restrict mode, FILE *restrict stream);
 int fclose(FILE *fp);
 size_t fread(void *restrict, size_t, size_t, FILE *restrict);
 size_t fwrite(const void *restrict, size_t, size_t, FILE *restrict);
@@ -52,7 +52,7 @@
 int fseek(FILE *__stream, long int __off, int __whence);
 long int ftell(FILE *__stream);
 void rewind(FILE *__stream);
-int fgetpos(FILE *stream, fpos_t *pos);
+int fgetpos(FILE *restrict stream, fpos_t *restrict pos);
 int fsetpos(FILE *stream, const fpos_t *pos);
 void clearerr(FILE *stream);
 int feof(FILE *stream);
Index: clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -17,6 +17,7 @@
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
@@ -85,10 +86,10 @@
 /// Full state information about a stream pointer.
 struct StreamState {
   /// The last file operation called in the stream.
+  /// Can be nullptr.
   const FnDescription *LastOperation;
 
   /// State of a stream symbol.
-  /// FIXME: We need maybe an "escaped" state later.
   enum KindTy {
     Opened, /// Stream is opened.
     Closed, /// Closed stream (an invalid stream pointer after it was closed).
@@ -202,7 +203,7 @@
 ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State,
                         CheckerContext &C, const CallExpr *CE) {
   State = State->BindExpr(CE, C.getLocationContext(),
-                          C.getSValBuilder().makeIntVal(Value, false));
+                          C.getSValBuilder().makeIntVal(Value, CE->getType()));
   return State;
 }
 
@@ -249,10 +250,14 @@
        {&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}},
-      {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}},
-      {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}},
+      {{"ftell", 1},
+       {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}},
+      {{"rewind", 1},
+       {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}},
+      {{"fgetpos", 2},
+       {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}},
+      {{"fsetpos", 2},
+       {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}},
       {{"clearerr", 1},
        {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}},
       {{"feof", 1},
@@ -278,6 +283,8 @@
         0}},
   };
 
+  mutable Optional<int> EofVal;
+
   void evalFopen(const FnDescription *Desc, const CallEvent &Call,
                  CheckerContext &C) const;
 
@@ -303,6 +310,18 @@
   void evalFseek(const FnDescription *Desc, const CallEvent &Call,
                  CheckerContext &C) const;
 
+  void evalFgetpos(const FnDescription *Desc, const CallEvent &Call,
+                   CheckerContext &C) const;
+
+  void evalFsetpos(const FnDescription *Desc, const CallEvent &Call,
+                   CheckerContext &C) const;
+
+  void evalFtell(const FnDescription *Desc, const CallEvent &Call,
+                 CheckerContext &C) const;
+
+  void evalRewind(const FnDescription *Desc, const CallEvent &Call,
+                  CheckerContext &C) const;
+
   void preDefault(const FnDescription *Desc, const CallEvent &Call,
                   CheckerContext &C) const;
 
@@ -411,6 +430,17 @@
     });
   }
 
+  void initEof(CheckerContext &C) const {
+    if (EofVal)
+      return;
+
+    if (const llvm::Optional<int> OptInt =
+            tryExpandAsInteger("EOF", C.getPreprocessor()))
+      EofVal = *OptInt;
+    else
+      EofVal = -1;
+  }
+
   /// Searches for the ExplodedNode where the file descriptor was acquired for
   /// StreamSym.
   static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N,
@@ -426,8 +456,7 @@
 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
 
 inline void assertStreamStateOpened(const StreamState *SS) {
-  assert(SS->isOpened() &&
-         "Previous create of error node for non-opened stream failed?");
+  assert(SS->isOpened() && "Stream is expected to be opened");
 }
 
 const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N,
@@ -457,6 +486,8 @@
 
 void StreamChecker::checkPreCall(const CallEvent &Call,
                                  CheckerContext &C) const {
+  initEof(C);
+
   const FnDescription *Desc = lookupFn(Call);
   if (!Desc || !Desc->PreFn)
     return;
@@ -574,6 +605,10 @@
   if (!SS)
     return;
 
+  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return;
+
   assertStreamStateOpened(SS);
 
   // Close the File Descriptor.
@@ -581,7 +616,16 @@
   // and can not be used any more.
   State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc));
 
-  C.addTransition(State);
+  // Return 0 on success, EOF on failure.
+  SValBuilder &SVB = C.getSValBuilder();
+  ProgramStateRef StateSuccess = State->BindExpr(
+      CE, C.getLocationContext(), SVB.makeIntVal(0, C.getASTContext().IntTy));
+  ProgramStateRef StateFailure =
+      State->BindExpr(CE, C.getLocationContext(),
+                      SVB.makeIntVal(*EofVal, C.getASTContext().IntTy));
+
+  C.addTransition(StateSuccess);
+  C.addTransition(StateFailure);
 }
 
 void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call,
@@ -766,6 +810,131 @@
   C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym));
 }
 
+void StreamChecker::evalFgetpos(const FnDescription *Desc,
+                                const CallEvent &Call,
+                                CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
+  if (!Sym)
+    return;
+
+  // Do not evaluate if stream is not found.
+  if (!State->get<StreamMap>(Sym))
+    return;
+
+  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return;
+
+  DefinedSVal RetVal = makeRetVal(C, CE);
+  State = State->BindExpr(CE, C.getLocationContext(), RetVal);
+  ProgramStateRef StateNotFailed, StateFailed;
+  std::tie(StateFailed, StateNotFailed) =
+      C.getConstraintManager().assumeDual(State, RetVal);
+
+  // This function does not affect the stream state.
+  // Still we add success and failure state with the appropriate return value.
+  // StdLibraryFunctionsChecker can change these states (set the 'errno' state).
+  C.addTransition(StateNotFailed);
+  C.addTransition(StateFailed);
+}
+
+void StreamChecker::evalFsetpos(const FnDescription *Desc,
+                                const CallEvent &Call,
+                                CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
+  if (!StreamSym)
+    return;
+
+  const StreamState *SS = State->get<StreamMap>(StreamSym);
+  if (!SS)
+    return;
+
+  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return;
+
+  assertStreamStateOpened(SS);
+
+  DefinedSVal RetVal = makeRetVal(C, CE);
+  State = State->BindExpr(CE, C.getLocationContext(), RetVal);
+  ProgramStateRef StateNotFailed, StateFailed;
+  std::tie(StateFailed, StateNotFailed) =
+      C.getConstraintManager().assumeDual(State, RetVal);
+
+  StateNotFailed = StateNotFailed->set<StreamMap>(
+      StreamSym, StreamState::getOpened(Desc, ErrorNone, false));
+
+  // At failure ferror could be set.
+  // The standards do not tell what happens with the file position at failure.
+  // But we can assume that it is dangerous to make a next I/O operation after
+  // the position was not set correctly (similar to 'fseek').
+  StateFailed = StateFailed->set<StreamMap>(
+      StreamSym, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true));
+
+  C.addTransition(StateNotFailed);
+  C.addTransition(StateFailed);
+}
+
+void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call,
+                              CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol();
+  if (!Sym)
+    return;
+
+  if (!State->get<StreamMap>(Sym))
+    return;
+
+  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return;
+
+  SValBuilder &SVB = C.getSValBuilder();
+  NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>();
+  ProgramStateRef StateNotFailed =
+      State->BindExpr(CE, C.getLocationContext(), RetVal);
+  auto Cond = SVB.evalBinOp(State, BO_GE, RetVal,
+                            SVB.makeZeroVal(C.getASTContext().LongTy),
+                            SVB.getConditionType())
+                  .getAs<DefinedOrUnknownSVal>();
+  if (!Cond)
+    return;
+  StateNotFailed = StateNotFailed->assume(*Cond, true);
+  if (!StateNotFailed)
+    return;
+
+  ProgramStateRef StateFailed = State->BindExpr(
+      CE, C.getLocationContext(), SVB.makeIntVal(-1, C.getASTContext().LongTy));
+
+  C.addTransition(StateNotFailed);
+  C.addTransition(StateFailed);
+}
+
+void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call,
+                               CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol();
+  if (!StreamSym)
+    return;
+
+  const StreamState *SS = State->get<StreamMap>(StreamSym);
+  if (!SS)
+    return;
+
+  auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+  if (!CE)
+    return;
+
+  assertStreamStateOpened(SS);
+
+  State = State->set<StreamMap>(StreamSym,
+                                StreamState::getOpened(Desc, ErrorNone, false));
+
+  C.addTransition(State);
+}
+
 void StreamChecker::evalClearerr(const FnDescription *Desc,
                                  const CallEvent &Call,
                                  CheckerContext &C) const {
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to