balazske created this revision. Herald added subscribers: cfe-commits, gamesh411, Szelethus, dkrupp, mgorny. Herald added a project: clang.
First simple implementation, infrastructure is ready. Only one kind of check (NULL error return value) is implemented. Documentation not added yet. Idea of the checker is taken from: https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors Repository: rG LLVM Github Monorepo 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,323 @@ +// 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() +*/ + +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}} +} 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,8 @@ #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); Index: clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp =================================================================== --- /dev/null +++ clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp @@ -0,0 +1,320 @@ +//===-- 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; + +/// Store a saved argument value for an API function call to check. +REGISTER_MAP_WITH_PROGRAMSTATE(ParmValMap, SymbolRef, llvm::APSInt) + +namespace { + +/// Defines the way of checking a specific kind of function call. +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; +}; + +/// Check functions whose error return is a NULL value. +/// 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(); + } +}; + +using FnFilter = std::function<bool(const CallEvent &, CheckerContext &)>; + +/// Data about an API function to check. +struct FnInfo { + /// Minimal argument count, only for variadic functions. + Optional<unsigned int> MinArgCount; + + /// Checker for the function. + CheckForErrorResultChecker *Checker; + + /// Index of argument whose value at call is to be saved in ParmValMap. + Optional<unsigned int> ParmI; + + /// Possibility to exclude a function from the check. + FnFilter Filter; + + /// Return type of the function (initialized at runtime). + 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) { + ; + } +}; + +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; + + NullErrorResultChecker CheckNullErrorResult; + + CallDescriptionMap<FnInfo> CheckedFunctions = { + {{"aligned_alloc", 2}, FnInfo{&CheckNullErrorResult}}, + {{"bsearch", 5}, FnInfo{&CheckNullErrorResult}}, + {{"bsearch_s", 6}, FnInfo{&CheckNullErrorResult}}, + {{"calloc", 2}, FnInfo{&CheckNullErrorResult}}, + {{"fgets", 3}, FnInfo{&CheckNullErrorResult}}, + {{"fopen", 2}, FnInfo{&CheckNullErrorResult}}, + {{"freopen", 3}, FnInfo{&CheckNullErrorResult}}, + {{"getenv", 1}, FnInfo{&CheckNullErrorResult}}, + {{"getenv_s", 4}, FnInfo{&CheckNullErrorResult}}, + {{"gets_s", 2}, FnInfo{&CheckNullErrorResult}}, + {{"gmtime", 1}, FnInfo{&CheckNullErrorResult}}, + {{"gmtime_s", 2}, FnInfo{&CheckNullErrorResult}}, + {{"localtime", 1}, FnInfo{&CheckNullErrorResult}}, + {{"localtime_s", 2}, FnInfo{&CheckNullErrorResult}}, + {{"malloc", 1}, FnInfo{&CheckNullErrorResult}}, + {{"memchr", 3}, FnInfo{&CheckNullErrorResult}}, + {{"realloc", 2}, FnInfo{&CheckNullErrorResult}}, + {{"setlocale", 2}, FnInfo{&CheckNullErrorResult}}, + {{"strchr", 2}, FnInfo{&CheckNullErrorResult}}, + {{"strpbrk", 2}, FnInfo{&CheckNullErrorResult}}, + {{"strrchr", 2}, FnInfo{&CheckNullErrorResult}}, + {{"strstr", 2}, FnInfo{&CheckNullErrorResult}}, + {{"strtok", 2}, FnInfo{&CheckNullErrorResult}}, + {{"strtok_s", 4}, FnInfo{&CheckNullErrorResult}}, + {{"tmpfile", 0}, FnInfo{&CheckNullErrorResult}}, + {{"tmpnam", 1}, FnInfo{&CheckNullErrorResult}}, + {{"wcschr", 2}, FnInfo{&CheckNullErrorResult}}, + {{"wcspbrk", 2}, FnInfo{&CheckNullErrorResult}}, + {{"wcsrchr", 2}, FnInfo{&CheckNullErrorResult}}, + {{"wcsstr", 2}, FnInfo{&CheckNullErrorResult}}, + {{"wcstok", 3}, FnInfo{&CheckNullErrorResult}}, + {{"wcstok_s", 4}, FnInfo{&CheckNullErrorResult}}, + {{"wmemchr", 3}, FnInfo{&CheckNullErrorResult}}, + }; +}; + +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() || !Call.isInSystemHeader()) + 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 @@ -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