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 @@ -516,6 +516,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">, Dependencies<[PthreadLockBase]>, 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 @@ -40,6 +40,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,534 @@ +//===-- 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/CheckerHelpers.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; + +namespace { + +class ErrorReturnChecker; + +/// 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: + ErrorReturnCheckKind(const ErrorReturnChecker *Checker) : Checker{Checker} {}; + virtual ~ErrorReturnCheckKind(){}; + + /// 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, the other side is passed in `OtherSideExpr`. + virtual bool testBinOpForCheckStatement(CheckerContext &C, QualType RetTy, + const BinaryOperator *BinOp, + const Expr *OtherSideExpr, + bool OtherSideIsRHS) const; + +protected: + const ErrorReturnChecker *const Checker; +}; + +/// 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: + EOFOrNegativeErrorReturn(const ErrorReturnChecker *Checker) + : ErrorReturnCheckKind{Checker} {} + + bool testBinOpForCheckStatement(CheckerContext &C, QualType RetTy, + const BinaryOperator *BinOp, + const Expr *OtherSideExpr, + bool OtherSideIsRHS) const override; +}; + +/// Description of an API function to check. +struct FnInfo { + /// Error return check kind for the function. + ErrorReturnCheckKind *ErrorReturnKind; + + /// Is an unchecked use of return value error. + bool UncheckedUseIsError; + + /// Return type of the function (initialized at runtime). + mutable QualType RetTy; + + FnInfo(ErrorReturnCheckKind *ErrorReturnKind) + : ErrorReturnKind(ErrorReturnKind) { + ; + } +}; + +/// 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 FunctionCallData { + /// Point out the kind of the function that was called. + const FnInfo *Info; + /// Source range of the calling statement. + SourceRange CallLocation; + + FunctionCallData(const FunctionCallData &Call) + : Info(Call.Info), CallLocation(Call.CallLocation) {} + FunctionCallData(const FnInfo *Info, const SourceRange &CallLocation) + : Info{Info}, CallLocation{CallLocation} {} + + FunctionCallData &operator=(const FunctionCallData &Call) { + Info = Call.Info; + CallLocation = Call.CallLocation; + return *this; + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddPointer(Info); + ID.AddInteger(CallLocation.getBegin().getRawEncoding()); + } + + bool operator==(const FunctionCallData &Call) const { + return Info == Call.Info && CallLocation == Call.CallLocation; + } +}; + +class ErrorReturnChecker + : public Checker { + mutable std::unique_ptr BT_UncheckedUse; + + void checkAccess(CheckerContext &C, ProgramStateRef State, const Stmt *LoadS, + SymbolRef CallSym, const FunctionCallData *Call) const; + ProgramStateRef processEscapedParams(CheckerContext &C, const CallEvent &Call, + ProgramStateRef State) const; + const FnInfo *findFunctionToCheck(CheckerContext &C, + const CallEvent &Call) const; + +public: + void initialize(CheckerManager &Mgr); + + 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; + + int getEOFValue() const { return EOFValue; } + + /// Determine the "known value" (if a constant) of an Expr. + static const llvm::APSInt *getKnownConstantVal(CheckerContext &C, + const Expr *E) { + Optional ConstantVal = C.getSValBuilder().getConstantVal(E); + if (ConstantVal) + return C.getSValBuilder().getKnownValue(C.getState(), *ConstantVal); + return nullptr; + } + +private: + EOFOrNegativeErrorReturn CheckForEOFOrNegative{this}; + + CallDescriptionMap CheckedFunctions = { + {{"fputs", 2}, FnInfo{&CheckForEOFOrNegative}}, + {{"fputws", 2}, FnInfo{&CheckForEOFOrNegative}}, + }; + + int EOFValue = -1; +}; + +bool EOFOrNegativeErrorReturn::testBinOpForCheckStatement( + CheckerContext &C, QualType RetTy, const BinaryOperator *BinOp, + const Expr *OtherSideExpr, bool OtherSideIsRHS) const { + // Anything other than comparison is not a valid error check. + if (!BinOp->isComparisonOp()) + return false; + + bool NullFound = false; + bool EOFFound = false; + const llvm::APSInt *ValueToTestAgainst = + ErrorReturnChecker::getKnownConstantVal(C, OtherSideExpr); + if (ValueToTestAgainst) { + NullFound = ValueToTestAgainst->isNullValue(); + EOFFound = ((*ValueToTestAgainst) == + C.getSValBuilder().getBasicValueFactory().getValue( + Checker->getEOFValue(), RetTy)); + } else { + // If a function is called at the other side of comparison, assume that it + // returns a correct NULL or EOF value. For example: 'X == getEOFValue()' It + // is not assumable that a NULL or EOF is obtained using any other + // expression. + NullFound = isa(OtherSideExpr); + EOFFound = NullFound; + } + + BinaryOperator::Opcode Op = BinOp->getOpcode(); + if (!OtherSideIsRHS) + Op = BinaryOperator::reverseComparisonOp(Op); + + switch (Op) { + case BO_EQ: // 'X == -1' + case BO_NE: // 'X != -1' + return EOFFound; + case BO_LT: // 'X < 0' + return NullFound; + case BO_GE: // 'X >= 0' + return NullFound; + case BO_LE: // 'X <= -1' + return EOFFound; + case BO_GT: // 'X > -1' + return EOFFound; + default: + return false; + } + + return false; +} + +/// Result of the ErrorCheckTestStmtVisitor. +enum VisitResult { + // Value use found that is not an error check. + NoCheckUseFound, + // 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). + ContinueAtParentStmt, + // Value appeared in any other way. + StopExamineNoError +}; + +/// Determine if a statement is an error-check for a return value. +/// The statement to check is stored in `Child`. +/// This can be the actual load statement that uses the return value, +/// or a parent statement of it that stands for the same value. +/// See `ErrorReturnChecker::checkAccess` for the calling algorithm. +class ErrorCheckTestStmtVisitor + : public ConstStmtVisitor { + CheckerContext &C; + /// Currently examined child statement. + const Stmt *Child; + /// Data about function whose return value is checked. + const FunctionCallData *Call; + const ParentMap &PM; + + /// Check if `Child` is a child of `Parent` in `ParentMap`. + bool isChildOf(const Stmt *Parent) const { + for (const Stmt *C = Child; C; C = PM.getParent(C)) + if (C == Parent) + return true; + return false; + }; + +public: + ErrorCheckTestStmtVisitor(CheckerContext &C, const Stmt *Child, + const FunctionCallData *Call) + : C(C), Child(Child), Call(Call), + PM(C.getLocationContext()->getParentMap()) {} + + VisitResult VisitBinaryOperator(const BinaryOperator *BO) { + // Try to find the child expression on one side of the operator. + Expr *OtherS = nullptr; + for (const Stmt *P = Child; P && !OtherS; P = PM.getParent(P)) { + if (P == BO->getLHS()) + OtherS = BO->getRHS(); + else if (P == BO->getRHS()) + OtherS = BO->getLHS(); + } + assert(OtherS && "Invalid parent at binary operator."); + + BinaryOperatorKind Op = BO->getOpcode(); + if (Op == BO_Assign) { + // Value of child is transferred to parent, can continue with parent. + assert(OtherS == BO->getLHS() && "Loaded value not on assignment RHS."); + return ContinueAtParentStmt; + } + + // `Child` appears in the binary operator. + // Perform a specific check on the binary operator to determine if it is + // an error-check statement. + if (Call->Info->ErrorReturnKind->testBinOpForCheckStatement( + C, Call->Info->RetTy, BO, OtherS, OtherS == BO->getRHS())) + return CheckFound; + // Value appears in binary operator in other way than error check. + return NoCheckUseFound; + } + + VisitResult VisitCallExpr(const CallExpr *CE) { + const FunctionDecl *CalledF = C.getCalleeDecl(CE); + SourceLocation Loc = CalledF->getLocation(); + // Check if system function is called. + // It is assumed that any system function does not accept value that is + // an error return value from any other function. + // In other words error check is required before system function is called + // with the value. + if (Loc.isValid() && C.getSourceManager().isInSystemHeader(Loc)) + return NoCheckUseFound; + // Non-system function call is no error, the call may be inlined and can + // contain error check. + return StopExamineNoError; + } + + // Value appears in other expression. + VisitResult VisitExpr(const Stmt *S) { return NoCheckUseFound; } + + VisitResult VisitDeclStmt(const DeclStmt *Decl) { + // Value is used at initialization. + return StopExamineNoError; + } + + VisitResult VisitIfStmt(const IfStmt *S) { + if (isChildOf(S->getCond())) + return NoCheckUseFound; + return StopExamineNoError; + } + + VisitResult VisitForStmt(const ForStmt *S) { + if (isChildOf(S->getInit()) || isChildOf(S->getCond()) || + isChildOf(S->getInc())) + return NoCheckUseFound; + return StopExamineNoError; + } + + VisitResult VisitDoStmt(const DoStmt *S) { + if (isChildOf(S->getCond())) + return NoCheckUseFound; + return StopExamineNoError; + } + + VisitResult VisitWhileStmt(const WhileStmt *S) { + if (isChildOf(S->getCond())) + return NoCheckUseFound; + return StopExamineNoError; + } + + // Value appears in other statement. + VisitResult VisitStmt(const Stmt *S) { return StopExamineNoError; } +}; + +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(FunctionCallDataMap, SymbolRef, FunctionCallData) + +void ErrorReturnChecker::initialize(CheckerManager &Mgr) { + Optional EOFExpanded = tryExpandAsInteger("EOF", Mgr.getPreprocessor()); + if (EOFExpanded) + EOFValue = *EOFExpanded; +} + +void ErrorReturnChecker::checkAccess(CheckerContext &C, ProgramStateRef State, + const Stmt *LoadS, SymbolRef CallSym, + const FunctionCallData *Call) 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, Call}; + switch (FindErrorCheck.Visit(ParentS)) { + case NoCheckUseFound: { + 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 = Call->CallLocation; + 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); + C.addTransition(State); + return; + + case ContinueAtParentStmt: + // Continue checking at upper level (check with result of assignment). + LoadS = ParentS; + continue; + + case StopExamineNoError: + 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); + } + } + 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; + + 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; + } + + // 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(); + + FunctionCallData CallData{Fn, Call.getSourceRange()}; + State = State->set(RetSym, CallData); + + checkAccess(C, State, Call.getOriginExpr(), RetSym, &CallData); +} + +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 FunctionCallData *Call = State->get(Sym); + if (!Call) + return; + + checkAccess(C, State, S, Sym, Call); +} + +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); + } + return State; +} + +void ento::registerErrorReturnChecker(CheckerManager &Mgr) { + auto *Checker = Mgr.registerChecker(); + Checker->initialize(Mgr); +} + +bool ento::shouldRegisterErrorReturnChecker(const CheckerManager &Mgr) { + 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 @@ -49,6 +49,7 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); int fputc(int ch, FILE *stream); +int fputs(const char *restrict s, FILE *restrict stream); int fseek(FILE *__stream, long int __off, int __whence); long int ftell(FILE *__stream); void rewind(FILE *__stream); @@ -100,6 +101,7 @@ //The following are fake system header functions for generic testing. void fakeSystemHeaderCallInt(int *); void fakeSystemHeaderCallIntPtr(int **); +void fakeSystemHeaderCallIntVal(int); // Some data strauctures may hold onto the pointer and free it later. void fake_insque(void *, void *); 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,196 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.unix.ErrorReturn -verify %s + +#include "Inputs/system-header-simulator.h" + +FILE *file(); + +void test_EOFOrNeg_LT_Good() { + if (fputs("str", file()) < 0) { + } +} + +void test_EOFOrNeg_LT_Bad() { + if (fputs("str", file()) < -1) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_GT_Good() { + if (fputs("str", file()) > -1) { + } +} + +void test_EOFOrNeg_GT_Bad() { + if (fputs("str", file()) > 0) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_LE_Good() { + if (fputs("str", file()) <= -1) { + } +} + +void test_EOFOrNeg_LE_Bad() { + if (fputs("str", file()) <= 0) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_GE_Good() { + if (fputs("str", file()) >= 0) { + } +} + +void test_EOFOrNeg_GE_Bad() { + if (fputs("str", file()) >= -1) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_EQ_Good() { + if (fputs("str", file()) == -1) { + } +} + +void test_EOFOrNeg_EQ_Bad() { + if (fputs("str", file()) == 0) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_NE_Good() { + if (fputs("str", file()) != -1) { + } +} + +void test_EOFOrNeg_NE_Bad() { + if (fputs("str", file()) != 0) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_EQ_BadVal() { + if (fputs("str", file()) == -2) { // expected-warning{{Use of return value that was not checked}} + } + if (fputs("str", file()) == 1) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_VarAssign() { + int X = fputs("str", file()); + if (X != 0) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_VarAssignInCond() { + int X; + if ((X = fputs("str", file())) != 0) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_VarAssign1() { + int X = fputs("str", file()); + int Y = X; + if (Y != 0) { // expected-warning{{Use of return value that was not checked}} + } +} + +void badcheck(int X) { + if (X == 0) { + } // expected-warning{{Use of return value that was not checked}} +} + +void test_EOFOrNeg_Call() { + int X = fputs("str", file()); + badcheck(X); +} + +void test_EOFOrNeg_Syscall() { + int X = fputs("str", file()); + fakeSystemHeaderCallIntVal(X); // expected-warning{{Use of return value that was not checked}} + fakeSystemHeaderCallIntVal(fputs("str", file())); // expected-warning{{Use of return value that was not checked}} +} + +void test_EOFOrNeg_Use_LNot() { + int X = fputs("str", file()); + if (!X) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_Use_Add() { + int X = fputs("str", file()); + int Y = X + 1; // 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_IfCond() { + if (fputs("str", file())) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_ForInit() { + for (fputs("str", file());;) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_ForCond() { + for (; fputs("str", file());) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_ForInc() { + for (;; fputs("str", file())) { // expected-warning{{Use of return value that was not checked}} + } +} + +void test_EOFOrNeg_DoCond() { + do { + } while (fputs("str", file())); // expected-warning{{Use of return value that was not checked}} +} + +void test_EOFOrNeg_WhileCond() { + while (fputs("str", file())) { // expected-warning{{Use of return value that was not checked}} + }; +} + +void unknown1(int); + +void test_EOFOrNeg_EscapeCall() { + int X = fputs("str", file()); + unknown1(X); + int Y = X + 1; +} + +int GlobalInt; + +void test_EOFOrNeg_EscapeGlobalAssign() { + GlobalInt = fputs("str", file()); + int X = GlobalInt + 1; +} + +void test_EOFOrNeg_NoErrorAfterGoodCheck() { + int X = fputs("str", file()); + if (X < 0) { + } + if (X < 1) { + } +} + +void test_EOFOrNeg_Unused() { + // FIXME: Detect this + fputs("str", file()); +} + +int getEOFValue(); + +void test_EOFOrNeg_CompareToFunction() { + int R = fputs("str", file()); + if (R == getEOFValue()) + return; +} + +void test_EOFOrNeg_CompareToOther() { + int R = fputs("str", file()); + if (R == 1 + getEOFValue()) // expected-warning{{Use of return value that was not checked}} + return; +}