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