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

Uploading new diff with added comments and renaming.
(And a new feature of leaking pointer detection.)
The code is at least saved here, can be used as starting point for smaller 
changes.


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D72705

Files:
  clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
  clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
  clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp
  clang/test/Analysis/Inputs/system-header-simulator.h
  clang/test/Analysis/error-return.c

Index: clang/test/Analysis/error-return.c
===================================================================
--- /dev/null
+++ clang/test/Analysis/error-return.c
@@ -0,0 +1,556 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.unix.ErrorReturn -verify %s
+
+#include "Inputs/system-header-simulator.h"
+
+/*
+Functions from CERT ERR33-C that should be checked for error:
+https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors
+
+void *aligned_alloc( size_t alignment, size_t size );
+errno_t asctime_s(char *buf, rsize_t bufsz, const struct tm *time_ptr);
+int at_quick_exit( void (*func)(void) );
+int atexit( void (*func)(void) );
+void* bsearch( const void *key, const void *ptr, size_t count, size_t size,
+               int (*comp)(const void*, const void*) );
+void* bsearch_s( const void *key, const void *ptr, rsize_t count, rsize_t size,
+                 int (*comp)(const void *, const void *, void *),
+                 void *context );
+wint_t btowc( int c );
+size_t c16rtomb( char * restrict s, char16_t c16, mbstate_t * restrict ps );
+size_t c32rtomb( char * restrict s, char32_t c32, mbstate_t * restrict ps );
+void* calloc( size_t num, size_t size );
+clock_t clock(void);
+int cnd_broadcast( cnd_t *cond );
+int cnd_init( cnd_t* cond );
+int cnd_signal( cnd_t *cond );
+int cnd_timedwait( cnd_t* restrict cond, mtx_t* restrict mutex,
+                   const struct timespec* restrict time_point );
+int cnd_wait( cnd_t* cond, mtx_t* mutex );
+errno_t ctime_s(char *buffer, rsize_t bufsz, const time_t *time);
+int fclose( FILE *stream );
+int fflush( FILE *stream );
+int fgetc( FILE *stream );
+int fgetpos( FILE *restrict stream, fpos_t *restrict pos );
+char *fgets( char *restrict str, int count, FILE *restrict stream );
+wint_t fgetwc( FILE *stream );
+FILE *fopen( const char *restrict filename, const char *restrict mode );
+errno_t fopen_s(FILE *restrict *restrict streamptr,
+                const char *restrict filename,
+                const char *restrict mode);
+int fprintf( FILE *restrict stream, const char *restrict format, ... );
+int fprintf_s(FILE *restrict stream, const char *restrict format, ...);
+int fputc( int ch, FILE *stream );
+int fputs( const char *restrict str, FILE *restrict stream );
+wint_t fputwc( wchar_t ch, FILE *stream );
+int fputws( const wchar_t * restrict str, FILE * restrict stream );
+size_t fread( void *restrict buffer, size_t size, size_t count,
+              FILE *restrict stream );
+FILE *freopen( const char *restrict filename, const char *restrict mode,
+               FILE *restrict stream );
+errno_t freopen_s(FILE *restrict *restrict newstreamptr,
+                  const char *restrict filename, const char *restrict mode,
+                  FILE *restrict stream);
+int fscanf( FILE *restrict stream, const char *restrict format, ... );
+int fscanf_s(FILE *restrict stream, const char *restrict format, ...);
+int fseek( FILE *stream, long offset, int origin );
+int fsetpos( FILE *stream, const fpos_t *pos );
+long ftell( FILE *stream );
+int fwprintf( FILE *restrict stream,
+              const wchar_t *restrict format, ... );
+int fwprintf_s( FILE *restrict stream,
+                const wchar_t *restrict format, ...);
+size_t fwrite( const void *restrict buffer, size_t size, size_t count,
+               FILE *restrict stream ); // more exact error return: < count
+int fwscanf( FILE *restrict stream,
+             const wchar_t *restrict format, ... );
+int fwscanf_s( FILE *restrict stream,
+               const wchar_t *restrict format, ...);
+int getc( FILE *stream );
+int getchar(void);
+char *getenv( const char *name );
+errno_t getenv_s( size_t *restrict len, char *restrict value,
+                  rsize_t valuesz, const char *restrict name );
+char *gets_s( char *str, rsize_t n );
+wint_t getwc( FILE *stream );
+wint_t getwchar(void);
+struct tm *gmtime( const time_t *time );
+struct tm *gmtime_s(const time_t *restrict time, struct tm *restrict result);
+struct tm *localtime( const time_t *time );
+struct tm *localtime_s(const time_t *restrict time, struct tm *restrict result);
+void* malloc( size_t size );
+int mblen( const char* s, size_t n );
+size_t mbrlen( const char *restrict s, size_t n, mbstate_t *restrict ps );
+size_t mbrtoc16( char16_t * restrict pc16, const char * restrict s,
+                 size_t n, mbstate_t * restrict ps );
+size_t mbrtoc32( char32_t restrict * pc32, const char * restrict s,
+                 size_t n, mbstate_t * restrict ps );
+size_t mbrtowc( wchar_t *restrict pwc, const char *restrict s, size_t n,
+                mbstate_t *restrict ps );
+size_t mbsrtowcs( wchar_t *restrict dst, const char **restrict src, size_t len,
+                  mbstate_t *restrict ps);
+errno_t mbsrtowcs_s( size_t *restrict retval,
+                     wchar_t *restrict dst, rsize_t dstsz,
+                     const char **restrict src, rsize_t len,
+                     mbstate_t *restrict ps);
+size_t mbstowcs( wchar_t *restrict dst, const char *restrict src, size_t len);
+errno_t mbstowcs_s(size_t *restrict retval, wchar_t *restrict dst,
+                  rsize_t dstsz, const char *restrict src, rsize_t len);
+int mbtowc( wchar_t *restrict pwc, const char *restrict s, size_t n );
+void* memchr( const void* ptr, int ch, size_t count );
+time_t mktime( struct tm *time );
+int mtx_init( mtx_t* mutex, int type );
+int mtx_lock( mtx_t* mutex );
+int mtx_timedlock( mtx_t *restrict mutex,
+                   const struct timespec *restrict time_point );
+int mtx_trylock( mtx_t *mutex );
+int mtx_unlock( mtx_t *mutex );
+int printf_s(const char *restrict format, ...);
+int putc( int ch, FILE *stream );
+wint_t putwc( wchar_t ch, FILE *stream );
+int raise( int sig );
+void *realloc( void *ptr, size_t new_size );
+int remove( const char *fname );
+int rename( const char *old_filename, const char *new_filename );
+char* setlocale( int category, const char* locale);
+int setvbuf( FILE *restrict stream, char *restrict buffer,
+             int mode, size_t size );
+int scanf( const char *restrict format, ... );
+int scanf_s(const char *restrict format, ...);
+void (*signal( int sig, void (*handler) (int))) (int);
+int snprintf( char *restrict buffer, size_t bufsz,
+              const char *restrict format, ... );
+int snprintf_s(char *restrict buffer, rsize_t bufsz,
+               const char *restrict format, ...);
+int snwprintf_s( wchar_t * restrict s, rsize_t n,
+                 const wchar_t * restrict format, ...); // missing from CERT list
+int sprintf( char *restrict buffer, const char *restrict format, ... );
+int sprintf_s(char *restrict buffer, rsize_t bufsz,
+              const char *restrict format, ...);
+int sscanf( const char *restrict buffer, const char *restrict format, ... );
+int sscanf_s(const char *restrict buffer, const char *restrict format, ...);
+char *strchr( const char *str, int ch );
+errno_t strerror_s( char *buf, rsize_t bufsz, errno_t errnum );
+size_t strftime( char *restrict str, size_t count,
+                 const char *restrict format, const struct tm *restrict time );
+char* strpbrk( const char* dest, const char* breakset );
+char *strrchr( const char *str, int ch );
+char *strstr( const char* str, const char* substr );
+double      strtod( const char *restrict str, char **restrict str_end );
+float       strtof( const char *restrict str, char **restrict str_end );
+intmax_t strtoimax( const char *restrict nptr,
+                    char **restrict endptr, int base );
+char *strtok( char *restrict str, const char *restrict delim );
+char *strtok_s(char *restrict str, rsize_t *restrict strmax,
+     const char *restrict delim, char **restrict ptr);
+long      strtol( const char *restrict str, char **restrict str_end, int base );
+long double strtold( const char *restrict str, char **restrict str_end );
+long long strtoll( const char *restrict str, char **restrict str_end, int base );
+uintmax_t strtoumax( const char *restrict nptr,
+                     char **restrict endptr, int base );
+unsigned long      strtoul( const char *restrict str, char **restrict str_end,
+                            int base );
+unsigned long long strtoull( const char *restrict str, char **restrict str_end,
+                             int base );
+size_t strxfrm( char *restrict dest, const char *restrict src,
+                size_t count );
+int swprintf( wchar_t *restrict buffer, size_t bufsz,
+              const wchar_t *restrict format, ... );
+int swprintf_s( wchar_t *restrict buffer, rsize_t bufsz,
+                const wchar_t* restrict format, ...);
+int swscanf( const wchar_t *restrict buffer,
+             const wchar_t *restrict format, ... );
+int swscanf_s( const wchar_t *restrict s,
+               const wchar_t *restrict format, ...);
+int thrd_create( thrd_t *thr, thrd_start_t func, void *arg );
+int thrd_detach( thrd_t thr );
+int thrd_join( thrd_t thr, int *res );
+int thrd_sleep( const struct timespec* duration,
+                struct timespec* remaining );
+time_t time( time_t *arg );
+int timespec_get( struct timespec *ts, int base);
+FILE *tmpfile(void);
+errno_t tmpfile_s(FILE * restrict * restrict streamptr);
+char *tmpnam( char *filename );
+errno_t tmpnam_s(char *filename_s, rsize_t maxsize);
+int tss_create( tss_t* tss_key, tss_dtor_t destructor );
+void *tss_get( tss_t tss_key );
+int tss_set( tss_t tss_id, void *val );
+int ungetc( int ch, FILE *stream );
+wint_t ungetwc( wint_t ch, FILE *stream );
+int vfprintf( FILE *restrict stream, const char *restrict format,
+              va_list vlist );
+int vfprintf_s( FILE *restrict stream, const char *restrict format,
+                va_list arg);
+int vfscanf( FILE *restrict stream, const char *restrict format,
+             va_list vlist );
+int vfscanf_s( FILE *restrict stream, const char *restrict format,
+               va_list vlist);
+int vfwprintf( FILE *restrict stream,
+               const wchar_t *restrict format, va_list vlist );
+int vfwprintf_s( FILE * restrict stream,
+                 const wchar_t *restrict format, va_list vlist);
+int vfwscanf( FILE *restrict stream,
+              const wchar_t *restrict format, va_list vlist );
+int vfwscanf_s( FILE *restrict stream,
+                const wchar_t *restrict format, va_list vlist );
+int vprintf( const char *restrict format, va_list vlist ); // missing from CERT list
+int vprintf_s( const char *restrict format, va_list arg);
+int vscanf( const char *restrict format, va_list vlist );
+int vscanf_s(const char *restrict format, va_list vlist);
+int vsnprintf( char *restrict buffer, size_t bufsz,
+               const char *restrict format, va_list vlist );
+int vsnprintf_s(char *restrict buffer, rsize_t bufsz,
+                const char *restrict format, va_list arg);
+int vsnwprintf_s( wchar_t *restrict buffer, rsize_t bufsz,
+                  const wchar_t *restrict format, va_list vlist); // missing from CERT list
+int vsprintf( char *restrict buffer, const char *restrict format,
+              va_list vlist );
+int vsprintf_s( char *restrict buffer, rsize_t bufsz,
+                const char *restrict format, va_list arg);
+int vsscanf( const char *restrict buffer, const char *restrict format,
+             va_list vlist );
+int vsscanf_s( const char *restrict buffer, const char *restrict format,
+               va_list vlist);
+int vswprintf( wchar_t *restrict buffer, size_t bufsz,
+               const wchar_t *restrict format, va_list vlist );
+int vswprintf_s( wchar_t *restrict buffer, rsize_t bufsz,
+                 const wchar_t * restrict format, va_list vlist);
+int vswscanf( const wchar_t *restrict buffer,
+              const wchar_t *restrict format, va_list vlist );
+int vswscanf_s( const wchar_t *restrict buffer,
+                const wchar_t *restrict format, va_list vlist );
+int vwprintf_s( const wchar_t *restrict format, va_list vlist);
+int vwscanf( const wchar_t *restrict format, va_list vlist );
+int vwscanf_s( const wchar_t *restrict format, va_list vlist );
+size_t wcrtomb( char *restrict s, wchar_t wc, mbstate_t *restrict ps);
+errno_t wcrtomb_s(size_t *restrict retval, char *restrict s, rsize_t ssz,
+                  wchar_t wc, mbstate_t *restrict ps); // missing from CERT list
+wchar_t* wcschr( const wchar_t* str, wchar_t ch );
+size_t wcsftime( wchar_t* str, size_t count, const wchar_t* format, tm* time );
+wchar_t* wcspbrk( const wchar_t* dest, const wchar_t* str );
+wchar_t* wcsrchr( const wchar_t* str, wchar_t ch );
+size_t wcsrtombs( char *restrict dst, const wchar_t **restrict src, size_t len,
+                  mbstate_t *restrict ps);
+errno_t wcsrtombs_s( size_t *restrict retval, char *restrict dst, rsize_t dstsz,
+                     const wchar_t **restrict src, rsize_t len,
+                     mbstate_t *restrict ps);
+wchar_t* wcsstr( const wchar_t* dest, const wchar_t* src );
+double      wcstod( const wchar_t * restrict str, wchar_t ** restrict str_end );
+float       wcstof( const wchar_t * restrict str, wchar_t ** restrict str_end );
+intmax_t wcstoimax( const wchar_t *restrict nptr,
+                    wchar_t **restrict endptr, int base );
+wchar_t *wcstok(wchar_t * restrict str, const wchar_t * restrict delim,
+                wchar_t **restrict ptr);
+wchar_t *wcstok_s( wchar_t *restrict str, rsize_t *restrict strmax,
+                   const wchar_t *restrict delim, wchar_t **restrict ptr);
+long      wcstol( const wchar_t * str, wchar_t ** restrict str_end,
+                  int base );
+long double wcstold( const wchar_t * restrict str, wchar_t ** restrict str_end );
+long long wcstoll( const wchar_t * restrict str, wchar_t ** restrict str_end,
+                   int base );
+size_t wcstombs( char *restrict dst, const wchar_t *restrict src, size_t len );
+errno_t wcstombs_s( size_t *restrict retval, char *restrict dst, rsize_t dstsz,
+                    const wchar_t *restrict src, rsize_t len );
+uintmax_t wcstoumax( const wchar_t *restrict nptr,
+                     wchar_t **restrict endptr, int base );
+unsigned long      wcstoul( const wchar_t * restrict str,
+                            wchar_t ** restrict str_end, int base );
+unsigned long long wcstoull( const wchar_t * restrict str,
+                             wchar_t ** restrict str_end, int base );
+size_t wcsxfrm( wchar_t* restrict dest, const wchar_t* restrict src, size_t count );
+int wctob( wint_t c );
+int wctomb( char *s, wchar_t wc );
+errno_t wctomb_s(int *restrict status, char *restrict s, rsize_t ssz, wchar_t wc); // CERT list contains wrong description?
+wctrans_t wctrans( const char* str );
+wctype_t wctype( const char* str );
+wchar_t *wmemchr( const wchar_t *ptr, wchar_t ch, size_t count );
+int wprintf_s( const wchar_t *restrict format, ...);
+int wscanf( const wchar_t *restrict format, ... );
+int wscanf_s( const wchar_t *restrict format, ...);
+
+These are OK if not checked:
+int putchar( int ch );
+wint_t putwchar( wchar_t ch );
+int puts( const char *str );
+int printf( const char *restrict format, ... );
+int vprintf( const char *restrict format, va_list vlist );
+int wprintf( const wchar_t *restrict format, ... );
+int vwprintf( const wchar_t *restrict format, va_list vlist );
+
+The following functions are OK if not checked.
+These have no error return value therefore can not be handled by this checker:
+kill_dependency()
+memcpy(), wmemcpy()
+memmove(), wmemmove()
+strcpy(), wcscpy()
+strncpy(), wcsncpy()
+strcat(), wcscat()
+strncat(), wcsncat()
+memset(), wmemset()
+*/
+
+///////////////////////////////////////////////////////////////////////////////
+// Null
+///////////////////////////////////////////////////////////////////////////////
+
+void *ptr();
+
+void test_Null_LNot() {
+  void *P = aligned_alloc(4, 8);
+  if (!P) {
+  }
+}
+
+void test_Null_LAnd() {
+  void *P = aligned_alloc(4, 8);
+  if (P && ptr()) {
+  }
+}
+
+void test_Null_LOr() {
+  void *P = aligned_alloc(4, 8);
+  if (P || ptr()) {
+  }
+}
+
+void test_Null_EQ() {
+  void *P = aligned_alloc(4, 8);
+  if (P == NULL) {
+  }
+}
+
+void test_Null_NE() {
+  void *P = aligned_alloc(4, 8);
+  if (P != NULL) {
+  }
+}
+
+void test_Null_Add() {
+  void *P = aligned_alloc(4, 8);
+  void *X = P + 1; // expected-warning{{Use of return value that was not checked}}
+}
+
+void test_Null_Cond_Cond() {
+  void *P = aligned_alloc(4, 8);
+  void *X = (P ? ptr() : NULL);
+}
+
+int f();
+
+void test_Null_Cond_Use() {
+  void *P = aligned_alloc(4, 8);
+  // Both conditional paths cause error.
+  void *X = (f() ? P : ptr());
+  // expected-warning@-1{{Use of return value that was not checked}}
+  // expected-warning@-2{{Return value was not checked}}
+}
+
+void test_Null_Call_EQ() {
+  if (aligned_alloc(4, 8) == NULL) {
+  }
+}
+
+int test_Null_Ret_EQ() {
+  return aligned_alloc(4, 8) == NULL;
+}
+
+void test_Null_Assign_EQ() {
+  int X = (aligned_alloc(4, 8) == NULL);
+}
+
+void test_Null_Call_Assign_EQ() {
+  void *P;
+  if ((P = aligned_alloc(4, 8)) == NULL) {
+  }
+}
+
+void test_Null_CastedUse() {
+  char *P = (char*)aligned_alloc(4, 8);
+  char X = *P; // expected-warning{{Use of return value that was not checked}}
+}
+
+void test_Null_VoidCast() {
+  (void)aligned_alloc(4, 8);
+}
+
+void test_Null_Initialized() {
+  void *P = aligned_alloc(4, 8);
+  void *P1 = P;
+  if (!P1) {
+  }
+}
+
+void test_Null_Assigned() {
+  void *P = aligned_alloc(4, 8);
+  void *P1;
+  P1 = P;
+  if (!P1) {
+  }
+}
+
+void test_Null_If() {
+  void *P = aligned_alloc(4, 8);
+  if (P) {
+  }
+}
+
+void test_Null_While() {
+  void *P = aligned_alloc(4, 8);
+  while (P) {
+    P = aligned_alloc(4, 8);
+  }
+}
+
+void test_Null_Do() {
+  void *P;
+  do {
+    P = aligned_alloc(4, 8);
+  } while (P);
+}
+
+void test_Null_For() {
+  void *P;
+  for (P = aligned_alloc(4, 8); P;) {
+  }
+}
+
+void test_Null_UnusedVar() {
+  void *P = aligned_alloc(4, 8);
+} // expected-warning{{Return value was not checked}}
+
+void test_Null_UnusedCall() {
+  aligned_alloc(4, 8);
+} // expected-warning{{Return value was not checked}}
+
+void test_Null_WrongEQ() {
+  if (aligned_alloc(4, 8) == (void *)1) { // expected-warning{{Use of return value that was not checked}}
+  }
+}
+
+void test_Null_UncheckedSysCall() {
+  void *P = aligned_alloc(4, 8);
+  memcpy(P, ptr(), 1); // expected-warning{{Use of return value that was not checked}}
+}
+
+void test_Null_UncheckedSysCallInPlace() {
+  memcpy(aligned_alloc(4, 8), ptr(), 1); // expected-warning{{Use of return value that was not checked}}
+}
+
+void test_Null_CheckAfterUse() {
+  void *P = aligned_alloc(4, 8);
+  memcpy(P, ptr(), 1); // expected-warning{{Use of return value that was not checked}}
+  if (P) {
+  }
+}
+
+void check1(void *P) {
+  if (P) {
+  }
+}
+
+void test_Null_CheckInFn() {
+  void *P = aligned_alloc(4, 8);
+  check1(P);
+}
+
+void check2(void *P) {
+  memcpy(P, ptr(), 1); // expected-warning{{Use of return value that was not checked}}
+}
+
+void test_Null_SysCallInFn() {
+  void *P = aligned_alloc(4, 8);
+  check2(P);
+}
+
+void unknown(void *);
+
+void test_Null_Escape() {
+  void *P = aligned_alloc(4, 8);
+  unknown(P);
+  memcpy(P, ptr(), 1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// EOFOrNeg
+///////////////////////////////////////////////////////////////////////////////
+
+FILE *file();
+
+void test_EOFOrNeg_LT() {
+  int X = fputs("str", file());
+  if (X < 0) {
+  }
+}
+
+void test_EOFOrNeg_LT_Wrong1() {
+  int X = fputs("str", file());
+  if (X < 1) { // expected-warning{{Use of return value that was not checked}}
+  }
+}
+
+void test_EOFOrNeg_LT_Wrong2() {
+  int X = fputs("str", file());
+  if (X < -1) { // expected-warning{{Use of return value that was not checked}}
+  }
+}
+
+void test_EOFOrNeg_GT() {
+  int X = fputs("str", file());
+  if (0 > X) {
+  }
+}
+
+void test_EOFOrNeg_EQ() {
+  int X = fputs("str", file());
+  if (X == -1) {
+  }
+}
+
+void test_EOFOrNeg_EQ_Wrong() {
+  int X = fputs("str", file());
+  if (X == 0) { // expected-warning{{Use of return value that was not checked}}
+  }
+}
+
+void test_EOFOrNeg_NE() {
+  int X = fputs("str", file());
+  if (X != -1) {
+  }
+}
+
+void test_EOFOrNeg_LNot() {
+  int X = fputs("str", file());
+  if (!X) { // expected-warning{{Use of return value that was not checked}}
+  }
+}
+
+void test_EOFOrNeg_If() {
+  int X = fputs("str", file());
+  if (X) { // expected-warning{{Use of return value that was not checked}}
+  }
+}
+
+void test_EOFOrNeg_Call_If() {
+  if (fputs("str", file())) { // expected-warning{{Use of return value that was not checked}}
+  }
+}
+
+void test_EOFOrNeg_Use() {
+  int X = fputs("str", file());
+  int Y = X + 1; // expected-warning{{Use of return value that was not checked}}
+}
+
+void unknown1(int);
+
+void test_EOFOrNeg_EscapeFunction() {
+  int X = fputs("str", file());
+  unknown1(X);
+  int Y = X + 1;
+}
+
+int GlobalInt;
+
+void test_EOFOrNeg_EscapeAssignment() {
+  GlobalInt = fputs("str", file());
+  int X = GlobalInt;
+}
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
@@ -112,4 +112,9 @@
 #define NULL __DARWIN_NULL
 #endif
 
-#define offsetof(t, d) __builtin_offsetof(t, d)
\ No newline at end of file
+#define offsetof(t, d) __builtin_offsetof(t, d)
+
+// Some more functions.
+
+void *aligned_alloc(size_t alignment, size_t size);
+int fputs(const char *restrict str, FILE *restrict stream);
Index: clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp
===================================================================
--- /dev/null
+++ clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp
@@ -0,0 +1,735 @@
+//===-- ErrorReturnChecker.cpp ------------------------------------*- C++ -*--//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines ErrorReturnChecker, a builtin checker that checks for
+// error checking of certain C API function return values.
+// This check is taken from SEI CERT ERR33-C:
+// https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors
+//
+// About the checker:
+// It involves a predefined set of system call functions that can fail and
+// return a specific error code on failure. (This list is provided by the CERT
+// rule.) The checker tries to verify if there is a statement in the code that
+// checks the returned value. For different kinds of error return values
+// different kinds of check statements are accepted. The first use (that is not
+// assignment or pass to function) of the return value of the function call that
+// is checked should be the check statement. Any other use of the returned value
+// (except assignment or pass to function call) is taken as use before check and
+// reported as checker warning. Additionally, if the return value is not used at
+// all a warning is generated for unchecked return value. Passing the return
+// value to a system function results in warning too.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Expr.h"
+#include "clang/AST/ParentMap.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#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;
+
+/// Store a saved argument value for an API function call to check.
+/// For certain function calls the error result return value is related to an
+/// argument that was passed to the function. This value needs to be saved at
+/// the event of the function call.
+REGISTER_MAP_WITH_PROGRAMSTATE(ParmValMap, SymbolRef, llvm::APSInt)
+
+namespace {
+
+/// Interface and abstraction for various kinds of error return value and way of
+/// checking it. The "test" functions are called if a corresponding construct in
+/// the code is found. The functions should return true the code is found to be
+/// acceptable as error check.
+class ErrorReturnCheckKind {
+public:
+  /// Test if an encountered binary operator where the return value is involved
+  /// is a valid check statement. The return value appears in one side of the
+  /// operator (`ChildIsLHS` indicates if it is on the LHS). If the other side
+  /// contains a known (mostly constant) value, it is already calculated in
+  /// `KnownValue`. `RetTy` is the type of the return value (return type of the
+  /// function call in the code to check).
+  virtual bool testBinOpForCheckStatement(BasicValueFactory &BVF,
+                                          const BinaryOperator *BinOp,
+                                          const llvm::APSInt *KnownValue,
+                                          QualType RetTy,
+                                          bool ChildIsLHS) const = 0;
+  /// Tell if the bare appearance of the return value in place of a condition
+  /// (for example 'X' in 'if (X)') can be taken as a check for error return
+  /// value.
+  virtual bool testConditionAsCheckStatement() const = 0;
+};
+
+/// Error return value is a NULL value.
+/// Should work with any (pointer) type of null value.
+class NullErrorReturn : public ErrorReturnCheckKind {
+public:
+  bool testBinOpForCheckStatement(BasicValueFactory &BVF,
+                                  const BinaryOperator *BinOp,
+                                  const llvm::APSInt *KnownValue,
+                                  QualType RetTy,
+                                  bool ChildIsLHS) const override {
+    BinaryOperatorKind Opcode = BinOp->getOpcode();
+    switch (Opcode) {
+    case BO_EQ:
+    case BO_NE:
+      return KnownValue && KnownValue->isNullValue();
+    case BO_LAnd:
+    case BO_LOr:
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  virtual bool testConditionAsCheckStatement() const override { return true; }
+};
+
+/// Error return is a -1 or any negative value (both is accepted).
+/// More precise, the check for error return value should be comparison to -1
+/// or relational comparison to 0.
+/// This is to be used with signed types only.
+class EOFOrNegativeErrorReturn : public ErrorReturnCheckKind {
+public:
+  bool testBinOpForCheckStatement(BasicValueFactory &BVF,
+                                  const BinaryOperator *BinOp,
+                                  const llvm::APSInt *KnownValue,
+                                  QualType RetTy,
+                                  bool ChildIsLHS) const override {
+    if (!KnownValue)
+      return false;
+
+    bool KnownNull = KnownValue->isNullValue();
+    bool KnownEOF = ((*KnownValue) == BVF.getValue(-1, RetTy));
+
+    if (ChildIsLHS) {
+      switch (BinOp->getOpcode()) {
+      case BO_EQ: // 'X == -1'
+      case BO_NE: // 'X != -1'
+        return KnownEOF;
+      case BO_LT: // 'X < 0'
+        return KnownNull;
+      case BO_GE: // 'X >= 0'
+        return KnownNull;
+      case BO_LE: // 'X <= -1'
+        return KnownEOF;
+      case BO_GT: // 'X > -1'
+        return KnownEOF;
+      default:
+        return false;
+      }
+    } else {
+      switch (BinOp->getOpcode()) {
+      case BO_EQ: // '-1 == X'
+      case BO_NE: // '-1 != X'
+        return KnownEOF;
+      case BO_LT: // '0 > X'
+        return KnownNull;
+      case BO_GE: // '0 <= X'
+        return KnownNull;
+      case BO_LE: // '-1 >= X'
+        return KnownEOF;
+      case BO_GT: // '-1 < X'
+        return KnownEOF;
+      default:
+        return false;
+      }
+    }
+    return false;
+  }
+
+  virtual bool testConditionAsCheckStatement() const override { return false; }
+};
+
+/// Any comparison of the return value is a sufficient error check.
+/// This is needed if an unknown fixed value returned from the function can be
+/// accepted as a success code. This success code is not known but there needs
+/// to be a comparison to the value that can be relational (or any other) too.
+/// The function may have an error return value too but it is not determinable
+/// if the comparison to the error return or the success return code is done in
+/// the code. Therefore any comparison with any value is accepted now. This is
+/// the case for example at "scanf" style of functions. It is assumed that not
+/// the zero is the success or error return value.
+class AnythingErrorReturn : public ErrorReturnCheckKind {
+public:
+  bool testBinOpForCheckStatement(BasicValueFactory &BVF,
+                                  const BinaryOperator *BinOp,
+                                  const llvm::APSInt *KnownValue,
+                                  QualType RetTy,
+                                  bool ChildIsLHS) const override {
+    return BinOp->isComparisonOp();
+  }
+
+  virtual bool testConditionAsCheckStatement() const override { return false; }
+};
+
+using FnFilter = std::function<bool(const CallEvent &, CheckerContext &)>;
+
+/// Description of an API function to check.
+struct FnInfo {
+  /// Minimal argument count, used only for variadic functions.
+  /// This is needed because the CallDescription can not handle variadic
+  /// functions with minimal argument count.
+  Optional<unsigned int> MinArgCount;
+
+  /// Error return check kind for the function.
+  ErrorReturnCheckKind *ErrorReturnKind;
+
+  /// Index of argument whose value at call is to be saved in ParmValMap.
+  /// This value is used if the error return check kind needs it.
+  Optional<unsigned int> ParmI;
+
+  /// Some functions have no meaningful error return code if specific arguments
+  /// are passed. This filter is used to omit such cases. The function should
+  /// determine if the actual call is to be excluded from check, return true if
+  /// yes.
+  FnFilter Filter;
+
+  /// Return type of the function (initialized at runtime).
+  mutable QualType RetTy;
+
+  FnInfo(ErrorReturnCheckKind *ErrorReturnKind,
+         Optional<unsigned int> ParmI = {}, FnFilter Filter = {})
+      : ErrorReturnKind(ErrorReturnKind), ParmI(ParmI), Filter(Filter) {
+    ;
+  }
+  FnInfo(unsigned int MinArgCount, ErrorReturnCheckKind *ErrorReturnKind,
+         Optional<unsigned int> ParmI = {}, FnFilter Filter = {})
+      : MinArgCount(MinArgCount), ErrorReturnKind(ErrorReturnKind),
+        ParmI(ParmI), Filter(Filter) {
+    ;
+  }
+};
+
+/// Information about a specific function call that has an error return code to
+/// check. This data is stored in a map and indexed by the SymbolRef that stands
+/// for the result of the function call.
+struct CalledFunctionData {
+  /// Point out the kind of the function that was called.
+  const FnInfo *Info;
+  /// Source range of the calling statement.
+  SourceRange CallLocation;
+
+  CalledFunctionData(const CalledFunctionData &CFD)
+      : Info(CFD.Info), CallLocation(CFD.CallLocation) {}
+  CalledFunctionData(const FnInfo *Info, const SourceRange &CallLocation)
+      : Info{Info}, CallLocation{CallLocation} {}
+
+  CalledFunctionData &operator=(const CalledFunctionData &CFD) {
+    Info = CFD.Info;
+    CallLocation = CFD.CallLocation;
+    return *this;
+  }
+
+  void Profile(llvm::FoldingSetNodeID &ID) const {
+    ID.AddPointer(Info);
+    ID.AddInteger(CallLocation.getBegin().getRawEncoding());
+  }
+
+  bool operator==(const CalledFunctionData &CFD) const {
+    return Info == CFD.Info && CallLocation == CFD.CallLocation;
+  }
+};
+
+class ErrorReturnChecker
+    : public Checker<check::PostCall, check::Location, check::DeadSymbols,
+                     check::PointerEscape> {
+  mutable std::unique_ptr<BuiltinBug> BT_UncheckedUse;
+  mutable std::unique_ptr<BuiltinBug> BT_Unchecked;
+
+  void checkAccess(CheckerContext &C, ProgramStateRef State, const Stmt *LoadS,
+                   SymbolRef CallSym, const CalledFunctionData *CFD) const;
+  ProgramStateRef processEscapedParams(CheckerContext &C, const CallEvent &Call,
+                                       ProgramStateRef State) const;
+  const FnInfo *findFunctionToCheck(CheckerContext &C,
+                                    const CallEvent &Call) const;
+
+public:
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+  void checkLocation(SVal L, bool IsLoad, const Stmt *S,
+                     CheckerContext &C) const;
+  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+  ProgramStateRef checkPointerEscape(ProgramStateRef State,
+                                     const InvalidatedSymbols &Escaped,
+                                     const CallEvent *Call,
+                                     PointerEscapeKind Kind) const;
+
+private:
+  NullErrorReturn CheckForNull;
+  EOFOrNegativeErrorReturn CheckForEOFOrNegative;
+  AnythingErrorReturn CheckForAnything;
+
+  CallDescriptionMap<FnInfo> CheckedFunctions = {
+      // Null
+      {{"aligned_alloc", 2}, FnInfo{&CheckForNull}},
+      {{"bsearch", 5}, FnInfo{&CheckForNull}},
+      {{"bsearch_s", 6}, FnInfo{&CheckForNull}},
+      {{"calloc", 2}, FnInfo{&CheckForNull}},
+      {{"fgets", 3}, FnInfo{&CheckForNull}},
+      {{"fopen", 2}, FnInfo{&CheckForNull}},
+      {{"freopen", 3}, FnInfo{&CheckForNull}},
+      {{"getenv", 1}, FnInfo{&CheckForNull}},
+      {{"getenv_s", 4}, FnInfo{&CheckForNull}},
+      {{"gets_s", 2}, FnInfo{&CheckForNull}},
+      {{"gmtime", 1}, FnInfo{&CheckForNull}},
+      {{"gmtime_s", 2}, FnInfo{&CheckForNull}},
+      {{"localtime", 1}, FnInfo{&CheckForNull}},
+      {{"localtime_s", 2}, FnInfo{&CheckForNull}},
+      {{"malloc", 1}, FnInfo{&CheckForNull}},
+      {{"memchr", 3}, FnInfo{&CheckForNull}},
+      {{"realloc", 2}, FnInfo{&CheckForNull}},
+      {{"setlocale", 2}, FnInfo{&CheckForNull}},
+      {{"strchr", 2}, FnInfo{&CheckForNull}},
+      {{"strpbrk", 2}, FnInfo{&CheckForNull}},
+      {{"strrchr", 2}, FnInfo{&CheckForNull}},
+      {{"strstr", 2}, FnInfo{&CheckForNull}},
+      {{"strtok", 2}, FnInfo{&CheckForNull}},
+      {{"strtok_s", 4}, FnInfo{&CheckForNull}},
+      {{"tmpfile", 0}, FnInfo{&CheckForNull}},
+      {{"tmpnam", 1}, FnInfo{&CheckForNull}},
+      {{"wcschr", 2}, FnInfo{&CheckForNull}},
+      {{"wcspbrk", 2}, FnInfo{&CheckForNull}},
+      {{"wcsrchr", 2}, FnInfo{&CheckForNull}},
+      {{"wcsstr", 2}, FnInfo{&CheckForNull}},
+      {{"wcstok", 3}, FnInfo{&CheckForNull}},
+      {{"wcstok_s", 4}, FnInfo{&CheckForNull}},
+      {{"wmemchr", 3}, FnInfo{&CheckForNull}},
+      // EOFOrNegative
+      {{"fputs", 2}, FnInfo{&CheckForEOFOrNegative}},
+      {{"fputws", 2}, FnInfo{&CheckForEOFOrNegative}},
+      // Anything
+      {{"fscanf"}, FnInfo{2, &CheckForAnything}},
+      {{"fscanf_s"}, FnInfo{2, &CheckForAnything}},
+      {{"fwscanf"}, FnInfo{2, &CheckForAnything}},
+      {{"fwscanf_s"}, FnInfo{2, &CheckForAnything}},
+      {{"scanf"}, FnInfo{1, &CheckForAnything}},
+      {{"scanf_s"}, FnInfo{1, &CheckForAnything}},
+      {{"sscanf"}, FnInfo{2, &CheckForAnything}},
+      {{"sscanf_s"}, FnInfo{3, &CheckForAnything}},
+      {{"swscanf"}, FnInfo{2, &CheckForAnything}},
+      {{"swscanf_s"}, FnInfo{2, &CheckForAnything}},
+      {{"vfscanf", 3}, FnInfo{&CheckForAnything}},
+      {{"vfscanf_s", 3}, FnInfo{&CheckForAnything}},
+      {{"vfwscanf", 3}, FnInfo{&CheckForAnything}},
+      {{"vfwscanf_s", 3}, FnInfo{&CheckForAnything}},
+      {{"vscanf", 2}, FnInfo{&CheckForAnything}},
+      {{"vscanf_s", 2}, FnInfo{&CheckForAnything}},
+      {{"vsscanf", 3}, FnInfo{&CheckForAnything}},
+      {{"vsscanf_s", 3}, FnInfo{&CheckForAnything}},
+      {{"vswscanf", 3}, FnInfo{&CheckForAnything}},
+      {{"vswscanf_s", 3}, FnInfo{&CheckForAnything}},
+      {{"vwscanf", 2}, FnInfo{&CheckForAnything}},
+      {{"vwscanf_s", 2}, FnInfo{&CheckForAnything}},
+      {{"wscanf"}, FnInfo{1, &CheckForAnything}},
+      {{"wscanf_s"}, FnInfo{1, &CheckForAnything}},
+  };
+};
+
+/// Result of the ErrorCheckTestStmtVisitor.
+enum VisitResult {
+  // An "unchecked use" error was found.
+  UncheckedUseFound,
+  // An error check was found.
+  CheckFound,
+  // Assignment (like) condition was found (something that returns the same
+  // value as `Child`), the check should continue on upper level of the
+  // expression (with the current parent as new child).
+  AssignmentFound,
+  // Used for every other case.
+  NoAction
+};
+
+/// Used when a statement that uses (reads) the return value (from a previous
+/// function call to check) was encountered. This visitor determines if the
+/// statement is accepted as an error check in the checked code. The decision is
+/// made based on found code. The result depends on the statement and on the
+/// error return check kind and on additional data obtained from the function
+/// call. The visitor is executed on the parent of the actual value-load
+/// statement, to find out how the value is used. In a simple example "X < 0",
+/// the `Child` denotes the reference to "X" (this is the loading statement),
+/// and the visitor is called on a `Parent` that is the binary operator. See
+/// `ErrorReturnCHecker::checkAccess` for the calling algorithm.
+class ErrorCheckTestStmtVisitor
+    : public ConstStmtVisitor<ErrorCheckTestStmtVisitor, VisitResult> {
+  CheckerContext &C;
+  const Stmt *Child;
+  const CalledFunctionData *CFD;
+  const ParentMap &PM;
+
+  /// Check if the `Child` expression is contained in the parent map under
+  /// `ParentCond`.
+  /// (`ParentCond` is a parent of `Child`.)
+  bool findChildInParent(const Expr *ParentCond) const {
+    for (const Stmt *P = Child; P; P = PM.getParent(P))
+      if (P == ParentCond)
+        return true;
+    return false;
+  };
+
+  /// Determine the "known value" (if a constant) of an Expr.
+  const llvm::APSInt *getKnownConstantVal(const Expr *E) const {
+    Optional<SVal> ConstantVal = C.getSValBuilder().getConstantVal(E);
+    if (ConstantVal)
+      return C.getSValBuilder().getKnownValue(C.getState(), *ConstantVal);
+    return nullptr;
+  }
+
+public:
+  ErrorCheckTestStmtVisitor(CheckerContext &C, const Stmt *Child,
+                            const CalledFunctionData *CFD)
+      : C(C), Child(Child), CFD(CFD),
+        PM(C.getLocationContext()->getParentMap()) {}
+
+  VisitResult VisitBinaryOperator(const BinaryOperator *BO) {
+    Expr *OtherS = nullptr;
+    // Test on which side the child appears and save the other.
+    for (const Stmt *P = Child; P; P = PM.getParent(P)) {
+      if (P == BO->getLHS()) {
+        OtherS = BO->getRHS();
+        break;
+      } else if (P == BO->getRHS()) {
+        OtherS = BO->getLHS();
+        break;
+      }
+    }
+    assert(OtherS && "Statement not found under its parent.");
+
+    BinaryOperatorKind Op = BO->getOpcode();
+    if (Op == BO_Assign) {
+      assert(OtherS == BO->getLHS() && "Loaded value not on assignment RHS.");
+      return AssignmentFound;
+    }
+
+    // Perform the specific check on the binary operator.
+    if (CFD->Info->ErrorReturnKind->testBinOpForCheckStatement(
+            C.getSValBuilder().getBasicValueFactory(), BO,
+            getKnownConstantVal(OtherS), CFD->Info->RetTy,
+            OtherS == BO->getRHS()))
+      return CheckFound;
+    return UncheckedUseFound;
+  }
+
+  VisitResult VisitUnaryOperator(const UnaryOperator *UO) {
+    // For unary operator, only the logical negation is acceptable, if the
+    // error return check kind allows it.
+    // Every other use is reported as error.
+    UnaryOperatorKind Op = UO->getOpcode();
+    if (Op == UO_LNot &&
+        CFD->Info->ErrorReturnKind->testConditionAsCheckStatement())
+      return CheckFound;
+    return UncheckedUseFound;
+  }
+
+  VisitResult VisitConditionalOperator(const ConditionalOperator *CO) {
+    if (!findChildInParent(CO->getCond()))
+      return UncheckedUseFound;
+    if (CFD->Info->ErrorReturnKind->testConditionAsCheckStatement())
+      return CheckFound;
+    return UncheckedUseFound;
+  }
+
+  VisitResult VisitIfStmt(const IfStmt *If) {
+    if (!findChildInParent(If->getCond()))
+      return NoAction;
+    if (CFD->Info->ErrorReturnKind->testConditionAsCheckStatement())
+      return CheckFound;
+    return UncheckedUseFound;
+  }
+
+  VisitResult VisitWhileStmt(const WhileStmt *While) {
+    if (!findChildInParent(While->getCond()))
+      return NoAction;
+    if (CFD->Info->ErrorReturnKind->testConditionAsCheckStatement())
+      return CheckFound;
+    return UncheckedUseFound;
+  }
+
+  VisitResult VisitDoStmt(const DoStmt *Do) {
+    if (!findChildInParent(Do->getCond()))
+      return NoAction;
+    if (CFD->Info->ErrorReturnKind->testConditionAsCheckStatement())
+      return CheckFound;
+    return UncheckedUseFound;
+  }
+
+  VisitResult VisitForStmt(const ForStmt *For) {
+    if (!findChildInParent(For->getCond()))
+      return NoAction;
+    if (CFD->Info->ErrorReturnKind->testConditionAsCheckStatement())
+      return CheckFound;
+    return UncheckedUseFound;
+  }
+
+  VisitResult VisitCallExpr(const CallExpr *CE) {
+    const FunctionDecl *CalledF = C.getCalleeDecl(CE);
+    SourceLocation Loc = CalledF->getLocation();
+    // Check if system function is called with the (unchecked) value.
+    if (Loc.isValid() && C.getSourceManager().isInSystemHeader(Loc))
+      return UncheckedUseFound;
+    // Not a system function, may check the value.
+    return NoAction;
+  }
+
+  VisitResult VisitDeclStmt(const DeclStmt *Decl) {
+    // Value is used at initialization.
+    return NoAction;
+  }
+
+  // Use in every other expression is error.
+  VisitResult VisitExpr(const Stmt *S) { return UncheckedUseFound; }
+
+  // This case is no error, the symbol may be found by garbage collector.
+  VisitResult VisitStmt(const Stmt *S) { return NoAction; }
+};
+
+} // end anonymous namespace
+
+REGISTER_MAP_WITH_PROGRAMSTATE(CalledFunctionDataMap, SymbolRef,
+                               CalledFunctionData)
+
+void ErrorReturnChecker::checkAccess(CheckerContext &C, ProgramStateRef State,
+                                     const Stmt *LoadS, SymbolRef CallSym,
+                                     const CalledFunctionData *CFD) const {
+  const ParentMap &PM = C.getLocationContext()->getParentMap();
+  //llvm::errs()<<"LoadS\n";
+  //LoadS->dumpColor();
+
+  while (LoadS) {
+    const Stmt *ParentS = PM.getParentIgnoreParenCasts(LoadS);
+    //llvm::errs()<<"ParentS\n";
+    //ParentS->dumpColor();
+
+    ErrorCheckTestStmtVisitor FindErrorCheck{C, LoadS, CFD};
+    switch (FindErrorCheck.Visit(ParentS)) {
+    case UncheckedUseFound: {
+      if (!BT_UncheckedUse)
+        BT_UncheckedUse.reset(
+            new BuiltinBug(this, "Use of unchecked return value",
+                          "Use of return value that was not checked for error"));
+
+      SourceRange CallLocation = CFD->CallLocation;
+      State = State->remove<CalledFunctionDataMap>(CallSym);
+      State = State->remove<ParmValMap>(CallSym);
+
+      ExplodedNode *N = C.generateNonFatalErrorNode(State);
+      if (!N) {
+        C.addTransition(State);
+        return;
+      }
+
+      auto Report = std::make_unique<PathSensitiveBugReport>(
+          *BT_UncheckedUse, BT_UncheckedUse->getDescription(), N);
+      // Report->markInteresting(CallSym);
+      Report->addRange(CallLocation);
+      C.emitReport(std::move(Report));
+      
+      //auto Report = std::make_unique<BasicBugReport>(*BT_UncheckedUse, BT_UncheckedUse->getDescription(), PathDiagnosticLocation{LoadS, C.getSourceManager(), C.getLocationContext()});
+      //Report->addRange(CallLocation);
+      //C.emitReport(std::move(Report));
+      
+      return;
+    }
+    case CheckFound:
+      // A correct error check was found, remove from state.
+      State = State->remove<CalledFunctionDataMap>(CallSym);
+      State = State->remove<ParmValMap>(CallSym);
+      C.addTransition(State);
+      return;
+
+    case AssignmentFound:
+      // Value is assigned, still need to find a check later.
+      // Continue checking at upper level (check with result of assignment).
+      LoadS = ParentS;
+      continue;
+
+    case NoAction:
+      // The value is passed to other place out of the current expression.
+      C.addTransition(State);
+      return;
+    };
+  }
+}
+
+ProgramStateRef ErrorReturnChecker::processEscapedParams(
+    CheckerContext &C, const CallEvent &Call, ProgramStateRef State) const {
+  for (unsigned int I = 0, E = Call.getNumArgs(); I < E; ++I) {
+    SVal V = Call.getArgSVal(I);
+    SymbolRef Sym = V.getAsSymbol();
+    if (Sym) {
+      State = State->remove<CalledFunctionDataMap>(Sym);
+      State = State->remove<ParmValMap>(Sym);
+    }
+  }
+  return State;
+}
+
+const FnInfo *
+ErrorReturnChecker::findFunctionToCheck(CheckerContext &C,
+                                        const CallEvent &Call) const {
+  const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
+  const ParentMap &PM = C.getLocationContext()->getParentMap();
+
+  if (!FD || FD->getKind() != Decl::Function)
+    return nullptr;
+
+  if (!Call.isGlobalCFunction() || !Call.isInSystemHeader())
+    return nullptr;
+
+  const FnInfo *Fn = CheckedFunctions.lookup(Call);
+  if (!Fn)
+    return nullptr;
+
+  // For variadic functions check for minimal argument count.
+  if (Fn->MinArgCount && Call.getNumArgs() < *(Fn->MinArgCount))
+    return nullptr;
+
+  const Stmt *S = PM.getParent(Call.getOriginExpr());
+
+  // Check for explicit cast to void.
+  if (auto *Cast = dyn_cast<const CStyleCastExpr>(S)) {
+    if (Cast->getTypeAsWritten().getTypePtr()->isVoidType())
+      return nullptr;
+  }
+
+  // Use a function-specific rule to omit specific calls.
+  if (Fn->Filter && Fn->Filter(Call, C))
+    return nullptr;
+
+  // The call should have a symbolic return value to analyze it.
+  SVal RetSV = Call.getReturnValue();
+  if (RetSV.isUnknownOrUndef())
+    return nullptr;
+  SymbolRef RetSym = RetSV.getAsSymbol();
+  if (!RetSym)
+    return nullptr;
+
+  // Lazy-init the return type when the function is found.
+  if (Fn->RetTy.isNull())
+    Fn->RetTy = FD->getReturnType();
+
+  return Fn;
+}
+
+void ErrorReturnChecker::checkPostCall(const CallEvent &Call,
+                                       CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+
+  State = processEscapedParams(C, Call, State);
+
+  const FnInfo *Fn = findFunctionToCheck(C, Call);
+  if (!Fn) {
+    C.addTransition(State);
+    return;
+  }
+
+  SymbolRef RetSym = Call.getReturnValue().getAsSymbol();
+
+  CalledFunctionData CFD{Fn, Call.getSourceRange()};
+  State = State->set<CalledFunctionDataMap>(RetSym, CFD);
+
+  // Try to compute value of specific argument at the time of call (if needed).
+  if (Fn->ParmI) {
+    const llvm::APSInt *ParmVal =
+        C.getSValBuilder().getKnownValue(State, Call.getArgSVal(*Fn->ParmI));
+    if (ParmVal)
+      State = State->set<ParmValMap>(RetSym, *ParmVal);
+  }
+
+  checkAccess(C, State, Call.getOriginExpr(), RetSym, &CFD);
+}
+
+void ErrorReturnChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S,
+                                       CheckerContext &C) const {
+  if (!IsLoad)
+    return;
+  if (L.isUnknownOrUndef())
+    return;
+
+  auto Location = L.castAs<DefinedOrUnknownSVal>().getAs<Loc>();
+  if (!Location)
+    return;
+
+  ProgramStateRef State = C.getState();
+  SymbolRef Sym = State->getSVal(*Location).getAsSymbol();
+  if (!Sym)
+    return;
+
+  const CalledFunctionData *CFD = State->get<CalledFunctionDataMap>(Sym);
+  if (!CFD)
+    return;
+
+  checkAccess(C, State, S, Sym, CFD);
+}
+
+void ErrorReturnChecker::checkDeadSymbols(SymbolReaper &SymReaper,
+                                          CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  const CalledFunctionDataMapTy &Map = State->get<CalledFunctionDataMap>();
+  llvm::SmallMapVector<SymbolRef, CalledFunctionData, 8> Errors;
+  for (const auto &I : Map)
+    if (SymReaper.isDead(I.first))
+      Errors.insert(I);
+
+  if (Errors.empty())
+    return;
+
+  if (!BT_Unchecked)
+    BT_Unchecked.reset(
+        new BuiltinBug(this, "Unchecked return value",
+                       "Return value was not checked for error"));
+
+  ExplodedNode *N = C.generateNonFatalErrorNode(State);
+
+  for (const auto &E : Errors) {
+    auto Report = std::make_unique<PathSensitiveBugReport>(
+        *BT_Unchecked, BT_Unchecked->getDescription(), N);
+    Report->addRange(E.second.CallLocation);
+    Report->markInteresting(E.first);
+    C.emitReport(std::move(Report));
+    //auto Report = std::make_unique<BasicBugReport>(*BT_Unchecked, BT_Unchecked->getDescription(), PathDiagnosticLocation{E.second.CallLocation.getBegin(), C.getSourceManager()});
+    //Report->addRange(E.second.CallLocation);
+    //C.emitReport(std::move(Report));
+
+    State = State->remove<CalledFunctionDataMap>(E.first);
+    State = State->remove<ParmValMap>(E.first);
+  }
+
+  C.addTransition(State, N);
+}
+
+ProgramStateRef ErrorReturnChecker::checkPointerEscape(
+    ProgramStateRef State, const InvalidatedSymbols &Escaped,
+    const CallEvent *Call, PointerEscapeKind Kind) const {
+  for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
+                                          E = Escaped.end();
+       I != E; ++I) {
+    SymbolRef Sym = *I;
+    State = State->remove<CalledFunctionDataMap>(Sym);
+    State = State->remove<ParmValMap>(Sym);
+  }
+  return State;
+}
+
+void ento::registerErrorReturnChecker(CheckerManager &mgr) {
+  mgr.registerChecker<ErrorReturnChecker>();
+}
+
+bool ento::shouldRegisterErrorReturnChecker(const LangOptions &LO) {
+  return true;
+}
Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -37,6 +37,7 @@
   DynamicTypePropagation.cpp
   DynamicTypeChecker.cpp
   EnumCastOutOfRangeChecker.cpp
+  ErrorReturnChecker.cpp
   ExprInspectionChecker.cpp
   FixedAddressChecker.cpp
   FuchsiaHandleChecker.cpp
Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -429,6 +429,10 @@
   HelpText<"Check improper use of chroot">,
   Documentation<HasAlphaDocumentation>;
 
+def ErrorReturnChecker : Checker<"ErrorReturn">,
+  HelpText<"Check for unchecked error return values">,
+  Documentation<HasAlphaDocumentation>;
+
 def PthreadLockChecker : Checker<"PthreadLock">,
   HelpText<"Simple lock -> unlock checker">,
   Documentation<HasAlphaDocumentation>;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to