balazske updated this revision to Diff 237061.
balazske added a comment.
- More fixes in test files.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D71510/new/
https://reviews.llvm.org/D71510
Files:
clang/docs/analyzer/checkers.rst
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,625 @@
+// 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:
+
+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()
+*/
+
+void CheckError(errno_t e) {
+ if (e == 0) {
+ }
+}
+
+void test_ZeroCorrectCheck1() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL);
+ if (err == 0) {
+ }
+}
+
+void test_ZeroCorrectCheck2() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL);
+ if (err != 0) {
+ }
+}
+
+void test_ZeroCorrectCheck3() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL);
+ if (err > 0) {
+ } else if (err < 0) {
+ }
+}
+
+void test_ZeroCorrectCheck4() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL);
+ CheckError(err);
+}
+
+void test_ZeroCorrectCheck5() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL);
+ if (err + 1 == 1) {
+ }
+}
+
+void test_ZeroCorrectCheck6() {
+ char buf[1];
+ // FIXME: This is a false positive.
+ errno_t err = asctime_s(buf, 1, NULL); // expected-warning{{Missing or incomplete error check}}
+ if (err) {
+ }
+}
+
+void test_ZeroCorrectCheck7() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL);
+ if (!err) {
+ }
+}
+
+void test_ZeroCorrectCheck8() {
+ char buf[1];
+ if (asctime_s(buf, 1, NULL) == 0) {
+ }
+}
+
+void test_ZeroBadCheck1() {
+ char buf[1];
+ asctime_s(buf, 9, NULL); // expected-warning{{Missing or incomplete error check}}
+}
+
+void test_ZeroBadCheck2() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL); // expected-warning{{Missing or incomplete error check}}
+ if (err == 1) {
+ }
+}
+
+void test_ZeroBadCheck3() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL); // expected-warning{{Missing or incomplete error check}}
+ if (err < 0) {
+ }
+}
+
+void test_ZeroBadCheck4() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL); // expected-warning{{Missing or incomplete error check}}
+ if (err < 0 || err > 2) {
+ }
+}
+
+void test_ZeroBadCheck5() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL); // expected-warning{{Missing or incomplete error check}}
+ if (err > -2) {
+ }
+}
+
+void test_ZeroBadCheck6() {
+ char buf[1];
+ errno_t err = asctime_s(buf, 1, NULL); // expected-warning{{Missing or incomplete error check}}
+ err = asctime_s(buf, 1, NULL);
+ if (err == 0) {
+ }
+}
+
+void test_NullCorrectCheck1() {
+ // FIXME: This is a false positive.
+ void *P = aligned_alloc(4, 8); // expected-warning{{Missing or incomplete error check}}
+ if (P) {
+ }
+}
+
+void test_NullCorrectCheck2() {
+ void *P = aligned_alloc(4, 8);
+ if (!P) {
+ }
+}
+
+void test_NullCorrectCheck3() {
+ void *P = aligned_alloc(4, 8);
+ if (P == NULL) {
+ }
+}
+
+void test_NullCorrectCheck4() {
+ if (aligned_alloc(4, 8) == NULL) {
+ }
+}
+
+int test_NullCorrectCheck5() {
+ // No warning for this case.
+ return aligned_alloc(4, 8) == NULL;
+}
+
+void test_NullBadCheck1() {
+ void *P = aligned_alloc(4, 8); // expected-warning{{Missing or incomplete error check}}
+}
+
+void test_WEofCorrectCheck1() {
+ wint_t X = btowc(3);
+ if (X == WEOF) {
+ }
+}
+
+void test_WEofBadCheck1() {
+ btowc(3); // expected-warning{{Missing or incomplete error check}}
+}
+
+void test_ZeroOrEofCorrectCheck1() {
+ int X = fclose(NULL);
+ if (X == -1) {
+ }
+}
+
+void test_ZeroOrEofCorrectCheck2() {
+ int X = fclose(NULL);
+ if (X < 0) {
+ }
+}
+
+void test_ZeroOrEofCorrectCheck3() {
+ int X = fclose(NULL);
+ if (X == 0) {
+ }
+}
+
+void test_ZeroOrEofBadCheck1() {
+ int X = fclose(NULL); // expected-warning{{Missing or incomplete error check}}
+ if (X == -2) {
+ }
+}
+
+void test_ZeroOrEofBadCheck2() {
+ int X = fclose(NULL); // expected-warning{{Missing or incomplete error check}}
+ if (X < 1) {
+ }
+}
+
+void test_ZeroOrEofBadCheck3() {
+ int X = fclose(NULL); // expected-warning{{Missing or incomplete error check}}
+ if (X > 0) {
+ }
+}
+
+void test_NegativeOrEofCorrectCheck1() {
+ int X = fputs("", NULL);
+ if (X == -1) {
+ }
+}
+
+void test_NegativeOrEofCorrectCheck2() {
+ int X = fputs("", NULL);
+ if (X < 0) {
+ }
+}
+
+void test_NegativeOrEofCorrectCheck3() {
+ int X = fputs("", NULL);
+ if (X >= 0) {
+ }
+}
+
+void test_NegativeOrEofBadCheck1() {
+ int X = fputs("", NULL); // expected-warning{{Missing or incomplete error check}}
+ if (X == -2) {
+ }
+}
+
+void test_NegativeOrEofBadCheck2() {
+ int X = fputs("", NULL); // expected-warning{{Missing or incomplete error check}}
+ if (X == 0) {
+ }
+}
+
+void test_NegativeOrEofBadCheck3() {
+ int X = fputs("", NULL); // expected-warning{{Missing or incomplete error check}}
+ if (X < -1) {
+ }
+}
+
+void test_LessThanParmValCorrectCheck1() {
+ int X = fread(NULL, 1, 2, NULL);
+ if (X == 2) {
+ }
+}
+
+void test_LessThanParmValCorrectCheck2() {
+ int X = fread(NULL, 1, 2, NULL);
+ if (X < 2) {
+ }
+}
+
+void test_LessThanParmValCorrectCheck3() {
+ int X = fread(NULL, 1, 2, NULL);
+ if (X >= 2) {
+ } else {
+ }
+}
+
+void test_LessThanParmValBadCheck1() {
+ int X = fread(NULL, 1, 2, NULL); // expected-warning{{Missing or incomplete error check}}
+ if (X == 1) {
+ }
+}
+
+void test_LessThanParmValBadCheck2() {
+ int X = fread(NULL, 1, 2, NULL); // expected-warning{{Missing or incomplete error check}}
+ if (X < 3) {
+ }
+}
+
+void test_FilterEofCorrectCheck1() {
+ int X = mblen("xxx", 1);
+ if (X == -1) {
+ }
+}
+
+void test_FilterEofCorrectCheck2() {
+ int X = mblen(NULL, 1);
+ if (X == 0) {
+ }
+}
+
+void test_FilterEofCorrectCheck3() {
+ mblen(NULL, 1);
+}
+
+void test_FilterEofBadCheck1() {
+ mblen("xxx", 1); // expected-warning{{Missing or incomplete error check}}
+}
+
+void test_FilterEofBadCheck2() {
+ int X = mblen("xxx", 1); // expected-warning{{Missing or incomplete error check}}
+ if (X == 0) {
+ }
+}
+
+void test_MinMaxCorrectCheck1() {
+ intmax_t X = strtoimax("", NULL, 10);
+ if (X == INTMAX_MIN) {
+ } else if (X == INTMAX_MAX) {
+ }
+}
+
+void test_MinMaxCorrectCheck2() {
+ intmax_t X = strtoimax("", NULL, 10);
+ if (X > INTMAX_MIN && X < INTMAX_MAX) {
+ }
+}
+
+void test_MinMaxCorrectCheck3() {
+ intmax_t X = strtoimax("", NULL, 10);
+ if (X > INTMAX_MIN) {
+ }
+ if (X < INTMAX_MAX) {
+ }
+}
+
+void test_MinMaxCorrectCheck4() {
+ // FIXME: This case is like false positive.
+ intmax_t X = strtoimax("", NULL, 10); // expected-warning{{Missing or incomplete error check}}
+ if (X < 0 || X > 10) {
+ }
+}
+
+void test_MinMaxBadCheck1() {
+ intmax_t X = strtoimax("", NULL, 10); // expected-warning{{Missing or incomplete error check}}
+ if (X == INTMAX_MIN) {
+ }
+}
+
+void test_MinMaxBadCheck2() {
+ intmax_t X = strtoimax("", NULL, 10); // expected-warning{{Missing or incomplete error check}}
+ if (X > 0) {
+ }
+}
+
+void test_MaxCorrectCheck1() {
+ unsigned long X = strtoul("", NULL, 10);
+ if (X == ULONG_MAX) {
+ }
+}
+
+void test_MaxBadCheck1() {
+ unsigned long X = strtoul("", NULL, 10); // expected-warning{{Missing or incomplete error check}}
+ if (X == 0) {
+ }
+}
+
+void test_Variadic() {
+ fprintf(NULL, "xxx"); // expected-warning{{Missing or incomplete error check}}
+ fprintf(NULL, "%i", 0); // expected-warning{{Missing or incomplete error check}}
+ fprintf_s(1);
+}
+
+void test_VoidCast() {
+ (void)fprintf(NULL, "xxx");
+}
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,26 @@
#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.
+
+typedef __typeof(errno) errno_t;
+typedef size_t rsize_t;
+typedef unsigned int wint_t;
+#define WEOF (0xffffffffu)
+typedef long long intmax_t;
+#define INTMAX_MAX 9223372036854775807ll
+#define INTMAX_MIN (-INTMAX_MAX - 1)
+#define ULONG_MAX 18446744073709551615ul
+
+void *aligned_alloc(size_t alignment, size_t size);
+errno_t asctime_s(char *buf, rsize_t bufsz, const struct tm *time_ptr);
+wint_t btowc(int c);
+int fputs(const char *restrict str, FILE *restrict stream);
+size_t fread(void *restrict buffer, size_t size, size_t count, FILE *restrict stream);
+int mblen(const char *s, size_t n);
+intmax_t strtoimax(const char *restrict nptr, char **restrict endptr, int base);
+unsigned long strtoul(const char *restrict str, char **restrict str_end, int base);
+// A fake function that should not be recognized as the normal fprintf_s system function.
+extern int fprintf_s(int);
Index: clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp
===================================================================
--- /dev/null
+++ clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp
@@ -0,0 +1,694 @@
+//===-- 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 corresponds to SEI CERT ERR33-C.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Expr.h"
+#include "clang/AST/ParentMap.h"
+#include "clang/AST/Stmt.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;
+
+REGISTER_MAP_WITH_PROGRAMSTATE(ParmValMap, SymbolRef, llvm::APSInt)
+
+namespace {
+
+class CheckForErrorResultChecker {
+public:
+ // The idea for this check is that the constraints (generated by branch
+ // conditions) on the return value of a function call to check point out what
+ // checks are done in the code. At each branch condition test the constraints
+ // on the function return value by making assumptions that it satisfies the
+ // error condition. If at one point the return value is sure to satisfy or
+ // non-satisfy the error condition we assume that there was a branch condition
+ // that makes these constraints so the error check is there. (The code seems
+ // to work even if the function is modeled by some other checker.)
+ //
+ // 'Sym' is the symbol for the function call to check (key in GDM).
+ // 'Value' is the current value of the symbol.
+ // 'RetTy' is the return type of the function (obtained from the AST).
+ // Return true if correct error checking was found.
+ virtual bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const = 0;
+};
+
+// Error return is a fixed value (default zero).
+class ValueErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ ValueErrorResultChecker(uint64_t ErrorResultValue = 0)
+ : ErrorResultValue(ErrorResultValue) {
+ ;
+ }
+
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ SValBuilder &SVB = C.getSValBuilder();
+ // Test if the return value equals a fixed error code.
+ DefinedOrUnknownSVal Eval =
+ SVB.evalEQ(State, Value, SVB.makeIntVal(ErrorResultValue, RetTy));
+ auto Assumed = State->assume(Eval);
+ // The error check is correct if we know that the error or the non-error
+ // condition exactly is satisfied.
+ return ((Assumed.first && !Assumed.second) ||
+ (!Assumed.first && Assumed.second));
+ }
+
+private:
+ uint64_t ErrorResultValue;
+};
+
+// Error return is a NULL value.
+// This should work with any type of null value.
+class NullErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ ConditionTruthVal Nullness = State->isNull(Value);
+ return Nullness.isConstrained();
+ }
+};
+
+// Error return is any negative value.
+// This should check for the "return value < 0" type of code.
+class NegativeErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ SValBuilder &SVB = C.getSValBuilder();
+ // Test if the return value is negative.
+ SVal Eval = SVB.evalBinOp(State, clang::BO_LT, Value,
+ SVB.makeZeroVal(RetTy), SVB.getConditionType());
+ auto DefEval = Eval.getAs<DefinedOrUnknownSVal>();
+ if (DefEval) {
+ auto Assumed = State->assume(*DefEval);
+ // The error check is correct if we know that the error or the non-error
+ // condition exactly is satisfied.
+ return ((Assumed.first && !Assumed.second) ||
+ (!Assumed.first && Assumed.second));
+ }
+ return true; // Assume that the check is correct if we could not evaluate
+ // (to avoid false positives).
+ }
+};
+
+// Error return is any positive (or non-positive) value.
+// This can be used when zero or negative return value indicates some kind of
+// error.
+class PositiveErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ SValBuilder &SVB = C.getSValBuilder();
+ // Test if the return value is negative.
+ SVal Eval = SVB.evalBinOp(State, clang::BO_GT, Value,
+ SVB.makeZeroVal(RetTy), SVB.getConditionType());
+ auto DefEval = Eval.getAs<DefinedOrUnknownSVal>();
+ if (DefEval) {
+ auto Assumed = State->assume(*DefEval);
+ // The error check is correct if we know that the error or the non-error
+ // condition exactly is satisfied.
+ return ((Assumed.first && !Assumed.second) ||
+ (!Assumed.first && Assumed.second));
+ }
+ return true;
+ }
+};
+
+// 0 is success, -1 (EOF) is error, no other values possible.
+class ZeroOrEofErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ SValBuilder &SVB = C.getSValBuilder();
+ DefinedOrUnknownSVal Eval1 =
+ SVB.evalEQ(State, Value, SVB.makeIntVal(0, RetTy));
+ DefinedOrUnknownSVal Eval2 =
+ SVB.evalEQ(State, Value, SVB.makeIntVal(-1, RetTy));
+ auto Assumed1 = State->assume(Eval1);
+ auto Assumed2 = State->assume(Eval2);
+ return ((Assumed1.first && !Assumed1.second) ||
+ (!Assumed1.first && Assumed1.second)) ||
+ ((Assumed2.first && !Assumed2.second) ||
+ (!Assumed2.first && Assumed2.second));
+ }
+};
+
+// Only EOF is the possible negative return value, and it is OK to have "return
+// value < 0" check too.
+class NegativeOrEofErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ SValBuilder &SVB = C.getSValBuilder();
+ SVal EvalNegative =
+ SVB.evalBinOp(State, clang::BO_LT, Value, SVB.makeZeroVal(RetTy),
+ SVB.getConditionType());
+ auto DefEvalNegative = EvalNegative.getAs<DefinedOrUnknownSVal>();
+ if (!DefEvalNegative)
+ return true;
+ DefinedOrUnknownSVal EvalEof =
+ SVB.evalEQ(State, Value, SVB.makeIntVal(-1, RetTy));
+ auto AssumedNegative = State->assume(*DefEvalNegative);
+ auto AssumedEof = State->assume(EvalEof);
+ return ((AssumedNegative.first && !AssumedNegative.second) ||
+ (!AssumedNegative.first && AssumedNegative.second)) ||
+ ((AssumedEof.first && !AssumedEof.second) ||
+ (!AssumedEof.first && AssumedEof.second));
+ }
+};
+
+// Error value is minimal or maximal value of the return type.
+class MinMaxErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ SValBuilder &SVB = C.getSValBuilder();
+ BasicValueFactory &BVF = SVB.getBasicValueFactory();
+ DefinedOrUnknownSVal EvalMin =
+ SVB.evalEQ(State, Value, SVB.makeIntVal(BVF.getMinValue(RetTy)));
+ DefinedOrUnknownSVal EvalMax =
+ SVB.evalEQ(State, Value, SVB.makeIntVal(BVF.getMaxValue(RetTy)));
+ auto AssumedMin = State->assume(EvalMin);
+ auto AssumedMax = State->assume(EvalMax);
+ return ((AssumedMin.first && !AssumedMin.second) ||
+ (!AssumedMin.first && AssumedMin.second)) &&
+ ((AssumedMax.first && !AssumedMax.second) ||
+ (!AssumedMax.first && AssumedMax.second));
+ }
+};
+
+// Error value is maximal value of the return type.
+class MaxErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ SValBuilder &SVB = C.getSValBuilder();
+ BasicValueFactory &BVF = SVB.getBasicValueFactory();
+ DefinedOrUnknownSVal EvalMax =
+ SVB.evalEQ(State, Value, SVB.makeIntVal(BVF.getMaxValue(RetTy)));
+ auto AssumedMax = State->assume(EvalMax);
+ return ((AssumedMax.first && !AssumedMax.second) ||
+ (!AssumedMax.first && AssumedMax.second));
+ }
+};
+
+// Error return value is something less than the value of an argument passed to
+// the function. Typical case: The 'fread' function.
+class LessThanParmValErrorResultChecker : public CheckForErrorResultChecker {
+public:
+ bool checkBranchCondition(CheckerContext &C, ProgramStateRef State,
+ SymbolRef Sym, DefinedOrUnknownSVal Value,
+ const QualType &RetTy) const override {
+ const llvm::APSInt *ParmVal = State->get<ParmValMap>(Sym);
+ if (!ParmVal)
+ return true;
+ SValBuilder &SVB = C.getSValBuilder();
+ DefinedOrUnknownSVal Eval1 =
+ SVB.evalEQ(State, Value, SVB.makeIntVal(*ParmVal));
+ auto Assumed1 = State->assume(Eval1);
+ SVal Eval2 = SVB.evalBinOp(State, BO_LT, Value, SVB.makeIntVal(*ParmVal),
+ SVB.getConditionType());
+ auto DefEval2 = Eval2.getAs<DefinedOrUnknownSVal>();
+ if (DefEval2) {
+ auto Assumed2 = State->assume(*DefEval2);
+ return ((Assumed1.first && !Assumed1.second) ||
+ (!Assumed1.first && Assumed1.second)) ||
+ ((Assumed2.first && !Assumed2.second) ||
+ (!Assumed2.first && Assumed2.second));
+ } else {
+ return ((Assumed1.first && !Assumed1.second) ||
+ (!Assumed1.first && Assumed1.second));
+ }
+ }
+};
+
+using FnFilter = std::function<bool(const CallEvent &, CheckerContext &)>;
+
+struct FnInfo {
+ Optional<unsigned int> MinArgCount;
+ CheckForErrorResultChecker *Checker;
+ Optional<unsigned int> ParmI;
+ FnFilter Filter;
+ mutable QualType RetTy;
+
+ FnInfo(CheckForErrorResultChecker *Checker, Optional<unsigned int> ParmI = {},
+ FnFilter Filter = {})
+ : Checker(Checker), ParmI(ParmI), Filter(Filter) {
+ ;
+ }
+ FnInfo(unsigned int MinArgCount, CheckForErrorResultChecker *Checker,
+ Optional<unsigned int> ParmI = {}, FnFilter Filter = {})
+ : MinArgCount(MinArgCount), Checker(Checker), ParmI(ParmI),
+ Filter(Filter) {
+ ;
+ }
+};
+
+bool filterNullArg(const CallEvent &Call, CheckerContext &C,
+ unsigned int ArgI) {
+ ConditionTruthVal IsNull = C.getState()->isNull(Call.getArgSVal(ArgI));
+ return IsNull.isConstrainedTrue();
+}
+
+class ErrorReturnChecker
+ : public Checker<check::PostCall, check::BranchCondition,
+ check::DeadSymbols> {
+ mutable std::unique_ptr<BuiltinBug> BT_Unchecked;
+
+public:
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkBranchCondition(const Stmt *S, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+
+private:
+ void emitUnchecked(CheckerContext &C, ExplodedNode *N, SymbolRef Sym) const;
+
+ // These checkers are symmetric for the error condition.
+ // (The 'zero error' works with check for zero or nonzero error return value.)
+ ValueErrorResultChecker CheckZeroErrorResult;
+ NullErrorResultChecker CheckNullErrorResult;
+ // Assume that any kind of EOF is the representation of a -1 value.
+ ValueErrorResultChecker CheckEofErrorResult{-1ul};
+ NegativeErrorResultChecker CheckNegativeErrorResult;
+ PositiveErrorResultChecker CheckPositiveErrorResult;
+ ZeroOrEofErrorResultChecker CheckZeroOrEofErrorResult;
+ NegativeOrEofErrorResultChecker CheckNegativeOrEofErrorResult;
+ MinMaxErrorResultChecker CheckMinMaxErrorResult;
+ MaxErrorResultChecker CheckMaxErrorResult;
+ LessThanParmValErrorResultChecker CheckLessThanParmValErrorResult;
+
+ CallDescriptionMap<FnInfo> CheckedFunctions = {
+ {{"aligned_alloc", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"asctime_s", 3}, FnInfo{&CheckZeroErrorResult}},
+ {{"at_quick_exit", 1}, FnInfo{&CheckZeroErrorResult}},
+ {{"atexit", 1}, FnInfo{&CheckZeroErrorResult}},
+ {{"bsearch", 5}, FnInfo{&CheckNullErrorResult}},
+ {{"bsearch_s", 6}, FnInfo{&CheckNullErrorResult}},
+ {{"btowc", 1}, FnInfo{&CheckEofErrorResult}},
+ {{"c16rtomb", 3}, FnInfo{&CheckEofErrorResult}},
+ {{"c32rtomb", 3}, FnInfo{&CheckEofErrorResult}},
+ {{"calloc", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"clock", 0}, FnInfo{&CheckEofErrorResult}},
+
+ // FIXME: Knowledge of enum values is needed for these.
+ //{{"cnd_broadcast", 1}, FnInfo{&CheckZeroErrorResult}},
+ //{{"cnd_init", 1}, FnInfo{&CheckZeroErrorResult}},
+ //{{"cnd_signal", 1}, FnInfo{&CheckZeroErrorResult}},
+ //{{"cnd_timedwait", 3}, FnInfo{&CheckZeroErrorResult}},
+ //{{"cnd_wait", 2}, FnInfo{&CheckZeroErrorResult}},
+
+ {{"ctime_s", 3}, FnInfo{&CheckZeroErrorResult}},
+ {{"fclose", 1}, FnInfo{&CheckZeroOrEofErrorResult}},
+ {{"fflush", 1}, FnInfo{&CheckZeroOrEofErrorResult}},
+ {{"fgetc", 1}, FnInfo{&CheckEofErrorResult}},
+ {{"fgetpos", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"fgets", 3}, FnInfo{&CheckNullErrorResult}},
+ {{"fgetwc", 1}, FnInfo{&CheckEofErrorResult}},
+ {{"fopen", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"fopen_s", 3}, FnInfo{&CheckZeroErrorResult}},
+ {{"fprintf"}, FnInfo{2, &CheckNegativeErrorResult}},
+ {{"fprintf_s"}, FnInfo{2, &CheckNegativeErrorResult}},
+ {{"fputc", 2}, FnInfo{&CheckEofErrorResult}},
+ {{"fputs", 2}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"fputwc", 2}, FnInfo{&CheckEofErrorResult}},
+ {{"fputws", 2}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"fread", 4}, FnInfo{&CheckLessThanParmValErrorResult, 2}},
+ {{"freopen", 3}, FnInfo{&CheckNullErrorResult}},
+ {{"freopen_s", 4}, FnInfo{&CheckZeroErrorResult}},
+ {{"fscanf"}, FnInfo{2, &CheckNegativeOrEofErrorResult}},
+ {{"fscanf_s"}, FnInfo{2, &CheckNegativeOrEofErrorResult}},
+ {{"fseek", 3}, FnInfo{&CheckZeroErrorResult}},
+ {{"fsetpos", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"ftell", 1}, FnInfo{&CheckEofErrorResult}},
+ {{"fwprintf"}, FnInfo{2, &CheckNegativeErrorResult}},
+ {{"fwprintf_s"}, FnInfo{2, &CheckNegativeErrorResult}},
+ {{"fwrite", 4}, FnInfo{&CheckLessThanParmValErrorResult, 2}},
+ {{"fwscanf"}, FnInfo{2, &CheckNegativeOrEofErrorResult}},
+ {{"fwscanf_s"}, FnInfo{2, &CheckNegativeOrEofErrorResult}},
+ {{"getc", 1}, FnInfo{&CheckEofErrorResult}},
+ {{"getchar", 0}, FnInfo{&CheckEofErrorResult}},
+ {{"getenv", 1}, FnInfo{&CheckNullErrorResult}},
+ {{"getenv_s", 4}, FnInfo{&CheckNullErrorResult}},
+ {{"gets_s", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"getwc", 1}, FnInfo{&CheckEofErrorResult}},
+ {{"getwchar", 0}, FnInfo{&CheckEofErrorResult}},
+ {{"gmtime", 1}, FnInfo{&CheckNullErrorResult}},
+ {{"gmtime_s", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"localtime", 1}, FnInfo{&CheckNullErrorResult}},
+ {{"localtime_s", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"malloc", 1}, FnInfo{&CheckNullErrorResult}},
+ {{"mblen", 2},
+ FnInfo{&CheckEofErrorResult, {}, std::bind(&filterNullArg, _1, _2, 0)}},
+ {{"mbrlen", 3},
+ FnInfo{&CheckEofErrorResult, {}, std::bind(&filterNullArg, _1, _2, 0)}},
+ {{"mbrtoc16", 4}, FnInfo{&CheckEofErrorResult}},
+ {{"mbrtoc32", 4}, FnInfo{&CheckEofErrorResult}},
+ {{"mbrtowc", 4},
+ FnInfo{&CheckEofErrorResult, {}, std::bind(&filterNullArg, _1, _2, 1)}},
+ {{"mbsrtowcs", 4}, FnInfo{&CheckEofErrorResult}},
+ {{"mbsrtowcs_s", 6}, FnInfo{&CheckZeroErrorResult}},
+ {{"mbstowcs", 3}, FnInfo{&CheckEofErrorResult}},
+ {{"mbstowcs_s", 5}, FnInfo{&CheckZeroErrorResult}},
+ {{"mbtowc", 3},
+ FnInfo{&CheckZeroErrorResult, {}, std::bind(&filterNullArg, _1, _2, 1)}},
+ {{"memchr", 3}, FnInfo{&CheckNullErrorResult}},
+ {{"mktime", 1}, FnInfo{&CheckEofErrorResult}},
+
+ // FIXME: Knowledge of enum values is needed for these.
+ //{{"mtx_init", 2}, FnInfo{&CheckZeroErrorResult}},
+ //{{"mtx_lock", 1}, FnInfo{&CheckZeroErrorResult}},
+ //{{"mtx_timedlock", 2}, FnInfo{&CheckZeroErrorResult}},
+ //{{"mtx_trylock", 1}, FnInfo{&CheckZeroErrorResult}},
+ //{{"mtx_unlock", 1}, FnInfo{&CheckZeroErrorResult}},
+
+ {{"printf_s"}, FnInfo{1, &CheckNegativeErrorResult}},
+ {{"putc", 2}, FnInfo{&CheckEofErrorResult}},
+ {{"putwc", 2}, FnInfo{&CheckEofErrorResult}},
+ {{"raise", 1}, FnInfo{&CheckZeroErrorResult}},
+ {{"realloc", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"remove", 1}, FnInfo{&CheckZeroErrorResult}},
+ {{"rename", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"setlocale", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"setvbuf", 4}, FnInfo{&CheckZeroErrorResult}},
+ {{"scanf"}, FnInfo{1, &CheckNegativeOrEofErrorResult}},
+ {{"scanf_s"}, FnInfo{1, &CheckNegativeOrEofErrorResult}},
+ // FIXME: Should find value of SIG_ERR macro.
+ //{{"signal", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"snprintf"}, FnInfo{3, &CheckNegativeErrorResult}},
+ {{"snprintf_s"}, FnInfo{3, &CheckNegativeErrorResult}},
+ {{"snwprintf_s"}, FnInfo{3, &CheckNegativeErrorResult}},
+ {{"sprintf"}, FnInfo{2, &CheckNegativeErrorResult}},
+ {{"sprintf_s"}, FnInfo{3, &CheckNegativeErrorResult}},
+ {{"sscanf"}, FnInfo{2, &CheckNegativeOrEofErrorResult}},
+ {{"sscanf_s"}, FnInfo{3, &CheckNegativeOrEofErrorResult}},
+ {{"strchr", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"strerror_s", 3}, FnInfo{&CheckZeroErrorResult}},
+ {{"strftime", 4}, FnInfo{&CheckZeroErrorResult}},
+ {{"strpbrk", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"strrchr", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"strstr", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"strtod", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"strtof", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"strtoimax", 3}, FnInfo{&CheckMinMaxErrorResult}},
+ {{"strtok", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"strtok_s", 4}, FnInfo{&CheckNullErrorResult}},
+ {{"strtol", 3}, FnInfo{&CheckMinMaxErrorResult}},
+ // FIXME: Floating-point is not doable and need to know HUGE_VAL.
+ //{{"strtold", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"strtoll", 3}, FnInfo{&CheckMinMaxErrorResult}},
+ {{"strtoumax", 3}, FnInfo{&CheckMinMaxErrorResult}},
+ {{"strtoul", 3}, FnInfo{&CheckMaxErrorResult}},
+ {{"strtoull", 3}, FnInfo{&CheckMaxErrorResult}},
+ // FIXME: strxfrm can not fail according to cppreference.com?
+ //{{"strxfrm", 3}, FnInfo{&CheckZeroErrorResult}},
+ {{"swprintf"}, FnInfo{3, &CheckNegativeErrorResult}},
+ {{"swprintf_s"}, FnInfo{3, &CheckNegativeErrorResult}},
+ {{"swscanf"}, FnInfo{2, &CheckNegativeOrEofErrorResult}},
+ {{"swscanf_s"}, FnInfo{2, &CheckNegativeOrEofErrorResult}},
+
+ // FIXME: Knowledge of enum values is needed for these.
+ //{{"thrd_create", 3}, FnInfo{&CheckZeroErrorResult}},
+ //{{"thrd_detach", 1}, FnInfo{&CheckZeroErrorResult}},
+ //{{"thrd_join", 2}, FnInfo{&CheckZeroErrorResult}},
+ //{{"thrd_sleep", 2}, FnInfo{&CheckZeroErrorResult}},
+
+ {{"time", 1}, FnInfo{&CheckEofErrorResult}},
+ {{"timespec_get", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"tmpfile", 0}, FnInfo{&CheckNullErrorResult}},
+ {{"tmpfile_s", 1}, FnInfo{&CheckZeroErrorResult}},
+ {{"tmpnam", 1}, FnInfo{&CheckNullErrorResult}},
+ {{"tmpnam_s", 2}, FnInfo{&CheckZeroErrorResult}},
+ // FIXME: Knowledge of enum values is needed.
+ //{{"tss_create", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"tss_get", 1}, FnInfo{&CheckZeroErrorResult}},
+ // FIXME: Knowledge of enum value is needed.
+ //{{"tss_set", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"ungetc", 2}, FnInfo{&CheckEofErrorResult}},
+ {{"ungetwc", 2}, FnInfo{&CheckEofErrorResult}},
+ {{"vfprintf", 3}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vfprintf_s", 3}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vfscanf", 3}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vfscanf_s", 3}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vfwprintf", 3}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vfwprintf_s", 3}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vfwscanf", 3}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vfwscanf_s", 3}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vprintf", 2}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vprintf_s", 2}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vscanf", 2}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vscanf_s", 2}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vsnprintf", 4}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vsnprintf_s", 4}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vsnwprintf_s", 4}, FnInfo{&CheckPositiveErrorResult}},
+ {{"vsprintf", 3}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vsprintf_s", 4}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vsscanf", 3}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vsscanf_s", 3}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vswprintf", 4}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vswprintf_s", 4}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vswscanf", 3}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vswscanf_s", 3}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vwprintf_s", 2}, FnInfo{&CheckNegativeErrorResult}},
+ {{"vwscanf", 2}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"vwscanf_s", 2}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ {{"wcrtomb", 3}, FnInfo{&CheckEofErrorResult}},
+ {{"wcrtomb_s", 5}, FnInfo{&CheckZeroErrorResult}},
+ {{"wcschr", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"wcsftime", 4}, FnInfo{&CheckZeroErrorResult}},
+ {{"wcspbrk", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"wcsrchr", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"wcsrtombs", 4}, FnInfo{&CheckEofErrorResult}},
+ {{"wcsrtombs_s", 5}, FnInfo{&CheckZeroErrorResult}},
+ {{"wcsstr", 2}, FnInfo{&CheckNullErrorResult}},
+ {{"wcstod", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"wcstof", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"wcstoimax", 3}, FnInfo{&CheckMinMaxErrorResult}},
+ {{"wcstok", 3}, FnInfo{&CheckNullErrorResult}},
+ {{"wcstok_s", 4}, FnInfo{&CheckNullErrorResult}},
+ {{"wcstol", 3}, FnInfo{&CheckMinMaxErrorResult}},
+ {{"wcstold", 2}, FnInfo{&CheckZeroErrorResult}},
+ {{"wcstoll", 3}, FnInfo{&CheckMinMaxErrorResult}},
+ {{"wcstombs", 3}, FnInfo{&CheckEofErrorResult}},
+ {{"wcstombs_s", 5}, FnInfo{&CheckZeroErrorResult}},
+ {{"wcstoumax", 3}, FnInfo{&CheckMaxErrorResult}},
+ {{"wcstoul", 3}, FnInfo{&CheckMaxErrorResult}},
+ {{"wcstoull", 3}, FnInfo{&CheckMaxErrorResult}},
+ // FIXME: wcsxfrm can not fail according to cppreference.com?
+ //{{"wcsxfrm", 3}, FnInfo{&CheckZeroErrorResult}},
+ {{"wctob", 1}, FnInfo{&CheckEofErrorResult}},
+ {{"wctomb", 2},
+ FnInfo{&CheckEofErrorResult, {}, std::bind(&filterNullArg, _1, _2, 0)}},
+ {{"wctomb_s", 4}, FnInfo{&CheckZeroErrorResult}},
+ {{"wctrans", 1}, FnInfo{&CheckZeroErrorResult}},
+ {{"wctype", 1}, FnInfo{&CheckZeroErrorResult}},
+ {{"wmemchr", 3}, FnInfo{&CheckNullErrorResult}},
+ {{"wprintf_s"}, FnInfo{1, &CheckNegativeErrorResult}},
+ {{"wscanf"}, FnInfo{1, &CheckNegativeOrEofErrorResult}},
+ {{"wscanf_s"}, FnInfo{1, &CheckNegativeOrEofErrorResult}},
+ // Functions where the return value is not needed to be checked.
+ //{{"putchar", 1}, FnInfo{&CheckEofErrorResult}},
+ //{{"putwchar", 1}, FnInfo{&CheckEofErrorResult}},
+ //{{"puts", 1}, FnInfo{&CheckNegativeOrEofErrorResult}},
+ //{{"printf"}, FnInfo{1, &CheckNegativeErrorResult}},
+ //{{"vprintf", 2}, FnInfo{&CheckNegativeErrorResult}},
+ //{{"wprintf"}, FnInfo{1, &CheckNegativeErrorResult}},
+ //{{"vwprintf", 2}, FnInfo{&CheckNegativeErrorResult}},
+ };
+};
+
+struct CalledFunctionData {
+ const FnInfo *Info;
+ SourceRange CallLocation;
+
+ 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;
+ }
+};
+
+} // end anonymous namespace
+
+REGISTER_MAP_WITH_PROGRAMSTATE(FunctionCalledMap, SymbolRef, CalledFunctionData)
+
+void ErrorReturnChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
+ ProgramStateRef State = C.getState();
+ const ParentMap &PM = C.getLocationContext()->getParentMap();
+
+ if (!FD || FD->getKind() != Decl::Function)
+ return;
+
+ if (!Call.isGlobalCFunction())
+ return;
+
+ const FnInfo *Fn = CheckedFunctions.lookup(Call);
+ if (!Fn)
+ return;
+
+ // For variadic functions check for minimal argument count.
+ if (Fn->MinArgCount && Call.getNumArgs() < *(Fn->MinArgCount))
+ return;
+
+ 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;
+ }
+ // Check if the call is inside a return statement (can not evaluate this
+ // case).
+ while (S) {
+ if (isa<ReturnStmt>(S))
+ return;
+ S = PM.getParent(S);
+ }
+
+ // Use a function-specific rule to omit specific calls.
+ if (Fn->Filter && Fn->Filter(Call, C))
+ return;
+
+ // The call should have a symbolic return value to analyze it.
+ SVal RetSV = Call.getReturnValue();
+ if (RetSV.isUnknownOrUndef())
+ return;
+ SymbolRef RetSym = RetSV.getAsSymbol();
+ if (!RetSym)
+ return;
+
+ // Lazy-init the return type when the function is found.
+ if (Fn->RetTy.isNull())
+ Fn->RetTy = FD->getReturnType();
+
+ CalledFunctionData CFD{Fn, Call.getSourceRange()};
+ State = State->set<FunctionCalledMap>(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);
+ }
+
+ C.addTransition(State);
+}
+
+void ErrorReturnChecker::checkBranchCondition(const Stmt *S,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ SValBuilder &SVB = C.getSValBuilder();
+
+ const FunctionCalledMapTy &Map = State->get<FunctionCalledMap>();
+ llvm::SmallSet<SymbolRef, 10> SymbolsToRemove;
+ for (const auto &I : Map) {
+ SymbolRef Sym = I.first;
+ auto Val = SVB.makeSymbolVal(Sym).getAs<DefinedOrUnknownSVal>();
+ if (Val) {
+ // Check if we know that in the current state there was error check.
+ bool IsCompleteErrorCheck = I.second.Info->Checker->checkBranchCondition(
+ C, State, I.first, *Val, I.second.Info->RetTy);
+ // If the error check was found, remove the function call, no more need
+ // for handle it.
+ if (IsCompleteErrorCheck)
+ SymbolsToRemove.insert(Sym);
+ }
+ }
+ for (const auto I : SymbolsToRemove) {
+ State = State->remove<FunctionCalledMap>(I);
+ State = State->remove<ParmValMap>(I);
+ }
+ if (!SymbolsToRemove.empty())
+ C.addTransition(State);
+}
+
+void ErrorReturnChecker::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ llvm::SmallSet<SymbolRef, 10> BugSymbols;
+ const FunctionCalledMapTy &Map = State->get<FunctionCalledMap>();
+ for (const auto &I : Map) {
+ SymbolRef Sym = I.first;
+ if (!SymReaper.isDead(Sym))
+ continue;
+
+ // If a function call is stored in the state at this point, no error
+ // check was found for it.
+ BugSymbols.insert(Sym);
+ }
+
+ if (BugSymbols.empty())
+ return;
+
+ for (const auto I : BugSymbols)
+ State = State->remove<ParmValMap>(I);
+
+ ExplodedNode *N = C.generateNonFatalErrorNode(State);
+ for (const auto I : BugSymbols) {
+ emitUnchecked(C, N, I);
+ }
+
+ // FIXME: Remove items from FunctionCalledMap.
+}
+
+void ErrorReturnChecker::emitUnchecked(CheckerContext &C, ExplodedNode *N,
+ SymbolRef Sym) const {
+ if (N) {
+ const CalledFunctionData *CFD = C.getState()->get<FunctionCalledMap>(Sym);
+ assert(CFD && "Function data not found.");
+ if (!BT_Unchecked)
+ BT_Unchecked.reset(
+ new BuiltinBug(this, "Unchecked return value",
+ "Missing or incomplete error check of return value"));
+ auto Report = std::make_unique<BasicBugReport>(
+ *BT_Unchecked, BT_Unchecked->getDescription(),
+ PathDiagnosticLocation{CFD->CallLocation.getBegin(),
+ C.getSourceManager()});
+ Report->addRange(CFD->CallLocation);
+ C.emitReport(std::move(Report));
+ }
+}
+
+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
@@ -35,6 +35,7 @@
DynamicTypePropagation.cpp
DynamicTypeChecker.cpp
EnumCastOutOfRangeChecker.cpp
+ ErrorReturnChecker.cpp
ExprInspectionChecker.cpp
FixedAddressChecker.cpp
GCDAntipatternChecker.cpp
Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -427,6 +427,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>;
Index: clang/docs/analyzer/checkers.rst
===================================================================
--- clang/docs/analyzer/checkers.rst
+++ clang/docs/analyzer/checkers.rst
@@ -2083,6 +2083,69 @@
f(); // warn: no call of chdir("/") immediately after chroot
}
+.. _alpha-unix-ErrorReturn:
+
+alpha.unix.ErrorReturn (C)
+""""""""""""""""""""""""""
+Check for unchecked return value from certain API functions.
+A warning is produced if the function is used without checking the return value of it for error condition.
+No warning is produced when the function call is explicitly casted to ``void``.
+
+Limitations:
+
+* To be found by the checker, the error checking should occur on the same stack level as the function call to check or in a called function.
+* For functions that return the smallest or largest representable numeric value on error, the error check should test specifically for these
+ values, not for a smaller interval.
+* Implicit conversion to condition type (``if (value)``) is not detected. You can use code like ``if (!value)`` or ``if (value != NULL)`` instead.
+
+The following functions are supported:
+``aligned_alloc, asctime_s, at_quick_exit, atexit, bsearch, bsearch_s, btowc, c16rtomb, c32rtomb, calloc, clock, ctime_s, fclose, fflush,``
+``fgetc, fgetpos, fgets, fgetwc, fopen, fopen_s, fprintf, fprintf_s, fputc, fputs, fputwc, fputws, fread, freopen, freopen_s, fscanf, fscanf_s,``
+``fseek, fsetpos, ftell, fwprintf, fwprintf_s, fwrite, fwscanf, fwscanf_s, getc, getchar, getenv, getenv_s, gets_s, getwc, getwchar, gmtime,``
+``gmtime_s, localtime, localtime_s, malloc, mblen, mbrlen, mbrtoc16, mbrtoc32, mbrtowc, mbsrtowcs, mbsrtowcs_s, mbstowcs, mbstowcs_s, mbtowc,``
+``memchr, mktime, printf_s, putc, putwc, raise, realloc, remove, rename, setlocale, setvbuf, scanf, scanf_s, snprintf, snprintf_s,``
+``snwprintf_s, sprintf, sprintf_s, sscanf, sscanf_s, strchr, strerror_s, strftime, strpbrk, strrchr, strstr, strtod, strtof,``
+``strtoimax, strtok, strtok_s, strtol, strtoll, strtoumax, strtoul, strtoull, swprintf, swprintf_s, swscanf, swscanf_s, time, timespec_get,``
+``tmpfile, tmpfile_s, tmpnam, tmpnam_s, tss_get, ungetc, ungetwc, vfprintf, vfprintf_s, vfscanf, vfscanf_s, vfwprintf, vfwprintf_s,``
+``vfwscanf, vfwscanf_s, vprintf, vprintf_s, vscanf, vscanf_s, vsnprintf, vsnprintf_s, vsnwprintf_s, vsprintf, vsprintf_s, vsscanf,``
+``vsscanf_s, vswprintf, vswprintf_s, vswscanf, vswscanf_s, vwprintf_s, vwscanf, vwscanf_s, wcrtomb, wcrtomb_s, wcschr, wcsftime,``
+``wcspbrk, wcsrchr, wcsrtombs, wcsrtombs_s, wcsstr, wcstod, wcstof, wcstoimax, wcstok, wcstok_s, wcstol, wcstold, wcstoll, wcstombs,``
+``wcstombs_s, wcstoumax, wcstoul, wcstoull, wctob, wctomb, wctomb_s, wctrans, wctype, wmemchr, wprintf_s, wscanf, wscanf_s``
+
+The following functions are not supported:
+``cnd_broadcast, cnd_init, cnd_signal, cnd_timedwait, cnd_wait, mtx_init, mtx_lock, mtx_timedlock, mtx_trylock, mtx_unlock, signal, strtold,``
+``thrd_create, thrd_detach, thrd_join, thrd_sleep, tss_create, tss_set``
+
+The following functions are not checked:
+``putchar, putwchar, puts, printf, vprintf, wprintf, vwprintf, strxfrm, wcsxfrm¸``
+``kill_dependency, memcpy, wmemcpy, memmove, wmemmove, strcpy, wcscpy, strncpy, wcsncpy,``
+``strcat, wcscat, strncat, wcsncat, memset, wmemset``
+Return value of these functions is traditionally not checked for error or there is no specific error condition.
+
+.. code-block:: c
+
+ void test1() {
+ void *p = malloc(10);
+ // warn: missing or incomplete error check of return value
+ }
+
+ void test2() {
+ FILE* fp = fopen("test.txt", "r");
+ // warn: missing or incomplete error check of return value
+ fclose(fp);
+ // warn: missing or incomplete error check of return value
+ }
+
+ void test3() {
+ FILE* fp = fopen("test.txt", "r");
+ if (!fp) {
+ perror("File opening failed");
+ return;
+ }
+ (void)fclose(fp);
+ // warning is suppressed by explicit cast to void
+ }
+
.. _alpha-unix-PthreadLock:
alpha.unix.PthreadLock (C)
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits