diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst --- a/clang/docs/analyzer/checkers.rst +++ b/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) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -427,6 +427,10 @@ HelpText<"Check improper use of chroot">, Documentation; +def ErrorReturnChecker : Checker<"ErrorReturn">, + HelpText<"Check for unchecked error return values">, + Documentation; + def PthreadLockChecker : Checker<"PthreadLock">, HelpText<"Simple lock -> unlock checker">, Documentation; diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -35,6 +35,7 @@ DynamicTypePropagation.cpp DynamicTypeChecker.cpp EnumCastOutOfRangeChecker.cpp + ErrorReturnChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp GCDAntipatternChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ErrorReturnChecker.cpp new file mode 100644 --- /dev/null +++ b/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 + +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(); + 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(); + 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(); + 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(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(); + 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; + +struct FnInfo { + Optional MinArgCount; + CheckForErrorResultChecker *Checker; + Optional ParmI; + FnFilter Filter; + mutable QualType RetTy; + + FnInfo(CheckForErrorResultChecker *Checker, Optional ParmI = {}, + FnFilter Filter = {}) + : Checker(Checker), ParmI(ParmI), Filter(Filter) { + ; + } + FnInfo(unsigned int MinArgCount, CheckForErrorResultChecker *Checker, + Optional 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 { + mutable std::unique_ptr 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 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(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(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(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(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(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(); + llvm::SmallSet SymbolsToRemove; + for (const auto &I : Map) { + SymbolRef Sym = I.first; + auto Val = SVB.makeSymbolVal(Sym).getAs(); + 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(I); + State = State->remove(I); + } + if (!SymbolsToRemove.empty()) + C.addTransition(State); +} + +void ErrorReturnChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + llvm::SmallSet BugSymbols; + const FunctionCalledMapTy &Map = State->get(); + 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(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(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( + *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(); +} + +bool ento::shouldRegisterErrorReturnChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/test/Analysis/Inputs/system-header-simulator.h b/clang/test/Analysis/Inputs/system-header-simulator.h --- a/clang/test/Analysis/Inputs/system-header-simulator.h +++ b/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); diff --git a/clang/test/Analysis/error-return.c b/clang/test/Analysis/error-return.c new file mode 100644 --- /dev/null +++ b/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"); +}