Index: lib/StaticAnalyzer/Checkers/StreamChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -19,183 +19,392 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" -#include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/StringSwitch.h" using namespace clang; using namespace ento; +/* + Work is still in progress. + TODO: + 1. Enable 'errno' checking + 2. Ensure that fdopen options do not conflict with options of the associated + descriptor + 3. Ensure that code does not attempt to write to a stream opened for reading + only + 4. OpenFileAux return value should not be equal to std{in,err,out} + (source of some false positives) +*/ namespace { struct StreamState { - enum Kind { Opened, Closed, OpenFailed, Escaped } K; - const Stmt *S; + enum Kind { Opened, Closed, OpenFailed, CloseFailed, Escaped, Cloned } K; - StreamState(Kind k, const Stmt *s) : K(k), S(s) {} + StreamState(Kind k) : K(k) {} bool isOpened() const { return K == Opened; } - bool isClosed() const { return K == Closed; } - //bool isOpenFailed() const { return K == OpenFailed; } - //bool isEscaped() const { return K == Escaped; } + bool isOpenFailed() const { return K == OpenFailed; } + bool isClosed() const { return K == Closed || K == CloseFailed; } + bool isClosedProperly() const { return K == Closed; } bool operator==(const StreamState &X) const { - return K == X.K && S == X.S; + return K == X.K; } - static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); } - static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); } - static StreamState getOpenFailed(const Stmt *s) { - return StreamState(OpenFailed, s); - } - static StreamState getEscaped(const Stmt *s) { - return StreamState(Escaped, s); - } + static StreamState getOpened() { return StreamState(Opened); } + static StreamState getClosed() { return StreamState(Closed); } + static StreamState getOpenFailed() { return StreamState(OpenFailed); } + static StreamState getCloseFailed() { return StreamState(CloseFailed); } + static StreamState getEscaped() { return StreamState(Escaped); } + static StreamState getCloned() { return StreamState(Cloned); } void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); - ID.AddPointer(S); } }; -class StreamChecker : public Checker { - mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread, - *II_fwrite, - *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos, - *II_clearerr, *II_feof, *II_ferror, *II_fileno; - mutable std::unique_ptr BT_nullfp, BT_illegalwhence, - BT_doubleclose, BT_ResourceLeak; +class StreamChecker : public Checker { + mutable std::unique_ptr BT_nullfp, BT_negativefd, + BT_illegalwhence, BT_doubleclose, BT_ResourceLeak, BT_accessAfterClose, + BT_closeunderlying; public: - StreamChecker() - : II_fopen(0), II_tmpfile(0) ,II_fclose(0), II_fread(0), II_fwrite(0), - II_fseek(0), II_ftell(0), II_rewind(0), II_fgetpos(0), II_fsetpos(0), - II_clearerr(0), II_feof(0), II_ferror(0), II_fileno(0) {} bool evalCall(const CallExpr *CE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; private: - void Fopen(CheckerContext &C, const CallExpr *CE) const; - void Tmpfile(CheckerContext &C, const CallExpr *CE) const; - void Fclose(CheckerContext &C, const CallExpr *CE) const; - void Fread(CheckerContext &C, const CallExpr *CE) const; - void Fwrite(CheckerContext &C, const CallExpr *CE) const; - void Fseek(CheckerContext &C, const CallExpr *CE) const; - void Ftell(CheckerContext &C, const CallExpr *CE) const; - void Rewind(CheckerContext &C, const CallExpr *CE) const; - void Fgetpos(CheckerContext &C, const CallExpr *CE) const; - void Fsetpos(CheckerContext &C, const CallExpr *CE) const; - void Clearerr(CheckerContext &C, const CallExpr *CE) const; - void Feof(CheckerContext &C, const CallExpr *CE) const; - void Ferror(CheckerContext &C, const CallExpr *CE) const; - void Fileno(CheckerContext &C, const CallExpr *CE) const; - - void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; - - ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, - CheckerContext &C) const; - ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, - CheckerContext &C) const; + // Signature functions +#define SIGN_FUNC(Name) void Name (CheckerContext &C, const CallExpr *CE) const; + SIGN_FUNC(Fopen) + SIGN_FUNC(Tmpfile) + SIGN_FUNC(Fdopen) + SIGN_FUNC(Freopen) + SIGN_FUNC(Fclose) + SIGN_FUNC(Fread) + SIGN_FUNC(Fwrite) + SIGN_FUNC(Fscanf) + SIGN_FUNC(Fprintf) + SIGN_FUNC(Fseek) + SIGN_FUNC(Lseek) + SIGN_FUNC(Fstat) + SIGN_FUNC(Ftell) + SIGN_FUNC(Rewind) + SIGN_FUNC(Fgetpos) + SIGN_FUNC(Fsetpos) + SIGN_FUNC(Clearerr) + SIGN_FUNC(Feof) + SIGN_FUNC(Ferror) + SIGN_FUNC(Fileno) + SIGN_FUNC(Fflush) + SIGN_FUNC(Open) + SIGN_FUNC(Close) + SIGN_FUNC(Read) + SIGN_FUNC(Write) + SIGN_FUNC(Dup) + SIGN_FUNC(Fcntl) + SIGN_FUNC(Opendir) + SIGN_FUNC(Closedir) + SIGN_FUNC(Fdopendir) + SIGN_FUNC(Scandir) + SIGN_FUNC(Seekdir) + SIGN_FUNC(Readdir) + SIGN_FUNC(Dirfd) + SIGN_FUNC(Rewinddir) + SIGN_FUNC(Telldir) +#undef SIGN_FUNC + + // Modelling functions + void AccessFileAux(CheckerContext &C, const CallExpr *CE, int ArgNo) const; + void CloseFileAux(CheckerContext &C, const CallExpr *CE, int ArgNo) const; + void SeekFileAux(CheckerContext &C, const CallExpr *CE) const; + void DescriptorOpenAux(CheckerContext &C, const CallExpr *CE, int ArgNo) + const; + void OpenFileAux(CheckerContext &C, const CallExpr *CE, + ProgramStateRef state = 0, SymbolRef Clone = 0) const; + void ReopenFile(CheckerContext &C, const CallExpr *CE, + ProgramStateRef state, const Expr *Arg) const; + + // Checking functions + void CheckNullStream(SVal SV, ProgramStateRef state, CheckerContext &C, + const Expr *Arg) const; + void CheckCloseUnderlying(SVal SV, CheckerContext &C, + ProgramStateRef state = 0) const; + void CheckAlreadyClosed(SVal SV, ProgramStateRef state, + CheckerContext &C, bool isClose = false) const; + void CheckAlreadyClosed(SymbolRef Sym, ProgramStateRef state, + CheckerContext &C, bool isClose = false) const; + void CheckSeekWhence(CheckerContext &C, const SVal &Whence) const; + + // Helper functions + const ExplodedNode *getAllocationSite(const ExplodedNode *N, SymbolRef Sym, + CheckerContext &C) const; + + class StreamBugVisitor : public BugReporterVisitorImpl { + protected: + // The opened descriptor symbol tracked by the main analysis. + SymbolRef Sym; + const bool IsLeak; + + public: + + StreamBugVisitor(SymbolRef S, bool isLeak = false) + : Sym(S), IsLeak(isLeak) {} + virtual ~StreamBugVisitor() {} + + void Profile(llvm::FoldingSetNodeID &ID) const { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Sym); + } + + inline bool isOpened(const StreamState *S, const StreamState *SPrev, + const Stmt *Stmt) { + // Did not track -> opened. Other state (released) -> opened. + return (Stmt && isa(Stmt) && + (S && S->isOpened()) && (!SPrev || !SPrev->isOpened())); + } + + inline bool isReleased(const StreamState *S, const StreamState *SPrev, + const Stmt *Stmt) { + // Did not track -> released. Other state (opened) -> released. + return (Stmt && isa(Stmt) && + (S && S->isClosedProperly()) && + (!SPrev || !SPrev->isClosedProperly())); + } + + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR); + + PathDiagnosticPiece* getEndPath(BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + BugReport &BR) { + if (!IsLeak) + return 0; + + PathDiagnosticLocation L = + PathDiagnosticLocation::createEndOfPath(EndPathNode, + BRC.getSourceManager()); + // Do not add the statement itself as a range in case of leak. + return new PathDiagnosticEventPiece(L, BR.getDescription(), false); + } +}; + }; } // end anonymous namespace REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) - +REGISTER_MAP_WITH_PROGRAMSTATE(AssociatedStreamMap, SymbolRef, SymbolRef) bool StreamChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD || FD->getKind() != Decl::Function) return false; - ASTContext &Ctx = C.getASTContext(); - if (!II_fopen) - II_fopen = &Ctx.Idents.get("fopen"); - if (!II_tmpfile) - II_tmpfile = &Ctx.Idents.get("tmpfile"); - if (!II_fclose) - II_fclose = &Ctx.Idents.get("fclose"); - if (!II_fread) - II_fread = &Ctx.Idents.get("fread"); - if (!II_fwrite) - II_fwrite = &Ctx.Idents.get("fwrite"); - if (!II_fseek) - II_fseek = &Ctx.Idents.get("fseek"); - if (!II_ftell) - II_ftell = &Ctx.Idents.get("ftell"); - if (!II_rewind) - II_rewind = &Ctx.Idents.get("rewind"); - if (!II_fgetpos) - II_fgetpos = &Ctx.Idents.get("fgetpos"); - if (!II_fsetpos) - II_fsetpos = &Ctx.Idents.get("fsetpos"); - if (!II_clearerr) - II_clearerr = &Ctx.Idents.get("clearerr"); - if (!II_feof) - II_feof = &Ctx.Idents.get("feof"); - if (!II_ferror) - II_ferror = &Ctx.Idents.get("ferror"); - if (!II_fileno) - II_fileno = &Ctx.Idents.get("fileno"); - - if (FD->getIdentifier() == II_fopen) { - Fopen(C, CE); - return true; - } - if (FD->getIdentifier() == II_tmpfile) { - Tmpfile(C, CE); - return true; - } - if (FD->getIdentifier() == II_fclose) { - Fclose(C, CE); - return true; - } - if (FD->getIdentifier() == II_fread) { - Fread(C, CE); - return true; - } - if (FD->getIdentifier() == II_fwrite) { - Fwrite(C, CE); - return true; - } - if (FD->getIdentifier() == II_fseek) { - Fseek(C, CE); - return true; - } - if (FD->getIdentifier() == II_ftell) { - Ftell(C, CE); - return true; - } - if (FD->getIdentifier() == II_rewind) { - Rewind(C, CE); - return true; - } - if (FD->getIdentifier() == II_fgetpos) { - Fgetpos(C, CE); - return true; - } - if (FD->getIdentifier() == II_fsetpos) { - Fsetpos(C, CE); + const IdentifierInfo *II = FD->getIdentifier(); + if (!II) + return false; + StringRef Name = C.getCalleeName(FD); + if (Name.empty()) + return false; + typedef void (StreamChecker::*ModelFunction)(CheckerContext &C, + const CallExpr *CE) const; + ModelFunction evalFunction = llvm::StringSwitch(Name) + .Case("fopen", &StreamChecker::Fopen) + .Case("fclose", &StreamChecker::Fclose) + .Case("tmpfile", &StreamChecker::Tmpfile) + .Case("fdopen", &StreamChecker::Fdopen) + .Case("freopen", &StreamChecker::Freopen) + .Case("fread", &StreamChecker::Fread) + .Case("fwrite", &StreamChecker::Fwrite) + .Case("fscanf", &StreamChecker::Fscanf) + .Case("fprintf", &StreamChecker::Fprintf) + .Case("fseek", &StreamChecker::Fseek) + .Case("lseek", &StreamChecker::Lseek) + .Case("fstat", &StreamChecker::Fstat) + .Case("ftell", &StreamChecker::Ftell) + .Case("rewind", &StreamChecker::Rewind) + .Case("fgetpos", &StreamChecker::Fgetpos) + .Case("fsetpos", &StreamChecker::Fsetpos) + .Case("clearerr", &StreamChecker::Clearerr) + .Case("feof", &StreamChecker::Feof) + .Case("ferror", &StreamChecker::Ferror) + .Case("fileno", &StreamChecker::Fileno) + .Case("fflush", &StreamChecker::Fflush) + .Case("open", &StreamChecker::Open) + .Case("close", &StreamChecker::Close) + .Case("read", &StreamChecker::Read) + .Case("write", &StreamChecker::Write) + .Case("dup", &StreamChecker::Dup) + .Case("fcntl", &StreamChecker::Fcntl) + .Case("opendir", &StreamChecker::Opendir) + .Case("closedir", &StreamChecker::Closedir) + .Case("fdopendir", &StreamChecker::Fdopendir) + .Case("scandir", &StreamChecker::Scandir) + .Case("seekdir", &StreamChecker::Seekdir) + .Case("readdir", &StreamChecker::Readdir) + .Case("dirfd", &StreamChecker::Dirfd) + .Case("rewinddir", &StreamChecker::Rewinddir) + .Case("telldir", &StreamChecker::Telldir) + .Default(0); + if (evalFunction) { + (this->*evalFunction)(C, CE); return true; } - if (FD->getIdentifier() == II_clearerr) { - Clearerr(C, CE); - return true; + return false; +} + +void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE, + ProgramStateRef state, SymbolRef Clone) const { + if (!state) + state = C.getState(); + SValBuilder &svalBuilder = C.getSValBuilder(); + const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); + DefinedSVal RetVal = svalBuilder.conjureSymbolVal(0, CE, LCtx, C.blockCount()) + .castAs(); + SymbolRef Sym = RetVal.getAsSymbol(); + if (!Sym) + return; + state = state->BindExpr(CE, C.getLocationContext(), RetVal); + + ConstraintManager &CM = C.getConstraintManager(); + ProgramStateRef stateValid, stateInvalid; + + if (Optional FD = RetVal.getAs()) { + // integer descriptor (open/dup) + // Bifurcate the state into two: one with a valid (non-negative) + // descriptor, the other with a negative descriptor. + NonLoc MinusOne = svalBuilder.makeIntVal(-1, false); + SVal lowerBound = svalBuilder.evalBinOpNN(state, BO_EQ, *FD, MinusOne, + svalBuilder.getConditionType()); + Optional IsMinusOne = lowerBound.getAs(); + if (!IsMinusOne) + return; + stateInvalid = state->assume(*IsMinusOne, true); + // 0, 1 and 2 are standard descriptors and cannot be returned in open() + Optional TwoVal = svalBuilder.makeIntVal(2, false); + SVal NonNegative = svalBuilder.evalBinOpNN(state, BO_GT, *FD, *TwoVal, + svalBuilder.getConditionType()); + Optional IsNonNegative = NonNegative.getAs(); + if (!IsNonNegative) + return; + stateValid = state->assume(*IsNonNegative, true); + } else { // If return type is FILE* or DIR* + // Bifurcate the state into two: one with a valid pointer, the other + // with a NULL. + std::tie(stateValid, stateInvalid) = CM.assumeDual(state, RetVal); } - if (FD->getIdentifier() == II_feof) { - Feof(C, CE); - return true; + + // if RetVal is not NULL (or not -1), set the symbol's state to Opened. + if (!Clone) + stateValid = stateValid->set(Sym, StreamState::getOpened()); + else { + // or succesfully clone it and associate descriptors + stateValid = stateValid->set(Sym, Clone); + stateValid = stateValid->set(Clone, Sym); + stateValid = stateValid->set(Clone, StreamState::getCloned()); + stateValid = stateValid->set(Sym, StreamState::getCloned()); } - if (FD->getIdentifier() == II_ferror) { - Ferror(C, CE); - return true; + stateInvalid = stateInvalid->set(Sym, + StreamState::getOpenFailed()); + C.addTransition(stateValid); + C.addTransition(stateInvalid); +} + +void StreamChecker::ReopenFile(CheckerContext &C, const CallExpr *CE, + ProgramStateRef state, const Expr* Arg) const { + SValBuilder &svalBuilder = C.getSValBuilder(); + SVal ArgVal = state->getSVal(Arg, C.getLocationContext()); + SVal ZeroVal = svalBuilder.makeZeroVal(CE->getType()); + // Return 0 if freopen failed and Arg if it succeed + ProgramStateRef stateValid = state->BindExpr(CE, C.getLocationContext(), + ArgVal); + ProgramStateRef stateInvalid = state->BindExpr(CE, C.getLocationContext(), + ZeroVal); + stateValid = stateValid->set(ArgVal.getAsSymbol(), + StreamState::getOpened()); + C.addTransition(stateValid); + C.addTransition(stateInvalid); +} + +void StreamChecker::AccessFileAux(CheckerContext &C, const CallExpr *CE, + int ArgNo) const { + ProgramStateRef state = C.getState(); + SVal SV = state->getSVal(CE->getArg(ArgNo), C.getLocationContext()); + CheckAlreadyClosed(SV, state, C); + CheckNullStream(SV, state, C, CE->getArg(ArgNo)); +} + +void StreamChecker::CloseFileAux(CheckerContext &C, const CallExpr *CE, + int ArgNo) const { + ProgramStateRef state = C.getState(); + SVal SV = state->getSVal(CE->getArg(ArgNo), C.getLocationContext()); + CheckNullStream(SV, state, C, CE->getArg(ArgNo)); + CheckCloseUnderlying(SV, C, state); + SymbolRef Sym = SV.getAsSymbol(); + if (!Sym) + return; + const SymbolRef *FD = state->get(Sym); + if (FD) { + CheckAlreadyClosed(*FD, state, C, true); + state = state->set(*FD, StreamState::getClosed()); } - if (FD->getIdentifier() == II_fileno) { - Fileno(C, CE); - return true; + // Close the File Descriptor. + CheckAlreadyClosed(Sym, state, C, true); + if (SV.getAs()) { // integer descriptor + ProgramStateRef stateClosed, stateCloseFailed; + SValBuilder &svalBuilder = C.getSValBuilder(); + const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); + DefinedSVal RetVal = svalBuilder.conjureSymbolVal(0, CE, LCtx, + C.blockCount()) + .castAs(); + Optional FD = RetVal.getAs(); + // integer descriptor (open/dup) + // Bifurcate the state into two: one with a closed descriptor, + // the other with descriptor fail to close. + SymbolRef RetSym = RetVal.getAsSymbol(); + state = state->BindExpr(CE, C.getLocationContext(), RetVal); + if (!RetSym) + return; + + NonLoc MinusOne = svalBuilder.makeIntVal(-1, false); + SVal lowerBound = svalBuilder.evalBinOpNN(state, BO_EQ, *FD, MinusOne, + svalBuilder.getConditionType()); + Optional IsMinusOne = lowerBound.getAs(); + if (!IsMinusOne) + return; + // According to man page, close() can return -1 or 0 and nothing else + stateCloseFailed = state->assume(*IsMinusOne, true); + stateClosed = state->assume(RetVal, false); + stateCloseFailed = stateCloseFailed->set(Sym, + StreamState::getCloseFailed()); + stateClosed = stateClosed->set(Sym, StreamState::getClosed()); + C.addTransition(stateClosed); + C.addTransition(stateCloseFailed); + } else { + // Pointer descriptor - just close it + state = state->set(Sym, StreamState::getClosed()); + C.addTransition(state); } +} - return false; +void StreamChecker::SeekFileAux(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); + ProgramStateRef state = C.getState(); + SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); + CheckSeekWhence(C, Whence); +} + +void StreamChecker::DescriptorOpenAux(CheckerContext &C, const CallExpr *CE, + int ArgNo) const { + AccessFileAux(C, CE, ArgNo); + ProgramStateRef state = C.getState(); + SVal fd = state->getSVal(CE->getArg(0), C.getLocationContext()); + SymbolRef fdSym = fd.getAsSymbol(); + OpenFileAux(C, CE, state, fdSym); } void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const { @@ -206,191 +415,280 @@ OpenFileAux(C, CE); } -void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::Fdopen(CheckerContext &C, const CallExpr *CE) const { + DescriptorOpenAux(C, CE, 0); +} + +void StreamChecker::Freopen(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - SValBuilder &svalBuilder = C.getSValBuilder(); - const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - DefinedSVal RetVal = svalBuilder.conjureSymbolVal(0, CE, LCtx, C.blockCount()) - .castAs(); - state = state->BindExpr(CE, C.getLocationContext(), RetVal); - - ConstraintManager &CM = C.getConstraintManager(); - // Bifurcate the state into two: one with a valid FILE* pointer, the other - // with a NULL. - ProgramStateRef stateNotNull, stateNull; - std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); - - if (SymbolRef Sym = RetVal.getAsSymbol()) { - // if RetVal is not NULL, set the symbol's state to Opened. - stateNotNull = - stateNotNull->set(Sym,StreamState::getOpened(CE)); - stateNull = - stateNull->set(Sym, StreamState::getOpenFailed(CE)); - - C.addTransition(stateNotNull); - C.addTransition(stateNull); + // Check if first argument (file path) is NULL or not + SVal SV = state->getSVal(CE->getArg(0), C.getLocationContext()); + if (state->isNull(SV).isConstrainedTrue()) { + // If so, we are changing mode of an existing stream so it should be opened + AccessFileAux(C, CE, 2); } + ReopenFile(C, CE, state, CE->getArg(2)); } void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C); - if (state) - C.addTransition(state); + CloseFileAux(C, CE, 0); } void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 3); } void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(3), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 3); } -void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::Fscanf(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::Fprintf(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::Fflush(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); - if (!(state = CheckNullStream(state->getSVal(CE->getArg(0), - C.getLocationContext()), state, C))) - return; - // Check the legality of the 'whence' argument of 'fseek'. - SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); - Optional CI = Whence.getAs(); + SVal SV = state->getSVal(CE->getArg(0), C.getLocationContext()); + if (SV.isZeroConstant()) + return; // fflush(NULL) just flushes all open streams + AccessFileAux(C, CE, 0); +} - if (!CI) - return; +void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { + SeekFileAux(C, CE); +} - int64_t x = CI->getValue().getSExtValue(); - if (x >= 0 && x <= 2) - return; +void StreamChecker::Lseek(CheckerContext &C, const CallExpr *CE) const { + SeekFileAux(C, CE); +} - if (ExplodedNode *N = C.addTransition(state)) { - if (!BT_illegalwhence) - BT_illegalwhence.reset( - new BuiltinBug(this, "Illegal whence argument", - "The whence argument to fseek() should be " - "SEEK_SET, SEEK_END, or SEEK_CUR.")); - BugReport *R = new BugReport(*BT_illegalwhence, - BT_illegalwhence->getDescription(), N); - C.emitReport(R); - } +void StreamChecker::Fstat(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); } void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 0); } void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 0); } void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 0); } void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 0); } void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 0); } void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 0); } void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) - return; + AccessFileAux(C, CE, 0); } void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { + DescriptorOpenAux(C, CE, 0); +} + +void StreamChecker::Open(CheckerContext &C, const CallExpr *CE) const { + OpenFileAux(C, CE); +} + +void StreamChecker::Close(CheckerContext &C, const CallExpr *CE) const { + CloseFileAux(C, CE, 0); +} + +void StreamChecker::Read(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::Write(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::Dup(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); + OpenFileAux(C, CE); +} + +void StreamChecker::Fcntl(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); ProgramStateRef state = C.getState(); - if (!CheckNullStream(state->getSVal(CE->getArg(0), C.getLocationContext()), - state, C)) + // Check if the second argument is F_DUPFD or F_DUPFD_CLOEXEC + SVal SV = state->getSVal(CE->getArg(1), C.getLocationContext()); + Optional CI = SV.getAs(); + if (!CI) return; + int64_t x = CI->getValue().getSExtValue(); + // F_DUPFD is 0, F_DUPFD_CLOEXEC is 1030 (according to Linux headers) + // TODO: Maybe it should be replaced with macro name comparison? + if (x == 0 || x == 1030) + OpenFileAux(C, CE); +} + +void StreamChecker::Opendir(CheckerContext &C, const CallExpr *CE) const { + OpenFileAux(C, CE); +} + +void StreamChecker::Fdopendir(CheckerContext &C, const CallExpr *CE) const { + DescriptorOpenAux(C, CE, 0); +} + +void StreamChecker::Closedir(CheckerContext &C, const CallExpr *CE) const { + CloseFileAux(C, CE, 0); +} + +void StreamChecker::Scandir(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::Seekdir(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::Readdir(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::Dirfd(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::Rewinddir(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); } -ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, - CheckerContext &C) const { +void StreamChecker::Telldir(CheckerContext &C, const CallExpr *CE) const { + AccessFileAux(C, CE, 0); +} + +void StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, + CheckerContext &C, const Expr *Arg) const { Optional DV = SV.getAs(); if (!DV) - return 0; + return; ConstraintManager &CM = C.getConstraintManager(); - ProgramStateRef stateNotNull, stateNull; - std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); - - if (!stateNotNull && stateNull) { - if (ExplodedNode *N = C.generateSink(stateNull)) { - if (!BT_nullfp) - BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", - "Stream pointer might be NULL.")); - BugReport *R =new BugReport(*BT_nullfp, BT_nullfp->getDescription(), N); + SValBuilder &svalBuilder = C.getSValBuilder(); + ProgramStateRef stateValid, stateInvalid; + Optional FD = SV.getAs(); + + // If return type is FILE* or DIR* + if (!FD) { + // Bifurcate the state into two: one with a valid pointer, the other + // with a NULL. + std::tie(stateValid, stateInvalid) = CM.assumeDual(state, *DV); + } else { + // Check if integer descriptor (open/dup) is -1 (in case of error) + NonLoc MinusOne = svalBuilder.makeIntVal(-1, false); + if (Optional FD = DV.getValue().getAs()) { + SVal lowerBound = svalBuilder.evalBinOpNN(state, BO_EQ, *FD, MinusOne, + svalBuilder.getConditionType()); + + Optional IsMinusOne = lowerBound.getAs(); + if (!IsMinusOne) + return; + std::tie(stateInvalid, stateValid) = state->assume(*IsMinusOne); + } + } + + if (!stateValid && stateInvalid) { + BugReport *R; + if (ExplodedNode *N = C.generateSink(stateInvalid)) { + if (!FD) { + if (!BT_nullfp) + BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", + "Stream pointer might be NULL.")); + R = new BugReport(*BT_nullfp, BT_nullfp->getDescription(), N); + } else { + if (!BT_negativefd) + BT_negativefd.reset(new BuiltinBug(this, "Illegal file descriptor", + "File descriptor can be negative.")); + R = new BugReport(*BT_negativefd, BT_negativefd->getDescription(), N); + } + bugreporter::trackNullOrUndefValue(N, Arg, *R, true); + R->markInteresting(SV); C.emitReport(R); } - return 0; } - return stateNotNull; } -ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, - ProgramStateRef state, - CheckerContext &C) const { - SymbolRef Sym = - state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol(); - if (!Sym) - return state; - +void StreamChecker::CheckAlreadyClosed(SVal SV, ProgramStateRef state, + CheckerContext &C, bool isClose) const { + SymbolRef Sym = SV.getAsSymbol(); + if (Sym) + CheckAlreadyClosed(Sym, state, C, isClose); +} + +void StreamChecker::CheckAlreadyClosed(SymbolRef Sym, ProgramStateRef state, + CheckerContext &C, bool isClose) const { const StreamState *SS = state->get(Sym); // If the file stream is not tracked, return. if (!SS) - return state; - - // Check: Double close a File Descriptor could cause undefined behaviour. - // Conforming to man-pages - if (SS->isClosed()) { - ExplodedNode *N = C.generateSink(); + return; + // Check: Access a File Descriptor after it being closed could cause + // undefined behaviour. + if (SS->isClosedProperly()) { + ExplodedNode *N = C.generateSink(state); if (N) { - if (!BT_doubleclose) - BT_doubleclose.reset(new BuiltinBug( - this, "Double fclose", "Try to close a file Descriptor already" - " closed. Cause undefined behaviour.")); - BugReport *R = new BugReport(*BT_doubleclose, - BT_doubleclose->getDescription(), N); + BugReport *R; + if (isClose) { + if (!BT_doubleclose) + BT_doubleclose.reset(new BuiltinBug(this, "Double close", + "Try to close a descriptor already " + "closed. Cause undefined behaviour.")); + R = new BugReport(*BT_doubleclose, + BT_doubleclose->getDescription(), N); + } else { + if (!BT_accessAfterClose) + BT_accessAfterClose.reset(new BuiltinBug(this, + "Access after closing descriptor", + "Try to access a descriptor already " + "closed. Causes undefined behaviour.")); + R = new BugReport(*BT_accessAfterClose, + BT_accessAfterClose->getDescription(), N); + } + R->markInteresting(Sym); + R->addVisitor(new StreamBugVisitor(Sym)); + C.emitReport(R); + } + } +} + +// If we have a FILE*/DIR* opened with fdopen{dir}(), we should use only +// fclose/fclosedir to close file. +void StreamChecker::CheckCloseUnderlying(SVal SV, CheckerContext &C, + ProgramStateRef state) const { + SymbolRef Sym = SV.getAsSymbol(); + if (!Sym) + return; + const SymbolRef *FD = state->get(Sym); + if (FD && SV.getAs()) { + ExplodedNode *N = C.generateSink(state); + if (N) { + if (!BT_closeunderlying) + BT_closeunderlying.reset(new BuiltinBug(this, "Double close", + "Closing of underlying descriptor. Cause undefined behaviour.")); + BugReport *R = new BugReport(*BT_closeunderlying, + BT_closeunderlying->getDescription(), N); + R->markInteresting(Sym); + R->addVisitor(new StreamBugVisitor(Sym)); C.emitReport(R); } - return NULL; } - - // Close the File Descriptor. - return state->set(Sym, StreamState::getClosed(CE)); } void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, @@ -408,17 +706,153 @@ ExplodedNode *N = C.generateSink(); if (N) { if (!BT_ResourceLeak) - BT_ResourceLeak.reset(new BuiltinBug( - this, "Resource Leak", - "Opened File never closed. Potential Resource leak.")); - BugReport *R = new BugReport(*BT_ResourceLeak, - BT_ResourceLeak->getDescription(), N); + BT_ResourceLeak.reset(new BuiltinBug(this, "Resource Leak", + "Opened descriptor never closed. Potential resource leak.")); + + // Most bug reports are cached at the location where they occurred. + // With leaks, we want to unique them by the location where they were + // allocated, and only report a single path. + PathDiagnosticLocation LocUsedForUniqueing; + const ExplodedNode *AllocNode = getAllocationSite(N, Sym, C); + + ProgramPoint P = AllocNode->getLocation(); + const Stmt *AllocationStmt = 0; + if (Optional Exit = P.getAs()) + AllocationStmt = Exit->getCalleeContext()->getCallSite(); + else if (Optional SP = P.getAs()) + AllocationStmt = SP->getStmt(); + if (AllocationStmt) + LocUsedForUniqueing = PathDiagnosticLocation::createBegin( + AllocationStmt, + C.getSourceManager(), + AllocNode->getLocationContext()); + + BugReport *R = new BugReport(*BT_ResourceLeak, + BT_ResourceLeak->getDescription(), N, + LocUsedForUniqueing, + AllocNode->getLocationContext()->getDecl()); + R->markInteresting(Sym); + R->addVisitor(new StreamBugVisitor(Sym, true)); C.emitReport(R); } } } } +// Check the legality of the 'whence' argument of 'fseek' and 'lseek'. +void StreamChecker::CheckSeekWhence(CheckerContext &C, + const SVal &Whence) const { + Optional CI = Whence.getAs(); + + if (!CI) + return; + + int64_t x = CI->getValue().getSExtValue(); + if (x >= 0 && x <= 2) + return; + + if (ExplodedNode *N = C.generateSink()) { + if (!BT_illegalwhence) + BT_illegalwhence.reset(new BuiltinBug(this, "Illegal whence argument", + "The whence argument to fseek() should be " + "SEEK_SET, SEEK_END, or SEEK_CUR.")); + BugReport *R = new BugReport(*BT_illegalwhence, + BT_illegalwhence->getDescription(), N); + C.emitReport(R); + } +} + +const ExplodedNode * +StreamChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym, + CheckerContext &C) const { + const LocationContext *LeakContext = N->getLocationContext(); + // Walk the ExplodedGraph backwards and find the first node that referred to + // the tracked symbol. + const ExplodedNode *AllocNode = N; + const MemRegion *ReferenceRegion = 0; + + while (N) { + ProgramStateRef State = N->getState(); + if (!State->get(Sym)) + break; + + // Find the most recent expression bound to the symbol in the current + // context. + if (!ReferenceRegion) { + if (const MemRegion *MR = C.getLocationRegionIfPostStore(N)) { + SVal Val = State->getSVal(MR); + if (Val.getAsLocSymbol() == Sym) { + const VarRegion* VR = MR->getBaseRegion()->getAs(); + // Do not show local variables belonging to a function other than + // where the error is reported. + if (!VR || VR->getStackFrame() == LeakContext->getCurrentStackFrame()) + ReferenceRegion = MR; + } + } + } + + // Allocation node, is the last node in the current context in which the + // symbol was tracked. + if (N->getLocationContext() == LeakContext) + AllocNode = N; + N = N->pred_empty() ? NULL : *(N->pred_begin()); + } + + return AllocNode; +} + void ento::registerStreamChecker(CheckerManager &mgr) { mgr.registerChecker(); } + +PathDiagnosticPiece * +StreamChecker::StreamBugVisitor::VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) { + ProgramStateRef state = N->getState(); + ProgramStateRef statePrev = PrevN->getState(); + + const StreamState *RS = state->get(Sym); + if (!RS) + return 0; + const StreamState *RSPrev = statePrev->get(Sym); + + const Stmt *S = 0; + const char *Msg = 0; + StackHintGeneratorForSymbol *StackHint = 0; + + // Retrieve the associated statement. + ProgramPoint ProgLoc = N->getLocation(); + if (Optional SP = ProgLoc.getAs()) { + S = SP->getStmt(); + } else if (Optional Exit = ProgLoc.getAs()) { + S = Exit->getCalleeContext()->getCallSite(); + } else if (Optional Edge = ProgLoc.getAs()) { + // If an assumption was made on a branch, it should be caught + // here by looking at the state transition. + S = Edge->getSrc()->getTerminator(); + } + + if (!S) + return 0; + + if (isOpened(RS, RSPrev, S)) { + Msg = "Descriptor is opened"; + StackHint = new StackHintGeneratorForSymbol(Sym, + "Returned opened descriptor"); + } else if (isReleased(RS, RSPrev, S)) { + Msg = "Descriptor is closed"; + StackHint = new StackHintGeneratorForSymbol(Sym, + "Returning; descriptor was closed"); + } + + if (!Msg) + return 0; + assert(StackHint); + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return new PathDiagnosticEventPiece(Pos, Msg, true, StackHint); +} Index: test/Analysis/stream.c =================================================================== --- test/Analysis/stream.c +++ test/Analysis/stream.c @@ -2,16 +2,40 @@ typedef __typeof__(sizeof(int)) size_t; typedef struct _IO_FILE FILE; -#define SEEK_SET 0 /* Seek from beginning of file. */ -#define SEEK_CUR 1 /* Seek from current position. */ -#define SEEK_END 2 /* Seek from end of file. */ +typedef struct _IO_DIR DIR; +typedef struct _IO_DIRENTRY dirent; +#define SEEK_SET 0 /* Seek from beginning of file. */ +#define SEEK_CUR 1 /* Seek from current position. */ +#define SEEK_END 2 /* Seek from end of file. */ +#define O_RDONLY 0 /* Open file for reading only. */ +#define O_CREAT 4 /* Create file if it is not exist */ +#define F_GETOWN 5 /* Get SIGIO/SIGURG proc/pgrp */ +#define F_DUPFD 0 /* Duplicate descriptor */ +#define F_DUPFD_CLOEXEC 1030 /* Duplicate descriptor with close-on-exec flag */ extern FILE *fopen(const char *path, const char *mode); extern FILE *tmpfile(void); extern int fclose(FILE *fp); extern size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); extern int fseek (FILE *__stream, long int __off, int __whence); +extern int fprintf(FILE *stream, const char *format, ...); extern long int ftell (FILE *__stream); extern void rewind (FILE *__stream); +extern int write(int fd, const void *buf, size_t count); +extern int dup (int oldfd); +extern FILE *fdopen(int fd, const char *mode); +extern FILE *freopen(const char *path, const char *mode, FILE *stream); +extern int fileno(FILE *stream); +extern DIR *opendir(const char *path); +extern int closedir(DIR *dp); +extern int telldir(DIR *fp); +extern int seekdir(DIR *fp); +extern dirent *readdir(DIR *dirp); +extern DIR *fdopendir(int fd, const char *mode); +extern int open(const char *path, int mode); +extern int close(int fd); +extern int fcntl(int fd, int cmd, ...); +extern int lseek(int fd, long int __off, int __whence); +extern int read(int fd, void *buf, size_t count); void f1(void) { FILE *p = fopen("foo", "r"); @@ -49,8 +73,10 @@ void f6(void) { FILE *p = fopen("foo", "r"); - fclose(p); - fclose(p); // expected-warning {{Try to close a file Descriptor already closed. Cause undefined behaviour}} + if (p) { + fclose(p); + fclose(p); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} + } } void f7(void) { @@ -61,9 +87,10 @@ void f8(int c) { FILE *p = fopen("foo.c", "r"); - if(c) - return; // expected-warning {{Opened File never closed. Potential Resource leak}} - fclose(p); + if (c) + return; // expected-warning {{Opened descriptor never closed. Potential resource leak}} + if (p) + fclose(p); } FILE *f9(void) { @@ -74,6 +101,388 @@ return 0; } +void f10(FILE *fp) { + fclose(fp); + fclose(fp); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} +} + +void f11(void) { + int fd = open("foo", O_RDONLY); + char buf[1024]; + read(fd, buf, 1); // expected-warning {{File descriptor can be negative}} + close(fd); +} + +void f12(void) { + int fd = open("foo", O_RDONLY); + lseek(fd, 1, SEEK_SET); // expected-warning {{File descriptor can be negative}} + close(fd); +} + +void f13(void) { + int fd = open("foo", O_RDONLY); + fcntl(fd, F_GETOWN); // expected-warning {{File descriptor can be negative}} + close(fd); +} + +void f14(int fd) { + FILE *p = fdopen(fd, "r"); + rewind(p); // expected-warning {{Stream pointer might be NULL}} + fclose(p); +} +void f15(void) { + DIR *p = opendir("foo"); + readdir(p); // expected-warning {{Stream pointer might be NULL}} + closedir(p); +} + +void f16(void) { + DIR *p = opendir("foo"); + telldir(p); // expected-warning {{Stream pointer might be NULL}} + closedir(p); +} + +void f17(void) { + DIR *p = opendir("foo"); + seekdir(p); // expected-warning {{Stream pointer might be NULL}} + closedir(p); +} + +void f18(int fd) { + close(fd); + close(fd); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} +} + +void f19(void) { + int fd = open("foo", O_RDONLY); + if (fd != -1) { + close(fd); + close(fd); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} + } +} + +void f20(DIR *dp) { + closedir(dp); + closedir(dp); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} +} + +void f21(void) { + DIR *dp = opendir("foo"); + if (dp) { + closedir(dp); + closedir(dp); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} + } +} + +void f22(void) { + FILE *fp1 = fopen("foo", "r"); + if (fp1) { + FILE *fp2 = freopen(0, "w", fp1); + if (fp2) + fclose(fp2); // no-warning + else + fclose(fp1); + } +} + +void f23(void) { + FILE *fp1 = fopen("foo", "r"); + FILE *fp2 = freopen(0, "w", fp1); // expected-warning {{Stream pointer might be NULL}} // expected-warning {{Opened descriptor never closed. Potential resource leak}} + fclose(fp2); // expected-warning {{Stream pointer might be NULL}} +} + +void f24() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + FILE *f = fdopen(fd, "r"); + if (f) { + fprintf(f, "1"); + fclose(f); + } + close(fd); // expected-warning {{Closing of underlying descriptor. Cause undefined behaviour}} + } +} + +void f25() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + FILE *f = fdopen(fd, "r"); // expected-warning {{Opened descriptor never closed. Potential resource leak}} + if (f) { + fprintf(f, "1"); + fclose(f); // no-warning + } + } +} + +void f26() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + DIR *dp = fdopendir(fd, "r"); // expected-warning {{Opened descriptor never closed. Potential resource leak}} + if (dp) { + telldir(dp); + closedir(dp); // no-warning + } + } // no-warning +} + +void f27() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + DIR *dp = fdopendir(fd, "r"); + if (dp) { + telldir(dp); + closedir(dp); + } + close(fd); // expected-warning {{Closing of underlying descriptor. Cause undefined behaviour}} + } +} + +void f28() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + FILE *f = fdopen(fd, "r"); + if (f) { + fprintf(f, "1"); + } + close(fd); // expected-warning {{Closing of underlying descriptor. Cause undefined behaviour}} + } +} + +void f29(void) { + FILE *p = fopen("foo", "r"); + char buf[1024]; + if (p) { + fclose(p); + fread(buf, 1, 1, p); // expected-warning {{Try to access a descriptor already closed. Causes undefined behaviour}} + } +} + +void f30(void) { + FILE *p = fopen("foo", "r"); + if (p) { + fclose(p); + fseek(p, 1, SEEK_SET); // expected-warning {{Try to access a descriptor already closed. Causes undefined behaviour}} + } +} + +void f31(void) { + FILE *p = fopen("foo", "r"); + if (p) { + fclose(p); + ftell(p); // expected-warning {{Try to access a descriptor already closed. Causes undefined behaviour}} + } +} + +void f32(void) { + FILE *p = tmpfile(); + if (p) { + fclose(p); + ftell(p); // expected-warning {{Try to access a descriptor already closed. Causes undefined behaviour}} + } +} + +void f33(void) { + int fd = open("foo", O_CREAT); + char buf[1024]; + if (fd != -1) { + close(fd); + write(fd, buf, 1); // expected-warning {{Try to access a descriptor already closed. Causes undefined behaviour}} + } +} + +void f34(void) { + int fd = open("foo", O_RDONLY); + if (fd != -1) { + close(fd); + lseek(fd, 1, SEEK_SET); // expected-warning {{Try to access a descriptor already closed. Causes undefined behaviour}} + } +} + +void f35(void) { + int fd = open("foo", O_RDONLY); + if (fd != -1) { + int copy = dup(fd); + if (copy != -1) { + close(copy); // no-warning + } + close(fd); // no-warning + } +} + +void f36(void) { + int fd = open("foo", O_RDONLY); + if (fd != -1) { + int copy = dup(fd); + close(fd); // expected-warning {{Opened descriptor never closed. Potential resource leak}} + } +} + +void f37(void) { + int fd = open("foo", O_RDONLY); + if (fd != -1) { + int copy = fcntl(fd, F_DUPFD); + if (copy != -1) { + close(copy); // no-warning + } + close(fd); // no-warning + } +} + +void f38(void) { + int fd = open("foo", O_RDONLY); + if (fd != -1) { + int copy = fcntl(fd, F_DUPFD); + close(fd); // expected-warning {{Opened descriptor never closed. Potential resource leak}} + } +} + +void f39(void) { + FILE *p = fopen("foo", "r"); + if (p) { + int fd = fileno(p); + if (fd != -1) { + close(fd); // expected-warning {{Closing of underlying descriptor. Cause undefined behaviour}} + } + fclose(p); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} + } +} + +void f40(void) { + FILE *p = fopen("foo", "r"); + if (p) { + int fd = fileno(p); + fclose(p); // no-warning + } // no-warning +} + +void f41() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + FILE *f = fdopen(fd, "r"); + if (f) { + fprintf(f, "1"); + fclose(f); // no-warning + } else { + close(fd); + } + } // no-warning +} + +void f42() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + while (!close(fd)); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} + } // no-warning +} + +void f43() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + while (close(fd)); // no-warning + } // no-warning +} + +void f44() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + while (close(fd) != -1); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} + } // no-warning +} + +void f45() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + while (close(fd) == -1); // no-warning + } // no-warning +} + +void f46() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + while (close(fd) == 2); // no-warning + } // no-warning +} + +void f47() { + int fd = open("foo", O_CREAT); + if (fd != -1) { + while (close(fd) != 2); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} + } // no-warning +} + +void f48() { + FILE *f = fopen("foo", "r"); + if (f != 0) { + while (fclose(f) != 0); // expected-warning {{Try to close a descriptor already closed. Cause undefined behaviour}} + } // no-warning +} + +void f49(void) { + int fd = open("foo", O_RDONLY); + if (fd > -1) { + close(fd); // no-warning + } +}// no-warning + + +void f50(void) { + int fd = open("foo", O_RDONLY); + if (fd == -1) { + return; // no-warning + } + close(fd); +}// no-warning + +void f51(void) { + int fd = open("foo", O_RDONLY); + if (fd < 0) { + return; // no-warning + } + close(fd); +}// no-warning + +void f52(void) { + int fd = open("foo", O_RDONLY); + if (fd >= 0) { + return; // expected-warning {{Opened descriptor never closed. Potential resource leak}} + } + close(fd); // expected-warning {{File descriptor can be negative}} +} + +void f53(void) { + int fd = open("foo", O_RDONLY); + if (fd > 0) { + return; // expected-warning {{Opened descriptor never closed. Potential resource leak}} + } + close(fd); // expected-warning {{File descriptor can be negative}} +} + +void f54(void) { + char buf[1024]; + write(0, buf, 1); // no-warning +} // no-warning + +void f55(void) { + int fd = open("foo", O_RDONLY); + if (fd == -1) + fd = 1; + if (fd != 1) + close(fd); // no-warning +} // no-warning + +void f56(char *filename) { + FILE *f = fopen("foo", "r"); + FILE *file; + if (f) { + if ((file = freopen(filename, "w", f))) { + fprintf(f, "foo"); // no-warning + fclose(file); + return; + } + fclose(f); + } +} // no-warning + void pr7831(FILE *fp) { fclose(fp); // no-warning }