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,17 @@ f(); // warn: no call of chdir("/") immediately after chroot } +.. _alpha-unix-ErrorReturn: + +alpha.unix.ErrorReturn (C) +"""""""""""""""""""""" +Check for unchecked return value from API functions: ``xxx, yyy,`` +``zzz``. + +.. code-block:: c + + // warn: ?????????? + .. _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,665 @@ +//===-- 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/ParentMap.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: + // See if the result value from the system function (to check) is checked for + // error after a branch condition. 'Value' contains the (mostly conjured) + // symbolic value of the function call. 'RetTy' is the return type of the + // function (obtained from the AST). + // Return if the state after this branch condition is a correct check for + // error return condition. + virtual bool checkBranchCondition(CheckerContext &C, ProgramStateRef State, + SymbolRef Sym, + DefinedOrUnknownSVal FunctionCall, + 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. +// FIXME: Is this different from the ValueErrorResultChecker with 0 as 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)); + } + } +}; + +#if 0 +// Error return is a fixed value of an enum. +// FIXME: It is not possible to get value of a specific enumerator (whose string name is known) +// without a preprocessor object. +class EnumErrorResultChecker : public CheckForErrorResultChecker { +public: + EnumErrorResultChecker(const std::string &ErrorResultValue) : ErrorResultValue(ErrorResultValue) {;} + + bool checkBranchCondition(CheckerContext &C, ProgramStateRef State, DefinedOrUnknownSVal Value, const QualType &RetTy) const override { + + } +private: + std::string ErrorResultValue; +}; +#endif + +using FnFilter = std::function; + +struct FnInfo { + CheckForErrorResultChecker *Checker; + Optional ParmI; + FnFilter Filter; + mutable QualType RetTy; + + FnInfo(CheckForErrorResultChecker *Checker, Optional ParmI = {}, + FnFilter Filter = {}) + : 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", 2+}, FnInfo{&CheckZeroErrorResult}}, + //{{"fprintf_s", 2+}, FnInfo{&CheckZeroErrorResult}}, + {{"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", 2+}, FnInfo{&CheckZeroErrorResult}}, + //{{"fscanf_s", 2+}, FnInfo{&CheckZeroErrorResult}}, + {{"fseek", 3}, FnInfo{&CheckZeroErrorResult}}, + {{"fsetpos", 2}, FnInfo{&CheckZeroErrorResult}}, + {{"ftell", 1}, FnInfo{&CheckEofErrorResult}}, + //{{"fwprintf", 2+}, FnInfo{&CheckZeroErrorResult}}, + //{{"fwprintf_s", 2+}, FnInfo{&CheckZeroErrorResult}}, + {{"fwrite", 4}, FnInfo{&CheckLessThanParmValErrorResult, 2}}, + //{{"fwscanf", 2+}, FnInfo{&CheckZeroErrorResult}}, + //{{"fwscanf_s", 2+}, FnInfo{&CheckZeroErrorResult}}, + {{"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", 1+}, FnInfo{&CheckZeroErrorResult}}, + {{"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", 1+}, FnInfo{&CheckZeroErrorResult}}, + //{{"scanf_s", 1+}, FnInfo{&CheckZeroErrorResult}}, + // FIXME: Should find value of SIG_ERR macro. + //{{"signal", 2}, FnInfo{&CheckZeroErrorResult}}, + //{{"snprintf", 3+}, FnInfo{&CheckZeroErrorResult}}, + //{{"snprintf_s", 3+}, FnInfo{&CheckZeroErrorResult}}, + //{{"snwprintf_s", 3+}, FnInfo{&CheckZeroErrorResult}}, + //{{"sprintf", 2+}, FnInfo{&CheckZeroErrorResult}}, + //{{"sprintf_s", 3+}, FnInfo{&CheckZeroErrorResult}}, + //{{"sscanf", 2+}, FnInfo{&CheckZeroErrorResult}}, + //{{"sscanf_s", 2+}, FnInfo{&CheckZeroErrorResult}}, + {{"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", 3+}, FnInfo{&CheckZeroErrorResult}}, + //{{"swprintf_s", 3+}, FnInfo{&CheckZeroErrorResult}}, + //{{"swscanf", 2+}, FnInfo{&CheckZeroErrorResult}}, + //{{"swscanf_s", 2+}, FnInfo{&CheckZeroErrorResult}}, + + // 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", 1+}, FnInfo{&CheckZeroErrorResult}}, + //{{"wscanf", 1+}, FnInfo{&CheckZeroErrorResult}}, + //{{"wscanf_s", 1+}, FnInfo{&CheckZeroErrorResult}}, + }; +}; + +struct CalledFunctionData { + const FnInfo *Info; + SourceRange CallLocation; + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddPointer(Info); + ID.AddInteger(CallLocation.getBegin().getRawEncoding()); + // ID.AddInteger(CallLocation.getEnd().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(); + + if (!FD || FD->getKind() != Decl::Function) + return; + + if (!Call.isGlobalCFunction()) + return; + + const FnInfo *Fn = CheckedFunctions.lookup(Call); + if (!Fn) + return; + + if (Fn->Filter && Fn->Filter(Call, C)) + return; + + 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 a specific argument (needed later). + 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) { + bool IsCompleteErrorCheck = I.second.Info->Checker->checkBranchCondition( + C, State, I.first, *Val, I.second.Info->RetTy); + if (IsCompleteErrorCheck) + SymbolsToRemove.insert(Sym); + } + } + for (const auto I : SymbolsToRemove) { + State = State->remove(I); + State = State->remove(I); + } + 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; + + BugSymbols.insert(Sym); + } + + if (BugSymbols.empty()) + return; + + for (const auto I : BugSymbols) { + // State = State->remove(I); + State = State->remove(I); + // FIXME: source range can not be removed here because it is needed for + // report + } + ExplodedNode *N = C.generateNonFatalErrorNode(State); + for (const auto I : BugSymbols) { + emitUnchecked(C, N, I); + } +} + +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); + // Report->addNote("Function is called here", + // PathDiagnosticLocation{CFD->CallLocation.getBegin(), + // C.getSourceManager()}, 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,24 @@ #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) + +// + +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); 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,578 @@ +// 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 ); +*/ + +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]; + errno_t err = asctime_s(buf, 1, NULL); // FIXME: unexpected warning here + if (err) { + } +} + +void test_ZeroCorrectCheck7() { + char buf[1]; + errno_t err = asctime_s(buf, 1, NULL); + if (!err) { + } +} + +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() { + void *P = aligned_alloc(4, 8); // FIXME: unexpected warning here + 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) { + } +} + +int test_NullCorrectCheck4() { + return aligned_alloc(4, 8) == NULL; // FIXME: unexpected warning here +} + +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_MinMaxBadCheck1() { + intmax_t X = strtoimax("", NULL, 10); // expected-warning{{Missing or incomplete error check}} + if (X == INTMAX_MIN) { + } +} + +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) { + } +}