| //===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Defines a checker for proper use of fopen/fclose APIs. |
| // - If a file has been closed with fclose, it should not be accessed again. |
| // Accessing a closed file results in undefined behavior. |
| // - If a file was opened with fopen, it must be closed with fclose before |
| // the execution ends. Failing to do so results in a resource leak. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "ClangSACheckers.h" |
| #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| #include "clang/StaticAnalyzer/Core/Checker.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| |
| using namespace clang; |
| using namespace ento; |
| |
| namespace { |
| typedef SmallVector<SymbolRef, 2> SymbolVector; |
| |
| struct StreamState { |
| private: |
| enum Kind { Opened, Closed } K; |
| StreamState(Kind InK) : K(InK) { } |
| |
| public: |
| bool isOpened() const { return K == Opened; } |
| bool isClosed() const { return K == Closed; } |
| |
| static StreamState getOpened() { return StreamState(Opened); } |
| static StreamState getClosed() { return StreamState(Closed); } |
| |
| bool operator==(const StreamState &X) const { |
| return K == X.K; |
| } |
| void Profile(llvm::FoldingSetNodeID &ID) const { |
| ID.AddInteger(K); |
| } |
| }; |
| |
| class SimpleStreamChecker : public Checker<check::PostCall, |
| check::PreCall, |
| check::DeadSymbols, |
| check::PointerEscape> { |
| |
| mutable IdentifierInfo *IIfopen, *IIfclose; |
| |
| OwningPtr<BugType> DoubleCloseBugType; |
| OwningPtr<BugType> LeakBugType; |
| |
| void initIdentifierInfo(ASTContext &Ctx) const; |
| |
| void reportDoubleClose(SymbolRef FileDescSym, |
| const CallEvent &Call, |
| CheckerContext &C) const; |
| |
| void reportLeaks(SymbolVector LeakedStreams, |
| CheckerContext &C, |
| ExplodedNode *ErrNode) const; |
| |
| bool guaranteedNotToCloseFile(const CallEvent &Call) const; |
| |
| public: |
| SimpleStreamChecker(); |
| |
| /// Process fopen. |
| void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
| /// Process fclose. |
| void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
| |
| void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
| |
| /// Stop tracking addresses which escape. |
| ProgramStateRef checkPointerEscape(ProgramStateRef State, |
| const InvalidatedSymbols &Escaped, |
| const CallEvent *Call) const; |
| }; |
| |
| } // end anonymous namespace |
| |
| /// The state of the checker is a map from tracked stream symbols to their |
| /// state. Let's store it in the ProgramState. |
| REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) |
| |
| namespace { |
| class StopTrackingCallback : public SymbolVisitor { |
| ProgramStateRef state; |
| public: |
| StopTrackingCallback(ProgramStateRef st) : state(st) {} |
| ProgramStateRef getState() const { return state; } |
| |
| bool VisitSymbol(SymbolRef sym) { |
| state = state->remove<StreamMap>(sym); |
| return true; |
| } |
| }; |
| } // end anonymous namespace |
| |
| |
| SimpleStreamChecker::SimpleStreamChecker() : IIfopen(0), IIfclose(0) { |
| // Initialize the bug types. |
| DoubleCloseBugType.reset(new BugType("Double fclose", |
| "Unix Stream API Error")); |
| |
| LeakBugType.reset(new BugType("Resource Leak", |
| "Unix Stream API Error")); |
| // Sinks are higher importance bugs as well as calls to assert() or exit(0). |
| LeakBugType->setSuppressOnSink(true); |
| } |
| |
| void SimpleStreamChecker::checkPostCall(const CallEvent &Call, |
| CheckerContext &C) const { |
| initIdentifierInfo(C.getASTContext()); |
| |
| if (!Call.isGlobalCFunction()) |
| return; |
| |
| if (Call.getCalleeIdentifier() != IIfopen) |
| return; |
| |
| // Get the symbolic value corresponding to the file handle. |
| SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); |
| if (!FileDesc) |
| return; |
| |
| // Generate the next transition (an edge in the exploded graph). |
| ProgramStateRef State = C.getState(); |
| State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); |
| C.addTransition(State); |
| } |
| |
| void SimpleStreamChecker::checkPreCall(const CallEvent &Call, |
| CheckerContext &C) const { |
| initIdentifierInfo(C.getASTContext()); |
| |
| if (!Call.isGlobalCFunction()) |
| return; |
| |
| if (Call.getCalleeIdentifier() != IIfclose) |
| return; |
| |
| if (Call.getNumArgs() != 1) |
| return; |
| |
| // Get the symbolic value corresponding to the file handle. |
| SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); |
| if (!FileDesc) |
| return; |
| |
| // Check if the stream has already been closed. |
| ProgramStateRef State = C.getState(); |
| const StreamState *SS = State->get<StreamMap>(FileDesc); |
| if (SS && SS->isClosed()) { |
| reportDoubleClose(FileDesc, Call, C); |
| return; |
| } |
| |
| // Generate the next transition, in which the stream is closed. |
| State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); |
| C.addTransition(State); |
| } |
| |
| static bool isLeaked(SymbolRef Sym, const StreamState &SS, |
| bool IsSymDead, ProgramStateRef State) { |
| if (IsSymDead && SS.isOpened()) { |
| // If a symbol is NULL, assume that fopen failed on this path. |
| // A symbol should only be considered leaked if it is non-null. |
| ConstraintManager &CMgr = State->getConstraintManager(); |
| ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); |
| return !OpenFailed.isConstrainedTrue(); |
| } |
| return false; |
| } |
| |
| void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, |
| CheckerContext &C) const { |
| ProgramStateRef State = C.getState(); |
| SymbolVector LeakedStreams; |
| StreamMapTy TrackedStreams = State->get<StreamMap>(); |
| for (StreamMapTy::iterator I = TrackedStreams.begin(), |
| E = TrackedStreams.end(); I != E; ++I) { |
| SymbolRef Sym = I->first; |
| bool IsSymDead = SymReaper.isDead(Sym); |
| |
| // Collect leaked symbols. |
| if (isLeaked(Sym, I->second, IsSymDead, State)) |
| LeakedStreams.push_back(Sym); |
| |
| // Remove the dead symbol from the streams map. |
| if (IsSymDead) |
| State = State->remove<StreamMap>(Sym); |
| } |
| |
| ExplodedNode *N = C.addTransition(State); |
| reportLeaks(LeakedStreams, C, N); |
| } |
| |
| void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, |
| const CallEvent &Call, |
| CheckerContext &C) const { |
| // We reached a bug, stop exploring the path here by generating a sink. |
| ExplodedNode *ErrNode = C.generateSink(); |
| // If we've already reached this node on another path, return. |
| if (!ErrNode) |
| return; |
| |
| // Generate the report. |
| BugReport *R = new BugReport(*DoubleCloseBugType, |
| "Closing a previously closed file stream", ErrNode); |
| R->addRange(Call.getSourceRange()); |
| R->markInteresting(FileDescSym); |
| C.emitReport(R); |
| } |
| |
| void SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams, |
| CheckerContext &C, |
| ExplodedNode *ErrNode) const { |
| // Attach bug reports to the leak node. |
| // TODO: Identify the leaked file descriptor. |
| for (SmallVector<SymbolRef, 2>::iterator |
| I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) { |
| BugReport *R = new BugReport(*LeakBugType, |
| "Opened file is never closed; potential resource leak", ErrNode); |
| R->markInteresting(*I); |
| C.emitReport(R); |
| } |
| } |
| |
| bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{ |
| // If it's not in a system header, assume it might close a file. |
| if (!Call.isInSystemHeader()) |
| return false; |
| |
| // Handle cases where we know a buffer's /address/ can escape. |
| if (Call.argumentsMayEscape()) |
| return false; |
| |
| // Note, even though fclose closes the file, we do not list it here |
| // since the checker is modeling the call. |
| |
| return true; |
| } |
| |
| // If the pointer we are tracking escaped, do not track the symbol as |
| // we cannot reason about it anymore. |
| ProgramStateRef |
| SimpleStreamChecker::checkPointerEscape(ProgramStateRef State, |
| const InvalidatedSymbols &Escaped, |
| const CallEvent *Call) const { |
| // If we know that the call cannot close a file, there is nothing to do. |
| if (Call && guaranteedNotToCloseFile(*Call)) |
| return State; |
| |
| for (InvalidatedSymbols::const_iterator I = Escaped.begin(), |
| E = Escaped.end(); |
| I != E; ++I) { |
| SymbolRef Sym = *I; |
| |
| // The symbol escaped. Optimistically, assume that the corresponding file |
| // handle will be closed somewhere else. |
| State = State->remove<StreamMap>(Sym); |
| } |
| return State; |
| } |
| |
| void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const { |
| if (IIfopen) |
| return; |
| IIfopen = &Ctx.Idents.get("fopen"); |
| IIfclose = &Ctx.Idents.get("fclose"); |
| } |
| |
| void ento::registerSimpleStreamChecker(CheckerManager &mgr) { |
| mgr.registerChecker<SimpleStreamChecker>(); |
| } |